From 33406611798edcd275300b1257e75435b1743b44 Mon Sep 17 00:00:00 2001 From: theofficialomega <30985701+BellezaEmporium@users.noreply.github.com> Date: Thu, 10 Jul 2025 11:03:36 +0200 Subject: [PATCH 01/32] move html content to specific folder, remove yarn.lock --- sites/tv.dir.bg/tv.dir.bg.config.js | 136 +++++++++++++++++++--------- sites/tv.dir.bg/tv.dir.bg.test.js | 30 +++--- 2 files changed, 110 insertions(+), 56 deletions(-) diff --git a/sites/tv.dir.bg/tv.dir.bg.config.js b/sites/tv.dir.bg/tv.dir.bg.config.js index c1a15fbff..0e73bec10 100644 --- a/sites/tv.dir.bg/tv.dir.bg.config.js +++ b/sites/tv.dir.bg/tv.dir.bg.config.js @@ -1,28 +1,80 @@ const axios = require('axios') const cheerio = require('cheerio') +const url = require('url') const { DateTime } = require('luxon') +let cachedToken = null +let tokenExpiry = null + + +async function getToken() { + if (cachedToken && tokenExpiry && DateTime.now() < tokenExpiry) { + return cachedToken + } + + try { + const response = await axios.get('https://tv.dir.bg/init') + + // Check different possible locations for the token + let token = null + if (response.data && response.data.csrfToken) { + token = response.data.csrfToken + } + + if (token) { + cachedToken = token + tokenExpiry = DateTime.now().plus({ hours: 1 }) + return token + } else { + console.error('CSRF token not found in response structure:', Object.keys(response.data || {})) + return null + } + } catch (error) { + console.error('Error fetching token:', error.message) + return null + } +} + module.exports = { site: 'tv.dir.bg', days: 2, - url({ channel, date }) { - return `https://tv.dir.bg/tv_channel.php?id=${channel.site_id}&dd=${date.format('DD.MM')}` + async url({ channel, date }) { + const token = await getToken() + if (!token) { + throw new Error('Unable to retrieve CSRF token') + } + + const form = new url.URLSearchParams({ + _token: token, + channel: channel.site_id, + day: date.format('YYYY-MM-DD') + }) + + return axios.post('https://tv.dir.bg/load/programs', form.toString(), { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + 'X-Requested-With': 'XMLHttpRequest' + } + }) }, parser({ content, date }) { const programs = [] const items = parseItems(content) + items.forEach(item => { const $item = cheerio.load(item) const prev = programs[programs.length - 1] let start = parseStart($item, date) if (!start) return + if (prev) { if (start < prev.start) { start = start.plus({ days: 1 }) - date = date.add(1, 'd') + date = date.plus({ days: 1 }) } prev.stop = start } + const stop = start.plus({ minutes: 30 }) programs.push({ title: parseTitle($item), @@ -34,55 +86,57 @@ module.exports = { return programs }, async channels() { - const requests = [ - axios.get('https://tv.dir.bg/programata.php?t=0'), - axios.get('https://tv.dir.bg/programata.php?t=1') - ] - - const items = await Promise.all(requests) - .then(r => { - return r - .map(i => { - const html = i.data - const $ = cheerio.load(html) - return $('#programa-left > div > div > div > a').toArray() + try { + const response = await axios.get('https://tv.dir.bg/channels') + const $ = cheerio.load(response.data) + + const channels = [] + + $('.channel_cont').each((index, element) => { + const $element = $(element) + + const $link = $element.find('a.channel_link') + const href = $link.attr('href') + + const $img = $element.find('img') + const name = $img.attr('alt') + const logo = $img.attr('src') + + const site_id = href ? href.match(/\/programa\/(\d+)/)?.[1] : '' + + if (site_id && name) { + channels.push({ + lang: 'bg', + site_id: site_id, + name: name, + logo: logo }) - .reduce((acc, curr) => { - acc = acc.concat(curr) - return acc - }, []) + } }) - .catch(console.log) - - const $ = cheerio.load('') - return items.map(item => { - const $item = $(item) - return { - lang: 'bg', - site_id: $item.attr('href').replace('tv_channel.php?id=', ''), - name: $item.find('div.thumbnail > img').attr('alt') - } - }) + + return channels + + } catch (error) { + console.error('Error fetching channels:', error) + return [] } } +} function parseStart($item, date) { - const time = $item('i').text() - if (!time) return null - const dateString = `${date.format('MM/DD/YYYY')} ${time}` - - return DateTime.fromFormat(dateString, 'MM/dd/yyyy HH.mm', { zone: 'Europe/Sofia' }).toUTC() + const timeText = $item('.broadcast-time').text().trim() + if (!timeText) return null + + const [hours, minutes] = timeText.split(':').map(Number) + const dateTime = date.isValid ? date : DateTime.fromISO(date) + return dateTime.set({ hour: hours, minute: minutes, second: 0, millisecond: 0 }) } function parseTitle($item) { - return $item - .text() - .replace(/^\d{2}.\d{2}/, '') - .trim() + return $item('.broadcast-title').text().trim() } function parseItems(content) { const $ = cheerio.load(content) - - return $('#events > li').toArray() + return $('.broadcast-item').toArray() } diff --git a/sites/tv.dir.bg/tv.dir.bg.test.js b/sites/tv.dir.bg/tv.dir.bg.test.js index 463305447..b54e7d88c 100644 --- a/sites/tv.dir.bg/tv.dir.bg.test.js +++ b/sites/tv.dir.bg/tv.dir.bg.test.js @@ -1,23 +1,24 @@ const { parser, url } = require('./tv.dir.bg.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') dayjs.extend(customParseFormat) dayjs.extend(utc) -const date = dayjs.utc('2022-01-20', 'YYYY-MM-DD').startOf('d') +const date = dayjs.utc('2025-06-30', 'YYYY-MM-DD').startOf('d') const channel = { site_id: '12', xmltv_id: 'BTV.bg' } it('can generate valid url', () => { - expect(url({ channel, date })).toBe('https://tv.dir.bg/tv_channel.php?id=12&dd=20.01') + expect(url({ channel, date })).toBe('https://tv.dir.bg/programa/12') }) it('can parse response', () => { - const content = - '
' + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) const result = parser({ content, date }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() @@ -26,19 +27,19 @@ it('can parse response', () => { expect(result).toMatchObject([ { - start: '2022-01-20T04:00:00.000Z', - stop: '2022-01-20T13:00:00.000Z', - title: '„Тази сутрин” - информационно предаване с водещи Златимир Йочеви Биляна Гавазова' + start: '2025-06-30T08:00:00.000Z', + stop: '2025-06-30T10:00:00.000Z', + title: 'Купа на Франция: Еспали - Пари Сен Жермен' }, { - start: '2022-01-20T13:00:00.000Z', - stop: '2022-01-21T03:30:00.000Z', - title: '„Доктор Чудо” - сериал, еп.71' + start: '2025-06-30T10:00:00.000Z', + stop: '2025-06-30T12:00:00.000Z', + title: 'Ла Лига: Леганес - Реал Сосиедад' }, { - start: '2022-01-21T03:30:00.000Z', - stop: '2022-01-21T04:00:00.000Z', - title: '„Лице в лице” /п./' + start: '2025-06-30T12:00:00.000Z', + stop: '2025-06-30T13:00:00.000Z', + title: 'Пред Стадиона" - спортно шоу' } ]) }) @@ -47,8 +48,7 @@ it('can handle empty guide', () => { const result = parser({ date, channel, - content: - '
    ' + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_data.html')) }) expect(result).toMatchObject([]) }) From be7db6a324d935317c896b55717273cb44443fd4 Mon Sep 17 00:00:00 2001 From: theofficialomega <30985701+BellezaEmporium@users.noreply.github.com> Date: Thu, 10 Jul 2025 11:04:06 +0200 Subject: [PATCH 02/32] . --- .gitignore | 9 +- package-lock.json | 1549 ++-- sites/tv.dir.bg/__data__/content.html | 1 + sites/tv.dir.bg/__data__/no_data.html | 1 + sites/tvheute.at/__data__/content.html | 64 + sites/tvheute.at/__data__/no_content.html | 8 + sites/tvheute.at/tvheute.at.test.js | 10 +- yarn.lock | 8440 --------------------- 8 files changed, 843 insertions(+), 9239 deletions(-) create mode 100644 sites/tv.dir.bg/__data__/content.html create mode 100644 sites/tv.dir.bg/__data__/no_data.html create mode 100644 sites/tvheute.at/__data__/content.html create mode 100644 sites/tvheute.at/__data__/no_content.html delete mode 100644 yarn.lock diff --git a/.gitignore b/.gitignore index 01962643b..62054de41 100644 --- a/.gitignore +++ b/.gitignore @@ -5,11 +5,4 @@ /guide.xml.gz # macOS -.DS_Store - -# If Yarn is used (yarn.lock) -/.yarn/* -.yarnrc -.yarnrc.yml - -.idea \ No newline at end of file +.DS_Store \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 5de49fdf8..2384d4b7d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,26 +10,26 @@ "dependencies": { "@alex_neo/jest-expect-message": "^1.0.5", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "^9.29.0", + "@eslint/js": "^9.30.0", "@freearhey/core": "^0.8.2", "@freearhey/search-js": "^0.1.2", "@ntlab/sfetch": "^1.2.0", "@octokit/core": "^7.0.2", - "@octokit/plugin-paginate-rest": "^13.1.0", + "@octokit/plugin-paginate-rest": "^13.1.1", "@octokit/plugin-rest-endpoint-methods": "^16.0.0", - "@swc/core": "^1.12.5", + "@swc/core": "^1.12.7", "@swc/jest": "^0.2.38", "@types/cli-progress": "^3.11.6", "@types/fs-extra": "^11.0.4", "@types/inquirer": "^9.0.8", "@types/jest": "^30.0.0", "@types/langs": "^2.0.5", - "@types/lodash": "^4.17.18", + "@types/lodash": "^4.17.19", "@types/node": "^24.0.7", "@types/node-cleanup": "^2.1.5", "@types/numeral": "^2.0.5", - "@typescript-eslint/eslint-plugin": "^8.34.1", - "@typescript-eslint/parser": "^8.34.1", + "@typescript-eslint/eslint-plugin": "^8.35.0", + "@typescript-eslint/parser": "^8.35.0", "axios": "^1.10.0", "axios-cookiejar-support": "^6.0.2", "chalk": "^5.4.1", @@ -43,7 +43,7 @@ "dayjs": "^1.11.13", "epg-grabber": "^0.38.0", "epg-parser": "^0.3.1", - "eslint": "^9.29.0", + "eslint": "^9.30.0", "eslint-config-prettier": "^10.1.5", "form-data": "^4.0.3", "fs-extra": "^11.3.0", @@ -52,7 +52,7 @@ "husky": "^9.1.7", "iconv-lite": "^0.6.3", "inquirer": "^12.6.3", - "jest": "^30.0.2", + "jest": "^30.0.3", "jest-offline": "^1.0.1", "langs": "^2.0.0", "libxml2-wasm": "^0.5.0", @@ -125,30 +125,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz", - "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.7.tgz", + "integrity": "sha512-xgu/ySj2mTiUFmdE9yCMfBxLp4DHd5DwmbbD05YAuICfodYT3VvRxbrh81LGQ/8UpSdtMdfKMn3KouYDX59DGQ==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", - "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.7.tgz", + "integrity": "sha512-BU2f9tlKQ5CAthiMIgpzAh4eDTLWo1mqi9jqE2OxMG0E/OM199VJt2q8BztTxpnSW0i1ymdwLXRJnYzvDM5r2w==", "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", + "@babel/generator": "^7.27.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.4", - "@babel/parser": "^7.27.4", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.27.7", "@babel/template": "^7.27.2", - "@babel/traverse": "^7.27.4", - "@babel/types": "^7.27.3", + "@babel/traverse": "^7.27.7", + "@babel/types": "^7.27.7", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -293,12 +293,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", - "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.7.tgz", + "integrity": "sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q==", "license": "MIT", "dependencies": { - "@babel/types": "^7.27.3" + "@babel/types": "^7.27.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -544,16 +544,16 @@ } }, "node_modules/@babel/traverse": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", - "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.7.tgz", + "integrity": "sha512-X6ZlfR/O/s5EQ/SnUSLzr+6kGnkg8HXGMzpgsMsrJVcfDtH1vIp6ctCN4eZ1LS5c0+te5Cb6Y514fASjMRJ1nw==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", - "@babel/parser": "^7.27.4", + "@babel/generator": "^7.27.5", + "@babel/parser": "^7.27.7", "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3", + "@babel/types": "^7.27.7", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -571,9 +571,9 @@ } }, "node_modules/@babel/types": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", - "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.7.tgz", + "integrity": "sha512-8OLQgDScAOHXnAz2cV+RfzzNMipuLVBz2biuAJFMV9bfkNf393je3VM8CLkjQodW5+iWsSJdSgSWT6rsZoXHPw==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -1040,9 +1040,9 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz", - "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.6", @@ -1054,9 +1054,9 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.3.tgz", - "integrity": "sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", + "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1125,9 +1125,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.29.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz", - "integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==", + "version": "9.30.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.30.0.tgz", + "integrity": "sha512-Wzw3wQwPvc9sHM+NjakWTcPx11mbZyiYHuwWa/QfZ7cIRX7WK54PSk7bdyXDaoaopUcMatv1zaQvOAAO8hCdww==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1772,9 +1772,9 @@ } }, "node_modules/@jest/console/node_modules/@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==", + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", "license": "MIT" }, "node_modules/@jest/console/node_modules/chalk": { @@ -1794,9 +1794,9 @@ } }, "node_modules/@jest/core": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.0.2.tgz", - "integrity": "sha512-mUMFdDtYWu7la63NxlyNIhgnzynszxunXWrtryR7bV24jV9hmi7XCZTzZHaLJjcBU66MeUAPZ81HjwASVpYhYQ==", + "version": "30.0.3", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.0.3.tgz", + "integrity": "sha512-Mgs1N+NSHD3Fusl7bOq1jyxv1JDAUwjy+0DhVR93Q6xcBP9/bAQ+oZhXb5TTnP5sQzAHgb7ROCKQ2SnovtxYtg==", "license": "MIT", "dependencies": { "@jest/console": "30.0.2", @@ -1812,15 +1812,15 @@ "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", "jest-changed-files": "30.0.2", - "jest-config": "30.0.2", + "jest-config": "30.0.3", "jest-haste-map": "30.0.2", "jest-message-util": "30.0.2", "jest-regex-util": "30.0.1", "jest-resolve": "30.0.2", - "jest-resolve-dependencies": "30.0.2", - "jest-runner": "30.0.2", - "jest-runtime": "30.0.2", - "jest-snapshot": "30.0.2", + "jest-resolve-dependencies": "30.0.3", + "jest-runner": "30.0.3", + "jest-runtime": "30.0.3", + "jest-snapshot": "30.0.3", "jest-util": "30.0.2", "jest-validate": "30.0.2", "jest-watcher": "30.0.2", @@ -1871,9 +1871,9 @@ } }, "node_modules/@jest/core/node_modules/@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==", + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", "license": "MIT" }, "node_modules/@jest/core/node_modules/chalk": { @@ -1958,9 +1958,9 @@ } }, "node_modules/@jest/environment/node_modules/@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==", + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", "license": "MIT" }, "node_modules/@jest/environment/node_modules/chalk": { @@ -1980,22 +1980,22 @@ } }, "node_modules/@jest/expect": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.0.2.tgz", - "integrity": "sha512-blWRFPjv2cVfh42nLG6L3xIEbw+bnuiZYZDl/BZlsNG/i3wKV6FpPZ2EPHguk7t5QpLaouIu+7JmYO4uBR6AOg==", + "version": "30.0.3", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.0.3.tgz", + "integrity": "sha512-73BVLqfCeWjYWPEQoYjiRZ4xuQRhQZU0WdgvbyXGRHItKQqg5e6mt2y1kVhzLSuZpmUnccZHbGynoaL7IcLU3A==", "license": "MIT", "dependencies": { - "expect": "30.0.2", - "jest-snapshot": "30.0.2" + "expect": "30.0.3", + "jest-snapshot": "30.0.3" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.0.2.tgz", - "integrity": "sha512-FHF2YdtFBUQOo0/qdgt+6UdBFcNPF/TkVzcc+4vvf8uaBzUlONytGBeeudufIHHW1khRfM1sBbRT1VCK7n/0dQ==", + "version": "30.0.3", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.0.3.tgz", + "integrity": "sha512-SMtBvf2sfX2agcT0dA9pXwcUrKvOSDqBY4e4iRfT+Hya33XzV35YVg+98YQFErVGA/VR1Gto5Y2+A6G9LSQ3Yg==", "license": "MIT", "dependencies": { "@jest/get-type": "30.0.1" @@ -2052,9 +2052,9 @@ } }, "node_modules/@jest/fake-timers/node_modules/@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==", + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", "license": "MIT" }, "node_modules/@jest/fake-timers/node_modules/chalk": { @@ -2083,13 +2083,13 @@ } }, "node_modules/@jest/globals": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.0.2.tgz", - "integrity": "sha512-DwTtus9jjbG7b6jUdkcVdptf0wtD1v153A+PVwWB/zFwXhqu6hhtSd+uq88jofMhmYPtkmPmVGUBRNCZEKXn+w==", + "version": "30.0.3", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.0.3.tgz", + "integrity": "sha512-fIduqNyYpMeeSr5iEAiMn15KxCzvrmxl7X7VwLDRGj7t5CoHtbF+7K3EvKk32mOUIJ4kIvFRlaixClMH2h/Vaw==", "license": "MIT", "dependencies": { "@jest/environment": "30.0.2", - "@jest/expect": "30.0.2", + "@jest/expect": "30.0.3", "@jest/types": "30.0.1", "jest-mock": "30.0.2" }, @@ -2128,9 +2128,9 @@ } }, "node_modules/@jest/globals/node_modules/@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==", + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", "license": "MIT" }, "node_modules/@jest/globals/node_modules/chalk": { @@ -2235,9 +2235,9 @@ } }, "node_modules/@jest/reporters/node_modules/@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==", + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", "license": "MIT" }, "node_modules/@jest/reporters/node_modules/brace-expansion": { @@ -2394,9 +2394,9 @@ } }, "node_modules/@jest/snapshot-utils/node_modules/@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==", + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", "license": "MIT" }, "node_modules/@jest/snapshot-utils/node_modules/chalk": { @@ -2475,9 +2475,9 @@ } }, "node_modules/@jest/test-result/node_modules/@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==", + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", "license": "MIT" }, "node_modules/@jest/test-result/node_modules/chalk": { @@ -2568,9 +2568,9 @@ } }, "node_modules/@jest/transform/node_modules/@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==", + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", "license": "MIT" }, "node_modules/@jest/transform/node_modules/chalk": { @@ -2622,17 +2622,13 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.10.tgz", + "integrity": "sha512-HM2F4B9N4cA0RH2KQiIZOHAZqtP4xGS4IZ+SFe1SIbO4dyjf9MTY2Bo3vHYnm0hglWfXqBrzUBSa+cJfl3Xvrg==", "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -2644,25 +2640,16 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.2.tgz", + "integrity": "sha512-gKYheCylLIedI+CSZoDtGkFV9YEBxRRVcfCH7OfAqh4TyUyRjEE6WVE/aXDXX0p8BIe/QgLcaAoI0220KRRFgg==", "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.27", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.27.tgz", + "integrity": "sha512-VO95AxtSFMelbg3ouljAYnfvTEwSWVt/2YLf+U5Ejd8iT5mXE2Sa/1LGyvySMne2CGsepGLI7KpF3EzE3Aq9Mg==", "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -2785,9 +2772,9 @@ "license": "MIT" }, "node_modules/@octokit/plugin-paginate-rest": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-13.1.0.tgz", - "integrity": "sha512-16iNOa4rTTjaWtfsPGJcYYL79FJakseX8TQFIPfVuSPC3s5nkS/DSNQPFPc5lJHgEDBWNMxSApHrEymNblhA9w==", + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-13.1.1.tgz", + "integrity": "sha512-q9iQGlZlxAVNRN2jDNskJW/Cafy7/XE52wjZ5TTvyhyOD904Cvx//DNyoO3J/MXJ0ve3rPoNWKEg5iZrisQSuw==", "license": "MIT", "dependencies": { "@octokit/types": "^14.1.0" @@ -3094,9 +3081,9 @@ } }, "node_modules/@swc/core": { - "version": "1.12.5", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.12.5.tgz", - "integrity": "sha512-KxA0PHHIuUBmQ/Oi+xFpVzILj2Oo37sTtftCbyowQlyx5YOknEOw1kLpas5hMcpznXgFyAWbpK71xQps4INPgA==", + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.12.7.tgz", + "integrity": "sha512-bcpllEihyUSnqp0UtXTvXc19CT4wp3tGWLENhWnjr4B5iEOkzqMu+xHGz1FI5IBatjfqOQb29tgIfv6IL05QaA==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -3111,16 +3098,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.12.5", - "@swc/core-darwin-x64": "1.12.5", - "@swc/core-linux-arm-gnueabihf": "1.12.5", - "@swc/core-linux-arm64-gnu": "1.12.5", - "@swc/core-linux-arm64-musl": "1.12.5", - "@swc/core-linux-x64-gnu": "1.12.5", - "@swc/core-linux-x64-musl": "1.12.5", - "@swc/core-win32-arm64-msvc": "1.12.5", - "@swc/core-win32-ia32-msvc": "1.12.5", - "@swc/core-win32-x64-msvc": "1.12.5" + "@swc/core-darwin-arm64": "1.12.7", + "@swc/core-darwin-x64": "1.12.7", + "@swc/core-linux-arm-gnueabihf": "1.12.7", + "@swc/core-linux-arm64-gnu": "1.12.7", + "@swc/core-linux-arm64-musl": "1.12.7", + "@swc/core-linux-x64-gnu": "1.12.7", + "@swc/core-linux-x64-musl": "1.12.7", + "@swc/core-win32-arm64-msvc": "1.12.7", + "@swc/core-win32-ia32-msvc": "1.12.7", + "@swc/core-win32-x64-msvc": "1.12.7" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" @@ -3132,9 +3119,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.12.5", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.12.5.tgz", - "integrity": "sha512-3WF+naP/qkt5flrTfJr+p07b522JcixKvIivM7FgvllA6LjJxf+pheoILrTS8IwrNAK/XtHfKWYcGY+3eaA4mA==", + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.12.7.tgz", + "integrity": "sha512-w6BBT0hBRS56yS+LbReVym0h+iB7/PpCddqrn1ha94ra4rZ4R/A91A/rkv+LnQlPqU/+fhqdlXtCJU9mrhCBtA==", "cpu": [ "arm64" ], @@ -3148,9 +3135,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.12.5", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.12.5.tgz", - "integrity": "sha512-GCcD3dft8YN7unTBcW02Fx41jXp2MNQHCjx5ceWSEYOGvn7vBSUp7k7LkfTxGN5Ftxb9a1mxhPq8r4rD2u/aPw==", + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.12.7.tgz", + "integrity": "sha512-jN6LhFfGOpm4DY2mXPgwH4aa9GLOwublwMVFFZ/bGnHYYCRitLZs9+JWBbyWs7MyGcA246Ew+EREx36KVEAxjA==", "cpu": [ "x64" ], @@ -3164,9 +3151,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.12.5", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.12.5.tgz", - "integrity": "sha512-jWlzP/Y4+wbE/EJM+WGIDQsklLFV3g5LmbYTBgrY4+5nb517P31mkBzf5y2knfNWPrL7HzNu0578j3Zi2E6Iig==", + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.12.7.tgz", + "integrity": "sha512-rHn8XXi7G2StEtZRAeJ6c7nhJPDnqsHXmeNrAaYwk8Tvpa6ZYG2nT9E1OQNXj1/dfbSFTjdiA8M8ZvGYBlpBoA==", "cpu": [ "arm" ], @@ -3180,9 +3167,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.12.5", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.12.5.tgz", - "integrity": "sha512-GkzgIUz+2r6J6Tn3hb7/4ByaWHRrRZt4vuN9BLAd+y65m2Bt0vlEpPtWhrB/TVe4hEkFR+W5PDETLEbUT4i0tQ==", + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.12.7.tgz", + "integrity": "sha512-N15hKizSSh+hkZ2x3TDVrxq0TDcbvDbkQJi2ZrLb9fK+NdFUV/x+XF16ZDPlbxtrGXl1CT7VD439SNaMN9F7qw==", "cpu": [ "arm64" ], @@ -3196,9 +3183,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.12.5", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.12.5.tgz", - "integrity": "sha512-g0AJ7QmZPj3Uw+C5pDa48LAUG7JBgQmB0mN5cW+s2mjaFKT0mTSxYALtx/MDZwJExDPo0yJV8kSbFO1tvFPyhg==", + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.12.7.tgz", + "integrity": "sha512-jxyINtBezpxd3eIUDiDXv7UQ87YWlPsM9KumOwJk09FkFSO4oYxV2RT+Wu+Nt5tVWue4N0MdXT/p7SQsDEk4YA==", "cpu": [ "arm64" ], @@ -3212,9 +3199,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.12.5", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.12.5.tgz", - "integrity": "sha512-PeYoSziNy+iNiBHPtAsO84bzBne/mbCsG5ijYkAhS1GVsDgohClorUvRXXhcUZoX2gr8TfSI9WLHo30K+DKiHg==", + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.12.7.tgz", + "integrity": "sha512-PR4tPVwU1BQBfFDk2XfzXxsEIjF3x/bOV1BzZpYvrlkU0TKUDbR4t2wzvsYwD/coW7/yoQmlL70/qnuPtTp1Zw==", "cpu": [ "x64" ], @@ -3228,9 +3215,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.12.5", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.12.5.tgz", - "integrity": "sha512-EJrfCCIyuV5LLmYgKtIMwtgsnjVesdFe0IgQzEKs9OfB6cL6g7WO9conn8BkGX8jphVa7jChKxShDGkreWWDzA==", + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.12.7.tgz", + "integrity": "sha512-zy7JWfQtQItgMfUjSbbcS3DZqQUn2d9VuV0LSGpJxtTXwgzhRpF1S2Sj7cU9hGpbM27Y8RJ4DeFb3qbAufjbrw==", "cpu": [ "x64" ], @@ -3244,9 +3231,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.12.5", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.12.5.tgz", - "integrity": "sha512-FnwT7fxkJJMgsfiDoZKEVGyCzrPFbzpflFAAoTCUCu3MaHw6mW55o/MAAfofvJ1iIcEpec4o93OilsmKtpyO5Q==", + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.12.7.tgz", + "integrity": "sha512-52PeF0tyX04ZFD8nibNhy/GjMFOZWTEWPmIB3wpD1vIJ1po+smtBnEdRRll5WIXITKoiND8AeHlBNBPqcsdcwA==", "cpu": [ "arm64" ], @@ -3260,9 +3247,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.12.5", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.12.5.tgz", - "integrity": "sha512-jW6l4KFt9mIXSpGseE6BQOEFmbIeXeShDuWgldEJXKeXf/uPs8wrqv80XBIUwVpK0ZbmJwPQ0waGVj8UM3th2Q==", + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.12.7.tgz", + "integrity": "sha512-WzQwkNMuhB1qQShT9uUgz/mX2j7NIEPExEtzvGsBT7TlZ9j1kGZ8NJcZH/fwOFcSJL4W7DnkL7nAhx6DBlSPaA==", "cpu": [ "ia32" ], @@ -3276,9 +3263,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.12.5", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.12.5.tgz", - "integrity": "sha512-AZszwuEjlz1tSNLQRm3T5OZJ5eebxjJlDQnnzXJmg0B7DJMRoaAe1HTLOmejxjFK6yWr7fh+pSeCw2PgQLxgqA==", + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.12.7.tgz", + "integrity": "sha512-R52ivBi2lgjl+Bd3XCPum0YfgbZq/W1AUExITysddP9ErsNSwnreYyNB3exEijiazWGcqHEas2ChiuMOP7NYrA==", "cpu": [ "x64" ], @@ -3468,9 +3455,9 @@ "license": "MIT" }, "node_modules/@types/lodash": { - "version": "4.17.18", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.18.tgz", - "integrity": "sha512-KJ65INaxqxmU6EoCiJmRPZC9H9RVWCRd349tXM2M3O5NA7cY6YL7c0bHAHQ93NOfTObEQ004kd2QVHs/r0+m4g==", + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-NYqRyg/hIQrYPT9lbOeYc3kIRabJDn/k4qQHIXUpx88CBDww2fD15Sg5kbXlW86zm2XEW4g0QxkTI3/Kfkc7xQ==", "license": "MIT" }, "node_modules/@types/node": { @@ -3523,16 +3510,16 @@ "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.1.tgz", - "integrity": "sha512-STXcN6ebF6li4PxwNeFnqF8/2BNDvBupf2OPx2yWNzr6mKNGF7q49VM00Pz5FaomJyqvbXpY6PhO+T9w139YEQ==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.0.tgz", + "integrity": "sha512-ijItUYaiWuce0N1SoSMrEd0b6b6lYkYt99pqCPfybd+HKVXtEvYhICfLdwp42MhiI5mp0oq7PKEL+g1cNiz/Eg==", "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.34.1", - "@typescript-eslint/type-utils": "8.34.1", - "@typescript-eslint/utils": "8.34.1", - "@typescript-eslint/visitor-keys": "8.34.1", + "@typescript-eslint/scope-manager": "8.35.0", + "@typescript-eslint/type-utils": "8.35.0", + "@typescript-eslint/utils": "8.35.0", + "@typescript-eslint/visitor-keys": "8.35.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -3546,7 +3533,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.34.1", + "@typescript-eslint/parser": "^8.35.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } @@ -3561,15 +3548,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.1.tgz", - "integrity": "sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.35.0.tgz", + "integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==", "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.34.1", - "@typescript-eslint/types": "8.34.1", - "@typescript-eslint/typescript-estree": "8.34.1", - "@typescript-eslint/visitor-keys": "8.34.1", + "@typescript-eslint/scope-manager": "8.35.0", + "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/typescript-estree": "8.35.0", + "@typescript-eslint/visitor-keys": "8.35.0", "debug": "^4.3.4" }, "engines": { @@ -3585,13 +3572,13 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.1.tgz", - "integrity": "sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.0.tgz", + "integrity": "sha512-41xatqRwWZuhUMF/aZm2fcUsOFKNcG28xqRSS6ZVr9BVJtGExosLAm5A1OxTjRMagx8nJqva+P5zNIGt8RIgbQ==", "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.34.1", - "@typescript-eslint/types": "^8.34.1", + "@typescript-eslint/tsconfig-utils": "^8.35.0", + "@typescript-eslint/types": "^8.35.0", "debug": "^4.3.4" }, "engines": { @@ -3606,13 +3593,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.1.tgz", - "integrity": "sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.0.tgz", + "integrity": "sha512-+AgL5+mcoLxl1vGjwNfiWq5fLDZM1TmTPYs2UkyHfFhgERxBbqHlNjRzhThJqz+ktBqTChRYY6zwbMwy0591AA==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.34.1", - "@typescript-eslint/visitor-keys": "8.34.1" + "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/visitor-keys": "8.35.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3623,9 +3610,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.1.tgz", - "integrity": "sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.0.tgz", + "integrity": "sha512-04k/7247kZzFraweuEirmvUj+W3bJLI9fX6fbo1Qm2YykuBvEhRTPl8tcxlYO8kZZW+HIXfkZNoasVb8EV4jpA==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3639,13 +3626,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.1.tgz", - "integrity": "sha512-Tv7tCCr6e5m8hP4+xFugcrwTOucB8lshffJ6zf1mF1TbU67R+ntCc6DzLNKM+s/uzDyv8gLq7tufaAhIBYeV8g==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.35.0.tgz", + "integrity": "sha512-ceNNttjfmSEoM9PW87bWLDEIaLAyR+E6BoYJQ5PfaDau37UGca9Nyq3lBk8Bw2ad0AKvYabz6wxc7DMTO2jnNA==", "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.34.1", - "@typescript-eslint/utils": "8.34.1", + "@typescript-eslint/typescript-estree": "8.35.0", + "@typescript-eslint/utils": "8.35.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -3662,9 +3649,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.1.tgz", - "integrity": "sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.0.tgz", + "integrity": "sha512-0mYH3emanku0vHw2aRLNGqe7EXh9WHEhi7kZzscrMDf6IIRUQ5Jk4wp1QrledE/36KtdZrVfKnE32eZCf/vaVQ==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3675,15 +3662,15 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.1.tgz", - "integrity": "sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.0.tgz", + "integrity": "sha512-F+BhnaBemgu1Qf8oHrxyw14wq6vbL8xwWKKMwTMwYIRmFFY/1n/9T/jpbobZL8vp7QyEUcC6xGrnAO4ua8Kp7w==", "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.34.1", - "@typescript-eslint/tsconfig-utils": "8.34.1", - "@typescript-eslint/types": "8.34.1", - "@typescript-eslint/visitor-keys": "8.34.1", + "@typescript-eslint/project-service": "8.35.0", + "@typescript-eslint/tsconfig-utils": "8.35.0", + "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/visitor-keys": "8.35.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -3727,15 +3714,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.1.tgz", - "integrity": "sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.0.tgz", + "integrity": "sha512-nqoMu7WWM7ki5tPgLVsmPM8CkqtoPUG6xXGeefM5t4x3XumOEKMoUZPdi+7F+/EotukN4R9OWdmDxN80fqoZeg==", "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.34.1", - "@typescript-eslint/types": "8.34.1", - "@typescript-eslint/typescript-estree": "8.34.1" + "@typescript-eslint/scope-manager": "8.35.0", + "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/typescript-estree": "8.35.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3750,12 +3737,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.1.tgz", - "integrity": "sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.0.tgz", + "integrity": "sha512-zTh2+1Y8ZpmeQaQVIc/ZZxsx8UzgKJyNg1PTvjzC7WMhPSVS8bfDX34k1SrwOf016qd5RU3az2UxUNue3IfQ5g==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.34.1", + "@typescript-eslint/types": "8.35.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -3785,9 +3772,9 @@ "license": "ISC" }, "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.9.1.tgz", - "integrity": "sha512-dd7yIp1hfJFX9ZlVLQRrh/Re9WMUHHmF9hrKD1yIvxcyNr2BhQ3xc1upAVhy8NijadnCswAxWQu8MkkSMC1qXQ==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.9.2.tgz", + "integrity": "sha512-tS+lqTU3N0kkthU+rYp0spAYq15DU8ld9kXkaKg9sbQqJNF+WPMuNHZQGCgdxrUOEO0j22RKMwRVhF1HTl+X8A==", "cpu": [ "arm" ], @@ -3798,9 +3785,9 @@ ] }, "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.9.1.tgz", - "integrity": "sha512-EzUPcMFtDVlo5yrbzMqUsGq3HnLXw+3ZOhSd7CUaDmbTtnrzM+RO2ntw2dm2wjbbc5djWj3yX0wzbbg8pLhx8g==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.9.2.tgz", + "integrity": "sha512-MffGiZULa/KmkNjHeuuflLVqfhqLv1vZLm8lWIyeADvlElJ/GLSOkoUX+5jf4/EGtfwrNFcEaB8BRas03KT0/Q==", "cpu": [ "arm64" ], @@ -3811,9 +3798,9 @@ ] }, "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.9.1.tgz", - "integrity": "sha512-nB+dna3q4kOleKFcSZJ/wDXIsAd1kpMO9XrVAt8tG3RDWJ6vi+Ic6bpz4cmg5tWNeCfHEY4KuqJCB+pKejPEmQ==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.9.2.tgz", + "integrity": "sha512-dzJYK5rohS1sYl1DHdJ3mwfwClJj5BClQnQSyAgEfggbUwA9RlROQSSbKBLqrGfsiC/VyrDPtbO8hh56fnkbsQ==", "cpu": [ "arm64" ], @@ -3824,9 +3811,9 @@ ] }, "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.9.1.tgz", - "integrity": "sha512-aKWHCrOGaCGwZcekf3TnczQoBxk5w//W3RZ4EQyhux6rKDwBPgDU9Y2yGigCV1Z+8DWqZgVGQi+hdpnlSy3a1w==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.9.2.tgz", + "integrity": "sha512-gaIMWK+CWtXcg9gUyznkdV54LzQ90S3X3dn8zlh+QR5Xy7Y+Efqw4Rs4im61K1juy4YNb67vmJsCDAGOnIeffQ==", "cpu": [ "x64" ], @@ -3837,9 +3824,9 @@ ] }, "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.9.1.tgz", - "integrity": "sha512-4dIEMXrXt0UqDVgrsUd1I+NoIzVQWXy/CNhgpfS75rOOMK/4Abn0Mx2M2gWH4Mk9+ds/ASAiCmqoUFynmMY5hA==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.9.2.tgz", + "integrity": "sha512-S7QpkMbVoVJb0xwHFwujnwCAEDe/596xqY603rpi/ioTn9VDgBHnCCxh+UFrr5yxuMH+dliHfjwCZJXOPJGPnw==", "cpu": [ "x64" ], @@ -3850,9 +3837,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.9.1.tgz", - "integrity": "sha512-vtvS13IXPs1eE8DuS/soiosqMBeyh50YLRZ+p7EaIKAPPeevRnA9G/wu/KbVt01ZD5qiGjxS+CGIdVC7I6gTOw==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.9.2.tgz", + "integrity": "sha512-+XPUMCuCCI80I46nCDFbGum0ZODP5NWGiwS3Pj8fOgsG5/ctz+/zzuBlq/WmGa+EjWZdue6CF0aWWNv84sE1uw==", "cpu": [ "arm" ], @@ -3863,9 +3850,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.9.1.tgz", - "integrity": "sha512-BfdnN6aZ7NcX8djW8SR6GOJc+K+sFhWRF4vJueVE0vbUu5N1bLnBpxJg1TGlhSyo+ImC4SR0jcNiKN0jdoxt+A==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.9.2.tgz", + "integrity": "sha512-sqvUyAd1JUpwbz33Ce2tuTLJKM+ucSsYpPGl2vuFwZnEIg0CmdxiZ01MHQ3j6ExuRqEDUCy8yvkDKvjYFPb8Zg==", "cpu": [ "arm" ], @@ -3876,9 +3863,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.9.1.tgz", - "integrity": "sha512-Jhge7lFtH0QqfRz2PyJjJXWENqywPteITd+nOS0L6AhbZli+UmEyGBd2Sstt1c+l9C+j/YvKTl9wJo9PPmsFNg==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.9.2.tgz", + "integrity": "sha512-UYA0MA8ajkEDCFRQdng/FVx3F6szBvk3EPnkTTQuuO9lV1kPGuTB+V9TmbDxy5ikaEgyWKxa4CI3ySjklZ9lFA==", "cpu": [ "arm64" ], @@ -3889,9 +3876,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.9.1.tgz", - "integrity": "sha512-ofdK/ow+ZSbSU0pRoB7uBaiRHeaAOYQFU5Spp87LdcPL/P1RhbCTMSIYVb61XWzsVEmYKjHFtoIE0wxP6AFvrA==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.9.2.tgz", + "integrity": "sha512-P/CO3ODU9YJIHFqAkHbquKtFst0COxdphc8TKGL5yCX75GOiVpGqd1d15ahpqu8xXVsqP4MGFP2C3LRZnnL5MA==", "cpu": [ "arm64" ], @@ -3902,9 +3889,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.9.1.tgz", - "integrity": "sha512-eC8SXVn8de67HacqU7PoGdHA+9tGbqfEdD05AEFRAB81ejeQtNi5Fx7lPcxpLH79DW0BnMAHau3hi4RVkHfSCw==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.9.2.tgz", + "integrity": "sha512-uKStFlOELBxBum2s1hODPtgJhY4NxYJE9pAeyBgNEzHgTqTiVBPjfTlPFJkfxyTjQEuxZbbJlJnMCrRgD7ubzw==", "cpu": [ "ppc64" ], @@ -3915,9 +3902,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.9.1.tgz", - "integrity": "sha512-fIkwvAAQ41kfoGWfzeJ33iLGShl0JEDZHrMnwTHMErUcPkaaZRJYjQjsFhMl315NEQ4mmTlC+2nfK/J2IszDOw==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.9.2.tgz", + "integrity": "sha512-LkbNnZlhINfY9gK30AHs26IIVEZ9PEl9qOScYdmY2o81imJYI4IMnJiW0vJVtXaDHvBvxeAgEy5CflwJFIl3tQ==", "cpu": [ "riscv64" ], @@ -3928,9 +3915,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.9.1.tgz", - "integrity": "sha512-RAAszxImSOFLk44aLwnSqpcOdce8sBcxASledSzuFAd8Q5ZhhVck472SisspnzHdc7THCvGXiUeZ2hOC7NUoBQ==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.9.2.tgz", + "integrity": "sha512-vI+e6FzLyZHSLFNomPi+nT+qUWN4YSj8pFtQZSFTtmgFoxqB6NyjxSjAxEC1m93qn6hUXhIsh8WMp+fGgxCoRg==", "cpu": [ "riscv64" ], @@ -3941,9 +3928,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.9.1.tgz", - "integrity": "sha512-QoP9vkY+THuQdZi05bA6s6XwFd6HIz3qlx82v9bTOgxeqin/3C12Ye7f7EOD00RQ36OtOPWnhEMMm84sv7d1XQ==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.9.2.tgz", + "integrity": "sha512-sSO4AlAYhSM2RAzBsRpahcJB1msc6uYLAtP6pesPbZtptF8OU/CbCPhSRW6cnYOGuVmEmWVW5xVboAqCnWTeHQ==", "cpu": [ "s390x" ], @@ -3954,9 +3941,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.9.1.tgz", - "integrity": "sha512-/p77cGN/h9zbsfCseAP5gY7tK+7+DdM8fkPfr9d1ye1fsF6bmtGbtZN6e/8j4jCZ9NEIBBkT0GhdgixSelTK9g==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.9.2.tgz", + "integrity": "sha512-jkSkwch0uPFva20Mdu8orbQjv2A3G88NExTN2oPTI1AJ+7mZfYW3cDCTyoH6OnctBKbBVeJCEqh0U02lTkqD5w==", "cpu": [ "x64" ], @@ -3967,9 +3954,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.9.1.tgz", - "integrity": "sha512-wInTqT3Bu9u50mDStEig1v8uxEL2Ht+K8pir/YhyyrM5ordJtxoqzsL1vR/CQzOJuDunUTrDkMM0apjW/d7/PA==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.9.2.tgz", + "integrity": "sha512-Uk64NoiTpQbkpl+bXsbeyOPRpUoMdcUqa+hDC1KhMW7aN1lfW8PBlBH4mJ3n3Y47dYE8qi0XTxy1mBACruYBaw==", "cpu": [ "x64" ], @@ -3980,9 +3967,9 @@ ] }, "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.9.1.tgz", - "integrity": "sha512-eNwqO5kUa+1k7yFIircwwiniKWA0UFHo2Cfm8LYgkh9km7uMad+0x7X7oXbQonJXlqfitBTSjhA0un+DsHIrhw==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.9.2.tgz", + "integrity": "sha512-EpBGwkcjDicjR/ybC0g8wO5adPNdVuMrNalVgYcWi+gYtC1XYNuxe3rufcO7dA76OHGeVabcO6cSkPJKVcbCXQ==", "cpu": [ "wasm32" ], @@ -3996,9 +3983,9 @@ } }, "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.9.1.tgz", - "integrity": "sha512-Eaz1xMUnoa2mFqh20mPqSdbYl6crnk8HnIXDu6nsla9zpgZJZO8w3c1gvNN/4Eb0RXRq3K9OG6mu8vw14gIqiA==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.9.2.tgz", + "integrity": "sha512-EdFbGn7o1SxGmN6aZw9wAkehZJetFPao0VGZ9OMBwKx6TkvDuj6cNeLimF/Psi6ts9lMOe+Dt6z19fZQ9Ye2fw==", "cpu": [ "arm64" ], @@ -4009,9 +3996,9 @@ ] }, "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.9.1.tgz", - "integrity": "sha512-H/+d+5BGlnEQif0gnwWmYbYv7HJj563PUKJfn8PlmzF8UmF+8KxdvXdwCsoOqh4HHnENnoLrav9NYBrv76x1wQ==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.9.2.tgz", + "integrity": "sha512-JY9hi1p7AG+5c/dMU8o2kWemM8I6VZxfGwn1GCtf3c5i+IKcMo2NQ8OjZ4Z3/itvY/Si3K10jOBQn7qsD/whUA==", "cpu": [ "ia32" ], @@ -4022,9 +4009,9 @@ ] }, "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.9.1.tgz", - "integrity": "sha512-rS86wI4R6cknYM3is3grCb/laE8XBEbpWAMSIPjYfmYp75KL5dT87jXF2orDa4tQYg5aajP5G8Fgh34dRyR+Rw==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.9.2.tgz", + "integrity": "sha512-ryoo+EB19lMxAd80ln9BVf8pdOAxLb97amrQ3SFN9OCRn/5M5wvwDgAe4i8ZjhpbiHoDeP8yavcTEnpKBo7lZg==", "cpu": [ "x64" ], @@ -4632,9 +4619,9 @@ } }, "node_modules/browserslist": { - "version": "4.25.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", - "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", "funding": [ { "type": "opencollective", @@ -4651,8 +4638,8 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001718", - "electron-to-chromium": "^1.5.160", + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, @@ -4755,9 +4742,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001724", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001724.tgz", - "integrity": "sha512-WqJo7p0TbHDOythNTqYujmaJTvtYRZrjpP8TCvH6Vb9CYJerJNKamKzIWOM4BkQatWj9H2lYulpdAQNBe7QhNA==", + "version": "1.0.30001726", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", + "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", "funding": [ { "type": "opencollective", @@ -5486,9 +5473,9 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, "node_modules/electron-to-chromium": { - "version": "1.5.171", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.171.tgz", - "integrity": "sha512-scWpzXEJEMrGJa4Y6m/tVotb0WuvNmasv3wWVzUAeCgKU0ToFOhUW6Z+xWnRQANMYGxN4ngJXIThgBJOqzVPCQ==", + "version": "1.5.177", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.177.tgz", + "integrity": "sha512-7EH2G59nLsEMj97fpDuvVcYi6lwTcM1xuWw3PssD8xzboAW7zj7iB3COEEEATUfjLHrs5uKBLQT03V/8URx06g==", "license": "ISC" }, "node_modules/emittery": { @@ -5756,18 +5743,18 @@ } }, "node_modules/eslint": { - "version": "9.29.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz", - "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", + "version": "9.30.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.30.0.tgz", + "integrity": "sha512-iN/SiPxmQu6EVkf+m1qpBxzUhE12YqFLOSySuOyVLJLEF9nzTf+h/1AJYc1JWzCnktggeNrjvQGLngDzXirU6g==", "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.20.1", - "@eslint/config-helpers": "^0.2.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.0", "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.29.0", + "@eslint/js": "9.30.0", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -6058,14 +6045,14 @@ } }, "node_modules/expect": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.0.2.tgz", - "integrity": "sha512-YN9Mgv2mtTWXVmifQq3QT+ixCL/uLuLJw+fdp8MOjKqu8K3XQh3o5aulMM1tn+O2DdrWNxLZTeJsCY/VofUA0A==", + "version": "30.0.3", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.0.3.tgz", + "integrity": "sha512-HXg6NvK35/cSYZCUKAtmlgCFyqKM4frEPbzrav5hRqb0GMz0E0lS5hfzYjSaiaE5ysnp/qI2aeZkeyeIAOeXzQ==", "license": "MIT", "dependencies": { - "@jest/expect-utils": "30.0.2", + "@jest/expect-utils": "30.0.3", "@jest/get-type": "30.0.1", - "jest-matcher-utils": "30.0.2", + "jest-matcher-utils": "30.0.3", "jest-message-util": "30.0.2", "jest-mock": "30.0.2", "jest-util": "30.0.2" @@ -7214,15 +7201,15 @@ } }, "node_modules/jest": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.0.2.tgz", - "integrity": "sha512-HlSEiHRcmTuGwNyeawLTEzpQUMFn+f741FfoNg7RXG2h0WLJKozVCpcQLT0GW17H6kNCqRwGf+Ii/I1YVNvEGQ==", + "version": "30.0.3", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.0.3.tgz", + "integrity": "sha512-Uy8xfeE/WpT2ZLGDXQmaYNzw2v8NUKuYeKGtkS6sDxwsdQihdgYCXaKIYnph1h95DN5H35ubFDm0dfmsQnjn4Q==", "license": "MIT", "dependencies": { - "@jest/core": "30.0.2", + "@jest/core": "30.0.3", "@jest/types": "30.0.1", "import-local": "^3.2.0", - "jest-cli": "30.0.2" + "jest-cli": "30.0.3" }, "bin": { "jest": "bin/jest.js" @@ -7254,13 +7241,13 @@ } }, "node_modules/jest-circus": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.0.2.tgz", - "integrity": "sha512-NRozwx4DaFHcCUtwdEd/0jBLL1imyMrCbla3vF//wdsB2g6jIicMbjx9VhqE/BYU4dwsOQld+06ODX0oZ9xOLg==", + "version": "30.0.3", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.0.3.tgz", + "integrity": "sha512-rD9qq2V28OASJHJWDRVdhoBdRs6k3u3EmBzDYcyuMby8XCO3Ll1uq9kyqM41ZcC4fMiPulMVh3qMw0cBvDbnyg==", "license": "MIT", "dependencies": { "@jest/environment": "30.0.2", - "@jest/expect": "30.0.2", + "@jest/expect": "30.0.3", "@jest/test-result": "30.0.2", "@jest/types": "30.0.1", "@types/node": "*", @@ -7269,10 +7256,10 @@ "dedent": "^1.6.0", "is-generator-fn": "^2.1.0", "jest-each": "30.0.2", - "jest-matcher-utils": "30.0.2", + "jest-matcher-utils": "30.0.3", "jest-message-util": "30.0.2", - "jest-runtime": "30.0.2", - "jest-snapshot": "30.0.2", + "jest-runtime": "30.0.3", + "jest-snapshot": "30.0.3", "jest-util": "30.0.2", "p-limit": "^3.1.0", "pretty-format": "30.0.2", @@ -7315,9 +7302,9 @@ } }, "node_modules/jest-circus/node_modules/@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==", + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", "license": "MIT" }, "node_modules/jest-circus/node_modules/chalk": { @@ -7337,18 +7324,18 @@ } }, "node_modules/jest-cli": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.0.2.tgz", - "integrity": "sha512-yQ6Qz747oUbMYLNAqOlEby+hwXx7WEJtCl0iolBRpJhr2uvkBgiVMrvuKirBc8utwQBnkETFlDUkYifbRpmBrQ==", + "version": "30.0.3", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.0.3.tgz", + "integrity": "sha512-UWDSj0ayhumEAxpYRlqQLrssEi29kdQ+kddP94AuHhZknrE+mT0cR0J+zMHKFe9XPfX3dKQOc2TfWki3WhFTsA==", "license": "MIT", "dependencies": { - "@jest/core": "30.0.2", + "@jest/core": "30.0.3", "@jest/test-result": "30.0.2", "@jest/types": "30.0.1", "chalk": "^4.1.2", "exit-x": "^0.2.2", "import-local": "^3.2.0", - "jest-config": "30.0.2", + "jest-config": "30.0.3", "jest-util": "30.0.2", "jest-validate": "30.0.2", "yargs": "^17.7.2" @@ -7399,9 +7386,9 @@ } }, "node_modules/jest-cli/node_modules/@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==", + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", "license": "MIT" }, "node_modules/jest-cli/node_modules/chalk": { @@ -7421,9 +7408,9 @@ } }, "node_modules/jest-config": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.0.2.tgz", - "integrity": "sha512-vo0fVq+uzDcXETFVnCUyr5HaUCM8ES6DEuS9AFpma34BVXMRRNlsqDyiW5RDHaEFoeFlJHoI4Xjh/WSYIAL58g==", + "version": "30.0.3", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.0.3.tgz", + "integrity": "sha512-j0L4oRCtJwNyZktXIqwzEiDVQXBbQ4dqXuLD/TZdn++hXIcIfZmjHgrViEy5s/+j4HvITmAXbexVZpQ/jnr0bg==", "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", @@ -7437,12 +7424,12 @@ "deepmerge": "^4.3.1", "glob": "^10.3.10", "graceful-fs": "^4.2.11", - "jest-circus": "30.0.2", + "jest-circus": "30.0.3", "jest-docblock": "30.0.1", "jest-environment-node": "30.0.2", "jest-regex-util": "30.0.1", "jest-resolve": "30.0.2", - "jest-runner": "30.0.2", + "jest-runner": "30.0.3", "jest-util": "30.0.2", "jest-validate": "30.0.2", "micromatch": "^4.0.8", @@ -7502,9 +7489,9 @@ } }, "node_modules/jest-config/node_modules/@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==", + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", "license": "MIT" }, "node_modules/jest-config/node_modules/brace-expansion": { @@ -7605,9 +7592,9 @@ } }, "node_modules/jest-diff": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.0.2.tgz", - "integrity": "sha512-2UjrNvDJDn/oHFpPrUTVmvYYDNeNtw2DlY3er8bI6vJJb9Fb35ycp/jFLd5RdV59tJ8ekVXX3o/nwPcscgXZJQ==", + "version": "30.0.3", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.0.3.tgz", + "integrity": "sha512-Q1TAV0cUcBTic57SVnk/mug0/ASyAqtSIOkr7RAlxx97llRYsM74+E8N5WdGJUlwCKwgxPAkVjKh653h1+HA9A==", "license": "MIT", "dependencies": { "@jest/diff-sequences": "30.0.1", @@ -7694,9 +7681,9 @@ } }, "node_modules/jest-each/node_modules/@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==", + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", "license": "MIT" }, "node_modules/jest-each/node_modules/chalk": { @@ -7764,9 +7751,9 @@ } }, "node_modules/jest-environment-node/node_modules/@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==", + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", "license": "MIT" }, "node_modules/jest-environment-node/node_modules/chalk": { @@ -7840,9 +7827,9 @@ } }, "node_modules/jest-haste-map/node_modules/@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==", + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", "license": "MIT" }, "node_modules/jest-haste-map/node_modules/chalk": { @@ -7875,14 +7862,14 @@ } }, "node_modules/jest-matcher-utils": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.0.2.tgz", - "integrity": "sha512-1FKwgJYECR8IT93KMKmjKHSLyru0DqguThov/aWpFccC0wbiXGOxYEu7SScderBD7ruDOpl7lc5NG6w3oxKfaA==", + "version": "30.0.3", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.0.3.tgz", + "integrity": "sha512-hMpVFGFOhYmIIRGJ0HgM9htC5qUiJ00famcc9sRFchJJiLZbbVKrAztcgE6VnXLRxA3XZ0bvNA7hQWh3oHXo/A==", "license": "MIT", "dependencies": { "@jest/get-type": "30.0.1", "chalk": "^4.1.2", - "jest-diff": "30.0.2", + "jest-diff": "30.0.3", "pretty-format": "30.0.2" }, "engines": { @@ -8097,13 +8084,13 @@ } }, "node_modules/jest-resolve-dependencies": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.0.2.tgz", - "integrity": "sha512-Lp1iIXpsF5fGM4vyP8xHiIy2H5L5yO67/nXoYJzH4kz+fQmO+ZMKxzYLyWxYy4EeCLeNQ6a9OozL+uHZV2iuEA==", + "version": "30.0.3", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.0.3.tgz", + "integrity": "sha512-FlL6u7LiHbF0Oe27k7DHYMq2T2aNpPhxnNo75F7lEtu4A6sSw+TKkNNUGNcVckdFoL0RCWREJsC1HsKDwKRZzQ==", "license": "MIT", "dependencies": { "jest-regex-util": "30.0.1", - "jest-snapshot": "30.0.2" + "jest-snapshot": "30.0.3" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -8126,9 +8113,9 @@ } }, "node_modules/jest-runner": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.0.2.tgz", - "integrity": "sha512-6H+CIFiDLVt1Ix6jLzASXz3IoIiDukpEIxL9FHtDQ2BD/k5eFtDF5e5N9uItzRE3V1kp7VoSRyrGBytXKra4xA==", + "version": "30.0.3", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.0.3.tgz", + "integrity": "sha512-CxYBzu9WStOBBXAKkLXGoUtNOWsiS1RRmUQb6SsdUdTcqVncOau7m8AJ4cW3Mz+YL1O9pOGPSYLyvl8HBdFmkQ==", "license": "MIT", "dependencies": { "@jest/console": "30.0.2", @@ -8147,7 +8134,7 @@ "jest-leak-detector": "30.0.2", "jest-message-util": "30.0.2", "jest-resolve": "30.0.2", - "jest-runtime": "30.0.2", + "jest-runtime": "30.0.3", "jest-util": "30.0.2", "jest-watcher": "30.0.2", "jest-worker": "30.0.2", @@ -8189,9 +8176,9 @@ } }, "node_modules/jest-runner/node_modules/@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==", + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", "license": "MIT" }, "node_modules/jest-runner/node_modules/chalk": { @@ -8211,14 +8198,14 @@ } }, "node_modules/jest-runtime": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.0.2.tgz", - "integrity": "sha512-H1a51/soNOeAjoggu6PZKTH7DFt8JEGN4mesTSwyqD2jU9PXD04Bp6DKbt2YVtQvh2JcvH2vjbkEerCZ3lRn7A==", + "version": "30.0.3", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.0.3.tgz", + "integrity": "sha512-Xjosq0C48G9XEQOtmgrjXJwPaUPaq3sPJwHDRaiC+5wi4ZWxO6Lx6jNkizK/0JmTulVNuxP8iYwt77LGnfg3/w==", "license": "MIT", "dependencies": { "@jest/environment": "30.0.2", "@jest/fake-timers": "30.0.2", - "@jest/globals": "30.0.2", + "@jest/globals": "30.0.3", "@jest/source-map": "30.0.1", "@jest/test-result": "30.0.2", "@jest/transform": "30.0.2", @@ -8234,7 +8221,7 @@ "jest-mock": "30.0.2", "jest-regex-util": "30.0.1", "jest-resolve": "30.0.2", - "jest-snapshot": "30.0.2", + "jest-snapshot": "30.0.3", "jest-util": "30.0.2", "slash": "^3.0.0", "strip-bom": "^4.0.0" @@ -8274,9 +8261,9 @@ } }, "node_modules/jest-runtime/node_modules/@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==", + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", "license": "MIT" }, "node_modules/jest-runtime/node_modules/brace-expansion": { @@ -8377,9 +8364,9 @@ } }, "node_modules/jest-snapshot": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.0.2.tgz", - "integrity": "sha512-KeoHikoKGln3OlN7NS7raJ244nIVr2K46fBTNdfuxqYv2/g4TVyWDSO4fmk08YBJQMjs3HNfG1rlLfL/KA+nUw==", + "version": "30.0.3", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.0.3.tgz", + "integrity": "sha512-F05JCohd3OA1N9+5aEPXA6I0qOfZDGIx0zTq5Z4yMBg2i1p5ELfBusjYAWwTkC12c7dHcbyth4QAfQbS7cRjow==", "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", @@ -8387,17 +8374,17 @@ "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.0.2", + "@jest/expect-utils": "30.0.3", "@jest/get-type": "30.0.1", "@jest/snapshot-utils": "30.0.1", "@jest/transform": "30.0.2", "@jest/types": "30.0.1", "babel-preset-current-node-syntax": "^1.1.0", "chalk": "^4.1.2", - "expect": "30.0.2", + "expect": "30.0.3", "graceful-fs": "^4.2.11", - "jest-diff": "30.0.2", - "jest-matcher-utils": "30.0.2", + "jest-diff": "30.0.3", + "jest-matcher-utils": "30.0.3", "jest-message-util": "30.0.2", "jest-util": "30.0.2", "pretty-format": "30.0.2", @@ -8439,9 +8426,9 @@ } }, "node_modules/jest-snapshot/node_modules/@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==", + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", "license": "MIT" }, "node_modules/jest-snapshot/node_modules/chalk": { @@ -8589,9 +8576,9 @@ } }, "node_modules/jest-validate/node_modules/@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==", + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", "license": "MIT" }, "node_modules/jest-validate/node_modules/camelcase": { @@ -8672,9 +8659,9 @@ } }, "node_modules/jest-watcher/node_modules/@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==", + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", "license": "MIT" }, "node_modules/jest-watcher/node_modules/chalk": { @@ -9217,9 +9204,9 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, "node_modules/napi-postinstall": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.2.4.tgz", - "integrity": "sha512-ZEzHJwBhZ8qQSbknHqYcdtQVr8zUgGyM/q6h6qAyhtyVMNrSgDhrC4disf03dYW0e+czXyLnZINnCTEkWy0eJg==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.2.5.tgz", + "integrity": "sha512-kmsgUvCRIJohHjbZ3V8avP0I1Pekw329MVAMDzVxsrkjgdnqiwvMX5XwR+hWV66vsAtZ+iM+fVnq8RTQawUmCQ==", "license": "MIT", "bin": { "napi-postinstall": "lib/cli.js" @@ -11321,37 +11308,37 @@ } }, "node_modules/unrs-resolver": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.9.1.tgz", - "integrity": "sha512-4AZVxP05JGN6DwqIkSP4VKLOcwQa5l37SWHF/ahcuqBMbfxbpN1L1QKafEhWCziHhzKex9H/AR09H0OuVyU+9g==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.9.2.tgz", + "integrity": "sha512-VUyWiTNQD7itdiMuJy+EuLEErLj3uwX/EpHQF8EOf33Dq3Ju6VW1GXm+swk6+1h7a49uv9fKZ+dft9jU7esdLA==", "hasInstallScript": true, "license": "MIT", "dependencies": { - "napi-postinstall": "^0.2.2" + "napi-postinstall": "^0.2.4" }, "funding": { "url": "https://opencollective.com/unrs-resolver" }, "optionalDependencies": { - "@unrs/resolver-binding-android-arm-eabi": "1.9.1", - "@unrs/resolver-binding-android-arm64": "1.9.1", - "@unrs/resolver-binding-darwin-arm64": "1.9.1", - "@unrs/resolver-binding-darwin-x64": "1.9.1", - "@unrs/resolver-binding-freebsd-x64": "1.9.1", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.9.1", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.9.1", - "@unrs/resolver-binding-linux-arm64-gnu": "1.9.1", - "@unrs/resolver-binding-linux-arm64-musl": "1.9.1", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.9.1", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.9.1", - "@unrs/resolver-binding-linux-riscv64-musl": "1.9.1", - "@unrs/resolver-binding-linux-s390x-gnu": "1.9.1", - "@unrs/resolver-binding-linux-x64-gnu": "1.9.1", - "@unrs/resolver-binding-linux-x64-musl": "1.9.1", - "@unrs/resolver-binding-wasm32-wasi": "1.9.1", - "@unrs/resolver-binding-win32-arm64-msvc": "1.9.1", - "@unrs/resolver-binding-win32-ia32-msvc": "1.9.1", - "@unrs/resolver-binding-win32-x64-msvc": "1.9.1" + "@unrs/resolver-binding-android-arm-eabi": "1.9.2", + "@unrs/resolver-binding-android-arm64": "1.9.2", + "@unrs/resolver-binding-darwin-arm64": "1.9.2", + "@unrs/resolver-binding-darwin-x64": "1.9.2", + "@unrs/resolver-binding-freebsd-x64": "1.9.2", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.9.2", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.9.2", + "@unrs/resolver-binding-linux-arm64-gnu": "1.9.2", + "@unrs/resolver-binding-linux-arm64-musl": "1.9.2", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.9.2", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.9.2", + "@unrs/resolver-binding-linux-riscv64-musl": "1.9.2", + "@unrs/resolver-binding-linux-s390x-gnu": "1.9.2", + "@unrs/resolver-binding-linux-x64-gnu": "1.9.2", + "@unrs/resolver-binding-linux-x64-musl": "1.9.2", + "@unrs/resolver-binding-wasm32-wasi": "1.9.2", + "@unrs/resolver-binding-win32-arm64-msvc": "1.9.2", + "@unrs/resolver-binding-win32-ia32-msvc": "1.9.2", + "@unrs/resolver-binding-win32-x64-msvc": "1.9.2" } }, "node_modules/unzipit": { @@ -11835,25 +11822,25 @@ } }, "@babel/compat-data": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz", - "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==" + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.7.tgz", + "integrity": "sha512-xgu/ySj2mTiUFmdE9yCMfBxLp4DHd5DwmbbD05YAuICfodYT3VvRxbrh81LGQ/8UpSdtMdfKMn3KouYDX59DGQ==" }, "@babel/core": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", - "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.7.tgz", + "integrity": "sha512-BU2f9tlKQ5CAthiMIgpzAh4eDTLWo1mqi9jqE2OxMG0E/OM199VJt2q8BztTxpnSW0i1ymdwLXRJnYzvDM5r2w==", "requires": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", + "@babel/generator": "^7.27.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.4", - "@babel/parser": "^7.27.4", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.27.7", "@babel/template": "^7.27.2", - "@babel/traverse": "^7.27.4", - "@babel/types": "^7.27.3", + "@babel/traverse": "^7.27.7", + "@babel/types": "^7.27.7", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -11948,11 +11935,11 @@ } }, "@babel/parser": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", - "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.7.tgz", + "integrity": "sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q==", "requires": { - "@babel/types": "^7.27.3" + "@babel/types": "^7.27.7" } }, "@babel/plugin-syntax-async-generators": { @@ -12102,15 +12089,15 @@ } }, "@babel/traverse": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", - "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.7.tgz", + "integrity": "sha512-X6ZlfR/O/s5EQ/SnUSLzr+6kGnkg8HXGMzpgsMsrJVcfDtH1vIp6ctCN4eZ1LS5c0+te5Cb6Y514fASjMRJ1nw==", "requires": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", - "@babel/parser": "^7.27.4", + "@babel/generator": "^7.27.5", + "@babel/parser": "^7.27.7", "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3", + "@babel/types": "^7.27.7", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -12123,9 +12110,9 @@ } }, "@babel/types": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", - "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.7.tgz", + "integrity": "sha512-8OLQgDScAOHXnAz2cV+RfzzNMipuLVBz2biuAJFMV9bfkNf393je3VM8CLkjQodW5+iWsSJdSgSWT6rsZoXHPw==", "requires": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" @@ -12343,9 +12330,9 @@ "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==" }, "@eslint/config-array": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz", - "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", "requires": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", @@ -12353,9 +12340,9 @@ } }, "@eslint/config-helpers": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.3.tgz", - "integrity": "sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==" + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", + "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==" }, "@eslint/core": { "version": "0.14.0", @@ -12402,9 +12389,9 @@ } }, "@eslint/js": { - "version": "9.29.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz", - "integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==" + "version": "9.30.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.30.0.tgz", + "integrity": "sha512-Wzw3wQwPvc9sHM+NjakWTcPx11mbZyiYHuwWa/QfZ7cIRX7WK54PSk7bdyXDaoaopUcMatv1zaQvOAAO8hCdww==" }, "@eslint/object-schema": { "version": "2.1.6", @@ -12767,9 +12754,9 @@ } }, "@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==" + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" }, "chalk": { "version": "4.1.2", @@ -12783,9 +12770,9 @@ } }, "@jest/core": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.0.2.tgz", - "integrity": "sha512-mUMFdDtYWu7la63NxlyNIhgnzynszxunXWrtryR7bV24jV9hmi7XCZTzZHaLJjcBU66MeUAPZ81HjwASVpYhYQ==", + "version": "30.0.3", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.0.3.tgz", + "integrity": "sha512-Mgs1N+NSHD3Fusl7bOq1jyxv1JDAUwjy+0DhVR93Q6xcBP9/bAQ+oZhXb5TTnP5sQzAHgb7ROCKQ2SnovtxYtg==", "requires": { "@jest/console": "30.0.2", "@jest/pattern": "30.0.1", @@ -12800,15 +12787,15 @@ "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", "jest-changed-files": "30.0.2", - "jest-config": "30.0.2", + "jest-config": "30.0.3", "jest-haste-map": "30.0.2", "jest-message-util": "30.0.2", "jest-regex-util": "30.0.1", "jest-resolve": "30.0.2", - "jest-resolve-dependencies": "30.0.2", - "jest-runner": "30.0.2", - "jest-runtime": "30.0.2", - "jest-snapshot": "30.0.2", + "jest-resolve-dependencies": "30.0.3", + "jest-runner": "30.0.3", + "jest-runtime": "30.0.3", + "jest-snapshot": "30.0.3", "jest-util": "30.0.2", "jest-validate": "30.0.2", "jest-watcher": "30.0.2", @@ -12840,9 +12827,9 @@ } }, "@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==" + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" }, "chalk": { "version": "4.1.2", @@ -12902,9 +12889,9 @@ } }, "@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==" + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" }, "chalk": { "version": "4.1.2", @@ -12918,18 +12905,18 @@ } }, "@jest/expect": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.0.2.tgz", - "integrity": "sha512-blWRFPjv2cVfh42nLG6L3xIEbw+bnuiZYZDl/BZlsNG/i3wKV6FpPZ2EPHguk7t5QpLaouIu+7JmYO4uBR6AOg==", + "version": "30.0.3", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.0.3.tgz", + "integrity": "sha512-73BVLqfCeWjYWPEQoYjiRZ4xuQRhQZU0WdgvbyXGRHItKQqg5e6mt2y1kVhzLSuZpmUnccZHbGynoaL7IcLU3A==", "requires": { - "expect": "30.0.2", - "jest-snapshot": "30.0.2" + "expect": "30.0.3", + "jest-snapshot": "30.0.3" } }, "@jest/expect-utils": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.0.2.tgz", - "integrity": "sha512-FHF2YdtFBUQOo0/qdgt+6UdBFcNPF/TkVzcc+4vvf8uaBzUlONytGBeeudufIHHW1khRfM1sBbRT1VCK7n/0dQ==", + "version": "30.0.3", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.0.3.tgz", + "integrity": "sha512-SMtBvf2sfX2agcT0dA9pXwcUrKvOSDqBY4e4iRfT+Hya33XzV35YVg+98YQFErVGA/VR1Gto5Y2+A6G9LSQ3Yg==", "requires": { "@jest/get-type": "30.0.1" } @@ -12970,9 +12957,9 @@ } }, "@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==" + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" }, "chalk": { "version": "4.1.2", @@ -12991,12 +12978,12 @@ "integrity": "sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw==" }, "@jest/globals": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.0.2.tgz", - "integrity": "sha512-DwTtus9jjbG7b6jUdkcVdptf0wtD1v153A+PVwWB/zFwXhqu6hhtSd+uq88jofMhmYPtkmPmVGUBRNCZEKXn+w==", + "version": "30.0.3", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.0.3.tgz", + "integrity": "sha512-fIduqNyYpMeeSr5iEAiMn15KxCzvrmxl7X7VwLDRGj7t5CoHtbF+7K3EvKk32mOUIJ4kIvFRlaixClMH2h/Vaw==", "requires": { "@jest/environment": "30.0.2", - "@jest/expect": "30.0.2", + "@jest/expect": "30.0.3", "@jest/types": "30.0.1", "jest-mock": "30.0.2" }, @@ -13024,9 +13011,9 @@ } }, "@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==" + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" }, "chalk": { "version": "4.1.2", @@ -13101,9 +13088,9 @@ } }, "@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==" + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" }, "brace-expansion": { "version": "2.0.2", @@ -13210,9 +13197,9 @@ } }, "@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==" + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" }, "chalk": { "version": "4.1.2", @@ -13269,9 +13256,9 @@ } }, "@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==" + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" }, "chalk": { "version": "4.1.2", @@ -13340,9 +13327,9 @@ } }, "@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==" + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" }, "chalk": { "version": "4.1.2", @@ -13380,12 +13367,11 @@ } }, "@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.10.tgz", + "integrity": "sha512-HM2F4B9N4cA0RH2KQiIZOHAZqtP4xGS4IZ+SFe1SIbO4dyjf9MTY2Bo3vHYnm0hglWfXqBrzUBSa+cJfl3Xvrg==", "requires": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, @@ -13394,20 +13380,15 @@ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==" }, - "@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==" - }, "@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.2.tgz", + "integrity": "sha512-gKYheCylLIedI+CSZoDtGkFV9YEBxRRVcfCH7OfAqh4TyUyRjEE6WVE/aXDXX0p8BIe/QgLcaAoI0220KRRFgg==" }, "@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.27", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.27.tgz", + "integrity": "sha512-VO95AxtSFMelbg3ouljAYnfvTEwSWVt/2YLf+U5Ejd8iT5mXE2Sa/1LGyvySMne2CGsepGLI7KpF3EzE3Aq9Mg==", "requires": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -13499,9 +13480,9 @@ "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==" }, "@octokit/plugin-paginate-rest": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-13.1.0.tgz", - "integrity": "sha512-16iNOa4rTTjaWtfsPGJcYYL79FJakseX8TQFIPfVuSPC3s5nkS/DSNQPFPc5lJHgEDBWNMxSApHrEymNblhA9w==", + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-13.1.1.tgz", + "integrity": "sha512-q9iQGlZlxAVNRN2jDNskJW/Cafy7/XE52wjZ5TTvyhyOD904Cvx//DNyoO3J/MXJ0ve3rPoNWKEg5iZrisQSuw==", "requires": { "@octokit/types": "^14.1.0" } @@ -13737,82 +13718,82 @@ } }, "@swc/core": { - "version": "1.12.5", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.12.5.tgz", - "integrity": "sha512-KxA0PHHIuUBmQ/Oi+xFpVzILj2Oo37sTtftCbyowQlyx5YOknEOw1kLpas5hMcpznXgFyAWbpK71xQps4INPgA==", + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.12.7.tgz", + "integrity": "sha512-bcpllEihyUSnqp0UtXTvXc19CT4wp3tGWLENhWnjr4B5iEOkzqMu+xHGz1FI5IBatjfqOQb29tgIfv6IL05QaA==", "requires": { - "@swc/core-darwin-arm64": "1.12.5", - "@swc/core-darwin-x64": "1.12.5", - "@swc/core-linux-arm-gnueabihf": "1.12.5", - "@swc/core-linux-arm64-gnu": "1.12.5", - "@swc/core-linux-arm64-musl": "1.12.5", - "@swc/core-linux-x64-gnu": "1.12.5", - "@swc/core-linux-x64-musl": "1.12.5", - "@swc/core-win32-arm64-msvc": "1.12.5", - "@swc/core-win32-ia32-msvc": "1.12.5", - "@swc/core-win32-x64-msvc": "1.12.5", + "@swc/core-darwin-arm64": "1.12.7", + "@swc/core-darwin-x64": "1.12.7", + "@swc/core-linux-arm-gnueabihf": "1.12.7", + "@swc/core-linux-arm64-gnu": "1.12.7", + "@swc/core-linux-arm64-musl": "1.12.7", + "@swc/core-linux-x64-gnu": "1.12.7", + "@swc/core-linux-x64-musl": "1.12.7", + "@swc/core-win32-arm64-msvc": "1.12.7", + "@swc/core-win32-ia32-msvc": "1.12.7", + "@swc/core-win32-x64-msvc": "1.12.7", "@swc/counter": "^0.1.3", "@swc/types": "^0.1.23" } }, "@swc/core-darwin-arm64": { - "version": "1.12.5", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.12.5.tgz", - "integrity": "sha512-3WF+naP/qkt5flrTfJr+p07b522JcixKvIivM7FgvllA6LjJxf+pheoILrTS8IwrNAK/XtHfKWYcGY+3eaA4mA==", + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.12.7.tgz", + "integrity": "sha512-w6BBT0hBRS56yS+LbReVym0h+iB7/PpCddqrn1ha94ra4rZ4R/A91A/rkv+LnQlPqU/+fhqdlXtCJU9mrhCBtA==", "optional": true }, "@swc/core-darwin-x64": { - "version": "1.12.5", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.12.5.tgz", - "integrity": "sha512-GCcD3dft8YN7unTBcW02Fx41jXp2MNQHCjx5ceWSEYOGvn7vBSUp7k7LkfTxGN5Ftxb9a1mxhPq8r4rD2u/aPw==", + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.12.7.tgz", + "integrity": "sha512-jN6LhFfGOpm4DY2mXPgwH4aa9GLOwublwMVFFZ/bGnHYYCRitLZs9+JWBbyWs7MyGcA246Ew+EREx36KVEAxjA==", "optional": true }, "@swc/core-linux-arm-gnueabihf": { - "version": "1.12.5", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.12.5.tgz", - "integrity": "sha512-jWlzP/Y4+wbE/EJM+WGIDQsklLFV3g5LmbYTBgrY4+5nb517P31mkBzf5y2knfNWPrL7HzNu0578j3Zi2E6Iig==", + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.12.7.tgz", + "integrity": "sha512-rHn8XXi7G2StEtZRAeJ6c7nhJPDnqsHXmeNrAaYwk8Tvpa6ZYG2nT9E1OQNXj1/dfbSFTjdiA8M8ZvGYBlpBoA==", "optional": true }, "@swc/core-linux-arm64-gnu": { - "version": "1.12.5", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.12.5.tgz", - "integrity": "sha512-GkzgIUz+2r6J6Tn3hb7/4ByaWHRrRZt4vuN9BLAd+y65m2Bt0vlEpPtWhrB/TVe4hEkFR+W5PDETLEbUT4i0tQ==", + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.12.7.tgz", + "integrity": "sha512-N15hKizSSh+hkZ2x3TDVrxq0TDcbvDbkQJi2ZrLb9fK+NdFUV/x+XF16ZDPlbxtrGXl1CT7VD439SNaMN9F7qw==", "optional": true }, "@swc/core-linux-arm64-musl": { - "version": "1.12.5", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.12.5.tgz", - "integrity": "sha512-g0AJ7QmZPj3Uw+C5pDa48LAUG7JBgQmB0mN5cW+s2mjaFKT0mTSxYALtx/MDZwJExDPo0yJV8kSbFO1tvFPyhg==", + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.12.7.tgz", + "integrity": "sha512-jxyINtBezpxd3eIUDiDXv7UQ87YWlPsM9KumOwJk09FkFSO4oYxV2RT+Wu+Nt5tVWue4N0MdXT/p7SQsDEk4YA==", "optional": true }, "@swc/core-linux-x64-gnu": { - "version": "1.12.5", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.12.5.tgz", - "integrity": "sha512-PeYoSziNy+iNiBHPtAsO84bzBne/mbCsG5ijYkAhS1GVsDgohClorUvRXXhcUZoX2gr8TfSI9WLHo30K+DKiHg==", + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.12.7.tgz", + "integrity": "sha512-PR4tPVwU1BQBfFDk2XfzXxsEIjF3x/bOV1BzZpYvrlkU0TKUDbR4t2wzvsYwD/coW7/yoQmlL70/qnuPtTp1Zw==", "optional": true }, "@swc/core-linux-x64-musl": { - "version": "1.12.5", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.12.5.tgz", - "integrity": "sha512-EJrfCCIyuV5LLmYgKtIMwtgsnjVesdFe0IgQzEKs9OfB6cL6g7WO9conn8BkGX8jphVa7jChKxShDGkreWWDzA==", + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.12.7.tgz", + "integrity": "sha512-zy7JWfQtQItgMfUjSbbcS3DZqQUn2d9VuV0LSGpJxtTXwgzhRpF1S2Sj7cU9hGpbM27Y8RJ4DeFb3qbAufjbrw==", "optional": true }, "@swc/core-win32-arm64-msvc": { - "version": "1.12.5", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.12.5.tgz", - "integrity": "sha512-FnwT7fxkJJMgsfiDoZKEVGyCzrPFbzpflFAAoTCUCu3MaHw6mW55o/MAAfofvJ1iIcEpec4o93OilsmKtpyO5Q==", + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.12.7.tgz", + "integrity": "sha512-52PeF0tyX04ZFD8nibNhy/GjMFOZWTEWPmIB3wpD1vIJ1po+smtBnEdRRll5WIXITKoiND8AeHlBNBPqcsdcwA==", "optional": true }, "@swc/core-win32-ia32-msvc": { - "version": "1.12.5", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.12.5.tgz", - "integrity": "sha512-jW6l4KFt9mIXSpGseE6BQOEFmbIeXeShDuWgldEJXKeXf/uPs8wrqv80XBIUwVpK0ZbmJwPQ0waGVj8UM3th2Q==", + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.12.7.tgz", + "integrity": "sha512-WzQwkNMuhB1qQShT9uUgz/mX2j7NIEPExEtzvGsBT7TlZ9j1kGZ8NJcZH/fwOFcSJL4W7DnkL7nAhx6DBlSPaA==", "optional": true }, "@swc/core-win32-x64-msvc": { - "version": "1.12.5", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.12.5.tgz", - "integrity": "sha512-AZszwuEjlz1tSNLQRm3T5OZJ5eebxjJlDQnnzXJmg0B7DJMRoaAe1HTLOmejxjFK6yWr7fh+pSeCw2PgQLxgqA==", + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.12.7.tgz", + "integrity": "sha512-R52ivBi2lgjl+Bd3XCPum0YfgbZq/W1AUExITysddP9ErsNSwnreYyNB3exEijiazWGcqHEas2ChiuMOP7NYrA==", "optional": true }, "@swc/counter": { @@ -13969,9 +13950,9 @@ "integrity": "sha512-DIUKT4mkbTBxSrX6lmnQR888ObeFVVo1uNEqBH5/ddQHpnG4CA24DibpK7aO8QAcJEZUTcIx0F96TWuzVT9Z4g==" }, "@types/lodash": { - "version": "4.17.18", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.18.tgz", - "integrity": "sha512-KJ65INaxqxmU6EoCiJmRPZC9H9RVWCRd349tXM2M3O5NA7cY6YL7c0bHAHQ93NOfTObEQ004kd2QVHs/r0+m4g==" + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-NYqRyg/hIQrYPT9lbOeYc3kIRabJDn/k4qQHIXUpx88CBDww2fD15Sg5kbXlW86zm2XEW4g0QxkTI3/Kfkc7xQ==" }, "@types/node": { "version": "24.0.7", @@ -14018,15 +13999,15 @@ "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" }, "@typescript-eslint/eslint-plugin": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.1.tgz", - "integrity": "sha512-STXcN6ebF6li4PxwNeFnqF8/2BNDvBupf2OPx2yWNzr6mKNGF7q49VM00Pz5FaomJyqvbXpY6PhO+T9w139YEQ==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.0.tgz", + "integrity": "sha512-ijItUYaiWuce0N1SoSMrEd0b6b6lYkYt99pqCPfybd+HKVXtEvYhICfLdwp42MhiI5mp0oq7PKEL+g1cNiz/Eg==", "requires": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.34.1", - "@typescript-eslint/type-utils": "8.34.1", - "@typescript-eslint/utils": "8.34.1", - "@typescript-eslint/visitor-keys": "8.34.1", + "@typescript-eslint/scope-manager": "8.35.0", + "@typescript-eslint/type-utils": "8.35.0", + "@typescript-eslint/utils": "8.35.0", + "@typescript-eslint/visitor-keys": "8.35.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -14041,67 +14022,67 @@ } }, "@typescript-eslint/parser": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.1.tgz", - "integrity": "sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.35.0.tgz", + "integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==", "requires": { - "@typescript-eslint/scope-manager": "8.34.1", - "@typescript-eslint/types": "8.34.1", - "@typescript-eslint/typescript-estree": "8.34.1", - "@typescript-eslint/visitor-keys": "8.34.1", + "@typescript-eslint/scope-manager": "8.35.0", + "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/typescript-estree": "8.35.0", + "@typescript-eslint/visitor-keys": "8.35.0", "debug": "^4.3.4" } }, "@typescript-eslint/project-service": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.1.tgz", - "integrity": "sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.0.tgz", + "integrity": "sha512-41xatqRwWZuhUMF/aZm2fcUsOFKNcG28xqRSS6ZVr9BVJtGExosLAm5A1OxTjRMagx8nJqva+P5zNIGt8RIgbQ==", "requires": { - "@typescript-eslint/tsconfig-utils": "^8.34.1", - "@typescript-eslint/types": "^8.34.1", + "@typescript-eslint/tsconfig-utils": "^8.35.0", + "@typescript-eslint/types": "^8.35.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.1.tgz", - "integrity": "sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.0.tgz", + "integrity": "sha512-+AgL5+mcoLxl1vGjwNfiWq5fLDZM1TmTPYs2UkyHfFhgERxBbqHlNjRzhThJqz+ktBqTChRYY6zwbMwy0591AA==", "requires": { - "@typescript-eslint/types": "8.34.1", - "@typescript-eslint/visitor-keys": "8.34.1" + "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/visitor-keys": "8.35.0" } }, "@typescript-eslint/tsconfig-utils": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.1.tgz", - "integrity": "sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.0.tgz", + "integrity": "sha512-04k/7247kZzFraweuEirmvUj+W3bJLI9fX6fbo1Qm2YykuBvEhRTPl8tcxlYO8kZZW+HIXfkZNoasVb8EV4jpA==", "requires": {} }, "@typescript-eslint/type-utils": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.1.tgz", - "integrity": "sha512-Tv7tCCr6e5m8hP4+xFugcrwTOucB8lshffJ6zf1mF1TbU67R+ntCc6DzLNKM+s/uzDyv8gLq7tufaAhIBYeV8g==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.35.0.tgz", + "integrity": "sha512-ceNNttjfmSEoM9PW87bWLDEIaLAyR+E6BoYJQ5PfaDau37UGca9Nyq3lBk8Bw2ad0AKvYabz6wxc7DMTO2jnNA==", "requires": { - "@typescript-eslint/typescript-estree": "8.34.1", - "@typescript-eslint/utils": "8.34.1", + "@typescript-eslint/typescript-estree": "8.35.0", + "@typescript-eslint/utils": "8.35.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" } }, "@typescript-eslint/types": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.1.tgz", - "integrity": "sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==" + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.0.tgz", + "integrity": "sha512-0mYH3emanku0vHw2aRLNGqe7EXh9WHEhi7kZzscrMDf6IIRUQ5Jk4wp1QrledE/36KtdZrVfKnE32eZCf/vaVQ==" }, "@typescript-eslint/typescript-estree": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.1.tgz", - "integrity": "sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.0.tgz", + "integrity": "sha512-F+BhnaBemgu1Qf8oHrxyw14wq6vbL8xwWKKMwTMwYIRmFFY/1n/9T/jpbobZL8vp7QyEUcC6xGrnAO4ua8Kp7w==", "requires": { - "@typescript-eslint/project-service": "8.34.1", - "@typescript-eslint/tsconfig-utils": "8.34.1", - "@typescript-eslint/types": "8.34.1", - "@typescript-eslint/visitor-keys": "8.34.1", + "@typescript-eslint/project-service": "8.35.0", + "@typescript-eslint/tsconfig-utils": "8.35.0", + "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/visitor-keys": "8.35.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -14129,22 +14110,22 @@ } }, "@typescript-eslint/utils": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.1.tgz", - "integrity": "sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.0.tgz", + "integrity": "sha512-nqoMu7WWM7ki5tPgLVsmPM8CkqtoPUG6xXGeefM5t4x3XumOEKMoUZPdi+7F+/EotukN4R9OWdmDxN80fqoZeg==", "requires": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.34.1", - "@typescript-eslint/types": "8.34.1", - "@typescript-eslint/typescript-estree": "8.34.1" + "@typescript-eslint/scope-manager": "8.35.0", + "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/typescript-estree": "8.35.0" } }, "@typescript-eslint/visitor-keys": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.1.tgz", - "integrity": "sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.0.tgz", + "integrity": "sha512-zTh2+1Y8ZpmeQaQVIc/ZZxsx8UzgKJyNg1PTvjzC7WMhPSVS8bfDX34k1SrwOf016qd5RU3az2UxUNue3IfQ5g==", "requires": { - "@typescript-eslint/types": "8.34.1", + "@typescript-eslint/types": "8.35.0", "eslint-visitor-keys": "^4.2.1" }, "dependencies": { @@ -14161,120 +14142,120 @@ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==" }, "@unrs/resolver-binding-android-arm-eabi": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.9.1.tgz", - "integrity": "sha512-dd7yIp1hfJFX9ZlVLQRrh/Re9WMUHHmF9hrKD1yIvxcyNr2BhQ3xc1upAVhy8NijadnCswAxWQu8MkkSMC1qXQ==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.9.2.tgz", + "integrity": "sha512-tS+lqTU3N0kkthU+rYp0spAYq15DU8ld9kXkaKg9sbQqJNF+WPMuNHZQGCgdxrUOEO0j22RKMwRVhF1HTl+X8A==", "optional": true }, "@unrs/resolver-binding-android-arm64": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.9.1.tgz", - "integrity": "sha512-EzUPcMFtDVlo5yrbzMqUsGq3HnLXw+3ZOhSd7CUaDmbTtnrzM+RO2ntw2dm2wjbbc5djWj3yX0wzbbg8pLhx8g==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.9.2.tgz", + "integrity": "sha512-MffGiZULa/KmkNjHeuuflLVqfhqLv1vZLm8lWIyeADvlElJ/GLSOkoUX+5jf4/EGtfwrNFcEaB8BRas03KT0/Q==", "optional": true }, "@unrs/resolver-binding-darwin-arm64": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.9.1.tgz", - "integrity": "sha512-nB+dna3q4kOleKFcSZJ/wDXIsAd1kpMO9XrVAt8tG3RDWJ6vi+Ic6bpz4cmg5tWNeCfHEY4KuqJCB+pKejPEmQ==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.9.2.tgz", + "integrity": "sha512-dzJYK5rohS1sYl1DHdJ3mwfwClJj5BClQnQSyAgEfggbUwA9RlROQSSbKBLqrGfsiC/VyrDPtbO8hh56fnkbsQ==", "optional": true }, "@unrs/resolver-binding-darwin-x64": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.9.1.tgz", - "integrity": "sha512-aKWHCrOGaCGwZcekf3TnczQoBxk5w//W3RZ4EQyhux6rKDwBPgDU9Y2yGigCV1Z+8DWqZgVGQi+hdpnlSy3a1w==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.9.2.tgz", + "integrity": "sha512-gaIMWK+CWtXcg9gUyznkdV54LzQ90S3X3dn8zlh+QR5Xy7Y+Efqw4Rs4im61K1juy4YNb67vmJsCDAGOnIeffQ==", "optional": true }, "@unrs/resolver-binding-freebsd-x64": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.9.1.tgz", - "integrity": "sha512-4dIEMXrXt0UqDVgrsUd1I+NoIzVQWXy/CNhgpfS75rOOMK/4Abn0Mx2M2gWH4Mk9+ds/ASAiCmqoUFynmMY5hA==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.9.2.tgz", + "integrity": "sha512-S7QpkMbVoVJb0xwHFwujnwCAEDe/596xqY603rpi/ioTn9VDgBHnCCxh+UFrr5yxuMH+dliHfjwCZJXOPJGPnw==", "optional": true }, "@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.9.1.tgz", - "integrity": "sha512-vtvS13IXPs1eE8DuS/soiosqMBeyh50YLRZ+p7EaIKAPPeevRnA9G/wu/KbVt01ZD5qiGjxS+CGIdVC7I6gTOw==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.9.2.tgz", + "integrity": "sha512-+XPUMCuCCI80I46nCDFbGum0ZODP5NWGiwS3Pj8fOgsG5/ctz+/zzuBlq/WmGa+EjWZdue6CF0aWWNv84sE1uw==", "optional": true }, "@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.9.1.tgz", - "integrity": "sha512-BfdnN6aZ7NcX8djW8SR6GOJc+K+sFhWRF4vJueVE0vbUu5N1bLnBpxJg1TGlhSyo+ImC4SR0jcNiKN0jdoxt+A==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.9.2.tgz", + "integrity": "sha512-sqvUyAd1JUpwbz33Ce2tuTLJKM+ucSsYpPGl2vuFwZnEIg0CmdxiZ01MHQ3j6ExuRqEDUCy8yvkDKvjYFPb8Zg==", "optional": true }, "@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.9.1.tgz", - "integrity": "sha512-Jhge7lFtH0QqfRz2PyJjJXWENqywPteITd+nOS0L6AhbZli+UmEyGBd2Sstt1c+l9C+j/YvKTl9wJo9PPmsFNg==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.9.2.tgz", + "integrity": "sha512-UYA0MA8ajkEDCFRQdng/FVx3F6szBvk3EPnkTTQuuO9lV1kPGuTB+V9TmbDxy5ikaEgyWKxa4CI3ySjklZ9lFA==", "optional": true }, "@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.9.1.tgz", - "integrity": "sha512-ofdK/ow+ZSbSU0pRoB7uBaiRHeaAOYQFU5Spp87LdcPL/P1RhbCTMSIYVb61XWzsVEmYKjHFtoIE0wxP6AFvrA==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.9.2.tgz", + "integrity": "sha512-P/CO3ODU9YJIHFqAkHbquKtFst0COxdphc8TKGL5yCX75GOiVpGqd1d15ahpqu8xXVsqP4MGFP2C3LRZnnL5MA==", "optional": true }, "@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.9.1.tgz", - "integrity": "sha512-eC8SXVn8de67HacqU7PoGdHA+9tGbqfEdD05AEFRAB81ejeQtNi5Fx7lPcxpLH79DW0BnMAHau3hi4RVkHfSCw==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.9.2.tgz", + "integrity": "sha512-uKStFlOELBxBum2s1hODPtgJhY4NxYJE9pAeyBgNEzHgTqTiVBPjfTlPFJkfxyTjQEuxZbbJlJnMCrRgD7ubzw==", "optional": true }, "@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.9.1.tgz", - "integrity": "sha512-fIkwvAAQ41kfoGWfzeJ33iLGShl0JEDZHrMnwTHMErUcPkaaZRJYjQjsFhMl315NEQ4mmTlC+2nfK/J2IszDOw==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.9.2.tgz", + "integrity": "sha512-LkbNnZlhINfY9gK30AHs26IIVEZ9PEl9qOScYdmY2o81imJYI4IMnJiW0vJVtXaDHvBvxeAgEy5CflwJFIl3tQ==", "optional": true }, "@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.9.1.tgz", - "integrity": "sha512-RAAszxImSOFLk44aLwnSqpcOdce8sBcxASledSzuFAd8Q5ZhhVck472SisspnzHdc7THCvGXiUeZ2hOC7NUoBQ==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.9.2.tgz", + "integrity": "sha512-vI+e6FzLyZHSLFNomPi+nT+qUWN4YSj8pFtQZSFTtmgFoxqB6NyjxSjAxEC1m93qn6hUXhIsh8WMp+fGgxCoRg==", "optional": true }, "@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.9.1.tgz", - "integrity": "sha512-QoP9vkY+THuQdZi05bA6s6XwFd6HIz3qlx82v9bTOgxeqin/3C12Ye7f7EOD00RQ36OtOPWnhEMMm84sv7d1XQ==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.9.2.tgz", + "integrity": "sha512-sSO4AlAYhSM2RAzBsRpahcJB1msc6uYLAtP6pesPbZtptF8OU/CbCPhSRW6cnYOGuVmEmWVW5xVboAqCnWTeHQ==", "optional": true }, "@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.9.1.tgz", - "integrity": "sha512-/p77cGN/h9zbsfCseAP5gY7tK+7+DdM8fkPfr9d1ye1fsF6bmtGbtZN6e/8j4jCZ9NEIBBkT0GhdgixSelTK9g==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.9.2.tgz", + "integrity": "sha512-jkSkwch0uPFva20Mdu8orbQjv2A3G88NExTN2oPTI1AJ+7mZfYW3cDCTyoH6OnctBKbBVeJCEqh0U02lTkqD5w==", "optional": true }, "@unrs/resolver-binding-linux-x64-musl": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.9.1.tgz", - "integrity": "sha512-wInTqT3Bu9u50mDStEig1v8uxEL2Ht+K8pir/YhyyrM5ordJtxoqzsL1vR/CQzOJuDunUTrDkMM0apjW/d7/PA==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.9.2.tgz", + "integrity": "sha512-Uk64NoiTpQbkpl+bXsbeyOPRpUoMdcUqa+hDC1KhMW7aN1lfW8PBlBH4mJ3n3Y47dYE8qi0XTxy1mBACruYBaw==", "optional": true }, "@unrs/resolver-binding-wasm32-wasi": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.9.1.tgz", - "integrity": "sha512-eNwqO5kUa+1k7yFIircwwiniKWA0UFHo2Cfm8LYgkh9km7uMad+0x7X7oXbQonJXlqfitBTSjhA0un+DsHIrhw==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.9.2.tgz", + "integrity": "sha512-EpBGwkcjDicjR/ybC0g8wO5adPNdVuMrNalVgYcWi+gYtC1XYNuxe3rufcO7dA76OHGeVabcO6cSkPJKVcbCXQ==", "optional": true, "requires": { "@napi-rs/wasm-runtime": "^0.2.11" } }, "@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.9.1.tgz", - "integrity": "sha512-Eaz1xMUnoa2mFqh20mPqSdbYl6crnk8HnIXDu6nsla9zpgZJZO8w3c1gvNN/4Eb0RXRq3K9OG6mu8vw14gIqiA==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.9.2.tgz", + "integrity": "sha512-EdFbGn7o1SxGmN6aZw9wAkehZJetFPao0VGZ9OMBwKx6TkvDuj6cNeLimF/Psi6ts9lMOe+Dt6z19fZQ9Ye2fw==", "optional": true }, "@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.9.1.tgz", - "integrity": "sha512-H/+d+5BGlnEQif0gnwWmYbYv7HJj563PUKJfn8PlmzF8UmF+8KxdvXdwCsoOqh4HHnENnoLrav9NYBrv76x1wQ==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.9.2.tgz", + "integrity": "sha512-JY9hi1p7AG+5c/dMU8o2kWemM8I6VZxfGwn1GCtf3c5i+IKcMo2NQ8OjZ4Z3/itvY/Si3K10jOBQn7qsD/whUA==", "optional": true }, "@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.9.1.tgz", - "integrity": "sha512-rS86wI4R6cknYM3is3grCb/laE8XBEbpWAMSIPjYfmYp75KL5dT87jXF2orDa4tQYg5aajP5G8Fgh34dRyR+Rw==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.9.2.tgz", + "integrity": "sha512-ryoo+EB19lMxAd80ln9BVf8pdOAxLb97amrQ3SFN9OCRn/5M5wvwDgAe4i8ZjhpbiHoDeP8yavcTEnpKBo7lZg==", "optional": true }, "@zeit/schemas": { @@ -14672,12 +14653,12 @@ } }, "browserslist": { - "version": "4.25.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", - "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", "requires": { - "caniuse-lite": "^1.0.30001718", - "electron-to-chromium": "^1.5.160", + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" } @@ -14745,9 +14726,9 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" }, "caniuse-lite": { - "version": "1.0.30001724", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001724.tgz", - "integrity": "sha512-WqJo7p0TbHDOythNTqYujmaJTvtYRZrjpP8TCvH6Vb9CYJerJNKamKzIWOM4BkQatWj9H2lYulpdAQNBe7QhNA==" + "version": "1.0.30001726", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", + "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==" }, "cdata": { "version": "0.1.3", @@ -15254,9 +15235,9 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, "electron-to-chromium": { - "version": "1.5.171", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.171.tgz", - "integrity": "sha512-scWpzXEJEMrGJa4Y6m/tVotb0WuvNmasv3wWVzUAeCgKU0ToFOhUW6Z+xWnRQANMYGxN4ngJXIThgBJOqzVPCQ==" + "version": "1.5.177", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.177.tgz", + "integrity": "sha512-7EH2G59nLsEMj97fpDuvVcYi6lwTcM1xuWw3PssD8xzboAW7zj7iB3COEEEATUfjLHrs5uKBLQT03V/8URx06g==" }, "emittery": { "version": "0.13.1", @@ -15448,17 +15429,17 @@ } }, "eslint": { - "version": "9.29.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz", - "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", + "version": "9.30.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.30.0.tgz", + "integrity": "sha512-iN/SiPxmQu6EVkf+m1qpBxzUhE12YqFLOSySuOyVLJLEF9nzTf+h/1AJYc1JWzCnktggeNrjvQGLngDzXirU6g==", "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.20.1", - "@eslint/config-helpers": "^0.2.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.0", "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.29.0", + "@eslint/js": "9.30.0", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -15630,13 +15611,13 @@ "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==" }, "expect": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.0.2.tgz", - "integrity": "sha512-YN9Mgv2mtTWXVmifQq3QT+ixCL/uLuLJw+fdp8MOjKqu8K3XQh3o5aulMM1tn+O2DdrWNxLZTeJsCY/VofUA0A==", + "version": "30.0.3", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.0.3.tgz", + "integrity": "sha512-HXg6NvK35/cSYZCUKAtmlgCFyqKM4frEPbzrav5hRqb0GMz0E0lS5hfzYjSaiaE5ysnp/qI2aeZkeyeIAOeXzQ==", "requires": { - "@jest/expect-utils": "30.0.2", + "@jest/expect-utils": "30.0.3", "@jest/get-type": "30.0.1", - "jest-matcher-utils": "30.0.2", + "jest-matcher-utils": "30.0.3", "jest-message-util": "30.0.2", "jest-mock": "30.0.2", "jest-util": "30.0.2" @@ -16367,14 +16348,14 @@ } }, "jest": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.0.2.tgz", - "integrity": "sha512-HlSEiHRcmTuGwNyeawLTEzpQUMFn+f741FfoNg7RXG2h0WLJKozVCpcQLT0GW17H6kNCqRwGf+Ii/I1YVNvEGQ==", + "version": "30.0.3", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.0.3.tgz", + "integrity": "sha512-Uy8xfeE/WpT2ZLGDXQmaYNzw2v8NUKuYeKGtkS6sDxwsdQihdgYCXaKIYnph1h95DN5H35ubFDm0dfmsQnjn4Q==", "requires": { - "@jest/core": "30.0.2", + "@jest/core": "30.0.3", "@jest/types": "30.0.1", "import-local": "^3.2.0", - "jest-cli": "30.0.2" + "jest-cli": "30.0.3" }, "dependencies": { "@jest/schemas": { @@ -16426,12 +16407,12 @@ } }, "jest-circus": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.0.2.tgz", - "integrity": "sha512-NRozwx4DaFHcCUtwdEd/0jBLL1imyMrCbla3vF//wdsB2g6jIicMbjx9VhqE/BYU4dwsOQld+06ODX0oZ9xOLg==", + "version": "30.0.3", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.0.3.tgz", + "integrity": "sha512-rD9qq2V28OASJHJWDRVdhoBdRs6k3u3EmBzDYcyuMby8XCO3Ll1uq9kyqM41ZcC4fMiPulMVh3qMw0cBvDbnyg==", "requires": { "@jest/environment": "30.0.2", - "@jest/expect": "30.0.2", + "@jest/expect": "30.0.3", "@jest/test-result": "30.0.2", "@jest/types": "30.0.1", "@types/node": "*", @@ -16440,10 +16421,10 @@ "dedent": "^1.6.0", "is-generator-fn": "^2.1.0", "jest-each": "30.0.2", - "jest-matcher-utils": "30.0.2", + "jest-matcher-utils": "30.0.3", "jest-message-util": "30.0.2", - "jest-runtime": "30.0.2", - "jest-snapshot": "30.0.2", + "jest-runtime": "30.0.3", + "jest-snapshot": "30.0.3", "jest-util": "30.0.2", "p-limit": "^3.1.0", "pretty-format": "30.0.2", @@ -16475,9 +16456,9 @@ } }, "@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==" + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" }, "chalk": { "version": "4.1.2", @@ -16491,17 +16472,17 @@ } }, "jest-cli": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.0.2.tgz", - "integrity": "sha512-yQ6Qz747oUbMYLNAqOlEby+hwXx7WEJtCl0iolBRpJhr2uvkBgiVMrvuKirBc8utwQBnkETFlDUkYifbRpmBrQ==", + "version": "30.0.3", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.0.3.tgz", + "integrity": "sha512-UWDSj0ayhumEAxpYRlqQLrssEi29kdQ+kddP94AuHhZknrE+mT0cR0J+zMHKFe9XPfX3dKQOc2TfWki3WhFTsA==", "requires": { - "@jest/core": "30.0.2", + "@jest/core": "30.0.3", "@jest/test-result": "30.0.2", "@jest/types": "30.0.1", "chalk": "^4.1.2", "exit-x": "^0.2.2", "import-local": "^3.2.0", - "jest-config": "30.0.2", + "jest-config": "30.0.3", "jest-util": "30.0.2", "jest-validate": "30.0.2", "yargs": "^17.7.2" @@ -16530,9 +16511,9 @@ } }, "@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==" + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" }, "chalk": { "version": "4.1.2", @@ -16546,9 +16527,9 @@ } }, "jest-config": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.0.2.tgz", - "integrity": "sha512-vo0fVq+uzDcXETFVnCUyr5HaUCM8ES6DEuS9AFpma34BVXMRRNlsqDyiW5RDHaEFoeFlJHoI4Xjh/WSYIAL58g==", + "version": "30.0.3", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.0.3.tgz", + "integrity": "sha512-j0L4oRCtJwNyZktXIqwzEiDVQXBbQ4dqXuLD/TZdn++hXIcIfZmjHgrViEy5s/+j4HvITmAXbexVZpQ/jnr0bg==", "requires": { "@babel/core": "^7.27.4", "@jest/get-type": "30.0.1", @@ -16561,12 +16542,12 @@ "deepmerge": "^4.3.1", "glob": "^10.3.10", "graceful-fs": "^4.2.11", - "jest-circus": "30.0.2", + "jest-circus": "30.0.3", "jest-docblock": "30.0.1", "jest-environment-node": "30.0.2", "jest-regex-util": "30.0.1", "jest-resolve": "30.0.2", - "jest-runner": "30.0.2", + "jest-runner": "30.0.3", "jest-util": "30.0.2", "jest-validate": "30.0.2", "micromatch": "^4.0.8", @@ -16599,9 +16580,9 @@ } }, "@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==" + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" }, "brace-expansion": { "version": "2.0.2", @@ -16667,9 +16648,9 @@ } }, "jest-diff": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.0.2.tgz", - "integrity": "sha512-2UjrNvDJDn/oHFpPrUTVmvYYDNeNtw2DlY3er8bI6vJJb9Fb35ycp/jFLd5RdV59tJ8ekVXX3o/nwPcscgXZJQ==", + "version": "30.0.3", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.0.3.tgz", + "integrity": "sha512-Q1TAV0cUcBTic57SVnk/mug0/ASyAqtSIOkr7RAlxx97llRYsM74+E8N5WdGJUlwCKwgxPAkVjKh653h1+HA9A==", "requires": { "@jest/diff-sequences": "30.0.1", "@jest/get-type": "30.0.1", @@ -16731,9 +16712,9 @@ } }, "@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==" + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" }, "chalk": { "version": "4.1.2", @@ -16783,9 +16764,9 @@ } }, "@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==" + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" }, "chalk": { "version": "4.1.2", @@ -16839,9 +16820,9 @@ } }, "@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==" + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" }, "chalk": { "version": "4.1.2", @@ -16864,13 +16845,13 @@ } }, "jest-matcher-utils": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.0.2.tgz", - "integrity": "sha512-1FKwgJYECR8IT93KMKmjKHSLyru0DqguThov/aWpFccC0wbiXGOxYEu7SScderBD7ruDOpl7lc5NG6w3oxKfaA==", + "version": "30.0.3", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.0.3.tgz", + "integrity": "sha512-hMpVFGFOhYmIIRGJ0HgM9htC5qUiJ00famcc9sRFchJJiLZbbVKrAztcgE6VnXLRxA3XZ0bvNA7hQWh3oHXo/A==", "requires": { "@jest/get-type": "30.0.1", "chalk": "^4.1.2", - "jest-diff": "30.0.2", + "jest-diff": "30.0.3", "pretty-format": "30.0.2" }, "dependencies": { @@ -17033,18 +17014,18 @@ } }, "jest-resolve-dependencies": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.0.2.tgz", - "integrity": "sha512-Lp1iIXpsF5fGM4vyP8xHiIy2H5L5yO67/nXoYJzH4kz+fQmO+ZMKxzYLyWxYy4EeCLeNQ6a9OozL+uHZV2iuEA==", + "version": "30.0.3", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.0.3.tgz", + "integrity": "sha512-FlL6u7LiHbF0Oe27k7DHYMq2T2aNpPhxnNo75F7lEtu4A6sSw+TKkNNUGNcVckdFoL0RCWREJsC1HsKDwKRZzQ==", "requires": { "jest-regex-util": "30.0.1", - "jest-snapshot": "30.0.2" + "jest-snapshot": "30.0.3" } }, "jest-runner": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.0.2.tgz", - "integrity": "sha512-6H+CIFiDLVt1Ix6jLzASXz3IoIiDukpEIxL9FHtDQ2BD/k5eFtDF5e5N9uItzRE3V1kp7VoSRyrGBytXKra4xA==", + "version": "30.0.3", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.0.3.tgz", + "integrity": "sha512-CxYBzu9WStOBBXAKkLXGoUtNOWsiS1RRmUQb6SsdUdTcqVncOau7m8AJ4cW3Mz+YL1O9pOGPSYLyvl8HBdFmkQ==", "requires": { "@jest/console": "30.0.2", "@jest/environment": "30.0.2", @@ -17062,7 +17043,7 @@ "jest-leak-detector": "30.0.2", "jest-message-util": "30.0.2", "jest-resolve": "30.0.2", - "jest-runtime": "30.0.2", + "jest-runtime": "30.0.3", "jest-util": "30.0.2", "jest-watcher": "30.0.2", "jest-worker": "30.0.2", @@ -17093,9 +17074,9 @@ } }, "@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==" + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" }, "chalk": { "version": "4.1.2", @@ -17109,13 +17090,13 @@ } }, "jest-runtime": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.0.2.tgz", - "integrity": "sha512-H1a51/soNOeAjoggu6PZKTH7DFt8JEGN4mesTSwyqD2jU9PXD04Bp6DKbt2YVtQvh2JcvH2vjbkEerCZ3lRn7A==", + "version": "30.0.3", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.0.3.tgz", + "integrity": "sha512-Xjosq0C48G9XEQOtmgrjXJwPaUPaq3sPJwHDRaiC+5wi4ZWxO6Lx6jNkizK/0JmTulVNuxP8iYwt77LGnfg3/w==", "requires": { "@jest/environment": "30.0.2", "@jest/fake-timers": "30.0.2", - "@jest/globals": "30.0.2", + "@jest/globals": "30.0.3", "@jest/source-map": "30.0.1", "@jest/test-result": "30.0.2", "@jest/transform": "30.0.2", @@ -17131,7 +17112,7 @@ "jest-mock": "30.0.2", "jest-regex-util": "30.0.1", "jest-resolve": "30.0.2", - "jest-snapshot": "30.0.2", + "jest-snapshot": "30.0.3", "jest-util": "30.0.2", "slash": "^3.0.0", "strip-bom": "^4.0.0" @@ -17160,9 +17141,9 @@ } }, "@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==" + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" }, "brace-expansion": { "version": "2.0.2", @@ -17228,26 +17209,26 @@ } }, "jest-snapshot": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.0.2.tgz", - "integrity": "sha512-KeoHikoKGln3OlN7NS7raJ244nIVr2K46fBTNdfuxqYv2/g4TVyWDSO4fmk08YBJQMjs3HNfG1rlLfL/KA+nUw==", + "version": "30.0.3", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.0.3.tgz", + "integrity": "sha512-F05JCohd3OA1N9+5aEPXA6I0qOfZDGIx0zTq5Z4yMBg2i1p5ELfBusjYAWwTkC12c7dHcbyth4QAfQbS7cRjow==", "requires": { "@babel/core": "^7.27.4", "@babel/generator": "^7.27.5", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.0.2", + "@jest/expect-utils": "30.0.3", "@jest/get-type": "30.0.1", "@jest/snapshot-utils": "30.0.1", "@jest/transform": "30.0.2", "@jest/types": "30.0.1", "babel-preset-current-node-syntax": "^1.1.0", "chalk": "^4.1.2", - "expect": "30.0.2", + "expect": "30.0.3", "graceful-fs": "^4.2.11", - "jest-diff": "30.0.2", - "jest-matcher-utils": "30.0.2", + "jest-diff": "30.0.3", + "jest-matcher-utils": "30.0.3", "jest-message-util": "30.0.2", "jest-util": "30.0.2", "pretty-format": "30.0.2", @@ -17278,9 +17259,9 @@ } }, "@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==" + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" }, "chalk": { "version": "4.1.2", @@ -17385,9 +17366,9 @@ } }, "@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==" + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" }, "camelcase": { "version": "6.3.0", @@ -17443,9 +17424,9 @@ } }, "@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==" + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" }, "chalk": { "version": "4.1.2", @@ -17822,9 +17803,9 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, "napi-postinstall": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.2.4.tgz", - "integrity": "sha512-ZEzHJwBhZ8qQSbknHqYcdtQVr8zUgGyM/q6h6qAyhtyVMNrSgDhrC4disf03dYW0e+czXyLnZINnCTEkWy0eJg==" + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.2.5.tgz", + "integrity": "sha512-kmsgUvCRIJohHjbZ3V8avP0I1Pekw329MVAMDzVxsrkjgdnqiwvMX5XwR+hWV66vsAtZ+iM+fVnq8RTQawUmCQ==" }, "natural-compare": { "version": "1.4.0", @@ -19260,30 +19241,30 @@ "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" }, "unrs-resolver": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.9.1.tgz", - "integrity": "sha512-4AZVxP05JGN6DwqIkSP4VKLOcwQa5l37SWHF/ahcuqBMbfxbpN1L1QKafEhWCziHhzKex9H/AR09H0OuVyU+9g==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.9.2.tgz", + "integrity": "sha512-VUyWiTNQD7itdiMuJy+EuLEErLj3uwX/EpHQF8EOf33Dq3Ju6VW1GXm+swk6+1h7a49uv9fKZ+dft9jU7esdLA==", "requires": { - "@unrs/resolver-binding-android-arm-eabi": "1.9.1", - "@unrs/resolver-binding-android-arm64": "1.9.1", - "@unrs/resolver-binding-darwin-arm64": "1.9.1", - "@unrs/resolver-binding-darwin-x64": "1.9.1", - "@unrs/resolver-binding-freebsd-x64": "1.9.1", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.9.1", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.9.1", - "@unrs/resolver-binding-linux-arm64-gnu": "1.9.1", - "@unrs/resolver-binding-linux-arm64-musl": "1.9.1", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.9.1", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.9.1", - "@unrs/resolver-binding-linux-riscv64-musl": "1.9.1", - "@unrs/resolver-binding-linux-s390x-gnu": "1.9.1", - "@unrs/resolver-binding-linux-x64-gnu": "1.9.1", - "@unrs/resolver-binding-linux-x64-musl": "1.9.1", - "@unrs/resolver-binding-wasm32-wasi": "1.9.1", - "@unrs/resolver-binding-win32-arm64-msvc": "1.9.1", - "@unrs/resolver-binding-win32-ia32-msvc": "1.9.1", - "@unrs/resolver-binding-win32-x64-msvc": "1.9.1", - "napi-postinstall": "^0.2.2" + "@unrs/resolver-binding-android-arm-eabi": "1.9.2", + "@unrs/resolver-binding-android-arm64": "1.9.2", + "@unrs/resolver-binding-darwin-arm64": "1.9.2", + "@unrs/resolver-binding-darwin-x64": "1.9.2", + "@unrs/resolver-binding-freebsd-x64": "1.9.2", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.9.2", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.9.2", + "@unrs/resolver-binding-linux-arm64-gnu": "1.9.2", + "@unrs/resolver-binding-linux-arm64-musl": "1.9.2", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.9.2", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.9.2", + "@unrs/resolver-binding-linux-riscv64-musl": "1.9.2", + "@unrs/resolver-binding-linux-s390x-gnu": "1.9.2", + "@unrs/resolver-binding-linux-x64-gnu": "1.9.2", + "@unrs/resolver-binding-linux-x64-musl": "1.9.2", + "@unrs/resolver-binding-wasm32-wasi": "1.9.2", + "@unrs/resolver-binding-win32-arm64-msvc": "1.9.2", + "@unrs/resolver-binding-win32-ia32-msvc": "1.9.2", + "@unrs/resolver-binding-win32-x64-msvc": "1.9.2", + "napi-postinstall": "^0.2.4" } }, "unzipit": { diff --git a/sites/tv.dir.bg/__data__/content.html b/sites/tv.dir.bg/__data__/content.html new file mode 100644 index 000000000..b369c353f --- /dev/null +++ b/sites/tv.dir.bg/__data__/content.html @@ -0,0 +1 @@ +
    \n
    \n
    \n

    \n \u0412\u0447\u0435\u0440\u0430\n <\/p>\n

    \n
    \n
    \n \"\u041a\u0443\u043f\u0430\n <\/div>\n
    \n 08:00\n <\/div>\n
    \n \u041a\u0443\u043f\u0430 \u043d\u0430 \u0424\u0440\u0430\u043d\u0446\u0438\u044f: \u0415\u0441\u043f\u0430\u043b\u0438 - \u041f\u0430\u0440\u0438 \u0421\u0435\u043d \u0416\u0435\u0440\u043c\u0435\u043d\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 10:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u041b\u0435\u0433\u0430\u043d\u0435\u0441 - \u0420\u0435\u0430\u043b \u0421\u043e\u0441\u0438\u0435\u0434\u0430\u0434\n <\/div>\n <\/div>\n
    \n
    \n \""\u041f\u0440\u0435\u0434\n <\/div>\n
    \n 12:00\n <\/div>\n
    \n "\u041f\u0440\u0435\u0434 \u0421\u0442\u0430\u0434\u0438\u043e\u043d\u0430" - \u0441\u043f\u043e\u0440\u0442\u043d\u043e \u0448\u043e\u0443\n <\/div>\n <\/div>\n
    \n
    \n \"\u041f\u0420\u042f\u041a\u041e,\n <\/div>\n
    \n 13:00\n <\/div>\n
    \n \u041f\u0420\u042f\u041a\u041e, \u0413\u043e\u043b\u0444: Italian Open\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 18:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0416\u0438\u0440\u043e\u043d\u0430 - \u0420\u0435\u0430\u043b \u041c\u0430\u0434\u0440\u0438\u0434\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 20:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0411\u0435\u0442\u0438\u0441 - \u0411\u0430\u0440\u0441\u0435\u043b\u043e\u043d\u0430\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 22:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0412\u0430\u043b\u0435\u043d\u0441\u0438\u044f - \u0420\u0430\u0439\u043e \u0412\u0430\u043b\u0435\u043a\u0430\u043d\u043e\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 00:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u041e\u0431\u0437\u043e\u0440 \u043d\u0430 \u0441\u0435\u0437\u043e\u043d\u0430\n <\/div>\n <\/div>\n
    \n
    \n \"\u041f\u0420\u042f\u041a\u041e,\n <\/div>\n
    \n 01:00\n <\/div>\n
    \n \u041f\u0420\u042f\u041a\u041e, \u041c\u0435\u0439\u0434\u0436\u044a\u0440 \u041b\u0438\u0439\u0433 \u0421\u043e\u043a\u044a\u0440: \u0424\u0438\u043b\u0430\u0434\u0435\u043b\u0444\u0438\u044f \u042e\u043d\u0438\u044a\u043d - \u041a\u044a\u043b\u044a\u043c\u0431\u044a\u0441 \u041a\u0440\u044e\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 03:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0410\u0442\u043b\u0435\u0442\u0438\u043a\u043e \u041c\u0430\u0434\u0440\u0438\u0434 - \u0421\u0435\u0432\u0438\u043b\u044f\n <\/div>\n <\/div>\n
    \n
    \n \"Trans\n <\/div>\n
    \n 05:00\n <\/div>\n
    \n Trans World Sport\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n
    \n
    \n

    \n \u0414\u043d\u0435\u0441\n <\/p>\n

    \n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 06:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0416\u0438\u0440\u043e\u043d\u0430 - \u0420\u0435\u0430\u043b \u041c\u0430\u0434\u0440\u0438\u0434\n <\/div>\n <\/div>\n
    \n
    \n \"\u041a\u0443\u043f\u0430\n <\/div>\n
    \n 08:00\n <\/div>\n
    \n \u041a\u0443\u043f\u0430 \u043d\u0430 \u0424\u0440\u0430\u043d\u0446\u0438\u044f: \u041b\u044c\u043e \u041c\u0430\u043d - \u041f\u0430\u0440\u0438 \u0421\u0435\u043d \u0416\u0435\u0440\u043c\u0435\u043d\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 10:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0412\u0430\u043b\u044f\u0434\u043e\u043b\u0438\u0434 - \u0412\u0430\u043b\u0435\u043d\u0441\u0438\u044f\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 12:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0421\u0435\u0432\u0438\u043b\u044f - \u0421\u0435\u043b\u0442\u0430\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 14:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0420\u0430\u0439\u043e \u0412\u0430\u043b\u0435\u043a\u0430\u043d\u043e - \u0420\u0435\u0430\u043b \u041c\u0430\u0434\u0440\u0438\u0434\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 16:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0410\u0442\u043b\u0435\u0442\u0438\u043a\u043e \u041c\u0430\u0434\u0440\u0438\u0434 - \u0425\u0435\u0442\u0430\u0444\u0435\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 18:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0411\u0430\u0440\u0441\u0435\u043b\u043e\u043d\u0430 - \u041b\u0435\u0433\u0430\u043d\u0435\u0441\n <\/div>\n <\/div>\n
    \n
    \n \"\u041f\u0420\u042f\u041a\u041e,\n <\/div>\n
    \n 20:00\n <\/div>\n
    \n \u041f\u0420\u042f\u041a\u041e, "\u041f\u0440\u0435\u0434 \u0421\u0442\u0430\u0434\u0438\u043e\u043d\u0430" - \u0441\u043f\u043e\u0440\u0442\u043d\u043e \u0448\u043e\u0443\n <\/div>\n <\/div>\n
    \n
    \n \"\u041c\u0435\u0439\u0434\u0436\u044a\u0440\n <\/div>\n
    \n 21:30\n <\/div>\n
    \n \u041c\u0435\u0439\u0434\u0436\u044a\u0440 \u041b\u0438\u0439\u0433 \u0421\u043e\u043a\u044a\u0440: \u0424\u0438\u043b\u0430\u0434\u0435\u043b\u0444\u0438\u044f \u042e\u043d\u0438\u044a\u043d - \u041a\u044a\u043b\u044a\u043c\u0431\u044a\u0441 \u041a\u0440\u044e\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 23:30\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0420\u0430\u0439\u043e \u0412\u0430\u043b\u0435\u043a\u0430\u043d\u043e - \u0420\u0435\u0430\u043b \u041c\u0430\u0434\u0440\u0438\u0434\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 01:30\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0421\u0435\u0432\u0438\u043b\u044f - \u0421\u0435\u043b\u0442\u0430\n <\/div>\n <\/div>\n
    \n
    \n \""\u041f\u0440\u0435\u0434\n <\/div>\n
    \n 03:30\n <\/div>\n
    \n "\u041f\u0440\u0435\u0434 \u0421\u0442\u0430\u0434\u0438\u043e\u043d\u0430" - \u0441\u043f\u043e\u0440\u0442\u043d\u043e \u0448\u043e\u0443\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 05:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0410\u0442\u043b\u0435\u0442\u0438\u043a\u043e \u041c\u0430\u0434\u0440\u0438\u0434 - \u0425\u0435\u0442\u0430\u0444\u0435\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n
    \n
    \n

    \n \u0423\u0442\u0440\u0435\n <\/p>\n

    \n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 07:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: "\u0428\u0430\u043c\u043f\u0438\u043e\u043d\u044a\u0442"\n <\/div>\n <\/div>\n
    \n
    \n \"\u041a\u0443\u043f\u0430\n <\/div>\n
    \n 08:00\n <\/div>\n
    \n \u041a\u0443\u043f\u0430 \u043d\u0430 \u0424\u0440\u0430\u043d\u0446\u0438\u044f: \u0411\u0440\u0435\u0441\u0442 - \u0414\u044e\u043d\u043a\u0435\u0440\u043a\n <\/div>\n <\/div>\n
    \n
    \n \""\u041f\u0440\u0435\u0434\n <\/div>\n
    \n 10:00\n <\/div>\n
    \n "\u041f\u0440\u0435\u0434 \u0421\u0442\u0430\u0434\u0438\u043e\u043d\u0430" - \u0441\u043f\u043e\u0440\u0442\u043d\u043e \u0448\u043e\u0443\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 11:30\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: "\u0413\u043e\u043b\u043c\u0430\u0439\u0441\u0442\u043e\u0440\u044a\u0442 \u043d\u0430 \u041b\u0430 \u041b\u0438\u0433\u0430"\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 12:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0411\u0430\u0440\u0441\u0435\u043b\u043e\u043d\u0430 - \u041b\u0435\u0433\u0430\u043d\u0435\u0441\n <\/div>\n <\/div>\n
    \n
    \n \"\u041c\u0435\u0439\u0434\u0436\u044a\u0440\n <\/div>\n
    \n 14:00\n <\/div>\n
    \n \u041c\u0435\u0439\u0434\u0436\u044a\u0440 \u041b\u0438\u0439\u0433 \u0421\u043e\u043a\u044a\u0440: \u0424\u0438\u043b\u0430\u0434\u0435\u043b\u0444\u0438\u044f \u042e\u043d\u0438\u044a\u043d - \u041a\u044a\u043b\u044a\u043c\u0431\u044a\u0441 \u041a\u0440\u044e\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 16:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0420\u0430\u0439\u043e \u0412\u0430\u043b\u0435\u043a\u0430\u043d\u043e - \u0420\u0435\u0430\u043b \u041c\u0430\u0434\u0440\u0438\u0434\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 18:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430 2: \u041c\u0438\u0440\u0430\u043d\u0434\u0435\u0441 - \u041e\u0432\u0438\u0435\u0434\u043e\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 20:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: "\u041d\u0430\u0439-\u0434\u043e\u0431\u0440\u0438\u044f\u0442 \u0432\u0440\u0430\u0442\u0430\u0440"\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 20:30\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430 2: \u041e\u0432\u0438\u0435\u0434\u043e - \u041c\u0438\u0440\u0430\u043d\u0434\u0435\u0441\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 23:15\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0421\u0435\u0432\u0438\u043b\u044f - \u0421\u0435\u043b\u0442\u0430\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 01:15\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0410\u0442\u043b\u0435\u0442\u0438\u043a\u043e \u041c\u0430\u0434\u0440\u0438\u0434 - \u0425\u0435\u0442\u0430\u0444\u0435\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 03:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0411\u0430\u0440\u0441\u0435\u043b\u043e\u043d\u0430 - \u041b\u0435\u0433\u0430\u043d\u0435\u0441\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 05:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0420\u0430\u0439\u043e \u0412\u0430\u043b\u0435\u043a\u0430\u043d\u043e - \u0420\u0435\u0430\u043b \u041c\u0430\u0434\u0440\u0438\u0434\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <\/div> \ No newline at end of file diff --git a/sites/tv.dir.bg/__data__/no_data.html b/sites/tv.dir.bg/__data__/no_data.html new file mode 100644 index 000000000..5f0823d88 --- /dev/null +++ b/sites/tv.dir.bg/__data__/no_data.html @@ -0,0 +1 @@ +
    \n
    \n
    \n

    \n 19.02\n

    \n
    \n
    \n
    \n
    \n
    \n
    \n

    \n 20.02\n

    \n
    \n
    \n
    \n
    \n
    \n
    \n

    \n 21.02\n

    \n
    \n
    \n
    \n
    \n
    \ No newline at end of file diff --git a/sites/tvheute.at/__data__/content.html b/sites/tvheute.at/__data__/content.html new file mode 100644 index 000000000..8eb1678e1 --- /dev/null +++ b/sites/tvheute.at/__data__/content.html @@ -0,0 +1,64 @@ +
    + +
    +

    Das ORF1 Programm mit allen Sendungen live im TV von tv.orf.at. Sie haben eine Sendung verpasst. In der ORF TVthek finden Sie viele Sendungen on demand zum Abruf als online Video und live stream.

    +
    +
    +
    +
    +
    +

    ORF1 heute

    + SKYsp2 ORF2 +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    SenderZeitZeitTitelStartTitel
    ORF1 Kids + +
    +
    + Monchhichi (Wh.) ANIMATIONSSERIE Der Streiche-Wettbewerb +
    + +
    +
    Roger hat sich Ärger mit Dr. Bellows eingehandelt, der ihn für einen Monat strafversetzen möchte. Einmal mehr hadert Roger mit dem Schicksal, dass er keinen eigenen Flaschengeist besitzt, der ihm aus der Patsche helfen kann. Jeannie schlägt vor, ihm Cousine Marilla zu schicken. Doch Tony ist strikt dagegen. Als ein Zaubererpärchen im exotischen Bühnenoutfit für die Zeit von Rogers Abwesenheit sein Apartment in Untermiete bezieht, glaubt Roger, Jeannie habe ihm ihre Verwandte doch noch gesandt.
    + +
    +
    +
    ORF1 + +
    +
    ZIB 18 NACHRICHTEN +
    +
    +
    \ No newline at end of file diff --git a/sites/tvheute.at/__data__/no_content.html b/sites/tvheute.at/__data__/no_content.html new file mode 100644 index 000000000..2d5f853f4 --- /dev/null +++ b/sites/tvheute.at/__data__/no_content.html @@ -0,0 +1,8 @@ + + + Object moved + + +

    Object moved to here.

    + + \ No newline at end of file diff --git a/sites/tvheute.at/tvheute.at.test.js b/sites/tvheute.at/tvheute.at.test.js index cb52784ce..e31587ca4 100644 --- a/sites/tvheute.at/tvheute.at.test.js +++ b/sites/tvheute.at/tvheute.at.test.js @@ -2,14 +2,12 @@ const { parser, url } = require('./tvheute.at.config.js') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') +const { readFileSync } = require('fs') dayjs.extend(customParseFormat) dayjs.extend(utc) const date = dayjs.utc('2021-11-08', 'YYYY-MM-DD').startOf('d') const channel = { site_id: 'orf1', xmltv_id: 'ORF1.at' } -const content = ` -

    Das ORF1 Programm mit allen Sendungen live im TV von tv.orf.at. Sie haben eine Sendung verpasst. In der ORF TVthek finden Sie viele Sendungen on demand zum Abruf als online Video und live stream.

    ORF1 heute

    SKYsp2 ORF2
    Sender Zeit Zeit Titel Start Titel
    ORF1 Kids
    Monchhichi (Wh.) ANIMATIONSSERIE Der Streiche-Wettbewerb
    Roger hat sich Ärger mit Dr. Bellows eingehandelt, der ihn für einen Monat strafversetzen möchte. Einmal mehr hadert Roger mit dem Schicksal, dass er keinen eigenen Flaschengeist besitzt, der ihm aus der Patsche helfen kann. Jeannie schlägt vor, ihm Cousine Marilla zu schicken. Doch Tony ist strikt dagegen. Als ein Zaubererpärchen im exotischen Bühnenoutfit für die Zeit von Rogers Abwesenheit sein Apartment in Untermiete bezieht, glaubt Roger, Jeannie habe ihm ihre Verwandte doch noch gesandt.
    ORF1
    ZIB 18 NACHRICHTEN
    -` it('can generate valid url', () => { expect(url({ channel, date })).toBe( @@ -18,7 +16,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - expect(parser({ date, channel, content })).toMatchObject([ + expect(parser({ date, channel, content: readFileSync('./__data__/content.html', 'utf8') })).toMatchObject([ { start: '2021-11-08T05:00:00.000Z', stop: '2021-11-08T05:10:00.000Z', @@ -40,9 +38,7 @@ it('can handle empty guide', () => { const result = parser({ date, channel, - content: `Object moved -

    Object moved to here.

    -` + content: readFileSync('./__data__/no_content.html', 'utf8') }) expect(result).toMatchObject([]) }) diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index 4c29f840b..000000000 --- a/yarn.lock +++ /dev/null @@ -1,8440 +0,0 @@ -# This file is generated by running "yarn install" inside your project. -# Manual changes might be lost - proceed with caution! - -__metadata: - version: 8 - cacheKey: 10c0 - -"@aashutoshrathi/word-wrap@npm:^1.2.3": - version: 1.2.6 - resolution: "@aashutoshrathi/word-wrap@npm:1.2.6" - checksum: 10c0/53c2b231a61a46792b39a0d43bc4f4f776bb4542aa57ee04930676802e5501282c2fc8aac14e4cd1f1120ff8b52616b6ff5ab539ad30aa2277d726444b71619f - languageName: node - linkType: hard - -"@alex_neo/jest-expect-message@npm:^1.0.5": - version: 1.0.5 - resolution: "@alex_neo/jest-expect-message@npm:1.0.5" - checksum: 10c0/4e31fcd6333fe06e2d17656fecb7b008ca299048d27e0dfbde26eb22ace6ec61505267bfd26e839a89d60aa069e6380335fed7ebe804948a1f6a5b7f6172a321 - languageName: node - linkType: hard - -"@ampproject/remapping@npm:^2.2.0": - version: 2.3.0 - resolution: "@ampproject/remapping@npm:2.3.0" - dependencies: - "@jridgewell/gen-mapping": "npm:^0.3.5" - "@jridgewell/trace-mapping": "npm:^0.3.24" - checksum: 10c0/81d63cca5443e0f0c72ae18b544cc28c7c0ec2cea46e7cb888bb0e0f411a1191d0d6b7af798d54e30777d8d1488b2ec0732aac2be342d3d7d3ffd271c6f489ed - languageName: node - linkType: hard - -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/code-frame@npm:7.27.1" - dependencies: - "@babel/helper-validator-identifier": "npm:^7.27.1" - js-tokens: "npm:^4.0.0" - picocolors: "npm:^1.1.1" - checksum: 10c0/5dd9a18baa5fce4741ba729acc3a3272c49c25cb8736c4b18e113099520e7ef7b545a4096a26d600e4416157e63e87d66db46aa3fbf0a5f2286da2705c12da00 - languageName: node - linkType: hard - -"@babel/compat-data@npm:^7.27.2": - version: 7.27.5 - resolution: "@babel/compat-data@npm:7.27.5" - checksum: 10c0/da2751fcd0b58eea958f2b2f7ff7d6de1280712b709fa1ad054b73dc7d31f589e353bb50479b9dc96007935f3ed3cada68ac5b45ce93086b7122ddc32e60dc00 - languageName: node - linkType: hard - -"@babel/core@npm:^7.23.9, @babel/core@npm:^7.27.4": - version: 7.27.4 - resolution: "@babel/core@npm:7.27.4" - dependencies: - "@ampproject/remapping": "npm:^2.2.0" - "@babel/code-frame": "npm:^7.27.1" - "@babel/generator": "npm:^7.27.3" - "@babel/helper-compilation-targets": "npm:^7.27.2" - "@babel/helper-module-transforms": "npm:^7.27.3" - "@babel/helpers": "npm:^7.27.4" - "@babel/parser": "npm:^7.27.4" - "@babel/template": "npm:^7.27.2" - "@babel/traverse": "npm:^7.27.4" - "@babel/types": "npm:^7.27.3" - convert-source-map: "npm:^2.0.0" - debug: "npm:^4.1.0" - gensync: "npm:^1.0.0-beta.2" - json5: "npm:^2.2.3" - semver: "npm:^6.3.1" - checksum: 10c0/d2d17b106a8d91d3eda754bb3f26b53a12eb7646df73c2b2d2e9b08d90529186bc69e3823f70a96ec6e5719dc2372fb54e14ad499da47ceeb172d2f7008787b5 - languageName: node - linkType: hard - -"@babel/generator@npm:^7.27.3, @babel/generator@npm:^7.27.5": - version: 7.27.5 - resolution: "@babel/generator@npm:7.27.5" - dependencies: - "@babel/parser": "npm:^7.27.5" - "@babel/types": "npm:^7.27.3" - "@jridgewell/gen-mapping": "npm:^0.3.5" - "@jridgewell/trace-mapping": "npm:^0.3.25" - jsesc: "npm:^3.0.2" - checksum: 10c0/8f649ef4cd81765c832bb11de4d6064b035ffebdecde668ba7abee68a7b0bce5c9feabb5dc5bb8aeba5bd9e5c2afa3899d852d2bd9ca77a711ba8c8379f416f0 - languageName: node - linkType: hard - -"@babel/helper-compilation-targets@npm:^7.27.2": - version: 7.27.2 - resolution: "@babel/helper-compilation-targets@npm:7.27.2" - dependencies: - "@babel/compat-data": "npm:^7.27.2" - "@babel/helper-validator-option": "npm:^7.27.1" - browserslist: "npm:^4.24.0" - lru-cache: "npm:^5.1.1" - semver: "npm:^6.3.1" - checksum: 10c0/f338fa00dcfea931804a7c55d1a1c81b6f0a09787e528ec580d5c21b3ecb3913f6cb0f361368973ce953b824d910d3ac3e8a8ee15192710d3563826447193ad1 - languageName: node - linkType: hard - -"@babel/helper-module-imports@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/helper-module-imports@npm:7.27.1" - dependencies: - "@babel/traverse": "npm:^7.27.1" - "@babel/types": "npm:^7.27.1" - checksum: 10c0/e00aace096e4e29290ff8648455c2bc4ed982f0d61dbf2db1b5e750b9b98f318bf5788d75a4f974c151bd318fd549e81dbcab595f46b14b81c12eda3023f51e8 - languageName: node - linkType: hard - -"@babel/helper-module-transforms@npm:^7.27.3": - version: 7.27.3 - resolution: "@babel/helper-module-transforms@npm:7.27.3" - dependencies: - "@babel/helper-module-imports": "npm:^7.27.1" - "@babel/helper-validator-identifier": "npm:^7.27.1" - "@babel/traverse": "npm:^7.27.3" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10c0/fccb4f512a13b4c069af51e1b56b20f54024bcf1591e31e978a30f3502567f34f90a80da6a19a6148c249216292a8074a0121f9e52602510ef0f32dbce95ca01 - languageName: node - linkType: hard - -"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.27.1, @babel/helper-plugin-utils@npm:^7.8.0": - version: 7.27.1 - resolution: "@babel/helper-plugin-utils@npm:7.27.1" - checksum: 10c0/94cf22c81a0c11a09b197b41ab488d416ff62254ce13c57e62912c85700dc2e99e555225787a4099ff6bae7a1812d622c80fbaeda824b79baa10a6c5ac4cf69b - languageName: node - linkType: hard - -"@babel/helper-string-parser@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/helper-string-parser@npm:7.27.1" - checksum: 10c0/8bda3448e07b5583727c103560bcf9c4c24b3c1051a4c516d4050ef69df37bb9a4734a585fe12725b8c2763de0a265aa1e909b485a4e3270b7cfd3e4dbe4b602 - languageName: node - linkType: hard - -"@babel/helper-validator-identifier@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/helper-validator-identifier@npm:7.27.1" - checksum: 10c0/c558f11c4871d526498e49d07a84752d1800bf72ac0d3dad100309a2eaba24efbf56ea59af5137ff15e3a00280ebe588560534b0e894a4750f8b1411d8f78b84 - languageName: node - linkType: hard - -"@babel/helper-validator-option@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/helper-validator-option@npm:7.27.1" - checksum: 10c0/6fec5f006eba40001a20f26b1ef5dbbda377b7b68c8ad518c05baa9af3f396e780bdfded24c4eef95d14bb7b8fd56192a6ed38d5d439b97d10efc5f1a191d148 - languageName: node - linkType: hard - -"@babel/helpers@npm:^7.27.4": - version: 7.27.6 - resolution: "@babel/helpers@npm:7.27.6" - dependencies: - "@babel/template": "npm:^7.27.2" - "@babel/types": "npm:^7.27.6" - checksum: 10c0/448bac96ef8b0f21f2294a826df9de6bf4026fd023f8a6bb6c782fe3e61946801ca24381490b8e58d861fee75cd695a1882921afbf1f53b0275ee68c938bd6d3 - languageName: node - linkType: hard - -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.27.4, @babel/parser@npm:^7.27.5": - version: 7.27.5 - resolution: "@babel/parser@npm:7.27.5" - dependencies: - "@babel/types": "npm:^7.27.3" - bin: - parser: ./bin/babel-parser.js - checksum: 10c0/f7faaebf21cc1f25d9ca8ac02c447ed38ef3460ea95be7ea760916dcf529476340d72a5a6010c6641d9ed9d12ad827c8424840277ec2295c5b082ba0f291220a - languageName: node - linkType: hard - -"@babel/plugin-syntax-async-generators@npm:^7.8.4": - version: 7.8.4 - resolution: "@babel/plugin-syntax-async-generators@npm:7.8.4" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/d13efb282838481348c71073b6be6245b35d4f2f964a8f71e4174f235009f929ef7613df25f8d2338e2d3e44bc4265a9f8638c6aaa136d7a61fe95985f9725c8 - languageName: node - linkType: hard - -"@babel/plugin-syntax-bigint@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-bigint@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/686891b81af2bc74c39013655da368a480f17dd237bf9fbc32048e5865cb706d5a8f65438030da535b332b1d6b22feba336da8fa931f663b6b34e13147d12dde - languageName: node - linkType: hard - -"@babel/plugin-syntax-class-properties@npm:^7.12.13": - version: 7.12.13 - resolution: "@babel/plugin-syntax-class-properties@npm:7.12.13" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.12.13" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/95168fa186416195280b1264fb18afcdcdcea780b3515537b766cb90de6ce042d42dd6a204a39002f794ae5845b02afb0fd4861a3308a861204a55e68310a120 - languageName: node - linkType: hard - -"@babel/plugin-syntax-class-static-block@npm:^7.14.5": - version: 7.14.5 - resolution: "@babel/plugin-syntax-class-static-block@npm:7.14.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.14.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/4464bf9115f4a2d02ce1454411baf9cfb665af1da53709c5c56953e5e2913745b0fcce82982a00463d6facbdd93445c691024e310b91431a1e2f024b158f6371 - languageName: node - linkType: hard - -"@babel/plugin-syntax-import-attributes@npm:^7.24.7": - version: 7.27.1 - resolution: "@babel/plugin-syntax-import-attributes@npm:7.27.1" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.27.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/e66f7a761b8360419bbb93ab67d87c8a97465ef4637a985ff682ce7ba6918b34b29d81190204cf908d0933058ee7b42737423cd8a999546c21b3aabad4affa9a - languageName: node - linkType: hard - -"@babel/plugin-syntax-import-meta@npm:^7.10.4": - version: 7.10.4 - resolution: "@babel/plugin-syntax-import-meta@npm:7.10.4" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.10.4" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/0b08b5e4c3128523d8e346f8cfc86824f0da2697b1be12d71af50a31aff7a56ceb873ed28779121051475010c28d6146a6bfea8518b150b71eeb4e46190172ee - languageName: node - linkType: hard - -"@babel/plugin-syntax-json-strings@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-json-strings@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/e98f31b2ec406c57757d115aac81d0336e8434101c224edd9a5c93cefa53faf63eacc69f3138960c8b25401315af03df37f68d316c151c4b933136716ed6906e - languageName: node - linkType: hard - -"@babel/plugin-syntax-jsx@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/plugin-syntax-jsx@npm:7.27.1" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.27.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/bc5afe6a458d5f0492c02a54ad98c5756a0c13bd6d20609aae65acd560a9e141b0876da5f358dce34ea136f271c1016df58b461184d7ae9c4321e0f98588bc84 - languageName: node - linkType: hard - -"@babel/plugin-syntax-logical-assignment-operators@npm:^7.10.4": - version: 7.10.4 - resolution: "@babel/plugin-syntax-logical-assignment-operators@npm:7.10.4" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.10.4" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/2594cfbe29411ad5bc2ad4058de7b2f6a8c5b86eda525a993959438615479e59c012c14aec979e538d60a584a1a799b60d1b8942c3b18468cb9d99b8fd34cd0b - languageName: node - linkType: hard - -"@babel/plugin-syntax-nullish-coalescing-operator@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-nullish-coalescing-operator@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/2024fbb1162899094cfc81152449b12bd0cc7053c6d4bda8ac2852545c87d0a851b1b72ed9560673cbf3ef6248257262c3c04aabf73117215c1b9cc7dd2542ce - languageName: node - linkType: hard - -"@babel/plugin-syntax-numeric-separator@npm:^7.10.4": - version: 7.10.4 - resolution: "@babel/plugin-syntax-numeric-separator@npm:7.10.4" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.10.4" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/c55a82b3113480942c6aa2fcbe976ff9caa74b7b1109ff4369641dfbc88d1da348aceb3c31b6ed311c84d1e7c479440b961906c735d0ab494f688bf2fd5b9bb9 - languageName: node - linkType: hard - -"@babel/plugin-syntax-object-rest-spread@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-object-rest-spread@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/ee1eab52ea6437e3101a0a7018b0da698545230015fc8ab129d292980ec6dff94d265e9e90070e8ae5fed42f08f1622c14c94552c77bcac784b37f503a82ff26 - languageName: node - linkType: hard - -"@babel/plugin-syntax-optional-catch-binding@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-optional-catch-binding@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/27e2493ab67a8ea6d693af1287f7e9acec206d1213ff107a928e85e173741e1d594196f99fec50e9dde404b09164f39dec5864c767212154ffe1caa6af0bc5af - languageName: node - linkType: hard - -"@babel/plugin-syntax-optional-chaining@npm:^7.8.3": - version: 7.8.3 - resolution: "@babel/plugin-syntax-optional-chaining@npm:7.8.3" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.8.0" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/46edddf2faa6ebf94147b8e8540dfc60a5ab718e2de4d01b2c0bdf250a4d642c2bd47cbcbb739febcb2bf75514dbcefad3c52208787994b8d0f8822490f55e81 - languageName: node - linkType: hard - -"@babel/plugin-syntax-private-property-in-object@npm:^7.14.5": - version: 7.14.5 - resolution: "@babel/plugin-syntax-private-property-in-object@npm:7.14.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.14.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/69822772561706c87f0a65bc92d0772cea74d6bc0911537904a676d5ff496a6d3ac4e05a166d8125fce4a16605bace141afc3611074e170a994e66e5397787f3 - languageName: node - linkType: hard - -"@babel/plugin-syntax-top-level-await@npm:^7.14.5": - version: 7.14.5 - resolution: "@babel/plugin-syntax-top-level-await@npm:7.14.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.14.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/14bf6e65d5bc1231ffa9def5f0ef30b19b51c218fcecaa78cd1bdf7939dfdf23f90336080b7f5196916368e399934ce5d581492d8292b46a2fb569d8b2da106f - languageName: node - linkType: hard - -"@babel/plugin-syntax-typescript@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/plugin-syntax-typescript@npm:7.27.1" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.27.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/11589b4c89c66ef02d57bf56c6246267851ec0c361f58929327dc3e070b0dab644be625bbe7fb4c4df30c3634bfdfe31244e1f517be397d2def1487dbbe3c37d - languageName: node - linkType: hard - -"@babel/template@npm:^7.27.2": - version: 7.27.2 - resolution: "@babel/template@npm:7.27.2" - dependencies: - "@babel/code-frame": "npm:^7.27.1" - "@babel/parser": "npm:^7.27.2" - "@babel/types": "npm:^7.27.1" - checksum: 10c0/ed9e9022651e463cc5f2cc21942f0e74544f1754d231add6348ff1b472985a3b3502041c0be62dc99ed2d12cfae0c51394bf827452b98a2f8769c03b87aadc81 - languageName: node - linkType: hard - -"@babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.27.3, @babel/traverse@npm:^7.27.4": - version: 7.27.4 - resolution: "@babel/traverse@npm:7.27.4" - dependencies: - "@babel/code-frame": "npm:^7.27.1" - "@babel/generator": "npm:^7.27.3" - "@babel/parser": "npm:^7.27.4" - "@babel/template": "npm:^7.27.2" - "@babel/types": "npm:^7.27.3" - debug: "npm:^4.3.1" - globals: "npm:^11.1.0" - checksum: 10c0/6de8aa2a0637a6ee6d205bf48b9e923928a02415771fdec60085ed754dcdf605e450bb3315c2552fa51c31a4662275b45d5ae4ad527ce55a7db9acebdbbbb8ed - languageName: node - linkType: hard - -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.27.6": - version: 7.27.6 - resolution: "@babel/types@npm:7.27.6" - dependencies: - "@babel/helper-string-parser": "npm:^7.27.1" - "@babel/helper-validator-identifier": "npm:^7.27.1" - checksum: 10c0/39d556be114f2a6d874ea25ad39826a9e3a0e98de0233ae6d932f6d09a4b222923a90a7274c635ed61f1ba49bbd345329226678800900ad1c8d11afabd573aaf - languageName: node - linkType: hard - -"@bcoe/v8-coverage@npm:^0.2.3": - version: 0.2.3 - resolution: "@bcoe/v8-coverage@npm:0.2.3" - checksum: 10c0/6b80ae4cb3db53f486da2dc63b6e190a74c8c3cca16bb2733f234a0b6a9382b09b146488ae08e2b22cf00f6c83e20f3e040a2f7894f05c045c946d6a090b1d52 - languageName: node - linkType: hard - -"@colors/colors@npm:1.5.0": - version: 1.5.0 - resolution: "@colors/colors@npm:1.5.0" - checksum: 10c0/eb42729851adca56d19a08e48d5a1e95efd2a32c55ae0323de8119052be0510d4b7a1611f2abcbf28c044a6c11e6b7d38f99fccdad7429300c37a8ea5fb95b44 - languageName: node - linkType: hard - -"@dabh/diagnostics@npm:^2.0.2": - version: 2.0.3 - resolution: "@dabh/diagnostics@npm:2.0.3" - dependencies: - colorspace: "npm:1.1.x" - enabled: "npm:2.0.x" - kuler: "npm:^2.0.0" - checksum: 10c0/a5133df8492802465ed01f2f0a5784585241a1030c362d54a602ed1839816d6c93d71dde05cf2ddb4fd0796238c19774406bd62fa2564b637907b495f52425fe - languageName: node - linkType: hard - -"@emnapi/core@npm:^1.4.3": - version: 1.4.3 - resolution: "@emnapi/core@npm:1.4.3" - dependencies: - "@emnapi/wasi-threads": "npm:1.0.2" - tslib: "npm:^2.4.0" - checksum: 10c0/e30101d16d37ef3283538a35cad60e22095aff2403fb9226a35330b932eb6740b81364d525537a94eb4fb51355e48ae9b10d779c0dd1cdcd55d71461fe4b45c7 - languageName: node - linkType: hard - -"@emnapi/runtime@npm:^1.4.3": - version: 1.4.3 - resolution: "@emnapi/runtime@npm:1.4.3" - dependencies: - tslib: "npm:^2.4.0" - checksum: 10c0/3b7ab72d21cb4e034f07df80165265f85f445ef3f581d1bc87b67e5239428baa00200b68a7d5e37a0425c3a78320b541b07f76c5530f6f6f95336a6294ebf30b - languageName: node - linkType: hard - -"@emnapi/wasi-threads@npm:1.0.2": - version: 1.0.2 - resolution: "@emnapi/wasi-threads@npm:1.0.2" - dependencies: - tslib: "npm:^2.4.0" - checksum: 10c0/f0621b1fc715221bd2d8332c0ca922617bcd77cdb3050eae50a124eb8923c54fa425d23982dc8f29d505c8798a62d1049bace8b0686098ff9dd82270e06d772e - languageName: node - linkType: hard - -"@esbuild/aix-ppc64@npm:0.25.2": - version: 0.25.2 - resolution: "@esbuild/aix-ppc64@npm:0.25.2" - conditions: os=aix & cpu=ppc64 - languageName: node - linkType: hard - -"@esbuild/android-arm64@npm:0.25.2": - version: 0.25.2 - resolution: "@esbuild/android-arm64@npm:0.25.2" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/android-arm@npm:0.25.2": - version: 0.25.2 - resolution: "@esbuild/android-arm@npm:0.25.2" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - -"@esbuild/android-x64@npm:0.25.2": - version: 0.25.2 - resolution: "@esbuild/android-x64@npm:0.25.2" - conditions: os=android & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/darwin-arm64@npm:0.25.2": - version: 0.25.2 - resolution: "@esbuild/darwin-arm64@npm:0.25.2" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/darwin-x64@npm:0.25.2": - version: 0.25.2 - resolution: "@esbuild/darwin-x64@npm:0.25.2" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/freebsd-arm64@npm:0.25.2": - version: 0.25.2 - resolution: "@esbuild/freebsd-arm64@npm:0.25.2" - conditions: os=freebsd & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/freebsd-x64@npm:0.25.2": - version: 0.25.2 - resolution: "@esbuild/freebsd-x64@npm:0.25.2" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/linux-arm64@npm:0.25.2": - version: 0.25.2 - resolution: "@esbuild/linux-arm64@npm:0.25.2" - conditions: os=linux & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/linux-arm@npm:0.25.2": - version: 0.25.2 - resolution: "@esbuild/linux-arm@npm:0.25.2" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - -"@esbuild/linux-ia32@npm:0.25.2": - version: 0.25.2 - resolution: "@esbuild/linux-ia32@npm:0.25.2" - conditions: os=linux & cpu=ia32 - languageName: node - linkType: hard - -"@esbuild/linux-loong64@npm:0.25.2": - version: 0.25.2 - resolution: "@esbuild/linux-loong64@npm:0.25.2" - conditions: os=linux & cpu=loong64 - languageName: node - linkType: hard - -"@esbuild/linux-mips64el@npm:0.25.2": - version: 0.25.2 - resolution: "@esbuild/linux-mips64el@npm:0.25.2" - conditions: os=linux & cpu=mips64el - languageName: node - linkType: hard - -"@esbuild/linux-ppc64@npm:0.25.2": - version: 0.25.2 - resolution: "@esbuild/linux-ppc64@npm:0.25.2" - conditions: os=linux & cpu=ppc64 - languageName: node - linkType: hard - -"@esbuild/linux-riscv64@npm:0.25.2": - version: 0.25.2 - resolution: "@esbuild/linux-riscv64@npm:0.25.2" - conditions: os=linux & cpu=riscv64 - languageName: node - linkType: hard - -"@esbuild/linux-s390x@npm:0.25.2": - version: 0.25.2 - resolution: "@esbuild/linux-s390x@npm:0.25.2" - conditions: os=linux & cpu=s390x - languageName: node - linkType: hard - -"@esbuild/linux-x64@npm:0.25.2": - version: 0.25.2 - resolution: "@esbuild/linux-x64@npm:0.25.2" - conditions: os=linux & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/netbsd-arm64@npm:0.25.2": - version: 0.25.2 - resolution: "@esbuild/netbsd-arm64@npm:0.25.2" - conditions: os=netbsd & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/netbsd-x64@npm:0.25.2": - version: 0.25.2 - resolution: "@esbuild/netbsd-x64@npm:0.25.2" - conditions: os=netbsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/openbsd-arm64@npm:0.25.2": - version: 0.25.2 - resolution: "@esbuild/openbsd-arm64@npm:0.25.2" - conditions: os=openbsd & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/openbsd-x64@npm:0.25.2": - version: 0.25.2 - resolution: "@esbuild/openbsd-x64@npm:0.25.2" - conditions: os=openbsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/sunos-x64@npm:0.25.2": - version: 0.25.2 - resolution: "@esbuild/sunos-x64@npm:0.25.2" - conditions: os=sunos & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/win32-arm64@npm:0.25.2": - version: 0.25.2 - resolution: "@esbuild/win32-arm64@npm:0.25.2" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/win32-ia32@npm:0.25.2": - version: 0.25.2 - resolution: "@esbuild/win32-ia32@npm:0.25.2" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - -"@esbuild/win32-x64@npm:0.25.2": - version: 0.25.2 - resolution: "@esbuild/win32-x64@npm:0.25.2" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - -"@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.7.0": - version: 4.7.0 - resolution: "@eslint-community/eslint-utils@npm:4.7.0" - dependencies: - eslint-visitor-keys: "npm:^3.4.3" - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - checksum: 10c0/c0f4f2bd73b7b7a9de74b716a664873d08ab71ab439e51befe77d61915af41a81ecec93b408778b3a7856185244c34c2c8ee28912072ec14def84ba2dec70adf - languageName: node - linkType: hard - -"@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.12.1": - version: 4.12.1 - resolution: "@eslint-community/regexpp@npm:4.12.1" - checksum: 10c0/a03d98c246bcb9109aec2c08e4d10c8d010256538dcb3f56610191607214523d4fb1b00aa81df830b6dffb74c5fa0be03642513a289c567949d3e550ca11cdf6 - languageName: node - linkType: hard - -"@eslint/config-array@npm:^0.21.0": - version: 0.21.0 - resolution: "@eslint/config-array@npm:0.21.0" - dependencies: - "@eslint/object-schema": "npm:^2.1.6" - debug: "npm:^4.3.1" - minimatch: "npm:^3.1.2" - checksum: 10c0/0ea801139166c4aa56465b309af512ef9b2d3c68f9198751bbc3e21894fe70f25fbf26e1b0e9fffff41857bc21bfddeee58649ae6d79aadcd747db0c5dca771f - languageName: node - linkType: hard - -"@eslint/config-helpers@npm:^0.3.0": - version: 0.3.0 - resolution: "@eslint/config-helpers@npm:0.3.0" - checksum: 10c0/013ae7b189eeae8b30cc2ee87bc5c9c091a9cd615579003290eb28bebad5d78806a478e74ba10b3fe08ed66975b52af7d2cd4b4b43990376412b14e5664878c8 - languageName: node - linkType: hard - -"@eslint/core@npm:^0.14.0": - version: 0.14.0 - resolution: "@eslint/core@npm:0.14.0" - dependencies: - "@types/json-schema": "npm:^7.0.15" - checksum: 10c0/259f279445834ba2d2cbcc18e9d43202a4011fde22f29d5fb802181d66e0f6f0bd1f6b4b4b46663451f545d35134498231bd5e656e18d9034a457824b92b7741 - languageName: node - linkType: hard - -"@eslint/core@npm:^0.15.0": - version: 0.15.0 - resolution: "@eslint/core@npm:0.15.0" - dependencies: - "@types/json-schema": "npm:^7.0.15" - checksum: 10c0/9882c69acfe29743ce473a619d5248589c6687561afaabe8ec8d7ffed07592db16edcca3af022f33ea92fe5f6cfbe3545ee53e89292579d22a944ebaeddcf72d - languageName: node - linkType: hard - -"@eslint/eslintrc@npm:^3.3.1": - version: 3.3.1 - resolution: "@eslint/eslintrc@npm:3.3.1" - dependencies: - ajv: "npm:^6.12.4" - debug: "npm:^4.3.2" - espree: "npm:^10.0.1" - globals: "npm:^14.0.0" - ignore: "npm:^5.2.0" - import-fresh: "npm:^3.2.1" - js-yaml: "npm:^4.1.0" - minimatch: "npm:^3.1.2" - strip-json-comments: "npm:^3.1.1" - checksum: 10c0/b0e63f3bc5cce4555f791a4e487bf999173fcf27c65e1ab6e7d63634d8a43b33c3693e79f192cbff486d7df1be8ebb2bd2edc6e70ddd486cbfa84a359a3e3b41 - languageName: node - linkType: hard - -"@eslint/js@npm:9.30.0, @eslint/js@npm:^9.30.0": - version: 9.30.0 - resolution: "@eslint/js@npm:9.30.0" - checksum: 10c0/aec2df7f4e4e884d693dc27dbf4713c1a48afa327bfadac25ebd0e61a2797ce906f2f2a9be0d7d922acb68ccd68cc88779737811f9769eb4933d1f5e574c469e - languageName: node - linkType: hard - -"@eslint/object-schema@npm:^2.1.6": - version: 2.1.6 - resolution: "@eslint/object-schema@npm:2.1.6" - checksum: 10c0/b8cdb7edea5bc5f6a96173f8d768d3554a628327af536da2fc6967a93b040f2557114d98dbcdbf389d5a7b290985ad6a9ce5babc547f36fc1fde42e674d11a56 - languageName: node - linkType: hard - -"@eslint/plugin-kit@npm:^0.3.1": - version: 0.3.2 - resolution: "@eslint/plugin-kit@npm:0.3.2" - dependencies: - "@eslint/core": "npm:^0.15.0" - levn: "npm:^0.4.1" - checksum: 10c0/e069b0a46eb9fa595a1ac7dea4540a9daa493afba88875ee054e9117609c1c41555e779303cb4cff36cf88f603ba6eba2556a927e8ced77002828206ee17fc7e - languageName: node - linkType: hard - -"@freearhey/core@npm:^0.8.2": - version: 0.8.2 - resolution: "@freearhey/core@npm:0.8.2" - dependencies: - consola: "npm:^3.4.2" - dayjs: "npm:^1.11.13" - fs-extra: "npm:^11.3.0" - glob: "npm:^11.0.1" - lodash: "npm:^4.17.21" - natural-orderby: "npm:^5.0.0" - normalize-url: "npm:^6.1.0" - object-treeify: "npm:^2.1.1" - pako: "npm:^2.1.0" - timer-node: "npm:^5.0.9" - checksum: 10c0/1f08c4db35583dd26e6604290aeabeb66bf2368bd5bbd51a73c951a6d32d2a4ad17b61d5f6c5efa3a1b2449cb76ac5aa4df1f079bcfe36c0e37223c265fba46a - languageName: node - linkType: hard - -"@freearhey/search-js@npm:^0.1.2": - version: 0.1.2 - resolution: "@freearhey/search-js@npm:0.1.2" - dependencies: - lodash: "npm:^4.17.21" - checksum: 10c0/205fb5a7b1a00327ee7967eaef82fce4da27c539d5e4ea267c888d7639d35a1b45d48b593a22cbb2529a9f3d7d18301b26c49419af72aa3621d3b0fe8fef95b2 - languageName: node - linkType: hard - -"@humanfs/core@npm:^0.19.1": - version: 0.19.1 - resolution: "@humanfs/core@npm:0.19.1" - checksum: 10c0/aa4e0152171c07879b458d0e8a704b8c3a89a8c0541726c6b65b81e84fd8b7564b5d6c633feadc6598307d34564bd53294b533491424e8e313d7ab6c7bc5dc67 - languageName: node - linkType: hard - -"@humanfs/node@npm:^0.16.6": - version: 0.16.6 - resolution: "@humanfs/node@npm:0.16.6" - dependencies: - "@humanfs/core": "npm:^0.19.1" - "@humanwhocodes/retry": "npm:^0.3.0" - checksum: 10c0/8356359c9f60108ec204cbd249ecd0356667359b2524886b357617c4a7c3b6aace0fd5a369f63747b926a762a88f8a25bc066fa1778508d110195ce7686243e1 - languageName: node - linkType: hard - -"@humanwhocodes/module-importer@npm:^1.0.1": - version: 1.0.1 - resolution: "@humanwhocodes/module-importer@npm:1.0.1" - checksum: 10c0/909b69c3b86d482c26b3359db16e46a32e0fb30bd306a3c176b8313b9e7313dba0f37f519de6aa8b0a1921349e505f259d19475e123182416a506d7f87e7f529 - languageName: node - linkType: hard - -"@humanwhocodes/retry@npm:^0.3.0": - version: 0.3.1 - resolution: "@humanwhocodes/retry@npm:0.3.1" - checksum: 10c0/f0da1282dfb45e8120480b9e2e275e2ac9bbe1cf016d046fdad8e27cc1285c45bb9e711681237944445157b430093412b4446c1ab3fc4bb037861b5904101d3b - languageName: node - linkType: hard - -"@humanwhocodes/retry@npm:^0.4.2": - version: 0.4.3 - resolution: "@humanwhocodes/retry@npm:0.4.3" - checksum: 10c0/3775bb30087d4440b3f7406d5a057777d90e4b9f435af488a4923ef249e93615fb78565a85f173a186a076c7706a81d0d57d563a2624e4de2c5c9c66c486ce42 - languageName: node - linkType: hard - -"@inquirer/checkbox@npm:^4.1.8": - version: 4.1.8 - resolution: "@inquirer/checkbox@npm:4.1.8" - dependencies: - "@inquirer/core": "npm:^10.1.13" - "@inquirer/figures": "npm:^1.0.12" - "@inquirer/type": "npm:^3.0.7" - ansi-escapes: "npm:^4.3.2" - yoctocolors-cjs: "npm:^2.1.2" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/6d726420b179c55b2f0001aaf6e339fa56e9e939afcbda31c386ab2e5d029ef6f2d392ec99c6a6950af1776a399791bbb88a635e4d047f1170b2ed8c5bba1e4c - languageName: node - linkType: hard - -"@inquirer/confirm@npm:^5.1.12": - version: 5.1.12 - resolution: "@inquirer/confirm@npm:5.1.12" - dependencies: - "@inquirer/core": "npm:^10.1.13" - "@inquirer/type": "npm:^3.0.7" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/581aedfe8ce45e177fb4470a12f874f5162a4396636bf4140edc5812ffc8ed0d1fa7e9bbc3a7af618203089a084f489e0b32112947eedc6930a766fad992449e - languageName: node - linkType: hard - -"@inquirer/core@npm:^10.1.13": - version: 10.1.13 - resolution: "@inquirer/core@npm:10.1.13" - dependencies: - "@inquirer/figures": "npm:^1.0.12" - "@inquirer/type": "npm:^3.0.7" - ansi-escapes: "npm:^4.3.2" - cli-width: "npm:^4.1.0" - mute-stream: "npm:^2.0.0" - signal-exit: "npm:^4.1.0" - wrap-ansi: "npm:^6.2.0" - yoctocolors-cjs: "npm:^2.1.2" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/919208a31307297d5a07a44b9ebe69a999ce1470b31a2e1b5a04538bc36624d2053808cd6c677637a61690af09bdbdd635bd7031b64e3dd86c5b18df3ca7c3f9 - languageName: node - linkType: hard - -"@inquirer/editor@npm:^4.2.13": - version: 4.2.13 - resolution: "@inquirer/editor@npm:4.2.13" - dependencies: - "@inquirer/core": "npm:^10.1.13" - "@inquirer/type": "npm:^3.0.7" - external-editor: "npm:^3.1.0" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/e1a27d75f737d7847905c14cf04d66d864eeb0f3e4cb2d36e34b51993741c5b70c22754171820c5d880a740765471455a8a98874285fd4a10b162342898f6c6b - languageName: node - linkType: hard - -"@inquirer/expand@npm:^4.0.15": - version: 4.0.15 - resolution: "@inquirer/expand@npm:4.0.15" - dependencies: - "@inquirer/core": "npm:^10.1.13" - "@inquirer/type": "npm:^3.0.7" - yoctocolors-cjs: "npm:^2.1.2" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/d558e367995a38a31d830de45d1e6831b73a798d6076c7fc8bdb639d3fac947a5d15810f7336b45c7712fc0e21fe8a2728f7f594550a20b6b4a839a18f9086cb - languageName: node - linkType: hard - -"@inquirer/figures@npm:^1.0.12": - version: 1.0.12 - resolution: "@inquirer/figures@npm:1.0.12" - checksum: 10c0/08694288bdf9aa474571ca94272113a5ac443229519ce71447eba9eb7d5a2007901bdc3e92216d929a69746dcbac29683886c20e67b7864a7c7f6c59b99d3269 - languageName: node - linkType: hard - -"@inquirer/input@npm:^4.1.12": - version: 4.1.12 - resolution: "@inquirer/input@npm:4.1.12" - dependencies: - "@inquirer/core": "npm:^10.1.13" - "@inquirer/type": "npm:^3.0.7" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/17b59547432f54a18ec573fde96c2c13c827f04faf694fc58239ec97e993ac6af151ed2a0521029c9199a4f422742dbe5dc23c20705748eafdc7dd26c7adca3a - languageName: node - linkType: hard - -"@inquirer/number@npm:^3.0.15": - version: 3.0.15 - resolution: "@inquirer/number@npm:3.0.15" - dependencies: - "@inquirer/core": "npm:^10.1.13" - "@inquirer/type": "npm:^3.0.7" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/724fc0d10611a0a9ea43280a94ed9194b8bb22d9a2af940eb37592d0cebc9e6e219edc4f79d8c176f53fd1b078543a9e4773037c7bde4b8d929a3034406eec90 - languageName: node - linkType: hard - -"@inquirer/password@npm:^4.0.15": - version: 4.0.15 - resolution: "@inquirer/password@npm:4.0.15" - dependencies: - "@inquirer/core": "npm:^10.1.13" - "@inquirer/type": "npm:^3.0.7" - ansi-escapes: "npm:^4.3.2" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/673d7c33dd0ee951c96f349d4fb66f8762f31c62188546da4d7af544202b638eecef6b8c78e62f43a46c72a5fa0712d94a56ed56f12e1badbb1001128bc991bd - languageName: node - linkType: hard - -"@inquirer/prompts@npm:^7.5.3": - version: 7.5.3 - resolution: "@inquirer/prompts@npm:7.5.3" - dependencies: - "@inquirer/checkbox": "npm:^4.1.8" - "@inquirer/confirm": "npm:^5.1.12" - "@inquirer/editor": "npm:^4.2.13" - "@inquirer/expand": "npm:^4.0.15" - "@inquirer/input": "npm:^4.1.12" - "@inquirer/number": "npm:^3.0.15" - "@inquirer/password": "npm:^4.0.15" - "@inquirer/rawlist": "npm:^4.1.3" - "@inquirer/search": "npm:^3.0.15" - "@inquirer/select": "npm:^4.2.3" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/14ba6f4a3bf1610d7c46399cd8367db8da1ab8c051ab7ff55003a5b36b5121429e3995e202c08156b7b6e7d4d9d032f39add98764c5ae3a7b4b657eb4926137f - languageName: node - linkType: hard - -"@inquirer/rawlist@npm:^4.1.3": - version: 4.1.3 - resolution: "@inquirer/rawlist@npm:4.1.3" - dependencies: - "@inquirer/core": "npm:^10.1.13" - "@inquirer/type": "npm:^3.0.7" - yoctocolors-cjs: "npm:^2.1.2" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/d653e730188e6849df540186cf7cb0f37f06c64d03f075b5a617145671fb015c27aeb60adb003d1a05a925795968efff0a3ae5a737a8d04c5679aa6fdc423662 - languageName: node - linkType: hard - -"@inquirer/search@npm:^3.0.15": - version: 3.0.15 - resolution: "@inquirer/search@npm:3.0.15" - dependencies: - "@inquirer/core": "npm:^10.1.13" - "@inquirer/figures": "npm:^1.0.12" - "@inquirer/type": "npm:^3.0.7" - yoctocolors-cjs: "npm:^2.1.2" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/32b29789e72e53a7b6cfdbc1803bd9e466c424d9f0368a145bef9e25c6fbde72af29cdd4667a785fee79de213f11fa76453f8120ea02ac5158dce259565ce7fd - languageName: node - linkType: hard - -"@inquirer/select@npm:^4.2.3": - version: 4.2.3 - resolution: "@inquirer/select@npm:4.2.3" - dependencies: - "@inquirer/core": "npm:^10.1.13" - "@inquirer/figures": "npm:^1.0.12" - "@inquirer/type": "npm:^3.0.7" - ansi-escapes: "npm:^4.3.2" - yoctocolors-cjs: "npm:^2.1.2" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/376535f50a9c2e19e27a5c81930cd1b5afa0b7d86228e5789782955a2d0a89bf5a8890a97943042e1b393094fe236ce97c9ff4bb777c9b44b22c1424f883b063 - languageName: node - linkType: hard - -"@inquirer/type@npm:^3.0.7": - version: 3.0.7 - resolution: "@inquirer/type@npm:3.0.7" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/bbaa33c274a10f70d3a587264e1db6dbfcd8c1458d595c54870d1d5b3fc113ab5063203ec12a098485bb9e2fcef1a87d8c6ecd2a6d44ddc575f5c4715379be5e - languageName: node - linkType: hard - -"@isaacs/balanced-match@npm:^4.0.1": - version: 4.0.1 - resolution: "@isaacs/balanced-match@npm:4.0.1" - checksum: 10c0/7da011805b259ec5c955f01cee903da72ad97c5e6f01ca96197267d3f33103d5b2f8a1af192140f3aa64526c593c8d098ae366c2b11f7f17645d12387c2fd420 - languageName: node - linkType: hard - -"@isaacs/brace-expansion@npm:^5.0.0": - version: 5.0.0 - resolution: "@isaacs/brace-expansion@npm:5.0.0" - dependencies: - "@isaacs/balanced-match": "npm:^4.0.1" - checksum: 10c0/b4d4812f4be53afc2c5b6c545001ff7a4659af68d4484804e9d514e183d20269bb81def8682c01a22b17c4d6aed14292c8494f7d2ac664e547101c1a905aa977 - languageName: node - linkType: hard - -"@isaacs/cliui@npm:^8.0.2": - version: 8.0.2 - resolution: "@isaacs/cliui@npm:8.0.2" - dependencies: - string-width: "npm:^5.1.2" - string-width-cjs: "npm:string-width@^4.2.0" - strip-ansi: "npm:^7.0.1" - strip-ansi-cjs: "npm:strip-ansi@^6.0.1" - wrap-ansi: "npm:^8.1.0" - wrap-ansi-cjs: "npm:wrap-ansi@^7.0.0" - checksum: 10c0/b1bf42535d49f11dc137f18d5e4e63a28c5569de438a221c369483731e9dac9fb797af554e8bf02b6192d1e5eba6e6402cf93900c3d0ac86391d00d04876789e - languageName: node - linkType: hard - -"@isaacs/fs-minipass@npm:^4.0.0": - version: 4.0.1 - resolution: "@isaacs/fs-minipass@npm:4.0.1" - dependencies: - minipass: "npm:^7.0.4" - checksum: 10c0/c25b6dc1598790d5b55c0947a9b7d111cfa92594db5296c3b907e2f533c033666f692a3939eadac17b1c7c40d362d0b0635dc874cbfe3e70db7c2b07cc97a5d2 - languageName: node - linkType: hard - -"@istanbuljs/load-nyc-config@npm:^1.0.0": - version: 1.1.0 - resolution: "@istanbuljs/load-nyc-config@npm:1.1.0" - dependencies: - camelcase: "npm:^5.3.1" - find-up: "npm:^4.1.0" - get-package-type: "npm:^0.1.0" - js-yaml: "npm:^3.13.1" - resolve-from: "npm:^5.0.0" - checksum: 10c0/dd2a8b094887da5a1a2339543a4933d06db2e63cbbc2e288eb6431bd832065df0c099d091b6a67436e71b7d6bf85f01ce7c15f9253b4cbebcc3b9a496165ba42 - languageName: node - linkType: hard - -"@istanbuljs/schema@npm:^0.1.2, @istanbuljs/schema@npm:^0.1.3": - version: 0.1.3 - resolution: "@istanbuljs/schema@npm:0.1.3" - checksum: 10c0/61c5286771676c9ca3eb2bd8a7310a9c063fb6e0e9712225c8471c582d157392c88f5353581c8c9adbe0dff98892317d2fdfc56c3499aa42e0194405206a963a - languageName: node - linkType: hard - -"@jest/console@npm:30.0.2": - version: 30.0.2 - resolution: "@jest/console@npm:30.0.2" - dependencies: - "@jest/types": "npm:30.0.1" - "@types/node": "npm:*" - chalk: "npm:^4.1.2" - jest-message-util: "npm:30.0.2" - jest-util: "npm:30.0.2" - slash: "npm:^3.0.0" - checksum: 10c0/24ef330985ff020963e1d82088d0c3a7fbe981a62bc810b7afb71e6565b8c6cbcb5e789d494d3973762efc2dc351770ad05b96568517d370ad9cd8fd33f5acd0 - languageName: node - linkType: hard - -"@jest/core@npm:30.0.3": - version: 30.0.3 - resolution: "@jest/core@npm:30.0.3" - dependencies: - "@jest/console": "npm:30.0.2" - "@jest/pattern": "npm:30.0.1" - "@jest/reporters": "npm:30.0.2" - "@jest/test-result": "npm:30.0.2" - "@jest/transform": "npm:30.0.2" - "@jest/types": "npm:30.0.1" - "@types/node": "npm:*" - ansi-escapes: "npm:^4.3.2" - chalk: "npm:^4.1.2" - ci-info: "npm:^4.2.0" - exit-x: "npm:^0.2.2" - graceful-fs: "npm:^4.2.11" - jest-changed-files: "npm:30.0.2" - jest-config: "npm:30.0.3" - jest-haste-map: "npm:30.0.2" - jest-message-util: "npm:30.0.2" - jest-regex-util: "npm:30.0.1" - jest-resolve: "npm:30.0.2" - jest-resolve-dependencies: "npm:30.0.3" - jest-runner: "npm:30.0.3" - jest-runtime: "npm:30.0.3" - jest-snapshot: "npm:30.0.3" - jest-util: "npm:30.0.2" - jest-validate: "npm:30.0.2" - jest-watcher: "npm:30.0.2" - micromatch: "npm:^4.0.8" - pretty-format: "npm:30.0.2" - slash: "npm:^3.0.0" - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - checksum: 10c0/0608245c0af4d69b8454628488ecdc44ed5cc8fee27d21640ed8c76bef26d34f8f0058f390e7350484d824d8de4f05a3b8b125cea950ca16251df8defe7cffe5 - languageName: node - linkType: hard - -"@jest/create-cache-key-function@npm:^29.7.0": - version: 29.7.0 - resolution: "@jest/create-cache-key-function@npm:29.7.0" - dependencies: - "@jest/types": "npm:^29.6.3" - checksum: 10c0/5c47ef62205264adf77b1ff26b969ce9fe84920b8275c3c5e83f4236859d6ae5e4e7027af99eef04a8e334c4e424d44af3e167972083406070aca733ac2a2795 - languageName: node - linkType: hard - -"@jest/diff-sequences@npm:30.0.1": - version: 30.0.1 - resolution: "@jest/diff-sequences@npm:30.0.1" - checksum: 10c0/3a840404e6021725ef7f86b11f7b2d13dd02846481264db0e447ee33b7ee992134e402cdc8b8b0ac969d37c6c0183044e382dedee72001cdf50cfb3c8088de74 - languageName: node - linkType: hard - -"@jest/environment@npm:30.0.2": - version: 30.0.2 - resolution: "@jest/environment@npm:30.0.2" - dependencies: - "@jest/fake-timers": "npm:30.0.2" - "@jest/types": "npm:30.0.1" - "@types/node": "npm:*" - jest-mock: "npm:30.0.2" - checksum: 10c0/b16683337bd61f4c1134035c9221f92b958b79965be16d4105a5008169a22705edb004ef06cb10f42cbc23464b69bbc0eb5746d60931f764b2cbf2455477b430 - languageName: node - linkType: hard - -"@jest/expect-utils@npm:30.0.2": - version: 30.0.2 - resolution: "@jest/expect-utils@npm:30.0.2" - dependencies: - "@jest/get-type": "npm:30.0.1" - checksum: 10c0/70d40c364170bb3cfabfb53bf24605f0bcb076c3968bdd3a9d9b9e102d3b918e666c53c1866e6bf5d6a0552aa6f7b611e406d5967723a6f8e99f235d01c94469 - languageName: node - linkType: hard - -"@jest/expect-utils@npm:30.0.3": - version: 30.0.3 - resolution: "@jest/expect-utils@npm:30.0.3" - dependencies: - "@jest/get-type": "npm:30.0.1" - checksum: 10c0/b3f662fd02980f12e4ec7b3657a728c13b1343a31b85eafd34363ea8c9a666b60ad156ffa33c1f8d2fce1cb1e06c1236361849eb52b6e31a1442195ed3b3eae0 - languageName: node - linkType: hard - -"@jest/expect@npm:30.0.3": - version: 30.0.3 - resolution: "@jest/expect@npm:30.0.3" - dependencies: - expect: "npm:30.0.3" - jest-snapshot: "npm:30.0.3" - checksum: 10c0/d76f727891df37bd1e93fff73ed4f12d6d77db33adf47cc12500b85951e7e6373e3e6f99d5826ff7c571e578d636e8a1260fd171ba0da0755b9a23b1ef75edbe - languageName: node - linkType: hard - -"@jest/fake-timers@npm:30.0.2": - version: 30.0.2 - resolution: "@jest/fake-timers@npm:30.0.2" - dependencies: - "@jest/types": "npm:30.0.1" - "@sinonjs/fake-timers": "npm:^13.0.0" - "@types/node": "npm:*" - jest-message-util: "npm:30.0.2" - jest-mock: "npm:30.0.2" - jest-util: "npm:30.0.2" - checksum: 10c0/896e727a1146948780998d62e7807214f9e2b0a724e283f19baca4dfe326fb8fb885244eee6d201bc5e1385336c176c093179f080e0fae03b20ec25c02604352 - languageName: node - linkType: hard - -"@jest/get-type@npm:30.0.1": - version: 30.0.1 - resolution: "@jest/get-type@npm:30.0.1" - checksum: 10c0/92437ae42d0df57e8acc2d067288151439db4752cde4f5e680c73c8a6e34568bbd8c1c81a2f2f9a637a619c2aac8bc87553fb80e31475b59e2ed789a71e5e540 - languageName: node - linkType: hard - -"@jest/globals@npm:30.0.3": - version: 30.0.3 - resolution: "@jest/globals@npm:30.0.3" - dependencies: - "@jest/environment": "npm:30.0.2" - "@jest/expect": "npm:30.0.3" - "@jest/types": "npm:30.0.1" - jest-mock: "npm:30.0.2" - checksum: 10c0/b080a924de4ff0cfb5fef4098eb7764efa5bc33de4a59b27116defc8c91ec76e6103c9e9a60cd33e00d060f03302e6c5a56ef8c4fc28133e29ae011b1be78d8e - languageName: node - linkType: hard - -"@jest/pattern@npm:30.0.1": - version: 30.0.1 - resolution: "@jest/pattern@npm:30.0.1" - dependencies: - "@types/node": "npm:*" - jest-regex-util: "npm:30.0.1" - checksum: 10c0/32c5a7bfb6c591f004dac0ed36d645002ed168971e4c89bd915d1577031672870032594767557b855c5bc330aa1e39a2f54bf150d2ee88a7a0886e9cb65318bc - languageName: node - linkType: hard - -"@jest/reporters@npm:30.0.2": - version: 30.0.2 - resolution: "@jest/reporters@npm:30.0.2" - dependencies: - "@bcoe/v8-coverage": "npm:^0.2.3" - "@jest/console": "npm:30.0.2" - "@jest/test-result": "npm:30.0.2" - "@jest/transform": "npm:30.0.2" - "@jest/types": "npm:30.0.1" - "@jridgewell/trace-mapping": "npm:^0.3.25" - "@types/node": "npm:*" - chalk: "npm:^4.1.2" - collect-v8-coverage: "npm:^1.0.2" - exit-x: "npm:^0.2.2" - glob: "npm:^10.3.10" - graceful-fs: "npm:^4.2.11" - istanbul-lib-coverage: "npm:^3.0.0" - istanbul-lib-instrument: "npm:^6.0.0" - istanbul-lib-report: "npm:^3.0.0" - istanbul-lib-source-maps: "npm:^5.0.0" - istanbul-reports: "npm:^3.1.3" - jest-message-util: "npm:30.0.2" - jest-util: "npm:30.0.2" - jest-worker: "npm:30.0.2" - slash: "npm:^3.0.0" - string-length: "npm:^4.0.2" - v8-to-istanbul: "npm:^9.0.1" - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - checksum: 10c0/4931fd1f3ae1236fba8f6068b8949b3788fe367ff2eaaa88293988344f50dcb5c15a4063a65cc4485546504bb3b85e2e6667c68acca249d3597b97425bbc2ee5 - languageName: node - linkType: hard - -"@jest/schemas@npm:30.0.1": - version: 30.0.1 - resolution: "@jest/schemas@npm:30.0.1" - dependencies: - "@sinclair/typebox": "npm:^0.34.0" - checksum: 10c0/27977359edc4b33293af7c85c53de5014a87c29b9ab98b0a827fedfc6635abdb522aad8c3ff276080080911f519699b094bd6f4e151b43f0cc5856ccc83c04a7 - languageName: node - linkType: hard - -"@jest/schemas@npm:^29.6.3": - version: 29.6.3 - resolution: "@jest/schemas@npm:29.6.3" - dependencies: - "@sinclair/typebox": "npm:^0.27.8" - checksum: 10c0/b329e89cd5f20b9278ae1233df74016ebf7b385e0d14b9f4c1ad18d096c4c19d1e687aa113a9c976b16ec07f021ae53dea811fb8c1248a50ac34fbe009fdf6be - languageName: node - linkType: hard - -"@jest/snapshot-utils@npm:30.0.1": - version: 30.0.1 - resolution: "@jest/snapshot-utils@npm:30.0.1" - dependencies: - "@jest/types": "npm:30.0.1" - chalk: "npm:^4.1.2" - graceful-fs: "npm:^4.2.11" - natural-compare: "npm:^1.4.0" - checksum: 10c0/a90f09733ca98e695bc2850afdbb0a9d958f4f8805b0e5420cba210422c5bfeb097de57bf66436006f3d5cc3da4109e1e65f6c3e2947474a4911f4d22a8496e8 - languageName: node - linkType: hard - -"@jest/source-map@npm:30.0.1": - version: 30.0.1 - resolution: "@jest/source-map@npm:30.0.1" - dependencies: - "@jridgewell/trace-mapping": "npm:^0.3.25" - callsites: "npm:^3.1.0" - graceful-fs: "npm:^4.2.11" - checksum: 10c0/e7bda2786fc9f483d9dd7566c58c4bd948830997be862dfe80a3ae5550ff3f84753abb52e705d02ebe9db9f34ba7ebec4c2db11882048cdeef7a66f6332b3897 - languageName: node - linkType: hard - -"@jest/test-result@npm:30.0.2": - version: 30.0.2 - resolution: "@jest/test-result@npm:30.0.2" - dependencies: - "@jest/console": "npm:30.0.2" - "@jest/types": "npm:30.0.1" - "@types/istanbul-lib-coverage": "npm:^2.0.6" - collect-v8-coverage: "npm:^1.0.2" - checksum: 10c0/f2a1d5b3f1c8f786acc76b77c72a73dc314e579a4ea91ad5ad19e9906156ffa17b56a69cab33cffd1d9be32cfc5f98c60a92fceedd4c700280933b8a14de4e35 - languageName: node - linkType: hard - -"@jest/test-sequencer@npm:30.0.2": - version: 30.0.2 - resolution: "@jest/test-sequencer@npm:30.0.2" - dependencies: - "@jest/test-result": "npm:30.0.2" - graceful-fs: "npm:^4.2.11" - jest-haste-map: "npm:30.0.2" - slash: "npm:^3.0.0" - checksum: 10c0/5d6d74a8c530db1fac4ba085b6a27e98b52a196e2d88d53462771f3a8e8165d3f593a3cea28ed73951cbaf95ba80c7389719c58e99cb3700f0ad122376d1430b - languageName: node - linkType: hard - -"@jest/transform@npm:30.0.2": - version: 30.0.2 - resolution: "@jest/transform@npm:30.0.2" - dependencies: - "@babel/core": "npm:^7.27.4" - "@jest/types": "npm:30.0.1" - "@jridgewell/trace-mapping": "npm:^0.3.25" - babel-plugin-istanbul: "npm:^7.0.0" - chalk: "npm:^4.1.2" - convert-source-map: "npm:^2.0.0" - fast-json-stable-stringify: "npm:^2.1.0" - graceful-fs: "npm:^4.2.11" - jest-haste-map: "npm:30.0.2" - jest-regex-util: "npm:30.0.1" - jest-util: "npm:30.0.2" - micromatch: "npm:^4.0.8" - pirates: "npm:^4.0.7" - slash: "npm:^3.0.0" - write-file-atomic: "npm:^5.0.1" - checksum: 10c0/2ab4c049b2c4851dd7abc9f837565c7b3feb5d395955608d929c5caffc0052955a0216c20bf5db1eebef9b9a888cec508a1ea3b6237648cc1f77fea00b2321dd - languageName: node - linkType: hard - -"@jest/types@npm:30.0.1": - version: 30.0.1 - resolution: "@jest/types@npm:30.0.1" - dependencies: - "@jest/pattern": "npm:30.0.1" - "@jest/schemas": "npm:30.0.1" - "@types/istanbul-lib-coverage": "npm:^2.0.6" - "@types/istanbul-reports": "npm:^3.0.4" - "@types/node": "npm:*" - "@types/yargs": "npm:^17.0.33" - chalk: "npm:^4.1.2" - checksum: 10c0/407469331e74f9bb1ffd40202c3a8cece2fd07ba535adeb60557bdcee13713cf2f14cf78869ba7ef50a7e6fe0ed7cc97ec775056dd640fc0a332e8fbfaec1ee8 - languageName: node - linkType: hard - -"@jest/types@npm:^29.6.3": - version: 29.6.3 - resolution: "@jest/types@npm:29.6.3" - dependencies: - "@jest/schemas": "npm:^29.6.3" - "@types/istanbul-lib-coverage": "npm:^2.0.0" - "@types/istanbul-reports": "npm:^3.0.0" - "@types/node": "npm:*" - "@types/yargs": "npm:^17.0.8" - chalk: "npm:^4.0.0" - checksum: 10c0/ea4e493dd3fb47933b8ccab201ae573dcc451f951dc44ed2a86123cd8541b82aa9d2b1031caf9b1080d6673c517e2dcc25a44b2dc4f3fbc37bfc965d444888c0 - languageName: node - linkType: hard - -"@jridgewell/gen-mapping@npm:^0.3.5": - version: 0.3.8 - resolution: "@jridgewell/gen-mapping@npm:0.3.8" - dependencies: - "@jridgewell/set-array": "npm:^1.2.1" - "@jridgewell/sourcemap-codec": "npm:^1.4.10" - "@jridgewell/trace-mapping": "npm:^0.3.24" - checksum: 10c0/c668feaf86c501d7c804904a61c23c67447b2137b813b9ce03eca82cb9d65ac7006d766c218685d76e3d72828279b6ee26c347aa1119dab23fbaf36aed51585a - languageName: node - linkType: hard - -"@jridgewell/resolve-uri@npm:^3.1.0": - version: 3.1.2 - resolution: "@jridgewell/resolve-uri@npm:3.1.2" - checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e - languageName: node - linkType: hard - -"@jridgewell/set-array@npm:^1.2.1": - version: 1.2.1 - resolution: "@jridgewell/set-array@npm:1.2.1" - checksum: 10c0/2a5aa7b4b5c3464c895c802d8ae3f3d2b92fcbe84ad12f8d0bfbb1f5ad006717e7577ee1fd2eac00c088abe486c7adb27976f45d2941ff6b0b92b2c3302c60f4 - languageName: node - linkType: hard - -"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14": - version: 1.5.0 - resolution: "@jridgewell/sourcemap-codec@npm:1.5.0" - checksum: 10c0/2eb864f276eb1096c3c11da3e9bb518f6d9fc0023c78344cdc037abadc725172c70314bdb360f2d4b7bffec7f5d657ce006816bc5d4ecb35e61b66132db00c18 - languageName: node - linkType: hard - -"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.23, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": - version: 0.3.25 - resolution: "@jridgewell/trace-mapping@npm:0.3.25" - dependencies: - "@jridgewell/resolve-uri": "npm:^3.1.0" - "@jridgewell/sourcemap-codec": "npm:^1.4.14" - checksum: 10c0/3d1ce6ebc69df9682a5a8896b414c6537e428a1d68b02fcc8363b04284a8ca0df04d0ee3013132252ab14f2527bc13bea6526a912ecb5658f0e39fd2860b4df4 - languageName: node - linkType: hard - -"@napi-rs/wasm-runtime@npm:^0.2.11": - version: 0.2.11 - resolution: "@napi-rs/wasm-runtime@npm:0.2.11" - dependencies: - "@emnapi/core": "npm:^1.4.3" - "@emnapi/runtime": "npm:^1.4.3" - "@tybys/wasm-util": "npm:^0.9.0" - checksum: 10c0/049bd14c58b99fbe0967b95e9921c5503df196b59be22948d2155f17652eb305cff6728efd8685338b855da7e476dd2551fbe3a313fc2d810938f0717478441e - languageName: node - linkType: hard - -"@nodelib/fs.scandir@npm:2.1.5": - version: 2.1.5 - resolution: "@nodelib/fs.scandir@npm:2.1.5" - dependencies: - "@nodelib/fs.stat": "npm:2.0.5" - run-parallel: "npm:^1.1.9" - checksum: 10c0/732c3b6d1b1e967440e65f284bd06e5821fedf10a1bea9ed2bb75956ea1f30e08c44d3def9d6a230666574edbaf136f8cfd319c14fd1f87c66e6a44449afb2eb - languageName: node - linkType: hard - -"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": - version: 2.0.5 - resolution: "@nodelib/fs.stat@npm:2.0.5" - checksum: 10c0/88dafe5e3e29a388b07264680dc996c17f4bda48d163a9d4f5c1112979f0ce8ec72aa7116122c350b4e7976bc5566dc3ddb579be1ceaacc727872eb4ed93926d - languageName: node - linkType: hard - -"@nodelib/fs.walk@npm:^1.2.3": - version: 1.2.8 - resolution: "@nodelib/fs.walk@npm:1.2.8" - dependencies: - "@nodelib/fs.scandir": "npm:2.1.5" - fastq: "npm:^1.6.0" - checksum: 10c0/db9de047c3bb9b51f9335a7bb46f4fcfb6829fb628318c12115fbaf7d369bfce71c15b103d1fc3b464812d936220ee9bc1c8f762d032c9f6be9acc99249095b1 - languageName: node - linkType: hard - -"@npmcli/agent@npm:^3.0.0": - version: 3.0.0 - resolution: "@npmcli/agent@npm:3.0.0" - dependencies: - agent-base: "npm:^7.1.0" - http-proxy-agent: "npm:^7.0.0" - https-proxy-agent: "npm:^7.0.1" - lru-cache: "npm:^10.0.1" - socks-proxy-agent: "npm:^8.0.3" - checksum: 10c0/efe37b982f30740ee77696a80c196912c274ecd2cb243bc6ae7053a50c733ce0f6c09fda085145f33ecf453be19654acca74b69e81eaad4c90f00ccffe2f9271 - languageName: node - linkType: hard - -"@npmcli/fs@npm:^4.0.0": - version: 4.0.0 - resolution: "@npmcli/fs@npm:4.0.0" - dependencies: - semver: "npm:^7.3.5" - checksum: 10c0/c90935d5ce670c87b6b14fab04a965a3b8137e585f8b2a6257263bd7f97756dd736cb165bb470e5156a9e718ecd99413dccc54b1138c1a46d6ec7cf325982fe5 - languageName: node - linkType: hard - -"@ntlab/sfetch@npm:^1.2.0": - version: 1.2.0 - resolution: "@ntlab/sfetch@npm:1.2.0" - dependencies: - axios: "npm:^1.7.9" - checksum: 10c0/e814cbb24e8f253d3f075c0ad35212cdd5b34756e7cfa6d4cd82b18985d7e32680ba6ef8dbb50034e8800dc969cb456cb7790cba13af4ecc451e9d99597401d8 - languageName: node - linkType: hard - -"@octokit/auth-token@npm:^6.0.0": - version: 6.0.0 - resolution: "@octokit/auth-token@npm:6.0.0" - checksum: 10c0/32ecc904c5f6f4e5d090bfcc679d70318690c0a0b5040cd9a25811ad9dcd44c33f2cf96b6dbee1cd56cf58fde28fb1819c01b58718aa5c971f79c822357cb5c0 - languageName: node - linkType: hard - -"@octokit/core@npm:^7.0.2": - version: 7.0.2 - resolution: "@octokit/core@npm:7.0.2" - dependencies: - "@octokit/auth-token": "npm:^6.0.0" - "@octokit/graphql": "npm:^9.0.1" - "@octokit/request": "npm:^10.0.2" - "@octokit/request-error": "npm:^7.0.0" - "@octokit/types": "npm:^14.0.0" - before-after-hook: "npm:^4.0.0" - universal-user-agent: "npm:^7.0.0" - checksum: 10c0/845a6ff07fcf307b4eab29119123cba698b9edcf93539a8cb4fc99b7e041573ac047d50b30cf7ebbe368fc18b29cdb9f30fdfcffb26267492d7c767d100fc25f - languageName: node - linkType: hard - -"@octokit/endpoint@npm:^11.0.0": - version: 11.0.0 - resolution: "@octokit/endpoint@npm:11.0.0" - dependencies: - "@octokit/types": "npm:^14.0.0" - universal-user-agent: "npm:^7.0.2" - checksum: 10c0/ba929128af5327393fdb3a31f416277ae3036a44566d35955a4eddd484a15b5ddc6abe219a56355f3313c7197d59f4e8bf574a4f0a8680bc1c8725b88433d391 - languageName: node - linkType: hard - -"@octokit/graphql@npm:^9.0.1": - version: 9.0.1 - resolution: "@octokit/graphql@npm:9.0.1" - dependencies: - "@octokit/request": "npm:^10.0.2" - "@octokit/types": "npm:^14.0.0" - universal-user-agent: "npm:^7.0.0" - checksum: 10c0/d80ec923b7624e8a7c84430a287ff18da3c77058e3166ce8e9a67950af00e88767f85d973b4032fc837b67b72d02b323aff2d8f7eeae1ae463bde1a51ddcb83d - languageName: node - linkType: hard - -"@octokit/openapi-types@npm:^25.1.0": - version: 25.1.0 - resolution: "@octokit/openapi-types@npm:25.1.0" - checksum: 10c0/b5b1293b11c6ec7112c7a2713f8507c2696d5db8902ce893b594080ab0329f5a6fcda1b5ac6fe6eed9425e897f4d03326c1bdf5c337e35d324e7b925e52a2661 - languageName: node - linkType: hard - -"@octokit/plugin-paginate-rest@npm:^13.1.1": - version: 13.1.1 - resolution: "@octokit/plugin-paginate-rest@npm:13.1.1" - dependencies: - "@octokit/types": "npm:^14.1.0" - peerDependencies: - "@octokit/core": ">=6" - checksum: 10c0/88d80608881df88f8e832856e9279ac1c1af30ced9adb7c847f4d120b4bb308c2ab9d791ffd4c9585759e57a938798b4c3f2f988a389f2d78a61aaaebc36ffa7 - languageName: node - linkType: hard - -"@octokit/plugin-rest-endpoint-methods@npm:^16.0.0": - version: 16.0.0 - resolution: "@octokit/plugin-rest-endpoint-methods@npm:16.0.0" - dependencies: - "@octokit/types": "npm:^14.1.0" - peerDependencies: - "@octokit/core": ">=6" - checksum: 10c0/6cfe068dbd550bd5914374e65b89482b9deac29f6c26bf02ab6298e956d95b62fc15a2a49dfc6ff76f5938c6ff7fdfe5b7eccdb7551eaff8b1daf7394bc946cb - languageName: node - linkType: hard - -"@octokit/request-error@npm:^7.0.0": - version: 7.0.0 - resolution: "@octokit/request-error@npm:7.0.0" - dependencies: - "@octokit/types": "npm:^14.0.0" - checksum: 10c0/e52bdd832a0187d66b20da5716c374d028f63d824908a9e16cad462754324083839b11cf6956e1d23f6112d3c77f17334ebbd80f49d56840b2b03ed9abef8cb0 - languageName: node - linkType: hard - -"@octokit/request@npm:^10.0.2": - version: 10.0.2 - resolution: "@octokit/request@npm:10.0.2" - dependencies: - "@octokit/endpoint": "npm:^11.0.0" - "@octokit/request-error": "npm:^7.0.0" - "@octokit/types": "npm:^14.0.0" - fast-content-type-parse: "npm:^3.0.0" - universal-user-agent: "npm:^7.0.2" - checksum: 10c0/9376a7ec15825e2ecbf6b526358ce70352286071c5dc97423236dfcf91d1a74ffa41cfb3b7c786a85a3afceadd7364c1d1afe718964b4dbdcc2f24457440fa23 - languageName: node - linkType: hard - -"@octokit/types@npm:^14.0.0, @octokit/types@npm:^14.1.0": - version: 14.1.0 - resolution: "@octokit/types@npm:14.1.0" - dependencies: - "@octokit/openapi-types": "npm:^25.1.0" - checksum: 10c0/4640a6c0a95386be4d015b96c3a906756ea657f7df3c6e706d19fea6bf3ac44fd2991c8c817afe1e670ff9042b85b0e06f7fd373f6bbd47da64208701bb46d5b - languageName: node - linkType: hard - -"@pkgjs/parseargs@npm:^0.11.0": - version: 0.11.0 - resolution: "@pkgjs/parseargs@npm:0.11.0" - checksum: 10c0/5bd7576bb1b38a47a7fc7b51ac9f38748e772beebc56200450c4a817d712232b8f1d3ef70532c80840243c657d491cf6a6be1e3a214cff907645819fdc34aadd - languageName: node - linkType: hard - -"@pkgr/core@npm:^0.2.4": - version: 0.2.7 - resolution: "@pkgr/core@npm:0.2.7" - checksum: 10c0/951f5ebf2feb6e9dbc202d937f1a364d60f2bf0e3e53594251bcc1d9d2ed0df0a919c49ba162a9499fce73cf46ebe4d7959a8dfbac03511dbe79b69f5fedb804 - languageName: node - linkType: hard - -"@pm2/agent@npm:~2.1.1": - version: 2.1.1 - resolution: "@pm2/agent@npm:2.1.1" - dependencies: - async: "npm:~3.2.0" - chalk: "npm:~3.0.0" - dayjs: "npm:~1.8.24" - debug: "npm:~4.3.1" - eventemitter2: "npm:~5.0.1" - fast-json-patch: "npm:^3.1.0" - fclone: "npm:~1.0.11" - pm2-axon: "npm:~4.0.1" - pm2-axon-rpc: "npm:~0.7.0" - proxy-agent: "npm:~6.4.0" - semver: "npm:~7.5.0" - ws: "npm:~7.5.10" - checksum: 10c0/fdd4512f126bda1beffe7761820532e2f07b7d36e1a8f50ea0c9a7363aee43abbf0a47fb2d56f34c497c99cd74bee0fe4117b90ed140ddd3c0e1b27bd621eed7 - languageName: node - linkType: hard - -"@pm2/io@npm:~6.1.0": - version: 6.1.0 - resolution: "@pm2/io@npm:6.1.0" - dependencies: - async: "npm:~2.6.1" - debug: "npm:~4.3.1" - eventemitter2: "npm:^6.3.1" - require-in-the-middle: "npm:^5.0.0" - semver: "npm:~7.5.4" - shimmer: "npm:^1.2.0" - signal-exit: "npm:^3.0.3" - tslib: "npm:1.9.3" - checksum: 10c0/6a9b095d60c8a3790dad95e8a68807c89c718c228802bb6d4ad22b721323706cabaec9a56a6c979b6c5a472af068aba62dab3c8bb7178a9c6e2092278444eb7c - languageName: node - linkType: hard - -"@pm2/js-api@npm:~0.8.0": - version: 0.8.0 - resolution: "@pm2/js-api@npm:0.8.0" - dependencies: - async: "npm:^2.6.3" - debug: "npm:~4.3.1" - eventemitter2: "npm:^6.3.1" - extrareqp2: "npm:^1.0.0" - ws: "npm:^7.0.0" - checksum: 10c0/a309bbf9db71fb4c954ccec8d151362570e759ad382ef7f7d7b365e4036041504b7129a8ac12c4dc3414d4ede2bd8ac624122f26b4c98a51eb3d172d69ba9c83 - languageName: node - linkType: hard - -"@pm2/pm2-version-check@npm:latest": - version: 1.0.4 - resolution: "@pm2/pm2-version-check@npm:1.0.4" - dependencies: - debug: "npm:^4.3.1" - checksum: 10c0/6b0e20fb41e2e771eed98de5ab82acf21bc027ce479773c8daf010d03b103d35c70d8d6e8fd6ac848ef300c86b5433a6ed9f6b368f1cc93d921691364013c49b - languageName: node - linkType: hard - -"@seald-io/binary-search-tree@npm:^1.0.3": - version: 1.0.3 - resolution: "@seald-io/binary-search-tree@npm:1.0.3" - checksum: 10c0/cf86bfca881c84cf22fa11a96dffd358f58424bd779c9afb7e36934be2e8ad923da54252972a12a2d42a9473557829a573950b0f78213b901ceb9dcc862397e1 - languageName: node - linkType: hard - -"@seald-io/nedb@npm:^4.0.2": - version: 4.1.1 - resolution: "@seald-io/nedb@npm:4.1.1" - dependencies: - "@seald-io/binary-search-tree": "npm:^1.0.3" - localforage: "npm:^1.9.0" - util: "npm:^0.12.4" - checksum: 10c0/048baeccbcf01f235c1f17d495a26fcb26b94ee3e9b5126cc0d278b39d5cda7e4499a66181d0b6b6473fecc0a3e8e3c2cd31a49d9f84dce05c232b045dffc477 - languageName: node - linkType: hard - -"@sinclair/typebox@npm:^0.27.8": - version: 0.27.8 - resolution: "@sinclair/typebox@npm:0.27.8" - checksum: 10c0/ef6351ae073c45c2ac89494dbb3e1f87cc60a93ce4cde797b782812b6f97da0d620ae81973f104b43c9b7eaa789ad20ba4f6a1359f1cc62f63729a55a7d22d4e - languageName: node - linkType: hard - -"@sinclair/typebox@npm:^0.34.0": - version: 0.34.35 - resolution: "@sinclair/typebox@npm:0.34.35" - checksum: 10c0/2e10a655eafeee803d16abeb270d4e365caf26178842c4d64c5fa81f1e85de75a41310098299c460c54b1ed14f1cd196e726fc7fc54c263a31b42479d53379ac - languageName: node - linkType: hard - -"@sinonjs/commons@npm:^3.0.1": - version: 3.0.1 - resolution: "@sinonjs/commons@npm:3.0.1" - dependencies: - type-detect: "npm:4.0.8" - checksum: 10c0/1227a7b5bd6c6f9584274db996d7f8cee2c8c350534b9d0141fc662eaf1f292ea0ae3ed19e5e5271c8fd390d27e492ca2803acd31a1978be2cdc6be0da711403 - languageName: node - linkType: hard - -"@sinonjs/fake-timers@npm:^13.0.0": - version: 13.0.5 - resolution: "@sinonjs/fake-timers@npm:13.0.5" - dependencies: - "@sinonjs/commons": "npm:^3.0.1" - checksum: 10c0/a707476efd523d2138ef6bba916c83c4a377a8372ef04fad87499458af9f01afc58f4f245c5fd062793d6d70587309330c6f96947b5bd5697961c18004dc3e26 - languageName: node - linkType: hard - -"@swc/core-darwin-arm64@npm:1.12.7": - version: 1.12.7 - resolution: "@swc/core-darwin-arm64@npm:1.12.7" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"@swc/core-darwin-x64@npm:1.12.7": - version: 1.12.7 - resolution: "@swc/core-darwin-x64@npm:1.12.7" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"@swc/core-linux-arm-gnueabihf@npm:1.12.7": - version: 1.12.7 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.12.7" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - -"@swc/core-linux-arm64-gnu@npm:1.12.7": - version: 1.12.7 - resolution: "@swc/core-linux-arm64-gnu@npm:1.12.7" - conditions: os=linux & cpu=arm64 & libc=glibc - languageName: node - linkType: hard - -"@swc/core-linux-arm64-musl@npm:1.12.7": - version: 1.12.7 - resolution: "@swc/core-linux-arm64-musl@npm:1.12.7" - conditions: os=linux & cpu=arm64 & libc=musl - languageName: node - linkType: hard - -"@swc/core-linux-x64-gnu@npm:1.12.7": - version: 1.12.7 - resolution: "@swc/core-linux-x64-gnu@npm:1.12.7" - conditions: os=linux & cpu=x64 & libc=glibc - languageName: node - linkType: hard - -"@swc/core-linux-x64-musl@npm:1.12.7": - version: 1.12.7 - resolution: "@swc/core-linux-x64-musl@npm:1.12.7" - conditions: os=linux & cpu=x64 & libc=musl - languageName: node - linkType: hard - -"@swc/core-win32-arm64-msvc@npm:1.12.7": - version: 1.12.7 - resolution: "@swc/core-win32-arm64-msvc@npm:1.12.7" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"@swc/core-win32-ia32-msvc@npm:1.12.7": - version: 1.12.7 - resolution: "@swc/core-win32-ia32-msvc@npm:1.12.7" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - -"@swc/core-win32-x64-msvc@npm:1.12.7": - version: 1.12.7 - resolution: "@swc/core-win32-x64-msvc@npm:1.12.7" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - -"@swc/core@npm:^1.12.7": - version: 1.12.7 - resolution: "@swc/core@npm:1.12.7" - dependencies: - "@swc/core-darwin-arm64": "npm:1.12.7" - "@swc/core-darwin-x64": "npm:1.12.7" - "@swc/core-linux-arm-gnueabihf": "npm:1.12.7" - "@swc/core-linux-arm64-gnu": "npm:1.12.7" - "@swc/core-linux-arm64-musl": "npm:1.12.7" - "@swc/core-linux-x64-gnu": "npm:1.12.7" - "@swc/core-linux-x64-musl": "npm:1.12.7" - "@swc/core-win32-arm64-msvc": "npm:1.12.7" - "@swc/core-win32-ia32-msvc": "npm:1.12.7" - "@swc/core-win32-x64-msvc": "npm:1.12.7" - "@swc/counter": "npm:^0.1.3" - "@swc/types": "npm:^0.1.23" - peerDependencies: - "@swc/helpers": ">=0.5.17" - dependenciesMeta: - "@swc/core-darwin-arm64": - optional: true - "@swc/core-darwin-x64": - optional: true - "@swc/core-linux-arm-gnueabihf": - optional: true - "@swc/core-linux-arm64-gnu": - optional: true - "@swc/core-linux-arm64-musl": - optional: true - "@swc/core-linux-x64-gnu": - optional: true - "@swc/core-linux-x64-musl": - optional: true - "@swc/core-win32-arm64-msvc": - optional: true - "@swc/core-win32-ia32-msvc": - optional: true - "@swc/core-win32-x64-msvc": - optional: true - peerDependenciesMeta: - "@swc/helpers": - optional: true - checksum: 10c0/c44aa91c433937b512ed902ee1ad9d8fc79e9f914b6291685e912e963002dbb0b86374f22be1435fd44515d03232212b0125acb2c12cf84f01fea3d2eb008656 - languageName: node - linkType: hard - -"@swc/counter@npm:^0.1.3": - version: 0.1.3 - resolution: "@swc/counter@npm:0.1.3" - checksum: 10c0/8424f60f6bf8694cfd2a9bca45845bce29f26105cda8cf19cdb9fd3e78dc6338699e4db77a89ae449260bafa1cc6bec307e81e7fb96dbf7dcfce0eea55151356 - languageName: node - linkType: hard - -"@swc/jest@npm:^0.2.38": - version: 0.2.38 - resolution: "@swc/jest@npm:0.2.38" - dependencies: - "@jest/create-cache-key-function": "npm:^29.7.0" - "@swc/counter": "npm:^0.1.3" - jsonc-parser: "npm:^3.2.0" - peerDependencies: - "@swc/core": "*" - checksum: 10c0/d92078dd6a32c2c1106d4eeb6b78340bedad9c2a27f1aa29b69ba638942d34f1dbf6eb4ef75692d2297c66e7442e9b355ab6b879540f9cf8a37f644a5a6e6924 - languageName: node - linkType: hard - -"@swc/types@npm:^0.1.23": - version: 0.1.23 - resolution: "@swc/types@npm:0.1.23" - dependencies: - "@swc/counter": "npm:^0.1.3" - checksum: 10c0/edbfe4a72257f40137e27b537bc17d47ccab28de7727471b859c00a1e67f5feac5e01e4b4e0a2365907ce024bb8c3de4b26b6260733e1b601094db54ae9b7477 - languageName: node - linkType: hard - -"@tootallnate/quickjs-emscripten@npm:^0.23.0": - version: 0.23.0 - resolution: "@tootallnate/quickjs-emscripten@npm:0.23.0" - checksum: 10c0/2a939b781826fb5fd3edd0f2ec3b321d259d760464cf20611c9877205aaca3ccc0b7304dea68416baa0d568e82cd86b17d29548d1e5139fa3155a4a86a2b4b49 - languageName: node - linkType: hard - -"@tybys/wasm-util@npm:^0.9.0": - version: 0.9.0 - resolution: "@tybys/wasm-util@npm:0.9.0" - dependencies: - tslib: "npm:^2.4.0" - checksum: 10c0/f9fde5c554455019f33af6c8215f1a1435028803dc2a2825b077d812bed4209a1a64444a4ca0ce2ea7e1175c8d88e2f9173a36a33c199e8a5c671aa31de8242d - languageName: node - linkType: hard - -"@types/babel__core@npm:^7.20.5": - version: 7.20.5 - resolution: "@types/babel__core@npm:7.20.5" - dependencies: - "@babel/parser": "npm:^7.20.7" - "@babel/types": "npm:^7.20.7" - "@types/babel__generator": "npm:*" - "@types/babel__template": "npm:*" - "@types/babel__traverse": "npm:*" - checksum: 10c0/bdee3bb69951e833a4b811b8ee9356b69a61ed5b7a23e1a081ec9249769117fa83aaaf023bb06562a038eb5845155ff663e2d5c75dd95c1d5ccc91db012868ff - languageName: node - linkType: hard - -"@types/babel__generator@npm:*": - version: 7.27.0 - resolution: "@types/babel__generator@npm:7.27.0" - dependencies: - "@babel/types": "npm:^7.0.0" - checksum: 10c0/9f9e959a8792df208a9d048092fda7e1858bddc95c6314857a8211a99e20e6830bdeb572e3587ae8be5429e37f2a96fcf222a9f53ad232f5537764c9e13a2bbd - languageName: node - linkType: hard - -"@types/babel__template@npm:*": - version: 7.4.4 - resolution: "@types/babel__template@npm:7.4.4" - dependencies: - "@babel/parser": "npm:^7.1.0" - "@babel/types": "npm:^7.0.0" - checksum: 10c0/cc84f6c6ab1eab1427e90dd2b76ccee65ce940b778a9a67be2c8c39e1994e6f5bbc8efa309f6cea8dc6754994524cd4d2896558df76d92e7a1f46ecffee7112b - languageName: node - linkType: hard - -"@types/babel__traverse@npm:*": - version: 7.20.7 - resolution: "@types/babel__traverse@npm:7.20.7" - dependencies: - "@babel/types": "npm:^7.20.7" - checksum: 10c0/5386f0af44f8746b063b87418f06129a814e16bb2686965a575e9d7376b360b088b89177778d8c426012abc43dd1a2d8ec3218bfc382280c898682746ce2ffbd - languageName: node - linkType: hard - -"@types/cli-progress@npm:^3.11.6": - version: 3.11.6 - resolution: "@types/cli-progress@npm:3.11.6" - dependencies: - "@types/node": "npm:*" - checksum: 10c0/d9a2d60b8fc6ccef73368fa20a23d5b16506808a81ec65f7e8eedf58d236ebaf2ab46578936c000c8e39dde825cb48a3cf9195c8b410177efd5388bcf9d07370 - languageName: node - linkType: hard - -"@types/estree@npm:^1.0.6": - version: 1.0.6 - resolution: "@types/estree@npm:1.0.6" - checksum: 10c0/cdfd751f6f9065442cd40957c07fd80361c962869aa853c1c2fd03e101af8b9389d8ff4955a43a6fcfa223dd387a089937f95be0f3eec21ca527039fd2d9859a - languageName: node - linkType: hard - -"@types/fs-extra@npm:^11.0.4": - version: 11.0.4 - resolution: "@types/fs-extra@npm:11.0.4" - dependencies: - "@types/jsonfile": "npm:*" - "@types/node": "npm:*" - checksum: 10c0/9e34f9b24ea464f3c0b18c3f8a82aefc36dc524cc720fc2b886e5465abc66486ff4e439ea3fb2c0acebf91f6d3f74e514f9983b1f02d4243706bdbb7511796ad - languageName: node - linkType: hard - -"@types/inquirer@npm:^9.0.8": - version: 9.0.8 - resolution: "@types/inquirer@npm:9.0.8" - dependencies: - "@types/through": "npm:*" - rxjs: "npm:^7.2.0" - checksum: 10c0/6b49b12ab1122b3e18d4d0f3be99dd21d67f4d03e0d61c211f1affbc2885b0094569d3e4fd977888fd42b3321842453f52ee6dcae9cc7bb706e77513538c4e09 - languageName: node - linkType: hard - -"@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1, @types/istanbul-lib-coverage@npm:^2.0.6": - version: 2.0.6 - resolution: "@types/istanbul-lib-coverage@npm:2.0.6" - checksum: 10c0/3948088654f3eeb45363f1db158354fb013b362dba2a5c2c18c559484d5eb9f6fd85b23d66c0a7c2fcfab7308d0a585b14dadaca6cc8bf89ebfdc7f8f5102fb7 - languageName: node - linkType: hard - -"@types/istanbul-lib-report@npm:*": - version: 3.0.0 - resolution: "@types/istanbul-lib-report@npm:3.0.0" - dependencies: - "@types/istanbul-lib-coverage": "npm:*" - checksum: 10c0/7ced458631276a28082ee40645224c3cdd8b861961039ff811d841069171c987ec7e50bc221845ec0d04df0022b2f457a21fb2f816dab2fbe64d59377b32031f - languageName: node - linkType: hard - -"@types/istanbul-reports@npm:^3.0.0, @types/istanbul-reports@npm:^3.0.4": - version: 3.0.4 - resolution: "@types/istanbul-reports@npm:3.0.4" - dependencies: - "@types/istanbul-lib-report": "npm:*" - checksum: 10c0/1647fd402aced5b6edac87274af14ebd6b3a85447ef9ad11853a70fd92a98d35f81a5d3ea9fcb5dbb5834e800c6e35b64475e33fcae6bfa9acc70d61497c54ee - languageName: node - linkType: hard - -"@types/jest@npm:^30.0.0": - version: 30.0.0 - resolution: "@types/jest@npm:30.0.0" - dependencies: - expect: "npm:^30.0.0" - pretty-format: "npm:^30.0.0" - checksum: 10c0/20c6ce574154bc16f8dd6a97afacca4b8c4921a819496a3970382031c509ebe87a1b37b152a1b8475089b82d8ca951a9e95beb4b9bf78fbf579b1536f0b65969 - languageName: node - linkType: hard - -"@types/json-schema@npm:^7.0.15": - version: 7.0.15 - resolution: "@types/json-schema@npm:7.0.15" - checksum: 10c0/a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db - languageName: node - linkType: hard - -"@types/jsonfile@npm:*": - version: 6.1.4 - resolution: "@types/jsonfile@npm:6.1.4" - dependencies: - "@types/node": "npm:*" - checksum: 10c0/b12d068b021e4078f6ac4441353965769be87acf15326173e2aea9f3bf8ead41bd0ad29421df5bbeb0123ec3fc02eb0a734481d52903704a1454a1845896b9eb - languageName: node - linkType: hard - -"@types/langs@npm:^2.0.5": - version: 2.0.5 - resolution: "@types/langs@npm:2.0.5" - checksum: 10c0/91f76173d2457788d947ad5d9f42364166117ef036a1a62a6b410319fc5ea0bdd5350aea7ed5e90b7ab280386f4bfa0eb20a280df1234d41203be337ac4b8d0a - languageName: node - linkType: hard - -"@types/lodash@npm:^4.17.19": - version: 4.17.19 - resolution: "@types/lodash@npm:4.17.19" - checksum: 10c0/0d512e90a92c09b48ec0e46945876392d3ef60c0be7d023fdab22ecb0224a65ff4ed0b76c7acbf1c0152c27768aa4661ea7a1c3afb5b5ab13a50bda674a7c3f7 - languageName: node - linkType: hard - -"@types/node-cleanup@npm:^2.1.5": - version: 2.1.5 - resolution: "@types/node-cleanup@npm:2.1.5" - checksum: 10c0/cc2b02685ed8dbe88fd6df726618191d67faa1248abc14400f5db2d0c1af56ba7f45af5444fca74dd7b181aeca037714e4b2d06eb2f465f4d90c5ac87d2c9ca4 - languageName: node - linkType: hard - -"@types/node@npm:*, @types/node@npm:^24.0.7": - version: 24.0.7 - resolution: "@types/node@npm:24.0.7" - dependencies: - undici-types: "npm:~7.8.0" - checksum: 10c0/be3849816dafc54ec79e6be6dafcf60bdb6466beaf0081b941142d260e2b2864855210dfe5b4395c59b276468528695aefcf4f060ac95cc433b2968e80a311f9 - languageName: node - linkType: hard - -"@types/numeral@npm:^2.0.5": - version: 2.0.5 - resolution: "@types/numeral@npm:2.0.5" - checksum: 10c0/b18766cc97e79b5c59130ce1d5d5ad8b9287e1efd5ecac402e8a64e45c50aea8c8940c9974358983036d1abbed365a08f7f4d11b8af16874a5d4d0edce9aa4d4 - languageName: node - linkType: hard - -"@types/stack-utils@npm:^2.0.3": - version: 2.0.3 - resolution: "@types/stack-utils@npm:2.0.3" - checksum: 10c0/1f4658385ae936330581bcb8aa3a066df03867d90281cdf89cc356d404bd6579be0f11902304e1f775d92df22c6dd761d4451c804b0a4fba973e06211e9bd77c - languageName: node - linkType: hard - -"@types/through@npm:*": - version: 0.0.31 - resolution: "@types/through@npm:0.0.31" - dependencies: - "@types/node": "npm:*" - checksum: 10c0/a251f146b4df92e82a8fdf4601ee76d2d5e2a0440bc028471c8545bc7ef02cb9894f1cbae75384ebe3db572b41a0d41e9167c0f4c7321321743aa314dc986997 - languageName: node - linkType: hard - -"@types/yargs-parser@npm:*": - version: 21.0.0 - resolution: "@types/yargs-parser@npm:21.0.0" - checksum: 10c0/cb89f3bb2e8002f1479a65a934e825be4cc18c50b350bbc656405d41cf90b8a299b105e7da497d7eb1aa460472a07d1e5a389f3af0862f1d1252279cfcdd017c - languageName: node - linkType: hard - -"@types/yargs@npm:^17.0.33, @types/yargs@npm:^17.0.8": - version: 17.0.33 - resolution: "@types/yargs@npm:17.0.33" - dependencies: - "@types/yargs-parser": "npm:*" - checksum: 10c0/d16937d7ac30dff697801c3d6f235be2166df42e4a88bf730fa6dc09201de3727c0a9500c59a672122313341de5f24e45ee0ff579c08ce91928e519090b7906b - languageName: node - linkType: hard - -"@typescript-eslint/eslint-plugin@npm:^8.35.0": - version: 8.35.0 - resolution: "@typescript-eslint/eslint-plugin@npm:8.35.0" - dependencies: - "@eslint-community/regexpp": "npm:^4.10.0" - "@typescript-eslint/scope-manager": "npm:8.35.0" - "@typescript-eslint/type-utils": "npm:8.35.0" - "@typescript-eslint/utils": "npm:8.35.0" - "@typescript-eslint/visitor-keys": "npm:8.35.0" - graphemer: "npm:^1.4.0" - ignore: "npm:^7.0.0" - natural-compare: "npm:^1.4.0" - ts-api-utils: "npm:^2.1.0" - peerDependencies: - "@typescript-eslint/parser": ^8.35.0 - eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/27391f1b168a175fdc62370e5afe51317d4433115abbbff8ee0aea8ecd7bf6dd541a76f8e0cc94119750ae3146863204862640acb45394f0b92809e88d39f881 - languageName: node - linkType: hard - -"@typescript-eslint/parser@npm:^8.35.0": - version: 8.35.0 - resolution: "@typescript-eslint/parser@npm:8.35.0" - dependencies: - "@typescript-eslint/scope-manager": "npm:8.35.0" - "@typescript-eslint/types": "npm:8.35.0" - "@typescript-eslint/typescript-estree": "npm:8.35.0" - "@typescript-eslint/visitor-keys": "npm:8.35.0" - debug: "npm:^4.3.4" - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/8f1cda98f8bee3d79266974e5e5c831a0ca473e928fb16f1dc1c85ee24f2cb9c0fcf3c1bcbbef9d6044cf063f6e59d3198b766a27000776830fe591043e11625 - languageName: node - linkType: hard - -"@typescript-eslint/project-service@npm:8.35.0": - version: 8.35.0 - resolution: "@typescript-eslint/project-service@npm:8.35.0" - dependencies: - "@typescript-eslint/tsconfig-utils": "npm:^8.35.0" - "@typescript-eslint/types": "npm:^8.35.0" - debug: "npm:^4.3.4" - peerDependencies: - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/c2d6d44b6b2ff3ecabec8ade824163196799060ac457661eb94049487d770ce68d128b33a2f24090adf1ebcb66ff6c9a05fc6659349b9a0784a5a080ecf8ff81 - languageName: node - linkType: hard - -"@typescript-eslint/scope-manager@npm:8.35.0": - version: 8.35.0 - resolution: "@typescript-eslint/scope-manager@npm:8.35.0" - dependencies: - "@typescript-eslint/types": "npm:8.35.0" - "@typescript-eslint/visitor-keys": "npm:8.35.0" - checksum: 10c0/a27cf27a1852bb0d6ea08f475fcc79557f1977be96ef563d92127e8011e4065566441c32c40eb7a530111ffd3a8489919da7f8a2b7466a610cfc9c07670a9601 - languageName: node - linkType: hard - -"@typescript-eslint/tsconfig-utils@npm:8.35.0, @typescript-eslint/tsconfig-utils@npm:^8.35.0": - version: 8.35.0 - resolution: "@typescript-eslint/tsconfig-utils@npm:8.35.0" - peerDependencies: - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/baa18e7137ba72f7d138f50d1168e8f334198a36499f954821e2369027e5b3d53ca93c354943e7782ba5caab604b050af10f353ccca34fbc0b23c48d6174832f - languageName: node - linkType: hard - -"@typescript-eslint/type-utils@npm:8.35.0": - version: 8.35.0 - resolution: "@typescript-eslint/type-utils@npm:8.35.0" - dependencies: - "@typescript-eslint/typescript-estree": "npm:8.35.0" - "@typescript-eslint/utils": "npm:8.35.0" - debug: "npm:^4.3.4" - ts-api-utils: "npm:^2.1.0" - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/9e23a332484a055eb73ba8918f95a981e0cec8fa623ba9ee0b57328af052628d630a415e32e0dbe95318574e62d4066f8aecc994728b3cedd906f36c616ec362 - languageName: node - linkType: hard - -"@typescript-eslint/types@npm:8.35.0, @typescript-eslint/types@npm:^8.35.0": - version: 8.35.0 - resolution: "@typescript-eslint/types@npm:8.35.0" - checksum: 10c0/a2711a932680805e83252b5d7c55ac30437bdc4d40c444606cf6ccb6ba23a682da015ec03c64635e77bf733f84d9bb76810bf4f7177fd3a660db8a2c8a05e845 - languageName: node - linkType: hard - -"@typescript-eslint/typescript-estree@npm:8.35.0": - version: 8.35.0 - resolution: "@typescript-eslint/typescript-estree@npm:8.35.0" - dependencies: - "@typescript-eslint/project-service": "npm:8.35.0" - "@typescript-eslint/tsconfig-utils": "npm:8.35.0" - "@typescript-eslint/types": "npm:8.35.0" - "@typescript-eslint/visitor-keys": "npm:8.35.0" - debug: "npm:^4.3.4" - fast-glob: "npm:^3.3.2" - is-glob: "npm:^4.0.3" - minimatch: "npm:^9.0.4" - semver: "npm:^7.6.0" - ts-api-utils: "npm:^2.1.0" - peerDependencies: - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/7e94f6a92efc5832289e8bfd0b61209aa501224c935359253c29aeef8e0b981b370ee2a43e2909991c3c3cf709fcccb6380474e0e9a863e8f89e2fbd213aed59 - languageName: node - linkType: hard - -"@typescript-eslint/utils@npm:8.35.0": - version: 8.35.0 - resolution: "@typescript-eslint/utils@npm:8.35.0" - dependencies: - "@eslint-community/eslint-utils": "npm:^4.7.0" - "@typescript-eslint/scope-manager": "npm:8.35.0" - "@typescript-eslint/types": "npm:8.35.0" - "@typescript-eslint/typescript-estree": "npm:8.35.0" - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/e3317df7875305bee16edd573e4bfdafc099f26f9c284d8adb351333683aacd5b668320870653dff7ec7e0da1982bbf89dc06197bc193a3be65362f21452dbea - languageName: node - linkType: hard - -"@typescript-eslint/visitor-keys@npm:8.35.0": - version: 8.35.0 - resolution: "@typescript-eslint/visitor-keys@npm:8.35.0" - dependencies: - "@typescript-eslint/types": "npm:8.35.0" - eslint-visitor-keys: "npm:^4.2.1" - checksum: 10c0/df18ca9b6931cb58f5dc404fcc94f9e0cc1c22f3053c7013ab588bb8ccccd3d58a70c577c01267845d57fa124a8cf8371260d284dad97505c56b2abcf70a3dce - languageName: node - linkType: hard - -"@ungap/structured-clone@npm:^1.3.0": - version: 1.3.0 - resolution: "@ungap/structured-clone@npm:1.3.0" - checksum: 10c0/0fc3097c2540ada1fc340ee56d58d96b5b536a2a0dab6e3ec17d4bfc8c4c86db345f61a375a8185f9da96f01c69678f836a2b57eeaa9e4b8eeafd26428e57b0a - languageName: node - linkType: hard - -"@unrs/resolver-binding-android-arm-eabi@npm:1.9.1": - version: 1.9.1 - resolution: "@unrs/resolver-binding-android-arm-eabi@npm:1.9.1" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - -"@unrs/resolver-binding-android-arm64@npm:1.9.1": - version: 1.9.1 - resolution: "@unrs/resolver-binding-android-arm64@npm:1.9.1" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - -"@unrs/resolver-binding-darwin-arm64@npm:1.9.1": - version: 1.9.1 - resolution: "@unrs/resolver-binding-darwin-arm64@npm:1.9.1" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"@unrs/resolver-binding-darwin-x64@npm:1.9.1": - version: 1.9.1 - resolution: "@unrs/resolver-binding-darwin-x64@npm:1.9.1" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"@unrs/resolver-binding-freebsd-x64@npm:1.9.1": - version: 1.9.1 - resolution: "@unrs/resolver-binding-freebsd-x64@npm:1.9.1" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - -"@unrs/resolver-binding-linux-arm-gnueabihf@npm:1.9.1": - version: 1.9.1 - resolution: "@unrs/resolver-binding-linux-arm-gnueabihf@npm:1.9.1" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - -"@unrs/resolver-binding-linux-arm-musleabihf@npm:1.9.1": - version: 1.9.1 - resolution: "@unrs/resolver-binding-linux-arm-musleabihf@npm:1.9.1" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - -"@unrs/resolver-binding-linux-arm64-gnu@npm:1.9.1": - version: 1.9.1 - resolution: "@unrs/resolver-binding-linux-arm64-gnu@npm:1.9.1" - conditions: os=linux & cpu=arm64 & libc=glibc - languageName: node - linkType: hard - -"@unrs/resolver-binding-linux-arm64-musl@npm:1.9.1": - version: 1.9.1 - resolution: "@unrs/resolver-binding-linux-arm64-musl@npm:1.9.1" - conditions: os=linux & cpu=arm64 & libc=musl - languageName: node - linkType: hard - -"@unrs/resolver-binding-linux-ppc64-gnu@npm:1.9.1": - version: 1.9.1 - resolution: "@unrs/resolver-binding-linux-ppc64-gnu@npm:1.9.1" - conditions: os=linux & cpu=ppc64 & libc=glibc - languageName: node - linkType: hard - -"@unrs/resolver-binding-linux-riscv64-gnu@npm:1.9.1": - version: 1.9.1 - resolution: "@unrs/resolver-binding-linux-riscv64-gnu@npm:1.9.1" - conditions: os=linux & cpu=riscv64 & libc=glibc - languageName: node - linkType: hard - -"@unrs/resolver-binding-linux-riscv64-musl@npm:1.9.1": - version: 1.9.1 - resolution: "@unrs/resolver-binding-linux-riscv64-musl@npm:1.9.1" - conditions: os=linux & cpu=riscv64 & libc=musl - languageName: node - linkType: hard - -"@unrs/resolver-binding-linux-s390x-gnu@npm:1.9.1": - version: 1.9.1 - resolution: "@unrs/resolver-binding-linux-s390x-gnu@npm:1.9.1" - conditions: os=linux & cpu=s390x & libc=glibc - languageName: node - linkType: hard - -"@unrs/resolver-binding-linux-x64-gnu@npm:1.9.1": - version: 1.9.1 - resolution: "@unrs/resolver-binding-linux-x64-gnu@npm:1.9.1" - conditions: os=linux & cpu=x64 & libc=glibc - languageName: node - linkType: hard - -"@unrs/resolver-binding-linux-x64-musl@npm:1.9.1": - version: 1.9.1 - resolution: "@unrs/resolver-binding-linux-x64-musl@npm:1.9.1" - conditions: os=linux & cpu=x64 & libc=musl - languageName: node - linkType: hard - -"@unrs/resolver-binding-wasm32-wasi@npm:1.9.1": - version: 1.9.1 - resolution: "@unrs/resolver-binding-wasm32-wasi@npm:1.9.1" - dependencies: - "@napi-rs/wasm-runtime": "npm:^0.2.11" - conditions: cpu=wasm32 - languageName: node - linkType: hard - -"@unrs/resolver-binding-win32-arm64-msvc@npm:1.9.1": - version: 1.9.1 - resolution: "@unrs/resolver-binding-win32-arm64-msvc@npm:1.9.1" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"@unrs/resolver-binding-win32-ia32-msvc@npm:1.9.1": - version: 1.9.1 - resolution: "@unrs/resolver-binding-win32-ia32-msvc@npm:1.9.1" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - -"@unrs/resolver-binding-win32-x64-msvc@npm:1.9.1": - version: 1.9.1 - resolution: "@unrs/resolver-binding-win32-x64-msvc@npm:1.9.1" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - -"@zeit/schemas@npm:2.36.0": - version: 2.36.0 - resolution: "@zeit/schemas@npm:2.36.0" - checksum: 10c0/858c3ae46d23122f65d576013dc74f120af0ca7f3256c4b7077bcd12e952c8f71d8241a5165c23d18f6378e198a1db7e93bc8fae8ed0769e4cf4e2df953ee955 - languageName: node - linkType: hard - -"abbrev@npm:^3.0.0": - version: 3.0.1 - resolution: "abbrev@npm:3.0.1" - checksum: 10c0/21ba8f574ea57a3106d6d35623f2c4a9111d9ee3e9a5be47baed46ec2457d2eac46e07a5c4a60186f88cb98abbe3e24f2d4cca70bc2b12f1692523e2209a9ccf - languageName: node - linkType: hard - -"accepts@npm:~1.3.5": - version: 1.3.8 - resolution: "accepts@npm:1.3.8" - dependencies: - mime-types: "npm:~2.1.34" - negotiator: "npm:0.6.3" - checksum: 10c0/3a35c5f5586cfb9a21163ca47a5f77ac34fa8ceb5d17d2fa2c0d81f41cbd7f8c6fa52c77e2c039acc0f4d09e71abdc51144246900f6bef5e3c4b333f77d89362 - languageName: node - linkType: hard - -"acorn-jsx@npm:^5.3.2": - version: 5.3.2 - resolution: "acorn-jsx@npm:5.3.2" - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: 10c0/4c54868fbef3b8d58927d5e33f0a4de35f59012fe7b12cf9dfbb345fb8f46607709e1c4431be869a23fb63c151033d84c4198fa9f79385cec34fcb1dd53974c1 - languageName: node - linkType: hard - -"acorn@npm:^8.15.0": - version: 8.15.0 - resolution: "acorn@npm:8.15.0" - bin: - acorn: bin/acorn - checksum: 10c0/dec73ff59b7d6628a01eebaece7f2bdb8bb62b9b5926dcad0f8931f2b8b79c2be21f6c68ac095592adb5adb15831a3635d9343e6a91d028bbe85d564875ec3ec - languageName: node - linkType: hard - -"agent-base@npm:^7.0.2, agent-base@npm:^7.1.0, agent-base@npm:^7.1.2, agent-base@npm:^7.1.3": - version: 7.1.3 - resolution: "agent-base@npm:7.1.3" - checksum: 10c0/6192b580c5b1d8fb399b9c62bf8343d76654c2dd62afcb9a52b2cf44a8b6ace1e3b704d3fe3547d91555c857d3df02603341ff2cb961b9cfe2b12f9f3c38ee11 - languageName: node - linkType: hard - -"ajv@npm:8.12.0": - version: 8.12.0 - resolution: "ajv@npm:8.12.0" - dependencies: - fast-deep-equal: "npm:^3.1.1" - json-schema-traverse: "npm:^1.0.0" - require-from-string: "npm:^2.0.2" - uri-js: "npm:^4.2.2" - checksum: 10c0/ac4f72adf727ee425e049bc9d8b31d4a57e1c90da8d28bcd23d60781b12fcd6fc3d68db5df16994c57b78b94eed7988f5a6b482fd376dc5b084125e20a0a622e - languageName: node - linkType: hard - -"ajv@npm:^6.12.4": - version: 6.12.6 - resolution: "ajv@npm:6.12.6" - dependencies: - fast-deep-equal: "npm:^3.1.1" - fast-json-stable-stringify: "npm:^2.0.0" - json-schema-traverse: "npm:^0.4.1" - uri-js: "npm:^4.2.2" - checksum: 10c0/41e23642cbe545889245b9d2a45854ebba51cda6c778ebced9649420d9205f2efb39cb43dbc41e358409223b1ea43303ae4839db682c848b891e4811da1a5a71 - languageName: node - linkType: hard - -"amp-message@npm:~0.1.1": - version: 0.1.2 - resolution: "amp-message@npm:0.1.2" - dependencies: - amp: "npm:0.3.1" - checksum: 10c0/07c20d31b30a7280f519ce6b5864e5ff04e105231b8d9b0f07b808f0fe9666f19e3ca7d68ab0786b40259ccc9d3241cc3becbdf90c825c8c4f7592eda766f0ed - languageName: node - linkType: hard - -"amp@npm:0.3.1, amp@npm:~0.3.1": - version: 0.3.1 - resolution: "amp@npm:0.3.1" - checksum: 10c0/a5fb811dfe4f0525de7305103aae1a3ef5305d749edf1de5b1def53683a0ad598b4e10cb8746771dccd2e50c131153f11a44a12889c6501526f2051952e304f8 - languageName: node - linkType: hard - -"ansi-align@npm:^3.0.1": - version: 3.0.1 - resolution: "ansi-align@npm:3.0.1" - dependencies: - string-width: "npm:^4.1.0" - checksum: 10c0/ad8b755a253a1bc8234eb341e0cec68a857ab18bf97ba2bda529e86f6e30460416523e0ec58c32e5c21f0ca470d779503244892873a5895dbd0c39c788e82467 - languageName: node - linkType: hard - -"ansi-colors@npm:^4.1.1": - version: 4.1.3 - resolution: "ansi-colors@npm:4.1.3" - checksum: 10c0/ec87a2f59902f74e61eada7f6e6fe20094a628dab765cfdbd03c3477599368768cffccdb5d3bb19a1b6c99126783a143b1fee31aab729b31ffe5836c7e5e28b9 - languageName: node - linkType: hard - -"ansi-escapes@npm:^4.3.2": - version: 4.3.2 - resolution: "ansi-escapes@npm:4.3.2" - dependencies: - type-fest: "npm:^0.21.3" - checksum: 10c0/da917be01871525a3dfcf925ae2977bc59e8c513d4423368645634bf5d4ceba5401574eb705c1e92b79f7292af5a656f78c5725a4b0e1cec97c4b413705c1d50 - languageName: node - linkType: hard - -"ansi-regex@npm:^5.0.1": - version: 5.0.1 - resolution: "ansi-regex@npm:5.0.1" - checksum: 10c0/9a64bb8627b434ba9327b60c027742e5d17ac69277960d041898596271d992d4d52ba7267a63ca10232e29f6107fc8a835f6ce8d719b88c5f8493f8254813737 - languageName: node - linkType: hard - -"ansi-regex@npm:^6.0.1": - version: 6.1.0 - resolution: "ansi-regex@npm:6.1.0" - checksum: 10c0/a91daeddd54746338478eef88af3439a7edf30f8e23196e2d6ed182da9add559c601266dbef01c2efa46a958ad6f1f8b176799657616c702b5b02e799e7fd8dc - languageName: node - linkType: hard - -"ansi-styles@npm:^3.2.1": - version: 3.2.1 - resolution: "ansi-styles@npm:3.2.1" - dependencies: - color-convert: "npm:^1.9.0" - checksum: 10c0/ece5a8ef069fcc5298f67e3f4771a663129abd174ea2dfa87923a2be2abf6cd367ef72ac87942da00ce85bd1d651d4cd8595aebdb1b385889b89b205860e977b - languageName: node - linkType: hard - -"ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0": - version: 4.3.0 - resolution: "ansi-styles@npm:4.3.0" - dependencies: - color-convert: "npm:^2.0.1" - checksum: 10c0/895a23929da416f2bd3de7e9cb4eabd340949328ab85ddd6e484a637d8f6820d485f53933446f5291c3b760cbc488beb8e88573dd0f9c7daf83dccc8fe81b041 - languageName: node - linkType: hard - -"ansi-styles@npm:^5.2.0": - version: 5.2.0 - resolution: "ansi-styles@npm:5.2.0" - checksum: 10c0/9c4ca80eb3c2fb7b33841c210d2f20807f40865d27008d7c3f707b7f95cab7d67462a565e2388ac3285b71cb3d9bb2173de8da37c57692a362885ec34d6e27df - languageName: node - linkType: hard - -"ansi-styles@npm:^6.1.0": - version: 6.2.1 - resolution: "ansi-styles@npm:6.2.1" - checksum: 10c0/5d1ec38c123984bcedd996eac680d548f31828bd679a66db2bdf11844634dde55fec3efa9c6bb1d89056a5e79c1ac540c4c784d592ea1d25028a92227d2f2d5c - languageName: node - linkType: hard - -"ansis@npm:4.0.0-node10": - version: 4.0.0-node10 - resolution: "ansis@npm:4.0.0-node10" - checksum: 10c0/7d6f4a6c1fbe405ee088421a17ea7cf1abd94ebee46ee69a6a739f8777e1ccfe1cd81abeee19f8e9a303963180556c222e1f1b97d25b0b601ea231c119463982 - languageName: node - linkType: hard - -"anymatch@npm:^3.1.3, anymatch@npm:~3.1.2": - version: 3.1.3 - resolution: "anymatch@npm:3.1.3" - dependencies: - normalize-path: "npm:^3.0.0" - picomatch: "npm:^2.0.4" - checksum: 10c0/57b06ae984bc32a0d22592c87384cd88fe4511b1dd7581497831c56d41939c8a001b28e7b853e1450f2bf61992dfcaa8ae2d0d161a0a90c4fb631ef07098fbac - languageName: node - linkType: hard - -"arch@npm:^2.2.0": - version: 2.2.0 - resolution: "arch@npm:2.2.0" - checksum: 10c0/4ceaf8d8207817c216ebc4469742052cb0a097bc45d9b7fcd60b7507220da545a28562ab5bdd4dfe87921bb56371a0805da4e10d704e01f93a15f83240f1284c - languageName: node - linkType: hard - -"arg@npm:5.0.2": - version: 5.0.2 - resolution: "arg@npm:5.0.2" - checksum: 10c0/ccaf86f4e05d342af6666c569f844bec426595c567d32a8289715087825c2ca7edd8a3d204e4d2fb2aa4602e09a57d0c13ea8c9eea75aac3dbb4af5514e6800e - languageName: node - linkType: hard - -"argparse@npm:^1.0.7": - version: 1.0.10 - resolution: "argparse@npm:1.0.10" - dependencies: - sprintf-js: "npm:~1.0.2" - checksum: 10c0/b2972c5c23c63df66bca144dbc65d180efa74f25f8fd9b7d9a0a6c88ae839db32df3d54770dcb6460cf840d232b60695d1a6b1053f599d84e73f7437087712de - languageName: node - linkType: hard - -"argparse@npm:^2.0.1": - version: 2.0.1 - resolution: "argparse@npm:2.0.1" - checksum: 10c0/c5640c2d89045371c7cedd6a70212a04e360fd34d6edeae32f6952c63949e3525ea77dbec0289d8213a99bbaeab5abfa860b5c12cf88a2e6cf8106e90dd27a7e - languageName: node - linkType: hard - -"ast-types@npm:^0.13.4": - version: 0.13.4 - resolution: "ast-types@npm:0.13.4" - dependencies: - tslib: "npm:^2.0.1" - checksum: 10c0/3a1a409764faa1471601a0ad01b3aa699292991aa9c8a30c7717002cabdf5d98008e7b53ae61f6e058f757fc6ba965e147967a93c13e62692c907d79cfb245f8 - languageName: node - linkType: hard - -"async@npm:^2.6.3, async@npm:~2.6.1": - version: 2.6.4 - resolution: "async@npm:2.6.4" - dependencies: - lodash: "npm:^4.17.14" - checksum: 10c0/0ebb3273ef96513389520adc88e0d3c45e523d03653cc9b66f5c46f4239444294899bfd13d2b569e7dbfde7da2235c35cf5fd3ece9524f935d41bbe4efccdad0 - languageName: node - linkType: hard - -"async@npm:^3.2.0, async@npm:^3.2.3, async@npm:~3.2.0, async@npm:~3.2.6": - version: 3.2.6 - resolution: "async@npm:3.2.6" - checksum: 10c0/36484bb15ceddf07078688d95e27076379cc2f87b10c03b6dd8a83e89475a3c8df5848859dd06a4c95af1e4c16fc973de0171a77f18ea00be899aca2a4f85e70 - languageName: node - linkType: hard - -"asynckit@npm:^0.4.0": - version: 0.4.0 - resolution: "asynckit@npm:0.4.0" - checksum: 10c0/d73e2ddf20c4eb9337e1b3df1a0f6159481050a5de457c55b14ea2e5cb6d90bb69e004c9af54737a5ee0917fcf2c9e25de67777bbe58261847846066ba75bc9d - languageName: node - linkType: hard - -"available-typed-arrays@npm:^1.0.7": - version: 1.0.7 - resolution: "available-typed-arrays@npm:1.0.7" - dependencies: - possible-typed-array-names: "npm:^1.0.0" - checksum: 10c0/d07226ef4f87daa01bd0fe80f8f310982e345f372926da2e5296aecc25c41cab440916bbaa4c5e1034b453af3392f67df5961124e4b586df1e99793a1374bdb2 - languageName: node - linkType: hard - -"axios-cache-interceptor@npm:^0.10.3": - version: 0.10.3 - resolution: "axios-cache-interceptor@npm:0.10.3" - dependencies: - cache-parser: "npm:^1.2.4" - fast-defer: "npm:^1.1.7" - object-code: "npm:^1.2.2" - checksum: 10c0/7c1a7d6e07f08c51bc11ffd3fb647f9e58995d559fd415cc5a6475af5c7721b1a6c46315e1f10410ab9cea24f483106a6a7816ceb6a4f0437f226f1a92e71008 - languageName: node - linkType: hard - -"axios-cookiejar-support@npm:^6.0.2": - version: 6.0.2 - resolution: "axios-cookiejar-support@npm:6.0.2" - dependencies: - http-cookie-agent: "npm:^7.0.1" - peerDependencies: - axios: ">=0.20.0" - tough-cookie: ">=4.0.0" - checksum: 10c0/54db57f37b5a54555f83584d7e321af8b9087419668fc2680ed155c17ec08431f67fd91af32213f707d4f05fb7e29d4a02ecb17a2796037ee7cd76df7def5c81 - languageName: node - linkType: hard - -"axios-mock-adapter@npm:^1.20.0": - version: 1.20.0 - resolution: "axios-mock-adapter@npm:1.20.0" - dependencies: - fast-deep-equal: "npm:^3.1.3" - is-blob: "npm:^2.1.0" - is-buffer: "npm:^2.0.5" - peerDependencies: - axios: ">= 0.9.0" - checksum: 10c0/fa037222d02fec61bda1ff2577572b16e2f29a2e2e945698d59c6e993358135b98801473f60308e3dfddf77df0e381ddc84445ceeff81a6e1c013cb313a2eb89 - languageName: node - linkType: hard - -"axios@npm:^1.10.0, axios@npm:^1.6.1, axios@npm:^1.7.9": - version: 1.10.0 - resolution: "axios@npm:1.10.0" - dependencies: - follow-redirects: "npm:^1.15.6" - form-data: "npm:^4.0.0" - proxy-from-env: "npm:^1.1.0" - checksum: 10c0/2239cb269cc789eac22f5d1aabd58e1a83f8f364c92c2caa97b6f5cbb4ab2903d2e557d9dc670b5813e9bcdebfb149e783fb8ab3e45098635cd2f559b06bd5d8 - languageName: node - linkType: hard - -"babel-jest@npm:30.0.2": - version: 30.0.2 - resolution: "babel-jest@npm:30.0.2" - dependencies: - "@jest/transform": "npm:30.0.2" - "@types/babel__core": "npm:^7.20.5" - babel-plugin-istanbul: "npm:^7.0.0" - babel-preset-jest: "npm:30.0.1" - chalk: "npm:^4.1.2" - graceful-fs: "npm:^4.2.11" - slash: "npm:^3.0.0" - peerDependencies: - "@babel/core": ^7.11.0 - checksum: 10c0/416deec120eea3f870b45166abc8a30ea29b9235d1acb4a2e50a3b7d623f401589621fa6502dcd4abfffbfaa506eccf20dbbef2c5d0eeac1df9344ec8d8de272 - languageName: node - linkType: hard - -"babel-plugin-istanbul@npm:^7.0.0": - version: 7.0.0 - resolution: "babel-plugin-istanbul@npm:7.0.0" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.0.0" - "@istanbuljs/load-nyc-config": "npm:^1.0.0" - "@istanbuljs/schema": "npm:^0.1.3" - istanbul-lib-instrument: "npm:^6.0.2" - test-exclude: "npm:^6.0.0" - checksum: 10c0/79c37bd59ea9bcb16218e874993621e24048776fac7ee72eabe78f0909200851bdb93b32f6eba5b463206f15a1ee7ad40a725af8447952321ae1fdf14e740fe9 - languageName: node - linkType: hard - -"babel-plugin-jest-hoist@npm:30.0.1": - version: 30.0.1 - resolution: "babel-plugin-jest-hoist@npm:30.0.1" - dependencies: - "@babel/template": "npm:^7.27.2" - "@babel/types": "npm:^7.27.3" - "@types/babel__core": "npm:^7.20.5" - checksum: 10c0/49087f45c8ac359d68c622f4bd471300376b0ca2b6bd6ecaa1bd254ea87eda8fa3ce6144848e3bbabad337d276474a47e2ac3f6272f82e1f2337924ff49a02bd - languageName: node - linkType: hard - -"babel-preset-current-node-syntax@npm:^1.1.0": - version: 1.1.0 - resolution: "babel-preset-current-node-syntax@npm:1.1.0" - dependencies: - "@babel/plugin-syntax-async-generators": "npm:^7.8.4" - "@babel/plugin-syntax-bigint": "npm:^7.8.3" - "@babel/plugin-syntax-class-properties": "npm:^7.12.13" - "@babel/plugin-syntax-class-static-block": "npm:^7.14.5" - "@babel/plugin-syntax-import-attributes": "npm:^7.24.7" - "@babel/plugin-syntax-import-meta": "npm:^7.10.4" - "@babel/plugin-syntax-json-strings": "npm:^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators": "npm:^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3" - "@babel/plugin-syntax-numeric-separator": "npm:^7.10.4" - "@babel/plugin-syntax-object-rest-spread": "npm:^7.8.3" - "@babel/plugin-syntax-optional-catch-binding": "npm:^7.8.3" - "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" - "@babel/plugin-syntax-private-property-in-object": "npm:^7.14.5" - "@babel/plugin-syntax-top-level-await": "npm:^7.14.5" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10c0/0b838d4412e3322cb4436f246e24e9c00bebcedfd8f00a2f51489db683bd35406bbd55a700759c28d26959c6e03f84dd6a1426f576f440267c1d7a73c5717281 - languageName: node - linkType: hard - -"babel-preset-jest@npm:30.0.1": - version: 30.0.1 - resolution: "babel-preset-jest@npm:30.0.1" - dependencies: - babel-plugin-jest-hoist: "npm:30.0.1" - babel-preset-current-node-syntax: "npm:^1.1.0" - peerDependencies: - "@babel/core": ^7.11.0 - checksum: 10c0/33da0094965929b1742b02e55272b544f189cd487d55bbba60e68d96d62d48f466264fe51f65950454829d4f2271541f2433e1c1c5e6a7ff5b9e91f1303471b7 - languageName: node - linkType: hard - -"balanced-match@npm:^1.0.0": - version: 1.0.2 - resolution: "balanced-match@npm:1.0.2" - checksum: 10c0/9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee - languageName: node - linkType: hard - -"basic-ftp@npm:^5.0.2": - version: 5.0.5 - resolution: "basic-ftp@npm:5.0.5" - checksum: 10c0/be983a3997749856da87b839ffce6b8ed6c7dbf91ea991d5c980d8add275f9f2926c19f80217ac3e7f353815be879371d636407ca72b038cea8cab30e53928a6 - languageName: node - linkType: hard - -"before-after-hook@npm:^4.0.0": - version: 4.0.0 - resolution: "before-after-hook@npm:4.0.0" - checksum: 10c0/9f8ae8d1b06142bcfb9ef6625226b5e50348bb11210f266660eddcf9734e0db6f9afc4cb48397ee3f5ac0a3728f3ae401cdeea88413f7bed748a71db84657be2 - languageName: node - linkType: hard - -"binary-extensions@npm:^2.0.0": - version: 2.3.0 - resolution: "binary-extensions@npm:2.3.0" - checksum: 10c0/75a59cafc10fb12a11d510e77110c6c7ae3f4ca22463d52487709ca7f18f69d886aa387557cc9864fbdb10153d0bdb4caacabf11541f55e89ed6e18d12ece2b5 - languageName: node - linkType: hard - -"blessed@npm:0.1.81": - version: 0.1.81 - resolution: "blessed@npm:0.1.81" - bin: - blessed: ./bin/tput.js - checksum: 10c0/19515ff7899e8af0dd6c080e30e849833ee9518508cc4eabae5a2ea5f17440537a2526169081f20c79d73fbbeca26cc798cc4b037ec250f91f4952b3a75a2143 - languageName: node - linkType: hard - -"bodec@npm:^0.1.0": - version: 0.1.0 - resolution: "bodec@npm:0.1.0" - checksum: 10c0/7a81a3e59ccdf6aa1baf5a1346f1f14ad1c24a31e16160d368604d9b716c9eb1b499b80b9bf823126326bdb2de775d116f3f80102f4f56fe7f4dbdb05745a659 - languageName: node - linkType: hard - -"boolbase@npm:^1.0.0": - version: 1.0.0 - resolution: "boolbase@npm:1.0.0" - checksum: 10c0/e4b53deb4f2b85c52be0e21a273f2045c7b6a6ea002b0e139c744cb6f95e9ec044439a52883b0d74dedd1ff3da55ed140cfdddfed7fb0cccbed373de5dce1bcf - languageName: node - linkType: hard - -"boxen@npm:7.0.0": - version: 7.0.0 - resolution: "boxen@npm:7.0.0" - dependencies: - ansi-align: "npm:^3.0.1" - camelcase: "npm:^7.0.0" - chalk: "npm:^5.0.1" - cli-boxes: "npm:^3.0.0" - string-width: "npm:^5.1.2" - type-fest: "npm:^2.13.0" - widest-line: "npm:^4.0.1" - wrap-ansi: "npm:^8.0.1" - checksum: 10c0/af5e8bc3f1486ac50ec7485ae482eb1d4db905233d7ab2acafc406b576375be85bdc60b53fab99c842c42c274328b7219c7ae79adab13161f4c84e139f4b06ae - languageName: node - linkType: hard - -"brace-expansion@npm:^1.1.7": - version: 1.1.12 - resolution: "brace-expansion@npm:1.1.12" - dependencies: - balanced-match: "npm:^1.0.0" - concat-map: "npm:0.0.1" - checksum: 10c0/975fecac2bb7758c062c20d0b3b6288c7cc895219ee25f0a64a9de662dbac981ff0b6e89909c3897c1f84fa353113a721923afdec5f8b2350255b097f12b1f73 - languageName: node - linkType: hard - -"brace-expansion@npm:^2.0.1": - version: 2.0.2 - resolution: "brace-expansion@npm:2.0.2" - dependencies: - balanced-match: "npm:^1.0.0" - checksum: 10c0/6d117a4c793488af86b83172deb6af143e94c17bc53b0b3cec259733923b4ca84679d506ac261f4ba3c7ed37c46018e2ff442f9ce453af8643ecd64f4a54e6cf - languageName: node - linkType: hard - -"braces@npm:^3.0.3, braces@npm:~3.0.2": - version: 3.0.3 - resolution: "braces@npm:3.0.3" - dependencies: - fill-range: "npm:^7.1.1" - checksum: 10c0/7c6dfd30c338d2997ba77500539227b9d1f85e388a5f43220865201e407e076783d0881f2d297b9f80951b4c957fcf0b51c1d2d24227631643c3f7c284b0aa04 - languageName: node - linkType: hard - -"browserslist@npm:^4.24.0": - version: 4.25.0 - resolution: "browserslist@npm:4.25.0" - dependencies: - caniuse-lite: "npm:^1.0.30001718" - electron-to-chromium: "npm:^1.5.160" - node-releases: "npm:^2.0.19" - update-browserslist-db: "npm:^1.1.3" - bin: - browserslist: cli.js - checksum: 10c0/cc16c55b4468b18684a0e1ca303592b38635b1155d6724f172407192737a2f405b8030d87a05813729592793445b3d15e737b0055f901cdecccb29b1e580a1c5 - languageName: node - linkType: hard - -"bser@npm:2.1.1": - version: 2.1.1 - resolution: "bser@npm:2.1.1" - dependencies: - node-int64: "npm:^0.4.0" - checksum: 10c0/24d8dfb7b6d457d73f32744e678a60cc553e4ec0e9e1a01cf614b44d85c3c87e188d3cc78ef0442ce5032ee6818de20a0162ba1074725c0d08908f62ea979227 - languageName: node - linkType: hard - -"buffer-from@npm:^1.0.0": - version: 1.1.2 - resolution: "buffer-from@npm:1.1.2" - checksum: 10c0/124fff9d66d691a86d3b062eff4663fe437a9d9ee4b47b1b9e97f5a5d14f6d5399345db80f796827be7c95e70a8e765dd404b7c3ff3b3324f98e9b0c8826cc34 - languageName: node - linkType: hard - -"bytes@npm:3.0.0": - version: 3.0.0 - resolution: "bytes@npm:3.0.0" - checksum: 10c0/91d42c38601c76460519ffef88371caacaea483a354c8e4b8808e7b027574436a5713337c003ea3de63ee4991c2a9a637884fdfe7f761760d746929d9e8fec60 - languageName: node - linkType: hard - -"cacache@npm:^19.0.1": - version: 19.0.1 - resolution: "cacache@npm:19.0.1" - dependencies: - "@npmcli/fs": "npm:^4.0.0" - fs-minipass: "npm:^3.0.0" - glob: "npm:^10.2.2" - lru-cache: "npm:^10.0.1" - minipass: "npm:^7.0.3" - minipass-collect: "npm:^2.0.1" - minipass-flush: "npm:^1.0.5" - minipass-pipeline: "npm:^1.2.4" - p-map: "npm:^7.0.2" - ssri: "npm:^12.0.0" - tar: "npm:^7.4.3" - unique-filename: "npm:^4.0.0" - checksum: 10c0/01f2134e1bd7d3ab68be851df96c8d63b492b1853b67f2eecb2c37bb682d37cb70bb858a16f2f0554d3c0071be6dfe21456a1ff6fa4b7eed996570d6a25ffe9c - languageName: node - linkType: hard - -"cache-parser@npm:^1.2.4": - version: 1.2.4 - resolution: "cache-parser@npm:1.2.4" - checksum: 10c0/5f9ee096b3cd42224208aae8bb82f4dc63b9a49f6d92898973854dc2e63fef281ef658932ea9c0b9c15ba0ff18f51a43a007624f1ef60820b06047488f1125fb - languageName: node - linkType: hard - -"call-bind-apply-helpers@npm:^1.0.0, call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": - version: 1.0.2 - resolution: "call-bind-apply-helpers@npm:1.0.2" - dependencies: - es-errors: "npm:^1.3.0" - function-bind: "npm:^1.1.2" - checksum: 10c0/47bd9901d57b857590431243fea704ff18078b16890a6b3e021e12d279bbf211d039155e27d7566b374d49ee1f8189344bac9833dec7a20cdec370506361c938 - languageName: node - linkType: hard - -"call-bind@npm:^1.0.8": - version: 1.0.8 - resolution: "call-bind@npm:1.0.8" - dependencies: - call-bind-apply-helpers: "npm:^1.0.0" - es-define-property: "npm:^1.0.0" - get-intrinsic: "npm:^1.2.4" - set-function-length: "npm:^1.2.2" - checksum: 10c0/a13819be0681d915144467741b69875ae5f4eba8961eb0bf322aab63ec87f8250eb6d6b0dcbb2e1349876412a56129ca338592b3829ef4343527f5f18a0752d4 - languageName: node - linkType: hard - -"call-bound@npm:^1.0.2, call-bound@npm:^1.0.3, call-bound@npm:^1.0.4": - version: 1.0.4 - resolution: "call-bound@npm:1.0.4" - dependencies: - call-bind-apply-helpers: "npm:^1.0.2" - get-intrinsic: "npm:^1.3.0" - checksum: 10c0/f4796a6a0941e71c766aea672f63b72bc61234c4f4964dc6d7606e3664c307e7d77845328a8f3359ce39ddb377fed67318f9ee203dea1d47e46165dcf2917644 - languageName: node - linkType: hard - -"callsites@npm:^3.0.0, callsites@npm:^3.1.0": - version: 3.1.0 - resolution: "callsites@npm:3.1.0" - checksum: 10c0/fff92277400eb06c3079f9e74f3af120db9f8ea03bad0e84d9aede54bbe2d44a56cccb5f6cf12211f93f52306df87077ecec5b712794c5a9b5dac6d615a3f301 - languageName: node - linkType: hard - -"camelcase@npm:^5.3.1": - version: 5.3.1 - resolution: "camelcase@npm:5.3.1" - checksum: 10c0/92ff9b443bfe8abb15f2b1513ca182d16126359ad4f955ebc83dc4ddcc4ef3fdd2c078bc223f2673dc223488e75c99b16cc4d056624374b799e6a1555cf61b23 - languageName: node - linkType: hard - -"camelcase@npm:^6.3.0": - version: 6.3.0 - resolution: "camelcase@npm:6.3.0" - checksum: 10c0/0d701658219bd3116d12da3eab31acddb3f9440790c0792e0d398f0a520a6a4058018e546862b6fba89d7ae990efaeb97da71e1913e9ebf5a8b5621a3d55c710 - languageName: node - linkType: hard - -"camelcase@npm:^7.0.0": - version: 7.0.1 - resolution: "camelcase@npm:7.0.1" - checksum: 10c0/3adfc9a0e96d51b3a2f4efe90a84dad3e206aaa81dfc664f1bd568270e1bf3b010aad31f01db16345b4ffe1910e16ab411c7273a19a859addd1b98ef7cf4cfbd - languageName: node - linkType: hard - -"caniuse-lite@npm:^1.0.30001718": - version: 1.0.30001724 - resolution: "caniuse-lite@npm:1.0.30001724" - checksum: 10c0/ed9ec0bcf619f0e7ef2d33aac74d2346d1faf52060dfded1fb9c32d87854de5c2988b3ba338c281034c88bf797d6b55468a804ce8396a7e16a48cb0d481d4bfe - languageName: node - linkType: hard - -"cdata@npm:^0.1.1": - version: 0.1.3 - resolution: "cdata@npm:0.1.3" - checksum: 10c0/1bab6ffbe4462b7705861c2a90f862537583ff30d49cadce6d5daa23c48f2e0117a2e7f8b07322c62884829056c10a24bd6c93d08bcf1570809d5ebf2ff901d8 - languageName: node - linkType: hard - -"chalk-template@npm:0.4.0": - version: 0.4.0 - resolution: "chalk-template@npm:0.4.0" - dependencies: - chalk: "npm:^4.1.2" - checksum: 10c0/6a4cb4252966475f0bd3ee1cd8780146e1ba69f445e59c565cab891ac18708c8143515d23e2b0fb7e192574fb7608d429ea5b28f3b7b9507770ad6fccd3467e3 - languageName: node - linkType: hard - -"chalk@npm:3.0.0, chalk@npm:~3.0.0": - version: 3.0.0 - resolution: "chalk@npm:3.0.0" - dependencies: - ansi-styles: "npm:^4.1.0" - supports-color: "npm:^7.1.0" - checksum: 10c0/ee650b0a065b3d7a6fda258e75d3a86fc8e4effa55871da730a9e42ccb035bf5fd203525e5a1ef45ec2582ecc4f65b47eb11357c526b84dd29a14fb162c414d2 - languageName: node - linkType: hard - -"chalk@npm:5.0.1": - version: 5.0.1 - resolution: "chalk@npm:5.0.1" - checksum: 10c0/97898611ae40cfdeda9778901731df1404ea49fac0eb8253804e8d21b8064917df9823e29c0c9d766aab623da1a0b43d0e072d19a73d4f62d0d9115aef4c64e6 - languageName: node - linkType: hard - -"chalk@npm:^2.3.2": - version: 2.4.2 - resolution: "chalk@npm:2.4.2" - dependencies: - ansi-styles: "npm:^3.2.1" - escape-string-regexp: "npm:^1.0.5" - supports-color: "npm:^5.3.0" - checksum: 10c0/e6543f02ec877732e3a2d1c3c3323ddb4d39fbab687c23f526e25bd4c6a9bf3b83a696e8c769d078e04e5754921648f7821b2a2acfd16c550435fd630026e073 - languageName: node - linkType: hard - -"chalk@npm:^4.0.0, chalk@npm:^4.1.2": - version: 4.1.2 - resolution: "chalk@npm:4.1.2" - dependencies: - ansi-styles: "npm:^4.1.0" - supports-color: "npm:^7.1.0" - checksum: 10c0/4a3fef5cc34975c898ffe77141450f679721df9dde00f6c304353fa9c8b571929123b26a0e4617bde5018977eb655b31970c297b91b63ee83bb82aeb04666880 - languageName: node - linkType: hard - -"chalk@npm:^5.0.1, chalk@npm:^5.4.1": - version: 5.4.1 - resolution: "chalk@npm:5.4.1" - checksum: 10c0/b23e88132c702f4855ca6d25cb5538b1114343e41472d5263ee8a37cccfccd9c4216d111e1097c6a27830407a1dc81fecdf2a56f2c63033d4dbbd88c10b0dcef - languageName: node - linkType: hard - -"char-regex@npm:^1.0.2": - version: 1.0.2 - resolution: "char-regex@npm:1.0.2" - checksum: 10c0/57a09a86371331e0be35d9083ba429e86c4f4648ecbe27455dbfb343037c16ee6fdc7f6b61f433a57cc5ded5561d71c56a150e018f40c2ffb7bc93a26dae341e - languageName: node - linkType: hard - -"chardet@npm:^0.7.0": - version: 0.7.0 - resolution: "chardet@npm:0.7.0" - checksum: 10c0/96e4731b9ec8050cbb56ab684e8c48d6c33f7826b755802d14e3ebfdc51c57afeece3ea39bc6b09acc359e4363525388b915e16640c1378053820f5e70d0f27d - languageName: node - linkType: hard - -"charm@npm:~0.1.1": - version: 0.1.2 - resolution: "charm@npm:0.1.2" - checksum: 10c0/5f516d3ceba660688f90041e719858e21e430de347625f462da85a31914b898c4215984b14a4fdefab7b8e50dee09c43d9770765053a93c59d0853cb09aac24d - languageName: node - linkType: hard - -"cheerio-select@npm:^2.1.0": - version: 2.1.0 - resolution: "cheerio-select@npm:2.1.0" - dependencies: - boolbase: "npm:^1.0.0" - css-select: "npm:^5.1.0" - css-what: "npm:^6.1.0" - domelementtype: "npm:^2.3.0" - domhandler: "npm:^5.0.3" - domutils: "npm:^3.0.1" - checksum: 10c0/2242097e593919dba4aacb97d7b8275def8b9ec70b00aa1f43335456870cfc9e284eae2080bdc832ed232dabb9eefcf56c722d152da4a154813fb8814a55d282 - languageName: node - linkType: hard - -"cheerio@npm:^1.0.0, cheerio@npm:^1.0.0-rc.12, cheerio@npm:^1.1.0": - version: 1.1.0 - resolution: "cheerio@npm:1.1.0" - dependencies: - cheerio-select: "npm:^2.1.0" - dom-serializer: "npm:^2.0.0" - domhandler: "npm:^5.0.3" - domutils: "npm:^3.2.2" - encoding-sniffer: "npm:^0.2.0" - htmlparser2: "npm:^10.0.0" - parse5: "npm:^7.3.0" - parse5-htmlparser2-tree-adapter: "npm:^7.1.0" - parse5-parser-stream: "npm:^7.1.2" - undici: "npm:^7.10.0" - whatwg-mimetype: "npm:^4.0.0" - checksum: 10c0/f7b940a89e1fe77bf6b4fe3b993f17b02a358942cc0b9d3b55ea235a0bc322829dbc47151763ef9986fd237494c00380909af759e46582c72470a10643b85abd - languageName: node - linkType: hard - -"chokidar@npm:^3.5.3": - version: 3.6.0 - resolution: "chokidar@npm:3.6.0" - dependencies: - anymatch: "npm:~3.1.2" - braces: "npm:~3.0.2" - fsevents: "npm:~2.3.2" - glob-parent: "npm:~5.1.2" - is-binary-path: "npm:~2.1.0" - is-glob: "npm:~4.0.1" - normalize-path: "npm:~3.0.0" - readdirp: "npm:~3.6.0" - dependenciesMeta: - fsevents: - optional: true - checksum: 10c0/8361dcd013f2ddbe260eacb1f3cb2f2c6f2b0ad118708a343a5ed8158941a39cb8fb1d272e0f389712e74ee90ce8ba864eece9e0e62b9705cb468a2f6d917462 - languageName: node - linkType: hard - -"chownr@npm:^3.0.0": - version: 3.0.0 - resolution: "chownr@npm:3.0.0" - checksum: 10c0/43925b87700f7e3893296c8e9c56cc58f926411cce3a6e5898136daaf08f08b9a8eb76d37d3267e707d0dcc17aed2e2ebdf5848c0c3ce95cf910a919935c1b10 - languageName: node - linkType: hard - -"ci-info@npm:^4.2.0": - version: 4.2.0 - resolution: "ci-info@npm:4.2.0" - checksum: 10c0/37a2f4b6a213a5cf835890eb0241f0d5b022f6cfefde58a69e9af8e3a0e71e06d6ad7754b0d4efb9cd2613e58a7a33996d71b56b0d04242722e86666f3f3d058 - languageName: node - linkType: hard - -"cjs-module-lexer@npm:^2.1.0": - version: 2.1.0 - resolution: "cjs-module-lexer@npm:2.1.0" - checksum: 10c0/91cf28686dc3948e4a06dfa03a2fccb14b7a97471ffe7ae0124f62060ddf2de28e8e997f60007babe6e122b1b06a47c01a1b72cc015f185824d9cac3ccfa5533 - languageName: node - linkType: hard - -"cli-boxes@npm:^3.0.0": - version: 3.0.0 - resolution: "cli-boxes@npm:3.0.0" - checksum: 10c0/4db3e8fbfaf1aac4fb3a6cbe5a2d3fa048bee741a45371b906439b9ffc821c6e626b0f108bdcd3ddf126a4a319409aedcf39a0730573ff050fdd7b6731e99fb9 - languageName: node - linkType: hard - -"cli-progress@npm:^3.12.0": - version: 3.12.0 - resolution: "cli-progress@npm:3.12.0" - dependencies: - string-width: "npm:^4.2.3" - checksum: 10c0/f464cb19ebde2f3880620a2adfaeeefaec6cb15c8e610c8a659ca1047ee90d69f3bf2fdabbb1fe33ac408678e882e3e0eecdb84ab5df0edf930b269b8a72682d - languageName: node - linkType: hard - -"cli-tableau@npm:^2.0.0": - version: 2.0.1 - resolution: "cli-tableau@npm:2.0.1" - dependencies: - chalk: "npm:3.0.0" - checksum: 10c0/fb0dd0973b090eef5c9cfea237ff3bd60f0384e6d35f3d335ef11e0c4f41b4e00846ccf257826654d926537ab9601e8c664d7b64f8279d7ca2bc28a6c45a7db1 - languageName: node - linkType: hard - -"cli-width@npm:^4.1.0": - version: 4.1.0 - resolution: "cli-width@npm:4.1.0" - checksum: 10c0/1fbd56413578f6117abcaf858903ba1f4ad78370a4032f916745fa2c7e390183a9d9029cf837df320b0fdce8137668e522f60a30a5f3d6529ff3872d265a955f - languageName: node - linkType: hard - -"clipboardy@npm:3.0.0": - version: 3.0.0 - resolution: "clipboardy@npm:3.0.0" - dependencies: - arch: "npm:^2.2.0" - execa: "npm:^5.1.1" - is-wsl: "npm:^2.2.0" - checksum: 10c0/299d66e13fcaccf656306e76d629ce6927eaba8ba58ae5328e3379ae627e469e29df8ef87408cdb234e2ad0e25f0024dd203393f7e59c67ae79772579c4de052 - languageName: node - linkType: hard - -"cliui@npm:^8.0.1": - version: 8.0.1 - resolution: "cliui@npm:8.0.1" - dependencies: - string-width: "npm:^4.2.0" - strip-ansi: "npm:^6.0.1" - wrap-ansi: "npm:^7.0.0" - checksum: 10c0/4bda0f09c340cbb6dfdc1ed508b3ca080f12992c18d68c6be4d9cf51756033d5266e61ec57529e610dacbf4da1c634423b0c1b11037709cc6b09045cbd815df5 - languageName: node - linkType: hard - -"co@npm:^4.6.0": - version: 4.6.0 - resolution: "co@npm:4.6.0" - checksum: 10c0/c0e85ea0ca8bf0a50cbdca82efc5af0301240ca88ebe3644a6ffb8ffe911f34d40f8fbcf8f1d52c5ddd66706abd4d3bfcd64259f1e8e2371d4f47573b0dc8c28 - languageName: node - linkType: hard - -"collect-v8-coverage@npm:^1.0.2": - version: 1.0.2 - resolution: "collect-v8-coverage@npm:1.0.2" - checksum: 10c0/ed7008e2e8b6852c5483b444a3ae6e976e088d4335a85aa0a9db2861c5f1d31bd2d7ff97a60469b3388deeba661a619753afbe201279fb159b4b9548ab8269a1 - languageName: node - linkType: hard - -"color-convert@npm:^1.9.0, color-convert@npm:^1.9.3": - version: 1.9.3 - resolution: "color-convert@npm:1.9.3" - dependencies: - color-name: "npm:1.1.3" - checksum: 10c0/5ad3c534949a8c68fca8fbc6f09068f435f0ad290ab8b2f76841b9e6af7e0bb57b98cb05b0e19fe33f5d91e5a8611ad457e5f69e0a484caad1f7487fd0e8253c - languageName: node - linkType: hard - -"color-convert@npm:^2.0.1": - version: 2.0.1 - resolution: "color-convert@npm:2.0.1" - dependencies: - color-name: "npm:~1.1.4" - checksum: 10c0/37e1150172f2e311fe1b2df62c6293a342ee7380da7b9cfdba67ea539909afbd74da27033208d01d6d5cfc65ee7868a22e18d7e7648e004425441c0f8a15a7d7 - languageName: node - linkType: hard - -"color-name@npm:1.1.3": - version: 1.1.3 - resolution: "color-name@npm:1.1.3" - checksum: 10c0/566a3d42cca25b9b3cd5528cd7754b8e89c0eb646b7f214e8e2eaddb69994ac5f0557d9c175eb5d8f0ad73531140d9c47525085ee752a91a2ab15ab459caf6d6 - languageName: node - linkType: hard - -"color-name@npm:^1.0.0, color-name@npm:~1.1.4": - version: 1.1.4 - resolution: "color-name@npm:1.1.4" - checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95 - languageName: node - linkType: hard - -"color-string@npm:^1.6.0": - version: 1.9.1 - resolution: "color-string@npm:1.9.1" - dependencies: - color-name: "npm:^1.0.0" - simple-swizzle: "npm:^0.2.2" - checksum: 10c0/b0bfd74c03b1f837f543898b512f5ea353f71630ccdd0d66f83028d1f0924a7d4272deb278b9aef376cacf1289b522ac3fb175e99895283645a2dc3a33af2404 - languageName: node - linkType: hard - -"color@npm:^3.1.3": - version: 3.2.1 - resolution: "color@npm:3.2.1" - dependencies: - color-convert: "npm:^1.9.3" - color-string: "npm:^1.6.0" - checksum: 10c0/39345d55825884c32a88b95127d417a2c24681d8b57069413596d9fcbb721459ef9d9ec24ce3e65527b5373ce171b73e38dbcd9c830a52a6487e7f37bf00e83c - languageName: node - linkType: hard - -"colorspace@npm:1.1.x": - version: 1.1.4 - resolution: "colorspace@npm:1.1.4" - dependencies: - color: "npm:^3.1.3" - text-hex: "npm:1.0.x" - checksum: 10c0/af5f91ff7f8e146b96e439ac20ed79b197210193bde721b47380a75b21751d90fa56390c773bb67c0aedd34ff85091883a437ab56861c779bd507d639ba7e123 - languageName: node - linkType: hard - -"combined-stream@npm:^1.0.8": - version: 1.0.8 - resolution: "combined-stream@npm:1.0.8" - dependencies: - delayed-stream: "npm:~1.0.0" - checksum: 10c0/0dbb829577e1b1e839fa82b40c07ffaf7de8a09b935cadd355a73652ae70a88b4320db322f6634a4ad93424292fa80973ac6480986247f1734a1137debf271d5 - languageName: node - linkType: hard - -"commander@npm:2.15.1": - version: 2.15.1 - resolution: "commander@npm:2.15.1" - checksum: 10c0/26793fd4c798a691bf354331fb19a8accb03a32fdd774a948099c829b5fc32ccb7c60b7071d3df8381fb699121fd0e944ca4ac9d07ecaf702ce8a64b49aba6f4 - languageName: node - linkType: hard - -"commander@npm:^14.0.0": - version: 14.0.0 - resolution: "commander@npm:14.0.0" - checksum: 10c0/73c4babfa558077868d84522b11ef56834165d472b9e86a634cd4c3ae7fc72d59af6377d8878e06bd570fe8f3161eced3cbe383c38f7093272bb65bd242b595b - languageName: node - linkType: hard - -"commander@npm:^7.1.0": - version: 7.2.0 - resolution: "commander@npm:7.2.0" - checksum: 10c0/8d690ff13b0356df7e0ebbe6c59b4712f754f4b724d4f473d3cc5b3fdcf978e3a5dc3078717858a2ceb50b0f84d0660a7f22a96cdc50fb877d0c9bb31593d23a - languageName: node - linkType: hard - -"compressible@npm:~2.0.16": - version: 2.0.18 - resolution: "compressible@npm:2.0.18" - dependencies: - mime-db: "npm:>= 1.43.0 < 2" - checksum: 10c0/8a03712bc9f5b9fe530cc5a79e164e665550d5171a64575d7dcf3e0395d7b4afa2d79ab176c61b5b596e28228b350dd07c1a2a6ead12fd81d1b6cd632af2fef7 - languageName: node - linkType: hard - -"compression@npm:1.7.4": - version: 1.7.4 - resolution: "compression@npm:1.7.4" - dependencies: - accepts: "npm:~1.3.5" - bytes: "npm:3.0.0" - compressible: "npm:~2.0.16" - debug: "npm:2.6.9" - on-headers: "npm:~1.0.2" - safe-buffer: "npm:5.1.2" - vary: "npm:~1.1.2" - checksum: 10c0/138db836202a406d8a14156a5564fb1700632a76b6e7d1546939472895a5304f2b23c80d7a22bf44c767e87a26e070dbc342ea63bb45ee9c863354fa5556bbbc - languageName: node - linkType: hard - -"concat-map@npm:0.0.1": - version: 0.0.1 - resolution: "concat-map@npm:0.0.1" - checksum: 10c0/c996b1cfdf95b6c90fee4dae37e332c8b6eb7d106430c17d538034c0ad9a1630cb194d2ab37293b1bdd4d779494beee7786d586a50bd9376fd6f7bcc2bd4c98f - languageName: node - linkType: hard - -"consola@npm:^3.4.2": - version: 3.4.2 - resolution: "consola@npm:3.4.2" - checksum: 10c0/7cebe57ecf646ba74b300bcce23bff43034ed6fbec9f7e39c27cee1dc00df8a21cd336b466ad32e304ea70fba04ec9e890c200270de9a526ce021ba8a7e4c11a - languageName: node - linkType: hard - -"content-disposition@npm:0.5.2": - version: 0.5.2 - resolution: "content-disposition@npm:0.5.2" - checksum: 10c0/49eebaa0da1f9609b192e99d7fec31d1178cb57baa9d01f5b63b29787ac31e9d18b5a1033e854c68c9b6cce790e700a6f7fa60e43f95e2e416404e114a8f2f49 - languageName: node - linkType: hard - -"convert-source-map@npm:^2.0.0": - version: 2.0.0 - resolution: "convert-source-map@npm:2.0.0" - checksum: 10c0/8f2f7a27a1a011cc6cc88cc4da2d7d0cfa5ee0369508baae3d98c260bb3ac520691464e5bbe4ae7cdf09860c1d69ecc6f70c63c6e7c7f7e3f18ec08484dc7d9b - languageName: node - linkType: hard - -"croner@npm:~4.1.92": - version: 4.1.97 - resolution: "croner@npm:4.1.97" - checksum: 10c0/1e0f2e3d9f04d2355e42df8b789a936b6a3d8d0282d24a68c0dcd559a25cbb625c9688ad3d87189065c32b0a1bf031e5faece8ccebc472304072e3fa9b98952d - languageName: node - linkType: hard - -"cross-env@npm:^7.0.3": - version: 7.0.3 - resolution: "cross-env@npm:7.0.3" - dependencies: - cross-spawn: "npm:^7.0.1" - bin: - cross-env: src/bin/cross-env.js - cross-env-shell: src/bin/cross-env-shell.js - checksum: 10c0/f3765c25746c69fcca369655c442c6c886e54ccf3ab8c16847d5ad0e91e2f337d36eedc6599c1227904bf2a228d721e690324446876115bc8e7b32a866735ecf - languageName: node - linkType: hard - -"cross-spawn@npm:^7.0.1, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.6": - version: 7.0.6 - resolution: "cross-spawn@npm:7.0.6" - dependencies: - path-key: "npm:^3.1.0" - shebang-command: "npm:^2.0.0" - which: "npm:^2.0.1" - checksum: 10c0/053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1 - languageName: node - linkType: hard - -"css-select@npm:^5.1.0": - version: 5.1.0 - resolution: "css-select@npm:5.1.0" - dependencies: - boolbase: "npm:^1.0.0" - css-what: "npm:^6.1.0" - domhandler: "npm:^5.0.2" - domutils: "npm:^3.0.1" - nth-check: "npm:^2.0.1" - checksum: 10c0/551c60dba5b54054741032c1793b5734f6ba45e23ae9e82761a3c0ed1acbb8cfedfa443aaba3a3c1a54cac12b456d2012a09d2cd5f0e82e430454c1b9d84d500 - languageName: node - linkType: hard - -"css-what@npm:^6.1.0": - version: 6.1.0 - resolution: "css-what@npm:6.1.0" - checksum: 10c0/a09f5a6b14ba8dcf57ae9a59474722e80f20406c53a61e9aedb0eedc693b135113ffe2983f4efc4b5065ae639442e9ae88df24941ef159c218b231011d733746 - languageName: node - linkType: hard - -"csv-parser@npm:^3.2.0": - version: 3.2.0 - resolution: "csv-parser@npm:3.2.0" - bin: - csv-parser: bin/csv-parser - checksum: 10c0/650769916607dae9679187803c71ef70d3b724cfb18e4adff187167b282faf18ac7c14a318c4ea6e92ae3483d3ed4b8d92ff213e77979e3049a5c0029034ac65 - languageName: node - linkType: hard - -"culvert@npm:^0.1.2": - version: 0.1.2 - resolution: "culvert@npm:0.1.2" - checksum: 10c0/185fc6fd1b3bc8c8e60c8e60f4ac71123d80267e97127f00acd0fc18e2abc69d2a30d1a8bc60e5ee651e940d67a2476688d6b851acb963a27c2043d9a8677d39 - languageName: node - linkType: hard - -"curl-generator@npm:^0.2.0": - version: 0.2.0 - resolution: "curl-generator@npm:0.2.0" - dependencies: - ms: "npm:^2.0.0" - checksum: 10c0/e8a6d54f3861fa2f0607cac22a2a6e907900c1e3f5149b79f522e9feb558ae949cd85ef391763076ba32d800f1ed85f44bfa1ba4a552e9206cf044168a7e584d - languageName: node - linkType: hard - -"cwait@npm:^1.1.2": - version: 1.1.2 - resolution: "cwait@npm:1.1.2" - dependencies: - cdata: "npm:^0.1.1" - checksum: 10c0/cac623a110bbe1d2710faf85f98505ccc2c620857fd6c56fc971c69e255ce7852d014832686590ae0f55664ccaf1a1604af3886c6282901fa3608f5b9b22f814 - languageName: node - linkType: hard - -"data-uri-to-buffer@npm:^6.0.2": - version: 6.0.2 - resolution: "data-uri-to-buffer@npm:6.0.2" - checksum: 10c0/f76922bf895b3d7d443059ff278c9cc5efc89d70b8b80cd9de0aa79b3adc6d7a17948eefb8692e30398c43635f70ece1673d6085cc9eba2878dbc6c6da5292ac - languageName: node - linkType: hard - -"dayjs@npm:^1.10.4, dayjs@npm:^1.11.13, dayjs@npm:^1.11.6, dayjs@npm:~1.11.13": - version: 1.11.13 - resolution: "dayjs@npm:1.11.13" - checksum: 10c0/a3caf6ac8363c7dade9d1ee797848ddcf25c1ace68d9fe8678ecf8ba0675825430de5d793672ec87b24a69bf04a1544b176547b2539982275d5542a7955f35b7 - languageName: node - linkType: hard - -"dayjs@npm:~1.8.24": - version: 1.8.36 - resolution: "dayjs@npm:1.8.36" - checksum: 10c0/fc9e85e7b3e64130688b579ea9b328e863bc5bf2a9dede81e4ff5b34230756f1252aa3bb290a7eafbf13750663e36e7e65d16f497c6a258e138529a168f2626e - languageName: node - linkType: hard - -"debug@npm:2.6.9": - version: 2.6.9 - resolution: "debug@npm:2.6.9" - dependencies: - ms: "npm:2.0.0" - checksum: 10c0/121908fb839f7801180b69a7e218a40b5a0b718813b886b7d6bdb82001b931c938e2941d1e4450f33a1b1df1da653f5f7a0440c197f29fbf8a6e9d45ff6ef589 - languageName: node - linkType: hard - -"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:~4.3.1": - version: 4.3.4 - resolution: "debug@npm:4.3.4" - dependencies: - ms: "npm:2.1.2" - peerDependenciesMeta: - supports-color: - optional: true - checksum: 10c0/cedbec45298dd5c501d01b92b119cd3faebe5438c3917ff11ae1bff86a6c722930ac9c8659792824013168ba6db7c4668225d845c633fbdafbbf902a6389f736 - languageName: node - linkType: hard - -"debug@npm:^3.1.0, debug@npm:^3.2.6": - version: 3.2.7 - resolution: "debug@npm:3.2.7" - dependencies: - ms: "npm:^2.1.1" - checksum: 10c0/37d96ae42cbc71c14844d2ae3ba55adf462ec89fd3a999459dec3833944cd999af6007ff29c780f1c61153bcaaf2c842d1e4ce1ec621e4fc4923244942e4a02a - languageName: node - linkType: hard - -"debug@npm:^4.3.7": - version: 4.4.1 - resolution: "debug@npm:4.4.1" - dependencies: - ms: "npm:^2.1.3" - peerDependenciesMeta: - supports-color: - optional: true - checksum: 10c0/d2b44bc1afd912b49bb7ebb0d50a860dc93a4dd7d946e8de94abc957bb63726b7dd5aa48c18c2386c379ec024c46692e15ed3ed97d481729f929201e671fcd55 - languageName: node - linkType: hard - -"dedent@npm:^1.6.0": - version: 1.6.0 - resolution: "dedent@npm:1.6.0" - peerDependencies: - babel-plugin-macros: ^3.1.0 - peerDependenciesMeta: - babel-plugin-macros: - optional: true - checksum: 10c0/671b8f5e390dd2a560862c4511dd6d2638e71911486f78cb32116551f8f2aa6fcaf50579ffffb2f866d46b5b80fd72470659ca5760ede8f967619ef7df79e8a5 - languageName: node - linkType: hard - -"deep-extend@npm:^0.6.0": - version: 0.6.0 - resolution: "deep-extend@npm:0.6.0" - checksum: 10c0/1c6b0abcdb901e13a44c7d699116d3d4279fdb261983122a3783e7273844d5f2537dc2e1c454a23fcf645917f93fbf8d07101c1d03c015a87faa662755212566 - languageName: node - linkType: hard - -"deep-is@npm:^0.1.3": - version: 0.1.4 - resolution: "deep-is@npm:0.1.4" - checksum: 10c0/7f0ee496e0dff14a573dc6127f14c95061b448b87b995fc96c017ce0a1e66af1675e73f1d6064407975bc4ea6ab679497a29fff7b5b9c4e99cb10797c1ad0b4c - languageName: node - linkType: hard - -"deepmerge@npm:^4.3.1": - version: 4.3.1 - resolution: "deepmerge@npm:4.3.1" - checksum: 10c0/e53481aaf1aa2c4082b5342be6b6d8ad9dfe387bc92ce197a66dea08bd4265904a087e75e464f14d1347cf2ac8afe1e4c16b266e0561cc5df29382d3c5f80044 - languageName: node - linkType: hard - -"define-data-property@npm:^1.1.4": - version: 1.1.4 - resolution: "define-data-property@npm:1.1.4" - dependencies: - es-define-property: "npm:^1.0.0" - es-errors: "npm:^1.3.0" - gopd: "npm:^1.0.1" - checksum: 10c0/dea0606d1483eb9db8d930d4eac62ca0fa16738b0b3e07046cddfacf7d8c868bbe13fa0cb263eb91c7d0d527960dc3f2f2471a69ed7816210307f6744fe62e37 - languageName: node - linkType: hard - -"degenerator@npm:^5.0.0": - version: 5.0.1 - resolution: "degenerator@npm:5.0.1" - dependencies: - ast-types: "npm:^0.13.4" - escodegen: "npm:^2.1.0" - esprima: "npm:^4.0.1" - checksum: 10c0/e48d8a651edeb512a648711a09afec269aac6de97d442a4bb9cf121a66877e0eec11b9727100a10252335c0666ae1c84a8bc1e3a3f47788742c975064d2c7b1c - languageName: node - linkType: hard - -"delayed-stream@npm:~1.0.0": - version: 1.0.0 - resolution: "delayed-stream@npm:1.0.0" - checksum: 10c0/d758899da03392e6712f042bec80aa293bbe9e9ff1b2634baae6a360113e708b91326594c8a486d475c69d6259afb7efacdc3537bfcda1c6c648e390ce601b19 - languageName: node - linkType: hard - -"detect-newline@npm:^3.1.0": - version: 3.1.0 - resolution: "detect-newline@npm:3.1.0" - checksum: 10c0/c38cfc8eeb9fda09febb44bcd85e467c970d4e3bf526095394e5a4f18bc26dd0cf6b22c69c1fa9969261521c593836db335c2795218f6d781a512aea2fb8209d - languageName: node - linkType: hard - -"dom-serializer@npm:^2.0.0": - version: 2.0.0 - resolution: "dom-serializer@npm:2.0.0" - dependencies: - domelementtype: "npm:^2.3.0" - domhandler: "npm:^5.0.2" - entities: "npm:^4.2.0" - checksum: 10c0/d5ae2b7110ca3746b3643d3ef60ef823f5f078667baf530cec096433f1627ec4b6fa8c072f09d079d7cda915fd2c7bc1b7b935681e9b09e591e1e15f4040b8e2 - languageName: node - linkType: hard - -"domelementtype@npm:^2.3.0": - version: 2.3.0 - resolution: "domelementtype@npm:2.3.0" - checksum: 10c0/686f5a9ef0fff078c1412c05db73a0dce096190036f33e400a07e2a4518e9f56b1e324f5c576a0a747ef0e75b5d985c040b0d51945ce780c0dd3c625a18cd8c9 - languageName: node - linkType: hard - -"domhandler@npm:^5.0.2, domhandler@npm:^5.0.3": - version: 5.0.3 - resolution: "domhandler@npm:5.0.3" - dependencies: - domelementtype: "npm:^2.3.0" - checksum: 10c0/bba1e5932b3e196ad6862286d76adc89a0dbf0c773e5ced1eb01f9af930c50093a084eff14b8de5ea60b895c56a04d5de8bbc4930c5543d029091916770b2d2a - languageName: node - linkType: hard - -"domutils@npm:^3.0.1, domutils@npm:^3.2.1, domutils@npm:^3.2.2": - version: 3.2.2 - resolution: "domutils@npm:3.2.2" - dependencies: - dom-serializer: "npm:^2.0.0" - domelementtype: "npm:^2.3.0" - domhandler: "npm:^5.0.3" - checksum: 10c0/47938f473b987ea71cd59e59626eb8666d3aa8feba5266e45527f3b636c7883cca7e582d901531961f742c519d7514636b7973353b648762b2e3bedbf235fada - languageName: node - linkType: hard - -"dunder-proto@npm:^1.0.1": - version: 1.0.1 - resolution: "dunder-proto@npm:1.0.1" - dependencies: - call-bind-apply-helpers: "npm:^1.0.1" - es-errors: "npm:^1.3.0" - gopd: "npm:^1.2.0" - checksum: 10c0/199f2a0c1c16593ca0a145dbf76a962f8033ce3129f01284d48c45ed4e14fea9bbacd7b3610b6cdc33486cef20385ac054948fefc6272fcce645c09468f93031 - languageName: node - linkType: hard - -"eastasianwidth@npm:^0.2.0": - version: 0.2.0 - resolution: "eastasianwidth@npm:0.2.0" - checksum: 10c0/26f364ebcdb6395f95124fda411f63137a4bfb5d3a06453f7f23dfe52502905bd84e0488172e0f9ec295fdc45f05c23d5d91baf16bd26f0fe9acd777a188dc39 - languageName: node - linkType: hard - -"electron-to-chromium@npm:^1.5.160": - version: 1.5.171 - resolution: "electron-to-chromium@npm:1.5.171" - checksum: 10c0/e9d7e70d5fe829951c955287877155889a752336e48c715e373c6919f8e438bb686b7278511013aa8456c329c55895059a1d9e4b799217483f28dbae60c198d8 - languageName: node - linkType: hard - -"emittery@npm:^0.13.1": - version: 0.13.1 - resolution: "emittery@npm:0.13.1" - checksum: 10c0/1573d0ae29ab34661b6c63251ff8f5facd24ccf6a823f19417ae8ba8c88ea450325788c67f16c99edec8de4b52ce93a10fe441ece389fd156e88ee7dab9bfa35 - languageName: node - linkType: hard - -"emoji-regex@npm:^8.0.0": - version: 8.0.0 - resolution: "emoji-regex@npm:8.0.0" - checksum: 10c0/b6053ad39951c4cf338f9092d7bfba448cdfd46fe6a2a034700b149ac9ffbc137e361cbd3c442297f86bed2e5f7576c1b54cc0a6bf8ef5106cc62f496af35010 - languageName: node - linkType: hard - -"emoji-regex@npm:^9.2.2": - version: 9.2.2 - resolution: "emoji-regex@npm:9.2.2" - checksum: 10c0/af014e759a72064cf66e6e694a7fc6b0ed3d8db680427b021a89727689671cefe9d04151b2cad51dbaf85d5ba790d061cd167f1cf32eb7b281f6368b3c181639 - languageName: node - linkType: hard - -"enabled@npm:2.0.x": - version: 2.0.0 - resolution: "enabled@npm:2.0.0" - checksum: 10c0/3b2c2af9bc7f8b9e291610f2dde4a75cf6ee52a68f4dd585482fbdf9a55d65388940e024e56d40bb03e05ef6671f5f53021fa8b72a20e954d7066ec28166713f - languageName: node - linkType: hard - -"encoding-sniffer@npm:^0.2.0": - version: 0.2.1 - resolution: "encoding-sniffer@npm:0.2.1" - dependencies: - iconv-lite: "npm:^0.6.3" - whatwg-encoding: "npm:^3.1.1" - checksum: 10c0/d6b591880788f3baf8dd1744636dd189d24a1ec93e6f9817267c60ac3458a5191ca70ab1a186fb67731beff1c3489c6527dfdc4718158ed8460ab2f400dd5e7d - languageName: node - linkType: hard - -"encoding@npm:^0.1.13": - version: 0.1.13 - resolution: "encoding@npm:0.1.13" - dependencies: - iconv-lite: "npm:^0.6.2" - checksum: 10c0/36d938712ff00fe1f4bac88b43bcffb5930c1efa57bbcdca9d67e1d9d6c57cfb1200fb01efe0f3109b2ce99b231f90779532814a81370a1bd3274a0f58585039 - languageName: node - linkType: hard - -"enquirer@npm:2.3.6": - version: 2.3.6 - resolution: "enquirer@npm:2.3.6" - dependencies: - ansi-colors: "npm:^4.1.1" - checksum: 10c0/8e070e052c2c64326a2803db9084d21c8aaa8c688327f133bf65c4a712586beb126fd98c8a01cfb0433e82a4bd3b6262705c55a63e0f7fb91d06b9cedbde9a11 - languageName: node - linkType: hard - -"entities@npm:^4.2.0": - version: 4.4.0 - resolution: "entities@npm:4.4.0" - checksum: 10c0/b7971419897622d3996bbbff99249e166caaaf3ea95d3841d6dc5d3bf315f133b649fbe932623e3cc527d871112e7563a8284e24f23e472126aa90c4e9c3215b - languageName: node - linkType: hard - -"entities@npm:^6.0.0": - version: 6.0.1 - resolution: "entities@npm:6.0.1" - checksum: 10c0/ed836ddac5acb34341094eb495185d527bd70e8632b6c0d59548cbfa23defdbae70b96f9a405c82904efa421230b5b3fd2283752447d737beffd3f3e6ee74414 - languageName: node - linkType: hard - -"env-paths@npm:^2.2.0": - version: 2.2.1 - resolution: "env-paths@npm:2.2.1" - checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 - languageName: node - linkType: hard - -"epg-grabber@npm:^0.38.0": - version: 0.38.0 - resolution: "epg-grabber@npm:0.38.0" - dependencies: - axios: "npm:^1.6.1" - axios-cache-interceptor: "npm:^0.10.3" - axios-mock-adapter: "npm:^1.20.0" - commander: "npm:^7.1.0" - curl-generator: "npm:^0.2.0" - cwait: "npm:^1.1.2" - dayjs: "npm:^1.10.4" - epg-parser: "npm:^0.1.6" - fs-extra: "npm:^11.1.1" - glob: "npm:^7.1.6" - http-cookie-agent: "npm:^6.0.8" - lodash: "npm:^4.17.21" - node-gzip: "npm:^1.1.2" - socks-proxy-agent: "npm:^8.0.5" - tough-cookie: "npm:^5.0.0" - winston: "npm:^3.3.3" - xml-js: "npm:^1.6.11" - bin: - epg-grabber: bin/epg-grabber.js - checksum: 10c0/0f1635ce04853825dd142856f8a2d2c8560476a560a8a4fea997d983266aa442c4f4384b9da5b90a750aba42491100fb12160ce92b4d240ac707d0e766a04397 - languageName: node - linkType: hard - -"epg-parser@npm:^0.1.6": - version: 0.1.6 - resolution: "epg-parser@npm:0.1.6" - dependencies: - xml-js: "npm:^1.6.11" - checksum: 10c0/cce5ac8765e122724893773cf06da908e19e4a63fdd6345c9a78e1a8f34b02691d2fc92fedd813dd651c41574d1e4f64d5420b0a93a62ce577a652292d6a475b - languageName: node - linkType: hard - -"epg-parser@npm:^0.3.1": - version: 0.3.1 - resolution: "epg-parser@npm:0.3.1" - dependencies: - dayjs: "npm:^1.11.6" - lodash: "npm:^4.17.21" - xml-js: "npm:^1.6.11" - checksum: 10c0/5d2898b54fe0d71c1e03765a0ed6b0016423973c8804286148b8fe637bb3899f725f9c5a79e17e5972fd7dd00015e34915586e07a9a230206f7b28e38db74b85 - languageName: node - linkType: hard - -"epg@workspace:.": - version: 0.0.0-use.local - resolution: "epg@workspace:." - dependencies: - "@alex_neo/jest-expect-message": "npm:^1.0.5" - "@eslint/eslintrc": "npm:^3.3.1" - "@eslint/js": "npm:^9.30.0" - "@freearhey/core": "npm:^0.8.2" - "@freearhey/search-js": "npm:^0.1.2" - "@ntlab/sfetch": "npm:^1.2.0" - "@octokit/core": "npm:^7.0.2" - "@octokit/plugin-paginate-rest": "npm:^13.1.1" - "@octokit/plugin-rest-endpoint-methods": "npm:^16.0.0" - "@swc/core": "npm:^1.12.7" - "@swc/jest": "npm:^0.2.38" - "@types/cli-progress": "npm:^3.11.6" - "@types/fs-extra": "npm:^11.0.4" - "@types/inquirer": "npm:^9.0.8" - "@types/jest": "npm:^30.0.0" - "@types/langs": "npm:^2.0.5" - "@types/lodash": "npm:^4.17.19" - "@types/node": "npm:^24.0.7" - "@types/node-cleanup": "npm:^2.1.5" - "@types/numeral": "npm:^2.0.5" - "@typescript-eslint/eslint-plugin": "npm:^8.35.0" - "@typescript-eslint/parser": "npm:^8.35.0" - axios: "npm:^1.10.0" - axios-cookiejar-support: "npm:^6.0.2" - chalk: "npm:^5.4.1" - cheerio: "npm:^1.1.0" - cli-progress: "npm:^3.12.0" - commander: "npm:^14.0.0" - consola: "npm:^3.4.2" - cross-env: "npm:^7.0.3" - csv-parser: "npm:^3.2.0" - cwait: "npm:^1.1.2" - dayjs: "npm:^1.11.13" - epg-grabber: "npm:^0.38.0" - epg-parser: "npm:^0.3.1" - eslint: "npm:^9.30.0" - eslint-config-prettier: "npm:^10.1.5" - form-data: "npm:^4.0.3" - fs-extra: "npm:^11.3.0" - glob: "npm:^11.0.3" - globals: "npm:^16.2.0" - husky: "npm:^9.1.7" - iconv-lite: "npm:^0.6.3" - inquirer: "npm:^12.6.3" - jest: "npm:^30.0.3" - jest-offline: "npm:^1.0.1" - langs: "npm:^2.0.0" - libxml2-wasm: "npm:^0.5.0" - lodash: "npm:^4.17.21" - luxon: "npm:^3.6.1" - mockdate: "npm:^3.0.5" - nedb-promises: "npm:^6.2.3" - node-cleanup: "npm:^2.1.2" - node-gzip: "npm:^1.1.2" - numeral: "npm:^2.0.6" - pako: "npm:^2.1.0" - parse-duration: "npm:^2.1.4" - pdf-parse: "npm:^1.1.1" - pm2: "npm:^6.0.8" - readline: "npm:^1.3.0" - run-script-os: "npm:^1.1.6" - serve: "npm:^14.2.4" - signale: "npm:^1.4.0" - skip-postinstall: "npm:^1.0.0" - socks-proxy-agent: "npm:^8.0.5" - srcset: "npm:^5.0.1" - table2array: "npm:^0.0.2" - tabletojson: "npm:^4.1.6" - tough-cookie: "npm:^5.1.2" - transliteration: "npm:^2.3.5" - tsx: "npm:^4.20.3" - typescript: "npm:^5.8.3" - unzipit: "npm:^1.4.3" - wildcard-match: "npm:^5.1.4" - languageName: unknown - linkType: soft - -"err-code@npm:^2.0.2": - version: 2.0.3 - resolution: "err-code@npm:2.0.3" - checksum: 10c0/b642f7b4dd4a376e954947550a3065a9ece6733ab8e51ad80db727aaae0817c2e99b02a97a3d6cecc648a97848305e728289cf312d09af395403a90c9d4d8a66 - languageName: node - linkType: hard - -"error-ex@npm:^1.3.1": - version: 1.3.2 - resolution: "error-ex@npm:1.3.2" - dependencies: - is-arrayish: "npm:^0.2.1" - checksum: 10c0/ba827f89369b4c93382cfca5a264d059dfefdaa56ecc5e338ffa58a6471f5ed93b71a20add1d52290a4873d92381174382658c885ac1a2305f7baca363ce9cce - languageName: node - linkType: hard - -"es-define-property@npm:^1.0.0, es-define-property@npm:^1.0.1": - version: 1.0.1 - resolution: "es-define-property@npm:1.0.1" - checksum: 10c0/3f54eb49c16c18707949ff25a1456728c883e81259f045003499efba399c08bad00deebf65cccde8c0e07908c1a225c9d472b7107e558f2a48e28d530e34527c - languageName: node - linkType: hard - -"es-errors@npm:^1.3.0": - version: 1.3.0 - resolution: "es-errors@npm:1.3.0" - checksum: 10c0/0a61325670072f98d8ae3b914edab3559b6caa980f08054a3b872052640d91da01d38df55df797fcc916389d77fc92b8d5906cf028f4db46d7e3003abecbca85 - languageName: node - linkType: hard - -"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1": - version: 1.1.1 - resolution: "es-object-atoms@npm:1.1.1" - dependencies: - es-errors: "npm:^1.3.0" - checksum: 10c0/65364812ca4daf48eb76e2a3b7a89b3f6a2e62a1c420766ce9f692665a29d94fe41fe88b65f24106f449859549711e4b40d9fb8002d862dfd7eb1c512d10be0c - languageName: node - linkType: hard - -"es-set-tostringtag@npm:^2.1.0": - version: 2.1.0 - resolution: "es-set-tostringtag@npm:2.1.0" - dependencies: - es-errors: "npm:^1.3.0" - get-intrinsic: "npm:^1.2.6" - has-tostringtag: "npm:^1.0.2" - hasown: "npm:^2.0.2" - checksum: 10c0/ef2ca9ce49afe3931cb32e35da4dcb6d86ab02592cfc2ce3e49ced199d9d0bb5085fc7e73e06312213765f5efa47cc1df553a6a5154584b21448e9fb8355b1af - languageName: node - linkType: hard - -"esbuild@npm:~0.25.0": - version: 0.25.2 - resolution: "esbuild@npm:0.25.2" - dependencies: - "@esbuild/aix-ppc64": "npm:0.25.2" - "@esbuild/android-arm": "npm:0.25.2" - "@esbuild/android-arm64": "npm:0.25.2" - "@esbuild/android-x64": "npm:0.25.2" - "@esbuild/darwin-arm64": "npm:0.25.2" - "@esbuild/darwin-x64": "npm:0.25.2" - "@esbuild/freebsd-arm64": "npm:0.25.2" - "@esbuild/freebsd-x64": "npm:0.25.2" - "@esbuild/linux-arm": "npm:0.25.2" - "@esbuild/linux-arm64": "npm:0.25.2" - "@esbuild/linux-ia32": "npm:0.25.2" - "@esbuild/linux-loong64": "npm:0.25.2" - "@esbuild/linux-mips64el": "npm:0.25.2" - "@esbuild/linux-ppc64": "npm:0.25.2" - "@esbuild/linux-riscv64": "npm:0.25.2" - "@esbuild/linux-s390x": "npm:0.25.2" - "@esbuild/linux-x64": "npm:0.25.2" - "@esbuild/netbsd-arm64": "npm:0.25.2" - "@esbuild/netbsd-x64": "npm:0.25.2" - "@esbuild/openbsd-arm64": "npm:0.25.2" - "@esbuild/openbsd-x64": "npm:0.25.2" - "@esbuild/sunos-x64": "npm:0.25.2" - "@esbuild/win32-arm64": "npm:0.25.2" - "@esbuild/win32-ia32": "npm:0.25.2" - "@esbuild/win32-x64": "npm:0.25.2" - dependenciesMeta: - "@esbuild/aix-ppc64": - optional: true - "@esbuild/android-arm": - optional: true - "@esbuild/android-arm64": - optional: true - "@esbuild/android-x64": - optional: true - "@esbuild/darwin-arm64": - optional: true - "@esbuild/darwin-x64": - optional: true - "@esbuild/freebsd-arm64": - optional: true - "@esbuild/freebsd-x64": - optional: true - "@esbuild/linux-arm": - optional: true - "@esbuild/linux-arm64": - optional: true - "@esbuild/linux-ia32": - optional: true - "@esbuild/linux-loong64": - optional: true - "@esbuild/linux-mips64el": - optional: true - "@esbuild/linux-ppc64": - optional: true - "@esbuild/linux-riscv64": - optional: true - "@esbuild/linux-s390x": - optional: true - "@esbuild/linux-x64": - optional: true - "@esbuild/netbsd-arm64": - optional: true - "@esbuild/netbsd-x64": - optional: true - "@esbuild/openbsd-arm64": - optional: true - "@esbuild/openbsd-x64": - optional: true - "@esbuild/sunos-x64": - optional: true - "@esbuild/win32-arm64": - optional: true - "@esbuild/win32-ia32": - optional: true - "@esbuild/win32-x64": - optional: true - bin: - esbuild: bin/esbuild - checksum: 10c0/87ce0b78699c4d192b8cf7e9b688e9a0da10e6f58ff85a368bf3044ca1fa95626c98b769b5459352282e0065585b6f994a5e6699af5cccf9d31178960e2b58fd - languageName: node - linkType: hard - -"escalade@npm:^3.1.1, escalade@npm:^3.2.0": - version: 3.2.0 - resolution: "escalade@npm:3.2.0" - checksum: 10c0/ced4dd3a78e15897ed3be74e635110bbf3b08877b0a41be50dcb325ee0e0b5f65fc2d50e9845194d7c4633f327e2e1c6cce00a71b617c5673df0374201d67f65 - languageName: node - linkType: hard - -"escape-string-regexp@npm:^1.0.5": - version: 1.0.5 - resolution: "escape-string-regexp@npm:1.0.5" - checksum: 10c0/a968ad453dd0c2724e14a4f20e177aaf32bb384ab41b674a8454afe9a41c5e6fe8903323e0a1052f56289d04bd600f81278edf140b0fcc02f5cac98d0f5b5371 - languageName: node - linkType: hard - -"escape-string-regexp@npm:^2.0.0": - version: 2.0.0 - resolution: "escape-string-regexp@npm:2.0.0" - checksum: 10c0/2530479fe8db57eace5e8646c9c2a9c80fa279614986d16dcc6bcaceb63ae77f05a851ba6c43756d816c61d7f4534baf56e3c705e3e0d884818a46808811c507 - languageName: node - linkType: hard - -"escape-string-regexp@npm:^4.0.0": - version: 4.0.0 - resolution: "escape-string-regexp@npm:4.0.0" - checksum: 10c0/9497d4dd307d845bd7f75180d8188bb17ea8c151c1edbf6b6717c100e104d629dc2dfb687686181b0f4b7d732c7dfdc4d5e7a8ff72de1b0ca283a75bbb3a9cd9 - languageName: node - linkType: hard - -"escodegen@npm:^2.1.0": - version: 2.1.0 - resolution: "escodegen@npm:2.1.0" - dependencies: - esprima: "npm:^4.0.1" - estraverse: "npm:^5.2.0" - esutils: "npm:^2.0.2" - source-map: "npm:~0.6.1" - dependenciesMeta: - source-map: - optional: true - bin: - escodegen: bin/escodegen.js - esgenerate: bin/esgenerate.js - checksum: 10c0/e1450a1f75f67d35c061bf0d60888b15f62ab63aef9df1901cffc81cffbbb9e8b3de237c5502cf8613a017c1df3a3003881307c78835a1ab54d8c8d2206e01d3 - languageName: node - linkType: hard - -"eslint-config-prettier@npm:^10.1.5": - version: 10.1.5 - resolution: "eslint-config-prettier@npm:10.1.5" - peerDependencies: - eslint: ">=7.0.0" - bin: - eslint-config-prettier: bin/cli.js - checksum: 10c0/5486255428e4577e8064b40f27db299faf7312b8e43d7b4bc913a6426e6c0f5950cd519cad81ae24e9aecb4002c502bc665c02e3b52efde57af2debcf27dd6e0 - languageName: node - linkType: hard - -"eslint-scope@npm:^8.4.0": - version: 8.4.0 - resolution: "eslint-scope@npm:8.4.0" - dependencies: - esrecurse: "npm:^4.3.0" - estraverse: "npm:^5.2.0" - checksum: 10c0/407f6c600204d0f3705bd557f81bd0189e69cd7996f408f8971ab5779c0af733d1af2f1412066b40ee1588b085874fc37a2333986c6521669cdbdd36ca5058e0 - languageName: node - linkType: hard - -"eslint-visitor-keys@npm:^3.4.3": - version: 3.4.3 - resolution: "eslint-visitor-keys@npm:3.4.3" - checksum: 10c0/92708e882c0a5ffd88c23c0b404ac1628cf20104a108c745f240a13c332a11aac54f49a22d5762efbffc18ecbc9a580d1b7ad034bf5f3cc3307e5cbff2ec9820 - languageName: node - linkType: hard - -"eslint-visitor-keys@npm:^4.2.1": - version: 4.2.1 - resolution: "eslint-visitor-keys@npm:4.2.1" - checksum: 10c0/fcd43999199d6740db26c58dbe0c2594623e31ca307e616ac05153c9272f12f1364f5a0b1917a8e962268fdecc6f3622c1c2908b4fcc2e047a106fe6de69dc43 - languageName: node - linkType: hard - -"eslint@npm:^9.30.0": - version: 9.30.0 - resolution: "eslint@npm:9.30.0" - dependencies: - "@eslint-community/eslint-utils": "npm:^4.2.0" - "@eslint-community/regexpp": "npm:^4.12.1" - "@eslint/config-array": "npm:^0.21.0" - "@eslint/config-helpers": "npm:^0.3.0" - "@eslint/core": "npm:^0.14.0" - "@eslint/eslintrc": "npm:^3.3.1" - "@eslint/js": "npm:9.30.0" - "@eslint/plugin-kit": "npm:^0.3.1" - "@humanfs/node": "npm:^0.16.6" - "@humanwhocodes/module-importer": "npm:^1.0.1" - "@humanwhocodes/retry": "npm:^0.4.2" - "@types/estree": "npm:^1.0.6" - "@types/json-schema": "npm:^7.0.15" - ajv: "npm:^6.12.4" - chalk: "npm:^4.0.0" - cross-spawn: "npm:^7.0.6" - debug: "npm:^4.3.2" - escape-string-regexp: "npm:^4.0.0" - eslint-scope: "npm:^8.4.0" - eslint-visitor-keys: "npm:^4.2.1" - espree: "npm:^10.4.0" - esquery: "npm:^1.5.0" - esutils: "npm:^2.0.2" - fast-deep-equal: "npm:^3.1.3" - file-entry-cache: "npm:^8.0.0" - find-up: "npm:^5.0.0" - glob-parent: "npm:^6.0.2" - ignore: "npm:^5.2.0" - imurmurhash: "npm:^0.1.4" - is-glob: "npm:^4.0.0" - json-stable-stringify-without-jsonify: "npm:^1.0.1" - lodash.merge: "npm:^4.6.2" - minimatch: "npm:^3.1.2" - natural-compare: "npm:^1.4.0" - optionator: "npm:^0.9.3" - peerDependencies: - jiti: "*" - peerDependenciesMeta: - jiti: - optional: true - bin: - eslint: bin/eslint.js - checksum: 10c0/ebc4b17cfd96f308ebaeb12dfab133a551eb03200c80109ecf663fbeb9af83c4eb3c143407c1b04522d23b5f5844fe9a629b00d409adfc460c1aadf5108da86a - languageName: node - linkType: hard - -"espree@npm:^10.0.1, espree@npm:^10.4.0": - version: 10.4.0 - resolution: "espree@npm:10.4.0" - dependencies: - acorn: "npm:^8.15.0" - acorn-jsx: "npm:^5.3.2" - eslint-visitor-keys: "npm:^4.2.1" - checksum: 10c0/c63fe06131c26c8157b4083313cb02a9a54720a08e21543300e55288c40e06c3fc284bdecf108d3a1372c5934a0a88644c98714f38b6ae8ed272b40d9ea08d6b - languageName: node - linkType: hard - -"esprima@npm:^4.0.0, esprima@npm:^4.0.1": - version: 4.0.1 - resolution: "esprima@npm:4.0.1" - bin: - esparse: ./bin/esparse.js - esvalidate: ./bin/esvalidate.js - checksum: 10c0/ad4bab9ead0808cf56501750fd9d3fb276f6b105f987707d059005d57e182d18a7c9ec7f3a01794ebddcca676773e42ca48a32d67a250c9d35e009ca613caba3 - languageName: node - linkType: hard - -"esquery@npm:^1.5.0": - version: 1.6.0 - resolution: "esquery@npm:1.6.0" - dependencies: - estraverse: "npm:^5.1.0" - checksum: 10c0/cb9065ec605f9da7a76ca6dadb0619dfb611e37a81e318732977d90fab50a256b95fee2d925fba7c2f3f0523aa16f91587246693bc09bc34d5a59575fe6e93d2 - languageName: node - linkType: hard - -"esrecurse@npm:^4.3.0": - version: 4.3.0 - resolution: "esrecurse@npm:4.3.0" - dependencies: - estraverse: "npm:^5.2.0" - checksum: 10c0/81a37116d1408ded88ada45b9fb16dbd26fba3aadc369ce50fcaf82a0bac12772ebd7b24cd7b91fc66786bf2c1ac7b5f196bc990a473efff972f5cb338877cf5 - languageName: node - linkType: hard - -"estraverse@npm:^5.1.0, estraverse@npm:^5.2.0": - version: 5.3.0 - resolution: "estraverse@npm:5.3.0" - checksum: 10c0/1ff9447b96263dec95d6d67431c5e0771eb9776427421260a3e2f0fdd5d6bd4f8e37a7338f5ad2880c9f143450c9b1e4fc2069060724570a49cf9cf0312bd107 - languageName: node - linkType: hard - -"esutils@npm:^2.0.2": - version: 2.0.3 - resolution: "esutils@npm:2.0.3" - checksum: 10c0/9a2fe69a41bfdade834ba7c42de4723c97ec776e40656919c62cbd13607c45e127a003f05f724a1ea55e5029a4cf2de444b13009f2af71271e42d93a637137c7 - languageName: node - linkType: hard - -"eventemitter2@npm:5.0.1, eventemitter2@npm:~5.0.1": - version: 5.0.1 - resolution: "eventemitter2@npm:5.0.1" - checksum: 10c0/2c82966b78341898dbdaebba5cfc536939bc162d145227b241f55d6e5f037a33863169c8f1f3437dd9cd440e7358f04f0e06b8eba2ff745220866c7dce4f7d0a - languageName: node - linkType: hard - -"eventemitter2@npm:^6.3.1": - version: 6.4.9 - resolution: "eventemitter2@npm:6.4.9" - checksum: 10c0/b2adf7d9f1544aa2d95ee271b0621acaf1e309d85ebcef1244fb0ebc7ab0afa6ffd5e371535d0981bc46195ad67fd6ff57a8d1db030584dee69aa5e371a27ea7 - languageName: node - linkType: hard - -"execa@npm:^5.1.1": - version: 5.1.1 - resolution: "execa@npm:5.1.1" - dependencies: - cross-spawn: "npm:^7.0.3" - get-stream: "npm:^6.0.0" - human-signals: "npm:^2.1.0" - is-stream: "npm:^2.0.0" - merge-stream: "npm:^2.0.0" - npm-run-path: "npm:^4.0.1" - onetime: "npm:^5.1.2" - signal-exit: "npm:^3.0.3" - strip-final-newline: "npm:^2.0.0" - checksum: 10c0/c8e615235e8de4c5addf2fa4c3da3e3aa59ce975a3e83533b4f6a71750fb816a2e79610dc5f1799b6e28976c9ae86747a36a606655bf8cb414a74d8d507b304f - languageName: node - linkType: hard - -"exit-x@npm:^0.2.2": - version: 0.2.2 - resolution: "exit-x@npm:0.2.2" - checksum: 10c0/212a7a095ca5540e9581f1ef2d1d6a40df7a6027c8cc96e78ce1d16b86d1a88326d4a0eff8dff2b5ec1e68bb0c1edd5d0dfdde87df1869bf7514d4bc6a5cbd72 - languageName: node - linkType: hard - -"expect@npm:30.0.3": - version: 30.0.3 - resolution: "expect@npm:30.0.3" - dependencies: - "@jest/expect-utils": "npm:30.0.3" - "@jest/get-type": "npm:30.0.1" - jest-matcher-utils: "npm:30.0.3" - jest-message-util: "npm:30.0.2" - jest-mock: "npm:30.0.2" - jest-util: "npm:30.0.2" - checksum: 10c0/6bb88a42d6fcacbd0b25d4f90c389e2e439cd1d3b68f4b708582bcfe4a9575d1584edb554921e21230bc484ae55f8d639fc8186545ba9e6070a83e82a18655d8 - languageName: node - linkType: hard - -"expect@npm:^30.0.0": - version: 30.0.2 - resolution: "expect@npm:30.0.2" - dependencies: - "@jest/expect-utils": "npm:30.0.2" - "@jest/get-type": "npm:30.0.1" - jest-matcher-utils: "npm:30.0.2" - jest-message-util: "npm:30.0.2" - jest-mock: "npm:30.0.2" - jest-util: "npm:30.0.2" - checksum: 10c0/c313c2afcee52e3d333ace771f88056230a689f0e5b4bad944841635f028e07c2eb3947568a032391e8c055439accb3b381d4832114a272bbd94bcd9953b1db0 - languageName: node - linkType: hard - -"exponential-backoff@npm:^3.1.1": - version: 3.1.2 - resolution: "exponential-backoff@npm:3.1.2" - checksum: 10c0/d9d3e1eafa21b78464297df91f1776f7fbaa3d5e3f7f0995648ca5b89c069d17055033817348d9f4a43d1c20b0eab84f75af6991751e839df53e4dfd6f22e844 - languageName: node - linkType: hard - -"external-editor@npm:^3.1.0": - version: 3.1.0 - resolution: "external-editor@npm:3.1.0" - dependencies: - chardet: "npm:^0.7.0" - iconv-lite: "npm:^0.4.24" - tmp: "npm:^0.0.33" - checksum: 10c0/c98f1ba3efdfa3c561db4447ff366a6adb5c1e2581462522c56a18bf90dfe4da382f9cd1feee3e330108c3595a854b218272539f311ba1b3298f841eb0fbf339 - languageName: node - linkType: hard - -"extrareqp2@npm:^1.0.0": - version: 1.0.0 - resolution: "extrareqp2@npm:1.0.0" - dependencies: - follow-redirects: "npm:^1.14.0" - checksum: 10c0/822861bcce551792a16e08d5459203332762755246bb0705432b5557837de16e1eb0d6c8416de8edcfe90c100fb087b09dfc264983cdea6d1a9804982ae836d1 - languageName: node - linkType: hard - -"fast-content-type-parse@npm:^3.0.0": - version: 3.0.0 - resolution: "fast-content-type-parse@npm:3.0.0" - checksum: 10c0/06251880c83b7118af3a5e66e8bcee60d44f48b39396fc60acc2b4630bd5f3e77552b999b5c8e943d45a818854360e5e97164c374ec4b562b4df96a2cdf2e188 - languageName: node - linkType: hard - -"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": - version: 3.1.3 - resolution: "fast-deep-equal@npm:3.1.3" - checksum: 10c0/40dedc862eb8992c54579c66d914635afbec43350afbbe991235fdcb4e3a8d5af1b23ae7e79bef7d4882d0ecee06c3197488026998fb19f72dc95acff1d1b1d0 - languageName: node - linkType: hard - -"fast-defer@npm:^1.1.7": - version: 1.1.7 - resolution: "fast-defer@npm:1.1.7" - checksum: 10c0/a5bc60f3dad537e83b9d7ef143b3ab6ec44462f35dafa77d2b73512b1a1ce1bf6f556de42cebda0fd53599082064aa2b8ff0c2820cfca493cb0fe61083f0fd09 - languageName: node - linkType: hard - -"fast-glob@npm:^3.3.2": - version: 3.3.3 - resolution: "fast-glob@npm:3.3.3" - dependencies: - "@nodelib/fs.stat": "npm:^2.0.2" - "@nodelib/fs.walk": "npm:^1.2.3" - glob-parent: "npm:^5.1.2" - merge2: "npm:^1.3.0" - micromatch: "npm:^4.0.8" - checksum: 10c0/f6aaa141d0d3384cf73cbcdfc52f475ed293f6d5b65bfc5def368b09163a9f7e5ec2b3014d80f733c405f58e470ee0cc451c2937685045cddcdeaa24199c43fe - languageName: node - linkType: hard - -"fast-json-patch@npm:^3.1.0": - version: 3.1.1 - resolution: "fast-json-patch@npm:3.1.1" - checksum: 10c0/8a0438b4818bb53153275fe5b38033610e8c9d9eb11869e6a7dc05eb92fa70f3caa57015e344eb3ae1e71c7a75ad4cc6bc2dc9e0ff281d6ed8ecd44505210ca8 - languageName: node - linkType: hard - -"fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0": - version: 2.1.0 - resolution: "fast-json-stable-stringify@npm:2.1.0" - checksum: 10c0/7f081eb0b8a64e0057b3bb03f974b3ef00135fbf36c1c710895cd9300f13c94ba809bb3a81cf4e1b03f6e5285610a61abbd7602d0652de423144dfee5a389c9b - languageName: node - linkType: hard - -"fast-levenshtein@npm:^2.0.6": - version: 2.0.6 - resolution: "fast-levenshtein@npm:2.0.6" - checksum: 10c0/111972b37338bcb88f7d9e2c5907862c280ebf4234433b95bc611e518d192ccb2d38119c4ac86e26b668d75f7f3894f4ff5c4982899afced7ca78633b08287c4 - languageName: node - linkType: hard - -"fastq@npm:^1.6.0": - version: 1.19.1 - resolution: "fastq@npm:1.19.1" - dependencies: - reusify: "npm:^1.0.4" - checksum: 10c0/ebc6e50ac7048daaeb8e64522a1ea7a26e92b3cee5cd1c7f2316cdca81ba543aa40a136b53891446ea5c3a67ec215fbaca87ad405f102dd97012f62916905630 - languageName: node - linkType: hard - -"fb-watchman@npm:^2.0.2": - version: 2.0.2 - resolution: "fb-watchman@npm:2.0.2" - dependencies: - bser: "npm:2.1.1" - checksum: 10c0/feae89ac148adb8f6ae8ccd87632e62b13563e6fb114cacb5265c51f585b17e2e268084519fb2edd133872f1d47a18e6bfd7e5e08625c0d41b93149694187581 - languageName: node - linkType: hard - -"fclone@npm:1.0.11, fclone@npm:~1.0.11": - version: 1.0.11 - resolution: "fclone@npm:1.0.11" - checksum: 10c0/dbe3ebd0883edeec2998874bf951aa03198d727f1091351b22af250ff53e227ee94872487ae88ba7280b2469fb164a7d4dd4e5ece10afd4988ab4712f49bc43b - languageName: node - linkType: hard - -"fdir@npm:^6.4.4": - version: 6.4.6 - resolution: "fdir@npm:6.4.6" - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - checksum: 10c0/45b559cff889934ebb8bc498351e5acba40750ada7e7d6bde197768d2fa67c149be8ae7f8ff34d03f4e1eb20f2764116e56440aaa2f6689e9a4aa7ef06acafe9 - languageName: node - linkType: hard - -"fecha@npm:^4.2.0": - version: 4.2.3 - resolution: "fecha@npm:4.2.3" - checksum: 10c0/0e895965959cf6a22bb7b00f0bf546f2783836310f510ddf63f463e1518d4c96dec61ab33fdfd8e79a71b4856a7c865478ce2ee8498d560fe125947703c9b1cf - languageName: node - linkType: hard - -"figures@npm:^2.0.0": - version: 2.0.0 - resolution: "figures@npm:2.0.0" - dependencies: - escape-string-regexp: "npm:^1.0.5" - checksum: 10c0/5dc5a75fec3e7e04ae65d6ce51d28b3e70d4656c51b06996b6fdb2cb5b542df512e3b3c04482f5193a964edddafa5521479ff948fa84e12ff556e53e094ab4ce - languageName: node - linkType: hard - -"file-entry-cache@npm:^8.0.0": - version: 8.0.0 - resolution: "file-entry-cache@npm:8.0.0" - dependencies: - flat-cache: "npm:^4.0.0" - checksum: 10c0/9e2b5938b1cd9b6d7e3612bdc533afd4ac17b2fc646569e9a8abbf2eb48e5eb8e316bc38815a3ef6a1b456f4107f0d0f055a614ca613e75db6bf9ff4d72c1638 - languageName: node - linkType: hard - -"fill-range@npm:^7.1.1": - version: 7.1.1 - resolution: "fill-range@npm:7.1.1" - dependencies: - to-regex-range: "npm:^5.0.1" - checksum: 10c0/b75b691bbe065472f38824f694c2f7449d7f5004aa950426a2c28f0306c60db9b880c0b0e4ed819997ffb882d1da02cfcfc819bddc94d71627f5269682edf018 - languageName: node - linkType: hard - -"find-up@npm:^2.0.0": - version: 2.1.0 - resolution: "find-up@npm:2.1.0" - dependencies: - locate-path: "npm:^2.0.0" - checksum: 10c0/c080875c9fe28eb1962f35cbe83c683796a0321899f1eed31a37577800055539815de13d53495049697d3ba313013344f843bb9401dd337a1b832be5edfc6840 - languageName: node - linkType: hard - -"find-up@npm:^4.0.0, find-up@npm:^4.1.0": - version: 4.1.0 - resolution: "find-up@npm:4.1.0" - dependencies: - locate-path: "npm:^5.0.0" - path-exists: "npm:^4.0.0" - checksum: 10c0/0406ee89ebeefa2d507feb07ec366bebd8a6167ae74aa4e34fb4c4abd06cf782a3ce26ae4194d70706f72182841733f00551c209fe575cb00bd92104056e78c1 - languageName: node - linkType: hard - -"find-up@npm:^5.0.0": - version: 5.0.0 - resolution: "find-up@npm:5.0.0" - dependencies: - locate-path: "npm:^6.0.0" - path-exists: "npm:^4.0.0" - checksum: 10c0/062c5a83a9c02f53cdd6d175a37ecf8f87ea5bbff1fdfb828f04bfa021441bc7583e8ebc0872a4c1baab96221fb8a8a275a19809fb93fbc40bd69ec35634069a - languageName: node - linkType: hard - -"flat-cache@npm:^4.0.0": - version: 4.0.1 - resolution: "flat-cache@npm:4.0.1" - dependencies: - flatted: "npm:^3.2.9" - keyv: "npm:^4.5.4" - checksum: 10c0/2c59d93e9faa2523e4fda6b4ada749bed432cfa28c8e251f33b25795e426a1c6dbada777afb1f74fcfff33934fdbdea921ee738fcc33e71adc9d6eca984a1cfc - languageName: node - linkType: hard - -"flatted@npm:^3.2.9": - version: 3.3.2 - resolution: "flatted@npm:3.3.2" - checksum: 10c0/24cc735e74d593b6c767fe04f2ef369abe15b62f6906158079b9874bdb3ee5ae7110bb75042e70cd3f99d409d766f357caf78d5ecee9780206f5fdc5edbad334 - languageName: node - linkType: hard - -"fn.name@npm:1.x.x": - version: 1.1.0 - resolution: "fn.name@npm:1.1.0" - checksum: 10c0/8ad62aa2d4f0b2a76d09dba36cfec61c540c13a0fd72e5d94164e430f987a7ce6a743112bbeb14877c810ef500d1f73d7f56e76d029d2e3413f20d79e3460a9a - languageName: node - linkType: hard - -"follow-redirects@npm:^1.14.0, follow-redirects@npm:^1.15.6": - version: 1.15.6 - resolution: "follow-redirects@npm:1.15.6" - peerDependenciesMeta: - debug: - optional: true - checksum: 10c0/9ff767f0d7be6aa6870c82ac79cf0368cd73e01bbc00e9eb1c2a16fbb198ec105e3c9b6628bb98e9f3ac66fe29a957b9645bcb9a490bb7aa0d35f908b6b85071 - languageName: node - linkType: hard - -"for-each@npm:^0.3.5": - version: 0.3.5 - resolution: "for-each@npm:0.3.5" - dependencies: - is-callable: "npm:^1.2.7" - checksum: 10c0/0e0b50f6a843a282637d43674d1fb278dda1dd85f4f99b640024cfb10b85058aac0cc781bf689d5fe50b4b7f638e91e548560723a4e76e04fe96ae35ef039cee - languageName: node - linkType: hard - -"foreground-child@npm:^3.1.0, foreground-child@npm:^3.3.1": - version: 3.3.1 - resolution: "foreground-child@npm:3.3.1" - dependencies: - cross-spawn: "npm:^7.0.6" - signal-exit: "npm:^4.0.1" - checksum: 10c0/8986e4af2430896e65bc2788d6679067294d6aee9545daefc84923a0a4b399ad9c7a3ea7bd8c0b2b80fdf4a92de4c69df3f628233ff3224260e9c1541a9e9ed3 - languageName: node - linkType: hard - -"form-data@npm:^4.0.0, form-data@npm:^4.0.3": - version: 4.0.3 - resolution: "form-data@npm:4.0.3" - dependencies: - asynckit: "npm:^0.4.0" - combined-stream: "npm:^1.0.8" - es-set-tostringtag: "npm:^2.1.0" - hasown: "npm:^2.0.2" - mime-types: "npm:^2.1.12" - checksum: 10c0/f0cf45873d600110b5fadf5804478377694f73a1ed97aaa370a74c90cebd7fe6e845a081171668a5476477d0d55a73a4e03d6682968fa8661eac2a81d651fcdb - languageName: node - linkType: hard - -"fs-extra@npm:^11.1.1, fs-extra@npm:^11.3.0": - version: 11.3.0 - resolution: "fs-extra@npm:11.3.0" - dependencies: - graceful-fs: "npm:^4.2.0" - jsonfile: "npm:^6.0.1" - universalify: "npm:^2.0.0" - checksum: 10c0/5f95e996186ff45463059feb115a22fb048bdaf7e487ecee8a8646c78ed8fdca63630e3077d4c16ce677051f5e60d3355a06f3cd61f3ca43f48cc58822a44d0a - languageName: node - linkType: hard - -"fs-minipass@npm:^3.0.0": - version: 3.0.3 - resolution: "fs-minipass@npm:3.0.3" - dependencies: - minipass: "npm:^7.0.3" - checksum: 10c0/63e80da2ff9b621e2cb1596abcb9207f1cf82b968b116ccd7b959e3323144cce7fb141462200971c38bbf2ecca51695069db45265705bed09a7cd93ae5b89f94 - languageName: node - linkType: hard - -"fs.realpath@npm:^1.0.0": - version: 1.0.0 - resolution: "fs.realpath@npm:1.0.0" - checksum: 10c0/444cf1291d997165dfd4c0d58b69f0e4782bfd9149fd72faa4fe299e68e0e93d6db941660b37dd29153bf7186672ececa3b50b7e7249477b03fdf850f287c948 - languageName: node - linkType: hard - -"fsevents@npm:^2.3.3, fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": - version: 2.3.3 - resolution: "fsevents@npm:2.3.3" - dependencies: - node-gyp: "npm:latest" - checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 - conditions: os=darwin - languageName: node - linkType: hard - -"fsevents@patch:fsevents@npm%3A^2.3.3#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": - version: 2.3.3 - resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" - dependencies: - node-gyp: "npm:latest" - conditions: os=darwin - languageName: node - linkType: hard - -"function-bind@npm:^1.1.2": - version: 1.1.2 - resolution: "function-bind@npm:1.1.2" - checksum: 10c0/d8680ee1e5fcd4c197e4ac33b2b4dce03c71f4d91717292785703db200f5c21f977c568d28061226f9b5900cbcd2c84463646134fd5337e7925e0942bc3f46d5 - languageName: node - linkType: hard - -"gensync@npm:^1.0.0-beta.2": - version: 1.0.0-beta.2 - resolution: "gensync@npm:1.0.0-beta.2" - checksum: 10c0/782aba6cba65b1bb5af3b095d96249d20edbe8df32dbf4696fd49be2583faf676173bf4809386588828e4dd76a3354fcbeb577bab1c833ccd9fc4577f26103f8 - languageName: node - linkType: hard - -"get-caller-file@npm:^2.0.5": - version: 2.0.5 - resolution: "get-caller-file@npm:2.0.5" - checksum: 10c0/c6c7b60271931fa752aeb92f2b47e355eac1af3a2673f47c9589e8f8a41adc74d45551c1bc57b5e66a80609f10ffb72b6f575e4370d61cc3f7f3aaff01757cde - languageName: node - linkType: hard - -"get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.3.0": - version: 1.3.0 - resolution: "get-intrinsic@npm:1.3.0" - dependencies: - call-bind-apply-helpers: "npm:^1.0.2" - es-define-property: "npm:^1.0.1" - es-errors: "npm:^1.3.0" - es-object-atoms: "npm:^1.1.1" - function-bind: "npm:^1.1.2" - get-proto: "npm:^1.0.1" - gopd: "npm:^1.2.0" - has-symbols: "npm:^1.1.0" - hasown: "npm:^2.0.2" - math-intrinsics: "npm:^1.1.0" - checksum: 10c0/52c81808af9a8130f581e6a6a83e1ba4a9f703359e7a438d1369a5267a25412322f03dcbd7c549edaef0b6214a0630a28511d7df0130c93cfd380f4fa0b5b66a - languageName: node - linkType: hard - -"get-package-type@npm:^0.1.0": - version: 0.1.0 - resolution: "get-package-type@npm:0.1.0" - checksum: 10c0/e34cdf447fdf1902a1f6d5af737eaadf606d2ee3518287abde8910e04159368c268568174b2e71102b87b26c2020486f126bfca9c4fb1ceb986ff99b52ecd1be - languageName: node - linkType: hard - -"get-proto@npm:^1.0.0, get-proto@npm:^1.0.1": - version: 1.0.1 - resolution: "get-proto@npm:1.0.1" - dependencies: - dunder-proto: "npm:^1.0.1" - es-object-atoms: "npm:^1.0.0" - checksum: 10c0/9224acb44603c5526955e83510b9da41baf6ae73f7398875fba50edc5e944223a89c4a72b070fcd78beb5f7bdda58ecb6294adc28f7acfc0da05f76a2399643c - languageName: node - linkType: hard - -"get-stream@npm:^6.0.0": - version: 6.0.1 - resolution: "get-stream@npm:6.0.1" - checksum: 10c0/49825d57d3fd6964228e6200a58169464b8e8970489b3acdc24906c782fb7f01f9f56f8e6653c4a50713771d6658f7cfe051e5eb8c12e334138c9c918b296341 - languageName: node - linkType: hard - -"get-tsconfig@npm:^4.7.5": - version: 4.8.1 - resolution: "get-tsconfig@npm:4.8.1" - dependencies: - resolve-pkg-maps: "npm:^1.0.0" - checksum: 10c0/536ee85d202f604f4b5fb6be81bcd6e6d9a96846811e83e9acc6de4a04fb49506edea0e1b8cf1d5ee7af33e469916ec2809d4c5445ab8ae015a7a51fbd1572f9 - languageName: node - linkType: hard - -"get-uri@npm:^6.0.1": - version: 6.0.4 - resolution: "get-uri@npm:6.0.4" - dependencies: - basic-ftp: "npm:^5.0.2" - data-uri-to-buffer: "npm:^6.0.2" - debug: "npm:^4.3.4" - checksum: 10c0/07c87abe1f97a4545fae329a37a45e276ec57e6ad48dad2a97780f87c96b00a82c2043ab49e1a991f99bb5cff8f8ed975e44e4f8b3c9600f35493a97f123499f - languageName: node - linkType: hard - -"git-node-fs@npm:^1.0.0": - version: 1.0.0 - resolution: "git-node-fs@npm:1.0.0" - checksum: 10c0/a69f81c2495db04aebb13d7884ddafca12d3635fd8f45ea05264bd1417b915aea9f09f2beb5ed8a744d7fb1a94b352993921432c1e078c5b24d102631c529374 - languageName: node - linkType: hard - -"git-sha1@npm:^0.1.2": - version: 0.1.2 - resolution: "git-sha1@npm:0.1.2" - checksum: 10c0/2781eb83f9f12ee482631024f495501cc7c855d23eda0c39d10094e05e49e05b7ee04397593e5b6d1188368034667cf4fdfff8e2f3fc35ac4193b75f99a79643 - languageName: node - linkType: hard - -"glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2": - version: 5.1.2 - resolution: "glob-parent@npm:5.1.2" - dependencies: - is-glob: "npm:^4.0.1" - checksum: 10c0/cab87638e2112bee3f839ef5f6e0765057163d39c66be8ec1602f3823da4692297ad4e972de876ea17c44d652978638d2fd583c6713d0eb6591706825020c9ee - languageName: node - linkType: hard - -"glob-parent@npm:^6.0.2": - version: 6.0.2 - resolution: "glob-parent@npm:6.0.2" - dependencies: - is-glob: "npm:^4.0.3" - checksum: 10c0/317034d88654730230b3f43bb7ad4f7c90257a426e872ea0bf157473ac61c99bf5d205fad8f0185f989be8d2fa6d3c7dce1645d99d545b6ea9089c39f838e7f8 - languageName: node - linkType: hard - -"glob@npm:^10.2.2, glob@npm:^10.3.10": - version: 10.4.5 - resolution: "glob@npm:10.4.5" - dependencies: - foreground-child: "npm:^3.1.0" - jackspeak: "npm:^3.1.2" - minimatch: "npm:^9.0.4" - minipass: "npm:^7.1.2" - package-json-from-dist: "npm:^1.0.0" - path-scurry: "npm:^1.11.1" - bin: - glob: dist/esm/bin.mjs - checksum: 10c0/19a9759ea77b8e3ca0a43c2f07ecddc2ad46216b786bb8f993c445aee80d345925a21e5280c7b7c6c59e860a0154b84e4b2b60321fea92cd3c56b4a7489f160e - languageName: node - linkType: hard - -"glob@npm:^11.0.1, glob@npm:^11.0.3": - version: 11.0.3 - resolution: "glob@npm:11.0.3" - dependencies: - foreground-child: "npm:^3.3.1" - jackspeak: "npm:^4.1.1" - minimatch: "npm:^10.0.3" - minipass: "npm:^7.1.2" - package-json-from-dist: "npm:^1.0.0" - path-scurry: "npm:^2.0.0" - bin: - glob: dist/esm/bin.mjs - checksum: 10c0/7d24457549ec2903920dfa3d8e76850e7c02aa709122f0164b240c712f5455c0b457e6f2a1eee39344c6148e39895be8094ae8cfef7ccc3296ed30bce250c661 - languageName: node - linkType: hard - -"glob@npm:^7.1.4, glob@npm:^7.1.6": - version: 7.2.3 - resolution: "glob@npm:7.2.3" - dependencies: - fs.realpath: "npm:^1.0.0" - inflight: "npm:^1.0.4" - inherits: "npm:2" - minimatch: "npm:^3.1.1" - once: "npm:^1.3.0" - path-is-absolute: "npm:^1.0.0" - checksum: 10c0/65676153e2b0c9095100fe7f25a778bf45608eeb32c6048cf307f579649bcc30353277b3b898a3792602c65764e5baa4f643714dfbdfd64ea271d210c7a425fe - languageName: node - linkType: hard - -"globals@npm:^11.1.0": - version: 11.12.0 - resolution: "globals@npm:11.12.0" - checksum: 10c0/758f9f258e7b19226bd8d4af5d3b0dcf7038780fb23d82e6f98932c44e239f884847f1766e8fa9cc5635ccb3204f7fa7314d4408dd4002a5e8ea827b4018f0a1 - languageName: node - linkType: hard - -"globals@npm:^14.0.0": - version: 14.0.0 - resolution: "globals@npm:14.0.0" - checksum: 10c0/b96ff42620c9231ad468d4c58ff42afee7777ee1c963013ff8aabe095a451d0ceeb8dcd8ef4cbd64d2538cef45f787a78ba3a9574f4a634438963e334471302d - languageName: node - linkType: hard - -"globals@npm:^16.2.0": - version: 16.2.0 - resolution: "globals@npm:16.2.0" - checksum: 10c0/c2b3ea163faa6f8a38076b471b12f4bda891f7df7f7d2e8294fb4801d735a51a73431bf4c1696c5bf5dbca5e0a0db894698acfcbd3068730c6b12eef185dea25 - languageName: node - linkType: hard - -"gopd@npm:^1.0.1, gopd@npm:^1.2.0": - version: 1.2.0 - resolution: "gopd@npm:1.2.0" - checksum: 10c0/50fff1e04ba2b7737c097358534eacadad1e68d24cccee3272e04e007bed008e68d2614f3987788428fd192a5ae3889d08fb2331417e4fc4a9ab366b2043cead - languageName: node - linkType: hard - -"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.6": - version: 4.2.11 - resolution: "graceful-fs@npm:4.2.11" - checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 - languageName: node - linkType: hard - -"graphemer@npm:^1.4.0": - version: 1.4.0 - resolution: "graphemer@npm:1.4.0" - checksum: 10c0/e951259d8cd2e0d196c72ec711add7115d42eb9a8146c8eeda5b8d3ac91e5dd816b9cd68920726d9fd4490368e7ed86e9c423f40db87e2d8dfafa00fa17c3a31 - languageName: node - linkType: hard - -"has-flag@npm:^3.0.0": - version: 3.0.0 - resolution: "has-flag@npm:3.0.0" - checksum: 10c0/1c6c83b14b8b1b3c25b0727b8ba3e3b647f99e9e6e13eb7322107261de07a4c1be56fc0d45678fc376e09772a3a1642ccdaf8fc69bdf123b6c086598397ce473 - languageName: node - linkType: hard - -"has-flag@npm:^4.0.0": - version: 4.0.0 - resolution: "has-flag@npm:4.0.0" - checksum: 10c0/2e789c61b7888d66993e14e8331449e525ef42aac53c627cc53d1c3334e768bcb6abdc4f5f0de1478a25beec6f0bd62c7549058b7ac53e924040d4f301f02fd1 - languageName: node - linkType: hard - -"has-property-descriptors@npm:^1.0.2": - version: 1.0.2 - resolution: "has-property-descriptors@npm:1.0.2" - dependencies: - es-define-property: "npm:^1.0.0" - checksum: 10c0/253c1f59e80bb476cf0dde8ff5284505d90c3bdb762983c3514d36414290475fe3fd6f574929d84de2a8eec00d35cf07cb6776205ff32efd7c50719125f00236 - languageName: node - linkType: hard - -"has-symbols@npm:^1.0.3, has-symbols@npm:^1.1.0": - version: 1.1.0 - resolution: "has-symbols@npm:1.1.0" - checksum: 10c0/dde0a734b17ae51e84b10986e651c664379018d10b91b6b0e9b293eddb32f0f069688c841fb40f19e9611546130153e0a2a48fd7f512891fb000ddfa36f5a20e - languageName: node - linkType: hard - -"has-tostringtag@npm:^1.0.2": - version: 1.0.2 - resolution: "has-tostringtag@npm:1.0.2" - dependencies: - has-symbols: "npm:^1.0.3" - checksum: 10c0/a8b166462192bafe3d9b6e420a1d581d93dd867adb61be223a17a8d6dad147aa77a8be32c961bb2f27b3ef893cae8d36f564ab651f5e9b7938ae86f74027c48c - languageName: node - linkType: hard - -"hasown@npm:^2.0.2": - version: 2.0.2 - resolution: "hasown@npm:2.0.2" - dependencies: - function-bind: "npm:^1.1.2" - checksum: 10c0/3769d434703b8ac66b209a4cca0737519925bbdb61dd887f93a16372b14694c63ff4e797686d87c90f08168e81082248b9b028bad60d4da9e0d1148766f56eb9 - languageName: node - linkType: hard - -"html-escaper@npm:^2.0.0": - version: 2.0.2 - resolution: "html-escaper@npm:2.0.2" - checksum: 10c0/208e8a12de1a6569edbb14544f4567e6ce8ecc30b9394fcaa4e7bb1e60c12a7c9a1ed27e31290817157e8626f3a4f29e76c8747030822eb84a6abb15c255f0a0 - languageName: node - linkType: hard - -"htmlparser2@npm:^10.0.0": - version: 10.0.0 - resolution: "htmlparser2@npm:10.0.0" - dependencies: - domelementtype: "npm:^2.3.0" - domhandler: "npm:^5.0.3" - domutils: "npm:^3.2.1" - entities: "npm:^6.0.0" - checksum: 10c0/47cfa37e529c86a7ba9a1e0e6f951ad26ef8ca5af898ab6e8916fa02c0264c1453b4a65f28b7b8a7f9d0d29b5a70abead8203bf8b3f07bc69407e85e7d9a68e4 - languageName: node - linkType: hard - -"http-cache-semantics@npm:^4.1.1": - version: 4.2.0 - resolution: "http-cache-semantics@npm:4.2.0" - checksum: 10c0/45b66a945cf13ec2d1f29432277201313babf4a01d9e52f44b31ca923434083afeca03f18417f599c9ab3d0e7b618ceb21257542338b57c54b710463b4a53e37 - languageName: node - linkType: hard - -"http-cookie-agent@npm:^6.0.8": - version: 6.0.8 - resolution: "http-cookie-agent@npm:6.0.8" - dependencies: - agent-base: "npm:^7.1.3" - peerDependencies: - tough-cookie: ^4.0.0 || ^5.0.0 - undici: ^5.11.0 || ^6.0.0 - peerDependenciesMeta: - undici: - optional: true - checksum: 10c0/d1931431982f5946e10cd67cd035d510119acce046db9588e38c3d37385b40b5fa4a757fd97d79e457f2a91546d3b95743511ac99e9fc6b825d1063299b04b58 - languageName: node - linkType: hard - -"http-cookie-agent@npm:^7.0.1": - version: 7.0.1 - resolution: "http-cookie-agent@npm:7.0.1" - dependencies: - agent-base: "npm:^7.1.3" - peerDependencies: - tough-cookie: ^4.0.0 || ^5.0.0 - undici: ^7.0.0 - peerDependenciesMeta: - undici: - optional: true - checksum: 10c0/d89b44247a00a38891ffd8398b5ba57e891cd8ac638eff57d2b11cbff525dad207f3058dbfff9d19678e646c891252195f90ef8f61e7ddcfbfae6a1b97b2be5e - languageName: node - linkType: hard - -"http-proxy-agent@npm:^7.0.0, http-proxy-agent@npm:^7.0.1": - version: 7.0.2 - resolution: "http-proxy-agent@npm:7.0.2" - dependencies: - agent-base: "npm:^7.1.0" - debug: "npm:^4.3.4" - checksum: 10c0/4207b06a4580fb85dd6dff521f0abf6db517489e70863dca1a0291daa7f2d3d2d6015a57bd702af068ea5cf9f1f6ff72314f5f5b4228d299c0904135d2aef921 - languageName: node - linkType: hard - -"https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.3, https-proxy-agent@npm:^7.0.6": - version: 7.0.6 - resolution: "https-proxy-agent@npm:7.0.6" - dependencies: - agent-base: "npm:^7.1.2" - debug: "npm:4" - checksum: 10c0/f729219bc735edb621fa30e6e84e60ee5d00802b8247aac0d7b79b0bd6d4b3294737a337b93b86a0bd9e68099d031858a39260c976dc14cdbba238ba1f8779ac - languageName: node - linkType: hard - -"human-signals@npm:^2.1.0": - version: 2.1.0 - resolution: "human-signals@npm:2.1.0" - checksum: 10c0/695edb3edfcfe9c8b52a76926cd31b36978782062c0ed9b1192b36bebc75c4c87c82e178dfcb0ed0fc27ca59d434198aac0bd0be18f5781ded775604db22304a - languageName: node - linkType: hard - -"husky@npm:^9.1.7": - version: 9.1.7 - resolution: "husky@npm:9.1.7" - bin: - husky: bin.js - checksum: 10c0/35bb110a71086c48906aa7cd3ed4913fb913823715359d65e32e0b964cb1e255593b0ae8014a5005c66a68e6fa66c38dcfa8056dbbdfb8b0187c0ffe7ee3a58f - languageName: node - linkType: hard - -"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2, iconv-lite@npm:^0.6.3": - version: 0.6.3 - resolution: "iconv-lite@npm:0.6.3" - dependencies: - safer-buffer: "npm:>= 2.1.2 < 3.0.0" - checksum: 10c0/98102bc66b33fcf5ac044099d1257ba0b7ad5e3ccd3221f34dd508ab4070edff183276221684e1e0555b145fce0850c9f7d2b60a9fcac50fbb4ea0d6e845a3b1 - languageName: node - linkType: hard - -"iconv-lite@npm:^0.4.24, iconv-lite@npm:^0.4.4": - version: 0.4.24 - resolution: "iconv-lite@npm:0.4.24" - dependencies: - safer-buffer: "npm:>= 2.1.2 < 3" - checksum: 10c0/c6886a24cc00f2a059767440ec1bc00d334a89f250db8e0f7feb4961c8727118457e27c495ba94d082e51d3baca378726cd110aaf7ded8b9bbfd6a44760cf1d4 - languageName: node - linkType: hard - -"ignore@npm:^5.2.0": - version: 5.3.2 - resolution: "ignore@npm:5.3.2" - checksum: 10c0/f9f652c957983634ded1e7f02da3b559a0d4cc210fca3792cb67f1b153623c9c42efdc1c4121af171e295444459fc4a9201101fb041b1104a3c000bccb188337 - languageName: node - linkType: hard - -"ignore@npm:^7.0.0": - version: 7.0.5 - resolution: "ignore@npm:7.0.5" - checksum: 10c0/ae00db89fe873064a093b8999fe4cc284b13ef2a178636211842cceb650b9c3e390d3339191acb145d81ed5379d2074840cf0c33a20bdbd6f32821f79eb4ad5d - languageName: node - linkType: hard - -"immediate@npm:~3.0.5": - version: 3.0.6 - resolution: "immediate@npm:3.0.6" - checksum: 10c0/f8ba7ede69bee9260241ad078d2d535848745ff5f6995c7c7cb41cfdc9ccc213f66e10fa5afb881f90298b24a3f7344b637b592beb4f54e582770cdce3f1f039 - languageName: node - linkType: hard - -"import-fresh@npm:^3.2.1": - version: 3.3.0 - resolution: "import-fresh@npm:3.3.0" - dependencies: - parent-module: "npm:^1.0.0" - resolve-from: "npm:^4.0.0" - checksum: 10c0/7f882953aa6b740d1f0e384d0547158bc86efbf2eea0f1483b8900a6f65c5a5123c2cf09b0d542cc419d0b98a759ecaeb394237e97ea427f2da221dc3cd80cc3 - languageName: node - linkType: hard - -"import-local@npm:^3.2.0": - version: 3.2.0 - resolution: "import-local@npm:3.2.0" - dependencies: - pkg-dir: "npm:^4.2.0" - resolve-cwd: "npm:^3.0.0" - bin: - import-local-fixture: fixtures/cli.js - checksum: 10c0/94cd6367a672b7e0cb026970c85b76902d2710a64896fa6de93bd5c571dd03b228c5759308959de205083e3b1c61e799f019c9e36ee8e9c523b993e1057f0433 - languageName: node - linkType: hard - -"imurmurhash@npm:^0.1.4": - version: 0.1.4 - resolution: "imurmurhash@npm:0.1.4" - checksum: 10c0/8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6 - languageName: node - linkType: hard - -"inflight@npm:^1.0.4": - version: 1.0.6 - resolution: "inflight@npm:1.0.6" - dependencies: - once: "npm:^1.3.0" - wrappy: "npm:1" - checksum: 10c0/7faca22584600a9dc5b9fca2cd5feb7135ac8c935449837b315676b4c90aa4f391ec4f42240178244b5a34e8bede1948627fda392ca3191522fc46b34e985ab2 - languageName: node - linkType: hard - -"inherits@npm:2, inherits@npm:^2.0.3": - version: 2.0.4 - resolution: "inherits@npm:2.0.4" - checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 - languageName: node - linkType: hard - -"ini@npm:^1.3.5, ini@npm:~1.3.0": - version: 1.3.8 - resolution: "ini@npm:1.3.8" - checksum: 10c0/ec93838d2328b619532e4f1ff05df7909760b6f66d9c9e2ded11e5c1897d6f2f9980c54dd638f88654b00919ce31e827040631eab0a3969e4d1abefa0719516a - languageName: node - linkType: hard - -"inquirer@npm:^12.6.3": - version: 12.6.3 - resolution: "inquirer@npm:12.6.3" - dependencies: - "@inquirer/core": "npm:^10.1.13" - "@inquirer/prompts": "npm:^7.5.3" - "@inquirer/type": "npm:^3.0.7" - ansi-escapes: "npm:^4.3.2" - mute-stream: "npm:^2.0.0" - run-async: "npm:^3.0.0" - rxjs: "npm:^7.8.2" - peerDependencies: - "@types/node": ">=18" - peerDependenciesMeta: - "@types/node": - optional: true - checksum: 10c0/926cd50b3adeac55425b5609ce5b1d08b6bd2db103c365ecf64f8b2e8311ab490f43f375128368e6dd26b4b4eaac6e9f4a49e83147815be66d4a3e2a51df5fbb - languageName: node - linkType: hard - -"ip-address@npm:^9.0.5": - version: 9.0.5 - resolution: "ip-address@npm:9.0.5" - dependencies: - jsbn: "npm:1.1.0" - sprintf-js: "npm:^1.1.3" - checksum: 10c0/331cd07fafcb3b24100613e4b53e1a2b4feab11e671e655d46dc09ee233da5011284d09ca40c4ecbdfe1d0004f462958675c224a804259f2f78d2465a87824bc - languageName: node - linkType: hard - -"is-arguments@npm:^1.0.4": - version: 1.2.0 - resolution: "is-arguments@npm:1.2.0" - dependencies: - call-bound: "npm:^1.0.2" - has-tostringtag: "npm:^1.0.2" - checksum: 10c0/6377344b31e9fcb707c6751ee89b11f132f32338e6a782ec2eac9393b0cbd32235dad93052998cda778ee058754860738341d8114910d50ada5615912bb929fc - languageName: node - linkType: hard - -"is-arrayish@npm:^0.2.1": - version: 0.2.1 - resolution: "is-arrayish@npm:0.2.1" - checksum: 10c0/e7fb686a739068bb70f860b39b67afc62acc62e36bb61c5f965768abce1873b379c563e61dd2adad96ebb7edf6651111b385e490cf508378959b0ed4cac4e729 - languageName: node - linkType: hard - -"is-arrayish@npm:^0.3.1": - version: 0.3.2 - resolution: "is-arrayish@npm:0.3.2" - checksum: 10c0/f59b43dc1d129edb6f0e282595e56477f98c40278a2acdc8b0a5c57097c9eff8fe55470493df5775478cf32a4dc8eaf6d3a749f07ceee5bc263a78b2434f6a54 - languageName: node - linkType: hard - -"is-binary-path@npm:~2.1.0": - version: 2.1.0 - resolution: "is-binary-path@npm:2.1.0" - dependencies: - binary-extensions: "npm:^2.0.0" - checksum: 10c0/a16eaee59ae2b315ba36fad5c5dcaf8e49c3e27318f8ab8fa3cdb8772bf559c8d1ba750a589c2ccb096113bb64497084361a25960899cb6172a6925ab6123d38 - languageName: node - linkType: hard - -"is-blob@npm:^2.1.0": - version: 2.1.0 - resolution: "is-blob@npm:2.1.0" - checksum: 10c0/324eb4dc16a359acf14c56c0f65a031c9ddb6ec8ca651894783abe3f3252688facd9e02edeb756eddca0c8ddb5b1efd0af97ea7d3c7b8567fc29fa75f9b1a159 - languageName: node - linkType: hard - -"is-buffer@npm:^2.0.5": - version: 2.0.5 - resolution: "is-buffer@npm:2.0.5" - checksum: 10c0/e603f6fced83cf94c53399cff3bda1a9f08e391b872b64a73793b0928be3e5f047f2bcece230edb7632eaea2acdbfcb56c23b33d8a20c820023b230f1485679a - languageName: node - linkType: hard - -"is-callable@npm:^1.2.7": - version: 1.2.7 - resolution: "is-callable@npm:1.2.7" - checksum: 10c0/ceebaeb9d92e8adee604076971dd6000d38d6afc40bb843ea8e45c5579b57671c3f3b50d7f04869618242c6cee08d1b67806a8cb8edaaaf7c0748b3720d6066f - languageName: node - linkType: hard - -"is-core-module@npm:^2.16.0": - version: 2.16.1 - resolution: "is-core-module@npm:2.16.1" - dependencies: - hasown: "npm:^2.0.2" - checksum: 10c0/898443c14780a577e807618aaae2b6f745c8538eca5c7bc11388a3f2dc6de82b9902bcc7eb74f07be672b11bbe82dd6a6edded44a00cb3d8f933d0459905eedd - languageName: node - linkType: hard - -"is-docker@npm:^2.0.0": - version: 2.2.1 - resolution: "is-docker@npm:2.2.1" - bin: - is-docker: cli.js - checksum: 10c0/e828365958d155f90c409cdbe958f64051d99e8aedc2c8c4cd7c89dcf35329daed42f7b99346f7828df013e27deb8f721cf9408ba878c76eb9e8290235fbcdcc - languageName: node - linkType: hard - -"is-extglob@npm:^2.1.1": - version: 2.1.1 - resolution: "is-extglob@npm:2.1.1" - checksum: 10c0/5487da35691fbc339700bbb2730430b07777a3c21b9ebaecb3072512dfd7b4ba78ac2381a87e8d78d20ea08affb3f1971b4af629173a6bf435ff8a4c47747912 - languageName: node - linkType: hard - -"is-fullwidth-code-point@npm:^3.0.0": - version: 3.0.0 - resolution: "is-fullwidth-code-point@npm:3.0.0" - checksum: 10c0/bb11d825e049f38e04c06373a8d72782eee0205bda9d908cc550ccb3c59b99d750ff9537982e01733c1c94a58e35400661f57042158ff5e8f3e90cf936daf0fc - languageName: node - linkType: hard - -"is-generator-fn@npm:^2.1.0": - version: 2.1.0 - resolution: "is-generator-fn@npm:2.1.0" - checksum: 10c0/2957cab387997a466cd0bf5c1b6047bd21ecb32bdcfd8996b15747aa01002c1c88731802f1b3d34ac99f4f6874b626418bd118658cf39380fe5fff32a3af9c4d - languageName: node - linkType: hard - -"is-generator-function@npm:^1.0.7": - version: 1.1.0 - resolution: "is-generator-function@npm:1.1.0" - dependencies: - call-bound: "npm:^1.0.3" - get-proto: "npm:^1.0.0" - has-tostringtag: "npm:^1.0.2" - safe-regex-test: "npm:^1.1.0" - checksum: 10c0/fdfa96c8087bf36fc4cd514b474ba2ff404219a4dd4cfa6cf5426404a1eed259bdcdb98f082a71029a48d01f27733e3436ecc6690129a7ec09cb0434bee03a2a - languageName: node - linkType: hard - -"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1": - version: 4.0.3 - resolution: "is-glob@npm:4.0.3" - dependencies: - is-extglob: "npm:^2.1.1" - checksum: 10c0/17fb4014e22be3bbecea9b2e3a76e9e34ff645466be702f1693e8f1ee1adac84710d0be0bd9f967d6354036fd51ab7c2741d954d6e91dae6bb69714de92c197a - languageName: node - linkType: hard - -"is-number@npm:^7.0.0": - version: 7.0.0 - resolution: "is-number@npm:7.0.0" - checksum: 10c0/b4686d0d3053146095ccd45346461bc8e53b80aeb7671cc52a4de02dbbf7dc0d1d2a986e2fe4ae206984b4d34ef37e8b795ebc4f4295c978373e6575e295d811 - languageName: node - linkType: hard - -"is-port-reachable@npm:4.0.0": - version: 4.0.0 - resolution: "is-port-reachable@npm:4.0.0" - checksum: 10c0/f0fddd9b5c082f7c32356faab38c3c6eab5ea5b54491184f5688f3189d482017d2142c648927ee5964299e4a62da83d41ee52a1d73bf1f700325c370c9ed0cef - languageName: node - linkType: hard - -"is-regex@npm:^1.2.1": - version: 1.2.1 - resolution: "is-regex@npm:1.2.1" - dependencies: - call-bound: "npm:^1.0.2" - gopd: "npm:^1.2.0" - has-tostringtag: "npm:^1.0.2" - hasown: "npm:^2.0.2" - checksum: 10c0/1d3715d2b7889932349241680032e85d0b492cfcb045acb75ffc2c3085e8d561184f1f7e84b6f8321935b4aea39bc9c6ba74ed595b57ce4881a51dfdbc214e04 - languageName: node - linkType: hard - -"is-stream@npm:^2.0.0": - version: 2.0.1 - resolution: "is-stream@npm:2.0.1" - checksum: 10c0/7c284241313fc6efc329b8d7f08e16c0efeb6baab1b4cd0ba579eb78e5af1aa5da11e68559896a2067cd6c526bd29241dda4eb1225e627d5aa1a89a76d4635a5 - languageName: node - linkType: hard - -"is-typed-array@npm:^1.1.3": - version: 1.1.15 - resolution: "is-typed-array@npm:1.1.15" - dependencies: - which-typed-array: "npm:^1.1.16" - checksum: 10c0/415511da3669e36e002820584e264997ffe277ff136643a3126cc949197e6ca3334d0f12d084e83b1994af2e9c8141275c741cf2b7da5a2ff62dd0cac26f76c4 - languageName: node - linkType: hard - -"is-wsl@npm:^2.2.0": - version: 2.2.0 - resolution: "is-wsl@npm:2.2.0" - dependencies: - is-docker: "npm:^2.0.0" - checksum: 10c0/a6fa2d370d21be487c0165c7a440d567274fbba1a817f2f0bfa41cc5e3af25041d84267baa22df66696956038a43973e72fca117918c91431920bdef490fa25e - languageName: node - linkType: hard - -"isexe@npm:^2.0.0": - version: 2.0.0 - resolution: "isexe@npm:2.0.0" - checksum: 10c0/228cfa503fadc2c31596ab06ed6aa82c9976eec2bfd83397e7eaf06d0ccf42cd1dfd6743bf9aeb01aebd4156d009994c5f76ea898d2832c1fe342da923ca457d - languageName: node - linkType: hard - -"isexe@npm:^3.1.1": - version: 3.1.1 - resolution: "isexe@npm:3.1.1" - checksum: 10c0/9ec257654093443eb0a528a9c8cbba9c0ca7616ccb40abd6dde7202734d96bb86e4ac0d764f0f8cd965856aacbff2f4ce23e730dc19dfb41e3b0d865ca6fdcc7 - languageName: node - linkType: hard - -"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": - version: 3.2.2 - resolution: "istanbul-lib-coverage@npm:3.2.2" - checksum: 10c0/6c7ff2106769e5f592ded1fb418f9f73b4411fd5a084387a5410538332b6567cd1763ff6b6cadca9b9eb2c443cce2f7ea7d7f1b8d315f9ce58539793b1e0922b - languageName: node - linkType: hard - -"istanbul-lib-instrument@npm:^6.0.0, istanbul-lib-instrument@npm:^6.0.2": - version: 6.0.3 - resolution: "istanbul-lib-instrument@npm:6.0.3" - dependencies: - "@babel/core": "npm:^7.23.9" - "@babel/parser": "npm:^7.23.9" - "@istanbuljs/schema": "npm:^0.1.3" - istanbul-lib-coverage: "npm:^3.2.0" - semver: "npm:^7.5.4" - checksum: 10c0/a1894e060dd2a3b9f046ffdc87b44c00a35516f5e6b7baf4910369acca79e506fc5323a816f811ae23d82334b38e3ddeb8b3b331bd2c860540793b59a8689128 - languageName: node - linkType: hard - -"istanbul-lib-report@npm:^3.0.0": - version: 3.0.1 - resolution: "istanbul-lib-report@npm:3.0.1" - dependencies: - istanbul-lib-coverage: "npm:^3.0.0" - make-dir: "npm:^4.0.0" - supports-color: "npm:^7.1.0" - checksum: 10c0/84323afb14392de8b6a5714bd7e9af845cfbd56cfe71ed276cda2f5f1201aea673c7111901227ee33e68e4364e288d73861eb2ed48f6679d1e69a43b6d9b3ba7 - languageName: node - linkType: hard - -"istanbul-lib-source-maps@npm:^5.0.0": - version: 5.0.6 - resolution: "istanbul-lib-source-maps@npm:5.0.6" - dependencies: - "@jridgewell/trace-mapping": "npm:^0.3.23" - debug: "npm:^4.1.1" - istanbul-lib-coverage: "npm:^3.0.0" - checksum: 10c0/ffe75d70b303a3621ee4671554f306e0831b16f39ab7f4ab52e54d356a5d33e534d97563e318f1333a6aae1d42f91ec49c76b6cd3f3fb378addcb5c81da0255f - languageName: node - linkType: hard - -"istanbul-reports@npm:^3.1.3": - version: 3.1.7 - resolution: "istanbul-reports@npm:3.1.7" - dependencies: - html-escaper: "npm:^2.0.0" - istanbul-lib-report: "npm:^3.0.0" - checksum: 10c0/a379fadf9cf8dc5dfe25568115721d4a7eb82fbd50b005a6672aff9c6989b20cc9312d7865814e0859cd8df58cbf664482e1d3604be0afde1f7fc3ccc1394a51 - languageName: node - linkType: hard - -"jackspeak@npm:^3.1.2": - version: 3.4.3 - resolution: "jackspeak@npm:3.4.3" - dependencies: - "@isaacs/cliui": "npm:^8.0.2" - "@pkgjs/parseargs": "npm:^0.11.0" - dependenciesMeta: - "@pkgjs/parseargs": - optional: true - checksum: 10c0/6acc10d139eaefdbe04d2f679e6191b3abf073f111edf10b1de5302c97ec93fffeb2fdd8681ed17f16268aa9dd4f8c588ed9d1d3bffbbfa6e8bf897cbb3149b9 - languageName: node - linkType: hard - -"jackspeak@npm:^4.1.1": - version: 4.1.1 - resolution: "jackspeak@npm:4.1.1" - dependencies: - "@isaacs/cliui": "npm:^8.0.2" - checksum: 10c0/84ec4f8e21d6514db24737d9caf65361511f75e5e424980eebca4199f400874f45e562ac20fa8aeb1dd20ca2f3f81f0788b6e9c3e64d216a5794fd6f30e0e042 - languageName: node - linkType: hard - -"jest-changed-files@npm:30.0.2": - version: 30.0.2 - resolution: "jest-changed-files@npm:30.0.2" - dependencies: - execa: "npm:^5.1.1" - jest-util: "npm:30.0.2" - p-limit: "npm:^3.1.0" - checksum: 10c0/794c9e47c460974f2303631d9ee44845d03f4ccd5240649a5f736aa94af78fa5931022324ab302c577dad6adb442ed17140dee9b9985bbfa0d43cad3048a7350 - languageName: node - linkType: hard - -"jest-circus@npm:30.0.3": - version: 30.0.3 - resolution: "jest-circus@npm:30.0.3" - dependencies: - "@jest/environment": "npm:30.0.2" - "@jest/expect": "npm:30.0.3" - "@jest/test-result": "npm:30.0.2" - "@jest/types": "npm:30.0.1" - "@types/node": "npm:*" - chalk: "npm:^4.1.2" - co: "npm:^4.6.0" - dedent: "npm:^1.6.0" - is-generator-fn: "npm:^2.1.0" - jest-each: "npm:30.0.2" - jest-matcher-utils: "npm:30.0.3" - jest-message-util: "npm:30.0.2" - jest-runtime: "npm:30.0.3" - jest-snapshot: "npm:30.0.3" - jest-util: "npm:30.0.2" - p-limit: "npm:^3.1.0" - pretty-format: "npm:30.0.2" - pure-rand: "npm:^7.0.0" - slash: "npm:^3.0.0" - stack-utils: "npm:^2.0.6" - checksum: 10c0/cb0838cc9f08984614d92c5fe857ea95f1bdff6de4a510a1b228cc9c0513d18bb2db89dcaf55624e754b11d77fb77bdba1fc56c6af34c1534102c498ce058399 - languageName: node - linkType: hard - -"jest-cli@npm:30.0.3": - version: 30.0.3 - resolution: "jest-cli@npm:30.0.3" - dependencies: - "@jest/core": "npm:30.0.3" - "@jest/test-result": "npm:30.0.2" - "@jest/types": "npm:30.0.1" - chalk: "npm:^4.1.2" - exit-x: "npm:^0.2.2" - import-local: "npm:^3.2.0" - jest-config: "npm:30.0.3" - jest-util: "npm:30.0.2" - jest-validate: "npm:30.0.2" - yargs: "npm:^17.7.2" - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - bin: - jest: ./bin/jest.js - checksum: 10c0/17925e9e885b00069e06672c221fbe073d1bff1d869f228bcba08ac23bf8d2c258c7211ce4d0e8408ca7d0edf0afb8ae4098e3d0f5da253eed22d385b135ca90 - languageName: node - linkType: hard - -"jest-config@npm:30.0.3": - version: 30.0.3 - resolution: "jest-config@npm:30.0.3" - dependencies: - "@babel/core": "npm:^7.27.4" - "@jest/get-type": "npm:30.0.1" - "@jest/pattern": "npm:30.0.1" - "@jest/test-sequencer": "npm:30.0.2" - "@jest/types": "npm:30.0.1" - babel-jest: "npm:30.0.2" - chalk: "npm:^4.1.2" - ci-info: "npm:^4.2.0" - deepmerge: "npm:^4.3.1" - glob: "npm:^10.3.10" - graceful-fs: "npm:^4.2.11" - jest-circus: "npm:30.0.3" - jest-docblock: "npm:30.0.1" - jest-environment-node: "npm:30.0.2" - jest-regex-util: "npm:30.0.1" - jest-resolve: "npm:30.0.2" - jest-runner: "npm:30.0.3" - jest-util: "npm:30.0.2" - jest-validate: "npm:30.0.2" - micromatch: "npm:^4.0.8" - parse-json: "npm:^5.2.0" - pretty-format: "npm:30.0.2" - slash: "npm:^3.0.0" - strip-json-comments: "npm:^3.1.1" - peerDependencies: - "@types/node": "*" - esbuild-register: ">=3.4.0" - ts-node: ">=9.0.0" - peerDependenciesMeta: - "@types/node": - optional: true - esbuild-register: - optional: true - ts-node: - optional: true - checksum: 10c0/bcde9e0e715bbc12dd36a135d6e081566291b0726ed7b3ac9a1e2ee2ade7c9bcc25d312ef8a649b72b9c99e2ad6661eb843eeb919ba6206f2ec2acccdd1e57d2 - languageName: node - linkType: hard - -"jest-diff@npm:30.0.2": - version: 30.0.2 - resolution: "jest-diff@npm:30.0.2" - dependencies: - "@jest/diff-sequences": "npm:30.0.1" - "@jest/get-type": "npm:30.0.1" - chalk: "npm:^4.1.2" - pretty-format: "npm:30.0.2" - checksum: 10c0/dada50ab8d4c1c907bb4728963d43d812cc391a114f0361356b0e51dcd9461936f0a6b27a3429cb3adb9164eaa78182667836440298ddab39319a9350b445a43 - languageName: node - linkType: hard - -"jest-diff@npm:30.0.3": - version: 30.0.3 - resolution: "jest-diff@npm:30.0.3" - dependencies: - "@jest/diff-sequences": "npm:30.0.1" - "@jest/get-type": "npm:30.0.1" - chalk: "npm:^4.1.2" - pretty-format: "npm:30.0.2" - checksum: 10c0/f6aaed30fc99bdca4b8b4505b283ffc78b780aa1bf33670dfbfe439e124721e7f6198c03217f7ed17a22c7d2ca79363afd6a4245643596fa21ae082b6b4ed4f5 - languageName: node - linkType: hard - -"jest-docblock@npm:30.0.1": - version: 30.0.1 - resolution: "jest-docblock@npm:30.0.1" - dependencies: - detect-newline: "npm:^3.1.0" - checksum: 10c0/f9bad2651db8afa029867ea7a40f422c9d73c67657360297371846a314a40c8786424be00483261df9137499f52c2af28cd458fbd15a7bf7fac8775b4bcd6ee1 - languageName: node - linkType: hard - -"jest-each@npm:30.0.2": - version: 30.0.2 - resolution: "jest-each@npm:30.0.2" - dependencies: - "@jest/get-type": "npm:30.0.1" - "@jest/types": "npm:30.0.1" - chalk: "npm:^4.1.2" - jest-util: "npm:30.0.2" - pretty-format: "npm:30.0.2" - checksum: 10c0/6fff0a470d08ba3f0149c58266b7e938e3e183398f99065fe937290f1297ca254635f0f4bca6196514f756fac0a9759144b1c7f67bef97cc0b7fa0b96304df9e - languageName: node - linkType: hard - -"jest-environment-node@npm:30.0.2": - version: 30.0.2 - resolution: "jest-environment-node@npm:30.0.2" - dependencies: - "@jest/environment": "npm:30.0.2" - "@jest/fake-timers": "npm:30.0.2" - "@jest/types": "npm:30.0.1" - "@types/node": "npm:*" - jest-mock: "npm:30.0.2" - jest-util: "npm:30.0.2" - jest-validate: "npm:30.0.2" - checksum: 10c0/e58515d26f13704c3be6281d029c4fa0902172d2a55751205badf0153630520c4e651f7923577e1ab0dfbb64c4fedb1e4b78622b53b3a8d8e0515c1923f3adc3 - languageName: node - linkType: hard - -"jest-haste-map@npm:30.0.2": - version: 30.0.2 - resolution: "jest-haste-map@npm:30.0.2" - dependencies: - "@jest/types": "npm:30.0.1" - "@types/node": "npm:*" - anymatch: "npm:^3.1.3" - fb-watchman: "npm:^2.0.2" - fsevents: "npm:^2.3.3" - graceful-fs: "npm:^4.2.11" - jest-regex-util: "npm:30.0.1" - jest-util: "npm:30.0.2" - jest-worker: "npm:30.0.2" - micromatch: "npm:^4.0.8" - walker: "npm:^1.0.8" - dependenciesMeta: - fsevents: - optional: true - checksum: 10c0/6427b6976beb3fd33cae9a516e24f409d0cc0be2afa12a62e95671001a0d0d61662e8b2185027639b2036fe3e3b055e9d9b4dfd2063e787cf2a5d2140da0b80a - languageName: node - linkType: hard - -"jest-leak-detector@npm:30.0.2": - version: 30.0.2 - resolution: "jest-leak-detector@npm:30.0.2" - dependencies: - "@jest/get-type": "npm:30.0.1" - pretty-format: "npm:30.0.2" - checksum: 10c0/1df28475c40b41024adc6e18af0d3dc8d8d318fdbbf5c3560321fea0af2e0784c57f788b5b152efd83274ab6ea8dc3b36662060a83a2a555ffd8cdf7d628ee76 - languageName: node - linkType: hard - -"jest-matcher-utils@npm:30.0.2": - version: 30.0.2 - resolution: "jest-matcher-utils@npm:30.0.2" - dependencies: - "@jest/get-type": "npm:30.0.1" - chalk: "npm:^4.1.2" - jest-diff: "npm:30.0.2" - pretty-format: "npm:30.0.2" - checksum: 10c0/6e862dfe259c30f066fe800cc302cad8cdb4ff92dad73538ce099960ecffd5ba119282af933521765ce24fb3d99b5338d7fa64261df08f9e8505350e9d112424 - languageName: node - linkType: hard - -"jest-matcher-utils@npm:30.0.3": - version: 30.0.3 - resolution: "jest-matcher-utils@npm:30.0.3" - dependencies: - "@jest/get-type": "npm:30.0.1" - chalk: "npm:^4.1.2" - jest-diff: "npm:30.0.3" - pretty-format: "npm:30.0.2" - checksum: 10c0/4d354f6d8d3992228ba5f0ecc728ec0c46f3693805927253d67e461e754deadc1e1b48ae80918e3f029c22da4abed9aaadb5049da1a1697f6714b0f6076eeafa - languageName: node - linkType: hard - -"jest-message-util@npm:30.0.2": - version: 30.0.2 - resolution: "jest-message-util@npm:30.0.2" - dependencies: - "@babel/code-frame": "npm:^7.27.1" - "@jest/types": "npm:30.0.1" - "@types/stack-utils": "npm:^2.0.3" - chalk: "npm:^4.1.2" - graceful-fs: "npm:^4.2.11" - micromatch: "npm:^4.0.8" - pretty-format: "npm:30.0.2" - slash: "npm:^3.0.0" - stack-utils: "npm:^2.0.6" - checksum: 10c0/c010d5b7d86e735e2fb4c4a220f57004349f488f5d4663240a7e9f2694d01b5228136540d55036777fde4227b5e0b56f08885b7f69395b295cab878357b1aeb1 - languageName: node - linkType: hard - -"jest-mock@npm:30.0.2": - version: 30.0.2 - resolution: "jest-mock@npm:30.0.2" - dependencies: - "@jest/types": "npm:30.0.1" - "@types/node": "npm:*" - jest-util: "npm:30.0.2" - checksum: 10c0/7728997c1d654475b88e18b7ba33a2a1b9f89ce33a9082bf2d14dcc3e831f372f80c762e481777886a3a04b4489ea5390ecdeb21c4def57fba5b2c77086a3959 - languageName: node - linkType: hard - -"jest-offline@npm:^1.0.1": - version: 1.0.1 - resolution: "jest-offline@npm:1.0.1" - dependencies: - mitm: "npm:^1.3.2" - checksum: 10c0/6d1e9ba6d66f4232516e94815ce44a0da71c73e3c53bb83228276012add583371811ad40d73304fdade8f6e68ebebac129ca1eeeb9568bff5798d4c1448f807a - languageName: node - linkType: hard - -"jest-pnp-resolver@npm:^1.2.3": - version: 1.2.3 - resolution: "jest-pnp-resolver@npm:1.2.3" - peerDependencies: - jest-resolve: "*" - peerDependenciesMeta: - jest-resolve: - optional: true - checksum: 10c0/86eec0c78449a2de733a6d3e316d49461af6a858070e113c97f75fb742a48c2396ea94150cbca44159ffd4a959f743a47a8b37a792ef6fdad2cf0a5cba973fac - languageName: node - linkType: hard - -"jest-regex-util@npm:30.0.1": - version: 30.0.1 - resolution: "jest-regex-util@npm:30.0.1" - checksum: 10c0/f30c70524ebde2d1012afe5ffa5691d5d00f7d5ba9e43d588f6460ac6fe96f9e620f2f9b36a02d0d3e7e77bc8efb8b3450ae3b80ac53c8be5099e01bf54f6728 - languageName: node - linkType: hard - -"jest-resolve-dependencies@npm:30.0.3": - version: 30.0.3 - resolution: "jest-resolve-dependencies@npm:30.0.3" - dependencies: - jest-regex-util: "npm:30.0.1" - jest-snapshot: "npm:30.0.3" - checksum: 10c0/5684e62f05d19c5ab97b2b2262075f056bd48745bf25501671d0b9a03f2a0548ab04370b9cec6e97207d57ead54d706a67ef3254729cacb6d6405ef381cdf511 - languageName: node - linkType: hard - -"jest-resolve@npm:30.0.2": - version: 30.0.2 - resolution: "jest-resolve@npm:30.0.2" - dependencies: - chalk: "npm:^4.1.2" - graceful-fs: "npm:^4.2.11" - jest-haste-map: "npm:30.0.2" - jest-pnp-resolver: "npm:^1.2.3" - jest-util: "npm:30.0.2" - jest-validate: "npm:30.0.2" - slash: "npm:^3.0.0" - unrs-resolver: "npm:^1.7.11" - checksum: 10c0/33ae69455b1206a926bb6f7dd46cd4b6cbf5e095387078873a05dfb693bef419b93897e052ee68026b31b5e5f537fdcfce42f2d31af0ce7e64a8179ed7882b51 - languageName: node - linkType: hard - -"jest-runner@npm:30.0.3": - version: 30.0.3 - resolution: "jest-runner@npm:30.0.3" - dependencies: - "@jest/console": "npm:30.0.2" - "@jest/environment": "npm:30.0.2" - "@jest/test-result": "npm:30.0.2" - "@jest/transform": "npm:30.0.2" - "@jest/types": "npm:30.0.1" - "@types/node": "npm:*" - chalk: "npm:^4.1.2" - emittery: "npm:^0.13.1" - exit-x: "npm:^0.2.2" - graceful-fs: "npm:^4.2.11" - jest-docblock: "npm:30.0.1" - jest-environment-node: "npm:30.0.2" - jest-haste-map: "npm:30.0.2" - jest-leak-detector: "npm:30.0.2" - jest-message-util: "npm:30.0.2" - jest-resolve: "npm:30.0.2" - jest-runtime: "npm:30.0.3" - jest-util: "npm:30.0.2" - jest-watcher: "npm:30.0.2" - jest-worker: "npm:30.0.2" - p-limit: "npm:^3.1.0" - source-map-support: "npm:0.5.13" - checksum: 10c0/d139ee4ed4f2d7aeefc8c496efc906960e938beadc22dce6167e7270db4e10260092eace6748a6efb7ee2a40e3bd3ee5d60cbefc2a1e3459826cfde69cdb9195 - languageName: node - linkType: hard - -"jest-runtime@npm:30.0.3": - version: 30.0.3 - resolution: "jest-runtime@npm:30.0.3" - dependencies: - "@jest/environment": "npm:30.0.2" - "@jest/fake-timers": "npm:30.0.2" - "@jest/globals": "npm:30.0.3" - "@jest/source-map": "npm:30.0.1" - "@jest/test-result": "npm:30.0.2" - "@jest/transform": "npm:30.0.2" - "@jest/types": "npm:30.0.1" - "@types/node": "npm:*" - chalk: "npm:^4.1.2" - cjs-module-lexer: "npm:^2.1.0" - collect-v8-coverage: "npm:^1.0.2" - glob: "npm:^10.3.10" - graceful-fs: "npm:^4.2.11" - jest-haste-map: "npm:30.0.2" - jest-message-util: "npm:30.0.2" - jest-mock: "npm:30.0.2" - jest-regex-util: "npm:30.0.1" - jest-resolve: "npm:30.0.2" - jest-snapshot: "npm:30.0.3" - jest-util: "npm:30.0.2" - slash: "npm:^3.0.0" - strip-bom: "npm:^4.0.0" - checksum: 10c0/01a184b80bf1ae2d6eca280daf37e355b983795e342406de461cf4d45c75ec48a635bf89c08d54fb73f851180e870ef82004fd1f6b335f0329dc07f3bd14a94d - languageName: node - linkType: hard - -"jest-snapshot@npm:30.0.3": - version: 30.0.3 - resolution: "jest-snapshot@npm:30.0.3" - dependencies: - "@babel/core": "npm:^7.27.4" - "@babel/generator": "npm:^7.27.5" - "@babel/plugin-syntax-jsx": "npm:^7.27.1" - "@babel/plugin-syntax-typescript": "npm:^7.27.1" - "@babel/types": "npm:^7.27.3" - "@jest/expect-utils": "npm:30.0.3" - "@jest/get-type": "npm:30.0.1" - "@jest/snapshot-utils": "npm:30.0.1" - "@jest/transform": "npm:30.0.2" - "@jest/types": "npm:30.0.1" - babel-preset-current-node-syntax: "npm:^1.1.0" - chalk: "npm:^4.1.2" - expect: "npm:30.0.3" - graceful-fs: "npm:^4.2.11" - jest-diff: "npm:30.0.3" - jest-matcher-utils: "npm:30.0.3" - jest-message-util: "npm:30.0.2" - jest-util: "npm:30.0.2" - pretty-format: "npm:30.0.2" - semver: "npm:^7.7.2" - synckit: "npm:^0.11.8" - checksum: 10c0/0af682495b79bc0e640edbb03ada06db073a0784d6a9c0bb11e592afa4d0dca63c63ab485f540e8d1bd7674456418906e194e7f0660cc20107423d4fe11b4d6e - languageName: node - linkType: hard - -"jest-util@npm:30.0.2": - version: 30.0.2 - resolution: "jest-util@npm:30.0.2" - dependencies: - "@jest/types": "npm:30.0.1" - "@types/node": "npm:*" - chalk: "npm:^4.1.2" - ci-info: "npm:^4.2.0" - graceful-fs: "npm:^4.2.11" - picomatch: "npm:^4.0.2" - checksum: 10c0/07de384790b8e5a5925fba5448fa1475790a5b52271fbf99958c18e468da1af940f8b45e330d87766576cf6c5d1f4f41ce51c976483a5079653d9fcdba8aac8e - languageName: node - linkType: hard - -"jest-validate@npm:30.0.2": - version: 30.0.2 - resolution: "jest-validate@npm:30.0.2" - dependencies: - "@jest/get-type": "npm:30.0.1" - "@jest/types": "npm:30.0.1" - camelcase: "npm:^6.3.0" - chalk: "npm:^4.1.2" - leven: "npm:^3.1.0" - pretty-format: "npm:30.0.2" - checksum: 10c0/9fd1b4f604851187655353eefe8db25db9638dd312d2e29d58868e626d78925edefe94fe2c8eb63305eefd41e5fe7f8aff334e2db9db5aaddeec866f9f6561d8 - languageName: node - linkType: hard - -"jest-watcher@npm:30.0.2": - version: 30.0.2 - resolution: "jest-watcher@npm:30.0.2" - dependencies: - "@jest/test-result": "npm:30.0.2" - "@jest/types": "npm:30.0.1" - "@types/node": "npm:*" - ansi-escapes: "npm:^4.3.2" - chalk: "npm:^4.1.2" - emittery: "npm:^0.13.1" - jest-util: "npm:30.0.2" - string-length: "npm:^4.0.2" - checksum: 10c0/7cb09da5feaa6c5558e5149406bde354c3e227ef692b5371efe4d13cf566d42a157c04a55f3a201d191afb7ebc49be84b1ed5a744f46497d9ecccc323d8963f5 - languageName: node - linkType: hard - -"jest-worker@npm:30.0.2": - version: 30.0.2 - resolution: "jest-worker@npm:30.0.2" - dependencies: - "@types/node": "npm:*" - "@ungap/structured-clone": "npm:^1.3.0" - jest-util: "npm:30.0.2" - merge-stream: "npm:^2.0.0" - supports-color: "npm:^8.1.1" - checksum: 10c0/d7d237e763a2f1aed4eba07f977490442a7bb085f7ab63163afa88776804c2644cc05a1e32da9d05a4b895ad22b2e939ef01a90ffb3024b53fc8c73b8ad1d3f1 - languageName: node - linkType: hard - -"jest@npm:^30.0.3": - version: 30.0.3 - resolution: "jest@npm:30.0.3" - dependencies: - "@jest/core": "npm:30.0.3" - "@jest/types": "npm:30.0.1" - import-local: "npm:^3.2.0" - jest-cli: "npm:30.0.3" - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - bin: - jest: ./bin/jest.js - checksum: 10c0/ae4fbee2756e03b6f99f612438e3b4e25789731599a4d4617ce5002d4c68f169f6223f6b21522fe65cd3d00519e0bb534ac6db6b2cdb7cd46a4ad3ded6542f38 - languageName: node - linkType: hard - -"js-git@npm:^0.7.8": - version: 0.7.8 - resolution: "js-git@npm:0.7.8" - dependencies: - bodec: "npm:^0.1.0" - culvert: "npm:^0.1.2" - git-sha1: "npm:^0.1.2" - pako: "npm:^0.2.5" - checksum: 10c0/703e6e06dce12fac727884a7643d7fd2098625e921a742cf61f783589f383bbbc42330f0c98924d85e1b16e43d5d014a99c4d1a5e41b76e7dd8a6a7a26fbca82 - languageName: node - linkType: hard - -"js-tokens@npm:^4.0.0": - version: 4.0.0 - resolution: "js-tokens@npm:4.0.0" - checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed - languageName: node - linkType: hard - -"js-yaml@npm:^3.13.1": - version: 3.14.1 - resolution: "js-yaml@npm:3.14.1" - dependencies: - argparse: "npm:^1.0.7" - esprima: "npm:^4.0.0" - bin: - js-yaml: bin/js-yaml.js - checksum: 10c0/6746baaaeac312c4db8e75fa22331d9a04cccb7792d126ed8ce6a0bbcfef0cedaddd0c5098fade53db067c09fe00aa1c957674b4765610a8b06a5a189e46433b - languageName: node - linkType: hard - -"js-yaml@npm:^4.1.0, js-yaml@npm:~4.1.0": - version: 4.1.0 - resolution: "js-yaml@npm:4.1.0" - dependencies: - argparse: "npm:^2.0.1" - bin: - js-yaml: bin/js-yaml.js - checksum: 10c0/184a24b4eaacfce40ad9074c64fd42ac83cf74d8c8cd137718d456ced75051229e5061b8633c3366b8aada17945a7a356b337828c19da92b51ae62126575018f - languageName: node - linkType: hard - -"jsbn@npm:1.1.0": - version: 1.1.0 - resolution: "jsbn@npm:1.1.0" - checksum: 10c0/4f907fb78d7b712e11dea8c165fe0921f81a657d3443dde75359ed52eb2b5d33ce6773d97985a089f09a65edd80b11cb75c767b57ba47391fee4c969f7215c96 - languageName: node - linkType: hard - -"jsesc@npm:^3.0.2": - version: 3.1.0 - resolution: "jsesc@npm:3.1.0" - bin: - jsesc: bin/jsesc - checksum: 10c0/531779df5ec94f47e462da26b4cbf05eb88a83d9f08aac2ba04206508fc598527a153d08bd462bae82fc78b3eaa1a908e1a4a79f886e9238641c4cdefaf118b1 - languageName: node - linkType: hard - -"json-buffer@npm:3.0.1": - version: 3.0.1 - resolution: "json-buffer@npm:3.0.1" - checksum: 10c0/0d1c91569d9588e7eef2b49b59851f297f3ab93c7b35c7c221e288099322be6b562767d11e4821da500f3219542b9afd2e54c5dc573107c1126ed1080f8e96d7 - languageName: node - linkType: hard - -"json-parse-better-errors@npm:^1.0.1": - version: 1.0.2 - resolution: "json-parse-better-errors@npm:1.0.2" - checksum: 10c0/2f1287a7c833e397c9ddd361a78638e828fc523038bb3441fd4fc144cfd2c6cd4963ffb9e207e648cf7b692600f1e1e524e965c32df5152120910e4903a47dcb - languageName: node - linkType: hard - -"json-parse-even-better-errors@npm:^2.3.0": - version: 2.3.1 - resolution: "json-parse-even-better-errors@npm:2.3.1" - checksum: 10c0/140932564c8f0b88455432e0f33c4cb4086b8868e37524e07e723f4eaedb9425bdc2bafd71bd1d9765bd15fd1e2d126972bc83990f55c467168c228c24d665f3 - languageName: node - linkType: hard - -"json-schema-traverse@npm:^0.4.1": - version: 0.4.1 - resolution: "json-schema-traverse@npm:0.4.1" - checksum: 10c0/108fa90d4cc6f08243aedc6da16c408daf81793bf903e9fd5ab21983cda433d5d2da49e40711da016289465ec2e62e0324dcdfbc06275a607fe3233fde4942ce - languageName: node - linkType: hard - -"json-schema-traverse@npm:^1.0.0": - version: 1.0.0 - resolution: "json-schema-traverse@npm:1.0.0" - checksum: 10c0/71e30015d7f3d6dc1c316d6298047c8ef98a06d31ad064919976583eb61e1018a60a0067338f0f79cabc00d84af3fcc489bd48ce8a46ea165d9541ba17fb30c6 - languageName: node - linkType: hard - -"json-stable-stringify-without-jsonify@npm:^1.0.1": - version: 1.0.1 - resolution: "json-stable-stringify-without-jsonify@npm:1.0.1" - checksum: 10c0/cb168b61fd4de83e58d09aaa6425ef71001bae30d260e2c57e7d09a5fd82223e2f22a042dedaab8db23b7d9ae46854b08bb1f91675a8be11c5cffebef5fb66a5 - languageName: node - linkType: hard - -"json-stringify-safe@npm:^5.0.1": - version: 5.0.1 - resolution: "json-stringify-safe@npm:5.0.1" - checksum: 10c0/7dbf35cd0411d1d648dceb6d59ce5857ec939e52e4afc37601aa3da611f0987d5cee5b38d58329ceddf3ed48bd7215229c8d52059ab01f2444a338bf24ed0f37 - languageName: node - linkType: hard - -"json5@npm:^2.2.3": - version: 2.2.3 - resolution: "json5@npm:2.2.3" - bin: - json5: lib/cli.js - checksum: 10c0/5a04eed94810fa55c5ea138b2f7a5c12b97c3750bc63d11e511dcecbfef758003861522a070c2272764ee0f4e3e323862f386945aeb5b85b87ee43f084ba586c - languageName: node - linkType: hard - -"jsonc-parser@npm:^3.2.0": - version: 3.3.1 - resolution: "jsonc-parser@npm:3.3.1" - checksum: 10c0/269c3ae0a0e4f907a914bf334306c384aabb9929bd8c99f909275ebd5c2d3bc70b9bcd119ad794f339dec9f24b6a4ee9cd5a8ab2e6435e730ad4075388fc2ab6 - languageName: node - linkType: hard - -"jsonfile@npm:^6.0.1": - version: 6.1.0 - resolution: "jsonfile@npm:6.1.0" - dependencies: - graceful-fs: "npm:^4.1.6" - universalify: "npm:^2.0.0" - dependenciesMeta: - graceful-fs: - optional: true - checksum: 10c0/4f95b5e8a5622b1e9e8f33c96b7ef3158122f595998114d1e7f03985649ea99cb3cd99ce1ed1831ae94c8c8543ab45ebd044207612f31a56fd08462140e46865 - languageName: node - linkType: hard - -"keyv@npm:^4.5.4": - version: 4.5.4 - resolution: "keyv@npm:4.5.4" - dependencies: - json-buffer: "npm:3.0.1" - checksum: 10c0/aa52f3c5e18e16bb6324876bb8b59dd02acf782a4b789c7b2ae21107fab95fab3890ed448d4f8dba80ce05391eeac4bfabb4f02a20221342982f806fa2cf271e - languageName: node - linkType: hard - -"kuler@npm:^2.0.0": - version: 2.0.0 - resolution: "kuler@npm:2.0.0" - checksum: 10c0/0a4e99d92ca373f8f74d1dc37931909c4d0d82aebc94cf2ba265771160fc12c8df34eaaac80805efbda367e2795cb1f1dd4c3d404b6b1cf38aec94035b503d2d - languageName: node - linkType: hard - -"langs@npm:^2.0.0": - version: 2.0.0 - resolution: "langs@npm:2.0.0" - checksum: 10c0/0eae81d768d579946f4df1c0fac29380e2ffc9071ffc037506754a09fa44c41ec0f0e61dc1ee384ea4b639d7de6cf68dd62083cc89a460eb302216792191b0a6 - languageName: node - linkType: hard - -"leven@npm:^3.1.0": - version: 3.1.0 - resolution: "leven@npm:3.1.0" - checksum: 10c0/cd778ba3fbab0f4d0500b7e87d1f6e1f041507c56fdcd47e8256a3012c98aaee371d4c15e0a76e0386107af2d42e2b7466160a2d80688aaa03e66e49949f42df - languageName: node - linkType: hard - -"levn@npm:^0.4.1": - version: 0.4.1 - resolution: "levn@npm:0.4.1" - dependencies: - prelude-ls: "npm:^1.2.1" - type-check: "npm:~0.4.0" - checksum: 10c0/effb03cad7c89dfa5bd4f6989364bfc79994c2042ec5966cb9b95990e2edee5cd8969ddf42616a0373ac49fac1403437deaf6e9050fbbaa3546093a59b9ac94e - languageName: node - linkType: hard - -"libxml2-wasm@npm:^0.5.0": - version: 0.5.0 - resolution: "libxml2-wasm@npm:0.5.0" - checksum: 10c0/b5be5eb100892728ba314a78e8a8b893277fba1e797c9c3d592db02d3fb58f729c2721bb8fadf2ea15ec9a4d4e26efcbb2ed80a2b3a2a1fc446ab5de7f1b8c27 - languageName: node - linkType: hard - -"lie@npm:3.1.1": - version: 3.1.1 - resolution: "lie@npm:3.1.1" - dependencies: - immediate: "npm:~3.0.5" - checksum: 10c0/d62685786590351b8e407814acdd89efe1cb136f05cb9236c5a97b2efdca1f631d2997310ad2d565c753db7596799870140e4777c9c9b8c44a0f6bf42d1804a1 - languageName: node - linkType: hard - -"lines-and-columns@npm:^1.1.6": - version: 1.2.4 - resolution: "lines-and-columns@npm:1.2.4" - checksum: 10c0/3da6ee62d4cd9f03f5dc90b4df2540fb85b352081bee77fe4bbcd12c9000ead7f35e0a38b8d09a9bb99b13223446dd8689ff3c4959807620726d788701a83d2d - languageName: node - linkType: hard - -"load-json-file@npm:^4.0.0": - version: 4.0.0 - resolution: "load-json-file@npm:4.0.0" - dependencies: - graceful-fs: "npm:^4.1.2" - parse-json: "npm:^4.0.0" - pify: "npm:^3.0.0" - strip-bom: "npm:^3.0.0" - checksum: 10c0/6b48f6a0256bdfcc8970be2c57f68f10acb2ee7e63709b386b2febb6ad3c86198f840889cdbe71d28f741cbaa2f23a7771206b138cd1bdd159564511ca37c1d5 - languageName: node - linkType: hard - -"localforage@npm:^1.9.0": - version: 1.10.0 - resolution: "localforage@npm:1.10.0" - dependencies: - lie: "npm:3.1.1" - checksum: 10c0/00f19f1f97002e6721587ed5017f502d58faf80dae567d5065d4d1ee0caf0762f40d2e2dba7f0ef7d3f14ee6203242daae9ecad97359bfc10ecff36df11d85a3 - languageName: node - linkType: hard - -"locate-path@npm:^2.0.0": - version: 2.0.0 - resolution: "locate-path@npm:2.0.0" - dependencies: - p-locate: "npm:^2.0.0" - path-exists: "npm:^3.0.0" - checksum: 10c0/24efa0e589be6aa3c469b502f795126b26ab97afa378846cb508174211515633b770aa0ba610cab113caedab8d2a4902b061a08aaed5297c12ab6f5be4df0133 - languageName: node - linkType: hard - -"locate-path@npm:^5.0.0": - version: 5.0.0 - resolution: "locate-path@npm:5.0.0" - dependencies: - p-locate: "npm:^4.1.0" - checksum: 10c0/33a1c5247e87e022f9713e6213a744557a3e9ec32c5d0b5efb10aa3a38177615bf90221a5592674857039c1a0fd2063b82f285702d37b792d973e9e72ace6c59 - languageName: node - linkType: hard - -"locate-path@npm:^6.0.0": - version: 6.0.0 - resolution: "locate-path@npm:6.0.0" - dependencies: - p-locate: "npm:^5.0.0" - checksum: 10c0/d3972ab70dfe58ce620e64265f90162d247e87159b6126b01314dd67be43d50e96a50b517bce2d9452a79409c7614054c277b5232377de50416564a77ac7aad3 - languageName: node - linkType: hard - -"lodash.merge@npm:^4.6.2": - version: 4.6.2 - resolution: "lodash.merge@npm:4.6.2" - checksum: 10c0/402fa16a1edd7538de5b5903a90228aa48eb5533986ba7fa26606a49db2572bf414ff73a2c9f5d5fd36b31c46a5d5c7e1527749c07cbcf965ccff5fbdf32c506 - languageName: node - linkType: hard - -"lodash@npm:^4.17.14, lodash@npm:^4.17.21": - version: 4.17.21 - resolution: "lodash@npm:4.17.21" - checksum: 10c0/d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c - languageName: node - linkType: hard - -"logform@npm:^2.3.2, logform@npm:^2.4.0": - version: 2.4.0 - resolution: "logform@npm:2.4.0" - dependencies: - "@colors/colors": "npm:1.5.0" - fecha: "npm:^4.2.0" - ms: "npm:^2.1.1" - safe-stable-stringify: "npm:^2.3.1" - triple-beam: "npm:^1.3.0" - checksum: 10c0/f616e92234b33e84c9197d961527c3bfd6b2cac19a51e422d4cd424285ff5da2b380dad8400e5a7da66a5c21227ae4aa63353d94fdaf8bce30a35a65295ca624 - languageName: node - linkType: hard - -"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0": - version: 10.4.3 - resolution: "lru-cache@npm:10.4.3" - checksum: 10c0/ebd04fbca961e6c1d6c0af3799adcc966a1babe798f685bb84e6599266599cd95d94630b10262f5424539bc4640107e8a33aa28585374abf561d30d16f4b39fb - languageName: node - linkType: hard - -"lru-cache@npm:^11.0.0": - version: 11.1.0 - resolution: "lru-cache@npm:11.1.0" - checksum: 10c0/85c312f7113f65fae6a62de7985348649937eb34fb3d212811acbf6704dc322a421788aca253b62838f1f07049a84cc513d88f494e373d3756514ad263670a64 - languageName: node - linkType: hard - -"lru-cache@npm:^5.1.1": - version: 5.1.1 - resolution: "lru-cache@npm:5.1.1" - dependencies: - yallist: "npm:^3.0.2" - checksum: 10c0/89b2ef2ef45f543011e38737b8a8622a2f8998cddf0e5437174ef8f1f70a8b9d14a918ab3e232cb3ba343b7abddffa667f0b59075b2b80e6b4d63c3de6127482 - languageName: node - linkType: hard - -"lru-cache@npm:^6.0.0": - version: 6.0.0 - resolution: "lru-cache@npm:6.0.0" - dependencies: - yallist: "npm:^4.0.0" - checksum: 10c0/cb53e582785c48187d7a188d3379c181b5ca2a9c78d2bce3e7dee36f32761d1c42983da3fe12b55cb74e1779fa94cdc2e5367c028a9b35317184ede0c07a30a9 - languageName: node - linkType: hard - -"lru-cache@npm:^7.14.1": - version: 7.18.3 - resolution: "lru-cache@npm:7.18.3" - checksum: 10c0/b3a452b491433db885beed95041eb104c157ef7794b9c9b4d647be503be91769d11206bb573849a16b4cc0d03cbd15ffd22df7960997788b74c1d399ac7a4fed - languageName: node - linkType: hard - -"luxon@npm:^3.6.1": - version: 3.6.1 - resolution: "luxon@npm:3.6.1" - checksum: 10c0/906d57a9dc4d1de9383f2e9223e378c298607c1b4d17b6657b836a3cd120feb1c1de3b5d06d846a3417e1ca764de8476e8c23b3cd4083b5cdb870adcb06a99d5 - languageName: node - linkType: hard - -"make-dir@npm:^4.0.0": - version: 4.0.0 - resolution: "make-dir@npm:4.0.0" - dependencies: - semver: "npm:^7.5.3" - checksum: 10c0/69b98a6c0b8e5c4fe9acb61608a9fbcfca1756d910f51e5dbe7a9e5cfb74fca9b8a0c8a0ffdf1294a740826c1ab4871d5bf3f62f72a3049e5eac6541ddffed68 - languageName: node - linkType: hard - -"make-fetch-happen@npm:^14.0.3": - version: 14.0.3 - resolution: "make-fetch-happen@npm:14.0.3" - dependencies: - "@npmcli/agent": "npm:^3.0.0" - cacache: "npm:^19.0.1" - http-cache-semantics: "npm:^4.1.1" - minipass: "npm:^7.0.2" - minipass-fetch: "npm:^4.0.0" - minipass-flush: "npm:^1.0.5" - minipass-pipeline: "npm:^1.2.4" - negotiator: "npm:^1.0.0" - proc-log: "npm:^5.0.0" - promise-retry: "npm:^2.0.1" - ssri: "npm:^12.0.0" - checksum: 10c0/c40efb5e5296e7feb8e37155bde8eb70bc57d731b1f7d90e35a092fde403d7697c56fb49334d92d330d6f1ca29a98142036d6480a12681133a0a1453164cb2f0 - languageName: node - linkType: hard - -"makeerror@npm:1.0.12": - version: 1.0.12 - resolution: "makeerror@npm:1.0.12" - dependencies: - tmpl: "npm:1.0.5" - checksum: 10c0/b0e6e599780ce6bab49cc413eba822f7d1f0dfebd1c103eaa3785c59e43e22c59018323cf9e1708f0ef5329e94a745d163fcbb6bff8e4c6742f9be9e86f3500c - languageName: node - linkType: hard - -"math-intrinsics@npm:^1.1.0": - version: 1.1.0 - resolution: "math-intrinsics@npm:1.1.0" - checksum: 10c0/7579ff94e899e2f76ab64491d76cf606274c874d8f2af4a442c016bd85688927fcfca157ba6bf74b08e9439dc010b248ce05b96cc7c126a354c3bae7fcb48b7f - languageName: node - linkType: hard - -"merge-stream@npm:^2.0.0": - version: 2.0.0 - resolution: "merge-stream@npm:2.0.0" - checksum: 10c0/867fdbb30a6d58b011449b8885601ec1690c3e41c759ecd5a9d609094f7aed0096c37823ff4a7190ef0b8f22cc86beb7049196ff68c016e3b3c671d0dac91ce5 - languageName: node - linkType: hard - -"merge2@npm:^1.3.0": - version: 1.4.1 - resolution: "merge2@npm:1.4.1" - checksum: 10c0/254a8a4605b58f450308fc474c82ac9a094848081bf4c06778200207820e5193726dc563a0d2c16468810516a5c97d9d3ea0ca6585d23c58ccfff2403e8dbbeb - languageName: node - linkType: hard - -"micromatch@npm:^4.0.8": - version: 4.0.8 - resolution: "micromatch@npm:4.0.8" - dependencies: - braces: "npm:^3.0.3" - picomatch: "npm:^2.3.1" - checksum: 10c0/166fa6eb926b9553f32ef81f5f531d27b4ce7da60e5baf8c021d043b27a388fb95e46a8038d5045877881e673f8134122b59624d5cecbd16eb50a42e7a6b5ca8 - languageName: node - linkType: hard - -"mime-db@npm:1.52.0, mime-db@npm:>= 1.43.0 < 2": - version: 1.52.0 - resolution: "mime-db@npm:1.52.0" - checksum: 10c0/0557a01deebf45ac5f5777fe7740b2a5c309c6d62d40ceab4e23da9f821899ce7a900b7ac8157d4548ddbb7beffe9abc621250e6d182b0397ec7f10c7b91a5aa - languageName: node - linkType: hard - -"mime-db@npm:~1.33.0": - version: 1.33.0 - resolution: "mime-db@npm:1.33.0" - checksum: 10c0/79172ce5468c8503b49dddfdddc18d3f5fe2599f9b5fe1bc321a8cbee14c96730fc6db22f907b23701b05b2936f865795f62ec3a78a7f3c8cb2450bb68c6763e - languageName: node - linkType: hard - -"mime-types@npm:2.1.18": - version: 2.1.18 - resolution: "mime-types@npm:2.1.18" - dependencies: - mime-db: "npm:~1.33.0" - checksum: 10c0/a96a8d12f4bb98bc7bfac6a8ccbd045f40368fc1030d9366050c3613825d3715d1c1f393e10a75a885d2cdc1a26cd6d5e11f3a2a0d5c4d361f00242139430a0f - languageName: node - linkType: hard - -"mime-types@npm:^2.1.12, mime-types@npm:~2.1.34": - version: 2.1.35 - resolution: "mime-types@npm:2.1.35" - dependencies: - mime-db: "npm:1.52.0" - checksum: 10c0/82fb07ec56d8ff1fc999a84f2f217aa46cb6ed1033fefaabd5785b9a974ed225c90dc72fff460259e66b95b73648596dbcc50d51ed69cdf464af2d237d3149b2 - languageName: node - linkType: hard - -"mimic-fn@npm:^2.1.0": - version: 2.1.0 - resolution: "mimic-fn@npm:2.1.0" - checksum: 10c0/b26f5479d7ec6cc2bce275a08f146cf78f5e7b661b18114e2506dd91ec7ec47e7a25bf4360e5438094db0560bcc868079fb3b1fb3892b833c1ecbf63f80c95a4 - languageName: node - linkType: hard - -"minimatch@npm:3.1.2, minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": - version: 3.1.2 - resolution: "minimatch@npm:3.1.2" - dependencies: - brace-expansion: "npm:^1.1.7" - checksum: 10c0/0262810a8fc2e72cca45d6fd86bd349eee435eb95ac6aa45c9ea2180e7ee875ef44c32b55b5973ceabe95ea12682f6e3725cbb63d7a2d1da3ae1163c8b210311 - languageName: node - linkType: hard - -"minimatch@npm:^10.0.3": - version: 10.0.3 - resolution: "minimatch@npm:10.0.3" - dependencies: - "@isaacs/brace-expansion": "npm:^5.0.0" - checksum: 10c0/e43e4a905c5d70ac4cec8530ceaeccb9c544b1ba8ac45238e2a78121a01c17ff0c373346472d221872563204eabe929ad02669bb575cb1f0cc30facab369f70f - languageName: node - linkType: hard - -"minimatch@npm:^9.0.4": - version: 9.0.5 - resolution: "minimatch@npm:9.0.5" - dependencies: - brace-expansion: "npm:^2.0.1" - checksum: 10c0/de96cf5e35bdf0eab3e2c853522f98ffbe9a36c37797778d2665231ec1f20a9447a7e567cb640901f89e4daaa95ae5d70c65a9e8aa2bb0019b6facbc3c0575ed - languageName: node - linkType: hard - -"minimist@npm:^1.2.0": - version: 1.2.6 - resolution: "minimist@npm:1.2.6" - checksum: 10c0/d0b566204044481c4401abbd24cc75814e753b37268e7fe7ccc78612bf3e37bf1e45a6c43fb0b119445ea1c413c000bde013f320b7211974f2f49bcbec1d0dbf - languageName: node - linkType: hard - -"minipass-collect@npm:^2.0.1": - version: 2.0.1 - resolution: "minipass-collect@npm:2.0.1" - dependencies: - minipass: "npm:^7.0.3" - checksum: 10c0/5167e73f62bb74cc5019594709c77e6a742051a647fe9499abf03c71dca75515b7959d67a764bdc4f8b361cf897fbf25e2d9869ee039203ed45240f48b9aa06e - languageName: node - linkType: hard - -"minipass-fetch@npm:^4.0.0": - version: 4.0.1 - resolution: "minipass-fetch@npm:4.0.1" - dependencies: - encoding: "npm:^0.1.13" - minipass: "npm:^7.0.3" - minipass-sized: "npm:^1.0.3" - minizlib: "npm:^3.0.1" - dependenciesMeta: - encoding: - optional: true - checksum: 10c0/a3147b2efe8e078c9bf9d024a0059339c5a09c5b1dded6900a219c218cc8b1b78510b62dae556b507304af226b18c3f1aeb1d48660283602d5b6586c399eed5c - languageName: node - linkType: hard - -"minipass-flush@npm:^1.0.5": - version: 1.0.5 - resolution: "minipass-flush@npm:1.0.5" - dependencies: - minipass: "npm:^3.0.0" - checksum: 10c0/2a51b63feb799d2bb34669205eee7c0eaf9dce01883261a5b77410c9408aa447e478efd191b4de6fc1101e796ff5892f8443ef20d9544385819093dbb32d36bd - languageName: node - linkType: hard - -"minipass-pipeline@npm:^1.2.4": - version: 1.2.4 - resolution: "minipass-pipeline@npm:1.2.4" - dependencies: - minipass: "npm:^3.0.0" - checksum: 10c0/cbda57cea20b140b797505dc2cac71581a70b3247b84480c1fed5ca5ba46c25ecc25f68bfc9e6dcb1a6e9017dab5c7ada5eab73ad4f0a49d84e35093e0c643f2 - languageName: node - linkType: hard - -"minipass-sized@npm:^1.0.3": - version: 1.0.3 - resolution: "minipass-sized@npm:1.0.3" - dependencies: - minipass: "npm:^3.0.0" - checksum: 10c0/298f124753efdc745cfe0f2bdfdd81ba25b9f4e753ca4a2066eb17c821f25d48acea607dfc997633ee5bf7b6dfffb4eee4f2051eb168663f0b99fad2fa4829cb - languageName: node - linkType: hard - -"minipass@npm:^3.0.0": - version: 3.3.6 - resolution: "minipass@npm:3.3.6" - dependencies: - yallist: "npm:^4.0.0" - checksum: 10c0/a114746943afa1dbbca8249e706d1d38b85ed1298b530f5808ce51f8e9e941962e2a5ad2e00eae7dd21d8a4aae6586a66d4216d1a259385e9d0358f0c1eba16c - languageName: node - linkType: hard - -"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": - version: 7.1.2 - resolution: "minipass@npm:7.1.2" - checksum: 10c0/b0fd20bb9fb56e5fa9a8bfac539e8915ae07430a619e4b86ff71f5fc757ef3924b23b2c4230393af1eda647ed3d75739e4e0acb250a6b1eb277cf7f8fe449557 - languageName: node - linkType: hard - -"minizlib@npm:^3.0.1": - version: 3.0.2 - resolution: "minizlib@npm:3.0.2" - dependencies: - minipass: "npm:^7.1.2" - checksum: 10c0/9f3bd35e41d40d02469cb30470c55ccc21cae0db40e08d1d0b1dff01cc8cc89a6f78e9c5d2b7c844e485ec0a8abc2238111213fdc5b2038e6d1012eacf316f78 - languageName: node - linkType: hard - -"mitm@npm:^1.3.2": - version: 1.7.3 - resolution: "mitm@npm:1.7.3" - dependencies: - semver: "npm:>= 5 < 6" - checksum: 10c0/1929d683fdb4073b3b8c810647064b948da9df14f3aca4f8a90fa2647ee0a8d8a7f85614d5de245b13cf756f11931902fcc5f6cb6467ff87024bfa8c6f525a26 - languageName: node - linkType: hard - -"mkdirp@npm:1.0.4": - version: 1.0.4 - resolution: "mkdirp@npm:1.0.4" - bin: - mkdirp: bin/cmd.js - checksum: 10c0/46ea0f3ffa8bc6a5bc0c7081ffc3907777f0ed6516888d40a518c5111f8366d97d2678911ad1a6882bf592fa9de6c784fea32e1687bb94e1f4944170af48a5cf - languageName: node - linkType: hard - -"mkdirp@npm:^3.0.1": - version: 3.0.1 - resolution: "mkdirp@npm:3.0.1" - bin: - mkdirp: dist/cjs/src/bin.js - checksum: 10c0/9f2b975e9246351f5e3a40dcfac99fcd0baa31fbfab615fe059fb11e51f10e4803c63de1f384c54d656e4db31d000e4767e9ef076a22e12a641357602e31d57d - languageName: node - linkType: hard - -"mockdate@npm:^3.0.5": - version: 3.0.5 - resolution: "mockdate@npm:3.0.5" - checksum: 10c0/36ab00c4b94d3e3cc8b9ecec27730dcf396d47d2ba046ad96635e7f9bc4bba0fff6125dedaba7313b1cadc0cefc0eb3f3ac0d5c3655707afd3b82fa4550aae92 - languageName: node - linkType: hard - -"module-details-from-path@npm:^1.0.3": - version: 1.0.4 - resolution: "module-details-from-path@npm:1.0.4" - checksum: 10c0/10863413e96dab07dee917eae07afe46f7bf853065cc75a7d2a718adf67574857fb64f8a2c0c9af12ac733a9a8cf652db7ed39b95f7a355d08106cb9cc50c83b - languageName: node - linkType: hard - -"ms@npm:2.0.0": - version: 2.0.0 - resolution: "ms@npm:2.0.0" - checksum: 10c0/f8fda810b39fd7255bbdc451c46286e549794fcc700dc9cd1d25658bbc4dc2563a5de6fe7c60f798a16a60c6ceb53f033cb353f493f0cf63e5199b702943159d - languageName: node - linkType: hard - -"ms@npm:2.1.2": - version: 2.1.2 - resolution: "ms@npm:2.1.2" - checksum: 10c0/a437714e2f90dbf881b5191d35a6db792efbca5badf112f87b9e1c712aace4b4b9b742dd6537f3edf90fd6f684de897cec230abde57e87883766712ddda297cc - languageName: node - linkType: hard - -"ms@npm:^2.0.0, ms@npm:^2.1.1, ms@npm:^2.1.3": - version: 2.1.3 - resolution: "ms@npm:2.1.3" - checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 - languageName: node - linkType: hard - -"mute-stream@npm:^2.0.0": - version: 2.0.0 - resolution: "mute-stream@npm:2.0.0" - checksum: 10c0/2cf48a2087175c60c8dcdbc619908b49c07f7adcfc37d29236b0c5c612d6204f789104c98cc44d38acab7b3c96f4a3ec2cfdc4934d0738d876dbefa2a12c69f4 - languageName: node - linkType: hard - -"mute-stream@npm:~0.0.4": - version: 0.0.8 - resolution: "mute-stream@npm:0.0.8" - checksum: 10c0/18d06d92e5d6d45e2b63c0e1b8f25376af71748ac36f53c059baa8b76ffac31c5ab225480494e7d35d30215ecdb18fed26ec23cafcd2f7733f2f14406bcd19e2 - languageName: node - linkType: hard - -"napi-postinstall@npm:^0.2.2": - version: 0.2.4 - resolution: "napi-postinstall@npm:0.2.4" - bin: - napi-postinstall: lib/cli.js - checksum: 10c0/e8c357d7e27848c4af7becf2796afff245a2fc8ba176e1b133410bb1c9934a66d4bc542d0c9f04c73b0ba34ee0486b30b6cd1c62ed3aa36797d394200c9a2a8b - languageName: node - linkType: hard - -"natural-compare@npm:^1.4.0": - version: 1.4.0 - resolution: "natural-compare@npm:1.4.0" - checksum: 10c0/f5f9a7974bfb28a91afafa254b197f0f22c684d4a1731763dda960d2c8e375b36c7d690e0d9dc8fba774c537af14a7e979129bca23d88d052fbeb9466955e447 - languageName: node - linkType: hard - -"natural-orderby@npm:^5.0.0": - version: 5.0.0 - resolution: "natural-orderby@npm:5.0.0" - checksum: 10c0/d4e8b3bf16636eedc0c99a4cd551cd371732dd8c9766f19c8e153946553d82c12527eab182b8e79d640681aa6cb718273b53662ccf88127d4726307b6e645b2b - languageName: node - linkType: hard - -"nedb-promises@npm:^6.2.3": - version: 6.2.3 - resolution: "nedb-promises@npm:6.2.3" - dependencies: - "@seald-io/nedb": "npm:^4.0.2" - checksum: 10c0/778610a6047c2e8be61db007fadacad956fc6e035e4f65c8764361a4614dd21e9f3a584b07fb483c92016993f17d77d27d13a8c73e28364f132ba407fe1f7097 - languageName: node - linkType: hard - -"needle@npm:2.4.0": - version: 2.4.0 - resolution: "needle@npm:2.4.0" - dependencies: - debug: "npm:^3.2.6" - iconv-lite: "npm:^0.4.4" - sax: "npm:^1.2.4" - bin: - needle: ./bin/needle - checksum: 10c0/3f64b77628b9fd792744592b5c6633d5a551671dca89057016e0b5d5009bf42f1e195d5096811d9cf97b300e1a7b9b581a500aa82f122cb53176260e06e6b316 - languageName: node - linkType: hard - -"negotiator@npm:0.6.3": - version: 0.6.3 - resolution: "negotiator@npm:0.6.3" - checksum: 10c0/3ec9fd413e7bf071c937ae60d572bc67155262068ed522cf4b3be5edbe6ddf67d095ec03a3a14ebf8fc8e95f8e1d61be4869db0dbb0de696f6b837358bd43fc2 - languageName: node - linkType: hard - -"negotiator@npm:^1.0.0": - version: 1.0.0 - resolution: "negotiator@npm:1.0.0" - checksum: 10c0/4c559dd52669ea48e1914f9d634227c561221dd54734070791f999c52ed0ff36e437b2e07d5c1f6e32909fc625fe46491c16e4a8f0572567d4dd15c3a4fda04b - languageName: node - linkType: hard - -"netmask@npm:^2.0.2": - version: 2.0.2 - resolution: "netmask@npm:2.0.2" - checksum: 10c0/cafd28388e698e1138ace947929f842944d0f1c0b87d3fa2601a61b38dc89397d33c0ce2c8e7b99e968584b91d15f6810b91bef3f3826adf71b1833b61d4bf4f - languageName: node - linkType: hard - -"node-cleanup@npm:^2.1.2": - version: 2.1.2 - resolution: "node-cleanup@npm:2.1.2" - checksum: 10c0/c59077d7cac01f6315e4417e5d13523e43aa19965b14768581dbd06b37419323abe5f7171afe6aa52b7a483bdd0027e5a5f62a532a2ebec2a8c4cdf163736c92 - languageName: node - linkType: hard - -"node-ensure@npm:^0.0.0": - version: 0.0.0 - resolution: "node-ensure@npm:0.0.0" - checksum: 10c0/7af391aee024a8b7df77c239ed8b90417e3f2539824fa06b60f243ce14c75ee455766464c7c3ba9407d5b1e4d1d74ed5cf5f8af10c67b0fc05aa6e29f5d2462b - languageName: node - linkType: hard - -"node-gyp@npm:latest": - version: 11.2.0 - resolution: "node-gyp@npm:11.2.0" - dependencies: - env-paths: "npm:^2.2.0" - exponential-backoff: "npm:^3.1.1" - graceful-fs: "npm:^4.2.6" - make-fetch-happen: "npm:^14.0.3" - nopt: "npm:^8.0.0" - proc-log: "npm:^5.0.0" - semver: "npm:^7.3.5" - tar: "npm:^7.4.3" - tinyglobby: "npm:^0.2.12" - which: "npm:^5.0.0" - bin: - node-gyp: bin/node-gyp.js - checksum: 10c0/bd8d8c76b06be761239b0c8680f655f6a6e90b48e44d43415b11c16f7e8c15be346fba0cbf71588c7cdfb52c419d928a7d3db353afc1d952d19756237d8f10b9 - languageName: node - linkType: hard - -"node-gzip@npm:^1.1.2": - version: 1.1.2 - resolution: "node-gzip@npm:1.1.2" - checksum: 10c0/c7aec81659bf69065bcfecb596293aaa3bd115ba328a2188a257f3640799f5ae8157ce82d93c17500494c695ff16e718308353ac628a9353679b2353f9e93402 - languageName: node - linkType: hard - -"node-int64@npm:^0.4.0": - version: 0.4.0 - resolution: "node-int64@npm:0.4.0" - checksum: 10c0/a6a4d8369e2f2720e9c645255ffde909c0fbd41c92ea92a5607fc17055955daac99c1ff589d421eee12a0d24e99f7bfc2aabfeb1a4c14742f6c099a51863f31a - languageName: node - linkType: hard - -"node-releases@npm:^2.0.19": - version: 2.0.19 - resolution: "node-releases@npm:2.0.19" - checksum: 10c0/52a0dbd25ccf545892670d1551690fe0facb6a471e15f2cfa1b20142a5b255b3aa254af5f59d6ecb69c2bec7390bc643c43aa63b13bf5e64b6075952e716b1aa - languageName: node - linkType: hard - -"nopt@npm:^8.0.0": - version: 8.1.0 - resolution: "nopt@npm:8.1.0" - dependencies: - abbrev: "npm:^3.0.0" - bin: - nopt: bin/nopt.js - checksum: 10c0/62e9ea70c7a3eb91d162d2c706b6606c041e4e7b547cbbb48f8b3695af457dd6479904d7ace600856bf923dd8d1ed0696f06195c8c20f02ac87c1da0e1d315ef - languageName: node - linkType: hard - -"normalize-path@npm:^3.0.0, normalize-path@npm:~3.0.0": - version: 3.0.0 - resolution: "normalize-path@npm:3.0.0" - checksum: 10c0/e008c8142bcc335b5e38cf0d63cfd39d6cf2d97480af9abdbe9a439221fd4d749763bab492a8ee708ce7a194bb00c9da6d0a115018672310850489137b3da046 - languageName: node - linkType: hard - -"normalize-url@npm:^6.1.0": - version: 6.1.0 - resolution: "normalize-url@npm:6.1.0" - checksum: 10c0/95d948f9bdd2cfde91aa786d1816ae40f8262946e13700bf6628105994fe0ff361662c20af3961161c38a119dc977adeb41fc0b41b1745eb77edaaf9cb22db23 - languageName: node - linkType: hard - -"npm-run-path@npm:^4.0.1": - version: 4.0.1 - resolution: "npm-run-path@npm:4.0.1" - dependencies: - path-key: "npm:^3.0.0" - checksum: 10c0/6f9353a95288f8455cf64cbeb707b28826a7f29690244c1e4bb61ec573256e021b6ad6651b394eb1ccfd00d6ec50147253aba2c5fe58a57ceb111fad62c519ac - languageName: node - linkType: hard - -"nth-check@npm:^2.0.1": - version: 2.1.1 - resolution: "nth-check@npm:2.1.1" - dependencies: - boolbase: "npm:^1.0.0" - checksum: 10c0/5fee7ff309727763689cfad844d979aedd2204a817fbaaf0e1603794a7c20db28548d7b024692f953557df6ce4a0ee4ae46cd8ebd9b36cfb300b9226b567c479 - languageName: node - linkType: hard - -"numeral@npm:^2.0.6": - version: 2.0.6 - resolution: "numeral@npm:2.0.6" - checksum: 10c0/5ed008d3fae05cfa4986b77a85ca10bff29ae6e1fa41a04cce05ea21f08a8a104226f88868930e2a94e3239708d6985d111b5d1291e8b9a3049ffc5365c332d4 - languageName: node - linkType: hard - -"object-code@npm:^1.2.2": - version: 1.2.2 - resolution: "object-code@npm:1.2.2" - checksum: 10c0/c884026afe8c5767962e4a44466915506209fdf358e716f2400f7c9d0afe19031b73e3452e4edffb3c90880e17ca6d609545239f628c4e9a372587eb04903ab7 - languageName: node - linkType: hard - -"object-treeify@npm:^2.1.1": - version: 2.1.1 - resolution: "object-treeify@npm:2.1.1" - checksum: 10c0/c2fb1a16f7b3aa26bd8ff6978167a76168a75180565e58bd6af10ed38ec47a40b4ff2f3751dd891b63ebc7c54d1c9ec5b632dc4203f5579aec04fc0da7128833 - languageName: node - linkType: hard - -"on-headers@npm:~1.0.2": - version: 1.0.2 - resolution: "on-headers@npm:1.0.2" - checksum: 10c0/f649e65c197bf31505a4c0444875db0258e198292f34b884d73c2f751e91792ef96bb5cf89aa0f4fecc2e4dc662461dda606b1274b0e564f539cae5d2f5fc32f - languageName: node - linkType: hard - -"once@npm:^1.3.0": - version: 1.4.0 - resolution: "once@npm:1.4.0" - dependencies: - wrappy: "npm:1" - checksum: 10c0/5d48aca287dfefabd756621c5dfce5c91a549a93e9fdb7b8246bc4c4790aa2ec17b34a260530474635147aeb631a2dcc8b32c613df0675f96041cbb8244517d0 - languageName: node - linkType: hard - -"one-time@npm:^1.0.0": - version: 1.0.0 - resolution: "one-time@npm:1.0.0" - dependencies: - fn.name: "npm:1.x.x" - checksum: 10c0/6e4887b331edbb954f4e915831cbec0a7b9956c36f4feb5f6de98c448ac02ff881fd8d9b55a6b1b55030af184c6b648f340a76eb211812f4ad8c9b4b8692fdaa - languageName: node - linkType: hard - -"onetime@npm:^5.1.2": - version: 5.1.2 - resolution: "onetime@npm:5.1.2" - dependencies: - mimic-fn: "npm:^2.1.0" - checksum: 10c0/ffcef6fbb2692c3c40749f31ea2e22677a876daea92959b8a80b521d95cca7a668c884d8b2045d1d8ee7d56796aa405c405462af112a1477594cc63531baeb8f - languageName: node - linkType: hard - -"optionator@npm:^0.9.3": - version: 0.9.3 - resolution: "optionator@npm:0.9.3" - dependencies: - "@aashutoshrathi/word-wrap": "npm:^1.2.3" - deep-is: "npm:^0.1.3" - fast-levenshtein: "npm:^2.0.6" - levn: "npm:^0.4.1" - prelude-ls: "npm:^1.2.1" - type-check: "npm:^0.4.0" - checksum: 10c0/66fba794d425b5be51353035cf3167ce6cfa049059cbb93229b819167687e0f48d2bc4603fcb21b091c99acb516aae1083624675b15c4765b2e4693a085e959c - languageName: node - linkType: hard - -"os-tmpdir@npm:~1.0.2": - version: 1.0.2 - resolution: "os-tmpdir@npm:1.0.2" - checksum: 10c0/f438450224f8e2687605a8dd318f0db694b6293c5d835ae509a69e97c8de38b6994645337e5577f5001115470414638978cc49da1cdcc25106dad8738dc69990 - languageName: node - linkType: hard - -"p-limit@npm:^1.1.0": - version: 1.3.0 - resolution: "p-limit@npm:1.3.0" - dependencies: - p-try: "npm:^1.0.0" - checksum: 10c0/5c1b1d53d180b2c7501efb04b7c817448e10efe1ba46f4783f8951994d5027e4cd88f36ad79af50546682594c4ebd11702ac4b9364c47f8074890e2acad0edee - languageName: node - linkType: hard - -"p-limit@npm:^2.2.0": - version: 2.3.0 - resolution: "p-limit@npm:2.3.0" - dependencies: - p-try: "npm:^2.0.0" - checksum: 10c0/8da01ac53efe6a627080fafc127c873da40c18d87b3f5d5492d465bb85ec7207e153948df6b9cbaeb130be70152f874229b8242ee2be84c0794082510af97f12 - languageName: node - linkType: hard - -"p-limit@npm:^3.0.2, p-limit@npm:^3.1.0": - version: 3.1.0 - resolution: "p-limit@npm:3.1.0" - dependencies: - yocto-queue: "npm:^0.1.0" - checksum: 10c0/9db675949dbdc9c3763c89e748d0ef8bdad0afbb24d49ceaf4c46c02c77d30db4e0652ed36d0a0a7a95154335fab810d95c86153105bb73b3a90448e2bb14e1a - languageName: node - linkType: hard - -"p-locate@npm:^2.0.0": - version: 2.0.0 - resolution: "p-locate@npm:2.0.0" - dependencies: - p-limit: "npm:^1.1.0" - checksum: 10c0/82da4be88fb02fd29175e66021610c881938d3cc97c813c71c1a605fac05617d57fd5d3b337494a6106c0edb2a37c860241430851411f1b265108cead34aee67 - languageName: node - linkType: hard - -"p-locate@npm:^4.1.0": - version: 4.1.0 - resolution: "p-locate@npm:4.1.0" - dependencies: - p-limit: "npm:^2.2.0" - checksum: 10c0/1b476ad69ad7f6059744f343b26d51ce091508935c1dbb80c4e0a2f397ffce0ca3a1f9f5cd3c7ce19d7929a09719d5c65fe70d8ee289c3f267cd36f2881813e9 - languageName: node - linkType: hard - -"p-locate@npm:^5.0.0": - version: 5.0.0 - resolution: "p-locate@npm:5.0.0" - dependencies: - p-limit: "npm:^3.0.2" - checksum: 10c0/2290d627ab7903b8b70d11d384fee714b797f6040d9278932754a6860845c4d3190603a0772a663c8cb5a7b21d1b16acb3a6487ebcafa9773094edc3dfe6009a - languageName: node - linkType: hard - -"p-map@npm:^7.0.2": - version: 7.0.3 - resolution: "p-map@npm:7.0.3" - checksum: 10c0/46091610da2b38ce47bcd1d8b4835a6fa4e832848a6682cf1652bc93915770f4617afc844c10a77d1b3e56d2472bb2d5622353fa3ead01a7f42b04fc8e744a5c - languageName: node - linkType: hard - -"p-try@npm:^1.0.0": - version: 1.0.0 - resolution: "p-try@npm:1.0.0" - checksum: 10c0/757ba31de5819502b80c447826fac8be5f16d3cb4fbf9bc8bc4971dba0682e84ac33e4b24176ca7058c69e29f64f34d8d9e9b08e873b7b7bb0aa89d620fa224a - languageName: node - linkType: hard - -"p-try@npm:^2.0.0": - version: 2.2.0 - resolution: "p-try@npm:2.2.0" - checksum: 10c0/c36c19907734c904b16994e6535b02c36c2224d433e01a2f1ab777237f4d86e6289fd5fd464850491e940379d4606ed850c03e0f9ab600b0ebddb511312e177f - languageName: node - linkType: hard - -"pac-proxy-agent@npm:^7.0.1": - version: 7.2.0 - resolution: "pac-proxy-agent@npm:7.2.0" - dependencies: - "@tootallnate/quickjs-emscripten": "npm:^0.23.0" - agent-base: "npm:^7.1.2" - debug: "npm:^4.3.4" - get-uri: "npm:^6.0.1" - http-proxy-agent: "npm:^7.0.0" - https-proxy-agent: "npm:^7.0.6" - pac-resolver: "npm:^7.0.1" - socks-proxy-agent: "npm:^8.0.5" - checksum: 10c0/0265c17c9401c2ea735697931a6553a0c6d8b20c4d7d4e3b3a0506080ba69a8d5ad656e2a6be875411212e2b6ed7a4d9526dd3997e08581fdfb1cbcad454c296 - languageName: node - linkType: hard - -"pac-resolver@npm:^7.0.1": - version: 7.0.1 - resolution: "pac-resolver@npm:7.0.1" - dependencies: - degenerator: "npm:^5.0.0" - netmask: "npm:^2.0.2" - checksum: 10c0/5f3edd1dd10fded31e7d1f95776442c3ee51aa098c28b74ede4927d9677ebe7cebb2636750c24e945f5b84445e41ae39093d3a1014a994e5ceb9f0b1b88ebff5 - languageName: node - linkType: hard - -"package-json-from-dist@npm:^1.0.0": - version: 1.0.1 - resolution: "package-json-from-dist@npm:1.0.1" - checksum: 10c0/62ba2785eb655fec084a257af34dbe24292ab74516d6aecef97ef72d4897310bc6898f6c85b5cd22770eaa1ce60d55a0230e150fb6a966e3ecd6c511e23d164b - languageName: node - linkType: hard - -"pako@npm:^0.2.5": - version: 0.2.9 - resolution: "pako@npm:0.2.9" - checksum: 10c0/79c1806ebcf325b60ae599e4d7227c2e346d7b829dc20f5cf24cef07c934079dc3a61c5b3c8278a2f7a190c4a613e343ea11e5302dbe252efd11712df4b6b041 - languageName: node - linkType: hard - -"pako@npm:^2.1.0": - version: 2.1.0 - resolution: "pako@npm:2.1.0" - checksum: 10c0/8e8646581410654b50eb22a5dfd71159cae98145bd5086c9a7a816ec0370b5f72b4648d08674624b3870a521e6a3daffd6c2f7bc00fdefc7063c9d8232ff5116 - languageName: node - linkType: hard - -"parent-module@npm:^1.0.0": - version: 1.0.1 - resolution: "parent-module@npm:1.0.1" - dependencies: - callsites: "npm:^3.0.0" - checksum: 10c0/c63d6e80000d4babd11978e0d3fee386ca7752a02b035fd2435960ffaa7219dc42146f07069fb65e6e8bf1caef89daf9af7535a39bddf354d78bf50d8294f556 - languageName: node - linkType: hard - -"parse-duration@npm:^2.1.4": - version: 2.1.4 - resolution: "parse-duration@npm:2.1.4" - checksum: 10c0/e6dc70dbd33a0c13d9b9edaf37141340dadece38672bd7fd5e01887ef6587cd04149cc4f2cb05e7a3b3f94abbdca72dd311e2016720351d3dd1e17c45014dfbb - languageName: node - linkType: hard - -"parse-json@npm:^4.0.0": - version: 4.0.0 - resolution: "parse-json@npm:4.0.0" - dependencies: - error-ex: "npm:^1.3.1" - json-parse-better-errors: "npm:^1.0.1" - checksum: 10c0/8d80790b772ccb1bcea4e09e2697555e519d83d04a77c2b4237389b813f82898943a93ffff7d0d2406203bdd0c30dcf95b1661e3a53f83d0e417f053957bef32 - languageName: node - linkType: hard - -"parse-json@npm:^5.2.0": - version: 5.2.0 - resolution: "parse-json@npm:5.2.0" - dependencies: - "@babel/code-frame": "npm:^7.0.0" - error-ex: "npm:^1.3.1" - json-parse-even-better-errors: "npm:^2.3.0" - lines-and-columns: "npm:^1.1.6" - checksum: 10c0/77947f2253005be7a12d858aedbafa09c9ae39eb4863adf330f7b416ca4f4a08132e453e08de2db46459256fb66afaac5ee758b44fe6541b7cdaf9d252e59585 - languageName: node - linkType: hard - -"parse5-htmlparser2-tree-adapter@npm:^7.1.0": - version: 7.1.0 - resolution: "parse5-htmlparser2-tree-adapter@npm:7.1.0" - dependencies: - domhandler: "npm:^5.0.3" - parse5: "npm:^7.0.0" - checksum: 10c0/e5a4e0b834c84c9e244b5749f8d007f4baaeafac7a1da2c54be3421ffd9ef8fdec4f198bf55cda22e88e6ba95e9943f6ed5aa3ae5900b39972ebf5dc8c3f4722 - languageName: node - linkType: hard - -"parse5-parser-stream@npm:^7.1.2": - version: 7.1.2 - resolution: "parse5-parser-stream@npm:7.1.2" - dependencies: - parse5: "npm:^7.0.0" - checksum: 10c0/e236c61000d38ecad369e725a48506b051cebad8abb00e6d4e8bff7aa85c183820fcb45db1559cc90955bdbbdbd665ea94c41259594e74566fff411478dc7fcb - languageName: node - linkType: hard - -"parse5@npm:^7.0.0, parse5@npm:^7.3.0": - version: 7.3.0 - resolution: "parse5@npm:7.3.0" - dependencies: - entities: "npm:^6.0.0" - checksum: 10c0/7fd2e4e247e85241d6f2a464d0085eed599a26d7b0a5233790c49f53473232eb85350e8133344d9b3fd58b89339e7ad7270fe1f89d28abe50674ec97b87f80b5 - languageName: node - linkType: hard - -"path-exists@npm:^3.0.0": - version: 3.0.0 - resolution: "path-exists@npm:3.0.0" - checksum: 10c0/17d6a5664bc0a11d48e2b2127d28a0e58822c6740bde30403f08013da599182289c56518bec89407e3f31d3c2b6b296a4220bc3f867f0911fee6952208b04167 - languageName: node - linkType: hard - -"path-exists@npm:^4.0.0": - version: 4.0.0 - resolution: "path-exists@npm:4.0.0" - checksum: 10c0/8c0bd3f5238188197dc78dced15207a4716c51cc4e3624c44fc97acf69558f5ebb9a2afff486fe1b4ee148e0c133e96c5e11a9aa5c48a3006e3467da070e5e1b - languageName: node - linkType: hard - -"path-is-absolute@npm:^1.0.0": - version: 1.0.1 - resolution: "path-is-absolute@npm:1.0.1" - checksum: 10c0/127da03c82172a2a50099cddbf02510c1791fc2cc5f7713ddb613a56838db1e8168b121a920079d052e0936c23005562059756d653b7c544c53185efe53be078 - languageName: node - linkType: hard - -"path-is-inside@npm:1.0.2": - version: 1.0.2 - resolution: "path-is-inside@npm:1.0.2" - checksum: 10c0/7fdd4b41672c70461cce734fc222b33e7b447fa489c7c4377c95e7e6852d83d69741f307d88ec0cc3b385b41cb4accc6efac3c7c511cd18512e95424f5fa980c - languageName: node - linkType: hard - -"path-key@npm:^3.0.0, path-key@npm:^3.1.0": - version: 3.1.1 - resolution: "path-key@npm:3.1.1" - checksum: 10c0/748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c - languageName: node - linkType: hard - -"path-parse@npm:^1.0.7": - version: 1.0.7 - resolution: "path-parse@npm:1.0.7" - checksum: 10c0/11ce261f9d294cc7a58d6a574b7f1b935842355ec66fba3c3fd79e0f036462eaf07d0aa95bb74ff432f9afef97ce1926c720988c6a7451d8a584930ae7de86e1 - languageName: node - linkType: hard - -"path-scurry@npm:^1.11.1": - version: 1.11.1 - resolution: "path-scurry@npm:1.11.1" - dependencies: - lru-cache: "npm:^10.2.0" - minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" - checksum: 10c0/32a13711a2a505616ae1cc1b5076801e453e7aae6ac40ab55b388bb91b9d0547a52f5aaceff710ea400205f18691120d4431e520afbe4266b836fadede15872d - languageName: node - linkType: hard - -"path-scurry@npm:^2.0.0": - version: 2.0.0 - resolution: "path-scurry@npm:2.0.0" - dependencies: - lru-cache: "npm:^11.0.0" - minipass: "npm:^7.1.2" - checksum: 10c0/3da4adedaa8e7ef8d6dc4f35a0ff8f05a9b4d8365f2b28047752b62d4c1ad73eec21e37b1579ef2d075920157856a3b52ae8309c480a6f1a8bbe06ff8e52b33c - languageName: node - linkType: hard - -"path-to-regexp@npm:3.3.0": - version: 3.3.0 - resolution: "path-to-regexp@npm:3.3.0" - checksum: 10c0/ffa0ebe7088d38d435a8d08b0fe6e8c93ceb2a81a65d4dd1d9a538f52e09d5e3474ed5f553cb3b180d894b0caa10698a68737ab599fd1e56b4663d1a64c9f77b - languageName: node - linkType: hard - -"pdf-parse@npm:^1.1.1": - version: 1.1.1 - resolution: "pdf-parse@npm:1.1.1" - dependencies: - debug: "npm:^3.1.0" - node-ensure: "npm:^0.0.0" - checksum: 10c0/cba2b6ddfbfa73d94ff0cd342cbe8ef2ef0501863ada687eddf99487a4d06766e00fc44525c40cef3b01f04376cb99d5873ab789bd3e2379a28c3ae5377f3298 - languageName: node - linkType: hard - -"picocolors@npm:^1.1.1": - version: 1.1.1 - resolution: "picocolors@npm:1.1.1" - checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 - languageName: node - linkType: hard - -"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.3.1": - version: 2.3.1 - resolution: "picomatch@npm:2.3.1" - checksum: 10c0/26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be - languageName: node - linkType: hard - -"picomatch@npm:^4.0.2": - version: 4.0.2 - resolution: "picomatch@npm:4.0.2" - checksum: 10c0/7c51f3ad2bb42c776f49ebf964c644958158be30d0a510efd5a395e8d49cb5acfed5b82c0c5b365523ce18e6ab85013c9ebe574f60305892ec3fa8eee8304ccc - languageName: node - linkType: hard - -"pidusage@npm:^2.0.21": - version: 2.0.21 - resolution: "pidusage@npm:2.0.21" - dependencies: - safe-buffer: "npm:^5.2.1" - checksum: 10c0/3f8e398c873d21452c88d675a3f6e7b0e00993eb2e2c4d5669c10a977c3c625ca25163c8b14e31d0f2080e82b97d813ce22559bedcd06e073c69241973be8615 - languageName: node - linkType: hard - -"pidusage@npm:~3.0": - version: 3.0.2 - resolution: "pidusage@npm:3.0.2" - dependencies: - safe-buffer: "npm:^5.2.1" - checksum: 10c0/605722ab95e146ebaeb224285023d5c95bd383d460769cb00be774d125d654193b5de806fe9d6e6c9e4994687524f7285fc35bd5c653081c28ee6bd24d11b854 - languageName: node - linkType: hard - -"pify@npm:^3.0.0": - version: 3.0.0 - resolution: "pify@npm:3.0.0" - checksum: 10c0/fead19ed9d801f1b1fcd0638a1ac53eabbb0945bf615f2f8806a8b646565a04a1b0e7ef115c951d225f042cca388fdc1cd3add46d10d1ed6951c20bd2998af10 - languageName: node - linkType: hard - -"pirates@npm:^4.0.7": - version: 4.0.7 - resolution: "pirates@npm:4.0.7" - checksum: 10c0/a51f108dd811beb779d58a76864bbd49e239fa40c7984cd11596c75a121a8cc789f1c8971d8bb15f0dbf9d48b76c05bb62fcbce840f89b688c0fa64b37e8478a - languageName: node - linkType: hard - -"pkg-conf@npm:^2.1.0": - version: 2.1.0 - resolution: "pkg-conf@npm:2.1.0" - dependencies: - find-up: "npm:^2.0.0" - load-json-file: "npm:^4.0.0" - checksum: 10c0/e1474a4f7714ee78204b4a7f2316dec9e59887762bdc126ebd0eb701bbde7c6a6da65c4dc9c2a7c1eaeee49914009bf4a4368f5d9894c596ddf812ff982fdb05 - languageName: node - linkType: hard - -"pkg-dir@npm:^4.2.0": - version: 4.2.0 - resolution: "pkg-dir@npm:4.2.0" - dependencies: - find-up: "npm:^4.0.0" - checksum: 10c0/c56bda7769e04907a88423feb320babaed0711af8c436ce3e56763ab1021ba107c7b0cafb11cde7529f669cfc22bffcaebffb573645cbd63842ea9fb17cd7728 - languageName: node - linkType: hard - -"pm2-axon-rpc@npm:~0.7.0, pm2-axon-rpc@npm:~0.7.1": - version: 0.7.1 - resolution: "pm2-axon-rpc@npm:0.7.1" - dependencies: - debug: "npm:^4.3.1" - checksum: 10c0/e6a7dffc0c3dadb1c3c98c9e9fc7a8c8ba56b3ae2206f490d9d8e69de47595f307549d09478a3f3b521c19e36b6683e50df6128003360cc3d7b5b70fe52a7a93 - languageName: node - linkType: hard - -"pm2-axon@npm:~4.0.1": - version: 4.0.1 - resolution: "pm2-axon@npm:4.0.1" - dependencies: - amp: "npm:~0.3.1" - amp-message: "npm:~0.1.1" - debug: "npm:^4.3.1" - escape-string-regexp: "npm:^4.0.0" - checksum: 10c0/caf6f4c26e3096380d635645762567aed53d64fe08d2b6c009d2260f0bcb22049cd605cfeda5ca242112a984e96310a17bf974ea96efb8e19dfe7e881c697e9d - languageName: node - linkType: hard - -"pm2-deploy@npm:~1.0.2": - version: 1.0.2 - resolution: "pm2-deploy@npm:1.0.2" - dependencies: - run-series: "npm:^1.1.8" - tv4: "npm:^1.3.0" - checksum: 10c0/5a15cf7ec4ce1080be7f2968478a0d368715debc807ea8408b8e013e329fd765b8ca347b09f1faad5287cd15237ce910e1020b9ae39399da1ce9e32e77e5c950 - languageName: node - linkType: hard - -"pm2-multimeter@npm:^0.1.2": - version: 0.1.2 - resolution: "pm2-multimeter@npm:0.1.2" - dependencies: - charm: "npm:~0.1.1" - checksum: 10c0/9702358fd339b9621ccc88689a4cbada314a6a4ac2e2f4a90e6642d5054ae3f4858c4dc06c26792789e3ffb84ae39ad7a978c94421ace023a4ad447b3cc55e9d - languageName: node - linkType: hard - -"pm2-sysmonit@npm:^1.2.8": - version: 1.2.8 - resolution: "pm2-sysmonit@npm:1.2.8" - dependencies: - async: "npm:^3.2.0" - debug: "npm:^4.3.1" - pidusage: "npm:^2.0.21" - systeminformation: "npm:^5.7" - tx2: "npm:~1.0.4" - checksum: 10c0/546601a933f696417074bfbfb1f2976556f28393bf5f5003ea439d8af2507b729be2b818ac8cc607a89087ab12b44457c59d768b4bd6e36a34876853dbc3040d - languageName: node - linkType: hard - -"pm2@npm:^6.0.8": - version: 6.0.8 - resolution: "pm2@npm:6.0.8" - dependencies: - "@pm2/agent": "npm:~2.1.1" - "@pm2/io": "npm:~6.1.0" - "@pm2/js-api": "npm:~0.8.0" - "@pm2/pm2-version-check": "npm:latest" - ansis: "npm:4.0.0-node10" - async: "npm:~3.2.6" - blessed: "npm:0.1.81" - chokidar: "npm:^3.5.3" - cli-tableau: "npm:^2.0.0" - commander: "npm:2.15.1" - croner: "npm:~4.1.92" - dayjs: "npm:~1.11.13" - debug: "npm:^4.3.7" - enquirer: "npm:2.3.6" - eventemitter2: "npm:5.0.1" - fclone: "npm:1.0.11" - js-yaml: "npm:~4.1.0" - mkdirp: "npm:1.0.4" - needle: "npm:2.4.0" - pidusage: "npm:~3.0" - pm2-axon: "npm:~4.0.1" - pm2-axon-rpc: "npm:~0.7.1" - pm2-deploy: "npm:~1.0.2" - pm2-multimeter: "npm:^0.1.2" - pm2-sysmonit: "npm:^1.2.8" - promptly: "npm:^2" - semver: "npm:^7.6.2" - source-map-support: "npm:0.5.21" - sprintf-js: "npm:1.1.2" - vizion: "npm:~2.2.1" - dependenciesMeta: - pm2-sysmonit: - optional: true - bin: - pm2: bin/pm2 - pm2-dev: bin/pm2-dev - pm2-docker: bin/pm2-docker - pm2-runtime: bin/pm2-runtime - checksum: 10c0/5b19ac7b1ed9f300c8549b372b95b825fb00302c402d3f6fc9b9cd31518a38035b12159f0aebc5c103aabe3d4f905716a8456bc2c17e4f5b7aedb280c9ee759c - languageName: node - linkType: hard - -"possible-typed-array-names@npm:^1.0.0": - version: 1.1.0 - resolution: "possible-typed-array-names@npm:1.1.0" - checksum: 10c0/c810983414142071da1d644662ce4caebce890203eb2bc7bf119f37f3fe5796226e117e6cca146b521921fa6531072674174a3325066ac66fce089a53e1e5196 - languageName: node - linkType: hard - -"prelude-ls@npm:^1.2.1": - version: 1.2.1 - resolution: "prelude-ls@npm:1.2.1" - checksum: 10c0/b00d617431e7886c520a6f498a2e14c75ec58f6d93ba48c3b639cf241b54232d90daa05d83a9e9b9fef6baa63cb7e1e4602c2372fea5bc169668401eb127d0cd - languageName: node - linkType: hard - -"pretty-format@npm:30.0.2, pretty-format@npm:^30.0.0": - version: 30.0.2 - resolution: "pretty-format@npm:30.0.2" - dependencies: - "@jest/schemas": "npm:30.0.1" - ansi-styles: "npm:^5.2.0" - react-is: "npm:^18.3.1" - checksum: 10c0/cf542dc2d0be95e2b1c6e3a397a4fc13fce1c9f8feed6b56165c0d23c7a83423abb6b032ed8e3e1b7c1c0709f9b117dd30b5185f107e58f8766616be6de84850 - languageName: node - linkType: hard - -"proc-log@npm:^5.0.0": - version: 5.0.0 - resolution: "proc-log@npm:5.0.0" - checksum: 10c0/bbe5edb944b0ad63387a1d5b1911ae93e05ce8d0f60de1035b218cdcceedfe39dbd2c697853355b70f1a090f8f58fe90da487c85216bf9671f9499d1a897e9e3 - languageName: node - linkType: hard - -"promise-retry@npm:^2.0.1": - version: 2.0.1 - resolution: "promise-retry@npm:2.0.1" - dependencies: - err-code: "npm:^2.0.2" - retry: "npm:^0.12.0" - checksum: 10c0/9c7045a1a2928094b5b9b15336dcd2a7b1c052f674550df63cc3f36cd44028e5080448175b6f6ca32b642de81150f5e7b1a98b728f15cb069f2dd60ac2616b96 - languageName: node - linkType: hard - -"promptly@npm:^2": - version: 2.2.0 - resolution: "promptly@npm:2.2.0" - dependencies: - read: "npm:^1.0.4" - checksum: 10c0/f9af3ddeaa6abf6263588115d86a6cc1c10f902ca338c75a2481e97ffee76cb0c1a3a5e0801365d1f56fe894d6ac5f65db33b44a5fe2b03bca1457d7da35ac09 - languageName: node - linkType: hard - -"proxy-agent@npm:~6.4.0": - version: 6.4.0 - resolution: "proxy-agent@npm:6.4.0" - dependencies: - agent-base: "npm:^7.0.2" - debug: "npm:^4.3.4" - http-proxy-agent: "npm:^7.0.1" - https-proxy-agent: "npm:^7.0.3" - lru-cache: "npm:^7.14.1" - pac-proxy-agent: "npm:^7.0.1" - proxy-from-env: "npm:^1.1.0" - socks-proxy-agent: "npm:^8.0.2" - checksum: 10c0/0c5b85cacf67eec9d8add025a5e577b2c895672e4187079ec41b0ee2a6dacd90e69a837936cb3ac141dd92b05b50a325b9bfe86ab0dc3b904011aa3bcf406fc0 - languageName: node - linkType: hard - -"proxy-from-env@npm:^1.1.0": - version: 1.1.0 - resolution: "proxy-from-env@npm:1.1.0" - checksum: 10c0/fe7dd8b1bdbbbea18d1459107729c3e4a2243ca870d26d34c2c1bcd3e4425b7bcc5112362df2d93cc7fb9746f6142b5e272fd1cc5c86ddf8580175186f6ad42b - languageName: node - linkType: hard - -"punycode@npm:^2.1.0": - version: 2.3.1 - resolution: "punycode@npm:2.3.1" - checksum: 10c0/14f76a8206bc3464f794fb2e3d3cc665ae416c01893ad7a02b23766eb07159144ee612ad67af5e84fa4479ccfe67678c4feb126b0485651b302babf66f04f9e9 - languageName: node - linkType: hard - -"pure-rand@npm:^7.0.0": - version: 7.0.1 - resolution: "pure-rand@npm:7.0.1" - checksum: 10c0/9cade41030f5ec95f5d55a11a71404cd6f46b69becaad892097cd7f58e2c6248cd0a933349ca7d21336ab629f1da42ffe899699b671bc4651600eaf6e57f837e - languageName: node - linkType: hard - -"queue-microtask@npm:^1.2.2": - version: 1.2.3 - resolution: "queue-microtask@npm:1.2.3" - checksum: 10c0/900a93d3cdae3acd7d16f642c29a642aea32c2026446151f0778c62ac089d4b8e6c986811076e1ae180a694cedf077d453a11b58ff0a865629a4f82ab558e102 - languageName: node - linkType: hard - -"range-parser@npm:1.2.0": - version: 1.2.0 - resolution: "range-parser@npm:1.2.0" - checksum: 10c0/c7aef4f6588eb974c475649c157f197d07437d8c6c8ff7e36280a141463fb5ab7a45918417334ebd7b665c6b8321cf31c763f7631dd5f5db9372249261b8b02a - languageName: node - linkType: hard - -"rc@npm:^1.0.1, rc@npm:^1.1.6": - version: 1.2.8 - resolution: "rc@npm:1.2.8" - dependencies: - deep-extend: "npm:^0.6.0" - ini: "npm:~1.3.0" - minimist: "npm:^1.2.0" - strip-json-comments: "npm:~2.0.1" - bin: - rc: ./cli.js - checksum: 10c0/24a07653150f0d9ac7168e52943cc3cb4b7a22c0e43c7dff3219977c2fdca5a2760a304a029c20811a0e79d351f57d46c9bde216193a0f73978496afc2b85b15 - languageName: node - linkType: hard - -"react-is@npm:^18.3.1": - version: 18.3.1 - resolution: "react-is@npm:18.3.1" - checksum: 10c0/f2f1e60010c683479e74c63f96b09fb41603527cd131a9959e2aee1e5a8b0caf270b365e5ca77d4a6b18aae659b60a86150bb3979073528877029b35aecd2072 - languageName: node - linkType: hard - -"read@npm:^1.0.4": - version: 1.0.7 - resolution: "read@npm:1.0.7" - dependencies: - mute-stream: "npm:~0.0.4" - checksum: 10c0/443533f05d5bb11b36ef1c6d625aae4e2ced8967e93cf546f35aa77b4eb6bd157f4256619e446bae43467f8f6619c7bc5c76983348dffaf36afedf4224f46216 - languageName: node - linkType: hard - -"readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0": - version: 3.6.0 - resolution: "readable-stream@npm:3.6.0" - dependencies: - inherits: "npm:^2.0.3" - string_decoder: "npm:^1.1.1" - util-deprecate: "npm:^1.0.1" - checksum: 10c0/937bedd29ac8a68331666291922bea892fa2be1a33269e582de9f844a2002f146cf831e39cd49fe6a378d3f0c27358f259ed0e20d20f0bdc6a3f8fc21fce42dc - languageName: node - linkType: hard - -"readdirp@npm:~3.6.0": - version: 3.6.0 - resolution: "readdirp@npm:3.6.0" - dependencies: - picomatch: "npm:^2.2.1" - checksum: 10c0/6fa848cf63d1b82ab4e985f4cf72bd55b7dcfd8e0a376905804e48c3634b7e749170940ba77b32804d5fe93b3cc521aa95a8d7e7d725f830da6d93f3669ce66b - languageName: node - linkType: hard - -"readline@npm:^1.3.0": - version: 1.3.0 - resolution: "readline@npm:1.3.0" - checksum: 10c0/7404c9edc3fd29430221ef1830867c8d87e50612e4ce70f84ecd55686f7db1c81d67c6a4dcb407839f4c459ad05dd34524a2c7a97681e91878367c68d0e38665 - languageName: node - linkType: hard - -"registry-auth-token@npm:3.3.2": - version: 3.3.2 - resolution: "registry-auth-token@npm:3.3.2" - dependencies: - rc: "npm:^1.1.6" - safe-buffer: "npm:^5.0.1" - checksum: 10c0/934b5d504ec6d94d78672dc5e74646c52793e74a6e400c1cffc78838bbb12c5f45e3ef3edba506f3295db794d4dda76f924f2948d48fe1f8e83b6500b0ba53c5 - languageName: node - linkType: hard - -"registry-url@npm:3.1.0": - version: 3.1.0 - resolution: "registry-url@npm:3.1.0" - dependencies: - rc: "npm:^1.0.1" - checksum: 10c0/345cf9638f99d95863d92800b3f595ac312c19d6865595e499fbeb33fcda04021a0dbdafbb5e61a838a89a558bc239d78752a1f90eb68cf53fdf0d91da816a7c - languageName: node - linkType: hard - -"require-directory@npm:^2.1.1": - version: 2.1.1 - resolution: "require-directory@npm:2.1.1" - checksum: 10c0/83aa76a7bc1531f68d92c75a2ca2f54f1b01463cb566cf3fbc787d0de8be30c9dbc211d1d46be3497dac5785fe296f2dd11d531945ac29730643357978966e99 - languageName: node - linkType: hard - -"require-from-string@npm:^2.0.2": - version: 2.0.2 - resolution: "require-from-string@npm:2.0.2" - checksum: 10c0/aaa267e0c5b022fc5fd4eef49d8285086b15f2a1c54b28240fdf03599cbd9c26049fee3eab894f2e1f6ca65e513b030a7c264201e3f005601e80c49fb2937ce2 - languageName: node - linkType: hard - -"require-in-the-middle@npm:^5.0.0": - version: 5.2.0 - resolution: "require-in-the-middle@npm:5.2.0" - dependencies: - debug: "npm:^4.1.1" - module-details-from-path: "npm:^1.0.3" - resolve: "npm:^1.22.1" - checksum: 10c0/6721975872907c11a7bbda676c970b4fcc4b9e939321934920c86aa2d371514cd31bf06947737e8ac0cb41ae5cca24ce257f33b49ed5a5dd6fec14272db3907e - languageName: node - linkType: hard - -"resolve-cwd@npm:^3.0.0": - version: 3.0.0 - resolution: "resolve-cwd@npm:3.0.0" - dependencies: - resolve-from: "npm:^5.0.0" - checksum: 10c0/e608a3ebd15356264653c32d7ecbc8fd702f94c6703ea4ac2fb81d9c359180cba0ae2e6b71faa446631ed6145454d5a56b227efc33a2d40638ac13f8beb20ee4 - languageName: node - linkType: hard - -"resolve-from@npm:^4.0.0": - version: 4.0.0 - resolution: "resolve-from@npm:4.0.0" - checksum: 10c0/8408eec31a3112ef96e3746c37be7d64020cda07c03a920f5024e77290a218ea758b26ca9529fd7b1ad283947f34b2291c1c0f6aa0ed34acfdda9c6014c8d190 - languageName: node - linkType: hard - -"resolve-from@npm:^5.0.0": - version: 5.0.0 - resolution: "resolve-from@npm:5.0.0" - checksum: 10c0/b21cb7f1fb746de8107b9febab60095187781137fd803e6a59a76d421444b1531b641bba5857f5dc011974d8a5c635d61cec49e6bd3b7fc20e01f0fafc4efbf2 - languageName: node - linkType: hard - -"resolve-pkg-maps@npm:^1.0.0": - version: 1.0.0 - resolution: "resolve-pkg-maps@npm:1.0.0" - checksum: 10c0/fb8f7bbe2ca281a73b7ef423a1cbc786fb244bd7a95cbe5c3fba25b27d327150beca8ba02f622baea65919a57e061eb5005204daa5f93ed590d9b77463a567ab - languageName: node - linkType: hard - -"resolve@npm:^1.22.1": - version: 1.22.10 - resolution: "resolve@npm:1.22.10" - dependencies: - is-core-module: "npm:^2.16.0" - path-parse: "npm:^1.0.7" - supports-preserve-symlinks-flag: "npm:^1.0.0" - bin: - resolve: bin/resolve - checksum: 10c0/8967e1f4e2cc40f79b7e080b4582b9a8c5ee36ffb46041dccb20e6461161adf69f843b43067b4a375de926a2cd669157e29a29578191def399dd5ef89a1b5203 - languageName: node - linkType: hard - -"resolve@patch:resolve@npm%3A^1.22.1#optional!builtin": - version: 1.22.10 - resolution: "resolve@patch:resolve@npm%3A1.22.10#optional!builtin::version=1.22.10&hash=c3c19d" - dependencies: - is-core-module: "npm:^2.16.0" - path-parse: "npm:^1.0.7" - supports-preserve-symlinks-flag: "npm:^1.0.0" - bin: - resolve: bin/resolve - checksum: 10c0/52a4e505bbfc7925ac8f4cd91fd8c4e096b6a89728b9f46861d3b405ac9a1ccf4dcbf8befb4e89a2e11370dacd0160918163885cbc669369590f2f31f4c58939 - languageName: node - linkType: hard - -"retry@npm:^0.12.0": - version: 0.12.0 - resolution: "retry@npm:0.12.0" - checksum: 10c0/59933e8501727ba13ad73ef4a04d5280b3717fd650408460c987392efe9d7be2040778ed8ebe933c5cbd63da3dcc37919c141ef8af0a54a6e4fca5a2af177bfe - languageName: node - linkType: hard - -"reusify@npm:^1.0.4": - version: 1.1.0 - resolution: "reusify@npm:1.1.0" - checksum: 10c0/4eff0d4a5f9383566c7d7ec437b671cc51b25963bd61bf127c3f3d3f68e44a026d99b8d2f1ad344afff8d278a8fe70a8ea092650a716d22287e8bef7126bb2fa - languageName: node - linkType: hard - -"run-async@npm:^3.0.0": - version: 3.0.0 - resolution: "run-async@npm:3.0.0" - checksum: 10c0/b18b562ae37c3020083dcaae29642e4cc360c824fbfb6b7d50d809a9d5227bb986152d09310255842c8dce40526e82ca768f02f00806c91ba92a8dfa6159cb85 - languageName: node - linkType: hard - -"run-parallel@npm:^1.1.9": - version: 1.2.0 - resolution: "run-parallel@npm:1.2.0" - dependencies: - queue-microtask: "npm:^1.2.2" - checksum: 10c0/200b5ab25b5b8b7113f9901bfe3afc347e19bb7475b267d55ad0eb86a62a46d77510cb0f232507c9e5d497ebda569a08a9867d0d14f57a82ad5564d991588b39 - languageName: node - linkType: hard - -"run-script-os@npm:^1.1.6": - version: 1.1.6 - resolution: "run-script-os@npm:1.1.6" - bin: - run-os: index.js - run-script-os: index.js - checksum: 10c0/620e240a650c666bb8e3f5437680d88c522366e6c68d4867300caf6cad010c85ff36a016de2c71010debaf10e968966b2c6aaa8816bab8298381e5620d41d8aa - languageName: node - linkType: hard - -"run-series@npm:^1.1.8": - version: 1.1.9 - resolution: "run-series@npm:1.1.9" - checksum: 10c0/45338061510d70045d78c80cc2f2a867d307870ecd87b200444c9027fdf3bd881840ed2eb8ee6ca3e568c6a581bd9e56a2bd26351b12b6760a6fd1ded04d318c - languageName: node - linkType: hard - -"rxjs@npm:^7.2.0, rxjs@npm:^7.8.2": - version: 7.8.2 - resolution: "rxjs@npm:7.8.2" - dependencies: - tslib: "npm:^2.1.0" - checksum: 10c0/1fcd33d2066ada98ba8f21fcbbcaee9f0b271de1d38dc7f4e256bfbc6ffcdde68c8bfb69093de7eeb46f24b1fb820620bf0223706cff26b4ab99a7ff7b2e2c45 - languageName: node - linkType: hard - -"safe-buffer@npm:5.1.2, safe-buffer@npm:^5.0.1": - version: 5.1.2 - resolution: "safe-buffer@npm:5.1.2" - checksum: 10c0/780ba6b5d99cc9a40f7b951d47152297d0e260f0df01472a1b99d4889679a4b94a13d644f7dbc4f022572f09ae9005fa2fbb93bbbd83643316f365a3e9a45b21 - languageName: node - linkType: hard - -"safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0": - version: 5.2.1 - resolution: "safe-buffer@npm:5.2.1" - checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3 - languageName: node - linkType: hard - -"safe-regex-test@npm:^1.1.0": - version: 1.1.0 - resolution: "safe-regex-test@npm:1.1.0" - dependencies: - call-bound: "npm:^1.0.2" - es-errors: "npm:^1.3.0" - is-regex: "npm:^1.2.1" - checksum: 10c0/f2c25281bbe5d39cddbbce7f86fca5ea9b3ce3354ea6cd7c81c31b006a5a9fff4286acc5450a3b9122c56c33eba69c56b9131ad751457b2b4a585825e6a10665 - languageName: node - linkType: hard - -"safe-stable-stringify@npm:^2.3.1": - version: 2.3.1 - resolution: "safe-stable-stringify@npm:2.3.1" - checksum: 10c0/5bdf3bae0a628c767cf0fad95a8245802ef3976d5d29709d5ffbbfecde49ad5f55a1c797cf438f1c4cb7d90ab3e88102cca8148829e3a12fe0dc0c36f6445031 - languageName: node - linkType: hard - -"safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0": - version: 2.1.2 - resolution: "safer-buffer@npm:2.1.2" - checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 - languageName: node - linkType: hard - -"sax@npm:^1.2.4": - version: 1.2.4 - resolution: "sax@npm:1.2.4" - checksum: 10c0/6e9b05ff443ee5e5096ce92d31c0740a20d33002fad714ebcb8fc7a664d9ee159103ebe8f7aef0a1f7c5ecacdd01f177f510dff95611c589399baf76437d3fe3 - languageName: node - linkType: hard - -"semver@npm:>= 5 < 6": - version: 5.7.2 - resolution: "semver@npm:5.7.2" - bin: - semver: bin/semver - checksum: 10c0/e4cf10f86f168db772ae95d86ba65b3fd6c5967c94d97c708ccb463b778c2ee53b914cd7167620950fc07faf5a564e6efe903836639e512a1aa15fbc9667fa25 - languageName: node - linkType: hard - -"semver@npm:^6.3.1": - version: 6.3.1 - resolution: "semver@npm:6.3.1" - bin: - semver: bin/semver.js - checksum: 10c0/e3d79b609071caa78bcb6ce2ad81c7966a46a7431d9d58b8800cfa9cb6a63699b3899a0e4bcce36167a284578212d9ae6942b6929ba4aa5015c079a67751d42d - languageName: node - linkType: hard - -"semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.2, semver@npm:^7.7.2": - version: 7.7.2 - resolution: "semver@npm:7.7.2" - bin: - semver: bin/semver.js - checksum: 10c0/aca305edfbf2383c22571cb7714f48cadc7ac95371b4b52362fb8eeffdfbc0de0669368b82b2b15978f8848f01d7114da65697e56cd8c37b0dab8c58e543f9ea - languageName: node - linkType: hard - -"semver@npm:~7.5.0, semver@npm:~7.5.4": - version: 7.5.4 - resolution: "semver@npm:7.5.4" - dependencies: - lru-cache: "npm:^6.0.0" - bin: - semver: bin/semver.js - checksum: 10c0/5160b06975a38b11c1ab55950cb5b8a23db78df88275d3d8a42ccf1f29e55112ac995b3a26a522c36e3b5f76b0445f1eef70d696b8c7862a2b4303d7b0e7609e - languageName: node - linkType: hard - -"serve-handler@npm:6.1.6": - version: 6.1.6 - resolution: "serve-handler@npm:6.1.6" - dependencies: - bytes: "npm:3.0.0" - content-disposition: "npm:0.5.2" - mime-types: "npm:2.1.18" - minimatch: "npm:3.1.2" - path-is-inside: "npm:1.0.2" - path-to-regexp: "npm:3.3.0" - range-parser: "npm:1.2.0" - checksum: 10c0/1e1cb6bbc51ee32bc1505f2e0605bdc2e96605c522277c977b67f83be9d66bd1eec8604388714a4d728e036d86b629bc9aec02120ea030d3d2c3899d44696503 - languageName: node - linkType: hard - -"serve@npm:^14.2.4": - version: 14.2.4 - resolution: "serve@npm:14.2.4" - dependencies: - "@zeit/schemas": "npm:2.36.0" - ajv: "npm:8.12.0" - arg: "npm:5.0.2" - boxen: "npm:7.0.0" - chalk: "npm:5.0.1" - chalk-template: "npm:0.4.0" - clipboardy: "npm:3.0.0" - compression: "npm:1.7.4" - is-port-reachable: "npm:4.0.0" - serve-handler: "npm:6.1.6" - update-check: "npm:1.5.4" - bin: - serve: build/main.js - checksum: 10c0/93abecd6214228d529065040f7c0cbe541c1cc321c6a94b8a968f45a519bd9c46a9fd5e45a9b24a1f5736c5b547b8fa60d5414ebc78f870e29431b64165c1d06 - languageName: node - linkType: hard - -"set-function-length@npm:^1.2.2": - version: 1.2.2 - resolution: "set-function-length@npm:1.2.2" - dependencies: - define-data-property: "npm:^1.1.4" - es-errors: "npm:^1.3.0" - function-bind: "npm:^1.1.2" - get-intrinsic: "npm:^1.2.4" - gopd: "npm:^1.0.1" - has-property-descriptors: "npm:^1.0.2" - checksum: 10c0/82850e62f412a258b71e123d4ed3873fa9377c216809551192bb6769329340176f109c2eeae8c22a8d386c76739855f78e8716515c818bcaef384b51110f0f3c - languageName: node - linkType: hard - -"shebang-command@npm:^2.0.0": - version: 2.0.0 - resolution: "shebang-command@npm:2.0.0" - dependencies: - shebang-regex: "npm:^3.0.0" - checksum: 10c0/a41692e7d89a553ef21d324a5cceb5f686d1f3c040759c50aab69688634688c5c327f26f3ecf7001ebfd78c01f3c7c0a11a7c8bfd0a8bc9f6240d4f40b224e4e - languageName: node - linkType: hard - -"shebang-regex@npm:^3.0.0": - version: 3.0.0 - resolution: "shebang-regex@npm:3.0.0" - checksum: 10c0/1dbed0726dd0e1152a92696c76c7f06084eb32a90f0528d11acd764043aacf76994b2fb30aa1291a21bd019d6699164d048286309a278855ee7bec06cf6fb690 - languageName: node - linkType: hard - -"shimmer@npm:^1.2.0": - version: 1.2.1 - resolution: "shimmer@npm:1.2.1" - checksum: 10c0/ae8b27c389db2a00acfc8da90240f11577685a8f3e40008f826a3bea8b4f3b3ecd305c26be024b4a0fd3b123d132c1569d6e238097960a9a543b6c60760fb46a - languageName: node - linkType: hard - -"signal-exit@npm:^3.0.3": - version: 3.0.7 - resolution: "signal-exit@npm:3.0.7" - checksum: 10c0/25d272fa73e146048565e08f3309d5b942c1979a6f4a58a8c59d5fa299728e9c2fcd1a759ec870863b1fd38653670240cd420dad2ad9330c71f36608a6a1c912 - languageName: node - linkType: hard - -"signal-exit@npm:^4.0.1, signal-exit@npm:^4.1.0": - version: 4.1.0 - resolution: "signal-exit@npm:4.1.0" - checksum: 10c0/41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83 - languageName: node - linkType: hard - -"signale@npm:^1.4.0": - version: 1.4.0 - resolution: "signale@npm:1.4.0" - dependencies: - chalk: "npm:^2.3.2" - figures: "npm:^2.0.0" - pkg-conf: "npm:^2.1.0" - checksum: 10c0/3b637421368a30805da3948f82350cb9959ddfb19073f44609495384b98baba1c62b1c5c094db57000836c8bc84c6c05c979aa7e072ceeaaf0032d7991b329c7 - languageName: node - linkType: hard - -"simple-swizzle@npm:^0.2.2": - version: 0.2.2 - resolution: "simple-swizzle@npm:0.2.2" - dependencies: - is-arrayish: "npm:^0.3.1" - checksum: 10c0/df5e4662a8c750bdba69af4e8263c5d96fe4cd0f9fe4bdfa3cbdeb45d2e869dff640beaaeb1ef0e99db4d8d2ec92f85508c269f50c972174851bc1ae5bd64308 - languageName: node - linkType: hard - -"skip-postinstall@npm:^1.0.0": - version: 1.0.0 - resolution: "skip-postinstall@npm:1.0.0" - bin: - skip-postinstall: index.js - checksum: 10c0/57104ba3e856112e681df26363a419e9a6bdadcb174c8f9984cea1b34eee94573aa612bfc12b76414d3a3b26cc7298f677215d901ea39733c59a6dbb04f71983 - languageName: node - linkType: hard - -"slash@npm:^3.0.0": - version: 3.0.0 - resolution: "slash@npm:3.0.0" - checksum: 10c0/e18488c6a42bdfd4ac5be85b2ced3ccd0224773baae6ad42cfbb9ec74fc07f9fa8396bd35ee638084ead7a2a0818eb5e7151111544d4731ce843019dab4be47b - languageName: node - linkType: hard - -"smart-buffer@npm:^4.2.0": - version: 4.2.0 - resolution: "smart-buffer@npm:4.2.0" - checksum: 10c0/a16775323e1404dd43fabafe7460be13a471e021637bc7889468eb45ce6a6b207261f454e4e530a19500cc962c4cc5348583520843b363f4193cee5c00e1e539 - languageName: node - linkType: hard - -"socks-proxy-agent@npm:^8.0.2, socks-proxy-agent@npm:^8.0.3, socks-proxy-agent@npm:^8.0.5": - version: 8.0.5 - resolution: "socks-proxy-agent@npm:8.0.5" - dependencies: - agent-base: "npm:^7.1.2" - debug: "npm:^4.3.4" - socks: "npm:^2.8.3" - checksum: 10c0/5d2c6cecba6821389aabf18728325730504bf9bb1d9e342e7987a5d13badd7a98838cc9a55b8ed3cb866ad37cc23e1086f09c4d72d93105ce9dfe76330e9d2a6 - languageName: node - linkType: hard - -"socks@npm:^2.8.3": - version: 2.8.3 - resolution: "socks@npm:2.8.3" - dependencies: - ip-address: "npm:^9.0.5" - smart-buffer: "npm:^4.2.0" - checksum: 10c0/d54a52bf9325165770b674a67241143a3d8b4e4c8884560c4e0e078aace2a728dffc7f70150660f51b85797c4e1a3b82f9b7aa25e0a0ceae1a243365da5c51a7 - languageName: node - linkType: hard - -"source-map-support@npm:0.5.13": - version: 0.5.13 - resolution: "source-map-support@npm:0.5.13" - dependencies: - buffer-from: "npm:^1.0.0" - source-map: "npm:^0.6.0" - checksum: 10c0/137539f8c453fa0f496ea42049ab5da4569f96781f6ac8e5bfda26937be9494f4e8891f523c5f98f0e85f71b35d74127a00c46f83f6a4f54672b58d53202565e - languageName: node - linkType: hard - -"source-map-support@npm:0.5.21": - version: 0.5.21 - resolution: "source-map-support@npm:0.5.21" - dependencies: - buffer-from: "npm:^1.0.0" - source-map: "npm:^0.6.0" - checksum: 10c0/9ee09942f415e0f721d6daad3917ec1516af746a8120bba7bb56278707a37f1eb8642bde456e98454b8a885023af81a16e646869975f06afc1a711fb90484e7d - languageName: node - linkType: hard - -"source-map@npm:^0.6.0, source-map@npm:~0.6.1": - version: 0.6.1 - resolution: "source-map@npm:0.6.1" - checksum: 10c0/ab55398007c5e5532957cb0beee2368529618ac0ab372d789806f5718123cc4367d57de3904b4e6a4170eb5a0b0f41373066d02ca0735a0c4d75c7d328d3e011 - languageName: node - linkType: hard - -"sprintf-js@npm:1.1.2": - version: 1.1.2 - resolution: "sprintf-js@npm:1.1.2" - checksum: 10c0/6cc8382f746348bd64b31bc5c99d8ebda7efff716025c41bf501e0e8be4f6744a9fa507e18513554753553d0bcb57fd5fc8dc8c42f94f8008127a52a2c544d21 - languageName: node - linkType: hard - -"sprintf-js@npm:^1.1.3": - version: 1.1.3 - resolution: "sprintf-js@npm:1.1.3" - checksum: 10c0/09270dc4f30d479e666aee820eacd9e464215cdff53848b443964202bf4051490538e5dd1b42e1a65cf7296916ca17640aebf63dae9812749c7542ee5f288dec - languageName: node - linkType: hard - -"sprintf-js@npm:~1.0.2": - version: 1.0.3 - resolution: "sprintf-js@npm:1.0.3" - checksum: 10c0/ecadcfe4c771890140da5023d43e190b7566d9cf8b2d238600f31bec0fc653f328da4450eb04bd59a431771a8e9cc0e118f0aa3974b683a4981b4e07abc2a5bb - languageName: node - linkType: hard - -"srcset@npm:^5.0.1": - version: 5.0.1 - resolution: "srcset@npm:5.0.1" - checksum: 10c0/411e5c8b9db0123f113d374e47a63c08cd3c6f2d43ac70756e5c1aafe64d8b9c9f364f1c1e8e980d81bd6c9a7b1e66138b661989bf032efa1eacd373482a76f6 - languageName: node - linkType: hard - -"ssri@npm:^12.0.0": - version: 12.0.0 - resolution: "ssri@npm:12.0.0" - dependencies: - minipass: "npm:^7.0.3" - checksum: 10c0/caddd5f544b2006e88fa6b0124d8d7b28208b83c72d7672d5ade44d794525d23b540f3396108c4eb9280dcb7c01f0bef50682f5b4b2c34291f7c5e211fd1417d - languageName: node - linkType: hard - -"stack-trace@npm:0.0.x": - version: 0.0.10 - resolution: "stack-trace@npm:0.0.10" - checksum: 10c0/9ff3dabfad4049b635a85456f927a075c9d0c210e3ea336412d18220b2a86cbb9b13ec46d6c37b70a302a4ea4d49e30e5d4944dd60ae784073f1cde778ac8f4b - languageName: node - linkType: hard - -"stack-utils@npm:^2.0.6": - version: 2.0.6 - resolution: "stack-utils@npm:2.0.6" - dependencies: - escape-string-regexp: "npm:^2.0.0" - checksum: 10c0/651c9f87667e077584bbe848acaecc6049bc71979f1e9a46c7b920cad4431c388df0f51b8ad7cfd6eed3db97a2878d0fc8b3122979439ea8bac29c61c95eec8a - languageName: node - linkType: hard - -"string-length@npm:^4.0.2": - version: 4.0.2 - resolution: "string-length@npm:4.0.2" - dependencies: - char-regex: "npm:^1.0.2" - strip-ansi: "npm:^6.0.0" - checksum: 10c0/1cd77409c3d7db7bc59406f6bcc9ef0783671dcbabb23597a1177c166906ef2ee7c8290f78cae73a8aec858768f189d2cb417797df5e15ec4eb5e16b3346340c - languageName: node - linkType: hard - -"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": - version: 4.2.3 - resolution: "string-width@npm:4.2.3" - dependencies: - emoji-regex: "npm:^8.0.0" - is-fullwidth-code-point: "npm:^3.0.0" - strip-ansi: "npm:^6.0.1" - checksum: 10c0/1e525e92e5eae0afd7454086eed9c818ee84374bb80328fc41217ae72ff5f065ef1c9d7f72da41de40c75fa8bb3dee63d92373fd492c84260a552c636392a47b - languageName: node - linkType: hard - -"string-width@npm:^5.0.1, string-width@npm:^5.1.2": - version: 5.1.2 - resolution: "string-width@npm:5.1.2" - dependencies: - eastasianwidth: "npm:^0.2.0" - emoji-regex: "npm:^9.2.2" - strip-ansi: "npm:^7.0.1" - checksum: 10c0/ab9c4264443d35b8b923cbdd513a089a60de339216d3b0ed3be3ba57d6880e1a192b70ae17225f764d7adbf5994e9bb8df253a944736c15a0240eff553c678ca - languageName: node - linkType: hard - -"string_decoder@npm:^1.1.1": - version: 1.3.0 - resolution: "string_decoder@npm:1.3.0" - dependencies: - safe-buffer: "npm:~5.2.0" - checksum: 10c0/810614ddb030e271cd591935dcd5956b2410dd079d64ff92a1844d6b7588bf992b3e1b69b0f4d34a3e06e0bd73046ac646b5264c1987b20d0601f81ef35d731d - languageName: node - linkType: hard - -"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": - version: 6.0.1 - resolution: "strip-ansi@npm:6.0.1" - dependencies: - ansi-regex: "npm:^5.0.1" - checksum: 10c0/1ae5f212a126fe5b167707f716942490e3933085a5ff6c008ab97ab2f272c8025d3aa218b7bd6ab25729ca20cc81cddb252102f8751e13482a5199e873680952 - languageName: node - linkType: hard - -"strip-ansi@npm:^7.0.1": - version: 7.1.0 - resolution: "strip-ansi@npm:7.1.0" - dependencies: - ansi-regex: "npm:^6.0.1" - checksum: 10c0/a198c3762e8832505328cbf9e8c8381de14a4fa50a4f9b2160138158ea88c0f5549fb50cb13c651c3088f47e63a108b34622ec18c0499b6c8c3a5ddf6b305ac4 - languageName: node - linkType: hard - -"strip-bom@npm:^3.0.0": - version: 3.0.0 - resolution: "strip-bom@npm:3.0.0" - checksum: 10c0/51201f50e021ef16672593d7434ca239441b7b760e905d9f33df6e4f3954ff54ec0e0a06f100d028af0982d6f25c35cd5cda2ce34eaebccd0250b8befb90d8f1 - languageName: node - linkType: hard - -"strip-bom@npm:^4.0.0": - version: 4.0.0 - resolution: "strip-bom@npm:4.0.0" - checksum: 10c0/26abad1172d6bc48985ab9a5f96c21e440f6e7e476686de49be813b5a59b3566dccb5c525b831ec54fe348283b47f3ffb8e080bc3f965fde12e84df23f6bb7ef - languageName: node - linkType: hard - -"strip-final-newline@npm:^2.0.0": - version: 2.0.0 - resolution: "strip-final-newline@npm:2.0.0" - checksum: 10c0/bddf8ccd47acd85c0e09ad7375409d81653f645fda13227a9d459642277c253d877b68f2e5e4d819fe75733b0e626bac7e954c04f3236f6d196f79c94fa4a96f - languageName: node - linkType: hard - -"strip-json-comments@npm:^3.1.1": - version: 3.1.1 - resolution: "strip-json-comments@npm:3.1.1" - checksum: 10c0/9681a6257b925a7fa0f285851c0e613cc934a50661fa7bb41ca9cbbff89686bb4a0ee366e6ecedc4daafd01e83eee0720111ab294366fe7c185e935475ebcecd - languageName: node - linkType: hard - -"strip-json-comments@npm:~2.0.1": - version: 2.0.1 - resolution: "strip-json-comments@npm:2.0.1" - checksum: 10c0/b509231cbdee45064ff4f9fd73609e2bcc4e84a4d508e9dd0f31f70356473fde18abfb5838c17d56fb236f5a06b102ef115438de0600b749e818a35fbbc48c43 - languageName: node - linkType: hard - -"supports-color@npm:^5.3.0": - version: 5.5.0 - resolution: "supports-color@npm:5.5.0" - dependencies: - has-flag: "npm:^3.0.0" - checksum: 10c0/6ae5ff319bfbb021f8a86da8ea1f8db52fac8bd4d499492e30ec17095b58af11f0c55f8577390a749b1c4dde691b6a0315dab78f5f54c9b3d83f8fb5905c1c05 - languageName: node - linkType: hard - -"supports-color@npm:^7.1.0": - version: 7.2.0 - resolution: "supports-color@npm:7.2.0" - dependencies: - has-flag: "npm:^4.0.0" - checksum: 10c0/afb4c88521b8b136b5f5f95160c98dee7243dc79d5432db7efc27efb219385bbc7d9427398e43dd6cc730a0f87d5085ce1652af7efbe391327bc0a7d0f7fc124 - languageName: node - linkType: hard - -"supports-color@npm:^8.1.1": - version: 8.1.1 - resolution: "supports-color@npm:8.1.1" - dependencies: - has-flag: "npm:^4.0.0" - checksum: 10c0/ea1d3c275dd604c974670f63943ed9bd83623edc102430c05adb8efc56ba492746b6e95386e7831b872ec3807fd89dd8eb43f735195f37b5ec343e4234cc7e89 - languageName: node - linkType: hard - -"supports-preserve-symlinks-flag@npm:^1.0.0": - version: 1.0.0 - resolution: "supports-preserve-symlinks-flag@npm:1.0.0" - checksum: 10c0/6c4032340701a9950865f7ae8ef38578d8d7053f5e10518076e6554a9381fa91bd9c6850193695c141f32b21f979c985db07265a758867bac95de05f7d8aeb39 - languageName: node - linkType: hard - -"synckit@npm:^0.11.8": - version: 0.11.8 - resolution: "synckit@npm:0.11.8" - dependencies: - "@pkgr/core": "npm:^0.2.4" - checksum: 10c0/a1de5131ee527512afcaafceb2399b2f3e63678e56b831e1cb2dc7019c972a8b654703a3b94ef4166868f87eb984ea252b467c9d9e486b018ec2e6a55c24dfd8 - languageName: node - linkType: hard - -"systeminformation@npm:^5.7": - version: 5.25.11 - resolution: "systeminformation@npm:5.25.11" - bin: - systeminformation: lib/cli.js - conditions: (os=darwin | os=linux | os=win32 | os=freebsd | os=openbsd | os=netbsd | os=sunos | os=android) - languageName: node - linkType: hard - -"table2array@npm:^0.0.2": - version: 0.0.2 - resolution: "table2array@npm:0.0.2" - dependencies: - cheerio: "npm:^1.0.0-rc.12" - checksum: 10c0/1310119c43ed39e4e1c7e32bab3f7b9f26e875d084c17d693a08bfbb3e7764fd288fdc2bf86beb91ac7ddfbdf5d54a891d1ab5f88de9eb1b7518ebd830657156 - languageName: node - linkType: hard - -"tabletojson@npm:^4.1.6": - version: 4.1.6 - resolution: "tabletojson@npm:4.1.6" - dependencies: - cheerio: "npm:^1.0.0" - checksum: 10c0/42584d4486c49daea8f0f1ed6cbbb084570cf0cfd4f2460602c039d351ec6ba38ecc6da1022a83b2341f05dc33e0158dfc6e6a6c75aeaa1131d5280caa8e9e7e - languageName: node - linkType: hard - -"tar@npm:^7.4.3": - version: 7.4.3 - resolution: "tar@npm:7.4.3" - dependencies: - "@isaacs/fs-minipass": "npm:^4.0.0" - chownr: "npm:^3.0.0" - minipass: "npm:^7.1.2" - minizlib: "npm:^3.0.1" - mkdirp: "npm:^3.0.1" - yallist: "npm:^5.0.0" - checksum: 10c0/d4679609bb2a9b48eeaf84632b6d844128d2412b95b6de07d53d8ee8baf4ca0857c9331dfa510390a0727b550fd543d4d1a10995ad86cdf078423fbb8d99831d - languageName: node - linkType: hard - -"test-exclude@npm:^6.0.0": - version: 6.0.0 - resolution: "test-exclude@npm:6.0.0" - dependencies: - "@istanbuljs/schema": "npm:^0.1.2" - glob: "npm:^7.1.4" - minimatch: "npm:^3.0.4" - checksum: 10c0/019d33d81adff3f9f1bfcff18125fb2d3c65564f437d9be539270ee74b994986abb8260c7c2ce90e8f30162178b09dbbce33c6389273afac4f36069c48521f57 - languageName: node - linkType: hard - -"text-hex@npm:1.0.x": - version: 1.0.0 - resolution: "text-hex@npm:1.0.0" - checksum: 10c0/57d8d320d92c79d7c03ffb8339b825bb9637c2cbccf14304309f51d8950015c44464b6fd1b6820a3d4821241c68825634f09f5a2d9d501e84f7c6fd14376860d - languageName: node - linkType: hard - -"timer-node@npm:^5.0.9": - version: 5.0.9 - resolution: "timer-node@npm:5.0.9" - checksum: 10c0/7a2f87480e784eebd2bcd5979e097ac40ad6a36ae62059482eef4c5127214c1f4c7958cd5099846bb005d6154c7e1477ac2924103a4c35676e55d757b9f9e923 - languageName: node - linkType: hard - -"tinyglobby@npm:^0.2.12": - version: 0.2.14 - resolution: "tinyglobby@npm:0.2.14" - dependencies: - fdir: "npm:^6.4.4" - picomatch: "npm:^4.0.2" - checksum: 10c0/f789ed6c924287a9b7d3612056ed0cda67306cd2c80c249fd280cf1504742b12583a2089b61f4abbd24605f390809017240e250241f09938054c9b363e51c0a6 - languageName: node - linkType: hard - -"tldts-core@npm:^6.1.68": - version: 6.1.68 - resolution: "tldts-core@npm:6.1.68" - checksum: 10c0/7ce2d0526aaf80656e746e322f9632b92afd07702c5aa61f92f908d325d4878fe6654d94039f30f625b32e7ff934e36cb83722b2e62c752ae34beed40fa1d123 - languageName: node - linkType: hard - -"tldts@npm:^6.1.32": - version: 6.1.68 - resolution: "tldts@npm:6.1.68" - dependencies: - tldts-core: "npm:^6.1.68" - bin: - tldts: bin/cli.js - checksum: 10c0/adbc9b23fa18a150e5124daec853ec1a2c7b656e2e294855b52ef2f7f7d5266660392ae222840862b6360165f7adbc3ae731f78f0a0f96ee1b4558328e428a55 - languageName: node - linkType: hard - -"tmp@npm:^0.0.33": - version: 0.0.33 - resolution: "tmp@npm:0.0.33" - dependencies: - os-tmpdir: "npm:~1.0.2" - checksum: 10c0/69863947b8c29cabad43fe0ce65cec5bb4b481d15d4b4b21e036b060b3edbf3bc7a5541de1bacb437bb3f7c4538f669752627fdf9b4aaf034cebd172ba373408 - languageName: node - linkType: hard - -"tmpl@npm:1.0.5": - version: 1.0.5 - resolution: "tmpl@npm:1.0.5" - checksum: 10c0/f935537799c2d1922cb5d6d3805f594388f75338fe7a4a9dac41504dd539704ca4db45b883b52e7b0aa5b2fd5ddadb1452bf95cd23a69da2f793a843f9451cc9 - languageName: node - linkType: hard - -"to-regex-range@npm:^5.0.1": - version: 5.0.1 - resolution: "to-regex-range@npm:5.0.1" - dependencies: - is-number: "npm:^7.0.0" - checksum: 10c0/487988b0a19c654ff3e1961b87f471702e708fa8a8dd02a298ef16da7206692e8552a0250e8b3e8759270f62e9d8314616f6da274734d3b558b1fc7b7724e892 - languageName: node - linkType: hard - -"tough-cookie@npm:^5.0.0, tough-cookie@npm:^5.1.2": - version: 5.1.2 - resolution: "tough-cookie@npm:5.1.2" - dependencies: - tldts: "npm:^6.1.32" - checksum: 10c0/5f95023a47de0f30a902bba951664b359725597d8adeabc66a0b93a931c3af801e1e697dae4b8c21a012056c0ea88bd2bf4dfe66b2adcf8e2f42cd9796fe0626 - languageName: node - linkType: hard - -"transliteration@npm:^2.3.5": - version: 2.3.5 - resolution: "transliteration@npm:2.3.5" - dependencies: - yargs: "npm:^17.5.1" - bin: - slugify: dist/bin/slugify - transliterate: dist/bin/transliterate - checksum: 10c0/68397225c2ca59b8e33206c65f905724e86b64460cbf90576d352dc2366e763ded97e2c7b8b1f140fb36a565d61a97c51080df9fa638e6b1769f6cb24f383756 - languageName: node - linkType: hard - -"triple-beam@npm:^1.3.0": - version: 1.3.0 - resolution: "triple-beam@npm:1.3.0" - checksum: 10c0/a6da96495f25b6c04b3629df5161c7eb84760927943f16665fd8dcd3a643daadf73d69eee78306b4b68d606937f22f8703afe763bc8d3723632ffb1f3a798493 - languageName: node - linkType: hard - -"ts-api-utils@npm:^2.1.0": - version: 2.1.0 - resolution: "ts-api-utils@npm:2.1.0" - peerDependencies: - typescript: ">=4.8.4" - checksum: 10c0/9806a38adea2db0f6aa217ccc6bc9c391ddba338a9fe3080676d0d50ed806d305bb90e8cef0276e793d28c8a929f400abb184ddd7ff83a416959c0f4d2ce754f - languageName: node - linkType: hard - -"tslib@npm:1.9.3": - version: 1.9.3 - resolution: "tslib@npm:1.9.3" - checksum: 10c0/752ee37e2cb12193732494d465241e7297dacf20b20f34a7591ab03713a4939183543d30d51fe2313ce3ae478044ac1fa10e4f19ad826ca5c81552372879c5a2 - languageName: node - linkType: hard - -"tslib@npm:^2.0.1, tslib@npm:^2.1.0": - version: 2.6.2 - resolution: "tslib@npm:2.6.2" - checksum: 10c0/e03a8a4271152c8b26604ed45535954c0a45296e32445b4b87f8a5abdb2421f40b59b4ca437c4346af0f28179780d604094eb64546bee2019d903d01c6c19bdb - languageName: node - linkType: hard - -"tslib@npm:^2.4.0": - version: 2.8.1 - resolution: "tslib@npm:2.8.1" - checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 - languageName: node - linkType: hard - -"tsx@npm:^4.20.3": - version: 4.20.3 - resolution: "tsx@npm:4.20.3" - dependencies: - esbuild: "npm:~0.25.0" - fsevents: "npm:~2.3.3" - get-tsconfig: "npm:^4.7.5" - dependenciesMeta: - fsevents: - optional: true - bin: - tsx: dist/cli.mjs - checksum: 10c0/6ff0d91ed046ec743fac7ed60a07f3c025e5b71a5aaf58f3d2a6b45e4db114c83e59ebbb078c8e079e48d3730b944a02bc0de87695088aef4ec8bbc705dc791b - languageName: node - linkType: hard - -"tv4@npm:^1.3.0": - version: 1.3.0 - resolution: "tv4@npm:1.3.0" - checksum: 10c0/714a37d005a902db0099379ed473c4780f6473f31c223cb0f023640cb32e0be9957cf2c95add8ec07665c6a699d45e6fd5c6ef49162b4e92000038b13fd8b759 - languageName: node - linkType: hard - -"tx2@npm:~1.0.4": - version: 1.0.5 - resolution: "tx2@npm:1.0.5" - dependencies: - json-stringify-safe: "npm:^5.0.1" - checksum: 10c0/2fcc1129c7aafc6fff3b4a38667acdc69867894120d3c75f5c071d3f3d932c71027b1f55c3a0e21c95d6fcf86f53a3eb38348fb45aae4fca7d31fe6da49a9875 - languageName: node - linkType: hard - -"type-check@npm:^0.4.0, type-check@npm:~0.4.0": - version: 0.4.0 - resolution: "type-check@npm:0.4.0" - dependencies: - prelude-ls: "npm:^1.2.1" - checksum: 10c0/7b3fd0ed43891e2080bf0c5c504b418fbb3e5c7b9708d3d015037ba2e6323a28152ec163bcb65212741fa5d2022e3075ac3c76440dbd344c9035f818e8ecee58 - languageName: node - linkType: hard - -"type-detect@npm:4.0.8": - version: 4.0.8 - resolution: "type-detect@npm:4.0.8" - checksum: 10c0/8fb9a51d3f365a7de84ab7f73b653534b61b622aa6800aecdb0f1095a4a646d3f5eb295322127b6573db7982afcd40ab492d038cf825a42093a58b1e1353e0bd - languageName: node - linkType: hard - -"type-fest@npm:^0.21.3": - version: 0.21.3 - resolution: "type-fest@npm:0.21.3" - checksum: 10c0/902bd57bfa30d51d4779b641c2bc403cdf1371fb9c91d3c058b0133694fcfdb817aef07a47f40faf79039eecbaa39ee9d3c532deff244f3a19ce68cea71a61e8 - languageName: node - linkType: hard - -"type-fest@npm:^2.13.0": - version: 2.19.0 - resolution: "type-fest@npm:2.19.0" - checksum: 10c0/a5a7ecf2e654251613218c215c7493574594951c08e52ab9881c9df6a6da0aeca7528c213c622bc374b4e0cb5c443aa3ab758da4e3c959783ce884c3194e12cb - languageName: node - linkType: hard - -"typescript@npm:^5.8.3": - version: 5.8.3 - resolution: "typescript@npm:5.8.3" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10c0/5f8bb01196e542e64d44db3d16ee0e4063ce4f3e3966df6005f2588e86d91c03e1fb131c2581baf0fb65ee79669eea6e161cd448178986587e9f6844446dbb48 - languageName: node - linkType: hard - -"typescript@patch:typescript@npm%3A^5.8.3#optional!builtin": - version: 5.8.3 - resolution: "typescript@patch:typescript@npm%3A5.8.3#optional!builtin::version=5.8.3&hash=5786d5" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10c0/39117e346ff8ebd87ae1510b3a77d5d92dae5a89bde588c747d25da5c146603a99c8ee588c7ef80faaf123d89ed46f6dbd918d534d641083177d5fac38b8a1cb - languageName: node - linkType: hard - -"undici-types@npm:~7.8.0": - version: 7.8.0 - resolution: "undici-types@npm:7.8.0" - checksum: 10c0/9d9d246d1dc32f318d46116efe3cfca5a72d4f16828febc1918d94e58f6ffcf39c158aa28bf5b4fc52f410446bc7858f35151367bd7a49f21746cab6497b709b - languageName: node - linkType: hard - -"undici@npm:^7.10.0": - version: 7.10.0 - resolution: "undici@npm:7.10.0" - checksum: 10c0/756ac876a8df845bc89eb8348c35d33a0ff63c17eb45b664075c961a7fbd4a398f94f9dce438262f55fe66e4bbb0a46aa63a3fd58ce51361c616aff11a270450 - languageName: node - linkType: hard - -"unique-filename@npm:^4.0.0": - version: 4.0.0 - resolution: "unique-filename@npm:4.0.0" - dependencies: - unique-slug: "npm:^5.0.0" - checksum: 10c0/38ae681cceb1408ea0587b6b01e29b00eee3c84baee1e41fd5c16b9ed443b80fba90c40e0ba69627e30855570a34ba8b06702d4a35035d4b5e198bf5a64c9ddc - languageName: node - linkType: hard - -"unique-slug@npm:^5.0.0": - version: 5.0.0 - resolution: "unique-slug@npm:5.0.0" - dependencies: - imurmurhash: "npm:^0.1.4" - checksum: 10c0/d324c5a44887bd7e105ce800fcf7533d43f29c48757ac410afd42975de82cc38ea2035c0483f4de82d186691bf3208ef35c644f73aa2b1b20b8e651be5afd293 - languageName: node - linkType: hard - -"universal-user-agent@npm:^7.0.0, universal-user-agent@npm:^7.0.2": - version: 7.0.3 - resolution: "universal-user-agent@npm:7.0.3" - checksum: 10c0/6043be466a9bb96c0ce82392842d9fddf4c37e296f7bacc2cb25f47123990eb436c82df824644f9c5070a94dbdb117be17f66d54599ab143648ec57ef93dbcc8 - languageName: node - linkType: hard - -"universalify@npm:^2.0.0": - version: 2.0.0 - resolution: "universalify@npm:2.0.0" - checksum: 10c0/07092b9f46df61b823d8ab5e57f0ee5120c178b39609a95e4a15a98c42f6b0b8e834e66fbb47ff92831786193be42f1fd36347169b88ce8639d0f9670af24a71 - languageName: node - linkType: hard - -"unrs-resolver@npm:^1.7.11": - version: 1.9.1 - resolution: "unrs-resolver@npm:1.9.1" - dependencies: - "@unrs/resolver-binding-android-arm-eabi": "npm:1.9.1" - "@unrs/resolver-binding-android-arm64": "npm:1.9.1" - "@unrs/resolver-binding-darwin-arm64": "npm:1.9.1" - "@unrs/resolver-binding-darwin-x64": "npm:1.9.1" - "@unrs/resolver-binding-freebsd-x64": "npm:1.9.1" - "@unrs/resolver-binding-linux-arm-gnueabihf": "npm:1.9.1" - "@unrs/resolver-binding-linux-arm-musleabihf": "npm:1.9.1" - "@unrs/resolver-binding-linux-arm64-gnu": "npm:1.9.1" - "@unrs/resolver-binding-linux-arm64-musl": "npm:1.9.1" - "@unrs/resolver-binding-linux-ppc64-gnu": "npm:1.9.1" - "@unrs/resolver-binding-linux-riscv64-gnu": "npm:1.9.1" - "@unrs/resolver-binding-linux-riscv64-musl": "npm:1.9.1" - "@unrs/resolver-binding-linux-s390x-gnu": "npm:1.9.1" - "@unrs/resolver-binding-linux-x64-gnu": "npm:1.9.1" - "@unrs/resolver-binding-linux-x64-musl": "npm:1.9.1" - "@unrs/resolver-binding-wasm32-wasi": "npm:1.9.1" - "@unrs/resolver-binding-win32-arm64-msvc": "npm:1.9.1" - "@unrs/resolver-binding-win32-ia32-msvc": "npm:1.9.1" - "@unrs/resolver-binding-win32-x64-msvc": "npm:1.9.1" - napi-postinstall: "npm:^0.2.2" - dependenciesMeta: - "@unrs/resolver-binding-android-arm-eabi": - optional: true - "@unrs/resolver-binding-android-arm64": - optional: true - "@unrs/resolver-binding-darwin-arm64": - optional: true - "@unrs/resolver-binding-darwin-x64": - optional: true - "@unrs/resolver-binding-freebsd-x64": - optional: true - "@unrs/resolver-binding-linux-arm-gnueabihf": - optional: true - "@unrs/resolver-binding-linux-arm-musleabihf": - optional: true - "@unrs/resolver-binding-linux-arm64-gnu": - optional: true - "@unrs/resolver-binding-linux-arm64-musl": - optional: true - "@unrs/resolver-binding-linux-ppc64-gnu": - optional: true - "@unrs/resolver-binding-linux-riscv64-gnu": - optional: true - "@unrs/resolver-binding-linux-riscv64-musl": - optional: true - "@unrs/resolver-binding-linux-s390x-gnu": - optional: true - "@unrs/resolver-binding-linux-x64-gnu": - optional: true - "@unrs/resolver-binding-linux-x64-musl": - optional: true - "@unrs/resolver-binding-wasm32-wasi": - optional: true - "@unrs/resolver-binding-win32-arm64-msvc": - optional: true - "@unrs/resolver-binding-win32-ia32-msvc": - optional: true - "@unrs/resolver-binding-win32-x64-msvc": - optional: true - checksum: 10c0/fded9251b6c180c92c0510abe63e4fa9a5a4adcdcf3c9f7920507dc9f1ec756de5e71d1258f12bf4a32f7042e1fe142b6dc1003d8a6fb4d0bf1234226c879b01 - languageName: node - linkType: hard - -"unzipit@npm:^1.4.3": - version: 1.4.3 - resolution: "unzipit@npm:1.4.3" - dependencies: - uzip-module: "npm:^1.0.2" - checksum: 10c0/6f710615aaafb6283d5f489950e54d158deaa1c8757ba486d98ddc76f3ae3b7eab24cd24017185dd5f4edcd2df43b04431bca009538d4116c60229b60db8dd3b - languageName: node - linkType: hard - -"update-browserslist-db@npm:^1.1.3": - version: 1.1.3 - resolution: "update-browserslist-db@npm:1.1.3" - dependencies: - escalade: "npm:^3.2.0" - picocolors: "npm:^1.1.1" - peerDependencies: - browserslist: ">= 4.21.0" - bin: - update-browserslist-db: cli.js - checksum: 10c0/682e8ecbf9de474a626f6462aa85927936cdd256fe584c6df2508b0df9f7362c44c957e9970df55dfe44d3623807d26316ea2c7d26b80bb76a16c56c37233c32 - languageName: node - linkType: hard - -"update-check@npm:1.5.4": - version: 1.5.4 - resolution: "update-check@npm:1.5.4" - dependencies: - registry-auth-token: "npm:3.3.2" - registry-url: "npm:3.1.0" - checksum: 10c0/ac4b8dafa5db9b1c8ff5d0cfcc3b4c5687c390526b3218155e27173c7ca647572ea9e523dd3463523e698ef94d273768b395748da54655fe773dada59ac9c7b0 - languageName: node - linkType: hard - -"uri-js@npm:^4.2.2": - version: 4.4.1 - resolution: "uri-js@npm:4.4.1" - dependencies: - punycode: "npm:^2.1.0" - checksum: 10c0/4ef57b45aa820d7ac6496e9208559986c665e49447cb072744c13b66925a362d96dd5a46c4530a6b8e203e5db5fe849369444440cb22ecfc26c679359e5dfa3c - languageName: node - linkType: hard - -"util-deprecate@npm:^1.0.1": - version: 1.0.2 - resolution: "util-deprecate@npm:1.0.2" - checksum: 10c0/41a5bdd214df2f6c3ecf8622745e4a366c4adced864bc3c833739791aeeeb1838119af7daed4ba36428114b5c67dcda034a79c882e97e43c03e66a4dd7389942 - languageName: node - linkType: hard - -"util@npm:^0.12.4": - version: 0.12.5 - resolution: "util@npm:0.12.5" - dependencies: - inherits: "npm:^2.0.3" - is-arguments: "npm:^1.0.4" - is-generator-function: "npm:^1.0.7" - is-typed-array: "npm:^1.1.3" - which-typed-array: "npm:^1.1.2" - checksum: 10c0/c27054de2cea2229a66c09522d0fa1415fb12d861d08523a8846bf2e4cbf0079d4c3f725f09dcb87493549bcbf05f5798dce1688b53c6c17201a45759e7253f3 - languageName: node - linkType: hard - -"uzip-module@npm:^1.0.2": - version: 1.0.3 - resolution: "uzip-module@npm:1.0.3" - checksum: 10c0/206e09cf620aa178e5d8ab20425d1a4f4484de9a86673e8a5913087b7b0febb12d49a22b15574a3c5b0c9d911a0d600c9f51651de2a78ffbc6e0345b8ef3af8d - languageName: node - linkType: hard - -"v8-to-istanbul@npm:^9.0.1": - version: 9.3.0 - resolution: "v8-to-istanbul@npm:9.3.0" - dependencies: - "@jridgewell/trace-mapping": "npm:^0.3.12" - "@types/istanbul-lib-coverage": "npm:^2.0.1" - convert-source-map: "npm:^2.0.0" - checksum: 10c0/968bcf1c7c88c04df1ffb463c179558a2ec17aa49e49376120504958239d9e9dad5281aa05f2a78542b8557f2be0b0b4c325710262f3b838b40d703d5ed30c23 - languageName: node - linkType: hard - -"vary@npm:~1.1.2": - version: 1.1.2 - resolution: "vary@npm:1.1.2" - checksum: 10c0/f15d588d79f3675135ba783c91a4083dcd290a2a5be9fcb6514220a1634e23df116847b1cc51f66bfb0644cf9353b2abb7815ae499bab06e46dd33c1a6bf1f4f - languageName: node - linkType: hard - -"vizion@npm:~2.2.1": - version: 2.2.1 - resolution: "vizion@npm:2.2.1" - dependencies: - async: "npm:^2.6.3" - git-node-fs: "npm:^1.0.0" - ini: "npm:^1.3.5" - js-git: "npm:^0.7.8" - checksum: 10c0/cdca365840e838af4724a3bca3dcafc79730ea589299c7f802d1084ea5771037a7e9b83c61e2e174cf6dd98df3de850fc0110faabb71a408edc5336161d9e930 - languageName: node - linkType: hard - -"walker@npm:^1.0.8": - version: 1.0.8 - resolution: "walker@npm:1.0.8" - dependencies: - makeerror: "npm:1.0.12" - checksum: 10c0/a17e037bccd3ca8a25a80cb850903facdfed0de4864bd8728f1782370715d679fa72e0a0f5da7c1c1379365159901e5935f35be531229da53bbfc0efdabdb48e - languageName: node - linkType: hard - -"whatwg-encoding@npm:^3.1.1": - version: 3.1.1 - resolution: "whatwg-encoding@npm:3.1.1" - dependencies: - iconv-lite: "npm:0.6.3" - checksum: 10c0/273b5f441c2f7fda3368a496c3009edbaa5e43b71b09728f90425e7f487e5cef9eb2b846a31bd760dd8077739c26faf6b5ca43a5f24033172b003b72cf61a93e - languageName: node - linkType: hard - -"whatwg-mimetype@npm:^4.0.0": - version: 4.0.0 - resolution: "whatwg-mimetype@npm:4.0.0" - checksum: 10c0/a773cdc8126b514d790bdae7052e8bf242970cebd84af62fb2f35a33411e78e981f6c0ab9ed1fe6ec5071b09d5340ac9178e05b52d35a9c4bcf558ba1b1551df - languageName: node - linkType: hard - -"which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.2": - version: 1.1.19 - resolution: "which-typed-array@npm:1.1.19" - dependencies: - available-typed-arrays: "npm:^1.0.7" - call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.4" - for-each: "npm:^0.3.5" - get-proto: "npm:^1.0.1" - gopd: "npm:^1.2.0" - has-tostringtag: "npm:^1.0.2" - checksum: 10c0/702b5dc878addafe6c6300c3d0af5983b175c75fcb4f2a72dfc3dd38d93cf9e89581e4b29c854b16ea37e50a7d7fca5ae42ece5c273d8060dcd603b2404bbb3f - languageName: node - linkType: hard - -"which@npm:^2.0.1": - version: 2.0.2 - resolution: "which@npm:2.0.2" - dependencies: - isexe: "npm:^2.0.0" - bin: - node-which: ./bin/node-which - checksum: 10c0/66522872a768b60c2a65a57e8ad184e5372f5b6a9ca6d5f033d4b0dc98aff63995655a7503b9c0a2598936f532120e81dd8cc155e2e92ed662a2b9377cc4374f - languageName: node - linkType: hard - -"which@npm:^5.0.0": - version: 5.0.0 - resolution: "which@npm:5.0.0" - dependencies: - isexe: "npm:^3.1.1" - bin: - node-which: bin/which.js - checksum: 10c0/e556e4cd8b7dbf5df52408c9a9dd5ac6518c8c5267c8953f5b0564073c66ed5bf9503b14d876d0e9c7844d4db9725fb0dcf45d6e911e17e26ab363dc3965ae7b - languageName: node - linkType: hard - -"widest-line@npm:^4.0.1": - version: 4.0.1 - resolution: "widest-line@npm:4.0.1" - dependencies: - string-width: "npm:^5.0.1" - checksum: 10c0/7da9525ba45eaf3e4ed1a20f3dcb9b85bd9443962450694dae950f4bdd752839747bbc14713522b0b93080007de8e8af677a61a8c2114aa553ad52bde72d0f9c - languageName: node - linkType: hard - -"wildcard-match@npm:^5.1.4": - version: 5.1.4 - resolution: "wildcard-match@npm:5.1.4" - checksum: 10c0/2f37e2fedceca003ec48d064e57c20792a71529ca5765c2d0d67c0964f3a184b33ed61efd8765ed78fd18086c9cf951b381c7277b8f0edb550638f76e3e17897 - languageName: node - linkType: hard - -"winston-transport@npm:^4.5.0": - version: 4.5.0 - resolution: "winston-transport@npm:4.5.0" - dependencies: - logform: "npm:^2.3.2" - readable-stream: "npm:^3.6.0" - triple-beam: "npm:^1.3.0" - checksum: 10c0/110a47c5acc87c3aa0f101741c0a992e52a86802272838c18aede8178d2b5e80254d2433dcac3439cefbc2777d9e22e65f84e9cee3130681c58e4ae5d58f50c3 - languageName: node - linkType: hard - -"winston@npm:^3.3.3": - version: 3.7.2 - resolution: "winston@npm:3.7.2" - dependencies: - "@dabh/diagnostics": "npm:^2.0.2" - async: "npm:^3.2.3" - is-stream: "npm:^2.0.0" - logform: "npm:^2.4.0" - one-time: "npm:^1.0.0" - readable-stream: "npm:^3.4.0" - safe-stable-stringify: "npm:^2.3.1" - stack-trace: "npm:0.0.x" - triple-beam: "npm:^1.3.0" - winston-transport: "npm:^4.5.0" - checksum: 10c0/c8f73f089b12101bacbd3cb050d1963083748e64fc9c6e8f5f45a019fb6edffc267d8465013f3adb456c32da241a8f5af530c5d46b3dc4b549426d9208d96839 - languageName: node - linkType: hard - -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": - version: 7.0.0 - resolution: "wrap-ansi@npm:7.0.0" - dependencies: - ansi-styles: "npm:^4.0.0" - string-width: "npm:^4.1.0" - strip-ansi: "npm:^6.0.0" - checksum: 10c0/d15fc12c11e4cbc4044a552129ebc75ee3f57aa9c1958373a4db0292d72282f54373b536103987a4a7594db1ef6a4f10acf92978f79b98c49306a4b58c77d4da - languageName: node - linkType: hard - -"wrap-ansi@npm:^6.2.0": - version: 6.2.0 - resolution: "wrap-ansi@npm:6.2.0" - dependencies: - ansi-styles: "npm:^4.0.0" - string-width: "npm:^4.1.0" - strip-ansi: "npm:^6.0.0" - checksum: 10c0/baad244e6e33335ea24e86e51868fe6823626e3a3c88d9a6674642afff1d34d9a154c917e74af8d845fd25d170c4ea9cf69a47133c3f3656e1252b3d462d9f6c - languageName: node - linkType: hard - -"wrap-ansi@npm:^8.0.1, wrap-ansi@npm:^8.1.0": - version: 8.1.0 - resolution: "wrap-ansi@npm:8.1.0" - dependencies: - ansi-styles: "npm:^6.1.0" - string-width: "npm:^5.0.1" - strip-ansi: "npm:^7.0.1" - checksum: 10c0/138ff58a41d2f877eae87e3282c0630fc2789012fc1af4d6bd626eeb9a2f9a65ca92005e6e69a75c7b85a68479fe7443c7dbe1eb8fbaa681a4491364b7c55c60 - languageName: node - linkType: hard - -"wrappy@npm:1": - version: 1.0.2 - resolution: "wrappy@npm:1.0.2" - checksum: 10c0/56fece1a4018c6a6c8e28fbc88c87e0fbf4ea8fd64fc6c63b18f4acc4bd13e0ad2515189786dd2c30d3eec9663d70f4ecf699330002f8ccb547e4a18231fc9f0 - languageName: node - linkType: hard - -"write-file-atomic@npm:^5.0.1": - version: 5.0.1 - resolution: "write-file-atomic@npm:5.0.1" - dependencies: - imurmurhash: "npm:^0.1.4" - signal-exit: "npm:^4.0.1" - checksum: 10c0/e8c850a8e3e74eeadadb8ad23c9d9d63e4e792bd10f4836ed74189ef6e996763959f1249c5650e232f3c77c11169d239cbfc8342fc70f3fe401407d23810505d - languageName: node - linkType: hard - -"ws@npm:^7.0.0, ws@npm:~7.5.10": - version: 7.5.10 - resolution: "ws@npm:7.5.10" - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - checksum: 10c0/bd7d5f4aaf04fae7960c23dcb6c6375d525e00f795dd20b9385902bd008c40a94d3db3ce97d878acc7573df852056ca546328b27b39f47609f80fb22a0a9b61d - languageName: node - linkType: hard - -"xml-js@npm:^1.6.11": - version: 1.6.11 - resolution: "xml-js@npm:1.6.11" - dependencies: - sax: "npm:^1.2.4" - bin: - xml-js: ./bin/cli.js - checksum: 10c0/c83631057f10bf90ea785cee434a8a1a0030c7314fe737ad9bf568a281083b565b28b14c9e9ba82f11fc9dc582a3a907904956af60beb725be1c9ad4b030bc5a - languageName: node - linkType: hard - -"y18n@npm:^5.0.5": - version: 5.0.8 - resolution: "y18n@npm:5.0.8" - checksum: 10c0/4df2842c36e468590c3691c894bc9cdbac41f520566e76e24f59401ba7d8b4811eb1e34524d57e54bc6d864bcb66baab7ffd9ca42bf1eda596618f9162b91249 - languageName: node - linkType: hard - -"yallist@npm:^3.0.2": - version: 3.1.1 - resolution: "yallist@npm:3.1.1" - checksum: 10c0/c66a5c46bc89af1625476f7f0f2ec3653c1a1791d2f9407cfb4c2ba812a1e1c9941416d71ba9719876530e3340a99925f697142989371b72d93b9ee628afd8c1 - languageName: node - linkType: hard - -"yallist@npm:^4.0.0": - version: 4.0.0 - resolution: "yallist@npm:4.0.0" - checksum: 10c0/2286b5e8dbfe22204ab66e2ef5cc9bbb1e55dfc873bbe0d568aa943eb255d131890dfd5bf243637273d31119b870f49c18fcde2c6ffbb7a7a092b870dc90625a - languageName: node - linkType: hard - -"yallist@npm:^5.0.0": - version: 5.0.0 - resolution: "yallist@npm:5.0.0" - checksum: 10c0/a499c81ce6d4a1d260d4ea0f6d49ab4da09681e32c3f0472dee16667ed69d01dae63a3b81745a24bd78476ec4fcf856114cb4896ace738e01da34b2c42235416 - languageName: node - linkType: hard - -"yargs-parser@npm:^21.1.1": - version: 21.1.1 - resolution: "yargs-parser@npm:21.1.1" - checksum: 10c0/f84b5e48169479d2f402239c59f084cfd1c3acc197a05c59b98bab067452e6b3ea46d4dd8ba2985ba7b3d32a343d77df0debd6b343e5dae3da2aab2cdf5886b2 - languageName: node - linkType: hard - -"yargs@npm:^17.5.1, yargs@npm:^17.7.2": - version: 17.7.2 - resolution: "yargs@npm:17.7.2" - dependencies: - cliui: "npm:^8.0.1" - escalade: "npm:^3.1.1" - get-caller-file: "npm:^2.0.5" - require-directory: "npm:^2.1.1" - string-width: "npm:^4.2.3" - y18n: "npm:^5.0.5" - yargs-parser: "npm:^21.1.1" - checksum: 10c0/ccd7e723e61ad5965fffbb791366db689572b80cca80e0f96aad968dfff4156cd7cd1ad18607afe1046d8241e6fb2d6c08bf7fa7bfb5eaec818735d8feac8f05 - languageName: node - linkType: hard - -"yocto-queue@npm:^0.1.0": - version: 0.1.0 - resolution: "yocto-queue@npm:0.1.0" - checksum: 10c0/dceb44c28578b31641e13695d200d34ec4ab3966a5729814d5445b194933c096b7ced71494ce53a0e8820685d1d010df8b2422e5bf2cdea7e469d97ffbea306f - languageName: node - linkType: hard - -"yoctocolors-cjs@npm:^2.1.2": - version: 2.1.2 - resolution: "yoctocolors-cjs@npm:2.1.2" - checksum: 10c0/a0e36eb88fea2c7981eab22d1ba45e15d8d268626e6c4143305e2c1628fa17ebfaa40cd306161a8ce04c0a60ee0262058eab12567493d5eb1409780853454c6f - languageName: node - linkType: hard From 12849d2350a7cd68c44d2ed29736b9f06a934632 Mon Sep 17 00:00:00 2001 From: theofficialomega <30985701+BellezaEmporium@users.noreply.github.com> Date: Sat, 12 Jul 2025 13:24:07 +0200 Subject: [PATCH 03/32] uniformise tests part 1 --- sites/tv.trueid.net/__data__/data.json | 5095 ++++++++++++++++- sites/tv.yandex.ru/__data__/no_content.html | 1 + sites/tv.yandex.ru/tv.yandex.ru.test.js | 2 +- sites/tv2go.t-2.net/__data__/content.json | 54 + sites/tv2go.t-2.net/tv2go.t-2.net.test.js | 5 +- sites/tvcesoir.fr/__data__/no_content.html | 1 + sites/tvcesoir.fr/tvcesoir.fr.test.js | 2 +- sites/tvcubana.icrt.cu/__data__/content.json | 55 + .../tvcubana.icrt.cu/__data__/no_content.html | 1 + .../tvcubana.icrt.cu/tvcubana.icrt.cu.test.js | 10 +- sites/tvguide.myjcom.jp/__data__/content.json | 34 + .../__data__/no_content.json | 1 + .../tvguide.myjcom.jp.test.js | 7 +- sites/tvheute.at/tvheute.at.test.js | 5 +- 14 files changed, 5259 insertions(+), 14 deletions(-) create mode 100644 sites/tv.yandex.ru/__data__/no_content.html create mode 100644 sites/tv2go.t-2.net/__data__/content.json create mode 100644 sites/tvcesoir.fr/__data__/no_content.html create mode 100644 sites/tvcubana.icrt.cu/__data__/content.json create mode 100644 sites/tvcubana.icrt.cu/__data__/no_content.html create mode 100644 sites/tvguide.myjcom.jp/__data__/content.json create mode 100644 sites/tvguide.myjcom.jp/__data__/no_content.json diff --git a/sites/tv.trueid.net/__data__/data.json b/sites/tv.trueid.net/__data__/data.json index dff308543..a66974aec 100644 --- a/sites/tv.trueid.net/__data__/data.json +++ b/sites/tv.trueid.net/__data__/data.json @@ -1 +1,5094 @@ -{"pageProps":{"currentLang":{"country":"th","lang":"en"},"isBotPerformance":false,"titleH1":"Watch Live TV Online 24 hours","metaData":{"title":"ดูทีวีออนไลน์ True Movie Hits - TrueID TV","description":"Watch Live TV Online 24 hours, Thai Drama, Full HD","imageURL":"https://cms.dmpcdn.com/livetv/2023/04/28/45345d10-e599-11ed-86b8-bb40638e3c49_webp_original.png","currentUrl":"https://tv.trueid.net/th-en/live/true-movie-hits","metaTitle":"ดูทีวีออนไลน์ True Movie Hits - TrueID TV"},"channelList":[{"id":"nQlqONGyoa4","thumb":"https://cms.dmpcdn.com/livetv/2023/07/24/5ff3e270-29cc-11ee-b2f4-e9de482d866e_webp_original.webp","slug":"ch3-hd","title":"Channel 3","content_type":"livetv","category":"livetv-ca|digitaltv-ca|entertainment-ca|freetv-ca","content_provider":"","channel_code":"c03","content_rights":null,"channel_info":{"channel_name_cbd":"ប៉ុស្តិ៍ 3 HD","channel_name_eng":"CH3 HD","channel_name_mm":"CH3 HD","channel_name_th":"ช่อง 3 HD"},"views":96184,"isLiveChat":false},{"id":"wKngqJ2Vqnl","thumb":"https://cms.dmpcdn.com/livetv/2019/01/10/35a35017-8473-4953-8474-5c58d805b74a.png","slug":"mono29","title":"MONO 29","content_type":"livetv","category":"livetv-ca|digitaltv-ca|freetv-ca|movies-series-ca","content_provider":"","channel_code":"d43","content_rights":null,"channel_info":{"channel_name_cbd":"មូណូ ធីវី","channel_name_eng":"Mono 29","channel_name_mm":"Mono 29","channel_name_th":"โมโน 29"},"views":33721,"isLiveChat":false},{"id":"8v732AYomo9","thumb":"https://cms.dmpcdn.com/livetv/2023/07/18/7dc7a180-2515-11ee-b8b2-77e2a8f4c31e_webp_original.webp","slug":"thairathtv-hd","title":"Thairath TV","content_type":"livetv","category":"livetv-ca|digitaltv-ca|freetv-ca|news-ca","content_provider":"","channel_code":"d05","content_rights":null,"channel_info":{"channel_name_cbd":"ថៃរ៉ាត់ ធីវី HD","channel_name_eng":"Thairath TV HD","channel_name_mm":"Thairath TV HD","channel_name_th":"ไทยรัฐ ทีวี HD"},"views":17228,"isLiveChat":false},{"id":"9O54lyP5Rqx","thumb":"https://cms.dmpcdn.com/livetv/2023/07/19/212d15e0-25e7-11ee-bfc1-85e95548413c_webp_original.webp","slug":"ch7-hd","title":"Channel 7HD","content_type":"livetv","category":"livetv-ca|digitaltv-ca|entertainment-ca|freetv-ca","content_provider":"","channel_code":"c07","content_rights":null,"channel_info":{"channel_name_cbd":"ប៉ុស្តិ៍​ 7","channel_name_eng":"CH 7HD","channel_name_mm":"Channel 7","channel_name_th":"ช่อง 7HD"},"views":12092,"isLiveChat":false},{"id":"0z4lvq6Xwoa","thumb":"https://cms.dmpcdn.com/livetv/2019/01/16/396384be-35dc-4d11-bf04-06c9546ec7bc.png","slug":"one-hd","title":"One31","content_type":"livetv","category":"livetv-ca|digitaltv-ca|entertainment-ca|freetv-ca","content_provider":"","channel_code":"d56","content_rights":null,"channel_info":{"channel_name_cbd":"វ័ន HD","channel_name_eng":"One HD","channel_name_mm":"One HD","channel_name_th":"วัน HD"},"views":10182,"isLiveChat":false},{"id":"vqbr1WgEnGQ","thumb":"https://cms.dmpcdn.com/livetv/2023/09/15/5408a390-5377-11ee-8e1b-194edbb69638_webp_original.webp","slug":"ch8","title":"Channel 8","content_type":"livetv","category":"livetv-ca|digitaltv-ca|entertainment-ca|freetv-ca","content_provider":"","channel_code":"d62","content_rights":null,"channel_info":{"channel_name_cbd":"ប៉ុស្តិ៍ 8","channel_name_eng":"CH8","channel_name_th":"ช่อง 8"},"views":8295,"isLiveChat":false},{"id":"OVKwZle4eop","thumb":"https://cms.dmpcdn.com/livetv/2023/09/15/84504210-5377-11ee-aaa1-7d584d8ca7a4_webp_original.webp","slug":"true4u","title":"True4U","content_type":"livetv","category":"livetv-ca|digitaltv-ca|entertainment-ca|freetv-ca|movies-series-ca","content_provider":"","channel_code":"207","content_rights":null,"channel_info":{"channel_name_cbd":"ទ្រូ4យូ","channel_name_chi":"True4U","channel_name_eng":"True4U","channel_name_mm":"True4U","channel_name_rus":"True4U","channel_name_th":"ทรูโฟร์ยู","channel_name_vie":"True4U"},"views":6489,"isLiveChat":false},{"id":"OBb6NzoJX7O","thumb":"https://cms.dmpcdn.com/livetv/2023/10/02/d2ec4b30-60f1-11ee-92a4-8597bcef0049_webp_original.webp","slug":"amarintv-hd","title":"Amarin TV","content_type":"livetv","category":"livetv-ca|digitaltv-ca|freetv-ca","content_provider":"","channel_code":"da0","content_rights":null,"channel_info":{"channel_name_cbd":"អាម៉ារិន","channel_name_eng":"Amarin TV","channel_name_mm":"Amarin TV","channel_name_th":"อมรินทร์"},"views":6407,"isLiveChat":false},{"id":"yYk6PvXwXDb","thumb":"https://cms.dmpcdn.com/livetv/2023/11/17/2a1de990-852d-11ee-bf98-41acc8fd04fc_webp_original.webp","slug":"workpointtv","title":"WorkPoint TV","content_type":"livetv","category":"livetv-ca|digitaltv-ca|entertainment-ca|freetv-ca","content_provider":"","channel_code":"d83","content_rights":null,"channel_info":{"channel_name_cbd":"វើកភ័ញ គ្រីអ៊ែតធិវ ធីវី​","channel_name_eng":"Workpoint TV","channel_name_mm":"Workpoint TV","channel_name_th":"เวิร์คพอยท์ ทีวี"},"views":6075,"isLiveChat":false},{"id":"qvgeWLPGMY6","thumb":"https://cms.dmpcdn.com/livetv/2020/11/19/ed873d50-2a22-11eb-bed4-0972e345f90c_original.png","slug":"gmm25","title":"GMM 25","content_type":"livetv","category":"livetv-ca|digitaltv-ca|entertainment-ca|freetv-ca","content_provider":"","channel_code":"d76","content_rights":null,"channel_info":{"channel_name_cbd":"GMM 25","channel_name_eng":"GMM 25","channel_name_mm":"GMM 25","channel_name_th":"จีเอ็มเอ็ม 25"},"views":4861,"isLiveChat":false},{"id":"zMLBpX7AWmk","thumb":"https://cms.dmpcdn.com/livetv/2023/09/09/9c6f59c0-4ebe-11ee-99a7-832609069236_webp_original.webp","slug":"nationtv","title":"Nation TV","content_type":"livetv","category":"livetv-ca|digitaltv-ca|freetv-ca|news-ca","content_provider":"","channel_code":"d78","content_rights":null,"channel_info":{"channel_name_cbd":"Nation TV 22","channel_name_chi":"Nation TV 22","channel_name_eng":"Nation TV 22","channel_name_mm":"Nation TV 22","channel_name_rus":"Nation TV 22","channel_name_th":"เนชั่น ทีวี","channel_name_vie":"Nation TV 22"},"views":4733,"isLiveChat":false},{"id":"QNBwOpdaxpQ","thumb":"https://cms.dmpcdn.com/livetv/2023/08/28/012eed00-458a-11ee-bd2b-6734a2d9e428_webp_original.webp","slug":"pptv-hd","title":"PPTV","content_type":"livetv","category":"livetv-ca|digitaltv-ca|freetv-ca","content_provider":"","channel_code":"da7","content_rights":null,"channel_info":{"channel_name_cbd":"ភីភីធីវី","channel_name_eng":"PPTV","channel_name_mm":"PPTV","channel_name_th":"พีพีทีวี"},"views":4723,"isLiveChat":false},{"id":"xqY73dWBoZye","thumb":"https://cms.dmpcdn.com/livetv/2023/05/03/ba425a00-e966-11ed-be07-cbff4c6d2c94_webp_original.png","slug":"truepremierfootballhd1","title":"True Premier Football 1","content_type":"livetv","category":"livetv-ca|football-ca|sports-ca|tvsnow|sports","content_provider":"true_vision","channel_code":"ht111","content_rights":null,"channel_info":{"channel_name_eng":"True Premier Football 1","channel_name_th":"ทรู พรีเมียร์ ฟุตบอล 1"},"views":3123,"isLiveChat":true},{"id":"QRP2K658b7G","thumb":"https://cms.dmpcdn.com/livetv/2023/09/15/ab170410-5377-11ee-8e1b-194edbb69638_webp_original.webp","slug":"thaipbs","title":"Thai PBS","content_type":"livetv","category":"livetv-ca|digitaltv-ca|freetv-ca|news-ca","content_provider":"","channel_code":"c12","content_rights":null,"channel_info":{"channel_name_cbd":"ធីភីបីអេស","channel_name_eng":"TPBS","channel_name_mm":"TPBS","channel_name_th":"ไทยพีบีเอส"},"views":2526,"isLiveChat":false},{"id":"OZeq8ZLPldY","thumb":"https://cms.dmpcdn.com/livetv/2023/10/02/75023d90-60f1-11ee-935a-5d4eba985103_webp_original.webp","slug":"tnn16","title":"TNN 16","content_type":"livetv","category":"livetv-ca|digitaltv-ca|freetv-ca|news-ca|tvsnow|tvsnews","content_provider":"true_vision","channel_code":"135","content_rights":null,"channel_info":{"channel_name_cbd":"ធីអិនអិន 16","channel_name_eng":"TNN​ 16","channel_name_mm":"TNN​ 16","channel_name_th":"ทีเอ็นเอ็น 16"},"views":2444,"isLiveChat":false},{"id":"LY2j6Pyxbla","thumb":"https://cms.dmpcdn.com/livetv/2023/10/02/4a2afc60-60f1-11ee-a78e-f70ba0052fab_webp_original.webp","slug":"nbt","title":"NBT","content_type":"livetv","category":"livetv-ca|digitaltv-ca|freetv-ca","content_provider":"","channel_code":"c11","content_rights":null,"channel_info":{"channel_name_cbd":"អិនបីធី​","channel_name_eng":"NBT","channel_name_mm":"์NBT","channel_name_th":"เอ็นบีที"},"views":2043,"isLiveChat":false},{"id":"Z9E4LnAbgjKy","thumb":"https://cms.dmpcdn.com/livetv/2021/06/15/3e4e0540-cdb4-11eb-9a22-7958179a38a7_original.png","slug":"jkn18","title":"JKN 18","content_type":"livetv","category":"livetv-ca|digitaltv-ca|freetv-ca","content_provider":"","channel_code":"d11","content_rights":null,"channel_info":{"channel_name_eng":"JKN 18","channel_name_mm":"JKN 18","channel_name_th":"เจเคเอ็น 18"},"views":1725,"isLiveChat":false},{"id":"rBWOx89v9Rk","thumb":"https://cms.dmpcdn.com/livetv/2023/09/09/9cc40970-4ebe-11ee-9801-97f95b5eed9a_webp_original.webp","slug":"9mcot-hd","title":"9 MCOT","content_type":"livetv","category":"livetv-ca|digitaltv-ca|freetv-ca","content_provider":"","channel_code":"c09","content_rights":null,"channel_info":{"channel_name_cbd":"ប៉ុស្តិ៍ 9 HD","channel_name_eng":"9 MCOT HD","channel_name_mm":"9 MCOT HD","channel_name_th":"9 เอ็มคอต HD"},"views":1323,"isLiveChat":false},{"id":"5PKobQk5gLOP","thumb":"https://cms.dmpcdn.com/livetv/2023/07/05/b74a2460-1b05-11ee-8ce6-b102b53cb4a2_webp_original.webp","slug":"boomerang-hd","title":"Boomerang","content_type":"livetv","category":"livetv-ca|freetv-ca|kids-ca","content_provider":"","channel_code":"i007","content_rights":null,"channel_info":{"channel_name_eng":"Boomerang","channel_name_th":"บูมเมอแรง"},"views":707,"isLiveChat":false},{"id":"KEN52vz3o6M","thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/296e96a0-e593-11ed-8507-4fc0b025fedb_webp_original.png","slug":"truesport-hd-3","title":"True Sports 3","content_type":"livetv","category":"livetv-ca|sports-ca|tvsnow|sports","content_provider":"true_vision","channel_code":"ht117","content_rights":null,"channel_info":{"channel_name_cbd":"True Sports 3","channel_name_chi":"True Sports 3","channel_name_eng":"True Sports 3","channel_name_mm":"True Sports 3","channel_name_rus":"True Sports 3","channel_name_th":"ทรูสปอร์ต 3","channel_name_vie":"True Sports 3"},"views":652,"isLiveChat":true},{"id":"1KDEkNJDZ9r","thumb":"https://cms.dmpcdn.com/livetv/2023/07/18/7e060a10-2515-11ee-864f-a52221dad038_webp_original.webp","slug":"ch5","title":"TV5 HD","content_type":"livetv","category":"livetv-ca|digitaltv-ca|freetv-ca|news-ca","content_provider":"","channel_code":"c05","content_rights":null,"channel_info":{"channel_name_cbd":"ប៉ុស្តិ៍ 5","channel_name_eng":"CH 5","channel_name_mm":"နံပါတ္ 5 အစီအစဥ","channel_name_th":"ช่อง 5"},"views":648,"isLiveChat":false},{"id":"NopZ5gjkGmE","thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/45345d10-e599-11ed-86b8-bb40638e3c49_webp_original.png","slug":"true-movie-hits","title":"True Movie Hits","content_type":"livetv","category":"livetv-ca|movies-series-ca|trueunlock-ca|tvsnow|movieseries","content_provider":"true_vision","channel_code":"057","content_rights":null,"channel_info":{"channel_name_cbd":"True Movie Hits","channel_name_chi":"True Movie Hits","channel_name_eng":"True Movie Hits","channel_name_mm":"True Movie Hits","channel_name_rus":"True Movie Hits","channel_name_th":"True Movie Hits","channel_name_vie":"True Movie Hits"},"views":640,"isLiveChat":false},{"id":"9xQq7Yk7Jzr","thumb":"https://cms.dmpcdn.com/livetv/2023/07/18/7d74eda0-2515-11ee-864f-a52221dad038_webp_original.webp","slug":"realitychannel-hd","title":"Reality","content_type":"livetv","category":"livetv-ca|education-ca|entertainment-ca|freetv-ca|kids-ca|truelittlemonk|tvsnow|entertainment","content_provider":"true_vision","channel_code":"107","content_rights":null,"channel_info":{"channel_name_cbd":"ប៉ុស្តិ៍ រែលអាលីធី","channel_name_eng":"Reality","channel_name_mm":"ထရူး ပူပန္ယာ","channel_name_th":"เรียลลิตี้"},"views":518,"isLiveChat":false},{"id":"GPVMYwpnzKv","thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/f63723d0-e595-11ed-abcb-c792e696f885_webp_original.png","slug":"truesport-7","title":"True Sports 7","content_type":"livetv","category":"livetv-ca|sports-ca|trueunlock-ca|tvsnow|sports","content_provider":"true_vision","channel_code":"105","content_rights":null,"channel_info":{"channel_name_cbd":"True Sports 7","channel_name_chi":"True Sports 7","channel_name_eng":"True Sports 7","channel_name_mm":"True Sports 7","channel_name_rus":"True Sports 7","channel_name_th":"ทรูสปอร์ต 7","channel_name_vie":"True Sports 7"},"views":499,"isLiveChat":true},{"id":"RN8ALdyRovrj","thumb":"https://cms.dmpcdn.com/livetv/2022/02/10/e00c0e00-8a3c-11ec-8f9e-831d2ccecc69_webp_original.png","slug":"t-sports-7-sd","title":"T Sports 7","content_type":"livetv","category":"livetv-ca|digitaltv-ca|freetv-ca|sports-ca","content_provider":"","channel_code":"t514","content_rights":null,"channel_info":{"channel_name_eng":"T Sports 7","channel_name_th":"สถานีโทรทัศน์เพื่อการท่องเที่ยวและกีฬา"},"views":484,"isLiveChat":false},{"id":"AlPo3NzNZa62","thumb":"https://cms.dmpcdn.com/livetv/2023/05/03/ba4c4510-e966-11ed-896e-69ce273284a6_webp_original.png","slug":"truepremierfootballhd2","title":"True Premier Football 2","content_type":"livetv","category":"livetv-ca|football-ca|sports-ca|tvsnow|sports","content_provider":"true_vision","channel_code":"ht112","content_rights":null,"channel_info":{"channel_name_eng":"True Premier Football 2","channel_name_th":"ทรู พรีเมียร์ ฟุตบอล 2"},"views":449,"isLiveChat":true},{"id":"PanRBOzKovQ","thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/43f28e40-e599-11ed-844f-795506bf0bf9_webp_original.png","slug":"true-film-hd","title":"True Film 1","content_type":"livetv","category":"hbtv-trueidtv-all|livetv-ca|movies-series-ca|trueidtv-movies-series|tvsnow|movieseries","content_provider":"true_vision","channel_code":"176","content_rights":null,"channel_info":{"channel_name_cbd":"True Film 1","channel_name_chi":"True Film 1","channel_name_eng":"True Film 1","channel_name_mm":"True Film 1","channel_name_rus":"True Film 1","channel_name_th":"True Film 1","channel_name_vie":"True Film 1"},"views":388,"isLiveChat":false},{"id":"GNd67OBJ6pv","thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/46f1c480-e599-11ed-96ec-4d05b9e2ca86_webp_original.png","slug":"thai-film","title":"True Thai Film","content_type":"livetv","category":"livetv-ca|movies-series-ca|trueunlock-ca|tvsnow|movieseries","content_provider":"true_vision","channel_code":"094","content_rights":null,"channel_info":{"channel_name_cbd":"True Thai Film","channel_name_chi":"True Thai Film","channel_name_eng":"True Thai Film","channel_name_mm":"True Thai Film","channel_name_rus":"True Thai Film","channel_name_th":"True Thai Film","channel_name_vie":"True Thai Film"},"views":359,"isLiveChat":false},{"id":"a0k7zw9OPrr0","thumb":"https://cms.dmpcdn.com/livetv/2020/07/14/72c22620-c5aa-11ea-a8d3-2b56c8ce453d_original.png","slug":"altv","title":"ALTV","content_type":"livetv","category":"livetv-ca|digitaltv-ca|education-ca|freetv-ca","content_provider":"","channel_code":"dum024","content_rights":null,"channel_info":{"channel_name_eng":"ALTV","channel_name_th":"เอแอลทีวี"},"views":255,"isLiveChat":false},{"id":"9WmoQMj0NOp","thumb":"https://cms.dmpcdn.com/livetv/2023/11/22/932dbce0-8919-11ee-820d-0ff332ca746f_webp_original.webp","slug":"trueplookpanya","title":"True Plook Panya","content_type":"livetv","category":"livetv-ca|documentary-ca|tvsnow|documentary","content_provider":"true_vision","channel_code":"139","content_rights":null,"channel_info":{"channel_name_cbd":"ទ្រូបណ្តុះគំណិត","channel_name_eng":"True Plookpanya","channel_name_mm":"True Plookpanya","channel_name_th":"ทรู ปลูกปัญญา"},"views":242,"isLiveChat":false},{"id":"KlW9OymBRqrD","thumb":"https://cms.dmpcdn.com/livetv/2023/09/25/c3898e20-5b4f-11ee-a599-1d1a4f7c1125_webp_original.webp","slug":"trueid-sports","title":"TrueID Sports","content_type":"livetv","category":"livetv-ca|sports-ca","content_provider":"","channel_code":"he003","content_rights":null,"channel_info":{"channel_name_eng":"TrueID Sports","channel_name_th":"ทรูไอดี สปอร์ต"},"views":240,"isLiveChat":true},{"id":"Vwz1j7XVRkdn","thumb":"https://cms.dmpcdn.com/livetv/2023/08/02/89262f60-30e1-11ee-b445-3703761d6f4d_webp_original.webp","slug":"true-ball-thai-1","title":"True Ball Thai 1","content_type":"livetv","category":"livetv-ca|football-ca|sports-ca|tvsnow|sports","content_provider":"true_vision","channel_code":"vc01","content_rights":null,"channel_info":{"channel_name_eng":"True Ball Thai 1","channel_name_th":"True Ball Thai 1"},"views":234,"isLiveChat":false},{"id":"YmaygkwgE6Lm","thumb":"https://cms.dmpcdn.com/livetv/2023/10/02/75023d90-60f1-11ee-935a-5d4eba985103_webp_original.webp","slug":"tnn16-hd","title":"TNN 16 HD","content_type":"livetv","category":"livetv-ca|news-ca|tnn|tvsnow|tvsnews","content_provider":"true_vision","channel_code":"t516","content_rights":null,"channel_info":{"channel_name_eng":"TNN​ 16 HD","channel_name_th":"TNN 16 HD"},"views":233,"isLiveChat":false},{"id":"GdgqaeMewGp4","thumb":"https://cms.dmpcdn.com/livetv/2023/05/03/baf9ea30-e966-11ed-a3d3-f3f98ac7a1a1_webp_original.png","slug":"truepremierfootballhd3","title":"True Premier Football 3","content_type":"livetv","category":"livetv-ca|football-ca|sports-ca|tvsnow|sports","content_provider":"true_vision","channel_code":"ht113","content_rights":null,"channel_info":{"channel_name_eng":"True Premier Football 3","channel_name_th":"ทรู พรีเมียร์ ฟุตบอล 3"},"views":199,"isLiveChat":true},{"id":"A36nrdXGn3V","thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/47a53600-e599-11ed-94a2-8feec94a4a3b_webp_original.png","slug":"true-asian-more","title":"True Asian More","content_type":"livetv","category":"livetv-ca|movies-series-ca|trueunlock-ca|tvsnow|movieseries","content_provider":"true_vision","channel_code":"081","content_rights":null,"channel_info":{"channel_name_cbd":"True Asian More","channel_name_chi":"True Asian More","channel_name_eng":"True Asian More","channel_name_mm":"True Asian More","channel_name_rus":"True Asian More","channel_name_th":"True Asian More","channel_name_vie":"True Asian More"},"views":190,"isLiveChat":false},{"id":"74ngXBo8ke0","thumb":"https://cms.dmpcdn.com/livetv/2019/01/21/a01a26bb-ed4a-45c5-88a9-ff30f6bbb039.png","slug":"cartoonclub","title":"Cartoon Club","content_type":"livetv","category":"cartoon|hbtv-trueidtv-all|hbtv-truetv-kids|trueidtv-all|trueidtv-kids|kids|livetv-ca|kids-ca","content_provider":"","channel_code":"143","content_rights":null,"channel_info":{"channel_name_cbd":"កាទូនខ្លឹប","channel_name_eng":"Cartoon Club","channel_name_mm":"ကာတြန္းကလပ္","channel_name_th":"การ์ตูน คลับ"},"views":189,"isLiveChat":false},{"id":"4QmJ09AyPm4","thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/43ffada0-e599-11ed-abcb-c792e696f885_webp_original.png","slug":"true-film-hd-2","title":"True Film 2","content_type":"livetv","category":"hbtv-truetv-movies-series|livetv-ca|movies-series-ca|tvsnow|movieseries","content_provider":"true_vision","channel_code":"221","content_rights":null,"channel_info":{"channel_name_cbd":"True Film 2","channel_name_chi":"True Film 2","channel_name_eng":"True Film 2","channel_name_mm":"True Film 2","channel_name_rus":"True Film 2","channel_name_th":"True Film 2","channel_name_vie":"True Film 2"},"views":185,"isLiveChat":false},{"id":"wQZrKd3mo65","thumb":"https://cms.dmpcdn.com/livetv/2023/04/24/81825540-e28a-11ed-9bb2-7fe2e28bfd8c_webp_original.png","slug":"truesport-hd","title":"True Sports 1","content_type":"livetv","category":"livetv-ca|sports-ca|tvsnow|sports","content_provider":"true_vision","channel_code":"097","content_rights":null,"channel_info":{"channel_name_cbd":"True Sports 1","channel_name_chi":"True Sports 1","channel_name_eng":"True Sports 1","channel_name_mm":"True Sports 1","channel_name_rus":"True Sports 1","channel_name_th":"ทรูสปอร์ต 1","channel_name_vie":"True Sports 1"},"views":179,"isLiveChat":true},{"id":"Lzz61DA3zYL","thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/486c7da0-e599-11ed-b481-1b121c78e74e_webp_original.png","slug":"true-explore-life","title":"True Explore Life","content_type":"livetv","category":"livetv-ca|documentary-ca|trueunlock-ca|tvsnow|documentary","content_provider":"true_vision","channel_code":"060","content_rights":null,"channel_info":{"channel_name_cbd":"True Explore Life","channel_name_chi":"True Explore Life","channel_name_eng":"True Explore Life","channel_name_mm":"True Explore Life","channel_name_rus":"True Explore Life","channel_name_th":"True Explore Life","channel_name_vie":"True Explore Life"},"views":124,"isLiveChat":false},{"id":"vNG2L371k5W","thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/433fe010-e599-11ed-96ec-4d05b9e2ca86_webp_original.png","slug":"true-explore-wild","title":"True Explore Wild","content_type":"livetv","category":"livetv-ca|documentary-ca|trueunlock-ca|true-unlock|true-unlock-atv|tvsnow|documentary","content_provider":"true_vision","channel_code":"058","content_rights":null,"channel_info":{"channel_name_cbd":"True Explore Wild","channel_name_eng":"True Explore Wild","channel_name_mm":"True Explore Wild","channel_name_th":"True Explore Wild"},"views":119,"isLiveChat":false},{"id":"jqepWV3ka8j","thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/46fb6170-e599-11ed-b606-c19576cb8b29_webp_original.png","slug":"true-x-zyte-hd","title":"True X-Zyte","content_type":"livetv","category":"livetv-ca|entertainment-ca|trueunlock-ca|tvsnow|entertainment","content_provider":"true_vision","channel_code":"034","content_rights":null,"channel_info":{"channel_name_cbd":"True X-Zyte","channel_name_chi":"True X-Zyte","channel_name_eng":"True X-Zyte","channel_name_mm":"True X-Zyte","channel_name_rus":"True X-Zyte","channel_name_th":"True X-Zyte","channel_name_vie":"True X-Zyte"},"views":107,"isLiveChat":false},{"id":"3wLvyKyryPAD","thumb":"https://cms.dmpcdn.com/livetv/2023/07/24/5fda18e0-29cc-11ee-846b-a1c4e5181c87_webp_original.webp","slug":"bein-sports-hd3","title":"beIN SPORTS 3","content_type":"livetv","category":"livetv-ca|football-ca|sports-ca|tvsnow|sports","content_provider":"true_vision","channel_code":"215","content_rights":null,"channel_info":{"channel_name_eng":"beIN SPORTS 3","channel_name_th":"บีอินสปอตส์ 3"},"views":96,"isLiveChat":false},{"id":"mVoXV1rk4B5","thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/488c61b0-e599-11ed-94a2-8feec94a4a3b_webp_original.png","slug":"true-explore-3","title":"True Explore Sci","content_type":"livetv","category":"livetv-ca|documentary-ca|trueunlock-ca|tvsnow|documentary","content_provider":"true_vision","channel_code":"061","content_rights":null,"channel_info":{"channel_name_cbd":"True Explore Sci","channel_name_chi":"True Explore Sci","channel_name_eng":"True Explore Sci","channel_name_mm":"True Explore Sci","channel_name_rus":"True Explore Sci","channel_name_th":"True Explore Sci","channel_name_vie":"True Explore Sci"},"views":91,"isLiveChat":false},{"id":"D1029rjaV6GQ","thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/0c14dd80-9407-11ee-b625-274874732f96_webp_original.webp","slug":"manchester-united","title":"Manchester United","content_type":"livetv","category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all","content_provider":"","channel_code":"mun01","content_rights":null,"channel_info":{"channel_name_eng":"Manchester United","channel_name_th":"แมนยู"},"views":91,"isLiveChat":false},{"id":"peWQgAb52vk","thumb":"https://cms.dmpcdn.com/livetv/2023/07/18/7cb4aae0-2515-11ee-9407-9367a664b338_webp_original.webp","slug":"golf-channel","title":"Golf Channel Thailand","content_type":"livetv","category":"livetv-ca|sports-ca|tvsnow|sports","content_provider":"true_vision","channel_code":"095","content_rights":null,"channel_info":{"channel_name_cbd":"Golf Channel Thailand HD","channel_name_chi":"Golf Channel Thailand HD","channel_name_eng":"Golf Channel Thailand HD","channel_name_mm":"Golf Channel Thailand HD","channel_name_rus":"Golf Channel Thailand HD","channel_name_th":"Golf Channel Thailand HD","channel_name_vie":"Golf Channel Thailand HD"},"views":88,"isLiveChat":false},{"id":"A8aVZWzlOmDE","thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/d7783cc0-9406-11ee-b445-0b5cfb8bf6f8_webp_original.webp","slug":"liverpool","title":"Liverpool","content_type":"livetv","category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all","content_provider":"","channel_code":"liv01","content_rights":null,"channel_info":{"channel_name_eng":"Liverpool","channel_name_th":"ลิเวอร์พูล"},"views":88,"isLiveChat":false},{"id":"Ay93Q8zlOeA","thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/456c5d00-e599-11ed-b550-9935ba8025b9_webp_original.png","slug":"true-series","title":"True Series","content_type":"livetv","category":"livetv-ca|movies-series-ca|tvsnow|movieseries","content_provider":"true_vision","channel_code":"st006","content_rights":null,"channel_info":{"channel_name_cbd":"True Series","channel_name_chi":"True Series","channel_name_eng":"True Series","channel_name_mm":"True Series","channel_name_rus":"True Series","channel_name_th":"True Series","channel_name_vie":"True Series"},"views":82,"isLiveChat":false},{"id":"g9ONWXWJV5pq","thumb":"https://cms.dmpcdn.com/livetv/2023/07/24/5f3c5240-29cc-11ee-b2f4-e9de482d866e_webp_original.webp","slug":"bein-sports-hd1","title":"beIN SPORTS 1","content_type":"livetv","category":"livetv-ca|football-ca|sports-ca|tvsnow|sports","content_provider":"true_vision","channel_code":"202","content_rights":null,"channel_info":{"channel_name_eng":"beIN SPORTS 1","channel_name_th":"บีอินสปอตส์ 1"},"views":80,"isLiveChat":false},{"id":"xR0n6ePG7wL","thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/2960b3f0-e593-11ed-b26c-6b89d082d464_webp_original.png","slug":"truesport-hd-2","title":"True Sports 2","content_type":"livetv","category":"livetv-ca|football-ca|sports-ca|trueunlock-ca|tvsnow|sports","content_provider":"true_vision","channel_code":"ht116","content_rights":null,"channel_info":{"channel_name_cbd":"True Sports 2","channel_name_chi":"True Sports 2","channel_name_eng":"True Sports 2","channel_name_mm":"True Sports 2","channel_name_rus":"True Sports 2","channel_name_th":"ทรูสปอร์ต 2","channel_name_vie":"True Sports 2"},"views":80,"isLiveChat":true},{"id":"JlrpNK19py0M","thumb":"https://cms.dmpcdn.com/livetv/2019/04/11/19b4bd2a-750c-4ee6-9d41-5080e1310bc3_original.png","slug":"Mangorn","title":"Mangorn","content_type":"livetv","category":"free-tv|livetv-ca|freetv-ca|movies-series-ca","content_provider":"","channel_code":"o020","content_rights":null,"channel_info":{"channel_name_eng":"Mangorn","channel_name_th":"มังกร"},"views":73,"isLiveChat":false},{"id":"lPXDJR6gN6l","thumb":"https://cms.dmpcdn.com/livetv/2019/02/28/a9490c72-7387-4409-b5a8-80db28585ca4.png","slug":"true-select","title":"True Select","content_type":"livetv","category":"livetv-ca|entertainment-ca|variety-ca|tvsnow|entertainment","content_provider":"true_vision","channel_code":"218","content_rights":null,"channel_info":{"channel_name_cbd":"True Select","channel_name_chi":"True Select","channel_name_eng":"True Select","channel_name_mm":"True Select","channel_name_rus":"True Select","channel_name_th":"True Select","channel_name_vie":"True Select"},"views":71,"isLiveChat":false},{"id":"NWY5K7ZELP2","thumb":"https://cms.dmpcdn.com/livetv/2018/12/17/0c30b192-953b-49b9-a9bf-a4c6e3e71de3.png","slug":"true-select-hd","title":"True Shopping","content_type":"livetv","category":"livetv-ca|entertainment-ca|tvsnow|entertainment","content_provider":"true_vision","channel_code":"127","content_rights":null,"channel_info":{"channel_name_cbd":"True Shopping","channel_name_chi":"True Shopping","channel_name_eng":"True Shopping","channel_name_mm":"True Shopping","channel_name_rus":"True Shopping","channel_name_th":"True Shopping","channel_name_vie":"True Shopping"},"views":70,"isLiveChat":true},{"id":"r71LNbqjaKe","thumb":"https://cms.dmpcdn.com/livetv/2019/01/31/a5aeb78c-c4db-474f-a5af-345cb9e2f5b5.png","slug":"rama-channel","title":"Rama Channel","content_type":"livetv","category":"livetv-ca|documentary-ca|news-ca|tvsnow|documentary","content_provider":"true_vision","channel_code":"128","content_rights":null,"channel_info":{"channel_name_cbd":"Rama Channel","channel_name_chi":"Rama Channel","channel_name_eng":"Rama Channel","channel_name_mm":"Rama Channel","channel_name_rus":"Rama Channel","channel_name_th":"Rama Channel","channel_name_vie":"Rama Channel"},"views":69,"isLiveChat":false},{"id":"YLN6d3oYyXEL","thumb":"https://cms.dmpcdn.com/livetv/2023/11/22/2a4de600-8919-11ee-8416-3dc6bea66698_webp_original.webp","slug":"tptv","title":"TPTV","content_type":"livetv","category":"livetv-ca|digitaltv-ca|education-ca|freetv-ca","content_provider":"","channel_code":"d31","content_rights":null,"channel_info":{"channel_name_eng":"TPTV - Thai Parliament TV","channel_name_th":"ทีพีทีวี"},"views":61,"isLiveChat":false},{"id":"eXlvvZ4EA5aY","thumb":"https://cms.dmpcdn.com/livetv/2022/12/22/d9313340-81d9-11ed-a7f9-412bbba270e9_webp_original.png","slug":"tv-nfl-nba","title":"NFL & NBA TV","content_type":"livetv","category":"livetv-ca|sports-ca","content_provider":"true_vision","channel_code":"t513","content_rights":null,"channel_info":{"channel_name_eng":"NFL & NBA TV","channel_name_th":"เอ็นเอฟแอล แอนด์ เอ็นบีเอ ทีวี"},"views":59,"isLiveChat":false},{"id":"zmvD0RO72nL","thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/f493fb20-e595-11ed-b26c-6b89d082d464_webp_original.png","slug":"truesport-5","title":"True Sports 5","content_type":"livetv","category":"livetv-ca|sports-ca|tvsnow|sports","content_provider":"true_vision","channel_code":"056","content_rights":null,"channel_info":{"channel_name_cbd":"True Sports 5","channel_name_chi":"True Sports 5","channel_name_eng":"True Sports 5","channel_name_mm":"True Sports 5","channel_name_rus":"True Sports 5","channel_name_th":"ทรูสปอร์ต 5","channel_name_vie":"True Sports 5"},"views":58,"isLiveChat":false},{"id":"mXQoNYKda2L9","thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/434696d0-e599-11ed-b26c-6b89d082d464_webp_original.png","slug":"film-asia-hd","title":"True Film Asia","content_type":"livetv","category":"livetv-ca|movies-series-ca|tvsnow|movieseries","content_provider":"true_vision","channel_code":"t500","content_rights":null,"channel_info":{"channel_name_cbd":"True Film Asia","channel_name_chi":"True Film Asia","channel_name_eng":"True Film Asia","channel_name_mm":"True Film Asia","channel_name_rus":"True Film Asia","channel_name_th":"True Film Asia","channel_name_vie":"True Film Asia"},"views":55,"isLiveChat":false},{"id":"P83vkq1M1Lp","thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/46065310-e599-11ed-96ec-4d05b9e2ca86_webp_original.png","slug":"true-spark","title":"True Spark Play","content_type":"livetv","category":"livetv-ca|kids-ca|trueunlock-ca|tvsnow|kids","content_provider":"true_vision","channel_code":"007","content_rights":null,"channel_info":{"channel_name_cbd":"True Spark Play","channel_name_chi":"True Spark Play","channel_name_eng":"True Spark Play","channel_name_mm":"True Spark Play","channel_name_rus":"True Spark Play","channel_name_th":"True Spark Play","channel_name_vie":"True Spark Play"},"views":54,"isLiveChat":false},{"id":"2L1ZZdJGxPej","thumb":"https://cms.dmpcdn.com/livetv/2023/07/24/61050450-29cc-11ee-b2f4-e9de482d866e_webp_original.webp","slug":"spotv2-hd","title":"SPOTV 2","content_type":"livetv","category":"livetv-ca|sports-ca|tvsnow|sports","content_provider":"true_vision","channel_code":"t511","content_rights":null,"channel_info":{"channel_name_eng":"SPOTV 2","channel_name_th":"SPOTV 2"},"views":46,"isLiveChat":false},{"id":"Mbx79DOD44J","thumb":"https://cms.dmpcdn.com/livetv/2021/06/01/e2f61c80-c234-11eb-92e3-4bf272c5d086_original.png","slug":"true-music-channel-hd","title":"True Music","content_type":"livetv","category":"livetv-ca|entertainment-ca|trueunlock-ca|tvsnow|entertainment","content_provider":"true_vision","channel_code":"159","content_rights":null,"channel_info":{"channel_name_cbd":"True Music","channel_name_chi":"True Music","channel_name_eng":"True Music","channel_name_mm":"True Music","channel_name_rus":"True Music","channel_name_th":"True Music","channel_name_vie":"True Music"},"views":40,"isLiveChat":false},{"id":"leVMNwY8LA1B","thumb":"https://cms.dmpcdn.com/livetv/2021/02/24/cc08cbe0-764d-11eb-b272-17d04980ce1e_original.png","slug":"ATTV","title":"@TV","content_type":"livetv","category":"free-tv|livetv-ca|freetv-ca","content_provider":"","channel_code":"i002","content_rights":null,"channel_info":{"channel_name_eng":"@TV","channel_name_th":"แอททีวี"},"views":39,"isLiveChat":false},{"id":"V14w2AL9grW6","thumb":"https://cms.dmpcdn.com/livetv/2023/11/13/bd6a6d20-8205-11ee-822c-6bbb3f82c35b_webp_original.webp","slug":"voicetv-2023","title":"VOICE TV","content_type":"livetv","category":"livetv-ca|freetv-ca|news-ca","content_provider":"","channel_code":"154","content_rights":null,"channel_info":{"channel_name_cbd":"វ៉យធីវី","channel_name_eng":"Voice TV","channel_name_mm":"Voice TV","channel_name_th":"วอยซ์ ทีวี"},"views":33,"isLiveChat":false},{"id":"NB2d2A9Zd94z","thumb":"https://cms.dmpcdn.com/livetv/2023/09/25/c389b530-5b4f-11ee-a6f1-ffa978a40b9f_webp_original.webp","slug":"trueid-live","title":"TrueID Live","content_type":"livetv","category":"livetv-ca|entertainment-ca|variety-ca","content_provider":"","channel_code":"ev04","content_rights":null,"channel_info":{"channel_name_th":"ทรูไอดี ไลฟ์"},"views":31,"isLiveChat":true},{"id":"GOPVJMln56Y","thumb":"https://cms.dmpcdn.com/livetv/2020/06/23/816989d0-b550-11ea-8fac-236a281cd6c5_original.png","slug":"dharmatv","title":"Dhamma TV","content_type":"livetv","category":"knowledge|livetv-ca|digitaltv-ca|documentary-ca|trueidtv-all|trueidtv-digital-tv|variety","content_provider":"","channel_code":"o016","content_rights":null,"channel_info":{"channel_name_cbd":"ព្រះធម៌ធីវី","channel_name_eng":"Dhamma TV","channel_name_mm":"Dhamma TV","channel_name_th":"ธรรมะทีวี"},"views":31,"isLiveChat":false},{"id":"09BRRXKbgge9","thumb":"https://cms.dmpcdn.com/livetv/2022/03/23/f7108720-aa94-11ec-9b91-03afdbb2e824_webp_original.png","slug":"truepremierfootballhd6","title":"True Premier Football 6","content_type":"livetv","category":"livetv-ca|football-ca|sports-ca","content_provider":"true_vision","channel_code":"t502","content_rights":null,"channel_info":{"channel_name_eng":"True Premier Football 6","channel_name_th":"ทรู พรีเมียร์ ฟุตบอล 6"},"views":31,"isLiveChat":false},{"id":"Q7vaEm8O9e4","thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/f65ea900-e595-11ed-86b8-bb40638e3c49_webp_original.png","slug":"true-tennis-hd","title":"True Tennis","content_type":"livetv","category":"livetv-ca|sports-ca|tvsnow|sports","content_provider":"true_vision","channel_code":"045","content_rights":null,"channel_info":{"channel_name_cbd":"True Tennis","channel_name_chi":"True Tennis","channel_name_eng":"True Tennis","channel_name_mm":"True Tennis","channel_name_rus":"True Tennis","channel_name_th":"True Tennis","channel_name_vie":"True Tennis"},"views":29,"isLiveChat":false},{"id":"N8E7v0JlM15e","thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/94a32ae0-9406-11ee-a0fd-836d91d2dd6e_webp_original.webp","slug":"chelsea","title":"Chelsea","content_type":"livetv","category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all","content_provider":"","channel_code":"che01","content_rights":null,"channel_info":{"channel_name_eng":"Chelsea","channel_name_th":"เชลซี"},"views":28,"isLiveChat":false},{"id":"PdOXKN4O1vDr","thumb":"https://cms.dmpcdn.com/livetv/2023/09/25/c3898e20-5b4f-11ee-a599-1d1a4f7c1125_webp_original.webp","slug":"trueid-sports02","title":"TrueID Sports 2","content_type":"livetv","category":"livetv-ca|sports-ca|trueidtv-sport","content_provider":"","channel_code":"he004","content_rights":null,"channel_info":{"channel_name_eng":"TrueID Sports 2","channel_name_th":"ทรูไอดี สปอร์ต 2"},"views":26,"isLiveChat":false},{"id":"k3B64mk9ELl3","thumb":"https://cms.dmpcdn.com/livetv/2023/07/24/6057ad50-29cc-11ee-846b-a1c4e5181c87_webp_original.webp","slug":"golfchannel-thhdplus","title":"Golf Channel Thailand HD+","content_type":"livetv","category":"livetv-ca|sports-ca|tvsnow|sports","content_provider":"true_vision","channel_code":"t501","content_rights":null,"channel_info":{"channel_name_cbd":"Golf Channel Thailand HD Plus","channel_name_chi":"Golf Channel Thailand HD Plus","channel_name_eng":"Golf Channel Thailand HD Plus","channel_name_mm":"Golf Channel Thailand HD Plus","channel_name_rus":"Golf Channel Thailand HD Plus","channel_name_th":"Golf Channel Thailand HD Plus","channel_name_vie":"Golf Channel Thailand HD Plus"},"views":26,"isLiveChat":false},{"id":"zWoZqZv6J6N5","thumb":"https://cms.dmpcdn.com/livetv/2022/10/11/f09e41a0-492e-11ed-bb17-0527d4e1664c_webp_original.png","slug":"crime-investigation","title":"Crime + Investigation","content_type":"livetv","category":"livetv-ca|documentary-ca|tvsnow|documentary","content_provider":"true_vision","channel_code":"t517","content_rights":null,"channel_info":{"channel_name_eng":"Crime Investigation","channel_name_th":"ไคร์ม แอนด์ อินเวสทิเกชั่น"},"views":25,"isLiveChat":false},{"id":"bDKPPGOdyAmn","thumb":"https://cms.dmpcdn.com/livetv/2023/07/24/60cba4d0-29cc-11ee-b2f4-e9de482d866e_webp_original.webp","slug":"spotv1-hd","title":"SPOTV 1","content_type":"livetv","category":"livetv-ca|sports-ca|tvsnow|sports","content_provider":"true_vision","channel_code":"t510","content_rights":null,"channel_info":{"channel_name_eng":"SPOTV 1","channel_name_th":"SPOTV 1"},"views":25,"isLiveChat":false},{"id":"zpwxwAgYOV7n","thumb":"https://cms.dmpcdn.com/livetv/2022/02/17/3943ca00-8fd6-11ec-b076-dffedf0eab22_webp_original.png","slug":"white-channel-hd","title":"White Channel","content_type":"livetv","category":"hbtv-trueidtv-all|livetv-ca|freetv-ca","content_provider":"","channel_code":"i006","content_rights":null,"channel_info":{"channel_name_eng":"White Channel","channel_name_th":"ไวท์แชนแนล"},"views":25,"isLiveChat":false},{"id":"vW6BOL0AzxdW","thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/21b70060-9406-11ee-906d-89adbc3169c1_webp_original.webp","slug":"arsenal","title":"Arsenal","content_type":"livetv","category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all","content_provider":"","channel_code":"ars01","content_rights":null,"channel_info":{"channel_name_eng":"Arsenal","channel_name_th":"อาร์เซน่อล"},"views":25,"isLiveChat":false},{"id":"5YQaWExRqD5","thumb":"https://cms.dmpcdn.com/livetv/2020/05/18/87773550-98c4-11ea-b284-2bff0287c295_original.png","slug":"dltv-3","title":"DLTV 3","content_type":"livetv","category":"education|hbtv-trueidtv-all|livetv-ca|education-ca","content_provider":"","channel_code":"dum003","content_rights":null,"channel_info":{"channel_name_cbd":"DLTV 3","channel_name_chi":"DLTV 3","channel_name_eng":"DLTV 3","channel_name_mm":"DLTV 3","channel_name_rus":"DLTV 3","channel_name_th":"DLTV 3","channel_name_vie":"DLTV 3"},"views":23,"isLiveChat":false},{"id":"xPgxpqoyqQ62","thumb":"https://cms.dmpcdn.com/livetv/2021/01/06/68be8520-500f-11eb-8d28-4b8e3f30b51b_original.png","slug":"zing","title":"Zing","content_type":"livetv","category":"livetv-ca|entertainment-ca|movies-series-ca|trueidtv-all","content_provider":"","channel_code":"i001","content_rights":null,"channel_info":{"channel_name_cbd":"Zing","channel_name_chi":"Zing","channel_name_eng":"Zing","channel_name_mm":"Zing","channel_name_rus":"Zing","channel_name_th":"Zing","channel_name_vie":"Zing"},"views":22,"isLiveChat":false},{"id":"5XaDjQd1JJgw","thumb":"https://cms.dmpcdn.com/livetv/2023/07/24/5f346300-29cc-11ee-b2f4-e9de482d866e_webp_original.webp","slug":"bein-sports-hd2","title":"beIN SPORTS 2","content_type":"livetv","category":"livetv-ca|football-ca|sports-ca|trueidtv-all|tvsnow|sports","content_provider":"true_vision","channel_code":"t521","content_rights":null,"channel_info":{"channel_name_eng":"beIN SPORTS 2","channel_name_th":"บีอินสปอตส์ 2"},"views":22,"isLiveChat":false},{"id":"pmXrb1NjLeP0","thumb":"https://cms.dmpcdn.com/livetv/2023/05/03/bbea1690-e966-11ed-935b-df134f58d288_webp_original.png","slug":"truepremierfootballhd5","title":"True Premier Football 5","content_type":"livetv","category":"livetv-ca|football-ca|sports-ca|tvsnow|sports","content_provider":"true_vision","channel_code":"ht115","content_rights":null,"channel_info":{"channel_name_eng":"True Premier Football 5","channel_name_th":"ทรู พรีเมียร์ ฟุตบอล 5"},"views":21,"isLiveChat":false},{"id":"GDna51EdVk4","thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/f4888970-e595-11ed-8507-4fc0b025fedb_webp_original.png","slug":"truesport-hd-4","title":"True Sports 4","content_type":"livetv","category":"livetv-ca|sports-ca|tvsnow|sports","content_provider":"true_vision","channel_code":"062","content_rights":null,"channel_info":{"channel_name_cbd":"True Sports 4","channel_name_chi":"True Sports 4","channel_name_eng":"True Sports 4","channel_name_mm":"True Sports 4","channel_name_rus":"True Sports 4","channel_name_th":"ทรูสปอร์ต 4","channel_name_vie":"True Sports 4"},"views":21,"isLiveChat":false},{"id":"o9vKOR0dLVm7","thumb":"https://cms.dmpcdn.com/livetv/2023/09/25/c3898e20-5b4f-11ee-a599-1d1a4f7c1125_webp_original.webp","slug":"trueid-sports03","title":"TrueID Sports 3","content_type":"livetv","category":"livetv-ca|sports-ca|trueidtv-sport","content_provider":"","channel_code":"he005","content_rights":null,"channel_info":{"channel_name_eng":"TrueID Sports 3","channel_name_th":"ทรูไอดี สปอร์ต 3"},"views":20,"isLiveChat":false},{"id":"rO7WMREyepr","thumb":"https://cms.dmpcdn.com/livetv/2020/05/18/c273ea90-98c4-11ea-bcb3-0320ce420b5e_original.png","slug":"dltv-15","title":"DLTV 15","content_type":"livetv","category":"education|hbtv-trueidtv-all|livetv-ca|education-ca","content_provider":"","channel_code":"dum015","content_rights":null,"channel_info":{"channel_name_cbd":"DLTV 15","channel_name_chi":"DLTV 15","channel_name_eng":"DLTV 15","channel_name_mm":"DLTV 15","channel_name_rus":"DLTV 15","channel_name_th":"DLTV 15","channel_name_vie":"DLTV 15"},"views":20,"isLiveChat":false},{"id":"67ollp0Raz2V","thumb":"https://cms.dmpcdn.com/livetv/2022/03/23/f7114a70-aa94-11ec-9b91-03afdbb2e824_webp_original.png","slug":"truepremierfootballhd7","title":"True Premier Football 7","content_type":"livetv","category":"livetv-ca|football-ca|sports-ca","content_provider":"true_vision","channel_code":"t503","content_rights":null,"channel_info":{"channel_name_eng":"True Premier Football 7","channel_name_th":"ทรู พรีเมียร์ ฟุตบอล 7"},"views":18,"isLiveChat":false},{"id":"2KyzkV6AyPZ","thumb":"https://cms.dmpcdn.com/livetv/2020/05/18/65dc9bb0-98c4-11ea-bcb3-0320ce420b5e_original.png","slug":"dltv-1","title":"DLTV 1","content_type":"livetv","category":"education|hbtv-trueidtv-all|livetv-ca|education-ca","content_provider":"","channel_code":"dum001","content_rights":null,"channel_info":{"channel_name_cbd":"DLTV 1","channel_name_chi":"DLTV 1","channel_name_eng":"DLTV 1","channel_name_mm":"DLTV 1","channel_name_rus":"DLTV 1","channel_name_th":"DLTV 1","channel_name_vie":"DLTV 1"},"views":17,"isLiveChat":false},{"id":"gqVn9n7MeYXq","thumb":"https://cms.dmpcdn.com/livetv/2022/09/08/e55200a0-2f27-11ed-a458-efe831982670_webp_original.png","slug":"arirang-tv","title":"Arirang TV","content_type":"livetv","category":"livetv-ca|entertainment-ca|tvsnow|entertainment","content_provider":"true_vision","channel_code":"t519","content_rights":null,"channel_info":{"channel_name_eng":"Arirang TV","channel_name_th":"Arirang TV"},"views":16,"isLiveChat":false},{"id":"r4PaaOpzr0Ow","thumb":"https://cms.dmpcdn.com/livetv/2022/03/23/f868c420-aa94-11ec-9b91-03afdbb2e824_webp_original.png","slug":"truepremierfootballhd8","title":"True Premier Football 8","content_type":"livetv","category":"livetv-ca|football-ca|sports-ca","content_provider":"true_vision","channel_code":"t504","content_rights":null,"channel_info":{"channel_name_eng":"True Premier Football 8","channel_name_th":"ทรู พรีเมียร์ ฟุตบอล 8"},"views":16,"isLiveChat":false},{"id":"Kz5zjkGyDVA","thumb":"https://cms.dmpcdn.com/livetv/2020/05/18/66ce9cd0-98c4-11ea-b284-2bff0287c295_original.png","slug":"dltv-6","title":"DLTV 6","content_type":"livetv","category":"education|hbtv-trueidtv-all|livetv-ca|education-ca","content_provider":"","channel_code":"dum006","content_rights":null,"channel_info":{"channel_name_cbd":"DLTV 6","channel_name_chi":"DLTV 6","channel_name_eng":"DLTV 6","channel_name_mm":"DLTV 6","channel_name_rus":"DLTV 6","channel_name_th":"DLTV 6","channel_name_vie":"DLTV 6"},"views":16,"isLiveChat":false},{"id":"Veb1NRpQ6LXk","thumb":"https://cms.dmpcdn.com/livetv/2022/02/10/16689820-8a46-11ec-8573-9fd52c482da3_webp_original.png","slug":"zee-anmol-sd","title":"Zee Anmol","content_type":"livetv","category":"livetv-ca|entertainment-ca|freetv-ca|movies-series-ca","content_provider":"","channel_code":"i005","content_rights":null,"channel_info":{"channel_name_eng":"Zee Anmol","channel_name_th":"Zee Anmol"},"views":15,"isLiveChat":false},{"id":"L3Jbvn0BnbA","thumb":"https://cms.dmpcdn.com/livetv/2020/05/18/65debe90-98c4-11ea-b284-2bff0287c295_original.png","slug":"dltv-2","title":"DLTV 2","content_type":"livetv","category":"education|hbtv-trueidtv-all|livetv-ca|education-ca","content_provider":"","channel_code":"dum002","content_rights":null,"channel_info":{"channel_name_cbd":"DLTV 2","channel_name_chi":"DLTV 2","channel_name_eng":"DLTV 2","channel_name_mm":"DLTV 2","channel_name_rus":"DLTV 2","channel_name_th":"DLTV 2","channel_name_vie":"DLTV 2"},"views":15,"isLiveChat":false},{"id":"v2M0K4kgbrN","thumb":"https://cms.dmpcdn.com/livetv/2020/05/18/66317270-98c4-11ea-b284-2bff0287c295_original.png","slug":"dltv-4","title":"DLTV 4","content_type":"livetv","category":"education|hbtv-trueidtv-all|livetv-ca|education-ca","content_provider":"","channel_code":"dum004","content_rights":null,"channel_info":{"channel_name_cbd":"DLTV 4","channel_name_chi":"DLTV 4","channel_name_eng":"DLTV 4","channel_name_mm":"DLTV 4","channel_name_rus":"DLTV 4","channel_name_th":"DLTV 4","channel_name_vie":"DLTV 4"},"views":15,"isLiveChat":false},{"id":"RGdlapJnLQNG","thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/f8af80b0-9406-11ee-8d65-879d2e0f23a3_webp_original.webp","slug":"manchester-city","title":"Manchester City","content_type":"livetv","category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all","content_provider":"","channel_code":"mci01","content_rights":null,"channel_info":{"channel_name_eng":"Manchester City","channel_name_th":"แมนซิตี้"},"views":15,"isLiveChat":false},{"id":"JkpG4LeljXJ0","thumb":"https://cms.dmpcdn.com/livetv/2023/08/02/8a0c49a0-30e1-11ee-b220-4544ede97b74_webp_original.webp","slug":"true-ball-thai-2","title":"True Ball Thai 2","content_type":"livetv","category":"livetv-ca|football-ca|sports-ca|tvsnow|sports","content_provider":"true_vision","channel_code":"vc02","content_rights":null,"channel_info":{"channel_name_eng":"True Ball Thai 2","channel_name_th":"True Ball Thai 2"},"views":14,"isLiveChat":false},{"id":"Yb4p39lbgvN","thumb":"https://cms.dmpcdn.com/livetv/2020/05/18/6683b120-98c4-11ea-b284-2bff0287c295_original.png","slug":"dltv-5","title":"DLTV 5","content_type":"livetv","category":"education|hbtv-trueidtv-all|livetv-ca|education-ca","content_provider":"","channel_code":"dum005","content_rights":null,"channel_info":{"channel_name_cbd":"DLTV 5","channel_name_chi":"DLTV 5","channel_name_eng":"DLTV 5","channel_name_mm":"DLTV 5","channel_name_rus":"DLTV 5","channel_name_th":"DLTV 5","channel_name_vie":"DLTV 5"},"views":14,"isLiveChat":false},{"id":"D38Lb540KAE3","thumb":"https://cms.dmpcdn.com/livetv/2021/02/24/cc358130-764d-11eb-9057-2d10fb4d0cf4_original.png","slug":"MediaTV","title":"Media TV","content_type":"livetv","category":"free-tv|livetv-ca|freetv-ca","content_provider":"","channel_code":"i003","content_rights":null,"channel_info":{"channel_name_eng":"Media TV","channel_name_th":"มีเดีย ทีวี"},"views":13,"isLiveChat":false},{"id":"JGAQ7VZpX9Y","thumb":"https://cms.dmpcdn.com/livetv/2020/05/18/044a2a10-98c5-11ea-bcb3-0320ce420b5e_original.png","slug":"dltv-12","title":"DLTV 12","content_type":"livetv","category":"education|hbtv-trueidtv-all|livetv-ca|education-ca","content_provider":"","channel_code":"dum012","content_rights":null,"channel_info":{"channel_name_cbd":"DLTV 12","channel_name_chi":"DLTV 12","channel_name_eng":"DLTV 12","channel_name_mm":"DLTV 12","channel_name_rus":"DLTV 12","channel_name_th":"DLTV 12","channel_name_vie":"DLTV 12"},"views":13,"isLiveChat":false},{"id":"zyab4aWZ0OWx","thumb":"https://cms.dmpcdn.com/livetv/2023/05/03/bb0beb90-e966-11ed-993c-b59183950f79_webp_original.png","slug":"truepremierfootballhd4","title":"True Premier Football 4","content_type":"livetv","category":"livetv-ca|football-ca|sports-ca|tvsnow|sports","content_provider":"true_vision","channel_code":"ht114","content_rights":null,"channel_info":{"channel_name_eng":"True Premier Football 4","channel_name_th":"ทรู พรีเมียร์ ฟุตบอล 4"},"views":12,"isLiveChat":false},{"id":"pQ6ok8M72AD","thumb":"https://cms.dmpcdn.com/livetv/2020/05/18/bfd21690-98c4-11ea-bcb3-0320ce420b5e_original.png","slug":"dltv-10","title":"DLTV 10","content_type":"livetv","category":"education|hbtv-trueidtv-all|livetv-ca|education-ca","content_provider":null,"channel_code":"dum010","content_rights":null,"channel_info":{"channel_name_cbd":"DLTV 10","channel_name_chi":"DLTV 10","channel_name_eng":"DLTV 10","channel_name_mm":"DLTV 10","channel_name_rus":"DLTV 10","channel_name_th":"DLTV 10","channel_name_vie":"DLTV 10"},"views":12,"isLiveChat":false},{"id":"wkrQgY603zM","thumb":"https://cms.dmpcdn.com/livetv/2020/05/18/c2364550-98c4-11ea-bcb3-0320ce420b5e_original.png","slug":"dltv-14","title":"DLTV 14","content_type":"livetv","category":"education|hbtv-trueidtv-all|livetv-ca|education-ca","content_provider":"","channel_code":"dum014","content_rights":null,"channel_info":{"channel_name_cbd":"DLTV 14","channel_name_chi":"DLTV 14","channel_name_eng":"DLTV 14","channel_name_mm":"DLTV 14","channel_name_rus":"DLTV 14","channel_name_th":"DLTV 14","channel_name_vie":"DLTV 14"},"views":12,"isLiveChat":false},{"id":"nYvz5QLWjyD","thumb":"https://cms.dmpcdn.com/livetv/2020/05/18/67145860-98c4-11ea-b284-2bff0287c295_original.png","slug":"dltv-7","title":"DLTV 7","content_type":"livetv","category":"education|hbtv-trueidtv-all|livetv-ca|education-ca","content_provider":"","channel_code":"dum007","content_rights":null,"channel_info":{"channel_name_cbd":"DLTV 7","channel_name_chi":"DLTV 7","channel_name_eng":"DLTV 7","channel_name_mm":"DLTV 7","channel_name_rus":"DLTV 7","channel_name_th":"DLTV 7","channel_name_vie":"DLTV 7"},"views":11,"isLiveChat":false},{"id":"MndX5W8rWaMn","thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/7003e750-9407-11ee-b445-0b5cfb8bf6f8_webp_original.webp","slug":"tottenham-hotspur","title":"Tottenham Hotspur","content_type":"livetv","category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all","content_provider":"","channel_code":"tot01","content_rights":null,"channel_info":{"channel_name_eng":"Tottenham Hotspur","channel_name_th":"สเปอร์"},"views":11,"isLiveChat":false},{"id":"E9k68z9aKDp","thumb":"https://cms.dmpcdn.com/livetv/2017/10/18/f1b957db-b175-45fc-ab2b-60150f9c570a.png","slug":"tnn-2","title":"TNN 2","content_type":"livetv","category":"livetv-ca|freetv-ca|news-ca|tvsnow|tvsnews","content_provider":"true_vision","channel_code":"074","content_rights":null,"channel_info":{"channel_name_cbd":"TNN 2","channel_name_chi":"TNN 2","channel_name_eng":"TNN 2","channel_name_mm":"TNN 2","channel_name_rus":"TNN 2","channel_name_th":"TNN 2","channel_name_vie":"TNN 2"},"views":10,"isLiveChat":false},{"id":"JeQ5L9PpVBJ","thumb":"https://cms.dmpcdn.com/livetv/2020/05/18/bf927580-98c4-11ea-bcb3-0320ce420b5e_original.png","slug":"dltv-9","title":"DLTV 9","content_type":"livetv","category":"education|hbtv-trueidtv-all|livetv-ca|education-ca","content_provider":"","channel_code":"dum009","content_rights":null,"channel_info":{"channel_name_cbd":"DLTV 9","channel_name_chi":"DLTV 9","channel_name_eng":"DLTV 9","channel_name_mm":"DLTV 9","channel_name_rus":"DLTV 9","channel_name_th":"DLTV 9","channel_name_vie":"DLTV 9"},"views":10,"isLiveChat":false},{"id":"M34YDGLk2wVj","thumb":"https://cms.dmpcdn.com/livetv/2023/08/02/8a3b21d0-30e1-11ee-a53e-b3f87dc8ba1e_webp_original.webp","slug":"true-ball-thai-3","title":"True Ball Thai 3","content_type":"livetv","category":"livetv-ca|football-ca|sports-ca|tvsnow|sports","content_provider":"true_vision","channel_code":"vc03","content_rights":null,"channel_info":{"channel_name_eng":"True Ball Thai 3","channel_name_th":"True Ball Thai 3"},"views":9,"isLiveChat":false},{"id":"R4WyxL6Mp8b","thumb":"https://cms.dmpcdn.com/livetv/2020/05/18/bf9386f0-98c4-11ea-b284-2bff0287c295_original.png","slug":"dltv-8","title":"DLTV 8","content_type":"livetv","category":"education|hbtv-trueidtv-all|livetv-ca|education-ca","content_provider":"","channel_code":"dum008","content_rights":null,"channel_info":{"channel_name_cbd":"DLTV 8","channel_name_chi":"DLTV 8","channel_name_eng":"DLTV 8","channel_name_mm":"DLTV 8","channel_name_rus":"DLTV 8","channel_name_th":"DLTV 8","channel_name_vie":"DLTV 8"},"views":9,"isLiveChat":false},{"id":"6Qna2oVjq3P","thumb":"https://cms.dmpcdn.com/livetv/2020/05/18/c2335f20-98c4-11ea-bcb3-0320ce420b5e_original.png","slug":"dltv-13","title":"DLTV 13","content_type":"livetv","category":"education|hbtv-trueidtv-all|livetv-ca|education-ca","content_provider":"","channel_code":"dum013","content_rights":null,"channel_info":{"channel_name_cbd":"DLTV 13","channel_name_chi":"DLTV 13","channel_name_eng":"DLTV 13","channel_name_mm":"DLTV 13","channel_name_rus":"DLTV 13","channel_name_th":"DLTV 13","channel_name_vie":"DLTV 13"},"views":9,"isLiveChat":false},{"id":"3JYow6Dx7zx0","thumb":"https://cms.dmpcdn.com/livetv/2023/08/02/2f7ad050-30f6-11ee-b57d-a9829f092f3e_webp_original.webp","slug":"bein-sports-6","title":"beIN SPORTS 6","content_type":"livetv","category":"livetv-ca|football-ca|sports-ca|tvsnow|sports","content_provider":"","channel_code":"216","content_rights":null,"channel_info":{"channel_name_eng":"beIN SPORTS 6","channel_name_th":"บีอินสปอตส์ 6"},"views":7,"isLiveChat":false},{"id":"EGvbeMNZOwq","thumb":"https://cms.dmpcdn.com/livetv/2020/05/18/bfdaa210-98c4-11ea-bcb3-0320ce420b5e_original.png","slug":"dltv-11","title":"DLTV 11","content_type":"livetv","category":"education|hbtv-trueidtv-all|livetv-ca|education-ca","content_provider":"","channel_code":"dum011","content_rights":null,"channel_info":{"channel_name_cbd":"DLTV 11","channel_name_chi":"DLTV 11","channel_name_eng":"DLTV 11","channel_name_mm":"DLTV 11","channel_name_rus":"DLTV 11","channel_name_th":"DLTV 11","channel_name_vie":"DLTV 11"},"views":6,"isLiveChat":false},{"id":"JpawvVMe6aXO","thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/1bf1afd0-9407-11ee-8d65-879d2e0f23a3_webp_original.webp","slug":"newcastle-united","title":"Newcastle United","content_type":"livetv","category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all","content_provider":"","channel_code":"new01","content_rights":null,"channel_info":{"channel_name_eng":"Newcastle United","channel_name_th":"นิวคาสเซิล"},"views":6,"isLiveChat":false},{"id":"RVrAxAOGx21v","thumb":"https://cms.dmpcdn.com/livetv/2022/09/08/e5b667c0-2f27-11ed-9e57-d98920d4c462_webp_original.png","slug":"dw-english","title":"DW English","content_type":"livetv","category":"livetv-ca|entertainment-ca|trueunlock-ca|tvsnow|entertainment","content_provider":"true_vision","channel_code":"t518","content_rights":null,"channel_info":{"channel_name_eng":"DW English","channel_name_th":"ดี ดับเบิ้ลยู อิงลิช"},"views":5,"isLiveChat":false},{"id":"eyEPa8A2WaJN","thumb":"https://cms.dmpcdn.com/livetv/2023/08/02/2f7a5b20-30f6-11ee-8c65-b3a6cba5ed9d_webp_original.webp","slug":"beinsports-4","title":"beIN SPORTS 4","content_type":"livetv","category":"livetv-ca|football-ca|sports-ca|tvsnow|sports","content_provider":"","channel_code":"217","content_rights":null,"channel_info":{"channel_name_eng":"beIN SPORTS 4","channel_name_th":"บีอินสปอตส์ 4"},"views":5,"isLiveChat":false},{"id":"pQNm6nA20a6e","thumb":"https://cms.dmpcdn.com/livetv/2023/08/02/300adb50-30f6-11ee-b3e7-85edd640cc04_webp_original.webp","slug":"beinsports-5","title":"beIN SPORTS 5","content_type":"livetv","category":"livetv-ca|football-ca|sports-ca|tvsnow|sports","content_provider":"","channel_code":"219","content_rights":null,"channel_info":{"channel_name_eng":"beIN SPORTS 5","channel_name_th":"บีอินสปอตส์ 5"},"views":2,"isLiveChat":false},{"id":"YZzDXGM1Yd68","thumb":"https://cms.dmpcdn.com/livetv/2021/10/28/3965c0a0-3794-11ec-8e1f-6bce3683de8c_webp_original.png","slug":"Event2","title":"Event 2","content_type":"livetv","category":"hbtv-trueidtv-all|hbtv-truetv-sport|ott-trueidtv-sport|sport|trueidtv-all|trueidtv-sport","content_provider":"","channel_code":"ev03","content_rights":null,"channel_info":null,"views":1,"isLiveChat":false},{"id":"LVQzz7xplYpP","thumb":"https://cms.dmpcdn.com/livetv/2021/10/28/3965c0a0-3794-11ec-8e1f-6bce3683de8c_webp_original.png","slug":"Event5","title":"Event 5","content_type":"livetv","category":"hbtv-trueidtv-all|hbtv-truetv-sport|ott-trueidtv-sport|sport|trueidtv-all|trueidtv-sport","content_provider":null,"channel_code":"emer05","content_rights":null,"channel_info":null,"views":1,"isLiveChat":false},{"id":"xVW7oVd8Gen","thumb":"https://cms.dmpcdn.com/livetv/2020/06/23/99bc7f60-b550-11ea-8fac-236a281cd6c5_original.png","slug":"super-entertain","title":"Super Bunteung","content_type":"livetv","category":"hbtv-trueidtv-all|hbtv-truetv-entertainment|tvsnow|entertainment|livetv-ca|entertainment-ca","content_provider":"true_vision","channel_code":"108","content_rights":null,"channel_info":{"channel_name_cbd":"ស៊ុបព័រអិនធើធែនមិន","channel_name_eng":"Super Bunteung","channel_name_mm":"အထူးေဖ်ာ္ေျဖမႈမ်ား","channel_name_th":"ซุปเปอร์ บันเทิง"},"views":1,"isLiveChat":false},{"id":"rVWJGN1VLOB","thumb":"https://cms.dmpcdn.com/livetv/2017/10/17/31a68f7b-d24e-43e0-9403-22f5e48f081b.png","slug":"etv","title":"ETV","content_type":"livetv","category":"hbtv-trueidtv-all|tvsnow|entertainment|livetv-ca|entertainment-ca|education-ca","content_provider":"true_vision","channel_code":"da2","content_rights":null,"channel_info":{"channel_name_cbd":"ETV","channel_name_chi":"ETV","channel_name_eng":"ETV","channel_name_mm":"ETV","channel_name_rus":"ETV","channel_name_th":"ETV","channel_name_vie":"ETV"},"views":1,"isLiveChat":false},{"id":"vAG5EZznD1Kl","thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/7e72f8d0-9407-11ee-b32f-2d43ff6700d5_webp_original.webp","slug":"west-ham-united","title":"West Ham United","content_type":"livetv","category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all","content_provider":"","channel_code":"whu01","content_rights":null,"channel_info":{"channel_name_eng":"West Ham United","channel_name_th":"เวสต์แฮม"},"views":1,"isLiveChat":false},{"id":"nle3eNnyVpag","thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/a4879e50-9406-11ee-b543-51f040e58632_webp_original.webp","slug":"crystal-palace","title":"Crystal Palace","content_type":"livetv","category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all","content_provider":"","channel_code":"cry01","content_rights":null,"channel_info":{"channel_name_eng":"Crystal-Palace","channel_name_th":"คริสตัลพาเลซ"},"views":1,"isLiveChat":false},{"id":"GB0gZlzxgnJr","thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/0a03a630-9406-11ee-906d-89adbc3169c1_webp_original.webp","slug":"bournemouth","title":"A.F.C. Bournemouth","content_type":"livetv","category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all","content_provider":"","channel_code":"bou01","content_rights":null,"channel_info":{"channel_name_eng":"Bournemouth","channel_name_th":"บอร์นมัธ"},"views":1,"isLiveChat":false},{"id":"1ZrGkEk3qP7L","thumb":"https://cms.dmpcdn.com/livetv/2022/07/18/79461e70-0667-11ed-b687-85f145af88ed_webp_original.png","slug":"test3","title":"ช่องทดสอบออกอากาศที่ 3","content_type":"livetv","category":"livetv-ca|sports-ca","content_provider":"","channel_code":"tmp005","content_rights":null,"channel_info":{"channel_name_eng":"ช่องทดสอบออกอากาศที่ 3","channel_name_th":"ช่องทดสอบออกอากาศที่ 3"},"views":0,"isLiveChat":false},{"id":"6G190MBm2kkG","thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/7bb41c60-9406-11ee-a0fd-836d91d2dd6e_webp_original.webp","slug":"burnley","title":"Burnley","content_type":"livetv","category":"livetv-ca|football-ca|sports-ca|trueunlock-ca","content_provider":"","channel_code":"brn01","content_rights":null,"channel_info":{"channel_name_eng":"Burnley","channel_name_th":"เบิร์นลีย์"},"views":0,"isLiveChat":false},{"id":"4GePx966Dzao","thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/e8af9600-9406-11ee-b625-274874732f96_webp_original.webp","slug":"luton-town","title":"Luton Town","content_type":"livetv","category":"livetv-ca|football-ca|sports-ca|trueunlock-ca","content_provider":"","channel_code":"lut01","content_rights":null,"channel_info":{"channel_name_eng":"Luton Town","channel_name_th":"ลูตัน ทาวน์"},"views":0,"isLiveChat":false},{"id":"4NYqR5KyQArN","thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/61985840-9407-11ee-a0fd-836d91d2dd6e_webp_original.webp","slug":"sheffield-united","title":"Sheffield United","content_type":"livetv","category":"livetv-ca|football-ca|sports-ca|trueunlock-ca","content_provider":"","channel_code":"shu01","content_rights":null,"channel_info":{"channel_name_eng":"Sheffield United","channel_name_th":"เชฟฟิลด์ ยูไนเต็ด"},"views":0,"isLiveChat":false},{"id":"xAzllg2VXjRm","thumb":"https://cms.dmpcdn.com/livetv/2021/10/28/3965c0a0-3794-11ec-8e1f-6bce3683de8c_webp_original.png","slug":"Event3","title":"Event 3","content_type":"livetv","category":"hbtv-trueidtv-all|hbtv-truetv-sport|ott-trueidtv-sport|sport|trueidtv-all|trueidtv-sport","content_provider":null,"channel_code":"emer03","content_rights":null,"channel_info":null,"views":0,"isLiveChat":false},{"id":"WGVqq6zeAzaZ","thumb":"https://cms.dmpcdn.com/livetv/2021/10/28/3965c0a0-3794-11ec-8e1f-6bce3683de8c_webp_original.png","slug":"Event4","title":"Event 4","content_type":"livetv","category":"hbtv-trueidtv-all|hbtv-truetv-sport|ott-trueidtv-sport|sport|trueidtv-all|trueidtv-sport","content_provider":null,"channel_code":"emer04","content_rights":null,"channel_info":null,"views":0,"isLiveChat":false},{"id":"RjY3XkeL5Mwl","thumb":"https://cms.dmpcdn.com/livetv/2021/10/28/3965c0a0-3794-11ec-8e1f-6bce3683de8c_webp_original.png","slug":"Event1","title":"Event 1","content_type":"livetv","category":"hbtv-trueidtv-all|hbtv-truetv-sport|ott-trueidtv-sport|sport|trueidtv-all|trueidtv-sport","content_provider":null,"channel_code":"ev02","content_rights":null,"channel_info":null,"views":0,"isLiveChat":false},{"id":"glmE5eNRz47l","thumb":"https://cms.dmpcdn.com/livetv/2022/07/18/79461e70-0667-11ed-b687-85f145af88ed_webp_original.png","slug":"test","title":"ช่องทดสอบการออกอากาศ","content_type":"livetv","category":"livetv-ca","content_provider":"","channel_code":"tmp003","content_rights":null,"channel_info":{"channel_name_eng":"ทดสอบการออกอากาศ","channel_name_th":"ทดสอบการออกอากาศ"},"views":0,"isLiveChat":false},{"id":"RvJwkNg06Qre","thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/35f494c0-9406-11ee-b32f-2d43ff6700d5_webp_original.webp","slug":"aston-villa","title":"Aston Villa","content_type":"livetv","category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all","content_provider":"","channel_code":"avl01","content_rights":null,"channel_info":{"channel_name_eng":"Aston Villa","channel_name_th":"แอสตันวิลล่า"},"views":0,"isLiveChat":false},{"id":"Vjb43gpNAVnl","thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/b6bb0ad0-9406-11ee-b543-51f040e58632_webp_original.webp","slug":"everton","title":"Everton","content_type":"livetv","category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all","content_provider":"","channel_code":"eve01","content_rights":null,"channel_info":{"channel_name_eng":"Everton","channel_name_th":"เอเวอร์ตัน"},"views":0,"isLiveChat":false},{"id":"K24pNw8k5Kj2","thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/94ed36c0-9407-11ee-906d-89adbc3169c1_webp_original.webp","slug":"wolves","title":"Wolves","content_type":"livetv","category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all","content_provider":"","channel_code":"wol01","content_rights":null,"channel_info":{"channel_name_eng":"Wolves","channel_name_th":"วูลฟ์"},"views":0,"isLiveChat":false},{"id":"vQEl8Do0nK46","thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/c843df70-9406-11ee-8d65-879d2e0f23a3_webp_original.webp","slug":"fulham","title":"Fulham","content_type":"livetv","category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all","content_provider":"","channel_code":"ful01","content_rights":null,"channel_info":{"channel_name_eng":"Fulham","channel_name_th":"ฟูแล่ม"},"views":0,"isLiveChat":false},{"id":"oDqg2NPZdJz5","thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/600d67f0-9406-11ee-ab3e-a51daa175c33_webp_original.webp","slug":"brighton-and-hove-albion","title":"Brighton & Hove Albion","content_type":"livetv","category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all","content_provider":"","channel_code":"bha01","content_rights":null,"channel_info":{"channel_name_eng":"Brighton-and-Hove-Albion","channel_name_th":"ไบร์ทตัน"},"views":0,"isLiveChat":false},{"id":"ykaXNqEMoPZR","thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/30299ee0-9407-11ee-a469-0b60cd4a260f_webp_original.webp","slug":"nottingham-forest","title":"Nottingham Forest","content_type":"livetv","category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all","content_provider":"","channel_code":"nfo01","content_rights":null,"channel_info":{"channel_name_eng":"Nottingham Forest","channel_name_th":"ฟอร์เรสต์"},"views":0,"isLiveChat":false},{"id":"3gaL3mjZoxrE","thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/4738bf40-9406-11ee-a0fd-836d91d2dd6e_webp_original.webp","slug":"brentford","title":"Brentford","content_type":"livetv","category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all","content_provider":"","channel_code":"bre01","content_rights":null,"channel_info":{"channel_name_eng":"Brentford","channel_name_th":"เบรนท์ฟอร์ด"},"views":0,"isLiveChat":false},{"id":"2Ag1bgVdNwoL","thumb":"https://cms.dmpcdn.com/livetv/2022/07/18/79461e70-0667-11ed-b687-85f145af88ed_webp_original.png","slug":"test2","title":"ช่องทดสอบออกอากาศที่2","content_type":"livetv","category":"livetv-ca|sports-ca","content_provider":"","channel_code":"tmp004","content_rights":null,"channel_info":{"channel_name_eng":"ช่องทดสอบออกอากาศที่2","channel_name_th":"ช่องทดสอบออกอากาศที่2"},"views":0,"isLiveChat":false},{"id":"OzKE5d4pdNwy","thumb":"https://cms.dmpcdn.com/livetv/2022/11/16/88521af0-6598-11ed-8215-b386a7bd4f58_webp_original.png","slug":"ufc","title":"UFC","content_type":"livetv","category":"livetv-ca|sports-ca|tvsnow|sports","content_provider":"true_vision","channel_code":"vc04","content_rights":null,"channel_info":{"channel_name_eng":"UFC","channel_name_th":"ยูเอฟซี"},"views":0,"isLiveChat":false}],"channelSlug":"true-movie-hits","baseShelves":{"adsConfig":{"adsNetworkId":"","adsUnit":"21682623839/TrueID_Web/TV"},"id":"3MPnXKpGjKqQ","shelfItems":[{"id":"G3rooMXA2b4Z","title":{"th":"แนะนำ","en":"Featured"},"type":"by_banner_homepage","viewType":"horizontal","shelfItems":[{"id":"J1X89KQE7001","title":"ContentMkt_AVOD_Series_SpiceAndSpell","thumb":"https://cms.dmpcdn.com/hilight/2023/11/24/890e7c60-8aac-11ee-ad2b-e5fcaaed0aed_webp_original.webp","redirectUrl":"https://movie.trueid.net/series/kbNxLxw5Eg52/rV1rP8Dj27kB/AkMBP4z2VOKG/XrVjVjeWXz2r","article_category":null,"content_type":"hilight"},{"id":"xrGQRbjWx1EL","title":"ContentMkt_TVOD_Movie_MI7Part1","thumb":"https://cms.dmpcdn.com/hilight/2023/11/23/32f66c30-89cf-11ee-976f-abdcd950f267_webp_original.webp","redirectUrl":"https://movie.trueid.net/movie/xDnNeqGkreJD","article_category":null,"content_type":"hilight"},{"id":"J52JxN0jXkA5","title":"ContentMkt_SVOD_Asianseries_MyPrecious","thumb":"https://cms.dmpcdn.com/hilight/2023/11/06/00af5020-7c19-11ee-a9a1-41799b41aff4_webp_original.webp","redirectUrl":"","article_category":null,"content_type":"hilight"},{"id":"yb22AdYZnQGb","title":"ContentMkt_AVOD_Lakorn_BakeMePlease","thumb":"https://cms.dmpcdn.com/hilight/2023/11/24/48523fc0-8aa9-11ee-bf9d-a586c3ad0143_webp_original.webp","redirectUrl":"https://movie.trueid.net/series/kN0QYgOQ0EJ5/zWBxNa65n6Vv/Eadp1WLqDXra/yqJgdKrkyRLY","article_category":null,"content_type":"hilight"},{"id":"VmW2pEP9q3rw","title":"ContentMkt_AVOD_Anime_JujutsuKaisen","thumb":"https://cms.dmpcdn.com/hilight/2023/11/24/88238020-8aac-11ee-873d-3f4e5da5fd9c_webp_original.webp","redirectUrl":"https://movie.trueid.net/series/LgR5wpRnPQVA/qYQ00Mby07Rl/MvKZ6RnxKRaQ/jdAX0Wagxl4R","article_category":null,"content_type":"hilight"},{"id":"BXGpEAn51mwW","title":"ContentMkt_TVOD_Movie_Ambulance","thumb":"https://cms.dmpcdn.com/hilight/2023/11/23/32aa20f0-89cf-11ee-ae81-d157aa7f87b4_webp_original.webp","redirectUrl":"https://movie.trueid.net/movie/Eq63XJL4okzw","article_category":null,"content_type":"hilight"},{"id":"DoLWMe0YeQR8","title":"ContentMkt_AVOD_Anime_DarkGathering","thumb":"https://cms.dmpcdn.com/hilight/2023/11/23/316e93b0-89cf-11ee-a5ec-b3b59a2ebd97_webp_original.webp","redirectUrl":"https://movie.trueid.net/series/d6425Em1DPjQ/QMrQQgYaEDGZ/OAqkmxqX05DQ/1G8xwp6ja86G","article_category":null,"content_type":"hilight"},{"id":"1PddoL4xG12P","title":"ContentMkt_AVOD_Anime_OnePiece","thumb":"https://cms.dmpcdn.com/hilight/2023/11/24/88241c60-8aac-11ee-bf9d-a586c3ad0143_webp_original.webp","redirectUrl":"https://movie.trueid.net/th-th/series/kxqkPYqVBq0D/4AoxBYpn4L1W/p2N5lYZGlVlL/lZ78MWznOElG","article_category":null,"content_type":"hilight"},{"id":"nYa5A41VxmXY","title":"TruelD One Package ความบันเทิงระดับโลก แบบไร้ขีดจำกัด V21-23","thumb":"https://cms.dmpcdn.com/hilight/2023/11/13/84d3ef30-8215-11ee-84cd-3b76e2935cfd_webp_original.webp","redirectUrl":"https://home.trueid.net/external-browser?website=https://myaccount.trueid.net/checkout?promotionCode=SUPERBUNDLE_TID_IQIYI_WETV_PRIME&utm_campaign=Package_NA_NA_TrueIDOne&utm_medium=inside-platform&utm_source=Today_new_release","article_category":null,"content_type":"hilight"},{"id":"VKvARdba10aW","title":"What's Wrong with My Princess","thumb":"https://cms.dmpcdn.com/hilight/2023/11/27/77b75b30-8cd2-11ee-9352-bd2663fbb8ed_webp_original.webp","redirectUrl":"https://movie.trueid.net/series/mP3G7aOXQGXP/QeDwwRKOL2Mp/RvD2p1OpZMRQ/zxnrl6JJnZ2x","article_category":null,"content_type":"hilight"}]},{"id":"k42naQeVKbK4","title":{"th":"","en":""},"type":"by_ads","viewType":"horizontal","shelfItems":[{"ALL":{"targetingArguments":{"TrueID_page":[],"Device":[]},"sizeMapping":[{"viewport":[1280,0],"sizes":[[750,200],[970,90],[728,90],"fluid",[800,250],[970,250],[1,1],[1280,250]]},{"viewport":[375,0],"sizes":[[1,1],[320,250],[375,250],"fluid",[300,250],[320,100]]},{"viewport":[800,0],"sizes":["fluid",[640,250],[800,250],[1,1],[728,90]]},{"viewport":[0,0],"sizes":[[320,50],[320,100],[1,1]]}],"slotId":"div-gpt-ad-lb-1","adUnit":"21682623839/TrueID_Web/TV","sizes":[[970,90],[728,90]]}}]},{"id":"O8pKrLmQlj2a","title":{"th":"ช่องฟรีทีวีฮิต","en":"Free TV"},"type":"by_livetv_channel","viewType":"vertical","shelfItems":[{"id":"wKngqJ2Vqnl","title":"MONO 29","thumb":"https://cms.dmpcdn.com/livetv/2019/01/10/35a35017-8473-4953-8474-5c58d805b74a.png","redirectUrl":"mono29","channel_code":"d43","views":33721,"article_category":["livetv-ca","digitaltv-ca","freetv-ca","movies-series-ca"],"content_type":"livetv","content_rights":"","isLiveChat":false},{"id":"yYk6PvXwXDb","title":"WorkPoint TV","thumb":"https://cms.dmpcdn.com/livetv/2023/11/17/2a1de990-852d-11ee-bf98-41acc8fd04fc_webp_original.webp","redirectUrl":"workpointtv","channel_code":"d83","views":6075,"article_category":["livetv-ca","digitaltv-ca","entertainment-ca","freetv-ca"],"content_type":"livetv","content_rights":"","isLiveChat":false},{"id":"QRP2K658b7G","title":"Thai PBS","thumb":"https://cms.dmpcdn.com/livetv/2023/09/15/ab170410-5377-11ee-8e1b-194edbb69638_webp_original.webp","redirectUrl":"thaipbs","channel_code":"c12","views":2526,"article_category":["livetv-ca","digitaltv-ca","freetv-ca","news-ca"],"content_type":"livetv","content_rights":"","isLiveChat":false},{"id":"vqbr1WgEnGQ","title":"Channel 8","thumb":"https://cms.dmpcdn.com/livetv/2023/09/15/5408a390-5377-11ee-8e1b-194edbb69638_webp_original.webp","redirectUrl":"ch8","channel_code":"d62","views":8295,"article_category":["livetv-ca","digitaltv-ca","entertainment-ca","freetv-ca"],"content_type":"livetv","content_rights":"","isLiveChat":false},{"id":"9O54lyP5Rqx","title":"Channel 7HD","thumb":"https://cms.dmpcdn.com/livetv/2023/07/19/212d15e0-25e7-11ee-bfc1-85e95548413c_webp_original.webp","redirectUrl":"ch7-hd","channel_code":"c07","views":12092,"article_category":["livetv-ca","digitaltv-ca","entertainment-ca","freetv-ca"],"content_type":"livetv","content_rights":"","isLiveChat":false},{"id":"zMLBpX7AWmk","title":"Nation TV","thumb":"https://cms.dmpcdn.com/livetv/2023/09/09/9c6f59c0-4ebe-11ee-99a7-832609069236_webp_original.webp","redirectUrl":"nationtv","channel_code":"d78","views":4733,"article_category":["livetv-ca","digitaltv-ca","freetv-ca","news-ca"],"content_type":"livetv","content_rights":"","isLiveChat":false},{"id":"nQlqONGyoa4","title":"Channel 3","thumb":"https://cms.dmpcdn.com/livetv/2023/07/24/5ff3e270-29cc-11ee-b2f4-e9de482d866e_webp_original.webp","redirectUrl":"ch3-hd","channel_code":"c03","views":96184,"article_category":["livetv-ca","digitaltv-ca","entertainment-ca","freetv-ca"],"content_type":"livetv","content_rights":"","isLiveChat":false},{"id":"5PKobQk5gLOP","title":"Boomerang","thumb":"https://cms.dmpcdn.com/livetv/2023/07/05/b74a2460-1b05-11ee-8ce6-b102b53cb4a2_webp_original.webp","redirectUrl":"boomerang-hd","channel_code":"i007","views":707,"article_category":["livetv-ca","freetv-ca","kids-ca"],"content_type":"livetv","content_rights":"","isLiveChat":false},{"id":"OVKwZle4eop","title":"True4U","thumb":"https://cms.dmpcdn.com/livetv/2023/09/15/84504210-5377-11ee-aaa1-7d584d8ca7a4_webp_original.webp","redirectUrl":"true4u","channel_code":"207","views":6489,"article_category":["livetv-ca","digitaltv-ca","entertainment-ca","freetv-ca","movies-series-ca"],"content_type":"livetv","content_rights":"","isLiveChat":false},{"id":"0z4lvq6Xwoa","title":"One31","thumb":"https://cms.dmpcdn.com/livetv/2019/01/16/396384be-35dc-4d11-bf04-06c9546ec7bc.png","redirectUrl":"one-hd","channel_code":"d56","views":10182,"article_category":["livetv-ca","digitaltv-ca","entertainment-ca","freetv-ca"],"content_type":"livetv","content_rights":"","isLiveChat":false},{"id":"rBWOx89v9Rk","title":"9 MCOT","thumb":"https://cms.dmpcdn.com/livetv/2023/09/09/9cc40970-4ebe-11ee-9801-97f95b5eed9a_webp_original.webp","redirectUrl":"9mcot-hd","channel_code":"c09","views":1323,"article_category":["livetv-ca","digitaltv-ca","freetv-ca"],"content_type":"livetv","content_rights":"","isLiveChat":false}]},{"id":"agbxxnP7GZQ4","title":{"th":"โปรแกรมทีวียอดนิยม","en":"Trending TV Program"},"type":"by_trending_tv_program","viewType":"horizontal","shelfItems":[{"id":"nQlqONGyoa4","title":"แชนแนลทรี ซีรีส์ สายใยรัก เหนือบัลลังก์","thumb":"https://cms.dmpcdn.com/livetv/2023/07/24/5ff3e270-29cc-11ee-b2f4-e9de482d866e_webp_original.webp","thumbLarge":"https://epgthumb.dmpcdn.com/thumbnail_large/c03.jpg?time=1702312743612","redirectUrl":"https://tv.trueid.net/th-en/live/ch3-hd","views":96184,"program_id":"wDGy4q2m963K","program_name":"แชนแนลทรี ซีรีส์ สายใยรัก เหนือบัลลังก์","article_category":["livetv-ca","digitaltv-ca","entertainment-ca","freetv-ca"],"content_type":"livetv","channel_info":{"channel_name_cbd":"ប៉ុស្តិ៍ 3 HD","channel_name_eng":"CH3 HD","channel_name_mm":"CH3 HD","channel_name_th":"ช่อง 3 HD"}},{"id":"8v732AYomo9","title":"ไทยรัฐเจาะประเด็น","thumb":"https://cms.dmpcdn.com/livetv/2023/07/18/7dc7a180-2515-11ee-b8b2-77e2a8f4c31e_webp_original.webp","thumbLarge":"https://epgthumb.dmpcdn.com/thumbnail_large/d05.jpg?time=1702312743612","redirectUrl":"https://tv.trueid.net/th-en/live/thairathtv-hd","views":17228,"program_id":"xkKBjq0VA926","program_name":"ไทยรัฐเจาะประเด็น","article_category":["livetv-ca","digitaltv-ca","freetv-ca","news-ca"],"content_type":"livetv","channel_info":{"channel_name_cbd":"ថៃរ៉ាត់ ធីវី HD","channel_name_eng":"Thairath TV HD","channel_name_mm":"Thairath TV HD","channel_name_th":"ไทยรัฐ ทีวี HD"}},{"id":"0z4lvq6Xwoa","title":"ละคร เสน่หาข้ามเส้น (ตอนอวสาน)","thumb":"https://cms.dmpcdn.com/livetv/2019/01/16/396384be-35dc-4d11-bf04-06c9546ec7bc.png","thumbLarge":"https://epgthumb.dmpcdn.com/thumbnail_large/d56.jpg?time=1702312743612","redirectUrl":"https://tv.trueid.net/th-en/live/one-hd","views":10182,"program_id":"VZ8mMrWEKxQ3","program_name":"ละคร เสน่หาข้ามเส้น (ตอนอวสาน)","article_category":["livetv-ca","digitaltv-ca","entertainment-ca","freetv-ca"],"content_type":"livetv","channel_info":{"channel_name_cbd":"វ័ន HD","channel_name_eng":"One HD","channel_name_mm":"One HD","channel_name_th":"วัน HD"}},{"id":"OVKwZle4eop","title":"ภาพยนตร์ อันธพาล","thumb":"https://cms.dmpcdn.com/livetv/2023/09/15/84504210-5377-11ee-aaa1-7d584d8ca7a4_webp_original.webp","thumbLarge":"https://epgthumb.dmpcdn.com/thumbnail_large/207.jpg?time=1702312743612","redirectUrl":"https://tv.trueid.net/th-en/live/true4u","views":6489,"program_id":"4A8jX4rrX58J","program_name":"ภาพยนตร์ อันธพาล","article_category":["livetv-ca","digitaltv-ca","entertainment-ca","freetv-ca","movies-series-ca"],"content_type":"livetv","channel_info":{"channel_name_cbd":"ទ្រូ4យូ","channel_name_chi":"True4U","channel_name_eng":"True4U","channel_name_mm":"True4U","channel_name_rus":"True4U","channel_name_th":"ทรูโฟร์ยู","channel_name_vie":"True4U"}},{"id":"9O54lyP5Rqx","title":"One Lumpinee Heroes","thumb":"https://cms.dmpcdn.com/livetv/2023/07/19/212d15e0-25e7-11ee-bfc1-85e95548413c_webp_original.webp","thumbLarge":"https://epgthumb.dmpcdn.com/thumbnail_large/c07.jpg?time=1702312743612","redirectUrl":"https://tv.trueid.net/th-en/live/ch7-hd","views":12092,"program_id":"vbQqm8mnpyYb","program_name":"One Lumpinee Heroes","article_category":["livetv-ca","digitaltv-ca","entertainment-ca","freetv-ca"],"content_type":"livetv","channel_info":{"channel_name_cbd":"ប៉ុស្តិ៍​ 7","channel_name_eng":"CH 7HD","channel_name_mm":"Channel 7","channel_name_th":"ช่อง 7HD"}},{"id":"yYk6PvXwXDb","title":"เคลียร์ชัดชัด รีรัน","thumb":"https://cms.dmpcdn.com/livetv/2023/11/17/2a1de990-852d-11ee-bf98-41acc8fd04fc_webp_original.webp","thumbLarge":"https://epgthumb.dmpcdn.com/thumbnail_large/d83.jpg?time=1702312743612","redirectUrl":"https://tv.trueid.net/th-en/live/workpointtv","views":6075,"program_id":"KzKlKXKPDkDY","program_name":"เคลียร์ชัดชัด รีรัน","article_category":["livetv-ca","digitaltv-ca","entertainment-ca","freetv-ca"],"content_type":"livetv","channel_info":{"channel_name_cbd":"វើកភ័ញ គ្រីអ៊ែតធិវ ធីវី​","channel_name_eng":"Workpoint TV","channel_name_mm":"Workpoint TV","channel_name_th":"เวิร์คพอยท์ ทีวี"}},{"id":"OBb6NzoJX7O","title":"ทรูช้อปปิ้ง","thumb":"https://cms.dmpcdn.com/livetv/2023/10/02/d2ec4b30-60f1-11ee-92a4-8597bcef0049_webp_original.webp","thumbLarge":"https://epgthumb.dmpcdn.com/thumbnail_large/da0.jpg?time=1702312743612","redirectUrl":"https://tv.trueid.net/th-en/live/amarintv-hd","views":6407,"program_id":"PwP4AzA5KBXw","program_name":"ทรูช้อปปิ้ง","article_category":["livetv-ca","digitaltv-ca","freetv-ca"],"content_type":"livetv","channel_info":{"channel_name_cbd":"អាម៉ារិន","channel_name_eng":"Amarin TV","channel_name_mm":"Amarin TV","channel_name_th":"อมรินทร์"}},{"id":"vqbr1WgEnGQ","title":"เด็ดมวยเดือด","thumb":"https://cms.dmpcdn.com/livetv/2023/09/15/5408a390-5377-11ee-8e1b-194edbb69638_webp_original.webp","thumbLarge":"https://epgthumb.dmpcdn.com/thumbnail_large/d62.jpg?time=1702312743612","redirectUrl":"https://tv.trueid.net/th-en/live/ch8","views":8295,"program_id":"XJ4NgMgdr7K2","program_name":"เด็ดมวยเดือด","article_category":["livetv-ca","digitaltv-ca","entertainment-ca","freetv-ca"],"content_type":"livetv","channel_info":{"channel_name_cbd":"ប៉ុស្តិ៍ 8","channel_name_eng":"CH8","channel_name_th":"ช่อง 8"}},{"id":"zMLBpX7AWmk","title":"ยุคลชนข่าว","thumb":"https://cms.dmpcdn.com/livetv/2023/09/09/9c6f59c0-4ebe-11ee-99a7-832609069236_webp_original.webp","thumbLarge":"https://epgthumb.dmpcdn.com/thumbnail_large/d78.jpg?time=1702312743612","redirectUrl":"https://tv.trueid.net/th-en/live/nationtv","views":4733,"program_id":"m6ejRJ5pKvdL","program_name":"ยุคลชนข่าว","article_category":["livetv-ca","digitaltv-ca","freetv-ca","news-ca"],"content_type":"livetv","channel_info":{"channel_name_cbd":"Nation TV 22","channel_name_chi":"Nation TV 22","channel_name_eng":"Nation TV 22","channel_name_mm":"Nation TV 22","channel_name_rus":"Nation TV 22","channel_name_th":"เนชั่น ทีวี","channel_name_vie":"Nation TV 22"}},{"id":"QNBwOpdaxpQ","title":"Highlights Bundesliga","thumb":"https://cms.dmpcdn.com/livetv/2023/08/28/012eed00-458a-11ee-bd2b-6734a2d9e428_webp_original.webp","thumbLarge":"https://epgthumb.dmpcdn.com/thumbnail_large/da7.jpg?time=1702312743612","redirectUrl":"https://tv.trueid.net/th-en/live/pptv-hd","views":4723,"program_id":"DwYXj5Jbvex1","program_name":"Highlights Bundesliga","article_category":["livetv-ca","digitaltv-ca","freetv-ca"],"content_type":"livetv","channel_info":{"channel_name_cbd":"ភីភីធីវី","channel_name_eng":"PPTV","channel_name_mm":"PPTV","channel_name_th":"พีพีทีวี"}}]}]},"channelDetail":{"display_country":"th","display_lang":"en","id":"NopZ5gjkGmE","content_type":"livetv","original_id":"279","title":"True Movie Hits","article_category":["livetv-ca","movies-series-ca","trueunlock-ca","tvsnow","movieseries"],"thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/45345d10-e599-11ed-86b8-bb40638e3c49_webp_original.png","tags":null,"status":"publish","count_views":538976,"publish_date":"2020-07-26T17:00:00.000Z","create_date":"2017-10-17T22:01:00.000Z","update_date":"2023-11-26T11:08:14.333Z","searchable":"Y","create_by":"Live TV","create_by_ssoid":null,"update_by":"KANT","update_by_ssoid":"112710659","source_url":null,"count_likes":null,"count_ratings":null,"source_country":null,"channel_code":"057","drm":"WV_FPS","channel_info":{"channel_name_cbd":"True Movie Hits","channel_name_chi":"True Movie Hits","channel_name_eng":"True Movie Hits","channel_name_mm":"True Movie Hits","channel_name_rus":"True Movie Hits","channel_name_th":"True Movie Hits","channel_name_vie":"True Movie Hits"},"lang_dual":"yes","setting":null,"slug":"true-movie-hits","allow_app":["trueidapp","trueidweb","trueidott","hybrid"],"detail":"

    ช่องภาพยนต์ต่างประเทศ รับชมได้ทั้งครบครัวด้วยระบบเสียงภาษาไทย

    ","content_provider":"true_vision","playready":"","score":null},"liveChatConfig":{"channelId":"NopZ5gjkGmE","isLiveChat":false,"slug":"true-movie-hits","disabledChat":false,"supportBrowser":{"chrome_browser_version":{"min_version":83,"live_chat":false},"firefox_browser_version":{"min_version":92,"live_chat":false},"msedge_browser_version":{"min_version":80,"live_chat":false},"off_livechat":false},"disabledChatList":[]},"epgList":[{"id":"w2xyzKz9eyb3","original_id":"057:20231212_020500","content_type":"epg","title":"The Last Witch Hunter","detail":"","status":"publish","channel_code":"057","title_id":"715307","ep_id":"2397606","ep_no":"1","ep_name":"LAST WITCH HUNTER, THE (2015) [MHS] [R]","movie_type":"series","first_run":"Y","cast_type":"tape","start_date":"2023-12-11T19:05:00.000Z","end_date":"2023-12-11T20:55:00.000Z","publish_date":"2023-12-11T10:36:41.801Z","lang":"en","thumb":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_020500.jpg","thumb_list":{"thumb":"https://epgthumb.dmpcdn.com/thumbnail/057/20231212/20231212_020500.jpg","thumb_catchup":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_021000.jpg","thumb_large":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_020500.jpg"},"black_out":0,"catch_up":0,"flag":"N","info":{"channel_name":"TR MOVIE HITS","image":"https://bms.dmpcdn.com/uploads/pic/381f853da5f4a310bf248357fed21a57.jpg","synopsis_en":"A young man is all that stands between humanity and the most horrifying witches in history.","director":"Breck Eisner","title_id":"715307","video":"pBNufkr4KkU","channel_code":"057","type":"Movie","synopsis_th":"หนุ่มนักล่าแม่มดถูกสาปให้เป็นอมตะจนกระทั่งราชินีแม่มดได้ฟื้นคืนชีพขึ้นมาจึงมีเพียงเขาคนเดียวเท่านั้นที่จะสามารถกอบกู้มวลมนุษยชาติได้","imdb_image":"https://bms.dmpcdn.com/uploads/pic/44b0eb2f46eed6a3953014cb5abdbff3.jpg","cast":"Vin Diesel, Rose Leslie, Elijah Wood","genres":"action","program_title":"LAST WITCH HUNTER, THE","production_year":"2015"},"isShowTime":"02:05","isActive":false},{"id":"GPApa0aZzprE","original_id":"057:20231212_035500","content_type":"epg","title":"Point Break","detail":"","status":"publish","channel_code":"057","title_id":"718258","ep_id":"2413906","ep_no":"1","ep_name":"POINT BREAK (2015) [MHS] [R]","movie_type":"series","first_run":"Y","cast_type":"tape","start_date":"2023-12-11T20:55:00.000Z","end_date":"2023-12-11T22:55:00.000Z","publish_date":"2023-12-11T10:36:41.801Z","lang":"en","thumb":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_035500.jpg","thumb_list":{"thumb":"https://epgthumb.dmpcdn.com/thumbnail/057/20231212/20231212_035500.jpg","thumb_catchup":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_040000.jpg","thumb_large":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_035500.jpg"},"black_out":0,"catch_up":0,"flag":"N","info":{"channel_name":"TR MOVIE HITS","image":"https://bms.dmpcdn.com/uploads/pic/e143a6f05ce8e87bf3e7c0f8dfca9914.jpeg","synopsis_en":"An FBI agent infiltrates a gang of thrill-seeking athlete thieves who are suspects in a spate of daring robberies.","director":"Ericson Core","title_id":"718258","video":"jcDD2-s4vWA","channel_code":"057","type":"Movie","synopsis_th":"เรื่องราวของเจ้าหน้าที่เอฟบีไอกับปฏิบัติการสืบสวนเพื่อตามล่าตัวมิจฉาชีพระดับโลกด้วยการแฝงตัวเข้าไปในกลุ่มนักเล่นกระดานโต้คลื่น","imdb_image":"https://bms.dmpcdn.com/uploads/pic/5a0fc22d59c8aef7e9693119687b2172.jpg","cast":"Edgar Ramirez, Luke Bracey, Ray Winstone","genres":"action","program_title":"POINT BREAK","production_year":"2015"},"isShowTime":"03:55","isActive":false},{"id":"Va15bqbQn58a","original_id":"057:20231212_055500","content_type":"epg","title":"The Art of War","detail":"","status":"publish","channel_code":"057","title_id":"712097","ep_id":"2372786","ep_no":"1","ep_name":"ART OF WAR, THE [2000] [MHS]","movie_type":"series","first_run":"Y","cast_type":"tape","start_date":"2023-12-11T22:55:00.000Z","end_date":"2023-12-12T00:55:00.000Z","publish_date":"2023-12-11T10:36:41.801Z","lang":"en","thumb":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_055500.jpg","thumb_list":{"thumb":"https://epgthumb.dmpcdn.com/thumbnail/057/20231212/20231212_055500.jpg","thumb_catchup":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_060000.jpg","thumb_large":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_055500.jpg"},"black_out":0,"catch_up":0,"flag":"N","info":{"channel_name":"TR MOVIE HITS","image":"https://bms.dmpcdn.com/uploads/pic/4f12f9d90f2e8d29b9e427ef415bcb4e.jpg","synopsis_en":"After a US agent is framed for the assassination of the Chinese ambassador, he faces a race against time to catch the real killers.","director":"Christian Duguay","title_id":"712097","video":"rKFmSpB-uGQ","channel_code":"057","type":"Movie","synopsis_th":"เมื่อสายลับถูกใส่ร้ายว่าเป็นฆาตกรเขาจึงต้องหลบหนีการตามไล่ล่าและแข่งกับเวลาเพื่อสืบหาตามล่าฆาตกรตัวจริงให้ได้โดยเร็วที่สุด","imdb_image":"https://bms.dmpcdn.com/uploads/pic/28ab499dffdff191fba497f64131e744.jpg","cast":"Wesley Snipes, Anne Archer, Maury Chaykin","genres":"crime","program_title":"ART OF WAR, THE","production_year":"2000"},"isShowTime":"05:55","isActive":false},{"id":"ALxXy6y32XOL","original_id":"057:20231212_075500","content_type":"epg","title":"The Marksman","detail":"","status":"publish","channel_code":"057","title_id":"726358","ep_id":"2466252","ep_no":"1","ep_name":"MARKSMAN, THE (2021) [MHS]","movie_type":"series","first_run":"Y","cast_type":"tape","start_date":"2023-12-12T00:55:00.000Z","end_date":"2023-12-12T02:50:00.000Z","publish_date":"2023-12-11T10:36:41.801Z","lang":"en","thumb":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_075500.jpg","thumb_list":{"thumb":"https://epgthumb.dmpcdn.com/thumbnail/057/20231212/20231212_075500.jpg","thumb_catchup":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_080000.jpg","thumb_large":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_075500.jpg"},"black_out":0,"catch_up":0,"flag":"N","info":{"channel_name":"TR MOVIE HITS","image":"https://bms.dmpcdn.com/uploads/pic/3945dc2c6cfff5ebeb61f021b58104ab.jpg","synopsis_en":"An Arizona rancher becomes the unlikely defender of a Mexican boy desperately fleeing the cartel assassins who've pursued him into the US.","director":"Robert Lorenz","title_id":"726358","video":"lEBPNi4bEbc","channel_code":"057","type":"Movie","synopsis_th":"อดีตทหารเรือ ที่หนีความวุ่นวายมาใช้ชีวิตอย่างสงบสุขในฟาร์มนอกเมือง แต่กลับต้องไปพัวพันกับสองแม่ลูกที่หลบหนีเอาตัวรอดจากกลุ่มนักฆ่าค้ายา","imdb_image":"https://bms.dmpcdn.com/uploads/pic/573349fd5394caccce8c4f818fdb57b5.jpg","cast":"Liam Neeson, Katheryn Winnick, Juan Pablo Raba","genres":"action","program_title":"MARKSMAN, THE","production_year":"2021"},"isShowTime":"07:55","isActive":false},{"id":"XzDNDnDrXNJ9","original_id":"057:20231212_095000","content_type":"epg","title":"Gods of Egypt","detail":"","status":"publish","channel_code":"057","title_id":"718274","ep_id":"2414078","ep_no":"1","ep_name":"GODS OF EGYPT (2016) [MHS] [R]","movie_type":"series","first_run":"Y","cast_type":"tape","start_date":"2023-12-12T02:50:00.000Z","end_date":"2023-12-12T05:05:00.000Z","publish_date":"2023-12-11T10:36:41.801Z","lang":"en","thumb":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_095000.jpg","thumb_list":{"thumb":"https://epgthumb.dmpcdn.com/thumbnail/057/20231212/20231212_095000.jpg","thumb_catchup":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_095500.jpg","thumb_large":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_095000.jpg"},"black_out":0,"catch_up":0,"flag":"N","info":{"channel_name":"TR MOVIE HITS","image":"https://bms.dmpcdn.com/uploads/pic/e772028844f60526e6dc0fe5b666425a.jpg","synopsis_en":"A young hero teams with the god Horus to fight against the god of darkness, who has usurped Egypt's throne.","director":"Alex Proyas","title_id":"718274","video":"Oijdb-a9GKY","channel_code":"057","type":"Movie","synopsis_th":"เรื่องราวความขัดแย้งและการช่วงชิงที่อุบัติขึ้นท่ามกลางความร้อนระอุแห่งทะเลทรายในดินแดนลุ่มแม่น้ำไนล์อันเต็มไปด้วยมนตรา ทวยเทพ และ เหล่าอสูร","imdb_image":"https://bms.dmpcdn.com/uploads/pic/0a945dc3853317779946f5b9f38269a1.jpg","cast":"Gerard Butler, Brenton Thwaites, Nikolaj Coster-Waldau","genres":"action","program_title":"GODS OF EGYPT","production_year":"2016"},"isShowTime":"09:50","isActive":false},{"id":"4DnvNENgamwD","original_id":"057:20231212_120500","content_type":"epg","title":"The Last Witch Hunter","detail":"","status":"publish","channel_code":"057","title_id":"715307","ep_id":"2397606","ep_no":"1","ep_name":"LAST WITCH HUNTER, THE (2015) [MHS] [R]","movie_type":"series","first_run":"Y","cast_type":"tape","start_date":"2023-12-12T05:05:00.000Z","end_date":"2023-12-12T06:55:00.000Z","publish_date":"2023-12-11T10:36:41.801Z","lang":"en","thumb":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_120500.jpg","thumb_list":{"thumb":"https://epgthumb.dmpcdn.com/thumbnail/057/20231212/20231212_120500.jpg","thumb_catchup":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_121000.jpg","thumb_large":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_120500.jpg"},"black_out":0,"catch_up":0,"flag":"N","info":{"channel_name":"TR MOVIE HITS","image":"https://bms.dmpcdn.com/uploads/pic/381f853da5f4a310bf248357fed21a57.jpg","synopsis_en":"A young man is all that stands between humanity and the most horrifying witches in history.","director":"Breck Eisner","title_id":"715307","video":"pBNufkr4KkU","channel_code":"057","type":"Movie","synopsis_th":"หนุ่มนักล่าแม่มดถูกสาปให้เป็นอมตะจนกระทั่งราชินีแม่มดได้ฟื้นคืนชีพขึ้นมาจึงมีเพียงเขาคนเดียวเท่านั้นที่จะสามารถกอบกู้มวลมนุษยชาติได้","imdb_image":"https://bms.dmpcdn.com/uploads/pic/44b0eb2f46eed6a3953014cb5abdbff3.jpg","cast":"Vin Diesel, Rose Leslie, Elijah Wood","genres":"action","program_title":"LAST WITCH HUNTER, THE","production_year":"2015"},"isShowTime":"12:05","isActive":false},{"id":"AEMb1g1LnzpQ","original_id":"057:20231212_135500","content_type":"epg","title":"Leon","detail":"","status":"publish","channel_code":"057","title_id":"729656","ep_id":"2488591","ep_no":"1","ep_name":"LEON [1994] [MHS]","movie_type":"series","first_run":"Y","cast_type":"tape","start_date":"2023-12-12T06:55:00.000Z","end_date":"2023-12-12T09:05:00.000Z","publish_date":"2023-12-11T10:36:41.801Z","lang":"en","thumb":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_135500.jpg","thumb_list":{"thumb":"https://epgthumb.dmpcdn.com/thumbnail/057/20231212/20231212_135500.jpg","thumb_catchup":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_140000.jpg","thumb_large":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_135500.jpg"},"black_out":0,"catch_up":0,"flag":"N","info":{"channel_name":"TR MOVIE HITS","image":"https://bms.dmpcdn.com/uploads/pic/79eabd21fdb1da338cca6b598de46cde.jpg","synopsis_en":"A hitman forms an unlikely bond with a young girl, teaching her his deadly skills while protecting her from ruthless criminals.","director":"Luc Besson","title_id":"729656","video":"aNQqoExfQsg","channel_code":"057","type":"Movie","synopsis_th":"เรื่องราวของนักฆ่าที่ได้สร้างความผูกพันธ์ที่ไม่น่าจะเป็นไปได้กับเด็กหญิง โดยสอนทักษะอันอันตรายแก่เธอพร้อมทั้งปกป้องเธอจากอาชญากรผู้โหดเหี้ยม","imdb_image":"https://bms.dmpcdn.com/uploads/pic/20be5d12ff2b8f86fb40f9db619d4cb8.jpg","cast":"Jean Reno, Gary Oldman, Natalie Portman","genres":"crime","program_title":"LEON","production_year":"1994"},"isShowTime":"13:55","isActive":false},{"id":"2xe7R1Rgamq4","original_id":"057:20231212_160500","content_type":"epg","title":"Gunpowder Milkshake","detail":"","status":"publish","channel_code":"057","title_id":"710376","ep_id":"2363208","ep_no":"1","ep_name":"GUNPOWDER MILKSHAKE (2021) [MHS]","movie_type":"series","first_run":"Y","cast_type":"tape","start_date":"2023-12-12T09:05:00.000Z","end_date":"2023-12-12T11:00:00.000Z","publish_date":"2023-12-11T10:36:41.801Z","lang":"en","thumb":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_160500.jpg","thumb_list":{"thumb":"https://epgthumb.dmpcdn.com/thumbnail/057/20231212/20231212_160500.jpg","thumb_catchup":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_161000.jpg","thumb_large":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_160500.jpg"},"black_out":0,"catch_up":0,"flag":"N","info":{"channel_name":"TR MOVIE HITS","image":"https://bms.dmpcdn.com/uploads/pic/0039112f2fef876ebf32f9bfb3a9fcf9.jpg","synopsis_en":"Three generations of women fight back against those who aim to take everything from them.","director":"Navot Papushado","title_id":"710376","video":"yxuAroBqt2c","channel_code":"057","type":"Movie","synopsis_th":"เรื่องราวของสามหญิงสามวัยที่ต้องต่อสู้กับผู้ซึ่งแย่งชิงทุกสิ่งทุกอย่างไปจากพวกเธอ","imdb_image":"https://bms.dmpcdn.com/uploads/pic/756c225bc8f5f2ed1268945c979b01a1.jpg","cast":"Karen Gillan, Lena Headey, Carla Gugino","genres":"action","program_title":"GUNPOWDER MILKSHAKE","production_year":"2021"},"isShowTime":"16:05","isActive":false},{"id":"QoeyO1O0Q3no","original_id":"057:20231212_180000","content_type":"epg","title":"Point Break","detail":"","status":"publish","channel_code":"057","title_id":"718258","ep_id":"2413906","ep_no":"1","ep_name":"POINT BREAK (2015) [MHS] [R]","movie_type":"series","first_run":"Y","cast_type":"tape","start_date":"2023-12-12T11:00:00.000Z","end_date":"2023-12-12T13:00:00.000Z","publish_date":"2023-12-11T10:36:41.801Z","lang":"en","thumb":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_180000.jpg","thumb_list":{"thumb":"https://epgthumb.dmpcdn.com/thumbnail/057/20231212/20231212_180000.jpg","thumb_catchup":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_180500.jpg","thumb_large":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_180000.jpg"},"black_out":0,"catch_up":0,"flag":"N","info":{"channel_name":"TR MOVIE HITS","image":"https://bms.dmpcdn.com/uploads/pic/e143a6f05ce8e87bf3e7c0f8dfca9914.jpeg","synopsis_en":"An FBI agent infiltrates a gang of thrill-seeking athlete thieves who are suspects in a spate of daring robberies.","director":"Ericson Core","title_id":"718258","video":"jcDD2-s4vWA","channel_code":"057","type":"Movie","synopsis_th":"เรื่องราวของเจ้าหน้าที่เอฟบีไอกับปฏิบัติการสืบสวนเพื่อตามล่าตัวมิจฉาชีพระดับโลกด้วยการแฝงตัวเข้าไปในกลุ่มนักเล่นกระดานโต้คลื่น","imdb_image":"https://bms.dmpcdn.com/uploads/pic/5a0fc22d59c8aef7e9693119687b2172.jpg","cast":"Edgar Ramirez, Luke Bracey, Ray Winstone","genres":"action","program_title":"POINT BREAK","production_year":"2015"},"isShowTime":"18:00","isActive":false},{"id":"voAargrBvRQo","original_id":"057:20231212_200000","content_type":"epg","title":"Max Steel","detail":"","status":"publish","channel_code":"057","title_id":"718257","ep_id":"2413902","ep_no":"1","ep_name":"MAX STEEL (2016) [MHS] [R]","movie_type":"series","first_run":"Y","cast_type":"tape","start_date":"2023-12-12T13:00:00.000Z","end_date":"2023-12-12T14:35:00.000Z","publish_date":"2023-12-11T10:36:41.801Z","lang":"en","thumb":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_200000.jpg","thumb_list":{"thumb":"https://epgthumb.dmpcdn.com/thumbnail/057/20231212/20231212_200000.jpg","thumb_catchup":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_200500.jpg","thumb_large":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_200000.jpg"},"black_out":0,"catch_up":0,"flag":"N","info":{"channel_name":"TR MOVIE HITS","image":"https://bms.dmpcdn.com/uploads/pic/a10785bc40cd82e82ae702d8a7827393.jpg","synopsis_en":"A young teen and an alien companion harness and combine their tremendous new powers to evolve into the turbo-charged superhero Max Steel.","director":"Stewart Hendler","title_id":"718257","video":"Tf4sa0BVJVw","channel_code":"057","type":"Movie","synopsis_th":"ชายหนุ่มที่ชีวิตต้องแปรเปลี่ยนไปตลอดกาลจากอุบัติเหตุภายในห้องทดลองซึ่งทำให้เขากลายเป็นยอดมนุษย์แกร่ง","imdb_image":"https://bms.dmpcdn.com/uploads/pic/c8e9e6d49546fbde72d0f0b552db97a6.jpg","cast":"Ben Winchell, Josh Brener, Maria Bello","genres":"action","program_title":"MAX STEEL","production_year":"2016"},"isShowTime":"20:00","isActive":false},{"id":"Ena7xBxkNK3z","original_id":"057:20231212_213500","content_type":"epg","title":"Pompeii","detail":"","status":"publish","channel_code":"057","title_id":"715311","ep_id":"2397620","ep_no":"1","ep_name":"POMPEII (2014) [MHS] [R]","movie_type":"series","first_run":"Y","cast_type":"tape","start_date":"2023-12-12T14:35:00.000Z","end_date":"2023-12-12T16:20:00.000Z","publish_date":"2023-12-11T10:36:41.801Z","lang":"en","thumb":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_213500.jpg","thumb_list":{"thumb":"https://epgthumb.dmpcdn.com/thumbnail/057/20231212/20231212_213500.jpg","thumb_catchup":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_214000.jpg","thumb_large":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_213500.jpg"},"black_out":0,"catch_up":0,"flag":"N","info":{"channel_name":"TR MOVIE HITS","image":"https://bms.dmpcdn.com/uploads/pic/28b7c486d1d8c52060413fb58c869c76.jpg","synopsis_en":"Just before the fateful eruption of Mt Vesuvius, a gladiator must save the love of his life from a corrupt Roman.","director":"Paul Anderson","title_id":"715311","video":"t6TRwfxDICM","channel_code":"057","type":"Movie","synopsis_th":"เรื่องราวของหนุ่มนักรบซึ่งเสี่ยงชีพช่วยเหลือหญิงสาวผู้เป็นที่รักจากมหาวิบัติกัมปนาทครั้งใหญ่แห่งประวัติศาสตร์เมื่อภูเขาไฟวิซูเวียสเกิดปะทุขึ้น","imdb_image":"https://bms.dmpcdn.com/uploads/pic/8675b7f9a08f3f0587bed52c7a8015e1.jpg","cast":"Kit Harington, Emily Browning, Kiefer Sutherland","genres":"action","program_title":"POMPEII","production_year":"2014"},"isShowTime":"21:35","isActive":false},{"id":"WNxrPpPwwkQl","original_id":"057:20231212_232000","content_type":"epg","title":"Leon","detail":"","status":"publish","channel_code":"057","title_id":"729656","ep_id":"2488591","ep_no":"1","ep_name":"LEON [1994] [MHS]","movie_type":"series","first_run":"Y","cast_type":"tape","start_date":"2023-12-12T16:20:00.000Z","end_date":"2023-12-12T18:30:00.000Z","publish_date":"2023-12-11T10:36:41.801Z","lang":"en","thumb":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_232000.jpg","thumb_list":{"thumb":"https://epgthumb.dmpcdn.com/thumbnail/057/20231212/20231212_232000.jpg","thumb_catchup":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_232500.jpg","thumb_large":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_232000.jpg"},"black_out":0,"catch_up":0,"flag":"N","info":{"channel_name":"TR MOVIE HITS","image":"https://bms.dmpcdn.com/uploads/pic/79eabd21fdb1da338cca6b598de46cde.jpg","synopsis_en":"A hitman forms an unlikely bond with a young girl, teaching her his deadly skills while protecting her from ruthless criminals.","director":"Luc Besson","title_id":"729656","video":"aNQqoExfQsg","channel_code":"057","type":"Movie","synopsis_th":"เรื่องราวของนักฆ่าที่ได้สร้างความผูกพันธ์ที่ไม่น่าจะเป็นไปได้กับเด็กหญิง โดยสอนทักษะอันอันตรายแก่เธอพร้อมทั้งปกป้องเธอจากอาชญากรผู้โหดเหี้ยม","imdb_image":"https://bms.dmpcdn.com/uploads/pic/20be5d12ff2b8f86fb40f9db619d4cb8.jpg","cast":"Jean Reno, Gary Oldman, Natalie Portman","genres":"crime","program_title":"LEON","production_year":"1994"},"isShowTime":"23:20","isActive":false}],"audioData":{"lang_locale":"","voice_commentary":""},"playerLanguage":{"data":{"aa":"Afar","ab":"Abkhazian","af":"Afrikaans","ak":"Akan","am":"Amharic","ar":"Arabic","an":"Aragonese","as":"Assamese","av":"Avaric","ae":"Avestan","ay":"Aymara","az":"Azerbaijani","ba":"Bashkir","bm":"Bambara","be":"Belarusian","bn":"Bengali","bh":"Biharilanguages","bi":"Bislama","bs":"Bosnian","br":"Breton","bg":"Bulgarian","ca":"CatalanValencian","ch":"Chamorro","ce":"Chechen","cu":"ChurchSlavicOldSlavonicChurchSlavonicOldBulgarianOldChurchSlavonic","cv":"Chuvash","kw":"Cornish","co":"Corsican","cr":"Cree","cs":"Czech","da":"Danish","dv":"DivehiDhivehiMaldivian","dz":"Dzongkha","en":"English","eo":"Esperanto","et":"Estonian","eu":"Basque","ee":"Ewe","fo":"Faroese","fj":"Fijian","fi":"Finnish","fr":"French","fy":"WesternFrisian","ff":"Fulah","de":"German","gd":"GaelicScottishGaelic","ga":"Irish","gl":"Galician","gv":"Manx","el":"Greek,Modern(1453-)","gn":"Guarani","gu":"Gujarati","ht":"HaitianHaitianCreole","ha":"Hausa","he":"Hebrew","hz":"Herero","hi":"Hindi","ho":"HiriMotu","hr":"Croatian","hu":"Hungarian","hy":"Armenian","ig":"Igbo","io":"Ido","ii":"SichuanYiNuosu","iu":"Inuktitut","ie":"InterlingueOccidental","ia":"Interlingua(InternationalAuxiliaryLanguageAssociation)","id":"Indonesian","ik":"Inupiaq","is":"Icelandic","it":"Italian","jv":"Javanese","ja":"Japanese","kl":"KalaallisutGreenlandic","kn":"Kannada","ks":"Kashmiri","ka":"Georgian","kr":"Kanuri","kk":"Kazakh","km":"CentralKhmer","ki":"KikuyuGikuyu","rw":"Kinyarwanda","ky":"KirghizKyrgyz","kv":"Komi","kg":"Kongo","ko":"Korean","kj":"KuanyamaKwanyama","ku":"Kurdish","lo":"Lao","la":"Latin","lv":"Latvian","li":"LimburganLimburgerLimburgish","ln":"Lingala","lt":"Lithuanian","lb":"LuxembourgishLetzeburgesch","lu":"Luba-Katanga","lg":"Ganda","mh":"Marshallese","ml":"Malayalam","mr":"Marathi","mk":"Macedonian","mg":"Malagasy","mt":"Maltese","mn":"Mongolian","mi":"Maori","ms":"Malay","my":"Burmese","na":"Nauru","nv":"NavajoNavaho","nr":"Ndebele,SouthSouthNdebele","nd":"Ndebele,NorthNorthNdebele","ng":"Ndonga","ne":"Nepali","nl":"DutchFlemish","nn":"NorwegianNynorskNynorsk,Norwegian","nb":"Bokmål,NorwegianNorwegianBokmål","no":"Norwegian","ny":"ChichewaChewaNyanja","oc":"Occitan(post1500)","oj":"Ojibwa","or":"Oriya","om":"Oromo","os":"OssetianOssetic","pa":"PanjabiPunjabi","fa":"Persian","pi":"Pali","pl":"Polish","pt":"Portuguese","ps":"PushtoPashto","qu":"Quechua","rm":"Romansh","ro":"RomanianMoldavianMoldovan","rn":"Rundi","ru":"Russian","sg":"Sango","sa":"Sanskrit","si":"SinhalaSinhalese","sk":"Slovak","sl":"Slovenian","se":"NorthernSami","sm":"Samoan","sn":"Shona","sd":"Sindhi","so":"Somali","st":"Sotho,Southern","es":"SpanishCastilian","sq":"Albanian","sc":"Sardinian","sr":"Serbian","ss":"Swati","su":"Sundanese","sw":"Swahili","sv":"Swedish","ty":"Tahitian","ta":"Tamil","tt":"Tatar","te":"Telugu","tg":"Tajik","tl":"Tagalog","th":"Thai","bo":"Tibetan","ti":"Tigrinya","to":"Tonga(TongaIslands)","tn":"Tswana","ts":"Tsonga","tk":"Turkmen","tr":"Turkish","tw":"Twi","ug":"UighurUyghur","uk":"Ukrainian","ur":"Urdu","uz":"Uzbek","ve":"Venda","vi":"Vietnamese","vo":"Volapük","cy":"Welsh","wa":"Walloon","wo":"Wolof","xh":"Xhosa","yi":"Yiddish","yo":"Yoruba","za":"ZhuangChuang","zh":"Chinese","zu":"Zulu","aar":"Afar","abk":"Abkhazian","ace":"Achinese","ach":"Acoli","ada":"Adangme","ady":"AdygheAdygei","afa":"Afro-Asiaticlanguages","afh":"Afrihili","afr":"Afrikaans","ain":"Ainu","aka":"Akan","akk":"Akkadian","ale":"Aleut","alg":"Algonquianlanguages","alt":"SouthernAltai","amh":"Amharic","ang":"English,Old(ca.450-1100)","anp":"Angika","apa":"Apachelanguages","ara":"Arabic","arc":"OfficialAramaic(700-300BCE)ImperialAramaic(700-300BCE)","arg":"Aragonese","arn":"MapudungunMapuche","arp":"Arapaho","art":"Artificiallanguages","arw":"Arawak","asm":"Assamese","ast":"AsturianBableLeoneseAsturleonese","ath":"Athapascanlanguages","aus":"Australianlanguages","ava":"Avaric","ave":"Avestan","awa":"Awadhi","aym":"Aymara","aze":"Azerbaijani","bad":"Bandalanguages","bai":"Bamilekelanguages","bak":"Bashkir","bal":"Baluchi","bam":"Bambara","ban":"Balinese","bas":"Basa","bat":"Balticlanguages","bej":"BejaBedawiyet","bel":"Belarusian","bem":"Bemba","ben":"Bengali","ber":"Berberlanguages","bho":"Bhojpuri","bih":"Biharilanguages","bik":"Bikol","bin":"BiniEdo","bis":"Bislama","bla":"Siksika","bnt":"Bantulanguages","bos":"Bosnian","bra":"Braj","bre":"Breton","btk":"Bataklanguages","bua":"Buriat","bug":"Buginese","bul":"Bulgarian","bur(B)mya(T)":"Burmese","byn":"BlinBilin","cad":"Caddo","cai":"CentralAmericanIndianlanguages","car":"GalibiCarib","cat":"CatalanValencian","cau":"Caucasianlanguages","ceb":"Cebuano","cel":"Celticlanguages","cha":"Chamorro","chb":"Chibcha","che":"Chechen","chg":"Chagatai","chk":"Chuukese","chm":"Mari","chn":"Chinookjargon","cho":"Choctaw","chp":"ChipewyanDeneSuline","chr":"Cherokee","chu":"ChurchSlavicOldSlavonicChurchSlavonicOldBulgarianOldChurchSlavonic","chv":"Chuvash","chy":"Cheyenne","cmc":"Chamiclanguages","cnr":"Montenegrin","cop":"Coptic","cor":"Cornish","cos":"Corsican","cpe":"Creolesandpidgins,Englishbased","cpf":"Creolesandpidgins,French-based","cpp":"Creolesandpidgins,Portuguese-based","cre":"Cree","crh":"CrimeanTatarCrimeanTurkish","crp":"Creolesandpidgins","csb":"Kashubian","cus":"Cushiticlanguages","cze(B)ces(T)":"Czech","dak":"Dakota","dan":"Danish","dar":"Dargwa","day":"LandDayaklanguages","del":"Delaware","den":"Slave(Athapascan)","dgr":"Dogrib","din":"Dinka","div":"DivehiDhivehiMaldivian","doi":"Dogri","dra":"Dravidianlanguages","dsb":"LowerSorbian","dua":"Duala","dum":"Dutch,Middle(ca.1050-1350)","dyu":"Dyula","dzo":"Dzongkha","efi":"Efik","egy":"Egyptian(Ancient)","eka":"Ekajuk","elx":"Elamite","eng":"English","enm":"English,Middle(1100-1500)","epo":"Esperanto","est":"Estonian","baq(B)eus(T)":"Basque","ewo":"Ewondo","fan":"Fang","fao":"Faroese","fat":"Fanti","fij":"Fijian","fil":"FilipinoPilipino","fin":"Finnish","fiu":"Finno-Ugrianlanguages","fre(B)fra(T)":"French","frm":"French,Middle(ca.1400-1600)","fro":"French,Old(842-ca.1400)","frr":"NorthernFrisian","frs":"EasternFrisian","fry":"WesternFrisian","ful":"Fulah","fur":"Friulian","gaa":"Ga","gay":"Gayo","gba":"Gbaya","gem":"Germaniclanguages","ger(B)deu(T)":"German","gez":"Geez","gil":"Gilbertese","gla":"GaelicScottishGaelic","gle":"Irish","glg":"Galician","glv":"Manx","gmh":"German,MiddleHigh(ca.1050-1500)","goh":"German,OldHigh(ca.750-1050)","gon":"Gondi","gor":"Gorontalo","got":"Gothic","grb":"Grebo","grc":"Greek,Ancient(to1453)","gre(B)ell(T)":"Greek,Modern(1453-)","grn":"Guarani","gsw":"SwissGermanAlemannicAlsatian","guj":"Gujarati","gwi":"Gwich'in","hai":"Haida","hat":"HaitianHaitianCreole","hau":"Hausa","haw":"Hawaiian","heb":"Hebrew","her":"Herero","hil":"Hiligaynon","him":"HimachalilanguagesWesternPaharilanguages","hin":"Hindi","hit":"Hittite","hmn":"HmongMong","hmo":"HiriMotu","hrv":"Croatian","hsb":"UpperSorbian","hun":"Hungarian","hup":"Hupa","arm(B)hye(T)":"Armenian","iba":"Iban","ibo":"Igbo","iii":"SichuanYiNuosu","ijo":"Ijolanguages","iku":"Inuktitut","ile":"InterlingueOccidental","ilo":"Iloko","ina":"Interlingua(InternationalAuxiliaryLanguageAssociation)","inc":"Indiclanguages","ind":"Indonesian","ine":"Indo-Europeanlanguages","inh":"Ingush","ipk":"Inupiaq","ira":"Iranianlanguages","iro":"Iroquoianlanguages","ice(B)isl(T)":"Icelandic","ita":"Italian","jav":"Javanese","jbo":"Lojban","jpn":"Japanese","jpr":"Judeo-Persian","jrb":"Judeo-Arabic","kaa":"Kara-Kalpak","kab":"Kabyle","kac":"KachinJingpho","kal":"KalaallisutGreenlandic","kam":"Kamba","kan":"Kannada","kar":"Karenlanguages","kas":"Kashmiri","geo(B)kat(T)":"Georgian","kau":"Kanuri","kaw":"Kawi","kaz":"Kazakh","kbd":"Kabardian","kha":"Khasi","khi":"Khoisanlanguages","khm":"CentralKhmer","kho":"KhotaneseSakan","kik":"KikuyuGikuyu","kin":"Kinyarwanda","kir":"KirghizKyrgyz","kmb":"Kimbundu","kok":"Konkani","kom":"Komi","kon":"Kongo","kor":"Korean","kos":"Kosraean","kpe":"Kpelle","krc":"Karachay-Balkar","krl":"Karelian","kro":"Krulanguages","kru":"Kurukh","kua":"KuanyamaKwanyama","kum":"Kumyk","kur":"Kurdish","kut":"Kutenai","lad":"Ladino","lah":"Lahnda","lam":"Lamba","lat":"Latin","lav":"Latvian","lez":"Lezghian","lim":"LimburganLimburgerLimburgish","lin":"Lingala","lit":"Lithuanian","lol":"Mongo","loz":"Lozi","ltz":"LuxembourgishLetzeburgesch","lua":"Luba-Lulua","lub":"Luba-Katanga","lug":"Ganda","lui":"Luiseno","lun":"Lunda","luo":"Luo(KenyaandTanzania)","lus":"Lushai","mac(B)mkd(T)":"Macedonian","mad":"Madurese","mag":"Magahi","mah":"Marshallese","mai":"Maithili","mak":"Makasar","mal":"Malayalam","man":"Mandingo","mao(B)mri(T)":"Maori","map":"Austronesianlanguages","mar":"Marathi","mas":"Masai","may(B)msa(T)":"Malay","mdf":"Moksha","mdr":"Mandar","men":"Mende","mga":"Irish,Middle(900-1200)","mic":"Mi'kmaqMicmac","min":"Minangkabau","mis":"Uncodedlanguages","mkh":"Mon-Khmerlanguages","mlg":"Malagasy","mlt":"Maltese","mnc":"Manchu","mni":"Manipuri","mno":"Manobolanguages","moh":"Mohawk","mon":"Mongolian","mos":"Mossi","mul":"Multiplelanguages","mun":"Mundalanguages","mus":"Creek","mwl":"Mirandese","mwr":"Marwari","myn":"Mayanlanguages","myv":"Erzya","nah":"Nahuatllanguages","nai":"NorthAmericanIndianlanguages","nap":"Neapolitan","nau":"Nauru","nav":"NavajoNavaho","nbl":"Ndebele,SouthSouthNdebele","nde":"Ndebele,NorthNorthNdebele","ndo":"Ndonga","nds":"LowGermanLowSaxonGerman,LowSaxon,Low","nep":"Nepali","new":"NepalBhasaNewari","nia":"Nias","nic":"Niger-Kordofanianlanguages","niu":"Niuean","dut(B)nld(T)":"DutchFlemish","nno":"NorwegianNynorskNynorsk,Norwegian","nob":"Bokmål,NorwegianNorwegianBokmål","nog":"Nogai","non":"Norse,Old","nor":"Norwegian","nqo":"N'Ko","nso":"PediSepediNorthernSotho","nub":"Nubianlanguages","nwc":"ClassicalNewariOldNewariClassicalNepalBhasa","nya":"ChichewaChewaNyanja","nym":"Nyamwezi","nyn":"Nyankole","nyo":"Nyoro","nzi":"Nzima","oci":"Occitan(post1500)","oji":"Ojibwa","ori":"Oriya","orm":"Oromo","osa":"Osage","oss":"OssetianOssetic","ota":"Turkish,Ottoman(1500-1928)","oto":"Otomianlanguages","paa":"Papuanlanguages","pag":"Pangasinan","pal":"Pahlavi","pam":"PampangaKapampangan","pan":"PanjabiPunjabi","pap":"Papiamento","pau":"Palauan","peo":"Persian,Old(ca.600-400B.C.)","per(B)fas(T)":"Persian","phi":"Philippinelanguages","phn":"Phoenician","pli":"Pali","pol":"Polish","pon":"Pohnpeian","por":"Portuguese","pra":"Prakritlanguages","pro":"Provençal,Old(to1500)Occitan,Old(to1500)","pus":"PushtoPashto","qaa-qtz":"Reservedforlocaluse","que":"Quechua","raj":"Rajasthani","rap":"Rapanui","rar":"RarotonganCookIslandsMaori","roa":"Romancelanguages","roh":"Romansh","rom":"Romany","rum(B)ron(T)":"RomanianMoldavianMoldovan","run":"Rundi","rup":"AromanianArumanianMacedo-Romanian","rus":"Russian","sad":"Sandawe","sag":"Sango","sah":"Yakut","sai":"SouthAmericanIndianlanguages","sal":"Salishanlanguages","sam":"SamaritanAramaic","san":"Sanskrit","sas":"Sasak","sat":"Santali","scn":"Sicilian","sco":"Scots","sel":"Selkup","sem":"Semiticlanguages","sga":"Irish,Old(to900)","sgn":"SignLanguages","shn":"Shan","sid":"Sidamo","sin":"SinhalaSinhalese","sio":"Siouanlanguages","sit":"Sino-Tibetanlanguages","sla":"Slaviclanguages","slo(B)slk(T)":"Slovak","slv":"Slovenian","sma":"SouthernSami","sme":"NorthernSami","smi":"Samilanguages","smj":"LuleSami","smn":"InariSami","smo":"Samoan","sms":"SkoltSami","sna":"Shona","snd":"Sindhi","snk":"Soninke","sog":"Sogdian","som":"Somali","son":"Songhailanguages","sot":"Sotho,Southern","spa":"SpanishCastilian","alb(B)sqi(T)":"Albanian","srd":"Sardinian","srn":"SrananTongo","srp":"Serbian","srr":"Serer","ssa":"Nilo-Saharanlanguages","ssw":"Swati","suk":"Sukuma","sun":"Sundanese","sus":"Susu","sux":"Sumerian","swa":"Swahili","swe":"Swedish","syc":"ClassicalSyriac","syr":"Syriac","tah":"Tahitian","tai":"Tailanguages","tam":"Tamil","tat":"Tatar","tel":"Telugu","tem":"Timne","ter":"Tereno","tet":"Tetum","tgk":"Tajik","tgl":"Tagalog","tha":"Thai","tib(B)bod(T)":"Tibetan","tig":"Tigre","tir":"Tigrinya","tiv":"Tiv","tkl":"Tokelau","tlh":"KlingontlhIngan-Hol","tli":"Tlingit","tmh":"Tamashek","tog":"Tonga(Nyasa)","ton":"Tonga(TongaIslands)","tpi":"TokPisin","tsi":"Tsimshian","tsn":"Tswana","tso":"Tsonga","tuk":"Turkmen","tum":"Tumbuka","tup":"Tupilanguages","tur":"Turkish","tut":"Altaiclanguages","tvl":"Tuvalu","tyv":"Tuvinian","udm":"Udmurt","uga":"Ugaritic","uig":"UighurUyghur","ukr":"Ukrainian","umb":"Umbundu","und":"Undetermined","urd":"Urdu","uzb":"Uzbek","ven":"Venda","vie":"Vietnamese","vol":"Volapük","vot":"Votic","wak":"Wakashanlanguages","wal":"WolaittaWolaytta","war":"Waray","was":"Washo","wel(B)cym(T)":"Welsh","wen":"Sorbianlanguages","wln":"Walloon","wol":"Wolof","xal":"KalmykOirat","xho":"Xhosa","yap":"Yapese","yid":"Yiddish","yor":"Yoruba","ypk":"Yupiklanguages","zap":"Zapotec","zbl":"BlissymbolsBlissymbolicsBliss","zen":"Zenaga","zgh":"StandardMoroccanTamazight","zha":"ZhuangChuang","chi(B)zho(T)":"Chinese","chi":"Chinese","znd":"Zandelanguages","zul":"Zulu","zun":"Zuni","zxx":"NolinguisticcontentNotapplicable","zza":"ZazaDimiliDimliKirdkiKirmanjkiZazaki","afar":"Afar","abkhazian":"Abkhazian","achinese":"Achinese","acoli":"Acoli","adangme":"Adangme","adygheadygei":"AdygheAdygei","afro-asiaticlanguages":"Afro-Asiaticlanguages","afrihili":"Afrihili","afrikaans":"Afrikaans","ainu":"Ainu","akan":"Akan","akkadian":"Akkadian","aleut":"Aleut","algonquianlanguages":"Algonquianlanguages","southernaltai":"SouthernAltai","amharic":"Amharic","english,old(ca.450-1100)":"English,Old(ca.450-1100)","angika":"Angika","apachelanguages":"Apachelanguages","arabic":"Arabic","officialaramaic(700-300bce)imperialaramaic(700-300bce)":"OfficialAramaic(700-300BCE)ImperialAramaic(700-300BCE)","aragonese":"Aragonese","mapudungunmapuche":"MapudungunMapuche","arapaho":"Arapaho","artificiallanguages":"Artificiallanguages","arawak":"Arawak","assamese":"Assamese","asturianbableleoneseasturleonese":"AsturianBableLeoneseAsturleonese","athapascanlanguages":"Athapascanlanguages","australianlanguages":"Australianlanguages","avaric":"Avaric","avestan":"Avestan","awadhi":"Awadhi","aymara":"Aymara","azerbaijani":"Azerbaijani","bandalanguages":"Bandalanguages","bamilekelanguages":"Bamilekelanguages","bashkir":"Bashkir","baluchi":"Baluchi","bambara":"Bambara","balinese":"Balinese","basa":"Basa","balticlanguages":"Balticlanguages","bejabedawiyet":"BejaBedawiyet","belarusian":"Belarusian","bemba":"Bemba","bengali":"Bengali","berberlanguages":"Berberlanguages","bhojpuri":"Bhojpuri","biharilanguages":"Biharilanguages","bikol":"Bikol","biniedo":"BiniEdo","bislama":"Bislama","siksika":"Siksika","bantulanguages":"Bantulanguages","bosnian":"Bosnian","braj":"Braj","breton":"Breton","bataklanguages":"Bataklanguages","buriat":"Buriat","buginese":"Buginese","bulgarian":"Bulgarian","blinbilin":"BlinBilin","caddo":"Caddo","centralamericanindianlanguages":"CentralAmericanIndianlanguages","galibicarib":"GalibiCarib","catalanvalencian":"CatalanValencian","caucasianlanguages":"Caucasianlanguages","cebuano":"Cebuano","celticlanguages":"Celticlanguages","chamorro":"Chamorro","chibcha":"Chibcha","chechen":"Chechen","chagatai":"Chagatai","chuukese":"Chuukese","mari":"Mari","chinookjargon":"Chinookjargon","choctaw":"Choctaw","chipewyandenesuline":"ChipewyanDeneSuline","cherokee":"Cherokee","churchslavicoldslavonicchurchslavonicoldbulgarianoldchurchslavonic":"ChurchSlavicOldSlavonicChurchSlavonicOldBulgarianOldChurchSlavonic","chuvash":"Chuvash","cheyenne":"Cheyenne","chamiclanguages":"Chamiclanguages","montenegrin":"Montenegrin","coptic":"Coptic","cornish":"Cornish","corsican":"Corsican","creolesandpidgins,englishbased":"Creolesandpidgins,Englishbased","creolesandpidgins,french-based":"Creolesandpidgins,French-based","creolesandpidgins,portuguese-based":"Creolesandpidgins,Portuguese-based","cree":"Cree","crimeantatarcrimeanturkish":"CrimeanTatarCrimeanTurkish","creolesandpidgins":"Creolesandpidgins","kashubian":"Kashubian","cushiticlanguages":"Cushiticlanguages","czech":"Czech","dakota":"Dakota","danish":"Danish","dargwa":"Dargwa","landdayaklanguages":"LandDayaklanguages","delaware":"Delaware","slave(athapascan)":"Slave(Athapascan)","dogrib":"Dogrib","dinka":"Dinka","divehidhivehimaldivian":"DivehiDhivehiMaldivian","dogri":"Dogri","dravidianlanguages":"Dravidianlanguages","lowersorbian":"LowerSorbian","duala":"Duala","dutch,middle(ca.1050-1350)":"Dutch,Middle(ca.1050-1350)","dyula":"Dyula","dzongkha":"Dzongkha","efik":"Efik","egyptian(ancient)":"Egyptian(Ancient)","ekajuk":"Ekajuk","elamite":"Elamite","english":"English","english,middle(1100-1500)":"English,Middle(1100-1500)","esperanto":"Esperanto","estonian":"Estonian","basque":"Basque","ewe":"Ewe","ewondo":"Ewondo","fang":"Fang","faroese":"Faroese","fanti":"Fanti","fijian":"Fijian","filipinopilipino":"FilipinoPilipino","finnish":"Finnish","finno-ugrianlanguages":"Finno-Ugrianlanguages","fon":"Fon","french":"French","french,middle(ca.1400-1600)":"French,Middle(ca.1400-1600)","french,old(842-ca.1400)":"French,Old(842-ca.1400)","northernfrisian":"NorthernFrisian","easternfrisian":"EasternFrisian","westernfrisian":"WesternFrisian","fulah":"Fulah","friulian":"Friulian","gayo":"Gayo","gbaya":"Gbaya","germaniclanguages":"Germaniclanguages","german":"German","geez":"Geez","gilbertese":"Gilbertese","gaelicscottishgaelic":"GaelicScottishGaelic","irish":"Irish","galician":"Galician","manx":"Manx","german,middlehigh(ca.1050-1500)":"German,MiddleHigh(ca.1050-1500)","german,oldhigh(ca.750-1050)":"German,OldHigh(ca.750-1050)","gondi":"Gondi","gorontalo":"Gorontalo","gothic":"Gothic","grebo":"Grebo","greek,ancient(to1453)":"Greek,Ancient(to1453)","greek,modern(1453-)":"Greek,Modern(1453-)","guarani":"Guarani","swissgermanalemannicalsatian":"SwissGermanAlemannicAlsatian","gujarati":"Gujarati","gwich'in":"Gwich'in","haida":"Haida","haitianhaitiancreole":"HaitianHaitianCreole","hausa":"Hausa","hawaiian":"Hawaiian","hebrew":"Hebrew","herero":"Herero","hiligaynon":"Hiligaynon","himachalilanguageswesternpaharilanguages":"HimachalilanguagesWesternPaharilanguages","hindi":"Hindi","hittite":"Hittite","hmongmong":"HmongMong","hirimotu":"HiriMotu","croatian":"Croatian","uppersorbian":"UpperSorbian","hungarian":"Hungarian","hupa":"Hupa","armenian":"Armenian","iban":"Iban","igbo":"Igbo","ido":"Ido","sichuanyinuosu":"SichuanYiNuosu","ijolanguages":"Ijolanguages","inuktitut":"Inuktitut","interlingueoccidental":"InterlingueOccidental","iloko":"Iloko","interlingua(internationalauxiliarylanguageassociation)":"Interlingua(InternationalAuxiliaryLanguageAssociation)","indiclanguages":"Indiclanguages","indonesian":"Indonesian","indo-europeanlanguages":"Indo-Europeanlanguages","ingush":"Ingush","inupiaq":"Inupiaq","iranianlanguages":"Iranianlanguages","iroquoianlanguages":"Iroquoianlanguages","icelandic":"Icelandic","italian":"Italian","javanese":"Javanese","lojban":"Lojban","japanese":"Japanese","judeo-persian":"Judeo-Persian","judeo-arabic":"Judeo-Arabic","kara-kalpak":"Kara-Kalpak","kabyle":"Kabyle","kachinjingpho":"KachinJingpho","kalaallisutgreenlandic":"KalaallisutGreenlandic","kamba":"Kamba","kannada":"Kannada","karenlanguages":"Karenlanguages","kashmiri":"Kashmiri","georgian":"Georgian","kanuri":"Kanuri","kawi":"Kawi","kazakh":"Kazakh","kabardian":"Kabardian","khasi":"Khasi","khoisanlanguages":"Khoisanlanguages","centralkhmer":"CentralKhmer","khotanesesakan":"KhotaneseSakan","kikuyugikuyu":"KikuyuGikuyu","kinyarwanda":"Kinyarwanda","kirghizkyrgyz":"KirghizKyrgyz","kimbundu":"Kimbundu","konkani":"Konkani","komi":"Komi","kongo":"Kongo","korean":"Korean","kosraean":"Kosraean","kpelle":"Kpelle","karachay-balkar":"Karachay-Balkar","karelian":"Karelian","krulanguages":"Krulanguages","kurukh":"Kurukh","kuanyamakwanyama":"KuanyamaKwanyama","kumyk":"Kumyk","kurdish":"Kurdish","kutenai":"Kutenai","ladino":"Ladino","lahnda":"Lahnda","lamba":"Lamba","lao":"Lao","latin":"Latin","latvian":"Latvian","lezghian":"Lezghian","limburganlimburgerlimburgish":"LimburganLimburgerLimburgish","lingala":"Lingala","lithuanian":"Lithuanian","mongo":"Mongo","lozi":"Lozi","luxembourgishletzeburgesch":"LuxembourgishLetzeburgesch","luba-lulua":"Luba-Lulua","luba-katanga":"Luba-Katanga","ganda":"Ganda","luiseno":"Luiseno","lunda":"Lunda","luo(kenyaandtanzania)":"Luo(KenyaandTanzania)","lushai":"Lushai","madurese":"Madurese","magahi":"Magahi","marshallese":"Marshallese","maithili":"Maithili","makasar":"Makasar","malayalam":"Malayalam","mandingo":"Mandingo","austronesianlanguages":"Austronesianlanguages","marathi":"Marathi","masai":"Masai","moksha":"Moksha","mandar":"Mandar","mende":"Mende","irish,middle(900-1200)":"Irish,Middle(900-1200)","mi'kmaqmicmac":"Mi'kmaqMicmac","minangkabau":"Minangkabau","uncodedlanguages":"Uncodedlanguages","macedonian":"Macedonian","mon-khmerlanguages":"Mon-Khmerlanguages","malagasy":"Malagasy","maltese":"Maltese","manchu":"Manchu","manipuri":"Manipuri","manobolanguages":"Manobolanguages","mohawk":"Mohawk","mongolian":"Mongolian","mossi":"Mossi","maori":"Maori","malay":"Malay","multiplelanguages":"Multiplelanguages","mundalanguages":"Mundalanguages","creek":"Creek","mirandese":"Mirandese","marwari":"Marwari","burmese":"Burmese","mayanlanguages":"Mayanlanguages","erzya":"Erzya","nahuatllanguages":"Nahuatllanguages","northamericanindianlanguages":"NorthAmericanIndianlanguages","neapolitan":"Neapolitan","nauru":"Nauru","navajonavaho":"NavajoNavaho","ndebele,southsouthndebele":"Ndebele,SouthSouthNdebele","ndebele,northnorthndebele":"Ndebele,NorthNorthNdebele","ndonga":"Ndonga","lowgermanlowsaxongerman,lowsaxon,low":"LowGermanLowSaxonGerman,LowSaxon,Low","nepali":"Nepali","nepalbhasanewari":"NepalBhasaNewari","nias":"Nias","niger-kordofanianlanguages":"Niger-Kordofanianlanguages","niuean":"Niuean","dutchflemish":"DutchFlemish","norwegiannynorsknynorsk,norwegian":"NorwegianNynorskNynorsk,Norwegian","bokmål,norwegiannorwegianbokmål":"Bokmål,NorwegianNorwegianBokmål","nogai":"Nogai","norse,old":"Norse,Old","norwegian":"Norwegian","n'ko":"N'Ko","pedisepedinorthernsotho":"PediSepediNorthernSotho","nubianlanguages":"Nubianlanguages","classicalnewarioldnewariclassicalnepalbhasa":"ClassicalNewariOldNewariClassicalNepalBhasa","chichewachewanyanja":"ChichewaChewaNyanja","nyamwezi":"Nyamwezi","nyankole":"Nyankole","nyoro":"Nyoro","nzima":"Nzima","occitan(post1500)":"Occitan(post1500)","ojibwa":"Ojibwa","oriya":"Oriya","oromo":"Oromo","osage":"Osage","ossetianossetic":"OssetianOssetic","turkish,ottoman(1500-1928)":"Turkish,Ottoman(1500-1928)","otomianlanguages":"Otomianlanguages","papuanlanguages":"Papuanlanguages","pangasinan":"Pangasinan","pahlavi":"Pahlavi","pampangakapampangan":"PampangaKapampangan","panjabipunjabi":"PanjabiPunjabi","papiamento":"Papiamento","palauan":"Palauan","persian,old(ca.600-400b.c.)":"Persian,Old(ca.600-400B.C.)","persian":"Persian","philippinelanguages":"Philippinelanguages","phoenician":"Phoenician","pali":"Pali","polish":"Polish","pohnpeian":"Pohnpeian","portuguese":"Portuguese","prakritlanguages":"Prakritlanguages","provençal,old(to1500)occitan,old(to1500)":"Provençal,Old(to1500)Occitan,Old(to1500)","pushtopashto":"PushtoPashto","reservedforlocaluse":"Reservedforlocaluse","quechua":"Quechua","rajasthani":"Rajasthani","rapanui":"Rapanui","rarotongancookislandsmaori":"RarotonganCookIslandsMaori","romancelanguages":"Romancelanguages","romansh":"Romansh","romany":"Romany","romanianmoldavianmoldovan":"RomanianMoldavianMoldovan","rundi":"Rundi","aromanianarumanianmacedo-romanian":"AromanianArumanianMacedo-Romanian","russian":"Russian","sandawe":"Sandawe","sango":"Sango","yakut":"Yakut","southamericanindianlanguages":"SouthAmericanIndianlanguages","salishanlanguages":"Salishanlanguages","samaritanaramaic":"SamaritanAramaic","sanskrit":"Sanskrit","sasak":"Sasak","santali":"Santali","sicilian":"Sicilian","scots":"Scots","selkup":"Selkup","semiticlanguages":"Semiticlanguages","irish,old(to900)":"Irish,Old(to900)","signlanguages":"SignLanguages","shan":"Shan","sidamo":"Sidamo","sinhalasinhalese":"SinhalaSinhalese","siouanlanguages":"Siouanlanguages","sino-tibetanlanguages":"Sino-Tibetanlanguages","slaviclanguages":"Slaviclanguages","slovak":"Slovak","slovenian":"Slovenian","southernsami":"SouthernSami","northernsami":"NorthernSami","samilanguages":"Samilanguages","lulesami":"LuleSami","inarisami":"InariSami","samoan":"Samoan","skoltsami":"SkoltSami","shona":"Shona","sindhi":"Sindhi","soninke":"Soninke","sogdian":"Sogdian","somali":"Somali","songhailanguages":"Songhailanguages","sotho,southern":"Sotho,Southern","spanishcastilian":"SpanishCastilian","albanian":"Albanian","sardinian":"Sardinian","sranantongo":"SrananTongo","serbian":"Serbian","serer":"Serer","nilo-saharanlanguages":"Nilo-Saharanlanguages","swati":"Swati","sukuma":"Sukuma","sundanese":"Sundanese","susu":"Susu","sumerian":"Sumerian","swahili":"Swahili","swedish":"Swedish","classicalsyriac":"ClassicalSyriac","syriac":"Syriac","tahitian":"Tahitian","tailanguages":"Tailanguages","tamil":"Tamil","tatar":"Tatar","telugu":"Telugu","timne":"Timne","tereno":"Tereno","tetum":"Tetum","tajik":"Tajik","tagalog":"Tagalog","thai":"Thai","tibetan":"Tibetan","tigre":"Tigre","tigrinya":"Tigrinya","tokelau":"Tokelau","klingontlhingan-hol":"KlingontlhIngan-Hol","tlingit":"Tlingit","tamashek":"Tamashek","tonga(nyasa)":"Tonga(Nyasa)","tonga(tongaislands)":"Tonga(TongaIslands)","tokpisin":"TokPisin","tsimshian":"Tsimshian","tswana":"Tswana","tsonga":"Tsonga","turkmen":"Turkmen","tumbuka":"Tumbuka","tupilanguages":"Tupilanguages","turkish":"Turkish","altaiclanguages":"Altaiclanguages","tuvalu":"Tuvalu","twi":"Twi","tuvinian":"Tuvinian","udmurt":"Udmurt","ugaritic":"Ugaritic","uighuruyghur":"UighurUyghur","ukrainian":"Ukrainian","umbundu":"Umbundu","undetermined":"Undetermined","urdu":"Urdu","uzbek":"Uzbek","vai":"Vai","venda":"Venda","vietnamese":"Vietnamese","volapük":"Volapük","votic":"Votic","wakashanlanguages":"Wakashanlanguages","wolaittawolaytta":"WolaittaWolaytta","waray":"Waray","washo":"Washo","welsh":"Welsh","sorbianlanguages":"Sorbianlanguages","walloon":"Walloon","wolof":"Wolof","kalmykoirat":"KalmykOirat","xhosa":"Xhosa","yao":"Yao","yapese":"Yapese","yiddish":"Yiddish","yoruba":"Yoruba","yupiklanguages":"Yupiklanguages","zapotec":"Zapotec","blissymbolsblissymbolicsbliss":"BlissymbolsBlissymbolicsBliss","zenaga":"Zenaga","standardmoroccantamazight":"StandardMoroccanTamazight","zhuangchuang":"ZhuangChuang","chinese":"Chinese","zandelanguages":"Zandelanguages","zulu":"Zulu","zuni":"Zuni","nolinguisticcontentnotapplicable":"NolinguisticcontentNotapplicable","zazadimilidimlikirdkikirmanjkizazaki":"ZazaDimiliDimliKirdkiKirmanjkiZazaki","influencer1":"Influencer1","influencer2":"Influencer2","original":"Original"}},"ccu":458,"deviceId":"yxN2Cd3kIhYKTM7aYAxGl5NnYCAdyIlQ","isAllowedWeb":true,"epgActive":{},"configBlackout":{"duration":300000,"enable":false,"btn_channel_list":{"title_en":"","title_th":"","url":"","url_th":"","url_en":""}},"activeCategory":"","adsSideBar":{"adsData":{"ALL":{"targetingArguments":{"TrueID_page":[],"Device":[]},"sizeMapping":[{"viewport":[0,0],"sizes":[[320,250],[300,250],[1,1],"fluid"]}],"slotId":"div-gpt-ad-rt-1","adUnit":"21682623839/TrueID_Web/TV","sizes":[[320,250]]}},"adsConfig":{"adsNetworkId":"","adsUnit":"21682623839/TrueID_Web/TV"}},"currentURL":"https://tv.trueid.net/th-en/live/true-movie-hits"},"__N_SSP":true} \ No newline at end of file +{ + "pageProps":{ + "currentLang":{ + "country":"th", + "lang":"en" + }, + "isBotPerformance":false, + "titleH1":"Watch Live TV Online 24 hours", + "metaData":{ + "title":"ดูทีวีออนไลน์ True Movie Hits - TrueID TV", + "description":"Watch Live TV Online 24 hours, Thai Drama, Full HD", + "imageURL":"https://cms.dmpcdn.com/livetv/2023/04/28/45345d10-e599-11ed-86b8-bb40638e3c49_webp_original.png", + "currentUrl":"https://tv.trueid.net/th-en/live/true-movie-hits", + "metaTitle":"ดูทีวีออนไลน์ True Movie Hits - TrueID TV" + }, + "channelList":[ + { + "id":"nQlqONGyoa4", + "thumb":"https://cms.dmpcdn.com/livetv/2023/07/24/5ff3e270-29cc-11ee-b2f4-e9de482d866e_webp_original.webp", + "slug":"ch3-hd", + "title":"Channel 3", + "content_type":"livetv", + "category":"livetv-ca|digitaltv-ca|entertainment-ca|freetv-ca", + "content_provider":"", + "channel_code":"c03", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"ប៉ុស្តិ៍ 3 HD", + "channel_name_eng":"CH3 HD", + "channel_name_mm":"CH3 HD", + "channel_name_th":"ช่อง 3 HD" + }, + "views":96184, + "isLiveChat":false + }, + { + "id":"wKngqJ2Vqnl", + "thumb":"https://cms.dmpcdn.com/livetv/2019/01/10/35a35017-8473-4953-8474-5c58d805b74a.png", + "slug":"mono29", + "title":"MONO 29", + "content_type":"livetv", + "category":"livetv-ca|digitaltv-ca|freetv-ca|movies-series-ca", + "content_provider":"", + "channel_code":"d43", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"មូណូ ធីវី", + "channel_name_eng":"Mono 29", + "channel_name_mm":"Mono 29", + "channel_name_th":"โมโน 29" + }, + "views":33721, + "isLiveChat":false + }, + { + "id":"8v732AYomo9", + "thumb":"https://cms.dmpcdn.com/livetv/2023/07/18/7dc7a180-2515-11ee-b8b2-77e2a8f4c31e_webp_original.webp", + "slug":"thairathtv-hd", + "title":"Thairath TV", + "content_type":"livetv", + "category":"livetv-ca|digitaltv-ca|freetv-ca|news-ca", + "content_provider":"", + "channel_code":"d05", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"ថៃរ៉ាត់ ធីវី HD", + "channel_name_eng":"Thairath TV HD", + "channel_name_mm":"Thairath TV HD", + "channel_name_th":"ไทยรัฐ ทีวี HD" + }, + "views":17228, + "isLiveChat":false + }, + { + "id":"9O54lyP5Rqx", + "thumb":"https://cms.dmpcdn.com/livetv/2023/07/19/212d15e0-25e7-11ee-bfc1-85e95548413c_webp_original.webp", + "slug":"ch7-hd", + "title":"Channel 7HD", + "content_type":"livetv", + "category":"livetv-ca|digitaltv-ca|entertainment-ca|freetv-ca", + "content_provider":"", + "channel_code":"c07", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"ប៉ុស្តិ៍​ 7", + "channel_name_eng":"CH 7HD", + "channel_name_mm":"Channel 7", + "channel_name_th":"ช่อง 7HD" + }, + "views":12092, + "isLiveChat":false + }, + { + "id":"0z4lvq6Xwoa", + "thumb":"https://cms.dmpcdn.com/livetv/2019/01/16/396384be-35dc-4d11-bf04-06c9546ec7bc.png", + "slug":"one-hd", + "title":"One31", + "content_type":"livetv", + "category":"livetv-ca|digitaltv-ca|entertainment-ca|freetv-ca", + "content_provider":"", + "channel_code":"d56", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"វ័ន HD", + "channel_name_eng":"One HD", + "channel_name_mm":"One HD", + "channel_name_th":"วัน HD" + }, + "views":10182, + "isLiveChat":false + }, + { + "id":"vqbr1WgEnGQ", + "thumb":"https://cms.dmpcdn.com/livetv/2023/09/15/5408a390-5377-11ee-8e1b-194edbb69638_webp_original.webp", + "slug":"ch8", + "title":"Channel 8", + "content_type":"livetv", + "category":"livetv-ca|digitaltv-ca|entertainment-ca|freetv-ca", + "content_provider":"", + "channel_code":"d62", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"ប៉ុស្តិ៍ 8", + "channel_name_eng":"CH8", + "channel_name_th":"ช่อง 8" + }, + "views":8295, + "isLiveChat":false + }, + { + "id":"OVKwZle4eop", + "thumb":"https://cms.dmpcdn.com/livetv/2023/09/15/84504210-5377-11ee-aaa1-7d584d8ca7a4_webp_original.webp", + "slug":"true4u", + "title":"True4U", + "content_type":"livetv", + "category":"livetv-ca|digitaltv-ca|entertainment-ca|freetv-ca|movies-series-ca", + "content_provider":"", + "channel_code":"207", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"ទ្រូ4យូ", + "channel_name_chi":"True4U", + "channel_name_eng":"True4U", + "channel_name_mm":"True4U", + "channel_name_rus":"True4U", + "channel_name_th":"ทรูโฟร์ยู", + "channel_name_vie":"True4U" + }, + "views":6489, + "isLiveChat":false + }, + { + "id":"OBb6NzoJX7O", + "thumb":"https://cms.dmpcdn.com/livetv/2023/10/02/d2ec4b30-60f1-11ee-92a4-8597bcef0049_webp_original.webp", + "slug":"amarintv-hd", + "title":"Amarin TV", + "content_type":"livetv", + "category":"livetv-ca|digitaltv-ca|freetv-ca", + "content_provider":"", + "channel_code":"da0", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"អាម៉ារិន", + "channel_name_eng":"Amarin TV", + "channel_name_mm":"Amarin TV", + "channel_name_th":"อมรินทร์" + }, + "views":6407, + "isLiveChat":false + }, + { + "id":"yYk6PvXwXDb", + "thumb":"https://cms.dmpcdn.com/livetv/2023/11/17/2a1de990-852d-11ee-bf98-41acc8fd04fc_webp_original.webp", + "slug":"workpointtv", + "title":"WorkPoint TV", + "content_type":"livetv", + "category":"livetv-ca|digitaltv-ca|entertainment-ca|freetv-ca", + "content_provider":"", + "channel_code":"d83", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"វើកភ័ញ គ្រីអ៊ែតធិវ ធីវី​", + "channel_name_eng":"Workpoint TV", + "channel_name_mm":"Workpoint TV", + "channel_name_th":"เวิร์คพอยท์ ทีวี" + }, + "views":6075, + "isLiveChat":false + }, + { + "id":"qvgeWLPGMY6", + "thumb":"https://cms.dmpcdn.com/livetv/2020/11/19/ed873d50-2a22-11eb-bed4-0972e345f90c_original.png", + "slug":"gmm25", + "title":"GMM 25", + "content_type":"livetv", + "category":"livetv-ca|digitaltv-ca|entertainment-ca|freetv-ca", + "content_provider":"", + "channel_code":"d76", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"GMM 25", + "channel_name_eng":"GMM 25", + "channel_name_mm":"GMM 25", + "channel_name_th":"จีเอ็มเอ็ม 25" + }, + "views":4861, + "isLiveChat":false + }, + { + "id":"zMLBpX7AWmk", + "thumb":"https://cms.dmpcdn.com/livetv/2023/09/09/9c6f59c0-4ebe-11ee-99a7-832609069236_webp_original.webp", + "slug":"nationtv", + "title":"Nation TV", + "content_type":"livetv", + "category":"livetv-ca|digitaltv-ca|freetv-ca|news-ca", + "content_provider":"", + "channel_code":"d78", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"Nation TV 22", + "channel_name_chi":"Nation TV 22", + "channel_name_eng":"Nation TV 22", + "channel_name_mm":"Nation TV 22", + "channel_name_rus":"Nation TV 22", + "channel_name_th":"เนชั่น ทีวี", + "channel_name_vie":"Nation TV 22" + }, + "views":4733, + "isLiveChat":false + }, + { + "id":"QNBwOpdaxpQ", + "thumb":"https://cms.dmpcdn.com/livetv/2023/08/28/012eed00-458a-11ee-bd2b-6734a2d9e428_webp_original.webp", + "slug":"pptv-hd", + "title":"PPTV", + "content_type":"livetv", + "category":"livetv-ca|digitaltv-ca|freetv-ca", + "content_provider":"", + "channel_code":"da7", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"ភីភីធីវី", + "channel_name_eng":"PPTV", + "channel_name_mm":"PPTV", + "channel_name_th":"พีพีทีวี" + }, + "views":4723, + "isLiveChat":false + }, + { + "id":"xqY73dWBoZye", + "thumb":"https://cms.dmpcdn.com/livetv/2023/05/03/ba425a00-e966-11ed-be07-cbff4c6d2c94_webp_original.png", + "slug":"truepremierfootballhd1", + "title":"True Premier Football 1", + "content_type":"livetv", + "category":"livetv-ca|football-ca|sports-ca|tvsnow|sports", + "content_provider":"true_vision", + "channel_code":"ht111", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"True Premier Football 1", + "channel_name_th":"ทรู พรีเมียร์ ฟุตบอล 1" + }, + "views":3123, + "isLiveChat":true + }, + { + "id":"QRP2K658b7G", + "thumb":"https://cms.dmpcdn.com/livetv/2023/09/15/ab170410-5377-11ee-8e1b-194edbb69638_webp_original.webp", + "slug":"thaipbs", + "title":"Thai PBS", + "content_type":"livetv", + "category":"livetv-ca|digitaltv-ca|freetv-ca|news-ca", + "content_provider":"", + "channel_code":"c12", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"ធីភីបីអេស", + "channel_name_eng":"TPBS", + "channel_name_mm":"TPBS", + "channel_name_th":"ไทยพีบีเอส" + }, + "views":2526, + "isLiveChat":false + }, + { + "id":"OZeq8ZLPldY", + "thumb":"https://cms.dmpcdn.com/livetv/2023/10/02/75023d90-60f1-11ee-935a-5d4eba985103_webp_original.webp", + "slug":"tnn16", + "title":"TNN 16", + "content_type":"livetv", + "category":"livetv-ca|digitaltv-ca|freetv-ca|news-ca|tvsnow|tvsnews", + "content_provider":"true_vision", + "channel_code":"135", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"ធីអិនអិន 16", + "channel_name_eng":"TNN​ 16", + "channel_name_mm":"TNN​ 16", + "channel_name_th":"ทีเอ็นเอ็น 16" + }, + "views":2444, + "isLiveChat":false + }, + { + "id":"LY2j6Pyxbla", + "thumb":"https://cms.dmpcdn.com/livetv/2023/10/02/4a2afc60-60f1-11ee-a78e-f70ba0052fab_webp_original.webp", + "slug":"nbt", + "title":"NBT", + "content_type":"livetv", + "category":"livetv-ca|digitaltv-ca|freetv-ca", + "content_provider":"", + "channel_code":"c11", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"អិនបីធី​", + "channel_name_eng":"NBT", + "channel_name_mm":"์NBT", + "channel_name_th":"เอ็นบีที" + }, + "views":2043, + "isLiveChat":false + }, + { + "id":"Z9E4LnAbgjKy", + "thumb":"https://cms.dmpcdn.com/livetv/2021/06/15/3e4e0540-cdb4-11eb-9a22-7958179a38a7_original.png", + "slug":"jkn18", + "title":"JKN 18", + "content_type":"livetv", + "category":"livetv-ca|digitaltv-ca|freetv-ca", + "content_provider":"", + "channel_code":"d11", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"JKN 18", + "channel_name_mm":"JKN 18", + "channel_name_th":"เจเคเอ็น 18" + }, + "views":1725, + "isLiveChat":false + }, + { + "id":"rBWOx89v9Rk", + "thumb":"https://cms.dmpcdn.com/livetv/2023/09/09/9cc40970-4ebe-11ee-9801-97f95b5eed9a_webp_original.webp", + "slug":"9mcot-hd", + "title":"9 MCOT", + "content_type":"livetv", + "category":"livetv-ca|digitaltv-ca|freetv-ca", + "content_provider":"", + "channel_code":"c09", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"ប៉ុស្តិ៍ 9 HD", + "channel_name_eng":"9 MCOT HD", + "channel_name_mm":"9 MCOT HD", + "channel_name_th":"9 เอ็มคอต HD" + }, + "views":1323, + "isLiveChat":false + }, + { + "id":"5PKobQk5gLOP", + "thumb":"https://cms.dmpcdn.com/livetv/2023/07/05/b74a2460-1b05-11ee-8ce6-b102b53cb4a2_webp_original.webp", + "slug":"boomerang-hd", + "title":"Boomerang", + "content_type":"livetv", + "category":"livetv-ca|freetv-ca|kids-ca", + "content_provider":"", + "channel_code":"i007", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"Boomerang", + "channel_name_th":"บูมเมอแรง" + }, + "views":707, + "isLiveChat":false + }, + { + "id":"KEN52vz3o6M", + "thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/296e96a0-e593-11ed-8507-4fc0b025fedb_webp_original.png", + "slug":"truesport-hd-3", + "title":"True Sports 3", + "content_type":"livetv", + "category":"livetv-ca|sports-ca|tvsnow|sports", + "content_provider":"true_vision", + "channel_code":"ht117", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"True Sports 3", + "channel_name_chi":"True Sports 3", + "channel_name_eng":"True Sports 3", + "channel_name_mm":"True Sports 3", + "channel_name_rus":"True Sports 3", + "channel_name_th":"ทรูสปอร์ต 3", + "channel_name_vie":"True Sports 3" + }, + "views":652, + "isLiveChat":true + }, + { + "id":"1KDEkNJDZ9r", + "thumb":"https://cms.dmpcdn.com/livetv/2023/07/18/7e060a10-2515-11ee-864f-a52221dad038_webp_original.webp", + "slug":"ch5", + "title":"TV5 HD", + "content_type":"livetv", + "category":"livetv-ca|digitaltv-ca|freetv-ca|news-ca", + "content_provider":"", + "channel_code":"c05", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"ប៉ុស្តិ៍ 5", + "channel_name_eng":"CH 5", + "channel_name_mm":"နံပါတ္ 5 အစီအစဥ", + "channel_name_th":"ช่อง 5" + }, + "views":648, + "isLiveChat":false + }, + { + "id":"NopZ5gjkGmE", + "thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/45345d10-e599-11ed-86b8-bb40638e3c49_webp_original.png", + "slug":"true-movie-hits", + "title":"True Movie Hits", + "content_type":"livetv", + "category":"livetv-ca|movies-series-ca|trueunlock-ca|tvsnow|movieseries", + "content_provider":"true_vision", + "channel_code":"057", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"True Movie Hits", + "channel_name_chi":"True Movie Hits", + "channel_name_eng":"True Movie Hits", + "channel_name_mm":"True Movie Hits", + "channel_name_rus":"True Movie Hits", + "channel_name_th":"True Movie Hits", + "channel_name_vie":"True Movie Hits" + }, + "views":640, + "isLiveChat":false + }, + { + "id":"9xQq7Yk7Jzr", + "thumb":"https://cms.dmpcdn.com/livetv/2023/07/18/7d74eda0-2515-11ee-864f-a52221dad038_webp_original.webp", + "slug":"realitychannel-hd", + "title":"Reality", + "content_type":"livetv", + "category":"livetv-ca|education-ca|entertainment-ca|freetv-ca|kids-ca|truelittlemonk|tvsnow|entertainment", + "content_provider":"true_vision", + "channel_code":"107", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"ប៉ុស្តិ៍ រែលអាលីធី", + "channel_name_eng":"Reality", + "channel_name_mm":"ထရူး ပူပန္ယာ", + "channel_name_th":"เรียลลิตี้" + }, + "views":518, + "isLiveChat":false + }, + { + "id":"GPVMYwpnzKv", + "thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/f63723d0-e595-11ed-abcb-c792e696f885_webp_original.png", + "slug":"truesport-7", + "title":"True Sports 7", + "content_type":"livetv", + "category":"livetv-ca|sports-ca|trueunlock-ca|tvsnow|sports", + "content_provider":"true_vision", + "channel_code":"105", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"True Sports 7", + "channel_name_chi":"True Sports 7", + "channel_name_eng":"True Sports 7", + "channel_name_mm":"True Sports 7", + "channel_name_rus":"True Sports 7", + "channel_name_th":"ทรูสปอร์ต 7", + "channel_name_vie":"True Sports 7" + }, + "views":499, + "isLiveChat":true + }, + { + "id":"RN8ALdyRovrj", + "thumb":"https://cms.dmpcdn.com/livetv/2022/02/10/e00c0e00-8a3c-11ec-8f9e-831d2ccecc69_webp_original.png", + "slug":"t-sports-7-sd", + "title":"T Sports 7", + "content_type":"livetv", + "category":"livetv-ca|digitaltv-ca|freetv-ca|sports-ca", + "content_provider":"", + "channel_code":"t514", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"T Sports 7", + "channel_name_th":"สถานีโทรทัศน์เพื่อการท่องเที่ยวและกีฬา" + }, + "views":484, + "isLiveChat":false + }, + { + "id":"AlPo3NzNZa62", + "thumb":"https://cms.dmpcdn.com/livetv/2023/05/03/ba4c4510-e966-11ed-896e-69ce273284a6_webp_original.png", + "slug":"truepremierfootballhd2", + "title":"True Premier Football 2", + "content_type":"livetv", + "category":"livetv-ca|football-ca|sports-ca|tvsnow|sports", + "content_provider":"true_vision", + "channel_code":"ht112", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"True Premier Football 2", + "channel_name_th":"ทรู พรีเมียร์ ฟุตบอล 2" + }, + "views":449, + "isLiveChat":true + }, + { + "id":"PanRBOzKovQ", + "thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/43f28e40-e599-11ed-844f-795506bf0bf9_webp_original.png", + "slug":"true-film-hd", + "title":"True Film 1", + "content_type":"livetv", + "category":"hbtv-trueidtv-all|livetv-ca|movies-series-ca|trueidtv-movies-series|tvsnow|movieseries", + "content_provider":"true_vision", + "channel_code":"176", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"True Film 1", + "channel_name_chi":"True Film 1", + "channel_name_eng":"True Film 1", + "channel_name_mm":"True Film 1", + "channel_name_rus":"True Film 1", + "channel_name_th":"True Film 1", + "channel_name_vie":"True Film 1" + }, + "views":388, + "isLiveChat":false + }, + { + "id":"GNd67OBJ6pv", + "thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/46f1c480-e599-11ed-96ec-4d05b9e2ca86_webp_original.png", + "slug":"thai-film", + "title":"True Thai Film", + "content_type":"livetv", + "category":"livetv-ca|movies-series-ca|trueunlock-ca|tvsnow|movieseries", + "content_provider":"true_vision", + "channel_code":"094", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"True Thai Film", + "channel_name_chi":"True Thai Film", + "channel_name_eng":"True Thai Film", + "channel_name_mm":"True Thai Film", + "channel_name_rus":"True Thai Film", + "channel_name_th":"True Thai Film", + "channel_name_vie":"True Thai Film" + }, + "views":359, + "isLiveChat":false + }, + { + "id":"a0k7zw9OPrr0", + "thumb":"https://cms.dmpcdn.com/livetv/2020/07/14/72c22620-c5aa-11ea-a8d3-2b56c8ce453d_original.png", + "slug":"altv", + "title":"ALTV", + "content_type":"livetv", + "category":"livetv-ca|digitaltv-ca|education-ca|freetv-ca", + "content_provider":"", + "channel_code":"dum024", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"ALTV", + "channel_name_th":"เอแอลทีวี" + }, + "views":255, + "isLiveChat":false + }, + { + "id":"9WmoQMj0NOp", + "thumb":"https://cms.dmpcdn.com/livetv/2023/11/22/932dbce0-8919-11ee-820d-0ff332ca746f_webp_original.webp", + "slug":"trueplookpanya", + "title":"True Plook Panya", + "content_type":"livetv", + "category":"livetv-ca|documentary-ca|tvsnow|documentary", + "content_provider":"true_vision", + "channel_code":"139", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"ទ្រូបណ្តុះគំណិត", + "channel_name_eng":"True Plookpanya", + "channel_name_mm":"True Plookpanya", + "channel_name_th":"ทรู ปลูกปัญญา" + }, + "views":242, + "isLiveChat":false + }, + { + "id":"KlW9OymBRqrD", + "thumb":"https://cms.dmpcdn.com/livetv/2023/09/25/c3898e20-5b4f-11ee-a599-1d1a4f7c1125_webp_original.webp", + "slug":"trueid-sports", + "title":"TrueID Sports", + "content_type":"livetv", + "category":"livetv-ca|sports-ca", + "content_provider":"", + "channel_code":"he003", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"TrueID Sports", + "channel_name_th":"ทรูไอดี สปอร์ต" + }, + "views":240, + "isLiveChat":true + }, + { + "id":"Vwz1j7XVRkdn", + "thumb":"https://cms.dmpcdn.com/livetv/2023/08/02/89262f60-30e1-11ee-b445-3703761d6f4d_webp_original.webp", + "slug":"true-ball-thai-1", + "title":"True Ball Thai 1", + "content_type":"livetv", + "category":"livetv-ca|football-ca|sports-ca|tvsnow|sports", + "content_provider":"true_vision", + "channel_code":"vc01", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"True Ball Thai 1", + "channel_name_th":"True Ball Thai 1" + }, + "views":234, + "isLiveChat":false + }, + { + "id":"YmaygkwgE6Lm", + "thumb":"https://cms.dmpcdn.com/livetv/2023/10/02/75023d90-60f1-11ee-935a-5d4eba985103_webp_original.webp", + "slug":"tnn16-hd", + "title":"TNN 16 HD", + "content_type":"livetv", + "category":"livetv-ca|news-ca|tnn|tvsnow|tvsnews", + "content_provider":"true_vision", + "channel_code":"t516", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"TNN​ 16 HD", + "channel_name_th":"TNN 16 HD" + }, + "views":233, + "isLiveChat":false + }, + { + "id":"GdgqaeMewGp4", + "thumb":"https://cms.dmpcdn.com/livetv/2023/05/03/baf9ea30-e966-11ed-a3d3-f3f98ac7a1a1_webp_original.png", + "slug":"truepremierfootballhd3", + "title":"True Premier Football 3", + "content_type":"livetv", + "category":"livetv-ca|football-ca|sports-ca|tvsnow|sports", + "content_provider":"true_vision", + "channel_code":"ht113", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"True Premier Football 3", + "channel_name_th":"ทรู พรีเมียร์ ฟุตบอล 3" + }, + "views":199, + "isLiveChat":true + }, + { + "id":"A36nrdXGn3V", + "thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/47a53600-e599-11ed-94a2-8feec94a4a3b_webp_original.png", + "slug":"true-asian-more", + "title":"True Asian More", + "content_type":"livetv", + "category":"livetv-ca|movies-series-ca|trueunlock-ca|tvsnow|movieseries", + "content_provider":"true_vision", + "channel_code":"081", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"True Asian More", + "channel_name_chi":"True Asian More", + "channel_name_eng":"True Asian More", + "channel_name_mm":"True Asian More", + "channel_name_rus":"True Asian More", + "channel_name_th":"True Asian More", + "channel_name_vie":"True Asian More" + }, + "views":190, + "isLiveChat":false + }, + { + "id":"74ngXBo8ke0", + "thumb":"https://cms.dmpcdn.com/livetv/2019/01/21/a01a26bb-ed4a-45c5-88a9-ff30f6bbb039.png", + "slug":"cartoonclub", + "title":"Cartoon Club", + "content_type":"livetv", + "category":"cartoon|hbtv-trueidtv-all|hbtv-truetv-kids|trueidtv-all|trueidtv-kids|kids|livetv-ca|kids-ca", + "content_provider":"", + "channel_code":"143", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"កាទូនខ្លឹប", + "channel_name_eng":"Cartoon Club", + "channel_name_mm":"ကာတြန္းကလပ္", + "channel_name_th":"การ์ตูน คลับ" + }, + "views":189, + "isLiveChat":false + }, + { + "id":"4QmJ09AyPm4", + "thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/43ffada0-e599-11ed-abcb-c792e696f885_webp_original.png", + "slug":"true-film-hd-2", + "title":"True Film 2", + "content_type":"livetv", + "category":"hbtv-truetv-movies-series|livetv-ca|movies-series-ca|tvsnow|movieseries", + "content_provider":"true_vision", + "channel_code":"221", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"True Film 2", + "channel_name_chi":"True Film 2", + "channel_name_eng":"True Film 2", + "channel_name_mm":"True Film 2", + "channel_name_rus":"True Film 2", + "channel_name_th":"True Film 2", + "channel_name_vie":"True Film 2" + }, + "views":185, + "isLiveChat":false + }, + { + "id":"wQZrKd3mo65", + "thumb":"https://cms.dmpcdn.com/livetv/2023/04/24/81825540-e28a-11ed-9bb2-7fe2e28bfd8c_webp_original.png", + "slug":"truesport-hd", + "title":"True Sports 1", + "content_type":"livetv", + "category":"livetv-ca|sports-ca|tvsnow|sports", + "content_provider":"true_vision", + "channel_code":"097", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"True Sports 1", + "channel_name_chi":"True Sports 1", + "channel_name_eng":"True Sports 1", + "channel_name_mm":"True Sports 1", + "channel_name_rus":"True Sports 1", + "channel_name_th":"ทรูสปอร์ต 1", + "channel_name_vie":"True Sports 1" + }, + "views":179, + "isLiveChat":true + }, + { + "id":"Lzz61DA3zYL", + "thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/486c7da0-e599-11ed-b481-1b121c78e74e_webp_original.png", + "slug":"true-explore-life", + "title":"True Explore Life", + "content_type":"livetv", + "category":"livetv-ca|documentary-ca|trueunlock-ca|tvsnow|documentary", + "content_provider":"true_vision", + "channel_code":"060", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"True Explore Life", + "channel_name_chi":"True Explore Life", + "channel_name_eng":"True Explore Life", + "channel_name_mm":"True Explore Life", + "channel_name_rus":"True Explore Life", + "channel_name_th":"True Explore Life", + "channel_name_vie":"True Explore Life" + }, + "views":124, + "isLiveChat":false + }, + { + "id":"vNG2L371k5W", + "thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/433fe010-e599-11ed-96ec-4d05b9e2ca86_webp_original.png", + "slug":"true-explore-wild", + "title":"True Explore Wild", + "content_type":"livetv", + "category":"livetv-ca|documentary-ca|trueunlock-ca|true-unlock|true-unlock-atv|tvsnow|documentary", + "content_provider":"true_vision", + "channel_code":"058", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"True Explore Wild", + "channel_name_eng":"True Explore Wild", + "channel_name_mm":"True Explore Wild", + "channel_name_th":"True Explore Wild" + }, + "views":119, + "isLiveChat":false + }, + { + "id":"jqepWV3ka8j", + "thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/46fb6170-e599-11ed-b606-c19576cb8b29_webp_original.png", + "slug":"true-x-zyte-hd", + "title":"True X-Zyte", + "content_type":"livetv", + "category":"livetv-ca|entertainment-ca|trueunlock-ca|tvsnow|entertainment", + "content_provider":"true_vision", + "channel_code":"034", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"True X-Zyte", + "channel_name_chi":"True X-Zyte", + "channel_name_eng":"True X-Zyte", + "channel_name_mm":"True X-Zyte", + "channel_name_rus":"True X-Zyte", + "channel_name_th":"True X-Zyte", + "channel_name_vie":"True X-Zyte" + }, + "views":107, + "isLiveChat":false + }, + { + "id":"3wLvyKyryPAD", + "thumb":"https://cms.dmpcdn.com/livetv/2023/07/24/5fda18e0-29cc-11ee-846b-a1c4e5181c87_webp_original.webp", + "slug":"bein-sports-hd3", + "title":"beIN SPORTS 3", + "content_type":"livetv", + "category":"livetv-ca|football-ca|sports-ca|tvsnow|sports", + "content_provider":"true_vision", + "channel_code":"215", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"beIN SPORTS 3", + "channel_name_th":"บีอินสปอตส์ 3" + }, + "views":96, + "isLiveChat":false + }, + { + "id":"mVoXV1rk4B5", + "thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/488c61b0-e599-11ed-94a2-8feec94a4a3b_webp_original.png", + "slug":"true-explore-3", + "title":"True Explore Sci", + "content_type":"livetv", + "category":"livetv-ca|documentary-ca|trueunlock-ca|tvsnow|documentary", + "content_provider":"true_vision", + "channel_code":"061", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"True Explore Sci", + "channel_name_chi":"True Explore Sci", + "channel_name_eng":"True Explore Sci", + "channel_name_mm":"True Explore Sci", + "channel_name_rus":"True Explore Sci", + "channel_name_th":"True Explore Sci", + "channel_name_vie":"True Explore Sci" + }, + "views":91, + "isLiveChat":false + }, + { + "id":"D1029rjaV6GQ", + "thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/0c14dd80-9407-11ee-b625-274874732f96_webp_original.webp", + "slug":"manchester-united", + "title":"Manchester United", + "content_type":"livetv", + "category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all", + "content_provider":"", + "channel_code":"mun01", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"Manchester United", + "channel_name_th":"แมนยู" + }, + "views":91, + "isLiveChat":false + }, + { + "id":"peWQgAb52vk", + "thumb":"https://cms.dmpcdn.com/livetv/2023/07/18/7cb4aae0-2515-11ee-9407-9367a664b338_webp_original.webp", + "slug":"golf-channel", + "title":"Golf Channel Thailand", + "content_type":"livetv", + "category":"livetv-ca|sports-ca|tvsnow|sports", + "content_provider":"true_vision", + "channel_code":"095", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"Golf Channel Thailand HD", + "channel_name_chi":"Golf Channel Thailand HD", + "channel_name_eng":"Golf Channel Thailand HD", + "channel_name_mm":"Golf Channel Thailand HD", + "channel_name_rus":"Golf Channel Thailand HD", + "channel_name_th":"Golf Channel Thailand HD", + "channel_name_vie":"Golf Channel Thailand HD" + }, + "views":88, + "isLiveChat":false + }, + { + "id":"A8aVZWzlOmDE", + "thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/d7783cc0-9406-11ee-b445-0b5cfb8bf6f8_webp_original.webp", + "slug":"liverpool", + "title":"Liverpool", + "content_type":"livetv", + "category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all", + "content_provider":"", + "channel_code":"liv01", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"Liverpool", + "channel_name_th":"ลิเวอร์พูล" + }, + "views":88, + "isLiveChat":false + }, + { + "id":"Ay93Q8zlOeA", + "thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/456c5d00-e599-11ed-b550-9935ba8025b9_webp_original.png", + "slug":"true-series", + "title":"True Series", + "content_type":"livetv", + "category":"livetv-ca|movies-series-ca|tvsnow|movieseries", + "content_provider":"true_vision", + "channel_code":"st006", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"True Series", + "channel_name_chi":"True Series", + "channel_name_eng":"True Series", + "channel_name_mm":"True Series", + "channel_name_rus":"True Series", + "channel_name_th":"True Series", + "channel_name_vie":"True Series" + }, + "views":82, + "isLiveChat":false + }, + { + "id":"g9ONWXWJV5pq", + "thumb":"https://cms.dmpcdn.com/livetv/2023/07/24/5f3c5240-29cc-11ee-b2f4-e9de482d866e_webp_original.webp", + "slug":"bein-sports-hd1", + "title":"beIN SPORTS 1", + "content_type":"livetv", + "category":"livetv-ca|football-ca|sports-ca|tvsnow|sports", + "content_provider":"true_vision", + "channel_code":"202", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"beIN SPORTS 1", + "channel_name_th":"บีอินสปอตส์ 1" + }, + "views":80, + "isLiveChat":false + }, + { + "id":"xR0n6ePG7wL", + "thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/2960b3f0-e593-11ed-b26c-6b89d082d464_webp_original.png", + "slug":"truesport-hd-2", + "title":"True Sports 2", + "content_type":"livetv", + "category":"livetv-ca|football-ca|sports-ca|trueunlock-ca|tvsnow|sports", + "content_provider":"true_vision", + "channel_code":"ht116", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"True Sports 2", + "channel_name_chi":"True Sports 2", + "channel_name_eng":"True Sports 2", + "channel_name_mm":"True Sports 2", + "channel_name_rus":"True Sports 2", + "channel_name_th":"ทรูสปอร์ต 2", + "channel_name_vie":"True Sports 2" + }, + "views":80, + "isLiveChat":true + }, + { + "id":"JlrpNK19py0M", + "thumb":"https://cms.dmpcdn.com/livetv/2019/04/11/19b4bd2a-750c-4ee6-9d41-5080e1310bc3_original.png", + "slug":"Mangorn", + "title":"Mangorn", + "content_type":"livetv", + "category":"free-tv|livetv-ca|freetv-ca|movies-series-ca", + "content_provider":"", + "channel_code":"o020", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"Mangorn", + "channel_name_th":"มังกร" + }, + "views":73, + "isLiveChat":false + }, + { + "id":"lPXDJR6gN6l", + "thumb":"https://cms.dmpcdn.com/livetv/2019/02/28/a9490c72-7387-4409-b5a8-80db28585ca4.png", + "slug":"true-select", + "title":"True Select", + "content_type":"livetv", + "category":"livetv-ca|entertainment-ca|variety-ca|tvsnow|entertainment", + "content_provider":"true_vision", + "channel_code":"218", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"True Select", + "channel_name_chi":"True Select", + "channel_name_eng":"True Select", + "channel_name_mm":"True Select", + "channel_name_rus":"True Select", + "channel_name_th":"True Select", + "channel_name_vie":"True Select" + }, + "views":71, + "isLiveChat":false + }, + { + "id":"NWY5K7ZELP2", + "thumb":"https://cms.dmpcdn.com/livetv/2018/12/17/0c30b192-953b-49b9-a9bf-a4c6e3e71de3.png", + "slug":"true-select-hd", + "title":"True Shopping", + "content_type":"livetv", + "category":"livetv-ca|entertainment-ca|tvsnow|entertainment", + "content_provider":"true_vision", + "channel_code":"127", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"True Shopping", + "channel_name_chi":"True Shopping", + "channel_name_eng":"True Shopping", + "channel_name_mm":"True Shopping", + "channel_name_rus":"True Shopping", + "channel_name_th":"True Shopping", + "channel_name_vie":"True Shopping" + }, + "views":70, + "isLiveChat":true + }, + { + "id":"r71LNbqjaKe", + "thumb":"https://cms.dmpcdn.com/livetv/2019/01/31/a5aeb78c-c4db-474f-a5af-345cb9e2f5b5.png", + "slug":"rama-channel", + "title":"Rama Channel", + "content_type":"livetv", + "category":"livetv-ca|documentary-ca|news-ca|tvsnow|documentary", + "content_provider":"true_vision", + "channel_code":"128", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"Rama Channel", + "channel_name_chi":"Rama Channel", + "channel_name_eng":"Rama Channel", + "channel_name_mm":"Rama Channel", + "channel_name_rus":"Rama Channel", + "channel_name_th":"Rama Channel", + "channel_name_vie":"Rama Channel" + }, + "views":69, + "isLiveChat":false + }, + { + "id":"YLN6d3oYyXEL", + "thumb":"https://cms.dmpcdn.com/livetv/2023/11/22/2a4de600-8919-11ee-8416-3dc6bea66698_webp_original.webp", + "slug":"tptv", + "title":"TPTV", + "content_type":"livetv", + "category":"livetv-ca|digitaltv-ca|education-ca|freetv-ca", + "content_provider":"", + "channel_code":"d31", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"TPTV - Thai Parliament TV", + "channel_name_th":"ทีพีทีวี" + }, + "views":61, + "isLiveChat":false + }, + { + "id":"eXlvvZ4EA5aY", + "thumb":"https://cms.dmpcdn.com/livetv/2022/12/22/d9313340-81d9-11ed-a7f9-412bbba270e9_webp_original.png", + "slug":"tv-nfl-nba", + "title":"NFL & NBA TV", + "content_type":"livetv", + "category":"livetv-ca|sports-ca", + "content_provider":"true_vision", + "channel_code":"t513", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"NFL & NBA TV", + "channel_name_th":"เอ็นเอฟแอล แอนด์ เอ็นบีเอ ทีวี" + }, + "views":59, + "isLiveChat":false + }, + { + "id":"zmvD0RO72nL", + "thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/f493fb20-e595-11ed-b26c-6b89d082d464_webp_original.png", + "slug":"truesport-5", + "title":"True Sports 5", + "content_type":"livetv", + "category":"livetv-ca|sports-ca|tvsnow|sports", + "content_provider":"true_vision", + "channel_code":"056", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"True Sports 5", + "channel_name_chi":"True Sports 5", + "channel_name_eng":"True Sports 5", + "channel_name_mm":"True Sports 5", + "channel_name_rus":"True Sports 5", + "channel_name_th":"ทรูสปอร์ต 5", + "channel_name_vie":"True Sports 5" + }, + "views":58, + "isLiveChat":false + }, + { + "id":"mXQoNYKda2L9", + "thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/434696d0-e599-11ed-b26c-6b89d082d464_webp_original.png", + "slug":"film-asia-hd", + "title":"True Film Asia", + "content_type":"livetv", + "category":"livetv-ca|movies-series-ca|tvsnow|movieseries", + "content_provider":"true_vision", + "channel_code":"t500", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"True Film Asia", + "channel_name_chi":"True Film Asia", + "channel_name_eng":"True Film Asia", + "channel_name_mm":"True Film Asia", + "channel_name_rus":"True Film Asia", + "channel_name_th":"True Film Asia", + "channel_name_vie":"True Film Asia" + }, + "views":55, + "isLiveChat":false + }, + { + "id":"P83vkq1M1Lp", + "thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/46065310-e599-11ed-96ec-4d05b9e2ca86_webp_original.png", + "slug":"true-spark", + "title":"True Spark Play", + "content_type":"livetv", + "category":"livetv-ca|kids-ca|trueunlock-ca|tvsnow|kids", + "content_provider":"true_vision", + "channel_code":"007", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"True Spark Play", + "channel_name_chi":"True Spark Play", + "channel_name_eng":"True Spark Play", + "channel_name_mm":"True Spark Play", + "channel_name_rus":"True Spark Play", + "channel_name_th":"True Spark Play", + "channel_name_vie":"True Spark Play" + }, + "views":54, + "isLiveChat":false + }, + { + "id":"2L1ZZdJGxPej", + "thumb":"https://cms.dmpcdn.com/livetv/2023/07/24/61050450-29cc-11ee-b2f4-e9de482d866e_webp_original.webp", + "slug":"spotv2-hd", + "title":"SPOTV 2", + "content_type":"livetv", + "category":"livetv-ca|sports-ca|tvsnow|sports", + "content_provider":"true_vision", + "channel_code":"t511", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"SPOTV 2", + "channel_name_th":"SPOTV 2" + }, + "views":46, + "isLiveChat":false + }, + { + "id":"Mbx79DOD44J", + "thumb":"https://cms.dmpcdn.com/livetv/2021/06/01/e2f61c80-c234-11eb-92e3-4bf272c5d086_original.png", + "slug":"true-music-channel-hd", + "title":"True Music", + "content_type":"livetv", + "category":"livetv-ca|entertainment-ca|trueunlock-ca|tvsnow|entertainment", + "content_provider":"true_vision", + "channel_code":"159", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"True Music", + "channel_name_chi":"True Music", + "channel_name_eng":"True Music", + "channel_name_mm":"True Music", + "channel_name_rus":"True Music", + "channel_name_th":"True Music", + "channel_name_vie":"True Music" + }, + "views":40, + "isLiveChat":false + }, + { + "id":"leVMNwY8LA1B", + "thumb":"https://cms.dmpcdn.com/livetv/2021/02/24/cc08cbe0-764d-11eb-b272-17d04980ce1e_original.png", + "slug":"ATTV", + "title":"@TV", + "content_type":"livetv", + "category":"free-tv|livetv-ca|freetv-ca", + "content_provider":"", + "channel_code":"i002", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"@TV", + "channel_name_th":"แอททีวี" + }, + "views":39, + "isLiveChat":false + }, + { + "id":"V14w2AL9grW6", + "thumb":"https://cms.dmpcdn.com/livetv/2023/11/13/bd6a6d20-8205-11ee-822c-6bbb3f82c35b_webp_original.webp", + "slug":"voicetv-2023", + "title":"VOICE TV", + "content_type":"livetv", + "category":"livetv-ca|freetv-ca|news-ca", + "content_provider":"", + "channel_code":"154", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"វ៉យធីវី", + "channel_name_eng":"Voice TV", + "channel_name_mm":"Voice TV", + "channel_name_th":"วอยซ์ ทีวี" + }, + "views":33, + "isLiveChat":false + }, + { + "id":"NB2d2A9Zd94z", + "thumb":"https://cms.dmpcdn.com/livetv/2023/09/25/c389b530-5b4f-11ee-a6f1-ffa978a40b9f_webp_original.webp", + "slug":"trueid-live", + "title":"TrueID Live", + "content_type":"livetv", + "category":"livetv-ca|entertainment-ca|variety-ca", + "content_provider":"", + "channel_code":"ev04", + "content_rights":null, + "channel_info":{ + "channel_name_th":"ทรูไอดี ไลฟ์" + }, + "views":31, + "isLiveChat":true + }, + { + "id":"GOPVJMln56Y", + "thumb":"https://cms.dmpcdn.com/livetv/2020/06/23/816989d0-b550-11ea-8fac-236a281cd6c5_original.png", + "slug":"dharmatv", + "title":"Dhamma TV", + "content_type":"livetv", + "category":"knowledge|livetv-ca|digitaltv-ca|documentary-ca|trueidtv-all|trueidtv-digital-tv|variety", + "content_provider":"", + "channel_code":"o016", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"ព្រះធម៌ធីវី", + "channel_name_eng":"Dhamma TV", + "channel_name_mm":"Dhamma TV", + "channel_name_th":"ธรรมะทีวี" + }, + "views":31, + "isLiveChat":false + }, + { + "id":"09BRRXKbgge9", + "thumb":"https://cms.dmpcdn.com/livetv/2022/03/23/f7108720-aa94-11ec-9b91-03afdbb2e824_webp_original.png", + "slug":"truepremierfootballhd6", + "title":"True Premier Football 6", + "content_type":"livetv", + "category":"livetv-ca|football-ca|sports-ca", + "content_provider":"true_vision", + "channel_code":"t502", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"True Premier Football 6", + "channel_name_th":"ทรู พรีเมียร์ ฟุตบอล 6" + }, + "views":31, + "isLiveChat":false + }, + { + "id":"Q7vaEm8O9e4", + "thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/f65ea900-e595-11ed-86b8-bb40638e3c49_webp_original.png", + "slug":"true-tennis-hd", + "title":"True Tennis", + "content_type":"livetv", + "category":"livetv-ca|sports-ca|tvsnow|sports", + "content_provider":"true_vision", + "channel_code":"045", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"True Tennis", + "channel_name_chi":"True Tennis", + "channel_name_eng":"True Tennis", + "channel_name_mm":"True Tennis", + "channel_name_rus":"True Tennis", + "channel_name_th":"True Tennis", + "channel_name_vie":"True Tennis" + }, + "views":29, + "isLiveChat":false + }, + { + "id":"N8E7v0JlM15e", + "thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/94a32ae0-9406-11ee-a0fd-836d91d2dd6e_webp_original.webp", + "slug":"chelsea", + "title":"Chelsea", + "content_type":"livetv", + "category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all", + "content_provider":"", + "channel_code":"che01", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"Chelsea", + "channel_name_th":"เชลซี" + }, + "views":28, + "isLiveChat":false + }, + { + "id":"PdOXKN4O1vDr", + "thumb":"https://cms.dmpcdn.com/livetv/2023/09/25/c3898e20-5b4f-11ee-a599-1d1a4f7c1125_webp_original.webp", + "slug":"trueid-sports02", + "title":"TrueID Sports 2", + "content_type":"livetv", + "category":"livetv-ca|sports-ca|trueidtv-sport", + "content_provider":"", + "channel_code":"he004", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"TrueID Sports 2", + "channel_name_th":"ทรูไอดี สปอร์ต 2" + }, + "views":26, + "isLiveChat":false + }, + { + "id":"k3B64mk9ELl3", + "thumb":"https://cms.dmpcdn.com/livetv/2023/07/24/6057ad50-29cc-11ee-846b-a1c4e5181c87_webp_original.webp", + "slug":"golfchannel-thhdplus", + "title":"Golf Channel Thailand HD+", + "content_type":"livetv", + "category":"livetv-ca|sports-ca|tvsnow|sports", + "content_provider":"true_vision", + "channel_code":"t501", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"Golf Channel Thailand HD Plus", + "channel_name_chi":"Golf Channel Thailand HD Plus", + "channel_name_eng":"Golf Channel Thailand HD Plus", + "channel_name_mm":"Golf Channel Thailand HD Plus", + "channel_name_rus":"Golf Channel Thailand HD Plus", + "channel_name_th":"Golf Channel Thailand HD Plus", + "channel_name_vie":"Golf Channel Thailand HD Plus" + }, + "views":26, + "isLiveChat":false + }, + { + "id":"zWoZqZv6J6N5", + "thumb":"https://cms.dmpcdn.com/livetv/2022/10/11/f09e41a0-492e-11ed-bb17-0527d4e1664c_webp_original.png", + "slug":"crime-investigation", + "title":"Crime + Investigation", + "content_type":"livetv", + "category":"livetv-ca|documentary-ca|tvsnow|documentary", + "content_provider":"true_vision", + "channel_code":"t517", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"Crime Investigation", + "channel_name_th":"ไคร์ม แอนด์ อินเวสทิเกชั่น" + }, + "views":25, + "isLiveChat":false + }, + { + "id":"bDKPPGOdyAmn", + "thumb":"https://cms.dmpcdn.com/livetv/2023/07/24/60cba4d0-29cc-11ee-b2f4-e9de482d866e_webp_original.webp", + "slug":"spotv1-hd", + "title":"SPOTV 1", + "content_type":"livetv", + "category":"livetv-ca|sports-ca|tvsnow|sports", + "content_provider":"true_vision", + "channel_code":"t510", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"SPOTV 1", + "channel_name_th":"SPOTV 1" + }, + "views":25, + "isLiveChat":false + }, + { + "id":"zpwxwAgYOV7n", + "thumb":"https://cms.dmpcdn.com/livetv/2022/02/17/3943ca00-8fd6-11ec-b076-dffedf0eab22_webp_original.png", + "slug":"white-channel-hd", + "title":"White Channel", + "content_type":"livetv", + "category":"hbtv-trueidtv-all|livetv-ca|freetv-ca", + "content_provider":"", + "channel_code":"i006", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"White Channel", + "channel_name_th":"ไวท์แชนแนล" + }, + "views":25, + "isLiveChat":false + }, + { + "id":"vW6BOL0AzxdW", + "thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/21b70060-9406-11ee-906d-89adbc3169c1_webp_original.webp", + "slug":"arsenal", + "title":"Arsenal", + "content_type":"livetv", + "category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all", + "content_provider":"", + "channel_code":"ars01", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"Arsenal", + "channel_name_th":"อาร์เซน่อล" + }, + "views":25, + "isLiveChat":false + }, + { + "id":"5YQaWExRqD5", + "thumb":"https://cms.dmpcdn.com/livetv/2020/05/18/87773550-98c4-11ea-b284-2bff0287c295_original.png", + "slug":"dltv-3", + "title":"DLTV 3", + "content_type":"livetv", + "category":"education|hbtv-trueidtv-all|livetv-ca|education-ca", + "content_provider":"", + "channel_code":"dum003", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"DLTV 3", + "channel_name_chi":"DLTV 3", + "channel_name_eng":"DLTV 3", + "channel_name_mm":"DLTV 3", + "channel_name_rus":"DLTV 3", + "channel_name_th":"DLTV 3", + "channel_name_vie":"DLTV 3" + }, + "views":23, + "isLiveChat":false + }, + { + "id":"xPgxpqoyqQ62", + "thumb":"https://cms.dmpcdn.com/livetv/2021/01/06/68be8520-500f-11eb-8d28-4b8e3f30b51b_original.png", + "slug":"zing", + "title":"Zing", + "content_type":"livetv", + "category":"livetv-ca|entertainment-ca|movies-series-ca|trueidtv-all", + "content_provider":"", + "channel_code":"i001", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"Zing", + "channel_name_chi":"Zing", + "channel_name_eng":"Zing", + "channel_name_mm":"Zing", + "channel_name_rus":"Zing", + "channel_name_th":"Zing", + "channel_name_vie":"Zing" + }, + "views":22, + "isLiveChat":false + }, + { + "id":"5XaDjQd1JJgw", + "thumb":"https://cms.dmpcdn.com/livetv/2023/07/24/5f346300-29cc-11ee-b2f4-e9de482d866e_webp_original.webp", + "slug":"bein-sports-hd2", + "title":"beIN SPORTS 2", + "content_type":"livetv", + "category":"livetv-ca|football-ca|sports-ca|trueidtv-all|tvsnow|sports", + "content_provider":"true_vision", + "channel_code":"t521", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"beIN SPORTS 2", + "channel_name_th":"บีอินสปอตส์ 2" + }, + "views":22, + "isLiveChat":false + }, + { + "id":"pmXrb1NjLeP0", + "thumb":"https://cms.dmpcdn.com/livetv/2023/05/03/bbea1690-e966-11ed-935b-df134f58d288_webp_original.png", + "slug":"truepremierfootballhd5", + "title":"True Premier Football 5", + "content_type":"livetv", + "category":"livetv-ca|football-ca|sports-ca|tvsnow|sports", + "content_provider":"true_vision", + "channel_code":"ht115", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"True Premier Football 5", + "channel_name_th":"ทรู พรีเมียร์ ฟุตบอล 5" + }, + "views":21, + "isLiveChat":false + }, + { + "id":"GDna51EdVk4", + "thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/f4888970-e595-11ed-8507-4fc0b025fedb_webp_original.png", + "slug":"truesport-hd-4", + "title":"True Sports 4", + "content_type":"livetv", + "category":"livetv-ca|sports-ca|tvsnow|sports", + "content_provider":"true_vision", + "channel_code":"062", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"True Sports 4", + "channel_name_chi":"True Sports 4", + "channel_name_eng":"True Sports 4", + "channel_name_mm":"True Sports 4", + "channel_name_rus":"True Sports 4", + "channel_name_th":"ทรูสปอร์ต 4", + "channel_name_vie":"True Sports 4" + }, + "views":21, + "isLiveChat":false + }, + { + "id":"o9vKOR0dLVm7", + "thumb":"https://cms.dmpcdn.com/livetv/2023/09/25/c3898e20-5b4f-11ee-a599-1d1a4f7c1125_webp_original.webp", + "slug":"trueid-sports03", + "title":"TrueID Sports 3", + "content_type":"livetv", + "category":"livetv-ca|sports-ca|trueidtv-sport", + "content_provider":"", + "channel_code":"he005", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"TrueID Sports 3", + "channel_name_th":"ทรูไอดี สปอร์ต 3" + }, + "views":20, + "isLiveChat":false + }, + { + "id":"rO7WMREyepr", + "thumb":"https://cms.dmpcdn.com/livetv/2020/05/18/c273ea90-98c4-11ea-bcb3-0320ce420b5e_original.png", + "slug":"dltv-15", + "title":"DLTV 15", + "content_type":"livetv", + "category":"education|hbtv-trueidtv-all|livetv-ca|education-ca", + "content_provider":"", + "channel_code":"dum015", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"DLTV 15", + "channel_name_chi":"DLTV 15", + "channel_name_eng":"DLTV 15", + "channel_name_mm":"DLTV 15", + "channel_name_rus":"DLTV 15", + "channel_name_th":"DLTV 15", + "channel_name_vie":"DLTV 15" + }, + "views":20, + "isLiveChat":false + }, + { + "id":"67ollp0Raz2V", + "thumb":"https://cms.dmpcdn.com/livetv/2022/03/23/f7114a70-aa94-11ec-9b91-03afdbb2e824_webp_original.png", + "slug":"truepremierfootballhd7", + "title":"True Premier Football 7", + "content_type":"livetv", + "category":"livetv-ca|football-ca|sports-ca", + "content_provider":"true_vision", + "channel_code":"t503", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"True Premier Football 7", + "channel_name_th":"ทรู พรีเมียร์ ฟุตบอล 7" + }, + "views":18, + "isLiveChat":false + }, + { + "id":"2KyzkV6AyPZ", + "thumb":"https://cms.dmpcdn.com/livetv/2020/05/18/65dc9bb0-98c4-11ea-bcb3-0320ce420b5e_original.png", + "slug":"dltv-1", + "title":"DLTV 1", + "content_type":"livetv", + "category":"education|hbtv-trueidtv-all|livetv-ca|education-ca", + "content_provider":"", + "channel_code":"dum001", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"DLTV 1", + "channel_name_chi":"DLTV 1", + "channel_name_eng":"DLTV 1", + "channel_name_mm":"DLTV 1", + "channel_name_rus":"DLTV 1", + "channel_name_th":"DLTV 1", + "channel_name_vie":"DLTV 1" + }, + "views":17, + "isLiveChat":false + }, + { + "id":"gqVn9n7MeYXq", + "thumb":"https://cms.dmpcdn.com/livetv/2022/09/08/e55200a0-2f27-11ed-a458-efe831982670_webp_original.png", + "slug":"arirang-tv", + "title":"Arirang TV", + "content_type":"livetv", + "category":"livetv-ca|entertainment-ca|tvsnow|entertainment", + "content_provider":"true_vision", + "channel_code":"t519", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"Arirang TV", + "channel_name_th":"Arirang TV" + }, + "views":16, + "isLiveChat":false + }, + { + "id":"r4PaaOpzr0Ow", + "thumb":"https://cms.dmpcdn.com/livetv/2022/03/23/f868c420-aa94-11ec-9b91-03afdbb2e824_webp_original.png", + "slug":"truepremierfootballhd8", + "title":"True Premier Football 8", + "content_type":"livetv", + "category":"livetv-ca|football-ca|sports-ca", + "content_provider":"true_vision", + "channel_code":"t504", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"True Premier Football 8", + "channel_name_th":"ทรู พรีเมียร์ ฟุตบอล 8" + }, + "views":16, + "isLiveChat":false + }, + { + "id":"Kz5zjkGyDVA", + "thumb":"https://cms.dmpcdn.com/livetv/2020/05/18/66ce9cd0-98c4-11ea-b284-2bff0287c295_original.png", + "slug":"dltv-6", + "title":"DLTV 6", + "content_type":"livetv", + "category":"education|hbtv-trueidtv-all|livetv-ca|education-ca", + "content_provider":"", + "channel_code":"dum006", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"DLTV 6", + "channel_name_chi":"DLTV 6", + "channel_name_eng":"DLTV 6", + "channel_name_mm":"DLTV 6", + "channel_name_rus":"DLTV 6", + "channel_name_th":"DLTV 6", + "channel_name_vie":"DLTV 6" + }, + "views":16, + "isLiveChat":false + }, + { + "id":"Veb1NRpQ6LXk", + "thumb":"https://cms.dmpcdn.com/livetv/2022/02/10/16689820-8a46-11ec-8573-9fd52c482da3_webp_original.png", + "slug":"zee-anmol-sd", + "title":"Zee Anmol", + "content_type":"livetv", + "category":"livetv-ca|entertainment-ca|freetv-ca|movies-series-ca", + "content_provider":"", + "channel_code":"i005", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"Zee Anmol", + "channel_name_th":"Zee Anmol" + }, + "views":15, + "isLiveChat":false + }, + { + "id":"L3Jbvn0BnbA", + "thumb":"https://cms.dmpcdn.com/livetv/2020/05/18/65debe90-98c4-11ea-b284-2bff0287c295_original.png", + "slug":"dltv-2", + "title":"DLTV 2", + "content_type":"livetv", + "category":"education|hbtv-trueidtv-all|livetv-ca|education-ca", + "content_provider":"", + "channel_code":"dum002", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"DLTV 2", + "channel_name_chi":"DLTV 2", + "channel_name_eng":"DLTV 2", + "channel_name_mm":"DLTV 2", + "channel_name_rus":"DLTV 2", + "channel_name_th":"DLTV 2", + "channel_name_vie":"DLTV 2" + }, + "views":15, + "isLiveChat":false + }, + { + "id":"v2M0K4kgbrN", + "thumb":"https://cms.dmpcdn.com/livetv/2020/05/18/66317270-98c4-11ea-b284-2bff0287c295_original.png", + "slug":"dltv-4", + "title":"DLTV 4", + "content_type":"livetv", + "category":"education|hbtv-trueidtv-all|livetv-ca|education-ca", + "content_provider":"", + "channel_code":"dum004", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"DLTV 4", + "channel_name_chi":"DLTV 4", + "channel_name_eng":"DLTV 4", + "channel_name_mm":"DLTV 4", + "channel_name_rus":"DLTV 4", + "channel_name_th":"DLTV 4", + "channel_name_vie":"DLTV 4" + }, + "views":15, + "isLiveChat":false + }, + { + "id":"RGdlapJnLQNG", + "thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/f8af80b0-9406-11ee-8d65-879d2e0f23a3_webp_original.webp", + "slug":"manchester-city", + "title":"Manchester City", + "content_type":"livetv", + "category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all", + "content_provider":"", + "channel_code":"mci01", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"Manchester City", + "channel_name_th":"แมนซิตี้" + }, + "views":15, + "isLiveChat":false + }, + { + "id":"JkpG4LeljXJ0", + "thumb":"https://cms.dmpcdn.com/livetv/2023/08/02/8a0c49a0-30e1-11ee-b220-4544ede97b74_webp_original.webp", + "slug":"true-ball-thai-2", + "title":"True Ball Thai 2", + "content_type":"livetv", + "category":"livetv-ca|football-ca|sports-ca|tvsnow|sports", + "content_provider":"true_vision", + "channel_code":"vc02", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"True Ball Thai 2", + "channel_name_th":"True Ball Thai 2" + }, + "views":14, + "isLiveChat":false + }, + { + "id":"Yb4p39lbgvN", + "thumb":"https://cms.dmpcdn.com/livetv/2020/05/18/6683b120-98c4-11ea-b284-2bff0287c295_original.png", + "slug":"dltv-5", + "title":"DLTV 5", + "content_type":"livetv", + "category":"education|hbtv-trueidtv-all|livetv-ca|education-ca", + "content_provider":"", + "channel_code":"dum005", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"DLTV 5", + "channel_name_chi":"DLTV 5", + "channel_name_eng":"DLTV 5", + "channel_name_mm":"DLTV 5", + "channel_name_rus":"DLTV 5", + "channel_name_th":"DLTV 5", + "channel_name_vie":"DLTV 5" + }, + "views":14, + "isLiveChat":false + }, + { + "id":"D38Lb540KAE3", + "thumb":"https://cms.dmpcdn.com/livetv/2021/02/24/cc358130-764d-11eb-9057-2d10fb4d0cf4_original.png", + "slug":"MediaTV", + "title":"Media TV", + "content_type":"livetv", + "category":"free-tv|livetv-ca|freetv-ca", + "content_provider":"", + "channel_code":"i003", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"Media TV", + "channel_name_th":"มีเดีย ทีวี" + }, + "views":13, + "isLiveChat":false + }, + { + "id":"JGAQ7VZpX9Y", + "thumb":"https://cms.dmpcdn.com/livetv/2020/05/18/044a2a10-98c5-11ea-bcb3-0320ce420b5e_original.png", + "slug":"dltv-12", + "title":"DLTV 12", + "content_type":"livetv", + "category":"education|hbtv-trueidtv-all|livetv-ca|education-ca", + "content_provider":"", + "channel_code":"dum012", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"DLTV 12", + "channel_name_chi":"DLTV 12", + "channel_name_eng":"DLTV 12", + "channel_name_mm":"DLTV 12", + "channel_name_rus":"DLTV 12", + "channel_name_th":"DLTV 12", + "channel_name_vie":"DLTV 12" + }, + "views":13, + "isLiveChat":false + }, + { + "id":"zyab4aWZ0OWx", + "thumb":"https://cms.dmpcdn.com/livetv/2023/05/03/bb0beb90-e966-11ed-993c-b59183950f79_webp_original.png", + "slug":"truepremierfootballhd4", + "title":"True Premier Football 4", + "content_type":"livetv", + "category":"livetv-ca|football-ca|sports-ca|tvsnow|sports", + "content_provider":"true_vision", + "channel_code":"ht114", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"True Premier Football 4", + "channel_name_th":"ทรู พรีเมียร์ ฟุตบอล 4" + }, + "views":12, + "isLiveChat":false + }, + { + "id":"pQ6ok8M72AD", + "thumb":"https://cms.dmpcdn.com/livetv/2020/05/18/bfd21690-98c4-11ea-bcb3-0320ce420b5e_original.png", + "slug":"dltv-10", + "title":"DLTV 10", + "content_type":"livetv", + "category":"education|hbtv-trueidtv-all|livetv-ca|education-ca", + "content_provider":null, + "channel_code":"dum010", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"DLTV 10", + "channel_name_chi":"DLTV 10", + "channel_name_eng":"DLTV 10", + "channel_name_mm":"DLTV 10", + "channel_name_rus":"DLTV 10", + "channel_name_th":"DLTV 10", + "channel_name_vie":"DLTV 10" + }, + "views":12, + "isLiveChat":false + }, + { + "id":"wkrQgY603zM", + "thumb":"https://cms.dmpcdn.com/livetv/2020/05/18/c2364550-98c4-11ea-bcb3-0320ce420b5e_original.png", + "slug":"dltv-14", + "title":"DLTV 14", + "content_type":"livetv", + "category":"education|hbtv-trueidtv-all|livetv-ca|education-ca", + "content_provider":"", + "channel_code":"dum014", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"DLTV 14", + "channel_name_chi":"DLTV 14", + "channel_name_eng":"DLTV 14", + "channel_name_mm":"DLTV 14", + "channel_name_rus":"DLTV 14", + "channel_name_th":"DLTV 14", + "channel_name_vie":"DLTV 14" + }, + "views":12, + "isLiveChat":false + }, + { + "id":"nYvz5QLWjyD", + "thumb":"https://cms.dmpcdn.com/livetv/2020/05/18/67145860-98c4-11ea-b284-2bff0287c295_original.png", + "slug":"dltv-7", + "title":"DLTV 7", + "content_type":"livetv", + "category":"education|hbtv-trueidtv-all|livetv-ca|education-ca", + "content_provider":"", + "channel_code":"dum007", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"DLTV 7", + "channel_name_chi":"DLTV 7", + "channel_name_eng":"DLTV 7", + "channel_name_mm":"DLTV 7", + "channel_name_rus":"DLTV 7", + "channel_name_th":"DLTV 7", + "channel_name_vie":"DLTV 7" + }, + "views":11, + "isLiveChat":false + }, + { + "id":"MndX5W8rWaMn", + "thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/7003e750-9407-11ee-b445-0b5cfb8bf6f8_webp_original.webp", + "slug":"tottenham-hotspur", + "title":"Tottenham Hotspur", + "content_type":"livetv", + "category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all", + "content_provider":"", + "channel_code":"tot01", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"Tottenham Hotspur", + "channel_name_th":"สเปอร์" + }, + "views":11, + "isLiveChat":false + }, + { + "id":"E9k68z9aKDp", + "thumb":"https://cms.dmpcdn.com/livetv/2017/10/18/f1b957db-b175-45fc-ab2b-60150f9c570a.png", + "slug":"tnn-2", + "title":"TNN 2", + "content_type":"livetv", + "category":"livetv-ca|freetv-ca|news-ca|tvsnow|tvsnews", + "content_provider":"true_vision", + "channel_code":"074", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"TNN 2", + "channel_name_chi":"TNN 2", + "channel_name_eng":"TNN 2", + "channel_name_mm":"TNN 2", + "channel_name_rus":"TNN 2", + "channel_name_th":"TNN 2", + "channel_name_vie":"TNN 2" + }, + "views":10, + "isLiveChat":false + }, + { + "id":"JeQ5L9PpVBJ", + "thumb":"https://cms.dmpcdn.com/livetv/2020/05/18/bf927580-98c4-11ea-bcb3-0320ce420b5e_original.png", + "slug":"dltv-9", + "title":"DLTV 9", + "content_type":"livetv", + "category":"education|hbtv-trueidtv-all|livetv-ca|education-ca", + "content_provider":"", + "channel_code":"dum009", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"DLTV 9", + "channel_name_chi":"DLTV 9", + "channel_name_eng":"DLTV 9", + "channel_name_mm":"DLTV 9", + "channel_name_rus":"DLTV 9", + "channel_name_th":"DLTV 9", + "channel_name_vie":"DLTV 9" + }, + "views":10, + "isLiveChat":false + }, + { + "id":"M34YDGLk2wVj", + "thumb":"https://cms.dmpcdn.com/livetv/2023/08/02/8a3b21d0-30e1-11ee-a53e-b3f87dc8ba1e_webp_original.webp", + "slug":"true-ball-thai-3", + "title":"True Ball Thai 3", + "content_type":"livetv", + "category":"livetv-ca|football-ca|sports-ca|tvsnow|sports", + "content_provider":"true_vision", + "channel_code":"vc03", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"True Ball Thai 3", + "channel_name_th":"True Ball Thai 3" + }, + "views":9, + "isLiveChat":false + }, + { + "id":"R4WyxL6Mp8b", + "thumb":"https://cms.dmpcdn.com/livetv/2020/05/18/bf9386f0-98c4-11ea-b284-2bff0287c295_original.png", + "slug":"dltv-8", + "title":"DLTV 8", + "content_type":"livetv", + "category":"education|hbtv-trueidtv-all|livetv-ca|education-ca", + "content_provider":"", + "channel_code":"dum008", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"DLTV 8", + "channel_name_chi":"DLTV 8", + "channel_name_eng":"DLTV 8", + "channel_name_mm":"DLTV 8", + "channel_name_rus":"DLTV 8", + "channel_name_th":"DLTV 8", + "channel_name_vie":"DLTV 8" + }, + "views":9, + "isLiveChat":false + }, + { + "id":"6Qna2oVjq3P", + "thumb":"https://cms.dmpcdn.com/livetv/2020/05/18/c2335f20-98c4-11ea-bcb3-0320ce420b5e_original.png", + "slug":"dltv-13", + "title":"DLTV 13", + "content_type":"livetv", + "category":"education|hbtv-trueidtv-all|livetv-ca|education-ca", + "content_provider":"", + "channel_code":"dum013", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"DLTV 13", + "channel_name_chi":"DLTV 13", + "channel_name_eng":"DLTV 13", + "channel_name_mm":"DLTV 13", + "channel_name_rus":"DLTV 13", + "channel_name_th":"DLTV 13", + "channel_name_vie":"DLTV 13" + }, + "views":9, + "isLiveChat":false + }, + { + "id":"3JYow6Dx7zx0", + "thumb":"https://cms.dmpcdn.com/livetv/2023/08/02/2f7ad050-30f6-11ee-b57d-a9829f092f3e_webp_original.webp", + "slug":"bein-sports-6", + "title":"beIN SPORTS 6", + "content_type":"livetv", + "category":"livetv-ca|football-ca|sports-ca|tvsnow|sports", + "content_provider":"", + "channel_code":"216", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"beIN SPORTS 6", + "channel_name_th":"บีอินสปอตส์ 6" + }, + "views":7, + "isLiveChat":false + }, + { + "id":"EGvbeMNZOwq", + "thumb":"https://cms.dmpcdn.com/livetv/2020/05/18/bfdaa210-98c4-11ea-bcb3-0320ce420b5e_original.png", + "slug":"dltv-11", + "title":"DLTV 11", + "content_type":"livetv", + "category":"education|hbtv-trueidtv-all|livetv-ca|education-ca", + "content_provider":"", + "channel_code":"dum011", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"DLTV 11", + "channel_name_chi":"DLTV 11", + "channel_name_eng":"DLTV 11", + "channel_name_mm":"DLTV 11", + "channel_name_rus":"DLTV 11", + "channel_name_th":"DLTV 11", + "channel_name_vie":"DLTV 11" + }, + "views":6, + "isLiveChat":false + }, + { + "id":"JpawvVMe6aXO", + "thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/1bf1afd0-9407-11ee-8d65-879d2e0f23a3_webp_original.webp", + "slug":"newcastle-united", + "title":"Newcastle United", + "content_type":"livetv", + "category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all", + "content_provider":"", + "channel_code":"new01", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"Newcastle United", + "channel_name_th":"นิวคาสเซิล" + }, + "views":6, + "isLiveChat":false + }, + { + "id":"RVrAxAOGx21v", + "thumb":"https://cms.dmpcdn.com/livetv/2022/09/08/e5b667c0-2f27-11ed-9e57-d98920d4c462_webp_original.png", + "slug":"dw-english", + "title":"DW English", + "content_type":"livetv", + "category":"livetv-ca|entertainment-ca|trueunlock-ca|tvsnow|entertainment", + "content_provider":"true_vision", + "channel_code":"t518", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"DW English", + "channel_name_th":"ดี ดับเบิ้ลยู อิงลิช" + }, + "views":5, + "isLiveChat":false + }, + { + "id":"eyEPa8A2WaJN", + "thumb":"https://cms.dmpcdn.com/livetv/2023/08/02/2f7a5b20-30f6-11ee-8c65-b3a6cba5ed9d_webp_original.webp", + "slug":"beinsports-4", + "title":"beIN SPORTS 4", + "content_type":"livetv", + "category":"livetv-ca|football-ca|sports-ca|tvsnow|sports", + "content_provider":"", + "channel_code":"217", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"beIN SPORTS 4", + "channel_name_th":"บีอินสปอตส์ 4" + }, + "views":5, + "isLiveChat":false + }, + { + "id":"pQNm6nA20a6e", + "thumb":"https://cms.dmpcdn.com/livetv/2023/08/02/300adb50-30f6-11ee-b3e7-85edd640cc04_webp_original.webp", + "slug":"beinsports-5", + "title":"beIN SPORTS 5", + "content_type":"livetv", + "category":"livetv-ca|football-ca|sports-ca|tvsnow|sports", + "content_provider":"", + "channel_code":"219", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"beIN SPORTS 5", + "channel_name_th":"บีอินสปอตส์ 5" + }, + "views":2, + "isLiveChat":false + }, + { + "id":"YZzDXGM1Yd68", + "thumb":"https://cms.dmpcdn.com/livetv/2021/10/28/3965c0a0-3794-11ec-8e1f-6bce3683de8c_webp_original.png", + "slug":"Event2", + "title":"Event 2", + "content_type":"livetv", + "category":"hbtv-trueidtv-all|hbtv-truetv-sport|ott-trueidtv-sport|sport|trueidtv-all|trueidtv-sport", + "content_provider":"", + "channel_code":"ev03", + "content_rights":null, + "channel_info":null, + "views":1, + "isLiveChat":false + }, + { + "id":"LVQzz7xplYpP", + "thumb":"https://cms.dmpcdn.com/livetv/2021/10/28/3965c0a0-3794-11ec-8e1f-6bce3683de8c_webp_original.png", + "slug":"Event5", + "title":"Event 5", + "content_type":"livetv", + "category":"hbtv-trueidtv-all|hbtv-truetv-sport|ott-trueidtv-sport|sport|trueidtv-all|trueidtv-sport", + "content_provider":null, + "channel_code":"emer05", + "content_rights":null, + "channel_info":null, + "views":1, + "isLiveChat":false + }, + { + "id":"xVW7oVd8Gen", + "thumb":"https://cms.dmpcdn.com/livetv/2020/06/23/99bc7f60-b550-11ea-8fac-236a281cd6c5_original.png", + "slug":"super-entertain", + "title":"Super Bunteung", + "content_type":"livetv", + "category":"hbtv-trueidtv-all|hbtv-truetv-entertainment|tvsnow|entertainment|livetv-ca|entertainment-ca", + "content_provider":"true_vision", + "channel_code":"108", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"ស៊ុបព័រអិនធើធែនមិន", + "channel_name_eng":"Super Bunteung", + "channel_name_mm":"အထူးေဖ်ာ္ေျဖမႈမ်ား", + "channel_name_th":"ซุปเปอร์ บันเทิง" + }, + "views":1, + "isLiveChat":false + }, + { + "id":"rVWJGN1VLOB", + "thumb":"https://cms.dmpcdn.com/livetv/2017/10/17/31a68f7b-d24e-43e0-9403-22f5e48f081b.png", + "slug":"etv", + "title":"ETV", + "content_type":"livetv", + "category":"hbtv-trueidtv-all|tvsnow|entertainment|livetv-ca|entertainment-ca|education-ca", + "content_provider":"true_vision", + "channel_code":"da2", + "content_rights":null, + "channel_info":{ + "channel_name_cbd":"ETV", + "channel_name_chi":"ETV", + "channel_name_eng":"ETV", + "channel_name_mm":"ETV", + "channel_name_rus":"ETV", + "channel_name_th":"ETV", + "channel_name_vie":"ETV" + }, + "views":1, + "isLiveChat":false + }, + { + "id":"vAG5EZznD1Kl", + "thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/7e72f8d0-9407-11ee-b32f-2d43ff6700d5_webp_original.webp", + "slug":"west-ham-united", + "title":"West Ham United", + "content_type":"livetv", + "category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all", + "content_provider":"", + "channel_code":"whu01", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"West Ham United", + "channel_name_th":"เวสต์แฮม" + }, + "views":1, + "isLiveChat":false + }, + { + "id":"nle3eNnyVpag", + "thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/a4879e50-9406-11ee-b543-51f040e58632_webp_original.webp", + "slug":"crystal-palace", + "title":"Crystal Palace", + "content_type":"livetv", + "category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all", + "content_provider":"", + "channel_code":"cry01", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"Crystal-Palace", + "channel_name_th":"คริสตัลพาเลซ" + }, + "views":1, + "isLiveChat":false + }, + { + "id":"GB0gZlzxgnJr", + "thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/0a03a630-9406-11ee-906d-89adbc3169c1_webp_original.webp", + "slug":"bournemouth", + "title":"A.F.C. Bournemouth", + "content_type":"livetv", + "category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all", + "content_provider":"", + "channel_code":"bou01", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"Bournemouth", + "channel_name_th":"บอร์นมัธ" + }, + "views":1, + "isLiveChat":false + }, + { + "id":"1ZrGkEk3qP7L", + "thumb":"https://cms.dmpcdn.com/livetv/2022/07/18/79461e70-0667-11ed-b687-85f145af88ed_webp_original.png", + "slug":"test3", + "title":"ช่องทดสอบออกอากาศที่ 3", + "content_type":"livetv", + "category":"livetv-ca|sports-ca", + "content_provider":"", + "channel_code":"tmp005", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"ช่องทดสอบออกอากาศที่ 3", + "channel_name_th":"ช่องทดสอบออกอากาศที่ 3" + }, + "views":0, + "isLiveChat":false + }, + { + "id":"6G190MBm2kkG", + "thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/7bb41c60-9406-11ee-a0fd-836d91d2dd6e_webp_original.webp", + "slug":"burnley", + "title":"Burnley", + "content_type":"livetv", + "category":"livetv-ca|football-ca|sports-ca|trueunlock-ca", + "content_provider":"", + "channel_code":"brn01", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"Burnley", + "channel_name_th":"เบิร์นลีย์" + }, + "views":0, + "isLiveChat":false + }, + { + "id":"4GePx966Dzao", + "thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/e8af9600-9406-11ee-b625-274874732f96_webp_original.webp", + "slug":"luton-town", + "title":"Luton Town", + "content_type":"livetv", + "category":"livetv-ca|football-ca|sports-ca|trueunlock-ca", + "content_provider":"", + "channel_code":"lut01", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"Luton Town", + "channel_name_th":"ลูตัน ทาวน์" + }, + "views":0, + "isLiveChat":false + }, + { + "id":"4NYqR5KyQArN", + "thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/61985840-9407-11ee-a0fd-836d91d2dd6e_webp_original.webp", + "slug":"sheffield-united", + "title":"Sheffield United", + "content_type":"livetv", + "category":"livetv-ca|football-ca|sports-ca|trueunlock-ca", + "content_provider":"", + "channel_code":"shu01", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"Sheffield United", + "channel_name_th":"เชฟฟิลด์ ยูไนเต็ด" + }, + "views":0, + "isLiveChat":false + }, + { + "id":"xAzllg2VXjRm", + "thumb":"https://cms.dmpcdn.com/livetv/2021/10/28/3965c0a0-3794-11ec-8e1f-6bce3683de8c_webp_original.png", + "slug":"Event3", + "title":"Event 3", + "content_type":"livetv", + "category":"hbtv-trueidtv-all|hbtv-truetv-sport|ott-trueidtv-sport|sport|trueidtv-all|trueidtv-sport", + "content_provider":null, + "channel_code":"emer03", + "content_rights":null, + "channel_info":null, + "views":0, + "isLiveChat":false + }, + { + "id":"WGVqq6zeAzaZ", + "thumb":"https://cms.dmpcdn.com/livetv/2021/10/28/3965c0a0-3794-11ec-8e1f-6bce3683de8c_webp_original.png", + "slug":"Event4", + "title":"Event 4", + "content_type":"livetv", + "category":"hbtv-trueidtv-all|hbtv-truetv-sport|ott-trueidtv-sport|sport|trueidtv-all|trueidtv-sport", + "content_provider":null, + "channel_code":"emer04", + "content_rights":null, + "channel_info":null, + "views":0, + "isLiveChat":false + }, + { + "id":"RjY3XkeL5Mwl", + "thumb":"https://cms.dmpcdn.com/livetv/2021/10/28/3965c0a0-3794-11ec-8e1f-6bce3683de8c_webp_original.png", + "slug":"Event1", + "title":"Event 1", + "content_type":"livetv", + "category":"hbtv-trueidtv-all|hbtv-truetv-sport|ott-trueidtv-sport|sport|trueidtv-all|trueidtv-sport", + "content_provider":null, + "channel_code":"ev02", + "content_rights":null, + "channel_info":null, + "views":0, + "isLiveChat":false + }, + { + "id":"glmE5eNRz47l", + "thumb":"https://cms.dmpcdn.com/livetv/2022/07/18/79461e70-0667-11ed-b687-85f145af88ed_webp_original.png", + "slug":"test", + "title":"ช่องทดสอบการออกอากาศ", + "content_type":"livetv", + "category":"livetv-ca", + "content_provider":"", + "channel_code":"tmp003", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"ทดสอบการออกอากาศ", + "channel_name_th":"ทดสอบการออกอากาศ" + }, + "views":0, + "isLiveChat":false + }, + { + "id":"RvJwkNg06Qre", + "thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/35f494c0-9406-11ee-b32f-2d43ff6700d5_webp_original.webp", + "slug":"aston-villa", + "title":"Aston Villa", + "content_type":"livetv", + "category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all", + "content_provider":"", + "channel_code":"avl01", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"Aston Villa", + "channel_name_th":"แอสตันวิลล่า" + }, + "views":0, + "isLiveChat":false + }, + { + "id":"Vjb43gpNAVnl", + "thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/b6bb0ad0-9406-11ee-b543-51f040e58632_webp_original.webp", + "slug":"everton", + "title":"Everton", + "content_type":"livetv", + "category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all", + "content_provider":"", + "channel_code":"eve01", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"Everton", + "channel_name_th":"เอเวอร์ตัน" + }, + "views":0, + "isLiveChat":false + }, + { + "id":"K24pNw8k5Kj2", + "thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/94ed36c0-9407-11ee-906d-89adbc3169c1_webp_original.webp", + "slug":"wolves", + "title":"Wolves", + "content_type":"livetv", + "category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all", + "content_provider":"", + "channel_code":"wol01", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"Wolves", + "channel_name_th":"วูลฟ์" + }, + "views":0, + "isLiveChat":false + }, + { + "id":"vQEl8Do0nK46", + "thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/c843df70-9406-11ee-8d65-879d2e0f23a3_webp_original.webp", + "slug":"fulham", + "title":"Fulham", + "content_type":"livetv", + "category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all", + "content_provider":"", + "channel_code":"ful01", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"Fulham", + "channel_name_th":"ฟูแล่ม" + }, + "views":0, + "isLiveChat":false + }, + { + "id":"oDqg2NPZdJz5", + "thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/600d67f0-9406-11ee-ab3e-a51daa175c33_webp_original.webp", + "slug":"brighton-and-hove-albion", + "title":"Brighton & Hove Albion", + "content_type":"livetv", + "category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all", + "content_provider":"", + "channel_code":"bha01", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"Brighton-and-Hove-Albion", + "channel_name_th":"ไบร์ทตัน" + }, + "views":0, + "isLiveChat":false + }, + { + "id":"ykaXNqEMoPZR", + "thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/30299ee0-9407-11ee-a469-0b60cd4a260f_webp_original.webp", + "slug":"nottingham-forest", + "title":"Nottingham Forest", + "content_type":"livetv", + "category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all", + "content_provider":"", + "channel_code":"nfo01", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"Nottingham Forest", + "channel_name_th":"ฟอร์เรสต์" + }, + "views":0, + "isLiveChat":false + }, + { + "id":"3gaL3mjZoxrE", + "thumb":"https://cms.dmpcdn.com/livetv/2023/12/06/4738bf40-9406-11ee-a0fd-836d91d2dd6e_webp_original.webp", + "slug":"brentford", + "title":"Brentford", + "content_type":"livetv", + "category":"hbtv-trueidtv-all|livetv-ca|football-ca|sports-ca|true-unlock|true-unlock-atv|trueidtv-all", + "content_provider":"", + "channel_code":"bre01", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"Brentford", + "channel_name_th":"เบรนท์ฟอร์ด" + }, + "views":0, + "isLiveChat":false + }, + { + "id":"2Ag1bgVdNwoL", + "thumb":"https://cms.dmpcdn.com/livetv/2022/07/18/79461e70-0667-11ed-b687-85f145af88ed_webp_original.png", + "slug":"test2", + "title":"ช่องทดสอบออกอากาศที่2", + "content_type":"livetv", + "category":"livetv-ca|sports-ca", + "content_provider":"", + "channel_code":"tmp004", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"ช่องทดสอบออกอากาศที่2", + "channel_name_th":"ช่องทดสอบออกอากาศที่2" + }, + "views":0, + "isLiveChat":false + }, + { + "id":"OzKE5d4pdNwy", + "thumb":"https://cms.dmpcdn.com/livetv/2022/11/16/88521af0-6598-11ed-8215-b386a7bd4f58_webp_original.png", + "slug":"ufc", + "title":"UFC", + "content_type":"livetv", + "category":"livetv-ca|sports-ca|tvsnow|sports", + "content_provider":"true_vision", + "channel_code":"vc04", + "content_rights":null, + "channel_info":{ + "channel_name_eng":"UFC", + "channel_name_th":"ยูเอฟซี" + }, + "views":0, + "isLiveChat":false + } + ], + "channelSlug":"true-movie-hits", + "baseShelves":{ + "adsConfig":{ + "adsNetworkId":"", + "adsUnit":"21682623839/TrueID_Web/TV" + }, + "id":"3MPnXKpGjKqQ", + "shelfItems":[ + { + "id":"G3rooMXA2b4Z", + "title":{ + "th":"แนะนำ", + "en":"Featured" + }, + "type":"by_banner_homepage", + "viewType":"horizontal", + "shelfItems":[ + { + "id":"J1X89KQE7001", + "title":"ContentMkt_AVOD_Series_SpiceAndSpell", + "thumb":"https://cms.dmpcdn.com/hilight/2023/11/24/890e7c60-8aac-11ee-ad2b-e5fcaaed0aed_webp_original.webp", + "redirectUrl":"https://movie.trueid.net/series/kbNxLxw5Eg52/rV1rP8Dj27kB/AkMBP4z2VOKG/XrVjVjeWXz2r", + "article_category":null, + "content_type":"hilight" + }, + { + "id":"xrGQRbjWx1EL", + "title":"ContentMkt_TVOD_Movie_MI7Part1", + "thumb":"https://cms.dmpcdn.com/hilight/2023/11/23/32f66c30-89cf-11ee-976f-abdcd950f267_webp_original.webp", + "redirectUrl":"https://movie.trueid.net/movie/xDnNeqGkreJD", + "article_category":null, + "content_type":"hilight" + }, + { + "id":"J52JxN0jXkA5", + "title":"ContentMkt_SVOD_Asianseries_MyPrecious", + "thumb":"https://cms.dmpcdn.com/hilight/2023/11/06/00af5020-7c19-11ee-a9a1-41799b41aff4_webp_original.webp", + "redirectUrl":"", + "article_category":null, + "content_type":"hilight" + }, + { + "id":"yb22AdYZnQGb", + "title":"ContentMkt_AVOD_Lakorn_BakeMePlease", + "thumb":"https://cms.dmpcdn.com/hilight/2023/11/24/48523fc0-8aa9-11ee-bf9d-a586c3ad0143_webp_original.webp", + "redirectUrl":"https://movie.trueid.net/series/kN0QYgOQ0EJ5/zWBxNa65n6Vv/Eadp1WLqDXra/yqJgdKrkyRLY", + "article_category":null, + "content_type":"hilight" + }, + { + "id":"VmW2pEP9q3rw", + "title":"ContentMkt_AVOD_Anime_JujutsuKaisen", + "thumb":"https://cms.dmpcdn.com/hilight/2023/11/24/88238020-8aac-11ee-873d-3f4e5da5fd9c_webp_original.webp", + "redirectUrl":"https://movie.trueid.net/series/LgR5wpRnPQVA/qYQ00Mby07Rl/MvKZ6RnxKRaQ/jdAX0Wagxl4R", + "article_category":null, + "content_type":"hilight" + }, + { + "id":"BXGpEAn51mwW", + "title":"ContentMkt_TVOD_Movie_Ambulance", + "thumb":"https://cms.dmpcdn.com/hilight/2023/11/23/32aa20f0-89cf-11ee-ae81-d157aa7f87b4_webp_original.webp", + "redirectUrl":"https://movie.trueid.net/movie/Eq63XJL4okzw", + "article_category":null, + "content_type":"hilight" + }, + { + "id":"DoLWMe0YeQR8", + "title":"ContentMkt_AVOD_Anime_DarkGathering", + "thumb":"https://cms.dmpcdn.com/hilight/2023/11/23/316e93b0-89cf-11ee-a5ec-b3b59a2ebd97_webp_original.webp", + "redirectUrl":"https://movie.trueid.net/series/d6425Em1DPjQ/QMrQQgYaEDGZ/OAqkmxqX05DQ/1G8xwp6ja86G", + "article_category":null, + "content_type":"hilight" + }, + { + "id":"1PddoL4xG12P", + "title":"ContentMkt_AVOD_Anime_OnePiece", + "thumb":"https://cms.dmpcdn.com/hilight/2023/11/24/88241c60-8aac-11ee-bf9d-a586c3ad0143_webp_original.webp", + "redirectUrl":"https://movie.trueid.net/th-th/series/kxqkPYqVBq0D/4AoxBYpn4L1W/p2N5lYZGlVlL/lZ78MWznOElG", + "article_category":null, + "content_type":"hilight" + }, + { + "id":"nYa5A41VxmXY", + "title":"TruelD One Package ความบันเทิงระดับโลก แบบไร้ขีดจำกัด V21-23", + "thumb":"https://cms.dmpcdn.com/hilight/2023/11/13/84d3ef30-8215-11ee-84cd-3b76e2935cfd_webp_original.webp", + "redirectUrl":"https://home.trueid.net/external-browser?website=https://myaccount.trueid.net/checkout?promotionCode=SUPERBUNDLE_TID_IQIYI_WETV_PRIME&utm_campaign=Package_NA_NA_TrueIDOne&utm_medium=inside-platform&utm_source=Today_new_release", + "article_category":null, + "content_type":"hilight" + }, + { + "id":"VKvARdba10aW", + "title":"What's Wrong with My Princess", + "thumb":"https://cms.dmpcdn.com/hilight/2023/11/27/77b75b30-8cd2-11ee-9352-bd2663fbb8ed_webp_original.webp", + "redirectUrl":"https://movie.trueid.net/series/mP3G7aOXQGXP/QeDwwRKOL2Mp/RvD2p1OpZMRQ/zxnrl6JJnZ2x", + "article_category":null, + "content_type":"hilight" + } + ] + }, + { + "id":"k42naQeVKbK4", + "title":{ + "th":"", + "en":"" + }, + "type":"by_ads", + "viewType":"horizontal", + "shelfItems":[ + { + "ALL":{ + "targetingArguments":{ + "TrueID_page":[ + + ], + "Device":[ + + ] + }, + "sizeMapping":[ + { + "viewport":[ + 1280, + 0 + ], + "sizes":[ + [ + 750, + 200 + ], + [ + 970, + 90 + ], + [ + 728, + 90 + ], + "fluid", + [ + 800, + 250 + ], + [ + 970, + 250 + ], + [ + 1, + 1 + ], + [ + 1280, + 250 + ] + ] + }, + { + "viewport":[ + 375, + 0 + ], + "sizes":[ + [ + 1, + 1 + ], + [ + 320, + 250 + ], + [ + 375, + 250 + ], + "fluid", + [ + 300, + 250 + ], + [ + 320, + 100 + ] + ] + }, + { + "viewport":[ + 800, + 0 + ], + "sizes":[ + "fluid", + [ + 640, + 250 + ], + [ + 800, + 250 + ], + [ + 1, + 1 + ], + [ + 728, + 90 + ] + ] + }, + { + "viewport":[ + 0, + 0 + ], + "sizes":[ + [ + 320, + 50 + ], + [ + 320, + 100 + ], + [ + 1, + 1 + ] + ] + } + ], + "slotId":"div-gpt-ad-lb-1", + "adUnit":"21682623839/TrueID_Web/TV", + "sizes":[ + [ + 970, + 90 + ], + [ + 728, + 90 + ] + ] + } + } + ] + }, + { + "id":"O8pKrLmQlj2a", + "title":{ + "th":"ช่องฟรีทีวีฮิต", + "en":"Free TV" + }, + "type":"by_livetv_channel", + "viewType":"vertical", + "shelfItems":[ + { + "id":"wKngqJ2Vqnl", + "title":"MONO 29", + "thumb":"https://cms.dmpcdn.com/livetv/2019/01/10/35a35017-8473-4953-8474-5c58d805b74a.png", + "redirectUrl":"mono29", + "channel_code":"d43", + "views":33721, + "article_category":[ + "livetv-ca", + "digitaltv-ca", + "freetv-ca", + "movies-series-ca" + ], + "content_type":"livetv", + "content_rights":"", + "isLiveChat":false + }, + { + "id":"yYk6PvXwXDb", + "title":"WorkPoint TV", + "thumb":"https://cms.dmpcdn.com/livetv/2023/11/17/2a1de990-852d-11ee-bf98-41acc8fd04fc_webp_original.webp", + "redirectUrl":"workpointtv", + "channel_code":"d83", + "views":6075, + "article_category":[ + "livetv-ca", + "digitaltv-ca", + "entertainment-ca", + "freetv-ca" + ], + "content_type":"livetv", + "content_rights":"", + "isLiveChat":false + }, + { + "id":"QRP2K658b7G", + "title":"Thai PBS", + "thumb":"https://cms.dmpcdn.com/livetv/2023/09/15/ab170410-5377-11ee-8e1b-194edbb69638_webp_original.webp", + "redirectUrl":"thaipbs", + "channel_code":"c12", + "views":2526, + "article_category":[ + "livetv-ca", + "digitaltv-ca", + "freetv-ca", + "news-ca" + ], + "content_type":"livetv", + "content_rights":"", + "isLiveChat":false + }, + { + "id":"vqbr1WgEnGQ", + "title":"Channel 8", + "thumb":"https://cms.dmpcdn.com/livetv/2023/09/15/5408a390-5377-11ee-8e1b-194edbb69638_webp_original.webp", + "redirectUrl":"ch8", + "channel_code":"d62", + "views":8295, + "article_category":[ + "livetv-ca", + "digitaltv-ca", + "entertainment-ca", + "freetv-ca" + ], + "content_type":"livetv", + "content_rights":"", + "isLiveChat":false + }, + { + "id":"9O54lyP5Rqx", + "title":"Channel 7HD", + "thumb":"https://cms.dmpcdn.com/livetv/2023/07/19/212d15e0-25e7-11ee-bfc1-85e95548413c_webp_original.webp", + "redirectUrl":"ch7-hd", + "channel_code":"c07", + "views":12092, + "article_category":[ + "livetv-ca", + "digitaltv-ca", + "entertainment-ca", + "freetv-ca" + ], + "content_type":"livetv", + "content_rights":"", + "isLiveChat":false + }, + { + "id":"zMLBpX7AWmk", + "title":"Nation TV", + "thumb":"https://cms.dmpcdn.com/livetv/2023/09/09/9c6f59c0-4ebe-11ee-99a7-832609069236_webp_original.webp", + "redirectUrl":"nationtv", + "channel_code":"d78", + "views":4733, + "article_category":[ + "livetv-ca", + "digitaltv-ca", + "freetv-ca", + "news-ca" + ], + "content_type":"livetv", + "content_rights":"", + "isLiveChat":false + }, + { + "id":"nQlqONGyoa4", + "title":"Channel 3", + "thumb":"https://cms.dmpcdn.com/livetv/2023/07/24/5ff3e270-29cc-11ee-b2f4-e9de482d866e_webp_original.webp", + "redirectUrl":"ch3-hd", + "channel_code":"c03", + "views":96184, + "article_category":[ + "livetv-ca", + "digitaltv-ca", + "entertainment-ca", + "freetv-ca" + ], + "content_type":"livetv", + "content_rights":"", + "isLiveChat":false + }, + { + "id":"5PKobQk5gLOP", + "title":"Boomerang", + "thumb":"https://cms.dmpcdn.com/livetv/2023/07/05/b74a2460-1b05-11ee-8ce6-b102b53cb4a2_webp_original.webp", + "redirectUrl":"boomerang-hd", + "channel_code":"i007", + "views":707, + "article_category":[ + "livetv-ca", + "freetv-ca", + "kids-ca" + ], + "content_type":"livetv", + "content_rights":"", + "isLiveChat":false + }, + { + "id":"OVKwZle4eop", + "title":"True4U", + "thumb":"https://cms.dmpcdn.com/livetv/2023/09/15/84504210-5377-11ee-aaa1-7d584d8ca7a4_webp_original.webp", + "redirectUrl":"true4u", + "channel_code":"207", + "views":6489, + "article_category":[ + "livetv-ca", + "digitaltv-ca", + "entertainment-ca", + "freetv-ca", + "movies-series-ca" + ], + "content_type":"livetv", + "content_rights":"", + "isLiveChat":false + }, + { + "id":"0z4lvq6Xwoa", + "title":"One31", + "thumb":"https://cms.dmpcdn.com/livetv/2019/01/16/396384be-35dc-4d11-bf04-06c9546ec7bc.png", + "redirectUrl":"one-hd", + "channel_code":"d56", + "views":10182, + "article_category":[ + "livetv-ca", + "digitaltv-ca", + "entertainment-ca", + "freetv-ca" + ], + "content_type":"livetv", + "content_rights":"", + "isLiveChat":false + }, + { + "id":"rBWOx89v9Rk", + "title":"9 MCOT", + "thumb":"https://cms.dmpcdn.com/livetv/2023/09/09/9cc40970-4ebe-11ee-9801-97f95b5eed9a_webp_original.webp", + "redirectUrl":"9mcot-hd", + "channel_code":"c09", + "views":1323, + "article_category":[ + "livetv-ca", + "digitaltv-ca", + "freetv-ca" + ], + "content_type":"livetv", + "content_rights":"", + "isLiveChat":false + } + ] + }, + { + "id":"agbxxnP7GZQ4", + "title":{ + "th":"โปรแกรมทีวียอดนิยม", + "en":"Trending TV Program" + }, + "type":"by_trending_tv_program", + "viewType":"horizontal", + "shelfItems":[ + { + "id":"nQlqONGyoa4", + "title":"แชนแนลทรี ซีรีส์ สายใยรัก เหนือบัลลังก์", + "thumb":"https://cms.dmpcdn.com/livetv/2023/07/24/5ff3e270-29cc-11ee-b2f4-e9de482d866e_webp_original.webp", + "thumbLarge":"https://epgthumb.dmpcdn.com/thumbnail_large/c03.jpg?time=1702312743612", + "redirectUrl":"https://tv.trueid.net/th-en/live/ch3-hd", + "views":96184, + "program_id":"wDGy4q2m963K", + "program_name":"แชนแนลทรี ซีรีส์ สายใยรัก เหนือบัลลังก์", + "article_category":[ + "livetv-ca", + "digitaltv-ca", + "entertainment-ca", + "freetv-ca" + ], + "content_type":"livetv", + "channel_info":{ + "channel_name_cbd":"ប៉ុស្តិ៍ 3 HD", + "channel_name_eng":"CH3 HD", + "channel_name_mm":"CH3 HD", + "channel_name_th":"ช่อง 3 HD" + } + }, + { + "id":"8v732AYomo9", + "title":"ไทยรัฐเจาะประเด็น", + "thumb":"https://cms.dmpcdn.com/livetv/2023/07/18/7dc7a180-2515-11ee-b8b2-77e2a8f4c31e_webp_original.webp", + "thumbLarge":"https://epgthumb.dmpcdn.com/thumbnail_large/d05.jpg?time=1702312743612", + "redirectUrl":"https://tv.trueid.net/th-en/live/thairathtv-hd", + "views":17228, + "program_id":"xkKBjq0VA926", + "program_name":"ไทยรัฐเจาะประเด็น", + "article_category":[ + "livetv-ca", + "digitaltv-ca", + "freetv-ca", + "news-ca" + ], + "content_type":"livetv", + "channel_info":{ + "channel_name_cbd":"ថៃរ៉ាត់ ធីវី HD", + "channel_name_eng":"Thairath TV HD", + "channel_name_mm":"Thairath TV HD", + "channel_name_th":"ไทยรัฐ ทีวี HD" + } + }, + { + "id":"0z4lvq6Xwoa", + "title":"ละคร เสน่หาข้ามเส้น (ตอนอวสาน)", + "thumb":"https://cms.dmpcdn.com/livetv/2019/01/16/396384be-35dc-4d11-bf04-06c9546ec7bc.png", + "thumbLarge":"https://epgthumb.dmpcdn.com/thumbnail_large/d56.jpg?time=1702312743612", + "redirectUrl":"https://tv.trueid.net/th-en/live/one-hd", + "views":10182, + "program_id":"VZ8mMrWEKxQ3", + "program_name":"ละคร เสน่หาข้ามเส้น (ตอนอวสาน)", + "article_category":[ + "livetv-ca", + "digitaltv-ca", + "entertainment-ca", + "freetv-ca" + ], + "content_type":"livetv", + "channel_info":{ + "channel_name_cbd":"វ័ន HD", + "channel_name_eng":"One HD", + "channel_name_mm":"One HD", + "channel_name_th":"วัน HD" + } + }, + { + "id":"OVKwZle4eop", + "title":"ภาพยนตร์ อันธพาล", + "thumb":"https://cms.dmpcdn.com/livetv/2023/09/15/84504210-5377-11ee-aaa1-7d584d8ca7a4_webp_original.webp", + "thumbLarge":"https://epgthumb.dmpcdn.com/thumbnail_large/207.jpg?time=1702312743612", + "redirectUrl":"https://tv.trueid.net/th-en/live/true4u", + "views":6489, + "program_id":"4A8jX4rrX58J", + "program_name":"ภาพยนตร์ อันธพาล", + "article_category":[ + "livetv-ca", + "digitaltv-ca", + "entertainment-ca", + "freetv-ca", + "movies-series-ca" + ], + "content_type":"livetv", + "channel_info":{ + "channel_name_cbd":"ទ្រូ4យូ", + "channel_name_chi":"True4U", + "channel_name_eng":"True4U", + "channel_name_mm":"True4U", + "channel_name_rus":"True4U", + "channel_name_th":"ทรูโฟร์ยู", + "channel_name_vie":"True4U" + } + }, + { + "id":"9O54lyP5Rqx", + "title":"One Lumpinee Heroes", + "thumb":"https://cms.dmpcdn.com/livetv/2023/07/19/212d15e0-25e7-11ee-bfc1-85e95548413c_webp_original.webp", + "thumbLarge":"https://epgthumb.dmpcdn.com/thumbnail_large/c07.jpg?time=1702312743612", + "redirectUrl":"https://tv.trueid.net/th-en/live/ch7-hd", + "views":12092, + "program_id":"vbQqm8mnpyYb", + "program_name":"One Lumpinee Heroes", + "article_category":[ + "livetv-ca", + "digitaltv-ca", + "entertainment-ca", + "freetv-ca" + ], + "content_type":"livetv", + "channel_info":{ + "channel_name_cbd":"ប៉ុស្តិ៍​ 7", + "channel_name_eng":"CH 7HD", + "channel_name_mm":"Channel 7", + "channel_name_th":"ช่อง 7HD" + } + }, + { + "id":"yYk6PvXwXDb", + "title":"เคลียร์ชัดชัด รีรัน", + "thumb":"https://cms.dmpcdn.com/livetv/2023/11/17/2a1de990-852d-11ee-bf98-41acc8fd04fc_webp_original.webp", + "thumbLarge":"https://epgthumb.dmpcdn.com/thumbnail_large/d83.jpg?time=1702312743612", + "redirectUrl":"https://tv.trueid.net/th-en/live/workpointtv", + "views":6075, + "program_id":"KzKlKXKPDkDY", + "program_name":"เคลียร์ชัดชัด รีรัน", + "article_category":[ + "livetv-ca", + "digitaltv-ca", + "entertainment-ca", + "freetv-ca" + ], + "content_type":"livetv", + "channel_info":{ + "channel_name_cbd":"វើកភ័ញ គ្រីអ៊ែតធិវ ធីវី​", + "channel_name_eng":"Workpoint TV", + "channel_name_mm":"Workpoint TV", + "channel_name_th":"เวิร์คพอยท์ ทีวี" + } + }, + { + "id":"OBb6NzoJX7O", + "title":"ทรูช้อปปิ้ง", + "thumb":"https://cms.dmpcdn.com/livetv/2023/10/02/d2ec4b30-60f1-11ee-92a4-8597bcef0049_webp_original.webp", + "thumbLarge":"https://epgthumb.dmpcdn.com/thumbnail_large/da0.jpg?time=1702312743612", + "redirectUrl":"https://tv.trueid.net/th-en/live/amarintv-hd", + "views":6407, + "program_id":"PwP4AzA5KBXw", + "program_name":"ทรูช้อปปิ้ง", + "article_category":[ + "livetv-ca", + "digitaltv-ca", + "freetv-ca" + ], + "content_type":"livetv", + "channel_info":{ + "channel_name_cbd":"អាម៉ារិន", + "channel_name_eng":"Amarin TV", + "channel_name_mm":"Amarin TV", + "channel_name_th":"อมรินทร์" + } + }, + { + "id":"vqbr1WgEnGQ", + "title":"เด็ดมวยเดือด", + "thumb":"https://cms.dmpcdn.com/livetv/2023/09/15/5408a390-5377-11ee-8e1b-194edbb69638_webp_original.webp", + "thumbLarge":"https://epgthumb.dmpcdn.com/thumbnail_large/d62.jpg?time=1702312743612", + "redirectUrl":"https://tv.trueid.net/th-en/live/ch8", + "views":8295, + "program_id":"XJ4NgMgdr7K2", + "program_name":"เด็ดมวยเดือด", + "article_category":[ + "livetv-ca", + "digitaltv-ca", + "entertainment-ca", + "freetv-ca" + ], + "content_type":"livetv", + "channel_info":{ + "channel_name_cbd":"ប៉ុស្តិ៍ 8", + "channel_name_eng":"CH8", + "channel_name_th":"ช่อง 8" + } + }, + { + "id":"zMLBpX7AWmk", + "title":"ยุคลชนข่าว", + "thumb":"https://cms.dmpcdn.com/livetv/2023/09/09/9c6f59c0-4ebe-11ee-99a7-832609069236_webp_original.webp", + "thumbLarge":"https://epgthumb.dmpcdn.com/thumbnail_large/d78.jpg?time=1702312743612", + "redirectUrl":"https://tv.trueid.net/th-en/live/nationtv", + "views":4733, + "program_id":"m6ejRJ5pKvdL", + "program_name":"ยุคลชนข่าว", + "article_category":[ + "livetv-ca", + "digitaltv-ca", + "freetv-ca", + "news-ca" + ], + "content_type":"livetv", + "channel_info":{ + "channel_name_cbd":"Nation TV 22", + "channel_name_chi":"Nation TV 22", + "channel_name_eng":"Nation TV 22", + "channel_name_mm":"Nation TV 22", + "channel_name_rus":"Nation TV 22", + "channel_name_th":"เนชั่น ทีวี", + "channel_name_vie":"Nation TV 22" + } + }, + { + "id":"QNBwOpdaxpQ", + "title":"Highlights Bundesliga", + "thumb":"https://cms.dmpcdn.com/livetv/2023/08/28/012eed00-458a-11ee-bd2b-6734a2d9e428_webp_original.webp", + "thumbLarge":"https://epgthumb.dmpcdn.com/thumbnail_large/da7.jpg?time=1702312743612", + "redirectUrl":"https://tv.trueid.net/th-en/live/pptv-hd", + "views":4723, + "program_id":"DwYXj5Jbvex1", + "program_name":"Highlights Bundesliga", + "article_category":[ + "livetv-ca", + "digitaltv-ca", + "freetv-ca" + ], + "content_type":"livetv", + "channel_info":{ + "channel_name_cbd":"ភីភីធីវី", + "channel_name_eng":"PPTV", + "channel_name_mm":"PPTV", + "channel_name_th":"พีพีทีวี" + } + } + ] + } + ] + }, + "channelDetail":{ + "display_country":"th", + "display_lang":"en", + "id":"NopZ5gjkGmE", + "content_type":"livetv", + "original_id":"279", + "title":"True Movie Hits", + "article_category":[ + "livetv-ca", + "movies-series-ca", + "trueunlock-ca", + "tvsnow", + "movieseries" + ], + "thumb":"https://cms.dmpcdn.com/livetv/2023/04/28/45345d10-e599-11ed-86b8-bb40638e3c49_webp_original.png", + "tags":null, + "status":"publish", + "count_views":538976, + "publish_date":"2020-07-26T17:00:00.000Z", + "create_date":"2017-10-17T22:01:00.000Z", + "update_date":"2023-11-26T11:08:14.333Z", + "searchable":"Y", + "create_by":"Live TV", + "create_by_ssoid":null, + "update_by":"KANT", + "update_by_ssoid":"112710659", + "source_url":null, + "count_likes":null, + "count_ratings":null, + "source_country":null, + "channel_code":"057", + "drm":"WV_FPS", + "channel_info":{ + "channel_name_cbd":"True Movie Hits", + "channel_name_chi":"True Movie Hits", + "channel_name_eng":"True Movie Hits", + "channel_name_mm":"True Movie Hits", + "channel_name_rus":"True Movie Hits", + "channel_name_th":"True Movie Hits", + "channel_name_vie":"True Movie Hits" + }, + "lang_dual":"yes", + "setting":null, + "slug":"true-movie-hits", + "allow_app":[ + "trueidapp", + "trueidweb", + "trueidott", + "hybrid" + ], + "detail":"

    ช่องภาพยนต์ต่างประเทศ รับชมได้ทั้งครบครัวด้วยระบบเสียงภาษาไทย

    ", + "content_provider":"true_vision", + "playready":"", + "score":null + }, + "liveChatConfig":{ + "channelId":"NopZ5gjkGmE", + "isLiveChat":false, + "slug":"true-movie-hits", + "disabledChat":false, + "supportBrowser":{ + "chrome_browser_version":{ + "min_version":83, + "live_chat":false + }, + "firefox_browser_version":{ + "min_version":92, + "live_chat":false + }, + "msedge_browser_version":{ + "min_version":80, + "live_chat":false + }, + "off_livechat":false + }, + "disabledChatList":[ + + ] + }, + "epgList":[ + { + "id":"w2xyzKz9eyb3", + "original_id":"057:20231212_020500", + "content_type":"epg", + "title":"The Last Witch Hunter", + "detail":"", + "status":"publish", + "channel_code":"057", + "title_id":"715307", + "ep_id":"2397606", + "ep_no":"1", + "ep_name":"LAST WITCH HUNTER, THE (2015) [MHS] [R]", + "movie_type":"series", + "first_run":"Y", + "cast_type":"tape", + "start_date":"2023-12-11T19:05:00.000Z", + "end_date":"2023-12-11T20:55:00.000Z", + "publish_date":"2023-12-11T10:36:41.801Z", + "lang":"en", + "thumb":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_020500.jpg", + "thumb_list":{ + "thumb":"https://epgthumb.dmpcdn.com/thumbnail/057/20231212/20231212_020500.jpg", + "thumb_catchup":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_021000.jpg", + "thumb_large":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_020500.jpg" + }, + "black_out":0, + "catch_up":0, + "flag":"N", + "info":{ + "channel_name":"TR MOVIE HITS", + "image":"https://bms.dmpcdn.com/uploads/pic/381f853da5f4a310bf248357fed21a57.jpg", + "synopsis_en":"A young man is all that stands between humanity and the most horrifying witches in history.", + "director":"Breck Eisner", + "title_id":"715307", + "video":"pBNufkr4KkU", + "channel_code":"057", + "type":"Movie", + "synopsis_th":"หนุ่มนักล่าแม่มดถูกสาปให้เป็นอมตะจนกระทั่งราชินีแม่มดได้ฟื้นคืนชีพขึ้นมาจึงมีเพียงเขาคนเดียวเท่านั้นที่จะสามารถกอบกู้มวลมนุษยชาติได้", + "imdb_image":"https://bms.dmpcdn.com/uploads/pic/44b0eb2f46eed6a3953014cb5abdbff3.jpg", + "cast":"Vin Diesel, Rose Leslie, Elijah Wood", + "genres":"action", + "program_title":"LAST WITCH HUNTER, THE", + "production_year":"2015" + }, + "isShowTime":"02:05", + "isActive":false + }, + { + "id":"GPApa0aZzprE", + "original_id":"057:20231212_035500", + "content_type":"epg", + "title":"Point Break", + "detail":"", + "status":"publish", + "channel_code":"057", + "title_id":"718258", + "ep_id":"2413906", + "ep_no":"1", + "ep_name":"POINT BREAK (2015) [MHS] [R]", + "movie_type":"series", + "first_run":"Y", + "cast_type":"tape", + "start_date":"2023-12-11T20:55:00.000Z", + "end_date":"2023-12-11T22:55:00.000Z", + "publish_date":"2023-12-11T10:36:41.801Z", + "lang":"en", + "thumb":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_035500.jpg", + "thumb_list":{ + "thumb":"https://epgthumb.dmpcdn.com/thumbnail/057/20231212/20231212_035500.jpg", + "thumb_catchup":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_040000.jpg", + "thumb_large":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_035500.jpg" + }, + "black_out":0, + "catch_up":0, + "flag":"N", + "info":{ + "channel_name":"TR MOVIE HITS", + "image":"https://bms.dmpcdn.com/uploads/pic/e143a6f05ce8e87bf3e7c0f8dfca9914.jpeg", + "synopsis_en":"An FBI agent infiltrates a gang of thrill-seeking athlete thieves who are suspects in a spate of daring robberies.", + "director":"Ericson Core", + "title_id":"718258", + "video":"jcDD2-s4vWA", + "channel_code":"057", + "type":"Movie", + "synopsis_th":"เรื่องราวของเจ้าหน้าที่เอฟบีไอกับปฏิบัติการสืบสวนเพื่อตามล่าตัวมิจฉาชีพระดับโลกด้วยการแฝงตัวเข้าไปในกลุ่มนักเล่นกระดานโต้คลื่น", + "imdb_image":"https://bms.dmpcdn.com/uploads/pic/5a0fc22d59c8aef7e9693119687b2172.jpg", + "cast":"Edgar Ramirez, Luke Bracey, Ray Winstone", + "genres":"action", + "program_title":"POINT BREAK", + "production_year":"2015" + }, + "isShowTime":"03:55", + "isActive":false + }, + { + "id":"Va15bqbQn58a", + "original_id":"057:20231212_055500", + "content_type":"epg", + "title":"The Art of War", + "detail":"", + "status":"publish", + "channel_code":"057", + "title_id":"712097", + "ep_id":"2372786", + "ep_no":"1", + "ep_name":"ART OF WAR, THE [2000] [MHS]", + "movie_type":"series", + "first_run":"Y", + "cast_type":"tape", + "start_date":"2023-12-11T22:55:00.000Z", + "end_date":"2023-12-12T00:55:00.000Z", + "publish_date":"2023-12-11T10:36:41.801Z", + "lang":"en", + "thumb":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_055500.jpg", + "thumb_list":{ + "thumb":"https://epgthumb.dmpcdn.com/thumbnail/057/20231212/20231212_055500.jpg", + "thumb_catchup":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_060000.jpg", + "thumb_large":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_055500.jpg" + }, + "black_out":0, + "catch_up":0, + "flag":"N", + "info":{ + "channel_name":"TR MOVIE HITS", + "image":"https://bms.dmpcdn.com/uploads/pic/4f12f9d90f2e8d29b9e427ef415bcb4e.jpg", + "synopsis_en":"After a US agent is framed for the assassination of the Chinese ambassador, he faces a race against time to catch the real killers.", + "director":"Christian Duguay", + "title_id":"712097", + "video":"rKFmSpB-uGQ", + "channel_code":"057", + "type":"Movie", + "synopsis_th":"เมื่อสายลับถูกใส่ร้ายว่าเป็นฆาตกรเขาจึงต้องหลบหนีการตามไล่ล่าและแข่งกับเวลาเพื่อสืบหาตามล่าฆาตกรตัวจริงให้ได้โดยเร็วที่สุด", + "imdb_image":"https://bms.dmpcdn.com/uploads/pic/28ab499dffdff191fba497f64131e744.jpg", + "cast":"Wesley Snipes, Anne Archer, Maury Chaykin", + "genres":"crime", + "program_title":"ART OF WAR, THE", + "production_year":"2000" + }, + "isShowTime":"05:55", + "isActive":false + }, + { + "id":"ALxXy6y32XOL", + "original_id":"057:20231212_075500", + "content_type":"epg", + "title":"The Marksman", + "detail":"", + "status":"publish", + "channel_code":"057", + "title_id":"726358", + "ep_id":"2466252", + "ep_no":"1", + "ep_name":"MARKSMAN, THE (2021) [MHS]", + "movie_type":"series", + "first_run":"Y", + "cast_type":"tape", + "start_date":"2023-12-12T00:55:00.000Z", + "end_date":"2023-12-12T02:50:00.000Z", + "publish_date":"2023-12-11T10:36:41.801Z", + "lang":"en", + "thumb":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_075500.jpg", + "thumb_list":{ + "thumb":"https://epgthumb.dmpcdn.com/thumbnail/057/20231212/20231212_075500.jpg", + "thumb_catchup":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_080000.jpg", + "thumb_large":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_075500.jpg" + }, + "black_out":0, + "catch_up":0, + "flag":"N", + "info":{ + "channel_name":"TR MOVIE HITS", + "image":"https://bms.dmpcdn.com/uploads/pic/3945dc2c6cfff5ebeb61f021b58104ab.jpg", + "synopsis_en":"An Arizona rancher becomes the unlikely defender of a Mexican boy desperately fleeing the cartel assassins who've pursued him into the US.", + "director":"Robert Lorenz", + "title_id":"726358", + "video":"lEBPNi4bEbc", + "channel_code":"057", + "type":"Movie", + "synopsis_th":"อดีตทหารเรือ ที่หนีความวุ่นวายมาใช้ชีวิตอย่างสงบสุขในฟาร์มนอกเมือง แต่กลับต้องไปพัวพันกับสองแม่ลูกที่หลบหนีเอาตัวรอดจากกลุ่มนักฆ่าค้ายา", + "imdb_image":"https://bms.dmpcdn.com/uploads/pic/573349fd5394caccce8c4f818fdb57b5.jpg", + "cast":"Liam Neeson, Katheryn Winnick, Juan Pablo Raba", + "genres":"action", + "program_title":"MARKSMAN, THE", + "production_year":"2021" + }, + "isShowTime":"07:55", + "isActive":false + }, + { + "id":"XzDNDnDrXNJ9", + "original_id":"057:20231212_095000", + "content_type":"epg", + "title":"Gods of Egypt", + "detail":"", + "status":"publish", + "channel_code":"057", + "title_id":"718274", + "ep_id":"2414078", + "ep_no":"1", + "ep_name":"GODS OF EGYPT (2016) [MHS] [R]", + "movie_type":"series", + "first_run":"Y", + "cast_type":"tape", + "start_date":"2023-12-12T02:50:00.000Z", + "end_date":"2023-12-12T05:05:00.000Z", + "publish_date":"2023-12-11T10:36:41.801Z", + "lang":"en", + "thumb":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_095000.jpg", + "thumb_list":{ + "thumb":"https://epgthumb.dmpcdn.com/thumbnail/057/20231212/20231212_095000.jpg", + "thumb_catchup":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_095500.jpg", + "thumb_large":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_095000.jpg" + }, + "black_out":0, + "catch_up":0, + "flag":"N", + "info":{ + "channel_name":"TR MOVIE HITS", + "image":"https://bms.dmpcdn.com/uploads/pic/e772028844f60526e6dc0fe5b666425a.jpg", + "synopsis_en":"A young hero teams with the god Horus to fight against the god of darkness, who has usurped Egypt's throne.", + "director":"Alex Proyas", + "title_id":"718274", + "video":"Oijdb-a9GKY", + "channel_code":"057", + "type":"Movie", + "synopsis_th":"เรื่องราวความขัดแย้งและการช่วงชิงที่อุบัติขึ้นท่ามกลางความร้อนระอุแห่งทะเลทรายในดินแดนลุ่มแม่น้ำไนล์อันเต็มไปด้วยมนตรา ทวยเทพ และ เหล่าอสูร", + "imdb_image":"https://bms.dmpcdn.com/uploads/pic/0a945dc3853317779946f5b9f38269a1.jpg", + "cast":"Gerard Butler, Brenton Thwaites, Nikolaj Coster-Waldau", + "genres":"action", + "program_title":"GODS OF EGYPT", + "production_year":"2016" + }, + "isShowTime":"09:50", + "isActive":false + }, + { + "id":"4DnvNENgamwD", + "original_id":"057:20231212_120500", + "content_type":"epg", + "title":"The Last Witch Hunter", + "detail":"", + "status":"publish", + "channel_code":"057", + "title_id":"715307", + "ep_id":"2397606", + "ep_no":"1", + "ep_name":"LAST WITCH HUNTER, THE (2015) [MHS] [R]", + "movie_type":"series", + "first_run":"Y", + "cast_type":"tape", + "start_date":"2023-12-12T05:05:00.000Z", + "end_date":"2023-12-12T06:55:00.000Z", + "publish_date":"2023-12-11T10:36:41.801Z", + "lang":"en", + "thumb":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_120500.jpg", + "thumb_list":{ + "thumb":"https://epgthumb.dmpcdn.com/thumbnail/057/20231212/20231212_120500.jpg", + "thumb_catchup":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_121000.jpg", + "thumb_large":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_120500.jpg" + }, + "black_out":0, + "catch_up":0, + "flag":"N", + "info":{ + "channel_name":"TR MOVIE HITS", + "image":"https://bms.dmpcdn.com/uploads/pic/381f853da5f4a310bf248357fed21a57.jpg", + "synopsis_en":"A young man is all that stands between humanity and the most horrifying witches in history.", + "director":"Breck Eisner", + "title_id":"715307", + "video":"pBNufkr4KkU", + "channel_code":"057", + "type":"Movie", + "synopsis_th":"หนุ่มนักล่าแม่มดถูกสาปให้เป็นอมตะจนกระทั่งราชินีแม่มดได้ฟื้นคืนชีพขึ้นมาจึงมีเพียงเขาคนเดียวเท่านั้นที่จะสามารถกอบกู้มวลมนุษยชาติได้", + "imdb_image":"https://bms.dmpcdn.com/uploads/pic/44b0eb2f46eed6a3953014cb5abdbff3.jpg", + "cast":"Vin Diesel, Rose Leslie, Elijah Wood", + "genres":"action", + "program_title":"LAST WITCH HUNTER, THE", + "production_year":"2015" + }, + "isShowTime":"12:05", + "isActive":false + }, + { + "id":"AEMb1g1LnzpQ", + "original_id":"057:20231212_135500", + "content_type":"epg", + "title":"Leon", + "detail":"", + "status":"publish", + "channel_code":"057", + "title_id":"729656", + "ep_id":"2488591", + "ep_no":"1", + "ep_name":"LEON [1994] [MHS]", + "movie_type":"series", + "first_run":"Y", + "cast_type":"tape", + "start_date":"2023-12-12T06:55:00.000Z", + "end_date":"2023-12-12T09:05:00.000Z", + "publish_date":"2023-12-11T10:36:41.801Z", + "lang":"en", + "thumb":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_135500.jpg", + "thumb_list":{ + "thumb":"https://epgthumb.dmpcdn.com/thumbnail/057/20231212/20231212_135500.jpg", + "thumb_catchup":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_140000.jpg", + "thumb_large":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_135500.jpg" + }, + "black_out":0, + "catch_up":0, + "flag":"N", + "info":{ + "channel_name":"TR MOVIE HITS", + "image":"https://bms.dmpcdn.com/uploads/pic/79eabd21fdb1da338cca6b598de46cde.jpg", + "synopsis_en":"A hitman forms an unlikely bond with a young girl, teaching her his deadly skills while protecting her from ruthless criminals.", + "director":"Luc Besson", + "title_id":"729656", + "video":"aNQqoExfQsg", + "channel_code":"057", + "type":"Movie", + "synopsis_th":"เรื่องราวของนักฆ่าที่ได้สร้างความผูกพันธ์ที่ไม่น่าจะเป็นไปได้กับเด็กหญิง โดยสอนทักษะอันอันตรายแก่เธอพร้อมทั้งปกป้องเธอจากอาชญากรผู้โหดเหี้ยม", + "imdb_image":"https://bms.dmpcdn.com/uploads/pic/20be5d12ff2b8f86fb40f9db619d4cb8.jpg", + "cast":"Jean Reno, Gary Oldman, Natalie Portman", + "genres":"crime", + "program_title":"LEON", + "production_year":"1994" + }, + "isShowTime":"13:55", + "isActive":false + }, + { + "id":"2xe7R1Rgamq4", + "original_id":"057:20231212_160500", + "content_type":"epg", + "title":"Gunpowder Milkshake", + "detail":"", + "status":"publish", + "channel_code":"057", + "title_id":"710376", + "ep_id":"2363208", + "ep_no":"1", + "ep_name":"GUNPOWDER MILKSHAKE (2021) [MHS]", + "movie_type":"series", + "first_run":"Y", + "cast_type":"tape", + "start_date":"2023-12-12T09:05:00.000Z", + "end_date":"2023-12-12T11:00:00.000Z", + "publish_date":"2023-12-11T10:36:41.801Z", + "lang":"en", + "thumb":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_160500.jpg", + "thumb_list":{ + "thumb":"https://epgthumb.dmpcdn.com/thumbnail/057/20231212/20231212_160500.jpg", + "thumb_catchup":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_161000.jpg", + "thumb_large":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_160500.jpg" + }, + "black_out":0, + "catch_up":0, + "flag":"N", + "info":{ + "channel_name":"TR MOVIE HITS", + "image":"https://bms.dmpcdn.com/uploads/pic/0039112f2fef876ebf32f9bfb3a9fcf9.jpg", + "synopsis_en":"Three generations of women fight back against those who aim to take everything from them.", + "director":"Navot Papushado", + "title_id":"710376", + "video":"yxuAroBqt2c", + "channel_code":"057", + "type":"Movie", + "synopsis_th":"เรื่องราวของสามหญิงสามวัยที่ต้องต่อสู้กับผู้ซึ่งแย่งชิงทุกสิ่งทุกอย่างไปจากพวกเธอ", + "imdb_image":"https://bms.dmpcdn.com/uploads/pic/756c225bc8f5f2ed1268945c979b01a1.jpg", + "cast":"Karen Gillan, Lena Headey, Carla Gugino", + "genres":"action", + "program_title":"GUNPOWDER MILKSHAKE", + "production_year":"2021" + }, + "isShowTime":"16:05", + "isActive":false + }, + { + "id":"QoeyO1O0Q3no", + "original_id":"057:20231212_180000", + "content_type":"epg", + "title":"Point Break", + "detail":"", + "status":"publish", + "channel_code":"057", + "title_id":"718258", + "ep_id":"2413906", + "ep_no":"1", + "ep_name":"POINT BREAK (2015) [MHS] [R]", + "movie_type":"series", + "first_run":"Y", + "cast_type":"tape", + "start_date":"2023-12-12T11:00:00.000Z", + "end_date":"2023-12-12T13:00:00.000Z", + "publish_date":"2023-12-11T10:36:41.801Z", + "lang":"en", + "thumb":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_180000.jpg", + "thumb_list":{ + "thumb":"https://epgthumb.dmpcdn.com/thumbnail/057/20231212/20231212_180000.jpg", + "thumb_catchup":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_180500.jpg", + "thumb_large":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_180000.jpg" + }, + "black_out":0, + "catch_up":0, + "flag":"N", + "info":{ + "channel_name":"TR MOVIE HITS", + "image":"https://bms.dmpcdn.com/uploads/pic/e143a6f05ce8e87bf3e7c0f8dfca9914.jpeg", + "synopsis_en":"An FBI agent infiltrates a gang of thrill-seeking athlete thieves who are suspects in a spate of daring robberies.", + "director":"Ericson Core", + "title_id":"718258", + "video":"jcDD2-s4vWA", + "channel_code":"057", + "type":"Movie", + "synopsis_th":"เรื่องราวของเจ้าหน้าที่เอฟบีไอกับปฏิบัติการสืบสวนเพื่อตามล่าตัวมิจฉาชีพระดับโลกด้วยการแฝงตัวเข้าไปในกลุ่มนักเล่นกระดานโต้คลื่น", + "imdb_image":"https://bms.dmpcdn.com/uploads/pic/5a0fc22d59c8aef7e9693119687b2172.jpg", + "cast":"Edgar Ramirez, Luke Bracey, Ray Winstone", + "genres":"action", + "program_title":"POINT BREAK", + "production_year":"2015" + }, + "isShowTime":"18:00", + "isActive":false + }, + { + "id":"voAargrBvRQo", + "original_id":"057:20231212_200000", + "content_type":"epg", + "title":"Max Steel", + "detail":"", + "status":"publish", + "channel_code":"057", + "title_id":"718257", + "ep_id":"2413902", + "ep_no":"1", + "ep_name":"MAX STEEL (2016) [MHS] [R]", + "movie_type":"series", + "first_run":"Y", + "cast_type":"tape", + "start_date":"2023-12-12T13:00:00.000Z", + "end_date":"2023-12-12T14:35:00.000Z", + "publish_date":"2023-12-11T10:36:41.801Z", + "lang":"en", + "thumb":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_200000.jpg", + "thumb_list":{ + "thumb":"https://epgthumb.dmpcdn.com/thumbnail/057/20231212/20231212_200000.jpg", + "thumb_catchup":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_200500.jpg", + "thumb_large":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_200000.jpg" + }, + "black_out":0, + "catch_up":0, + "flag":"N", + "info":{ + "channel_name":"TR MOVIE HITS", + "image":"https://bms.dmpcdn.com/uploads/pic/a10785bc40cd82e82ae702d8a7827393.jpg", + "synopsis_en":"A young teen and an alien companion harness and combine their tremendous new powers to evolve into the turbo-charged superhero Max Steel.", + "director":"Stewart Hendler", + "title_id":"718257", + "video":"Tf4sa0BVJVw", + "channel_code":"057", + "type":"Movie", + "synopsis_th":"ชายหนุ่มที่ชีวิตต้องแปรเปลี่ยนไปตลอดกาลจากอุบัติเหตุภายในห้องทดลองซึ่งทำให้เขากลายเป็นยอดมนุษย์แกร่ง", + "imdb_image":"https://bms.dmpcdn.com/uploads/pic/c8e9e6d49546fbde72d0f0b552db97a6.jpg", + "cast":"Ben Winchell, Josh Brener, Maria Bello", + "genres":"action", + "program_title":"MAX STEEL", + "production_year":"2016" + }, + "isShowTime":"20:00", + "isActive":false + }, + { + "id":"Ena7xBxkNK3z", + "original_id":"057:20231212_213500", + "content_type":"epg", + "title":"Pompeii", + "detail":"", + "status":"publish", + "channel_code":"057", + "title_id":"715311", + "ep_id":"2397620", + "ep_no":"1", + "ep_name":"POMPEII (2014) [MHS] [R]", + "movie_type":"series", + "first_run":"Y", + "cast_type":"tape", + "start_date":"2023-12-12T14:35:00.000Z", + "end_date":"2023-12-12T16:20:00.000Z", + "publish_date":"2023-12-11T10:36:41.801Z", + "lang":"en", + "thumb":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_213500.jpg", + "thumb_list":{ + "thumb":"https://epgthumb.dmpcdn.com/thumbnail/057/20231212/20231212_213500.jpg", + "thumb_catchup":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_214000.jpg", + "thumb_large":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_213500.jpg" + }, + "black_out":0, + "catch_up":0, + "flag":"N", + "info":{ + "channel_name":"TR MOVIE HITS", + "image":"https://bms.dmpcdn.com/uploads/pic/28b7c486d1d8c52060413fb58c869c76.jpg", + "synopsis_en":"Just before the fateful eruption of Mt Vesuvius, a gladiator must save the love of his life from a corrupt Roman.", + "director":"Paul Anderson", + "title_id":"715311", + "video":"t6TRwfxDICM", + "channel_code":"057", + "type":"Movie", + "synopsis_th":"เรื่องราวของหนุ่มนักรบซึ่งเสี่ยงชีพช่วยเหลือหญิงสาวผู้เป็นที่รักจากมหาวิบัติกัมปนาทครั้งใหญ่แห่งประวัติศาสตร์เมื่อภูเขาไฟวิซูเวียสเกิดปะทุขึ้น", + "imdb_image":"https://bms.dmpcdn.com/uploads/pic/8675b7f9a08f3f0587bed52c7a8015e1.jpg", + "cast":"Kit Harington, Emily Browning, Kiefer Sutherland", + "genres":"action", + "program_title":"POMPEII", + "production_year":"2014" + }, + "isShowTime":"21:35", + "isActive":false + }, + { + "id":"WNxrPpPwwkQl", + "original_id":"057:20231212_232000", + "content_type":"epg", + "title":"Leon", + "detail":"", + "status":"publish", + "channel_code":"057", + "title_id":"729656", + "ep_id":"2488591", + "ep_no":"1", + "ep_name":"LEON [1994] [MHS]", + "movie_type":"series", + "first_run":"Y", + "cast_type":"tape", + "start_date":"2023-12-12T16:20:00.000Z", + "end_date":"2023-12-12T18:30:00.000Z", + "publish_date":"2023-12-11T10:36:41.801Z", + "lang":"en", + "thumb":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_232000.jpg", + "thumb_list":{ + "thumb":"https://epgthumb.dmpcdn.com/thumbnail/057/20231212/20231212_232000.jpg", + "thumb_catchup":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_232500.jpg", + "thumb_large":"https://epgthumb.dmpcdn.com/thumbnail_large/057/20231212/20231212_232000.jpg" + }, + "black_out":0, + "catch_up":0, + "flag":"N", + "info":{ + "channel_name":"TR MOVIE HITS", + "image":"https://bms.dmpcdn.com/uploads/pic/79eabd21fdb1da338cca6b598de46cde.jpg", + "synopsis_en":"A hitman forms an unlikely bond with a young girl, teaching her his deadly skills while protecting her from ruthless criminals.", + "director":"Luc Besson", + "title_id":"729656", + "video":"aNQqoExfQsg", + "channel_code":"057", + "type":"Movie", + "synopsis_th":"เรื่องราวของนักฆ่าที่ได้สร้างความผูกพันธ์ที่ไม่น่าจะเป็นไปได้กับเด็กหญิง โดยสอนทักษะอันอันตรายแก่เธอพร้อมทั้งปกป้องเธอจากอาชญากรผู้โหดเหี้ยม", + "imdb_image":"https://bms.dmpcdn.com/uploads/pic/20be5d12ff2b8f86fb40f9db619d4cb8.jpg", + "cast":"Jean Reno, Gary Oldman, Natalie Portman", + "genres":"crime", + "program_title":"LEON", + "production_year":"1994" + }, + "isShowTime":"23:20", + "isActive":false + } + ], + "audioData":{ + "lang_locale":"", + "voice_commentary":"" + }, + "playerLanguage":{ + "data":{ + "aa":"Afar", + "ab":"Abkhazian", + "af":"Afrikaans", + "ak":"Akan", + "am":"Amharic", + "ar":"Arabic", + "an":"Aragonese", + "as":"Assamese", + "av":"Avaric", + "ae":"Avestan", + "ay":"Aymara", + "az":"Azerbaijani", + "ba":"Bashkir", + "bm":"Bambara", + "be":"Belarusian", + "bn":"Bengali", + "bh":"Biharilanguages", + "bi":"Bislama", + "bs":"Bosnian", + "br":"Breton", + "bg":"Bulgarian", + "ca":"CatalanValencian", + "ch":"Chamorro", + "ce":"Chechen", + "cu":"ChurchSlavicOldSlavonicChurchSlavonicOldBulgarianOldChurchSlavonic", + "cv":"Chuvash", + "kw":"Cornish", + "co":"Corsican", + "cr":"Cree", + "cs":"Czech", + "da":"Danish", + "dv":"DivehiDhivehiMaldivian", + "dz":"Dzongkha", + "en":"English", + "eo":"Esperanto", + "et":"Estonian", + "eu":"Basque", + "ee":"Ewe", + "fo":"Faroese", + "fj":"Fijian", + "fi":"Finnish", + "fr":"French", + "fy":"WesternFrisian", + "ff":"Fulah", + "de":"German", + "gd":"GaelicScottishGaelic", + "ga":"Irish", + "gl":"Galician", + "gv":"Manx", + "el":"Greek,Modern(1453-)", + "gn":"Guarani", + "gu":"Gujarati", + "ht":"HaitianHaitianCreole", + "ha":"Hausa", + "he":"Hebrew", + "hz":"Herero", + "hi":"Hindi", + "ho":"HiriMotu", + "hr":"Croatian", + "hu":"Hungarian", + "hy":"Armenian", + "ig":"Igbo", + "io":"Ido", + "ii":"SichuanYiNuosu", + "iu":"Inuktitut", + "ie":"InterlingueOccidental", + "ia":"Interlingua(InternationalAuxiliaryLanguageAssociation)", + "id":"Indonesian", + "ik":"Inupiaq", + "is":"Icelandic", + "it":"Italian", + "jv":"Javanese", + "ja":"Japanese", + "kl":"KalaallisutGreenlandic", + "kn":"Kannada", + "ks":"Kashmiri", + "ka":"Georgian", + "kr":"Kanuri", + "kk":"Kazakh", + "km":"CentralKhmer", + "ki":"KikuyuGikuyu", + "rw":"Kinyarwanda", + "ky":"KirghizKyrgyz", + "kv":"Komi", + "kg":"Kongo", + "ko":"Korean", + "kj":"KuanyamaKwanyama", + "ku":"Kurdish", + "lo":"Lao", + "la":"Latin", + "lv":"Latvian", + "li":"LimburganLimburgerLimburgish", + "ln":"Lingala", + "lt":"Lithuanian", + "lb":"LuxembourgishLetzeburgesch", + "lu":"Luba-Katanga", + "lg":"Ganda", + "mh":"Marshallese", + "ml":"Malayalam", + "mr":"Marathi", + "mk":"Macedonian", + "mg":"Malagasy", + "mt":"Maltese", + "mn":"Mongolian", + "mi":"Maori", + "ms":"Malay", + "my":"Burmese", + "na":"Nauru", + "nv":"NavajoNavaho", + "nr":"Ndebele,SouthSouthNdebele", + "nd":"Ndebele,NorthNorthNdebele", + "ng":"Ndonga", + "ne":"Nepali", + "nl":"DutchFlemish", + "nn":"NorwegianNynorskNynorsk,Norwegian", + "nb":"Bokmål,NorwegianNorwegianBokmål", + "no":"Norwegian", + "ny":"ChichewaChewaNyanja", + "oc":"Occitan(post1500)", + "oj":"Ojibwa", + "or":"Oriya", + "om":"Oromo", + "os":"OssetianOssetic", + "pa":"PanjabiPunjabi", + "fa":"Persian", + "pi":"Pali", + "pl":"Polish", + "pt":"Portuguese", + "ps":"PushtoPashto", + "qu":"Quechua", + "rm":"Romansh", + "ro":"RomanianMoldavianMoldovan", + "rn":"Rundi", + "ru":"Russian", + "sg":"Sango", + "sa":"Sanskrit", + "si":"SinhalaSinhalese", + "sk":"Slovak", + "sl":"Slovenian", + "se":"NorthernSami", + "sm":"Samoan", + "sn":"Shona", + "sd":"Sindhi", + "so":"Somali", + "st":"Sotho,Southern", + "es":"SpanishCastilian", + "sq":"Albanian", + "sc":"Sardinian", + "sr":"Serbian", + "ss":"Swati", + "su":"Sundanese", + "sw":"Swahili", + "sv":"Swedish", + "ty":"Tahitian", + "ta":"Tamil", + "tt":"Tatar", + "te":"Telugu", + "tg":"Tajik", + "tl":"Tagalog", + "th":"Thai", + "bo":"Tibetan", + "ti":"Tigrinya", + "to":"Tonga(TongaIslands)", + "tn":"Tswana", + "ts":"Tsonga", + "tk":"Turkmen", + "tr":"Turkish", + "tw":"Twi", + "ug":"UighurUyghur", + "uk":"Ukrainian", + "ur":"Urdu", + "uz":"Uzbek", + "ve":"Venda", + "vi":"Vietnamese", + "vo":"Volapük", + "cy":"Welsh", + "wa":"Walloon", + "wo":"Wolof", + "xh":"Xhosa", + "yi":"Yiddish", + "yo":"Yoruba", + "za":"ZhuangChuang", + "zh":"Chinese", + "zu":"Zulu", + "aar":"Afar", + "abk":"Abkhazian", + "ace":"Achinese", + "ach":"Acoli", + "ada":"Adangme", + "ady":"AdygheAdygei", + "afa":"Afro-Asiaticlanguages", + "afh":"Afrihili", + "afr":"Afrikaans", + "ain":"Ainu", + "aka":"Akan", + "akk":"Akkadian", + "ale":"Aleut", + "alg":"Algonquianlanguages", + "alt":"SouthernAltai", + "amh":"Amharic", + "ang":"English,Old(ca.450-1100)", + "anp":"Angika", + "apa":"Apachelanguages", + "ara":"Arabic", + "arc":"OfficialAramaic(700-300BCE)ImperialAramaic(700-300BCE)", + "arg":"Aragonese", + "arn":"MapudungunMapuche", + "arp":"Arapaho", + "art":"Artificiallanguages", + "arw":"Arawak", + "asm":"Assamese", + "ast":"AsturianBableLeoneseAsturleonese", + "ath":"Athapascanlanguages", + "aus":"Australianlanguages", + "ava":"Avaric", + "ave":"Avestan", + "awa":"Awadhi", + "aym":"Aymara", + "aze":"Azerbaijani", + "bad":"Bandalanguages", + "bai":"Bamilekelanguages", + "bak":"Bashkir", + "bal":"Baluchi", + "bam":"Bambara", + "ban":"Balinese", + "bas":"Basa", + "bat":"Balticlanguages", + "bej":"BejaBedawiyet", + "bel":"Belarusian", + "bem":"Bemba", + "ben":"Bengali", + "ber":"Berberlanguages", + "bho":"Bhojpuri", + "bih":"Biharilanguages", + "bik":"Bikol", + "bin":"BiniEdo", + "bis":"Bislama", + "bla":"Siksika", + "bnt":"Bantulanguages", + "bos":"Bosnian", + "bra":"Braj", + "bre":"Breton", + "btk":"Bataklanguages", + "bua":"Buriat", + "bug":"Buginese", + "bul":"Bulgarian", + "bur(B)mya(T)":"Burmese", + "byn":"BlinBilin", + "cad":"Caddo", + "cai":"CentralAmericanIndianlanguages", + "car":"GalibiCarib", + "cat":"CatalanValencian", + "cau":"Caucasianlanguages", + "ceb":"Cebuano", + "cel":"Celticlanguages", + "cha":"Chamorro", + "chb":"Chibcha", + "che":"Chechen", + "chg":"Chagatai", + "chk":"Chuukese", + "chm":"Mari", + "chn":"Chinookjargon", + "cho":"Choctaw", + "chp":"ChipewyanDeneSuline", + "chr":"Cherokee", + "chu":"ChurchSlavicOldSlavonicChurchSlavonicOldBulgarianOldChurchSlavonic", + "chv":"Chuvash", + "chy":"Cheyenne", + "cmc":"Chamiclanguages", + "cnr":"Montenegrin", + "cop":"Coptic", + "cor":"Cornish", + "cos":"Corsican", + "cpe":"Creolesandpidgins,Englishbased", + "cpf":"Creolesandpidgins,French-based", + "cpp":"Creolesandpidgins,Portuguese-based", + "cre":"Cree", + "crh":"CrimeanTatarCrimeanTurkish", + "crp":"Creolesandpidgins", + "csb":"Kashubian", + "cus":"Cushiticlanguages", + "cze(B)ces(T)":"Czech", + "dak":"Dakota", + "dan":"Danish", + "dar":"Dargwa", + "day":"LandDayaklanguages", + "del":"Delaware", + "den":"Slave(Athapascan)", + "dgr":"Dogrib", + "din":"Dinka", + "div":"DivehiDhivehiMaldivian", + "doi":"Dogri", + "dra":"Dravidianlanguages", + "dsb":"LowerSorbian", + "dua":"Duala", + "dum":"Dutch,Middle(ca.1050-1350)", + "dyu":"Dyula", + "dzo":"Dzongkha", + "efi":"Efik", + "egy":"Egyptian(Ancient)", + "eka":"Ekajuk", + "elx":"Elamite", + "eng":"English", + "enm":"English,Middle(1100-1500)", + "epo":"Esperanto", + "est":"Estonian", + "baq(B)eus(T)":"Basque", + "ewo":"Ewondo", + "fan":"Fang", + "fao":"Faroese", + "fat":"Fanti", + "fij":"Fijian", + "fil":"FilipinoPilipino", + "fin":"Finnish", + "fiu":"Finno-Ugrianlanguages", + "fre(B)fra(T)":"French", + "frm":"French,Middle(ca.1400-1600)", + "fro":"French,Old(842-ca.1400)", + "frr":"NorthernFrisian", + "frs":"EasternFrisian", + "fry":"WesternFrisian", + "ful":"Fulah", + "fur":"Friulian", + "gaa":"Ga", + "gay":"Gayo", + "gba":"Gbaya", + "gem":"Germaniclanguages", + "ger(B)deu(T)":"German", + "gez":"Geez", + "gil":"Gilbertese", + "gla":"GaelicScottishGaelic", + "gle":"Irish", + "glg":"Galician", + "glv":"Manx", + "gmh":"German,MiddleHigh(ca.1050-1500)", + "goh":"German,OldHigh(ca.750-1050)", + "gon":"Gondi", + "gor":"Gorontalo", + "got":"Gothic", + "grb":"Grebo", + "grc":"Greek,Ancient(to1453)", + "gre(B)ell(T)":"Greek,Modern(1453-)", + "grn":"Guarani", + "gsw":"SwissGermanAlemannicAlsatian", + "guj":"Gujarati", + "gwi":"Gwich'in", + "hai":"Haida", + "hat":"HaitianHaitianCreole", + "hau":"Hausa", + "haw":"Hawaiian", + "heb":"Hebrew", + "her":"Herero", + "hil":"Hiligaynon", + "him":"HimachalilanguagesWesternPaharilanguages", + "hin":"Hindi", + "hit":"Hittite", + "hmn":"HmongMong", + "hmo":"HiriMotu", + "hrv":"Croatian", + "hsb":"UpperSorbian", + "hun":"Hungarian", + "hup":"Hupa", + "arm(B)hye(T)":"Armenian", + "iba":"Iban", + "ibo":"Igbo", + "iii":"SichuanYiNuosu", + "ijo":"Ijolanguages", + "iku":"Inuktitut", + "ile":"InterlingueOccidental", + "ilo":"Iloko", + "ina":"Interlingua(InternationalAuxiliaryLanguageAssociation)", + "inc":"Indiclanguages", + "ind":"Indonesian", + "ine":"Indo-Europeanlanguages", + "inh":"Ingush", + "ipk":"Inupiaq", + "ira":"Iranianlanguages", + "iro":"Iroquoianlanguages", + "ice(B)isl(T)":"Icelandic", + "ita":"Italian", + "jav":"Javanese", + "jbo":"Lojban", + "jpn":"Japanese", + "jpr":"Judeo-Persian", + "jrb":"Judeo-Arabic", + "kaa":"Kara-Kalpak", + "kab":"Kabyle", + "kac":"KachinJingpho", + "kal":"KalaallisutGreenlandic", + "kam":"Kamba", + "kan":"Kannada", + "kar":"Karenlanguages", + "kas":"Kashmiri", + "geo(B)kat(T)":"Georgian", + "kau":"Kanuri", + "kaw":"Kawi", + "kaz":"Kazakh", + "kbd":"Kabardian", + "kha":"Khasi", + "khi":"Khoisanlanguages", + "khm":"CentralKhmer", + "kho":"KhotaneseSakan", + "kik":"KikuyuGikuyu", + "kin":"Kinyarwanda", + "kir":"KirghizKyrgyz", + "kmb":"Kimbundu", + "kok":"Konkani", + "kom":"Komi", + "kon":"Kongo", + "kor":"Korean", + "kos":"Kosraean", + "kpe":"Kpelle", + "krc":"Karachay-Balkar", + "krl":"Karelian", + "kro":"Krulanguages", + "kru":"Kurukh", + "kua":"KuanyamaKwanyama", + "kum":"Kumyk", + "kur":"Kurdish", + "kut":"Kutenai", + "lad":"Ladino", + "lah":"Lahnda", + "lam":"Lamba", + "lat":"Latin", + "lav":"Latvian", + "lez":"Lezghian", + "lim":"LimburganLimburgerLimburgish", + "lin":"Lingala", + "lit":"Lithuanian", + "lol":"Mongo", + "loz":"Lozi", + "ltz":"LuxembourgishLetzeburgesch", + "lua":"Luba-Lulua", + "lub":"Luba-Katanga", + "lug":"Ganda", + "lui":"Luiseno", + "lun":"Lunda", + "luo":"Luo(KenyaandTanzania)", + "lus":"Lushai", + "mac(B)mkd(T)":"Macedonian", + "mad":"Madurese", + "mag":"Magahi", + "mah":"Marshallese", + "mai":"Maithili", + "mak":"Makasar", + "mal":"Malayalam", + "man":"Mandingo", + "mao(B)mri(T)":"Maori", + "map":"Austronesianlanguages", + "mar":"Marathi", + "mas":"Masai", + "may(B)msa(T)":"Malay", + "mdf":"Moksha", + "mdr":"Mandar", + "men":"Mende", + "mga":"Irish,Middle(900-1200)", + "mic":"Mi'kmaqMicmac", + "min":"Minangkabau", + "mis":"Uncodedlanguages", + "mkh":"Mon-Khmerlanguages", + "mlg":"Malagasy", + "mlt":"Maltese", + "mnc":"Manchu", + "mni":"Manipuri", + "mno":"Manobolanguages", + "moh":"Mohawk", + "mon":"Mongolian", + "mos":"Mossi", + "mul":"Multiplelanguages", + "mun":"Mundalanguages", + "mus":"Creek", + "mwl":"Mirandese", + "mwr":"Marwari", + "myn":"Mayanlanguages", + "myv":"Erzya", + "nah":"Nahuatllanguages", + "nai":"NorthAmericanIndianlanguages", + "nap":"Neapolitan", + "nau":"Nauru", + "nav":"NavajoNavaho", + "nbl":"Ndebele,SouthSouthNdebele", + "nde":"Ndebele,NorthNorthNdebele", + "ndo":"Ndonga", + "nds":"LowGermanLowSaxonGerman,LowSaxon,Low", + "nep":"Nepali", + "new":"NepalBhasaNewari", + "nia":"Nias", + "nic":"Niger-Kordofanianlanguages", + "niu":"Niuean", + "dut(B)nld(T)":"DutchFlemish", + "nno":"NorwegianNynorskNynorsk,Norwegian", + "nob":"Bokmål,NorwegianNorwegianBokmål", + "nog":"Nogai", + "non":"Norse,Old", + "nor":"Norwegian", + "nqo":"N'Ko", + "nso":"PediSepediNorthernSotho", + "nub":"Nubianlanguages", + "nwc":"ClassicalNewariOldNewariClassicalNepalBhasa", + "nya":"ChichewaChewaNyanja", + "nym":"Nyamwezi", + "nyn":"Nyankole", + "nyo":"Nyoro", + "nzi":"Nzima", + "oci":"Occitan(post1500)", + "oji":"Ojibwa", + "ori":"Oriya", + "orm":"Oromo", + "osa":"Osage", + "oss":"OssetianOssetic", + "ota":"Turkish,Ottoman(1500-1928)", + "oto":"Otomianlanguages", + "paa":"Papuanlanguages", + "pag":"Pangasinan", + "pal":"Pahlavi", + "pam":"PampangaKapampangan", + "pan":"PanjabiPunjabi", + "pap":"Papiamento", + "pau":"Palauan", + "peo":"Persian,Old(ca.600-400B.C.)", + "per(B)fas(T)":"Persian", + "phi":"Philippinelanguages", + "phn":"Phoenician", + "pli":"Pali", + "pol":"Polish", + "pon":"Pohnpeian", + "por":"Portuguese", + "pra":"Prakritlanguages", + "pro":"Provençal,Old(to1500)Occitan,Old(to1500)", + "pus":"PushtoPashto", + "qaa-qtz":"Reservedforlocaluse", + "que":"Quechua", + "raj":"Rajasthani", + "rap":"Rapanui", + "rar":"RarotonganCookIslandsMaori", + "roa":"Romancelanguages", + "roh":"Romansh", + "rom":"Romany", + "rum(B)ron(T)":"RomanianMoldavianMoldovan", + "run":"Rundi", + "rup":"AromanianArumanianMacedo-Romanian", + "rus":"Russian", + "sad":"Sandawe", + "sag":"Sango", + "sah":"Yakut", + "sai":"SouthAmericanIndianlanguages", + "sal":"Salishanlanguages", + "sam":"SamaritanAramaic", + "san":"Sanskrit", + "sas":"Sasak", + "sat":"Santali", + "scn":"Sicilian", + "sco":"Scots", + "sel":"Selkup", + "sem":"Semiticlanguages", + "sga":"Irish,Old(to900)", + "sgn":"SignLanguages", + "shn":"Shan", + "sid":"Sidamo", + "sin":"SinhalaSinhalese", + "sio":"Siouanlanguages", + "sit":"Sino-Tibetanlanguages", + "sla":"Slaviclanguages", + "slo(B)slk(T)":"Slovak", + "slv":"Slovenian", + "sma":"SouthernSami", + "sme":"NorthernSami", + "smi":"Samilanguages", + "smj":"LuleSami", + "smn":"InariSami", + "smo":"Samoan", + "sms":"SkoltSami", + "sna":"Shona", + "snd":"Sindhi", + "snk":"Soninke", + "sog":"Sogdian", + "som":"Somali", + "son":"Songhailanguages", + "sot":"Sotho,Southern", + "spa":"SpanishCastilian", + "alb(B)sqi(T)":"Albanian", + "srd":"Sardinian", + "srn":"SrananTongo", + "srp":"Serbian", + "srr":"Serer", + "ssa":"Nilo-Saharanlanguages", + "ssw":"Swati", + "suk":"Sukuma", + "sun":"Sundanese", + "sus":"Susu", + "sux":"Sumerian", + "swa":"Swahili", + "swe":"Swedish", + "syc":"ClassicalSyriac", + "syr":"Syriac", + "tah":"Tahitian", + "tai":"Tailanguages", + "tam":"Tamil", + "tat":"Tatar", + "tel":"Telugu", + "tem":"Timne", + "ter":"Tereno", + "tet":"Tetum", + "tgk":"Tajik", + "tgl":"Tagalog", + "tha":"Thai", + "tib(B)bod(T)":"Tibetan", + "tig":"Tigre", + "tir":"Tigrinya", + "tiv":"Tiv", + "tkl":"Tokelau", + "tlh":"KlingontlhIngan-Hol", + "tli":"Tlingit", + "tmh":"Tamashek", + "tog":"Tonga(Nyasa)", + "ton":"Tonga(TongaIslands)", + "tpi":"TokPisin", + "tsi":"Tsimshian", + "tsn":"Tswana", + "tso":"Tsonga", + "tuk":"Turkmen", + "tum":"Tumbuka", + "tup":"Tupilanguages", + "tur":"Turkish", + "tut":"Altaiclanguages", + "tvl":"Tuvalu", + "tyv":"Tuvinian", + "udm":"Udmurt", + "uga":"Ugaritic", + "uig":"UighurUyghur", + "ukr":"Ukrainian", + "umb":"Umbundu", + "und":"Undetermined", + "urd":"Urdu", + "uzb":"Uzbek", + "ven":"Venda", + "vie":"Vietnamese", + "vol":"Volapük", + "vot":"Votic", + "wak":"Wakashanlanguages", + "wal":"WolaittaWolaytta", + "war":"Waray", + "was":"Washo", + "wel(B)cym(T)":"Welsh", + "wen":"Sorbianlanguages", + "wln":"Walloon", + "wol":"Wolof", + "xal":"KalmykOirat", + "xho":"Xhosa", + "yap":"Yapese", + "yid":"Yiddish", + "yor":"Yoruba", + "ypk":"Yupiklanguages", + "zap":"Zapotec", + "zbl":"BlissymbolsBlissymbolicsBliss", + "zen":"Zenaga", + "zgh":"StandardMoroccanTamazight", + "zha":"ZhuangChuang", + "chi(B)zho(T)":"Chinese", + "chi":"Chinese", + "znd":"Zandelanguages", + "zul":"Zulu", + "zun":"Zuni", + "zxx":"NolinguisticcontentNotapplicable", + "zza":"ZazaDimiliDimliKirdkiKirmanjkiZazaki", + "afar":"Afar", + "abkhazian":"Abkhazian", + "achinese":"Achinese", + "acoli":"Acoli", + "adangme":"Adangme", + "adygheadygei":"AdygheAdygei", + "afro-asiaticlanguages":"Afro-Asiaticlanguages", + "afrihili":"Afrihili", + "afrikaans":"Afrikaans", + "ainu":"Ainu", + "akan":"Akan", + "akkadian":"Akkadian", + "aleut":"Aleut", + "algonquianlanguages":"Algonquianlanguages", + "southernaltai":"SouthernAltai", + "amharic":"Amharic", + "english,old(ca.450-1100)":"English,Old(ca.450-1100)", + "angika":"Angika", + "apachelanguages":"Apachelanguages", + "arabic":"Arabic", + "officialaramaic(700-300bce)imperialaramaic(700-300bce)":"OfficialAramaic(700-300BCE)ImperialAramaic(700-300BCE)", + "aragonese":"Aragonese", + "mapudungunmapuche":"MapudungunMapuche", + "arapaho":"Arapaho", + "artificiallanguages":"Artificiallanguages", + "arawak":"Arawak", + "assamese":"Assamese", + "asturianbableleoneseasturleonese":"AsturianBableLeoneseAsturleonese", + "athapascanlanguages":"Athapascanlanguages", + "australianlanguages":"Australianlanguages", + "avaric":"Avaric", + "avestan":"Avestan", + "awadhi":"Awadhi", + "aymara":"Aymara", + "azerbaijani":"Azerbaijani", + "bandalanguages":"Bandalanguages", + "bamilekelanguages":"Bamilekelanguages", + "bashkir":"Bashkir", + "baluchi":"Baluchi", + "bambara":"Bambara", + "balinese":"Balinese", + "basa":"Basa", + "balticlanguages":"Balticlanguages", + "bejabedawiyet":"BejaBedawiyet", + "belarusian":"Belarusian", + "bemba":"Bemba", + "bengali":"Bengali", + "berberlanguages":"Berberlanguages", + "bhojpuri":"Bhojpuri", + "biharilanguages":"Biharilanguages", + "bikol":"Bikol", + "biniedo":"BiniEdo", + "bislama":"Bislama", + "siksika":"Siksika", + "bantulanguages":"Bantulanguages", + "bosnian":"Bosnian", + "braj":"Braj", + "breton":"Breton", + "bataklanguages":"Bataklanguages", + "buriat":"Buriat", + "buginese":"Buginese", + "bulgarian":"Bulgarian", + "blinbilin":"BlinBilin", + "caddo":"Caddo", + "centralamericanindianlanguages":"CentralAmericanIndianlanguages", + "galibicarib":"GalibiCarib", + "catalanvalencian":"CatalanValencian", + "caucasianlanguages":"Caucasianlanguages", + "cebuano":"Cebuano", + "celticlanguages":"Celticlanguages", + "chamorro":"Chamorro", + "chibcha":"Chibcha", + "chechen":"Chechen", + "chagatai":"Chagatai", + "chuukese":"Chuukese", + "mari":"Mari", + "chinookjargon":"Chinookjargon", + "choctaw":"Choctaw", + "chipewyandenesuline":"ChipewyanDeneSuline", + "cherokee":"Cherokee", + "churchslavicoldslavonicchurchslavonicoldbulgarianoldchurchslavonic":"ChurchSlavicOldSlavonicChurchSlavonicOldBulgarianOldChurchSlavonic", + "chuvash":"Chuvash", + "cheyenne":"Cheyenne", + "chamiclanguages":"Chamiclanguages", + "montenegrin":"Montenegrin", + "coptic":"Coptic", + "cornish":"Cornish", + "corsican":"Corsican", + "creolesandpidgins,englishbased":"Creolesandpidgins,Englishbased", + "creolesandpidgins,french-based":"Creolesandpidgins,French-based", + "creolesandpidgins,portuguese-based":"Creolesandpidgins,Portuguese-based", + "cree":"Cree", + "crimeantatarcrimeanturkish":"CrimeanTatarCrimeanTurkish", + "creolesandpidgins":"Creolesandpidgins", + "kashubian":"Kashubian", + "cushiticlanguages":"Cushiticlanguages", + "czech":"Czech", + "dakota":"Dakota", + "danish":"Danish", + "dargwa":"Dargwa", + "landdayaklanguages":"LandDayaklanguages", + "delaware":"Delaware", + "slave(athapascan)":"Slave(Athapascan)", + "dogrib":"Dogrib", + "dinka":"Dinka", + "divehidhivehimaldivian":"DivehiDhivehiMaldivian", + "dogri":"Dogri", + "dravidianlanguages":"Dravidianlanguages", + "lowersorbian":"LowerSorbian", + "duala":"Duala", + "dutch,middle(ca.1050-1350)":"Dutch,Middle(ca.1050-1350)", + "dyula":"Dyula", + "dzongkha":"Dzongkha", + "efik":"Efik", + "egyptian(ancient)":"Egyptian(Ancient)", + "ekajuk":"Ekajuk", + "elamite":"Elamite", + "english":"English", + "english,middle(1100-1500)":"English,Middle(1100-1500)", + "esperanto":"Esperanto", + "estonian":"Estonian", + "basque":"Basque", + "ewe":"Ewe", + "ewondo":"Ewondo", + "fang":"Fang", + "faroese":"Faroese", + "fanti":"Fanti", + "fijian":"Fijian", + "filipinopilipino":"FilipinoPilipino", + "finnish":"Finnish", + "finno-ugrianlanguages":"Finno-Ugrianlanguages", + "fon":"Fon", + "french":"French", + "french,middle(ca.1400-1600)":"French,Middle(ca.1400-1600)", + "french,old(842-ca.1400)":"French,Old(842-ca.1400)", + "northernfrisian":"NorthernFrisian", + "easternfrisian":"EasternFrisian", + "westernfrisian":"WesternFrisian", + "fulah":"Fulah", + "friulian":"Friulian", + "gayo":"Gayo", + "gbaya":"Gbaya", + "germaniclanguages":"Germaniclanguages", + "german":"German", + "geez":"Geez", + "gilbertese":"Gilbertese", + "gaelicscottishgaelic":"GaelicScottishGaelic", + "irish":"Irish", + "galician":"Galician", + "manx":"Manx", + "german,middlehigh(ca.1050-1500)":"German,MiddleHigh(ca.1050-1500)", + "german,oldhigh(ca.750-1050)":"German,OldHigh(ca.750-1050)", + "gondi":"Gondi", + "gorontalo":"Gorontalo", + "gothic":"Gothic", + "grebo":"Grebo", + "greek,ancient(to1453)":"Greek,Ancient(to1453)", + "greek,modern(1453-)":"Greek,Modern(1453-)", + "guarani":"Guarani", + "swissgermanalemannicalsatian":"SwissGermanAlemannicAlsatian", + "gujarati":"Gujarati", + "gwich'in":"Gwich'in", + "haida":"Haida", + "haitianhaitiancreole":"HaitianHaitianCreole", + "hausa":"Hausa", + "hawaiian":"Hawaiian", + "hebrew":"Hebrew", + "herero":"Herero", + "hiligaynon":"Hiligaynon", + "himachalilanguageswesternpaharilanguages":"HimachalilanguagesWesternPaharilanguages", + "hindi":"Hindi", + "hittite":"Hittite", + "hmongmong":"HmongMong", + "hirimotu":"HiriMotu", + "croatian":"Croatian", + "uppersorbian":"UpperSorbian", + "hungarian":"Hungarian", + "hupa":"Hupa", + "armenian":"Armenian", + "iban":"Iban", + "igbo":"Igbo", + "ido":"Ido", + "sichuanyinuosu":"SichuanYiNuosu", + "ijolanguages":"Ijolanguages", + "inuktitut":"Inuktitut", + "interlingueoccidental":"InterlingueOccidental", + "iloko":"Iloko", + "interlingua(internationalauxiliarylanguageassociation)":"Interlingua(InternationalAuxiliaryLanguageAssociation)", + "indiclanguages":"Indiclanguages", + "indonesian":"Indonesian", + "indo-europeanlanguages":"Indo-Europeanlanguages", + "ingush":"Ingush", + "inupiaq":"Inupiaq", + "iranianlanguages":"Iranianlanguages", + "iroquoianlanguages":"Iroquoianlanguages", + "icelandic":"Icelandic", + "italian":"Italian", + "javanese":"Javanese", + "lojban":"Lojban", + "japanese":"Japanese", + "judeo-persian":"Judeo-Persian", + "judeo-arabic":"Judeo-Arabic", + "kara-kalpak":"Kara-Kalpak", + "kabyle":"Kabyle", + "kachinjingpho":"KachinJingpho", + "kalaallisutgreenlandic":"KalaallisutGreenlandic", + "kamba":"Kamba", + "kannada":"Kannada", + "karenlanguages":"Karenlanguages", + "kashmiri":"Kashmiri", + "georgian":"Georgian", + "kanuri":"Kanuri", + "kawi":"Kawi", + "kazakh":"Kazakh", + "kabardian":"Kabardian", + "khasi":"Khasi", + "khoisanlanguages":"Khoisanlanguages", + "centralkhmer":"CentralKhmer", + "khotanesesakan":"KhotaneseSakan", + "kikuyugikuyu":"KikuyuGikuyu", + "kinyarwanda":"Kinyarwanda", + "kirghizkyrgyz":"KirghizKyrgyz", + "kimbundu":"Kimbundu", + "konkani":"Konkani", + "komi":"Komi", + "kongo":"Kongo", + "korean":"Korean", + "kosraean":"Kosraean", + "kpelle":"Kpelle", + "karachay-balkar":"Karachay-Balkar", + "karelian":"Karelian", + "krulanguages":"Krulanguages", + "kurukh":"Kurukh", + "kuanyamakwanyama":"KuanyamaKwanyama", + "kumyk":"Kumyk", + "kurdish":"Kurdish", + "kutenai":"Kutenai", + "ladino":"Ladino", + "lahnda":"Lahnda", + "lamba":"Lamba", + "lao":"Lao", + "latin":"Latin", + "latvian":"Latvian", + "lezghian":"Lezghian", + "limburganlimburgerlimburgish":"LimburganLimburgerLimburgish", + "lingala":"Lingala", + "lithuanian":"Lithuanian", + "mongo":"Mongo", + "lozi":"Lozi", + "luxembourgishletzeburgesch":"LuxembourgishLetzeburgesch", + "luba-lulua":"Luba-Lulua", + "luba-katanga":"Luba-Katanga", + "ganda":"Ganda", + "luiseno":"Luiseno", + "lunda":"Lunda", + "luo(kenyaandtanzania)":"Luo(KenyaandTanzania)", + "lushai":"Lushai", + "madurese":"Madurese", + "magahi":"Magahi", + "marshallese":"Marshallese", + "maithili":"Maithili", + "makasar":"Makasar", + "malayalam":"Malayalam", + "mandingo":"Mandingo", + "austronesianlanguages":"Austronesianlanguages", + "marathi":"Marathi", + "masai":"Masai", + "moksha":"Moksha", + "mandar":"Mandar", + "mende":"Mende", + "irish,middle(900-1200)":"Irish,Middle(900-1200)", + "mi'kmaqmicmac":"Mi'kmaqMicmac", + "minangkabau":"Minangkabau", + "uncodedlanguages":"Uncodedlanguages", + "macedonian":"Macedonian", + "mon-khmerlanguages":"Mon-Khmerlanguages", + "malagasy":"Malagasy", + "maltese":"Maltese", + "manchu":"Manchu", + "manipuri":"Manipuri", + "manobolanguages":"Manobolanguages", + "mohawk":"Mohawk", + "mongolian":"Mongolian", + "mossi":"Mossi", + "maori":"Maori", + "malay":"Malay", + "multiplelanguages":"Multiplelanguages", + "mundalanguages":"Mundalanguages", + "creek":"Creek", + "mirandese":"Mirandese", + "marwari":"Marwari", + "burmese":"Burmese", + "mayanlanguages":"Mayanlanguages", + "erzya":"Erzya", + "nahuatllanguages":"Nahuatllanguages", + "northamericanindianlanguages":"NorthAmericanIndianlanguages", + "neapolitan":"Neapolitan", + "nauru":"Nauru", + "navajonavaho":"NavajoNavaho", + "ndebele,southsouthndebele":"Ndebele,SouthSouthNdebele", + "ndebele,northnorthndebele":"Ndebele,NorthNorthNdebele", + "ndonga":"Ndonga", + "lowgermanlowsaxongerman,lowsaxon,low":"LowGermanLowSaxonGerman,LowSaxon,Low", + "nepali":"Nepali", + "nepalbhasanewari":"NepalBhasaNewari", + "nias":"Nias", + "niger-kordofanianlanguages":"Niger-Kordofanianlanguages", + "niuean":"Niuean", + "dutchflemish":"DutchFlemish", + "norwegiannynorsknynorsk,norwegian":"NorwegianNynorskNynorsk,Norwegian", + "bokmål,norwegiannorwegianbokmål":"Bokmål,NorwegianNorwegianBokmål", + "nogai":"Nogai", + "norse,old":"Norse,Old", + "norwegian":"Norwegian", + "n'ko":"N'Ko", + "pedisepedinorthernsotho":"PediSepediNorthernSotho", + "nubianlanguages":"Nubianlanguages", + "classicalnewarioldnewariclassicalnepalbhasa":"ClassicalNewariOldNewariClassicalNepalBhasa", + "chichewachewanyanja":"ChichewaChewaNyanja", + "nyamwezi":"Nyamwezi", + "nyankole":"Nyankole", + "nyoro":"Nyoro", + "nzima":"Nzima", + "occitan(post1500)":"Occitan(post1500)", + "ojibwa":"Ojibwa", + "oriya":"Oriya", + "oromo":"Oromo", + "osage":"Osage", + "ossetianossetic":"OssetianOssetic", + "turkish,ottoman(1500-1928)":"Turkish,Ottoman(1500-1928)", + "otomianlanguages":"Otomianlanguages", + "papuanlanguages":"Papuanlanguages", + "pangasinan":"Pangasinan", + "pahlavi":"Pahlavi", + "pampangakapampangan":"PampangaKapampangan", + "panjabipunjabi":"PanjabiPunjabi", + "papiamento":"Papiamento", + "palauan":"Palauan", + "persian,old(ca.600-400b.c.)":"Persian,Old(ca.600-400B.C.)", + "persian":"Persian", + "philippinelanguages":"Philippinelanguages", + "phoenician":"Phoenician", + "pali":"Pali", + "polish":"Polish", + "pohnpeian":"Pohnpeian", + "portuguese":"Portuguese", + "prakritlanguages":"Prakritlanguages", + "provençal,old(to1500)occitan,old(to1500)":"Provençal,Old(to1500)Occitan,Old(to1500)", + "pushtopashto":"PushtoPashto", + "reservedforlocaluse":"Reservedforlocaluse", + "quechua":"Quechua", + "rajasthani":"Rajasthani", + "rapanui":"Rapanui", + "rarotongancookislandsmaori":"RarotonganCookIslandsMaori", + "romancelanguages":"Romancelanguages", + "romansh":"Romansh", + "romany":"Romany", + "romanianmoldavianmoldovan":"RomanianMoldavianMoldovan", + "rundi":"Rundi", + "aromanianarumanianmacedo-romanian":"AromanianArumanianMacedo-Romanian", + "russian":"Russian", + "sandawe":"Sandawe", + "sango":"Sango", + "yakut":"Yakut", + "southamericanindianlanguages":"SouthAmericanIndianlanguages", + "salishanlanguages":"Salishanlanguages", + "samaritanaramaic":"SamaritanAramaic", + "sanskrit":"Sanskrit", + "sasak":"Sasak", + "santali":"Santali", + "sicilian":"Sicilian", + "scots":"Scots", + "selkup":"Selkup", + "semiticlanguages":"Semiticlanguages", + "irish,old(to900)":"Irish,Old(to900)", + "signlanguages":"SignLanguages", + "shan":"Shan", + "sidamo":"Sidamo", + "sinhalasinhalese":"SinhalaSinhalese", + "siouanlanguages":"Siouanlanguages", + "sino-tibetanlanguages":"Sino-Tibetanlanguages", + "slaviclanguages":"Slaviclanguages", + "slovak":"Slovak", + "slovenian":"Slovenian", + "southernsami":"SouthernSami", + "northernsami":"NorthernSami", + "samilanguages":"Samilanguages", + "lulesami":"LuleSami", + "inarisami":"InariSami", + "samoan":"Samoan", + "skoltsami":"SkoltSami", + "shona":"Shona", + "sindhi":"Sindhi", + "soninke":"Soninke", + "sogdian":"Sogdian", + "somali":"Somali", + "songhailanguages":"Songhailanguages", + "sotho,southern":"Sotho,Southern", + "spanishcastilian":"SpanishCastilian", + "albanian":"Albanian", + "sardinian":"Sardinian", + "sranantongo":"SrananTongo", + "serbian":"Serbian", + "serer":"Serer", + "nilo-saharanlanguages":"Nilo-Saharanlanguages", + "swati":"Swati", + "sukuma":"Sukuma", + "sundanese":"Sundanese", + "susu":"Susu", + "sumerian":"Sumerian", + "swahili":"Swahili", + "swedish":"Swedish", + "classicalsyriac":"ClassicalSyriac", + "syriac":"Syriac", + "tahitian":"Tahitian", + "tailanguages":"Tailanguages", + "tamil":"Tamil", + "tatar":"Tatar", + "telugu":"Telugu", + "timne":"Timne", + "tereno":"Tereno", + "tetum":"Tetum", + "tajik":"Tajik", + "tagalog":"Tagalog", + "thai":"Thai", + "tibetan":"Tibetan", + "tigre":"Tigre", + "tigrinya":"Tigrinya", + "tokelau":"Tokelau", + "klingontlhingan-hol":"KlingontlhIngan-Hol", + "tlingit":"Tlingit", + "tamashek":"Tamashek", + "tonga(nyasa)":"Tonga(Nyasa)", + "tonga(tongaislands)":"Tonga(TongaIslands)", + "tokpisin":"TokPisin", + "tsimshian":"Tsimshian", + "tswana":"Tswana", + "tsonga":"Tsonga", + "turkmen":"Turkmen", + "tumbuka":"Tumbuka", + "tupilanguages":"Tupilanguages", + "turkish":"Turkish", + "altaiclanguages":"Altaiclanguages", + "tuvalu":"Tuvalu", + "twi":"Twi", + "tuvinian":"Tuvinian", + "udmurt":"Udmurt", + "ugaritic":"Ugaritic", + "uighuruyghur":"UighurUyghur", + "ukrainian":"Ukrainian", + "umbundu":"Umbundu", + "undetermined":"Undetermined", + "urdu":"Urdu", + "uzbek":"Uzbek", + "vai":"Vai", + "venda":"Venda", + "vietnamese":"Vietnamese", + "volapük":"Volapük", + "votic":"Votic", + "wakashanlanguages":"Wakashanlanguages", + "wolaittawolaytta":"WolaittaWolaytta", + "waray":"Waray", + "washo":"Washo", + "welsh":"Welsh", + "sorbianlanguages":"Sorbianlanguages", + "walloon":"Walloon", + "wolof":"Wolof", + "kalmykoirat":"KalmykOirat", + "xhosa":"Xhosa", + "yao":"Yao", + "yapese":"Yapese", + "yiddish":"Yiddish", + "yoruba":"Yoruba", + "yupiklanguages":"Yupiklanguages", + "zapotec":"Zapotec", + "blissymbolsblissymbolicsbliss":"BlissymbolsBlissymbolicsBliss", + "zenaga":"Zenaga", + "standardmoroccantamazight":"StandardMoroccanTamazight", + "zhuangchuang":"ZhuangChuang", + "chinese":"Chinese", + "zandelanguages":"Zandelanguages", + "zulu":"Zulu", + "zuni":"Zuni", + "nolinguisticcontentnotapplicable":"NolinguisticcontentNotapplicable", + "zazadimilidimlikirdkikirmanjkizazaki":"ZazaDimiliDimliKirdkiKirmanjkiZazaki", + "influencer1":"Influencer1", + "influencer2":"Influencer2", + "original":"Original" + } + }, + "ccu":458, + "deviceId":"yxN2Cd3kIhYKTM7aYAxGl5NnYCAdyIlQ", + "isAllowedWeb":true, + "epgActive":{ + + }, + "configBlackout":{ + "duration":300000, + "enable":false, + "btn_channel_list":{ + "title_en":"", + "title_th":"", + "url":"", + "url_th":"", + "url_en":"" + } + }, + "activeCategory":"", + "adsSideBar":{ + "adsData":{ + "ALL":{ + "targetingArguments":{ + "TrueID_page":[ + + ], + "Device":[ + + ] + }, + "sizeMapping":[ + { + "viewport":[ + 0, + 0 + ], + "sizes":[ + [ + 320, + 250 + ], + [ + 300, + 250 + ], + [ + 1, + 1 + ], + "fluid" + ] + } + ], + "slotId":"div-gpt-ad-rt-1", + "adUnit":"21682623839/TrueID_Web/TV", + "sizes":[ + [ + 320, + 250 + ] + ] + } + }, + "adsConfig":{ + "adsNetworkId":"", + "adsUnit":"21682623839/TrueID_Web/TV" + } + }, + "currentURL":"https://tv.trueid.net/th-en/live/true-movie-hits" + }, + "__N_SSP":true +} \ No newline at end of file diff --git a/sites/tv.yandex.ru/__data__/no_content.html b/sites/tv.yandex.ru/__data__/no_content.html new file mode 100644 index 000000000..6fedfd4c7 --- /dev/null +++ b/sites/tv.yandex.ru/__data__/no_content.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sites/tv.yandex.ru/tv.yandex.ru.test.js b/sites/tv.yandex.ru/tv.yandex.ru.test.js index 9661089f4..a1b6cb09d 100644 --- a/sites/tv.yandex.ru/tv.yandex.ru.test.js +++ b/sites/tv.yandex.ru/tv.yandex.ru.test.js @@ -86,7 +86,7 @@ it('can handle empty guide', async () => { const result = await parser({ date, channel, - content: '' + content: fs.readFileSync(path.resolve(__dirname, '__data__', 'no_content.html'), 'utf8') }) expect(result).toMatchObject([]) }) diff --git a/sites/tv2go.t-2.net/__data__/content.json b/sites/tv2go.t-2.net/__data__/content.json new file mode 100644 index 000000000..d3bc32089 --- /dev/null +++ b/sites/tv2go.t-2.net/__data__/content.json @@ -0,0 +1,54 @@ +{ + "entries":[ + { + "channelId":1000259, + "startTimestamp":"1637283000000", + "endTimestamp":"1637284500000", + "name":"Dnevnik Slovencev v Italiji", + "nameSingleLine":"Dnevnik Slovencev v Italiji", + "description":"Informativni", + "images":[ + { + "url":"/static/media/img/epg/max_crop/EPG_IMG_2927405.jpg", + "width":1008, + "height":720, + "averageColor":[ + 143, + 147, + 161 + ] + } + ], + "show":{ + "id":51991133, + "title":"Dnevnik Slovencev v Italiji", + "originalTitle":"Dnevnik Slovencev v Italiji", + "shortDescription":"Dnevnik Slovencev v Italiji je informativna oddaja, v kateri novinarji poročajo predvsem o dnevnih dogodkih med Slovenci v Italiji.", + "longDescription":"Pomembno ogledalo vsakdana, v katerem opozarjajo na težave, s katerimi se soočajo, predstavljajo pa tudi pestro kulturno, športno in družbeno življenje slovenske narodne skupnosti. V oddajo so vključene tudi novice iz matične domovine.", + "type":{ + "id":10, + "name":"Show" + }, + "productionFrom":"1609502400000", + "countries":[ + { + "id":"SI", + "name":"Slovenija" + } + ], + "languages":[ + { + "languageId":2, + "name":"Slovenščina" + } + ], + "genres":[ + { + "id":1000002, + "name":"Informativni" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/sites/tv2go.t-2.net/tv2go.t-2.net.test.js b/sites/tv2go.t-2.net/tv2go.t-2.net.test.js index 23f828397..b54ba8d7b 100644 --- a/sites/tv2go.t-2.net/tv2go.t-2.net.test.js +++ b/sites/tv2go.t-2.net/tv2go.t-2.net.test.js @@ -1,5 +1,7 @@ const { parser, url, request } = require('./tv2go.t-2.net.config.js') const dayjs = require('dayjs') +const fs = require('fs') +const path = require('path') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') dayjs.extend(customParseFormat) @@ -36,8 +38,7 @@ it('can generate valid request data', () => { }) it('can parse response', () => { - const content = - '{"entries":[{"channelId":1000259,"startTimestamp":"1637283000000","endTimestamp":"1637284500000","name":"Dnevnik Slovencev v Italiji","nameSingleLine":"Dnevnik Slovencev v Italiji","description":"Informativni","images":[{"url":"/static/media/img/epg/max_crop/EPG_IMG_2927405.jpg","width":1008,"height":720,"averageColor":[143,147,161]}],"show":{"id":51991133,"title":"Dnevnik Slovencev v Italiji","originalTitle":"Dnevnik Slovencev v Italiji","shortDescription":"Dnevnik Slovencev v Italiji je informativna oddaja, v kateri novinarji poročajo predvsem o dnevnih dogodkih med Slovenci v Italiji.","longDescription":"Pomembno ogledalo vsakdana, v katerem opozarjajo na težave, s katerimi se soočajo, predstavljajo pa tudi pestro kulturno, športno in družbeno življenje slovenske narodne skupnosti. V oddajo so vključene tudi novice iz matične domovine.","type":{"id":10,"name":"Show"},"productionFrom":"1609502400000","countries":[{"id":"SI","name":"Slovenija"}],"languages":[{"languageId":2,"name":"Slovenščina"}],"genres":[{"id":1000002,"name":"Informativni"}]}}]}' + const content = fs.readFileSync(path.join(__dirname, '__data__', 'content.json'), 'utf8') const result = parser({ content, channel }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() diff --git a/sites/tvcesoir.fr/__data__/no_content.html b/sites/tvcesoir.fr/__data__/no_content.html new file mode 100644 index 000000000..6fedfd4c7 --- /dev/null +++ b/sites/tvcesoir.fr/__data__/no_content.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sites/tvcesoir.fr/tvcesoir.fr.test.js b/sites/tvcesoir.fr/tvcesoir.fr.test.js index 73acc873b..dfe0e991e 100644 --- a/sites/tvcesoir.fr/tvcesoir.fr.test.js +++ b/sites/tvcesoir.fr/tvcesoir.fr.test.js @@ -44,7 +44,7 @@ it('can handle empty guide', () => { const result = parser({ date, channel, - content: '' + content: fs.readFileSync(path.resolve(__dirname, './__data__/no_content.html'), 'utf8') }) expect(result).toMatchObject([]) }) diff --git a/sites/tvcubana.icrt.cu/__data__/content.json b/sites/tvcubana.icrt.cu/__data__/content.json new file mode 100644 index 000000000..b3398bc97 --- /dev/null +++ b/sites/tvcubana.icrt.cu/__data__/content.json @@ -0,0 +1,55 @@ +[ + { + "eventId":"6169c2300ad38b0a8d9e3760", + "title":"CARIBE NOTICIAS", + "description":"EMISI\\u00d3N DE CIERRE.", + "eventInitialDate":"2021-11-22T00:00:00", + "eventEndDate":"2021-11-22T00:00:00", + "idFromEprog":"5c096ea5bad1b202541503cf", + "extendedDescription":"", + "transmission":"Estreno", + "pid":"", + "space":"CARIBE NOTICIAS", + "eventStartTime":{ + "value":{ + "ticks":24000000000, + "days":0, + "hours":0, + "milliseconds":0, + "minutes":40, + "seconds":0, + "totalDays":0.027777777777777776, + "totalHours":0.6666666666666666, + "totalMilliseconds":2400000, + "totalMinutes":40, + "totalSeconds":2400 + }, + "hasValue":true + }, + "eventEndTime":{ + "value":{ + "ticks":30000000000, + "days":0, + "hours":0, + "milliseconds":0, + "minutes":50, + "seconds":0, + "totalDays":0.034722222222222224, + "totalHours":0.8333333333333334, + "totalMilliseconds":3000000, + "totalMinutes":50, + "totalSeconds":3000 + }, + "hasValue":true + }, + "eventDuration":"00:10:00", + "channelName":"Cubavisi\\u00f3n", + "eventInitialDateTime":"2021-11-22T00:40:00", + "eventEndDateTime":"2021-11-22T00:50:00", + "isEventWithNegativeDuration":false, + "isEventWithDurationOver24Hrs":false, + "isEventWithTextOverLength":false, + "created":"2021-11-22T10:32:27.476824", + "id":5309687 + } +] \ No newline at end of file diff --git a/sites/tvcubana.icrt.cu/__data__/no_content.html b/sites/tvcubana.icrt.cu/__data__/no_content.html new file mode 100644 index 000000000..03bc0ab4a --- /dev/null +++ b/sites/tvcubana.icrt.cu/__data__/no_content.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sites/tvcubana.icrt.cu/tvcubana.icrt.cu.test.js b/sites/tvcubana.icrt.cu/tvcubana.icrt.cu.test.js index b4854ae13..6741994df 100644 --- a/sites/tvcubana.icrt.cu/tvcubana.icrt.cu.test.js +++ b/sites/tvcubana.icrt.cu/tvcubana.icrt.cu.test.js @@ -1,5 +1,7 @@ const { parser, url } = require('./tvcubana.icrt.cu.config.js') const dayjs = require('dayjs') +const fs = require('fs') +const path = require('path') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') dayjs.extend(customParseFormat) @@ -10,8 +12,9 @@ const channel = { site_id: 'cv', xmltv_id: 'CubavisionNacional.cu' } -const content = - '[{"eventId":"6169c2300ad38b0a8d9e3760","title":"CARIBE NOTICIAS","description":"EMISI\\u00d3N DE CIERRE.","eventInitialDate":"2021-11-22T00:00:00","eventEndDate":"2021-11-22T00:00:00","idFromEprog":"5c096ea5bad1b202541503cf","extendedDescription":"","transmission":"Estreno","pid":"","space":"CARIBE NOTICIAS","eventStartTime":{"value":{"ticks":24000000000,"days":0,"hours":0,"milliseconds":0,"minutes":40,"seconds":0,"totalDays":0.027777777777777776,"totalHours":0.6666666666666666,"totalMilliseconds":2400000,"totalMinutes":40,"totalSeconds":2400},"hasValue":true},"eventEndTime":{"value":{"ticks":30000000000,"days":0,"hours":0,"milliseconds":0,"minutes":50,"seconds":0,"totalDays":0.034722222222222224,"totalHours":0.8333333333333334,"totalMilliseconds":3000000,"totalMinutes":50,"totalSeconds":3000},"hasValue":true},"eventDuration":"00:10:00","channelName":"Cubavisi\\u00f3n","eventInitialDateTime":"2021-11-22T00:40:00","eventEndDateTime":"2021-11-22T00:50:00","isEventWithNegativeDuration":false,"isEventWithDurationOver24Hrs":false,"isEventWithTextOverLength":false,"created":"2021-11-22T10:32:27.476824","id":5309687}]' +let content = fs.readFileSync(path.resolve(__dirname, './__data__/content.json'), {encoding: 'utf8'}) +// in the specific case of this site, the unicode escape sequences are double-escaped +content = content.replace(/\\\\u([0-9a-fA-F]{4})/g, '\\u$1') it('can generate valid url', () => { expect(url({ channel, date })).toBe('https://www.tvcubana.icrt.cu/cartv/cv/lunes.php') @@ -43,8 +46,7 @@ it('can handle empty guide', () => { const result = parser({ date, channel, - content: - '' + content: fs.readFileSync(path.resolve(__dirname, './__data__/no_content.html'), 'utf8') }) expect(result).toMatchObject([]) }) diff --git a/sites/tvguide.myjcom.jp/__data__/content.json b/sites/tvguide.myjcom.jp/__data__/content.json new file mode 100644 index 000000000..1bd1a96ea --- /dev/null +++ b/sites/tvguide.myjcom.jp/__data__/content.json @@ -0,0 +1,34 @@ +{ + "120_200_4_20220114":[ + { + "@search.score":1, + "cid":"120_7305523", + "serviceCode":"200_4", + "channelName":"スターチャンネル1", + "digitalNo":195, + "eventId":"181", + "title":"[5.1]フードロア:タマリンド", + "commentary":"HBO(R)アジア製作。日本の齊藤工などアジアの監督が、各国の食をテーマに描いたアンソロジーシリーズ。(全8話)(19年 シンガポール 56分)", + "attr":[ + "5.1", + "hd", + "cp1" + ], + "sortGenre":"31", + "hasImage":1, + "imgPath":"/monomedia/si/2022/20220114/7305523/image/7743d17b655b8d2274ca58b74f2f095c.jpg", + "isRecommended":null, + "programStart":20220114050000, + "programEnd":20220114060000, + "programDate":20220114, + "programId":568519, + "start_time":"00", + "duration":60, + "top":300, + "end_time":"20220114060000", + "channel_type":"120", + "is_end":false, + "show_remoterec":true + } + ] +} \ No newline at end of file diff --git a/sites/tvguide.myjcom.jp/__data__/no_content.json b/sites/tvguide.myjcom.jp/__data__/no_content.json new file mode 100644 index 000000000..ce916b672 --- /dev/null +++ b/sites/tvguide.myjcom.jp/__data__/no_content.json @@ -0,0 +1 @@ +{"120_200_3_20220114":[]} \ No newline at end of file diff --git a/sites/tvguide.myjcom.jp/tvguide.myjcom.jp.test.js b/sites/tvguide.myjcom.jp/tvguide.myjcom.jp.test.js index 9506861ac..d4edce274 100644 --- a/sites/tvguide.myjcom.jp/tvguide.myjcom.jp.test.js +++ b/sites/tvguide.myjcom.jp/tvguide.myjcom.jp.test.js @@ -1,5 +1,7 @@ const { parser, url } = require('./tvguide.myjcom.jp.config.js') const dayjs = require('dayjs') +const fs = require('fs') +const path = require('path') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') dayjs.extend(customParseFormat) @@ -11,8 +13,7 @@ const channel = { name: 'Star Channel 1', xmltv_id: 'StarChannel1.jp' } -const content = - '{"120_200_4_20220114":[{"@search.score":1,"cid":"120_7305523","serviceCode":"200_4","channelName":"スターチャンネル1","digitalNo":195,"eventId":"181","title":"[5.1]フードロア:タマリンド","commentary":"HBO(R)アジア製作。日本の齊藤工などアジアの監督が、各国の食をテーマに描いたアンソロジーシリーズ。(全8話)(19年 シンガポール 56分)","attr":["5.1","hd","cp1"],"sortGenre":"31","hasImage":1,"imgPath":"/monomedia/si/2022/20220114/7305523/image/7743d17b655b8d2274ca58b74f2f095c.jpg","isRecommended":null,"programStart":20220114050000,"programEnd":20220114060000,"programDate":20220114,"programId":568519,"start_time":"00","duration":60,"top":300,"end_time":"20220114060000","channel_type":"120","is_end":false,"show_remoterec":true}]}' +const content = fs.readFileSync(path.resolve(__dirname, './__data__/content.json'), 'utf8') it('can generate valid url', () => { const result = url({ date, channel }) @@ -44,7 +45,7 @@ it('can handle empty guide', () => { const result = parser({ date, channel, - content: '{"120_200_3_20220114":[]}' + content: fs.readFileSync(path.resolve(__dirname, './__data__/no_content.json'), 'utf8') }) expect(result).toMatchObject([]) }) diff --git a/sites/tvheute.at/tvheute.at.test.js b/sites/tvheute.at/tvheute.at.test.js index e31587ca4..e13afd210 100644 --- a/sites/tvheute.at/tvheute.at.test.js +++ b/sites/tvheute.at/tvheute.at.test.js @@ -1,5 +1,6 @@ const { parser, url } = require('./tvheute.at.config.js') const dayjs = require('dayjs') +const path = require('path') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') const { readFileSync } = require('fs') @@ -16,7 +17,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - expect(parser({ date, channel, content: readFileSync('./__data__/content.html', 'utf8') })).toMatchObject([ + expect(parser({ date, channel, content: readFileSync(path.resolve(__dirname, './__data__/content.html'), 'utf8') })).toMatchObject([ { start: '2021-11-08T05:00:00.000Z', stop: '2021-11-08T05:10:00.000Z', @@ -38,7 +39,7 @@ it('can handle empty guide', () => { const result = parser({ date, channel, - content: readFileSync('./__data__/no_content.html', 'utf8') + content: readFileSync(path.resolve(__dirname, './__data__/no_content.html'), 'utf8') }) expect(result).toMatchObject([]) }) From 2f5d209f5f5ca0d941410ffc4b349afccb2f1386 Mon Sep 17 00:00:00 2001 From: theofficialomega <30985701+BellezaEmporium@users.noreply.github.com> Date: Sat, 12 Jul 2025 13:44:19 +0200 Subject: [PATCH 04/32] rewrite mail.ru config, recreate UniqBy in native JS --- sites/tv.mail.ru/__data__/content.json | 65 +++++++++++++++++++++++ sites/tv.mail.ru/__data__/no_content.json | 23 ++++++++ sites/tv.mail.ru/tv.mail.ru.config.js | 19 +++++-- sites/tv.mail.ru/tv.mail.ru.test.js | 8 +-- 4 files changed, 108 insertions(+), 7 deletions(-) create mode 100644 sites/tv.mail.ru/__data__/content.json create mode 100644 sites/tv.mail.ru/__data__/no_content.json diff --git a/sites/tv.mail.ru/__data__/content.json b/sites/tv.mail.ru/__data__/content.json new file mode 100644 index 000000000..616c52bac --- /dev/null +++ b/sites/tv.mail.ru/__data__/content.json @@ -0,0 +1,65 @@ +{ + "status":"OK", + "schedule":[ + { + "channel":{ + "name":"21TV", + "pic_url":"https://resizer.mail.ru/p/1234c5ac-c19c-5cf2-9c6a-fc0efca920ac/AAACm2w9aDpGPSWXzsH7PBq2X3I6pbxqmrj-yeuVppAKyyBHXE_dH_7pHQ2rOavyKiC4iHIWTab9SeKo7pKgr71lqVA.png", + "pic_url_128":"https://resizer.mail.ru/p/1234c5ac-c19c-5cf2-9c6a-fc0efca920ac/AAACwjJ45j9sTP8fcjPJnJ4xk5e_ILr5iXwjLMhWhzlVnIJkrtT42vEp9walcgpXRKDq9KFoliEPR0xI-LEh96C_izY.png", + "pic_url_64":"https://resizer.mail.ru/p/1234c5ac-c19c-5cf2-9c6a-fc0efca920ac/dpr:200/AAACm2w9aDpGPSWXzsH7PBq2X3I6pbxqmrj-yeuVppAKyyBHXE_dH_7pHQ2rOavyKiC4iHIWTab9SeKo7pKgr71lqVA.png" + }, + "event":{ + "current":[ + { + "channel_id":"2785", + "name":"Պրոֆեսիոնալները", + "category_id":8, + "episode_title":"", + "url":"/moskva/channel/2785/173593246/", + "id":"173593246", + "start":"02:40", + "episode_num":0 + }, + { + "channel_id":"2785", + "name":"Նոնստոպ․ Տեսահոլովակներ", + "category_id":23, + "episode_title":"", + "url":"/moskva/channel/2785/173593142/", + "id":"173593142", + "start":"03:25", + "episode_num":0 + } + ], + "past":[ + { + "channel_id":"2785", + "name":"Նոնստոպ․ Տեսահոլովակներ", + "category_id":23, + "episode_title":"", + "url":"/moskva/channel/2785/173593328/", + "id":"173593328", + "start":"23:35", + "episode_num":0 + }, + { + "channel_id":"2785", + "video":{ + "currency":"RUB", + "price_min":"249.00", + "price_txt":"249 р." + }, + "name":"Վերջին թագավորությունը", + "category_id":2, + "episode_title":"", + "url":"/moskva/channel/2785/173593318/", + "id":"173593318", + "start":"01:40", + "our_event_id":"890224", + "episode_num":0 + } + ] + } + } + ] +} \ No newline at end of file diff --git a/sites/tv.mail.ru/__data__/no_content.json b/sites/tv.mail.ru/__data__/no_content.json new file mode 100644 index 000000000..67fcc17be --- /dev/null +++ b/sites/tv.mail.ru/__data__/no_content.json @@ -0,0 +1,23 @@ +{ + "status":"OK", + "current_ts":1637788593, + "form":{ + "values":[ + + ] + }, + "current_offset":10800, + "schedule":[ + { + "channel":null, + "event":{ + "current":[ + + ], + "past":[ + + ] + } + } + ] +} \ No newline at end of file diff --git a/sites/tv.mail.ru/tv.mail.ru.config.js b/sites/tv.mail.ru/tv.mail.ru.config.js index 92c196232..56b12bd10 100644 --- a/sites/tv.mail.ru/tv.mail.ru.config.js +++ b/sites/tv.mail.ru/tv.mail.ru.config.js @@ -1,6 +1,21 @@ const { DateTime } = require('luxon') const axios = require('axios') +// Remove the big lodash dependency by implementing a simple uniqBy function +// Complexity = O(n) +const uniqBy = (arr, predicate) => { + 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 = { site: 'tv.mail.ru', days: 2, @@ -35,8 +50,6 @@ module.exports = { return programs }, async channels() { - const _ = require('lodash') - const regions = [5506, 1096, 1125, 285] let channels = [] @@ -64,7 +77,7 @@ module.exports = { } } - return _.uniqBy(channels, 'site_id') + return uniqBy(channels, 'site_id') } } diff --git a/sites/tv.mail.ru/tv.mail.ru.test.js b/sites/tv.mail.ru/tv.mail.ru.test.js index d40d3aff0..49f76081a 100644 --- a/sites/tv.mail.ru/tv.mail.ru.test.js +++ b/sites/tv.mail.ru/tv.mail.ru.test.js @@ -2,6 +2,8 @@ const { parser, url } = require('./tv.mail.ru.config.js') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') +const fs = require('fs') +const path = require('path') dayjs.extend(customParseFormat) dayjs.extend(utc) @@ -10,8 +12,7 @@ const channel = { site_id: '2785', xmltv_id: '21TV.am' } -const content = - '{"status":"OK","schedule":[{"channel":{"name":"21TV","pic_url":"https://resizer.mail.ru/p/1234c5ac-c19c-5cf2-9c6a-fc0efca920ac/AAACm2w9aDpGPSWXzsH7PBq2X3I6pbxqmrj-yeuVppAKyyBHXE_dH_7pHQ2rOavyKiC4iHIWTab9SeKo7pKgr71lqVA.png","pic_url_128":"https://resizer.mail.ru/p/1234c5ac-c19c-5cf2-9c6a-fc0efca920ac/AAACwjJ45j9sTP8fcjPJnJ4xk5e_ILr5iXwjLMhWhzlVnIJkrtT42vEp9walcgpXRKDq9KFoliEPR0xI-LEh96C_izY.png","pic_url_64":"https://resizer.mail.ru/p/1234c5ac-c19c-5cf2-9c6a-fc0efca920ac/dpr:200/AAACm2w9aDpGPSWXzsH7PBq2X3I6pbxqmrj-yeuVppAKyyBHXE_dH_7pHQ2rOavyKiC4iHIWTab9SeKo7pKgr71lqVA.png"},"event":{"current":[{"channel_id":"2785","name":"Պրոֆեսիոնալները","category_id":8,"episode_title":"","url":"/moskva/channel/2785/173593246/","id":"173593246","start":"02:40","episode_num":0},{"channel_id":"2785","name":"Նոնստոպ․ Տեսահոլովակներ","category_id":23,"episode_title":"","url":"/moskva/channel/2785/173593142/","id":"173593142","start":"03:25","episode_num":0}],"past":[{"channel_id":"2785","name":"Նոնստոպ․ Տեսահոլովակներ","category_id":23,"episode_title":"","url":"/moskva/channel/2785/173593328/","id":"173593328","start":"23:35","episode_num":0},{"channel_id":"2785","video":{"currency":"RUB","price_min":"249.00","price_txt":"249 р."},"name":"Վերջին թագավորությունը","category_id":2,"episode_title":"","url":"/moskva/channel/2785/173593318/","id":"173593318","start":"01:40","our_event_id":"890224","episode_num":0}]}}]}' +const content = fs.readFileSync(path.join(__dirname, '__data__', 'content.json'), 'utf8') it('can generate valid url', () => { expect(url({ channel, date })).toBe( @@ -70,8 +71,7 @@ it('can handle empty guide', () => { const result = parser({ date, channel, - content: - '{"status":"OK","current_ts":1637788593,"form":{"values":[]},"current_offset":10800,"schedule":[{"channel":null,"event":{"current":[],"past":[]}}]}' + content: fs.readFileSync(path.join(__dirname, '__data__', 'no_content.json'), 'utf8') }) expect(result).toMatchObject([]) }) From d8e4372f223af4bae6c1422b1402798f3e4e061e Mon Sep 17 00:00:00 2001 From: theofficialomega <30985701+BellezaEmporium@users.noreply.github.com> Date: Wed, 16 Jul 2025 12:37:34 +0200 Subject: [PATCH 05/32] continue uniformizing + ditch lodash for native JS methods. --- scripts/core/configLoader.ts | 4 +- scripts/functions/functions.ts | 43 +++++++++++++++++++ sites/derana.lk/derana.lk.config.js | 6 ++- sites/dstv.com/dstv.com.config.js | 6 +-- sites/tv.blue.ch/__data__/content.json | 1 + .../__data__/content_invalid_siteid.json | 1 + .../__data__/content_without_image.json | 1 + sites/tv.blue.ch/__data__/no_content.json | 1 + sites/tv.blue.ch/tv.blue.ch.test.js | 14 +++--- sites/tv.lv/__data__/no_content.json | 1 + sites/tv.lv/tv.lv.test.js | 3 +- sites/tv.magenta.at/tv.magenta.at.config.js | 4 +- sites/tv.mail.ru/tv.mail.ru.config.js | 13 +----- 13 files changed, 67 insertions(+), 31 deletions(-) create mode 100644 scripts/functions/functions.ts create mode 100644 sites/tv.blue.ch/__data__/content.json create mode 100644 sites/tv.blue.ch/__data__/content_invalid_siteid.json create mode 100644 sites/tv.blue.ch/__data__/content_without_image.json create mode 100644 sites/tv.blue.ch/__data__/no_content.json create mode 100644 sites/tv.lv/__data__/no_content.json diff --git a/scripts/core/configLoader.ts b/scripts/core/configLoader.ts index 1beb3703f..93e1e5974 100644 --- a/scripts/core/configLoader.ts +++ b/scripts/core/configLoader.ts @@ -1,5 +1,5 @@ import { SiteConfig } from 'epg-grabber' -import _ from 'lodash' +import { deepMerge } from '../functions/functions' import { pathToFileURL } from 'url' export class ConfigLoader { @@ -28,6 +28,6 @@ export class ConfigLoader { channels: undefined } - return _.merge(defaultConfig, config) + return deepMerge(defaultConfig, config) as SiteConfig } } diff --git a/scripts/functions/functions.ts b/scripts/functions/functions.ts new file mode 100644 index 000000000..776179ee9 --- /dev/null +++ b/scripts/functions/functions.ts @@ -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 = (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} 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 => + 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) + +/** + * 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 = (key: keyof T): ((a: T, b: T) => number) => { + return (a: T, b: T) => (a[key] > b[key]) ? 1 : ((b[key] > a[key]) ? -1 : 0) +} \ No newline at end of file diff --git a/sites/derana.lk/derana.lk.config.js b/sites/derana.lk/derana.lk.config.js index 4d43be08b..c047855cf 100644 --- a/sites/derana.lk/derana.lk.config.js +++ b/sites/derana.lk/derana.lk.config.js @@ -3,7 +3,9 @@ const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') const parseDuration = require('parse-duration').default const timezone = require('dayjs/plugin/timezone') -const _ = require('lodash') + +// importing custom function sortBy +const sortBy = require('../functions/functions') dayjs.extend(customParseFormat) dayjs.extend(utc) @@ -28,7 +30,7 @@ module.exports = { } }) - return _.sortBy(programs, p => p.start) + return programs.concat().sort(sortBy('start')) } } diff --git a/sites/dstv.com/dstv.com.config.js b/sites/dstv.com/dstv.com.config.js index 8e526ab57..eb04a7663 100644 --- a/sites/dstv.com/dstv.com.config.js +++ b/sites/dstv.com/dstv.com.config.js @@ -3,6 +3,7 @@ const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const timezone = require('dayjs/plugin/timezone') const customParseFormat = require('dayjs/plugin/customParseFormat') +const { uniqBy } = require('../../functions/functions') dayjs.extend(utc) dayjs.extend(timezone) @@ -45,8 +46,7 @@ module.exports = { return programs }, async channels({ country }) { - const _ = require('lodash') - + const countries = { ao: 'ago', bj: 'ben', @@ -114,7 +114,7 @@ module.exports = { }) }) - return _.uniqBy(channels, 'site_id') + return uniqBy(channels, 'site_id') } } diff --git a/sites/tv.blue.ch/__data__/content.json b/sites/tv.blue.ch/__data__/content.json new file mode 100644 index 000000000..682d337ee --- /dev/null +++ b/sites/tv.blue.ch/__data__/content.json @@ -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"}]}]}}}]}} \ No newline at end of file diff --git a/sites/tv.blue.ch/__data__/content_invalid_siteid.json b/sites/tv.blue.ch/__data__/content_invalid_siteid.json new file mode 100644 index 000000000..974a99300 --- /dev/null +++ b/sites/tv.blue.ch/__data__/content_invalid_siteid.json @@ -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":[]}} \ No newline at end of file diff --git a/sites/tv.blue.ch/__data__/content_without_image.json b/sites/tv.blue.ch/__data__/content_without_image.json new file mode 100644 index 000000000..8e698d464 --- /dev/null +++ b/sites/tv.blue.ch/__data__/content_without_image.json @@ -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"}]}]}}}]}} \ No newline at end of file diff --git a/sites/tv.blue.ch/__data__/no_content.json b/sites/tv.blue.ch/__data__/no_content.json new file mode 100644 index 000000000..c4df608da --- /dev/null +++ b/sites/tv.blue.ch/__data__/no_content.json @@ -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"}}}]}} \ No newline at end of file diff --git a/sites/tv.blue.ch/tv.blue.ch.test.js b/sites/tv.blue.ch/tv.blue.ch.test.js index e01ec2864..539b2485c 100644 --- a/sites/tv.blue.ch/tv.blue.ch.test.js +++ b/sites/tv.blue.ch/tv.blue.ch.test.js @@ -1,6 +1,8 @@ const { parser, url } = require('./tv.blue.ch.config.js') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') +const fs = require('fs') +const path = require('path') const customParseFormat = require('dayjs/plugin/customParseFormat') dayjs.extend(customParseFormat) dayjs.extend(utc) @@ -18,8 +20,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - const content = - '{"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 content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const result = parser({ content }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() @@ -40,8 +41,7 @@ it('can parse response', () => { }) it('can parse response without image', () => { - const content = - '{"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 content = fs.readFileSync(path.resolve(__dirname, '__data__/content_without_image.json')) const result = parser({ content }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() @@ -59,16 +59,14 @@ it('can parse response without image', () => { it('can handle wrong site id', () => { const result = parser({ - content: - '{"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":[]}}' + content: fs.readFileSync(path.resolve(__dirname, '__data__/content_invalid_siteid.json')) }) expect(result).toMatchObject([]) }) it('can handle empty guide', () => { const result = parser({ - content: - '{"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"}}}]}}' + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) }) expect(result).toMatchObject([]) }) diff --git a/sites/tv.lv/__data__/no_content.json b/sites/tv.lv/__data__/no_content.json new file mode 100644 index 000000000..60a75b515 --- /dev/null +++ b/sites/tv.lv/__data__/no_content.json @@ -0,0 +1 @@ +{"schedule":{"programme":[],"dayName":"Sestdiena","date":"30.11.2024"},"diff":368,"nextDate":"01-12-2024","previousDate":"29-11-2024","current_timestamp":1701194084} \ No newline at end of file diff --git a/sites/tv.lv/tv.lv.test.js b/sites/tv.lv/tv.lv.test.js index e406789ac..2a53efd2c 100644 --- a/sites/tv.lv/tv.lv.test.js +++ b/sites/tv.lv/tv.lv.test.js @@ -48,8 +48,7 @@ it('can parse response', () => { it('can handle empty guide', () => { const results = parser({ - content: - '{"schedule":{"programme":[],"dayName":"Sestdiena","date":"30.11.2024"},"diff":368,"nextDate":"01-12-2024","previousDate":"29-11-2024","current_timestamp":1701194084}' + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) }) expect(results).toMatchObject([]) }) diff --git a/sites/tv.magenta.at/tv.magenta.at.config.js b/sites/tv.magenta.at/tv.magenta.at.config.js index b56c02e97..aebd8c0df 100644 --- a/sites/tv.magenta.at/tv.magenta.at.config.js +++ b/sites/tv.magenta.at/tv.magenta.at.config.js @@ -7,8 +7,8 @@ const API_ENDPOINT = 'https://tv-at-prod.yo-digital.com/at-bifrost' const headers = { 'Device-Id': crypto.randomUUID(), app_key: 'CTnKA63ruKM0JM1doxAXwwyQLLmQiEiy', - app_version: '02.0.830', - 'X-User-Agent': 'web|web|Firefox-120|02.0.830|1', + app_version: '02.0.1260', + 'X-User-Agent': 'web|web|Firefox-120|02.0.1260|1', 'x-request-tracking-id': crypto.randomUUID() } diff --git a/sites/tv.mail.ru/tv.mail.ru.config.js b/sites/tv.mail.ru/tv.mail.ru.config.js index 56b12bd10..4fc3098c6 100644 --- a/sites/tv.mail.ru/tv.mail.ru.config.js +++ b/sites/tv.mail.ru/tv.mail.ru.config.js @@ -3,18 +3,7 @@ const axios = require('axios') // Remove the big lodash dependency by implementing a simple uniqBy function // Complexity = O(n) -const uniqBy = (arr, predicate) => { - 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()] -} +const uniqBy = (arr, fn) => [...new Map(arr.map(x => [fn(x), x])).values()] module.exports = { site: 'tv.mail.ru', From 6b3e17861a20a895e969eee902cbefc56582c763 Mon Sep 17 00:00:00 2001 From: theofficialomega <30985701+BellezaEmporium@users.noreply.github.com> Date: Wed, 16 Jul 2025 15:19:12 +0200 Subject: [PATCH 06/32] continuing, WIP --- scripts/core/configLoader.ts | 2 +- scripts/functions/functions.ts | 3 +-- scripts/functions/index.ts | 1 + sites/derana.lk/derana.lk.config.js | 4 +--- sites/dstv.com/dstv.com.config.js | 2 +- sites/guida.tv/guida.tv.config.js | 7 +++---- sites/ontvtonight.com/ontvtonight.com.config.js | 7 +++---- .../streamingtvguides.com/streamingtvguides.com.config.js | 4 ++-- sites/tv.mail.ru/tv.mail.ru.config.js | 5 +---- 9 files changed, 14 insertions(+), 21 deletions(-) create mode 100644 scripts/functions/index.ts diff --git a/scripts/core/configLoader.ts b/scripts/core/configLoader.ts index 93e1e5974..ae9971bf5 100644 --- a/scripts/core/configLoader.ts +++ b/scripts/core/configLoader.ts @@ -1,5 +1,5 @@ import { SiteConfig } from 'epg-grabber' -import { deepMerge } from '../functions/functions' +import { deepMerge } from '../functions' import { pathToFileURL } from 'url' export class ConfigLoader { diff --git a/scripts/functions/functions.ts b/scripts/functions/functions.ts index 776179ee9..fcf498a36 100644 --- a/scripts/functions/functions.ts +++ b/scripts/functions/functions.ts @@ -11,8 +11,7 @@ * const uniqueItems = uniqBy(items, item => item.id); * // uniqueItems will be [{ id: 1, name: 'A' }, { id: 2, name: 'B' }] */ -export const uniqBy = (arr: T[], fn: (item: T) => K): T[] => - Array.from(new Map(arr.map(item => [fn(item), item])).values()) +export const uniqBy = (arr: T[], fn: (item: T) => K): T[] => [...new Map(arr.map(x => [fn(x), x])).values()] /** * Recursively merges multiple objects into a single object. diff --git a/scripts/functions/index.ts b/scripts/functions/index.ts new file mode 100644 index 000000000..8c9cb76ad --- /dev/null +++ b/scripts/functions/index.ts @@ -0,0 +1 @@ +export * from './functions' \ No newline at end of file diff --git a/sites/derana.lk/derana.lk.config.js b/sites/derana.lk/derana.lk.config.js index c047855cf..e8adcea22 100644 --- a/sites/derana.lk/derana.lk.config.js +++ b/sites/derana.lk/derana.lk.config.js @@ -3,9 +3,7 @@ const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') const parseDuration = require('parse-duration').default const timezone = require('dayjs/plugin/timezone') - -// importing custom function sortBy -const sortBy = require('../functions/functions') +const { sortBy } = require('../../scripts/functions') dayjs.extend(customParseFormat) dayjs.extend(utc) diff --git a/sites/dstv.com/dstv.com.config.js b/sites/dstv.com/dstv.com.config.js index eb04a7663..6f1ce2470 100644 --- a/sites/dstv.com/dstv.com.config.js +++ b/sites/dstv.com/dstv.com.config.js @@ -3,7 +3,7 @@ const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const timezone = require('dayjs/plugin/timezone') const customParseFormat = require('dayjs/plugin/customParseFormat') -const { uniqBy } = require('../../functions/functions') +const { uniqBy } = require('../../scripts/functions') dayjs.extend(utc) dayjs.extend(timezone) diff --git a/sites/guida.tv/guida.tv.config.js b/sites/guida.tv/guida.tv.config.js index 0c14a1cd7..a5e7feb3d 100644 --- a/sites/guida.tv/guida.tv.config.js +++ b/sites/guida.tv/guida.tv.config.js @@ -1,8 +1,10 @@ +const axios = require('axios') const cheerio = require('cheerio') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const timezone = require('dayjs/plugin/timezone') const customParseFormat = require('dayjs/plugin/customParseFormat') +const { uniqBy } = require('../../scripts/functions') dayjs.extend(utc) dayjs.extend(timezone) @@ -41,9 +43,6 @@ module.exports = { return programs }, async channels() { - const axios = require('axios') - const _ = require('lodash') - const providers = ['-1', '-2', '-3'] const channels = [] @@ -77,7 +76,7 @@ module.exports = { }) } - return _.uniqBy(channels, 'site_id') + return uniqBy(channels, 'site_id') } } diff --git a/sites/ontvtonight.com/ontvtonight.com.config.js b/sites/ontvtonight.com/ontvtonight.com.config.js index 20b6efd0a..0595d2081 100644 --- a/sites/ontvtonight.com/ontvtonight.com.config.js +++ b/sites/ontvtonight.com/ontvtonight.com.config.js @@ -1,8 +1,10 @@ +const axios = require('axios') const cheerio = require('cheerio') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const timezone = require('dayjs/plugin/timezone') const customParseFormat = require('dayjs/plugin/customParseFormat') +const { uniqBy } = require('../../scripts/functions') dayjs.extend(utc) dayjs.extend(timezone) @@ -45,9 +47,6 @@ module.exports = { return programs }, async channels({ country }) { - const axios = require('axios') - const _ = require('lodash') - const providers = { au: ['o', 'a'], ca: [ @@ -147,7 +146,7 @@ module.exports = { } } - return _.uniqBy(channels, 'site_id') + return uniqBy(channels, 'site_id') } } diff --git a/sites/streamingtvguides.com/streamingtvguides.com.config.js b/sites/streamingtvguides.com/streamingtvguides.com.config.js index 440ca9e1d..811a8b409 100644 --- a/sites/streamingtvguides.com/streamingtvguides.com.config.js +++ b/sites/streamingtvguides.com/streamingtvguides.com.config.js @@ -2,7 +2,7 @@ const cheerio = require('cheerio') const dayjs = require('dayjs') const customParseFormat = require('dayjs/plugin/customParseFormat') const timezone = require('dayjs/plugin/timezone') -const _ = require('lodash') +const { sortBy, uniqBy } = require('../../scripts/functions') dayjs.extend(customParseFormat) dayjs.extend(timezone) @@ -29,7 +29,7 @@ module.exports = { }) }) - programs = _.orderBy(_.uniqBy(programs, 'start'), 'start') + programs = sortBy(uniqBy(programs, p => p.start), 'start') return programs }, diff --git a/sites/tv.mail.ru/tv.mail.ru.config.js b/sites/tv.mail.ru/tv.mail.ru.config.js index 4fc3098c6..46c11d41a 100644 --- a/sites/tv.mail.ru/tv.mail.ru.config.js +++ b/sites/tv.mail.ru/tv.mail.ru.config.js @@ -1,9 +1,6 @@ const { DateTime } = require('luxon') const axios = require('axios') - -// Remove the big lodash dependency by implementing a simple uniqBy function -// Complexity = O(n) -const uniqBy = (arr, fn) => [...new Map(arr.map(x => [fn(x), x])).values()] +const { uniqBy } = require('../../scripts/functions') module.exports = { site: 'tv.mail.ru', From 5e953b6955118d6b860d1039455c1c4158c3827d Mon Sep 17 00:00:00 2001 From: theofficialomega <30985701+BellezaEmporium@users.noreply.github.com> Date: Fri, 18 Jul 2025 23:49:04 +0200 Subject: [PATCH 07/32] continue cleaning + simplify mtel.ba (right now broken EPG on backend) --- package-lock.json | 19 ++++ package.json | 1 + scripts/core/configLoader.ts | 3 +- scripts/functions/functions.ts | 89 ++++++++++++------- scripts/models/guide.ts | 4 +- sites/derana.lk/derana.lk.config.js | 2 +- .../mojmaxtv.hrvatskitelekom.hr.config.js | 4 +- sites/mtel.ba/mtel.ba.config.js | 18 ++-- sites/mtel.ba/mtel.ba.test.js | 2 +- sites/reportv.com.ar/reportv.com.ar.config.js | 4 +- .../streamingtvguides.com.config.js | 2 +- 11 files changed, 95 insertions(+), 53 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2384d4b7d..d80c4063f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -81,6 +81,7 @@ "tsx": "^4.20.3", "typescript": "^5.8.3", "unzipit": "^1.4.3", + "uuid": "^11.1.0", "wildcard-match": "^5.1.4" } }, @@ -11418,6 +11419,19 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, "node_modules/uzip-module": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/uzip-module/-/uzip-module-1.0.3.tgz", @@ -19318,6 +19332,11 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, + "uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==" + }, "uzip-module": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/uzip-module/-/uzip-module-1.0.3.tgz", diff --git a/package.json b/package.json index 4315f72ce..adfadf145 100644 --- a/package.json +++ b/package.json @@ -110,6 +110,7 @@ "tsx": "^4.20.3", "typescript": "^5.8.3", "unzipit": "^1.4.3", + "uuid": "^11.1.0", "wildcard-match": "^5.1.4" }, "packageManager": "yarn@4.9.2" diff --git a/scripts/core/configLoader.ts b/scripts/core/configLoader.ts index ae9971bf5..c5f05ad22 100644 --- a/scripts/core/configLoader.ts +++ b/scripts/core/configLoader.ts @@ -1,5 +1,4 @@ import { SiteConfig } from 'epg-grabber' -import { deepMerge } from '../functions' import { pathToFileURL } from 'url' export class ConfigLoader { @@ -28,6 +27,6 @@ export class ConfigLoader { channels: undefined } - return deepMerge(defaultConfig, config) as SiteConfig + return { ...defaultConfig, ...config } as SiteConfig } } diff --git a/scripts/functions/functions.ts b/scripts/functions/functions.ts index fcf498a36..346c5c94c 100644 --- a/scripts/functions/functions.ts +++ b/scripts/functions/functions.ts @@ -1,42 +1,65 @@ -// 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 = (arr: T[], fn: (item: T) => K): T[] => [...new Map(arr.map(x => [fn(x), x])).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. + * Sorts an array by the result of running each element through an iteratee function. + * Creates a shallow copy of the array before sorting to avoid mutating the original. * - * @param {...object[]} a - An array of objects to be merged - * @returns {Record} A new object containing all merged properties + * @param {Array} arr - The array to sort + * @param {Function} fn - The iteratee function to compute sort values + * @returns {Array} A new sorted array * * @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 } + * const users = [{name: 'john', age: 30}, {name: 'jane', age: 25}]; + * sortBy(users, x => x.age); // [{name: 'jane', age: 25}, {name: 'john', age: 30}] */ -export const deepMerge = (...a: (object)[]): Record => - 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) +export const sortBy = (arr: T[], fn: (item: T) => number | string): T[] => [...arr].sort((a, b) => fn(a) > fn(b) ? 1 : -1) /** - * Sort an array of objects by a specific key. + * Sorts an array by multiple criteria with customizable sort orders. + * Supports ascending (default) and descending order for each criterion. * - * @param {string} key - The key to sort by - * @returns {function} A comparison function for sorting + * @param {Array} arr - The array to sort + * @param {Array} fns - Array of iteratee functions to compute sort values + * @param {Array} orders - Array of sort orders ('asc' or 'desc'), defaults to all 'asc' + * @returns {Array} A new sorted array + * + * @example + * const users = [{name: 'john', age: 30}, {name: 'jane', age: 25}, {name: 'bob', age: 30}]; + * orderBy(users, [x => x.age, x => x.name], ['desc', 'asc']); + * // [{name: 'bob', age: 30}, {name: 'john', age: 30}, {name: 'jane', age: 25}] */ -export const sortBy = (key: keyof T): ((a: T, b: T) => number) => { - return (a: T, b: T) => (a[key] > b[key]) ? 1 : ((b[key] > a[key]) ? -1 : 0) -} \ No newline at end of file +export const orderBy = (arr: Array, fns: Array<(item: unknown) => string | number>, orders: Array = []): Array => [...arr].sort((a, b) => + fns.reduce((acc, fn, i) => + acc || ((orders[i] === 'desc' ? fn(b) > fn(a) : fn(a) > fn(b)) ? 1 : fn(a) === fn(b) ? 0 : -1), 0) +) + +/** + * Creates a duplicate-free version of an array using an iteratee function to generate + * the criterion by which uniqueness is computed. Only the first occurrence of each + * element is kept. + * + * @param {Array} arr - The array to inspect + * @param {Function} fn - The iteratee function to compute uniqueness criterion + * @returns {Array} A new duplicate-free array + * + * @example + * const users = [{id: 1, name: 'john'}, {id: 2, name: 'jane'}, {id: 1, name: 'john'}]; + * uniqBy(users, x => x.id); // [{id: 1, name: 'john'}, {id: 2, name: 'jane'}] + */ +export const uniqBy = (arr: T[], fn: (item: T) => unknown): T[] => arr.filter((item, index) => arr.findIndex(x => fn(x) === fn(item)) === index) + +/** + * Converts a string to start case (capitalizes the first letter of each word). + * Handles camelCase, snake_case, kebab-case, and regular spaces. + * + * @param {string} str - The string to convert + * @returns {string} The start case string + * + * @example + * startCase('hello_world'); // "Hello World" + * startCase('helloWorld'); // "Hello World" + * startCase('hello-world'); // "Hello World" + * startCase('hello world'); // "Hello World" + */ +export const startCase = (str: string): string => str + .replace(/([a-z])([A-Z])/g, '$1 $2') // Split camelCase + .replace(/[_-]/g, ' ') // Replace underscores and hyphens with spaces + .replace(/\b\w/g, c => c.toUpperCase()) // Capitalize first letter of each word \ No newline at end of file diff --git a/scripts/models/guide.ts b/scripts/models/guide.ts index 349d6d5cc..a7a4359f8 100644 --- a/scripts/models/guide.ts +++ b/scripts/models/guide.ts @@ -1,5 +1,5 @@ import type { GuideData } from '../types/guide' -import { uniqueId } from 'lodash' +import { v4 as uuidv4 } from 'uuid' export class Guide { channelId?: string @@ -21,7 +21,7 @@ export class Guide { } getUUID(): string { - if (!this.getStreamId() || !this.siteId) return uniqueId() + if (!this.getStreamId() || !this.siteId) return uuidv4() return this.getStreamId() + this.siteId } diff --git a/sites/derana.lk/derana.lk.config.js b/sites/derana.lk/derana.lk.config.js index e8adcea22..24ec4ae09 100644 --- a/sites/derana.lk/derana.lk.config.js +++ b/sites/derana.lk/derana.lk.config.js @@ -28,7 +28,7 @@ module.exports = { } }) - return programs.concat().sort(sortBy('start')) + return sortBy(programs, p => p.start.valueOf()) } } diff --git a/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js b/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js index db783843c..8aff9e1fe 100644 --- a/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js +++ b/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js @@ -1,8 +1,8 @@ const doFetch = require('@ntlab/sfetch') const axios = require('axios') const dayjs = require('dayjs') -const _ = require('lodash') const crypto = require('crypto') +const { sortBy } = require('../../scripts/functions') // API Configuration Constants const NATCO_CODE = 'hr' @@ -86,7 +86,7 @@ module.exports = { } }) - items = _.sortBy(items, i => dayjs(i.start_time).valueOf()) + items = sortBy(items, i => dayjs(i.start_time).valueOf()) // Fetch program details for each item const programs = [] diff --git a/sites/mtel.ba/mtel.ba.config.js b/sites/mtel.ba/mtel.ba.config.js index d5f6f2259..6f84f5cf9 100644 --- a/sites/mtel.ba/mtel.ba.config.js +++ b/sites/mtel.ba/mtel.ba.config.js @@ -1,9 +1,9 @@ -const _ = require('lodash') const doFetch = require('@ntlab/sfetch') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const timezone = require('dayjs/plugin/timezone') const customParseFormat = require('dayjs/plugin/customParseFormat') +const { sortBy } = require('../../scripts/functions') dayjs.extend(utc) dayjs.extend(timezone) @@ -15,7 +15,7 @@ module.exports = { url({ channel, date }) { const [platform] = channel.site_id.split('#') - return `https://mtel.ba/hybris/ecommerce/b2c/v1/products/channels/epg?platform=tv-${platform}¤tPage=0&pageSize=1000&date=${date.format( + return `https://mtel.ba/hybris/ecommerce/b2c/v1/products/channels/epg?platform=tv-${platform}&pageSize=999&date=${date.format( 'YYYY-MM-DD' )}` }, @@ -31,7 +31,6 @@ module.exports = { let programs = [] const items = parseItems(content, channel) items.forEach(item => { - if (item.title === 'Nema informacija o programu') return programs.push({ title: item.title, description: item.description, @@ -46,14 +45,14 @@ module.exports = { }, async channels({ platform = 'msat' }) { const platforms = { - msat: 'https://mtel.ba/hybris/ecommerce/b2c/v1/products/channels/search?pageSize=100¤tPage=&query=:relevantno:tv-kategorija:tv-msat', - iptv: 'https://mtel.ba/hybris/ecommerce/b2c/v1/products/channels/search?pageSize=100¤tPage=&query=:relevantno:tv-kategorija:tv-iptv:tv-iptv-paket:Svi+kanali' + msat: 'https://mtel.ba/hybris/ecommerce/b2c/v1/products/channels/search?pageSize=999&query=:relevantno:tv-kategorija:tv-msat', + iptv: 'https://mtel.ba/hybris/ecommerce/b2c/v1/products/channels/search?pageSize=999&query=:relevantno:tv-kategorija:tv-iptv' } const queue = [ { platform, - url: platforms[platform].replace('', 0) + url: platforms[platform] } ] @@ -62,7 +61,7 @@ module.exports = { if (data && data.pagination.currentPage < data.pagination.totalPages) { queue.push({ platform: req.platform, - url: platforms[req.platform].replace('', ++data.pagination.currentPage) + url: platforms[req.platform] }) } @@ -102,8 +101,9 @@ function parseItems(content, channel) { const [, channelId] = channel.site_id.split('#') const channelData = data.products.find(channel => channel.code === channelId) if (!channelData || !Array.isArray(channelData.programs)) return [] - - return _.sortBy(channelData.programs, p => parseStart(p).valueOf()) + // filter out programs that have the sentence "no program information available" + channelData.programs = channelData.programs.filter(p => !p.title.includes('Nema informacija o programu')) + return sortBy(channelData.programs, p => parseStart(p).valueOf()) } catch { return [] } diff --git a/sites/mtel.ba/mtel.ba.test.js b/sites/mtel.ba/mtel.ba.test.js index 2664d66b3..393da5075 100644 --- a/sites/mtel.ba/mtel.ba.test.js +++ b/sites/mtel.ba/mtel.ba.test.js @@ -12,7 +12,7 @@ const channel = { site_id: 'msat#ch-11-rtrs' } it('can generate valid url', () => { expect(url({ date, channel })).toBe( - 'https://mtel.ba/hybris/ecommerce/b2c/v1/products/channels/epg?platform=tv-msat¤tPage=0&pageSize=1000&date=2025-02-04' + 'https://mtel.ba/hybris/ecommerce/b2c/v1/products/channels/epg?platform=tv-msat&pageSize=999&date=2025-02-04' ) }) diff --git a/sites/reportv.com.ar/reportv.com.ar.config.js b/sites/reportv.com.ar/reportv.com.ar.config.js index 51c89d23a..29fc8a7b8 100644 --- a/sites/reportv.com.ar/reportv.com.ar.config.js +++ b/sites/reportv.com.ar/reportv.com.ar.config.js @@ -5,7 +5,7 @@ const cheerio = require('cheerio') const utc = require('dayjs/plugin/utc') const timezone = require('dayjs/plugin/timezone') const customParseFormat = require('dayjs/plugin/customParseFormat') -const _ = require('lodash') +const { startCase } = require('../../scripts/functions') dayjs.extend(utc) dayjs.extend(timezone) @@ -164,7 +164,7 @@ function parseDuration($item) { function parseItems(content, date) { if (!content) return [] const $ = cheerio.load(content) - const d = _.startCase(date.locale('es').format('DD MMMM YYYY')) + const d = startCase(date.locale('es').format('DD MMMM YYYY')) return $(`.trProg[title*="${d}"]`).toArray() } diff --git a/sites/streamingtvguides.com/streamingtvguides.com.config.js b/sites/streamingtvguides.com/streamingtvguides.com.config.js index 811a8b409..e0b1d5a64 100644 --- a/sites/streamingtvguides.com/streamingtvguides.com.config.js +++ b/sites/streamingtvguides.com/streamingtvguides.com.config.js @@ -29,7 +29,7 @@ module.exports = { }) }) - programs = sortBy(uniqBy(programs, p => p.start), 'start') + programs = sortBy(uniqBy(programs, p => p.start), p => p.start.valueOf()) return programs }, From 4acf1a7c6773ce876775ca9d6412f6b5c1e009b6 Mon Sep 17 00:00:00 2001 From: theofficialomega <30985701+BellezaEmporium@users.noreply.github.com> Date: Sat, 19 Jul 2025 22:38:50 +0200 Subject: [PATCH 08/32] finish lo-ditching and update deps --- package-lock.json | 12948 ++++------------ package.json | 39 +- sites/rtp.pt/rtp.pt.config.js | 3 +- sites/sky.com/sky.com.config.js | 4 +- sites/tvcesoir.fr/tvcesoir.fr.config.js | 4 +- sites/tvhebdo.com/tvhebdo.com.config.js | 4 +- sites/tvireland.ie/tvireland.ie.config.js | 4 +- sites/tvmusor.hu/tvmusor.hu.config.js | 4 +- .../web.magentatv.de.config.js | 3 +- 9 files changed, 2806 insertions(+), 10207 deletions(-) diff --git a/package-lock.json b/package-lock.json index d80c4063f..34a76856f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "epg", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { @@ -10,28 +10,27 @@ "dependencies": { "@alex_neo/jest-expect-message": "^1.0.5", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "^9.30.0", - "@freearhey/core": "^0.8.2", + "@eslint/js": "^9.31.0", + "@freearhey/core": "^0.10.2", "@freearhey/search-js": "^0.1.2", "@ntlab/sfetch": "^1.2.0", - "@octokit/core": "^7.0.2", + "@octokit/core": "^7.0.3", "@octokit/plugin-paginate-rest": "^13.1.1", "@octokit/plugin-rest-endpoint-methods": "^16.0.0", - "@swc/core": "^1.12.7", - "@swc/jest": "^0.2.38", + "@swc/core": "^1.13.0", + "@swc/jest": "^0.2.39", "@types/cli-progress": "^3.11.6", "@types/fs-extra": "^11.0.4", "@types/inquirer": "^9.0.8", "@types/jest": "^30.0.0", "@types/langs": "^2.0.5", - "@types/lodash": "^4.17.19", - "@types/node": "^24.0.7", + "@types/node": "^24.0.15", "@types/node-cleanup": "^2.1.5", "@types/numeral": "^2.0.5", - "@typescript-eslint/eslint-plugin": "^8.35.0", - "@typescript-eslint/parser": "^8.35.0", + "@typescript-eslint/eslint-plugin": "^8.37.0", + "@typescript-eslint/parser": "^8.37.0", "axios": "^1.10.0", - "axios-cookiejar-support": "^6.0.2", + "axios-cookiejar-support": "^6.0.4", "chalk": "^5.4.1", "cheerio": "^1.1.0", "cli-progress": "^3.12.0", @@ -41,23 +40,22 @@ "csv-parser": "^3.2.0", "cwait": "^1.1.2", "dayjs": "^1.11.13", - "epg-grabber": "^0.38.0", + "epg-grabber": "^0.41.0", "epg-parser": "^0.3.1", - "eslint": "^9.30.0", - "eslint-config-prettier": "^10.1.5", - "form-data": "^4.0.3", + "eslint": "^9.31.0", + "eslint-config-prettier": "^10.1.8", + "form-data": "^4.0.4", "fs-extra": "^11.3.0", "glob": "^11.0.3", - "globals": "^16.2.0", + "globals": "^16.3.0", "husky": "^9.1.7", "iconv-lite": "^0.6.3", - "inquirer": "^12.6.3", - "jest": "^30.0.3", + "inquirer": "^12.7.0", + "jest": "^30.0.4", "jest-offline": "^1.0.1", "langs": "^2.0.0", "libxml2-wasm": "^0.5.0", - "lodash": "^4.17.21", - "luxon": "^3.6.1", + "luxon": "^3.7.1", "mockdate": "^3.0.5", "nedb-promises": "^6.2.3", "node-cleanup": "^2.1.2", @@ -85,18 +83,11 @@ "wildcard-match": "^5.1.4" } }, - "node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/@alex_neo/jest-expect-message": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@alex_neo/jest-expect-message/-/jest-expect-message-1.0.5.tgz", - "integrity": "sha512-1eBykZCd0pPGl5qKtV6Z5ARA6yuhXzHsVN2h5GH5/H6svYa37Jr7vMio5OFpiw1LBHtscrZs7amSkZkcwm0cvQ==" + "integrity": "sha512-1eBykZCd0pPGl5qKtV6Z5ARA6yuhXzHsVN2h5GH5/H6svYa37Jr7vMio5OFpiw1LBHtscrZs7amSkZkcwm0cvQ==", + "license": "MIT" }, "node_modules/@ampproject/remapping": { "version": "2.3.0", @@ -126,30 +117,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.7.tgz", - "integrity": "sha512-xgu/ySj2mTiUFmdE9yCMfBxLp4DHd5DwmbbD05YAuICfodYT3VvRxbrh81LGQ/8UpSdtMdfKMn3KouYDX59DGQ==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", + "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.7.tgz", - "integrity": "sha512-BU2f9tlKQ5CAthiMIgpzAh4eDTLWo1mqi9jqE2OxMG0E/OM199VJt2q8BztTxpnSW0i1ymdwLXRJnYzvDM5r2w==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", + "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.5", + "@babel/generator": "^7.28.0", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", "@babel/helpers": "^7.27.6", - "@babel/parser": "^7.27.7", + "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", - "@babel/traverse": "^7.27.7", - "@babel/types": "^7.27.7", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -174,15 +165,15 @@ } }, "node_modules/@babel/generator": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", - "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", + "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.27.5", - "@babel/types": "^7.27.3", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", + "@babel/parser": "^7.28.0", + "@babel/types": "^7.28.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" }, "engines": { @@ -214,6 +205,15 @@ "semver": "bin/semver.js" } }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-module-imports": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", @@ -294,12 +294,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.7.tgz", - "integrity": "sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", + "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", "license": "MIT", "dependencies": { - "@babel/types": "^7.27.7" + "@babel/types": "^7.28.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -545,36 +545,27 @@ } }, "node_modules/@babel/traverse": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.7.tgz", - "integrity": "sha512-X6ZlfR/O/s5EQ/SnUSLzr+6kGnkg8HXGMzpgsMsrJVcfDtH1vIp6ctCN4eZ1LS5c0+te5Cb6Y514fASjMRJ1nw==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz", + "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.5", - "@babel/parser": "^7.27.7", + "@babel/generator": "^7.28.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", - "@babel/types": "^7.27.7", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@babel/types": "^7.28.0", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/types": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.7.tgz", - "integrity": "sha512-8OLQgDScAOHXnAz2cV+RfzzNMipuLVBz2biuAJFMV9bfkNf393je3VM8CLkjQodW5+iWsSJdSgSWT6rsZoXHPw==", + "version": "7.28.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.1.tgz", + "integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -591,9 +582,10 @@ "license": "MIT" }, "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", "engines": { "node": ">=0.1.90" } @@ -602,6 +594,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "license": "MIT", "dependencies": { "colorspace": "1.1.x", "enabled": "2.0.x", @@ -609,43 +602,65 @@ } }, "node_modules/@emnapi/core": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz", - "integrity": "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.5.tgz", + "integrity": "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==", "license": "MIT", "optional": true, "dependencies": { - "@emnapi/wasi-threads": "1.0.2", + "@emnapi/wasi-threads": "1.0.4", "tslib": "^2.4.0" } }, + "node_modules/@emnapi/core/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, "node_modules/@emnapi/runtime": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", - "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz", + "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==", "license": "MIT", "optional": true, "dependencies": { "tslib": "^2.4.0" } }, + "node_modules/@emnapi/runtime/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, "node_modules/@emnapi/wasi-threads": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz", - "integrity": "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.4.tgz", + "integrity": "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==", "license": "MIT", "optional": true, "dependencies": { "tslib": "^2.4.0" } }, + "node_modules/@emnapi/wasi-threads/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", - "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", + "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", "cpu": [ "ppc64" ], + "license": "MIT", "optional": true, "os": [ "aix" @@ -655,12 +670,13 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", - "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", + "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", "cpu": [ "arm" ], + "license": "MIT", "optional": true, "os": [ "android" @@ -670,12 +686,13 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", - "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", + "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "android" @@ -685,12 +702,13 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", - "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", + "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "android" @@ -700,12 +718,13 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", - "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", + "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "darwin" @@ -715,12 +734,13 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", - "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", + "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "darwin" @@ -730,12 +750,13 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", - "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", + "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -745,12 +766,13 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", - "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", + "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -760,12 +782,13 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", - "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", + "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", "cpu": [ "arm" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -775,12 +798,13 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", - "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", + "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -790,12 +814,13 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", - "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", + "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", "cpu": [ "ia32" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -805,12 +830,13 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", - "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", + "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", "cpu": [ "loong64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -820,12 +846,13 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", - "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", + "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", "cpu": [ "mips64el" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -835,12 +862,13 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", - "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", + "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", "cpu": [ "ppc64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -850,12 +878,13 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", - "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", + "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", "cpu": [ "riscv64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -865,12 +894,13 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", - "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", + "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", "cpu": [ "s390x" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -880,12 +910,13 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", - "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", + "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -895,12 +926,13 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", - "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", + "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "netbsd" @@ -910,12 +942,13 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", - "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", + "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "netbsd" @@ -925,12 +958,13 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", - "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", + "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -940,12 +974,13 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", - "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", + "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -954,13 +989,30 @@ "node": ">=18" } }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", + "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", - "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", + "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "sunos" @@ -970,12 +1022,13 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", - "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", + "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -985,12 +1038,13 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", - "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", + "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", "cpu": [ "ia32" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -1000,12 +1054,13 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", - "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", + "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -1036,6 +1091,7 @@ "version": "4.12.1", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } @@ -1064,9 +1120,9 @@ } }, "node_modules/@eslint/core": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", - "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" @@ -1098,15 +1154,11 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, "node_modules/@eslint/eslintrc/node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "license": "MIT", "engines": { "node": ">=18" }, @@ -1114,21 +1166,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/@eslint/js": { - "version": "9.30.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.30.0.tgz", - "integrity": "sha512-Wzw3wQwPvc9sHM+NjakWTcPx11mbZyiYHuwWa/QfZ7cIRX7WK54PSk7bdyXDaoaopUcMatv1zaQvOAAO8hCdww==", + "version": "9.31.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz", + "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1147,34 +1188,22 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.2.tgz", - "integrity": "sha512-4SaFZCNfJqvk/kenHpI8xvN42DMaoycy4PzKc5otHxRswww1kAt82OlBuwRVLofCACCTZEcla2Ydxv8scMXaTg==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.3.tgz", + "integrity": "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==", "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.15.0", + "@eslint/core": "^0.15.1", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.0.tgz", - "integrity": "sha512-b7ePw78tEWWkpgZCDYkbqDOP8dmM6qe+AOC6iuJqlq1R/0ahMAeH3qynpnqKFGkMltrp44ohV4ubGyvLX28tzw==", - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/@freearhey/core": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/@freearhey/core/-/core-0.8.2.tgz", - "integrity": "sha512-jlb1XUbhUf3lqD3B9Wmx3c8qYG4+s1I0cr2FFQfiMpJh4nMvfUNdJr2OhH31S/dbNP12ycT6RPVoZ2j2G3+mXA==", + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@freearhey/core/-/core-0.10.2.tgz", + "integrity": "sha512-crIE1oVYnhmCvISuNvJ4eP70tmoPzCTg6emlPxDIimu8LAtC8FVyckC04nBQCMYaXjLuju7tI80vHmKRBXniVg==", "license": "MIT", "dependencies": { "consola": "^3.4.2", @@ -1205,6 +1234,7 @@ "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "license": "Apache-2.0", "engines": { "node": ">=18.18.0" } @@ -1213,6 +1243,7 @@ "version": "0.16.6", "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "license": "Apache-2.0", "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" @@ -1225,6 +1256,7 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "license": "Apache-2.0", "engines": { "node": ">=18.18" }, @@ -1237,6 +1269,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "license": "Apache-2.0", "engines": { "node": ">=12.22" }, @@ -1259,12 +1292,12 @@ } }, "node_modules/@inquirer/checkbox": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.1.8.tgz", - "integrity": "sha512-d/QAsnwuHX2OPolxvYcgSj7A9DO9H6gVOy2DvBTx+P2LH2iRTo/RSGV3iwCzW024nP9hw98KIuDmdyhZQj1UQg==", + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.1.9.tgz", + "integrity": "sha512-DBJBkzI5Wx4jFaYm221LHvAhpKYkhVS0k9plqHwaHhofGNxvYB7J3Bz8w+bFJ05zaMb0sZNHo4KdmENQFlNTuQ==", "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.13", + "@inquirer/core": "^10.1.14", "@inquirer/figures": "^1.0.12", "@inquirer/type": "^3.0.7", "ansi-escapes": "^4.3.2", @@ -1283,12 +1316,12 @@ } }, "node_modules/@inquirer/confirm": { - "version": "5.1.12", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.12.tgz", - "integrity": "sha512-dpq+ielV9/bqgXRUbNH//KsY6WEw9DrGPmipkpmgC1Y46cwuBTNx7PXFWTjc3MQ+urcc0QxoVHcMI0FW4Ok0hg==", + "version": "5.1.13", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.13.tgz", + "integrity": "sha512-EkCtvp67ICIVVzjsquUiVSd+V5HRGOGQfsqA4E4vMWhYnB7InUL0pa0TIWt1i+OfP16Gkds8CdIu6yGZwOM1Yw==", "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.13", + "@inquirer/core": "^10.1.14", "@inquirer/type": "^3.0.7" }, "engines": { @@ -1304,9 +1337,9 @@ } }, "node_modules/@inquirer/core": { - "version": "10.1.13", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.13.tgz", - "integrity": "sha512-1viSxebkYN2nJULlzCxES6G9/stgHSepZ9LqqfdIGPHj5OHhiBUXVS0a6R0bEC2A+VL4D9w6QB66ebCr6HGllA==", + "version": "10.1.14", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.14.tgz", + "integrity": "sha512-Ma+ZpOJPewtIYl6HZHZckeX1STvDnHTCB2GVINNUlSEn2Am6LddWwfPkIGY0IUFVjUUrr/93XlBwTK6mfLjf0A==", "license": "MIT", "dependencies": { "@inquirer/figures": "^1.0.12", @@ -1330,34 +1363,13 @@ } } }, - "node_modules/@inquirer/core/node_modules/mute-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", - "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@inquirer/core/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@inquirer/editor": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.13.tgz", - "integrity": "sha512-WbicD9SUQt/K8O5Vyk9iC2ojq5RHoCLK6itpp2fHsWe44VxxcA9z3GTWlvjSTGmMQpZr+lbVmrxdHcumJoLbMA==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.14.tgz", + "integrity": "sha512-yd2qtLl4QIIax9DTMZ1ZN2pFrrj+yL3kgIWxm34SS6uwCr0sIhsNyudUjAo5q3TqI03xx4SEBkUJqZuAInp9uA==", "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.13", + "@inquirer/core": "^10.1.14", "@inquirer/type": "^3.0.7", "external-editor": "^3.1.0" }, @@ -1374,12 +1386,12 @@ } }, "node_modules/@inquirer/expand": { - "version": "4.0.15", - "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.15.tgz", - "integrity": "sha512-4Y+pbr/U9Qcvf+N/goHzPEXiHH8680lM3Dr3Y9h9FFw4gHS+zVpbj8LfbKWIb/jayIB4aSO4pWiBTrBYWkvi5A==", + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.16.tgz", + "integrity": "sha512-oiDqafWzMtofeJyyGkb1CTPaxUkjIcSxePHHQCfif8t3HV9pHcw1Kgdw3/uGpDvaFfeTluwQtWiqzPVjAqS3zA==", "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.13", + "@inquirer/core": "^10.1.14", "@inquirer/type": "^3.0.7", "yoctocolors-cjs": "^2.1.2" }, @@ -1405,12 +1417,12 @@ } }, "node_modules/@inquirer/input": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.1.12.tgz", - "integrity": "sha512-xJ6PFZpDjC+tC1P8ImGprgcsrzQRsUh9aH3IZixm1lAZFK49UGHxM3ltFfuInN2kPYNfyoPRh+tU4ftsjPLKqQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.2.0.tgz", + "integrity": "sha512-opqpHPB1NjAmDISi3uvZOTrjEEU5CWVu/HBkDby8t93+6UxYX0Z7Ps0Ltjm5sZiEbWenjubwUkivAEYQmy9xHw==", "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.13", + "@inquirer/core": "^10.1.14", "@inquirer/type": "^3.0.7" }, "engines": { @@ -1426,12 +1438,12 @@ } }, "node_modules/@inquirer/number": { - "version": "3.0.15", - "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.15.tgz", - "integrity": "sha512-xWg+iYfqdhRiM55MvqiTCleHzszpoigUpN5+t1OMcRkJrUrw7va3AzXaxvS+Ak7Gny0j2mFSTv2JJj8sMtbV2g==", + "version": "3.0.16", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.16.tgz", + "integrity": "sha512-kMrXAaKGavBEoBYUCgualbwA9jWUx2TjMA46ek+pEKy38+LFpL9QHlTd8PO2kWPUgI/KB+qi02o4y2rwXbzr3Q==", "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.13", + "@inquirer/core": "^10.1.14", "@inquirer/type": "^3.0.7" }, "engines": { @@ -1447,12 +1459,12 @@ } }, "node_modules/@inquirer/password": { - "version": "4.0.15", - "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.15.tgz", - "integrity": "sha512-75CT2p43DGEnfGTaqFpbDC2p2EEMrq0S+IRrf9iJvYreMy5mAWj087+mdKyLHapUEPLjN10mNvABpGbk8Wdraw==", + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.16.tgz", + "integrity": "sha512-g8BVNBj5Zeb5/Y3cSN+hDUL7CsIFDIuVxb9EPty3lkxBaYpjL5BNRKSYOF9yOLe+JOcKFd+TSVeADQ4iSY7rbg==", "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.13", + "@inquirer/core": "^10.1.14", "@inquirer/type": "^3.0.7", "ansi-escapes": "^4.3.2" }, @@ -1469,21 +1481,21 @@ } }, "node_modules/@inquirer/prompts": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.5.3.tgz", - "integrity": "sha512-8YL0WiV7J86hVAxrh3fE5mDCzcTDe1670unmJRz6ArDgN+DBK1a0+rbnNWp4DUB5rPMwqD5ZP6YHl9KK1mbZRg==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.6.0.tgz", + "integrity": "sha512-jAhL7tyMxB3Gfwn4HIJ0yuJ5pvcB5maYUcouGcgd/ub79f9MqZ+aVnBtuFf+VC2GTkCBF+R+eo7Vi63w5VZlzw==", "license": "MIT", "dependencies": { - "@inquirer/checkbox": "^4.1.8", - "@inquirer/confirm": "^5.1.12", - "@inquirer/editor": "^4.2.13", - "@inquirer/expand": "^4.0.15", - "@inquirer/input": "^4.1.12", - "@inquirer/number": "^3.0.15", - "@inquirer/password": "^4.0.15", - "@inquirer/rawlist": "^4.1.3", - "@inquirer/search": "^3.0.15", - "@inquirer/select": "^4.2.3" + "@inquirer/checkbox": "^4.1.9", + "@inquirer/confirm": "^5.1.13", + "@inquirer/editor": "^4.2.14", + "@inquirer/expand": "^4.0.16", + "@inquirer/input": "^4.2.0", + "@inquirer/number": "^3.0.16", + "@inquirer/password": "^4.0.16", + "@inquirer/rawlist": "^4.1.4", + "@inquirer/search": "^3.0.16", + "@inquirer/select": "^4.2.4" }, "engines": { "node": ">=18" @@ -1498,12 +1510,12 @@ } }, "node_modules/@inquirer/rawlist": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.3.tgz", - "integrity": "sha512-7XrV//6kwYumNDSsvJIPeAqa8+p7GJh7H5kRuxirct2cgOcSWwwNGoXDRgpNFbY/MG2vQ4ccIWCi8+IXXyFMZA==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.4.tgz", + "integrity": "sha512-5GGvxVpXXMmfZNtvWw4IsHpR7RzqAR624xtkPd1NxxlV5M+pShMqzL4oRddRkg8rVEOK9fKdJp1jjVML2Lr7TQ==", "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.13", + "@inquirer/core": "^10.1.14", "@inquirer/type": "^3.0.7", "yoctocolors-cjs": "^2.1.2" }, @@ -1520,12 +1532,12 @@ } }, "node_modules/@inquirer/search": { - "version": "3.0.15", - "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.0.15.tgz", - "integrity": "sha512-YBMwPxYBrADqyvP4nNItpwkBnGGglAvCLVW8u4pRmmvOsHUtCAUIMbUrLX5B3tFL1/WsLGdQ2HNzkqswMs5Uaw==", + "version": "3.0.16", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.0.16.tgz", + "integrity": "sha512-POCmXo+j97kTGU6aeRjsPyuCpQQfKcMXdeTMw708ZMtWrj5aykZvlUxH4Qgz3+Y1L/cAVZsSpA+UgZCu2GMOMg==", "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.13", + "@inquirer/core": "^10.1.14", "@inquirer/figures": "^1.0.12", "@inquirer/type": "^3.0.7", "yoctocolors-cjs": "^2.1.2" @@ -1543,12 +1555,12 @@ } }, "node_modules/@inquirer/select": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.2.3.tgz", - "integrity": "sha512-OAGhXU0Cvh0PhLz9xTF/kx6g6x+sP+PcyTiLvCrewI99P3BBeexD+VbuwkNDvqGkk3y2h5ZiWLeRP7BFlhkUDg==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.2.4.tgz", + "integrity": "sha512-unTppUcTjmnbl/q+h8XeQDhAqIOmwWYWNyiiP2e3orXrg6tOaa5DHXja9PChCSbChOsktyKgOieRZFnajzxoBg==", "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.13", + "@inquirer/core": "^10.1.14", "@inquirer/figures": "^1.0.12", "@inquirer/type": "^3.0.7", "ansi-escapes": "^4.3.2", @@ -1621,18 +1633,6 @@ "node": ">=12" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, "node_modules/@isaacs/cliui/node_modules/ansi-styles": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", @@ -1668,21 +1668,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -1716,6 +1701,95 @@ "node": ">=8" } }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -1726,9 +1800,9 @@ } }, "node_modules/@jest/console": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.0.2.tgz", - "integrity": "sha512-krGElPU0FipAqpVZ/BRZOy0MZh/ARdJ0Nj+PiH1ykFY1+VpBlYNLjdjVA5CFKxnKR6PFqFutO4Z7cdK9BlGiDA==", + "version": "30.0.4", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.0.4.tgz", + "integrity": "sha512-tMLCDvBJBwPqMm4OAiuKm2uF5y5Qe26KgcMn+nrDSWpEW+eeFmqA0iO4zJfL16GP7gE3bUUQ3hIuUJ22AqVRnw==", "license": "MIT", "dependencies": { "@jest/types": "30.0.1", @@ -1742,42 +1816,21 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/console/node_modules/@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", + "node_modules/@jest/console/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.34.0" + "color-convert": "^2.0.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/console/node_modules/@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" + "node": ">=8" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jest/console/node_modules/@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", - "license": "MIT" - }, "node_modules/@jest/console/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1794,17 +1847,35 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@jest/core": { - "version": "30.0.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.0.3.tgz", - "integrity": "sha512-Mgs1N+NSHD3Fusl7bOq1jyxv1JDAUwjy+0DhVR93Q6xcBP9/bAQ+oZhXb5TTnP5sQzAHgb7ROCKQ2SnovtxYtg==", + "node_modules/@jest/console/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "license": "MIT", "dependencies": { - "@jest/console": "30.0.2", + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/console/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/@jest/core": { + "version": "30.0.4", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.0.4.tgz", + "integrity": "sha512-MWScSO9GuU5/HoWjpXAOBs6F/iobvK1XlioelgOM9St7S0Z5WTI9kjCQLPeo4eQRRYusyLW25/J7J5lbFkrYXw==", + "license": "MIT", + "dependencies": { + "@jest/console": "30.0.4", "@jest/pattern": "30.0.1", - "@jest/reporters": "30.0.2", - "@jest/test-result": "30.0.2", - "@jest/transform": "30.0.2", + "@jest/reporters": "30.0.4", + "@jest/test-result": "30.0.4", + "@jest/transform": "30.0.4", "@jest/types": "30.0.1", "@types/node": "*", "ansi-escapes": "^4.3.2", @@ -1813,18 +1884,18 @@ "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", "jest-changed-files": "30.0.2", - "jest-config": "30.0.3", + "jest-config": "30.0.4", "jest-haste-map": "30.0.2", "jest-message-util": "30.0.2", "jest-regex-util": "30.0.1", "jest-resolve": "30.0.2", - "jest-resolve-dependencies": "30.0.3", - "jest-runner": "30.0.3", - "jest-runtime": "30.0.3", - "jest-snapshot": "30.0.3", + "jest-resolve-dependencies": "30.0.4", + "jest-runner": "30.0.4", + "jest-runtime": "30.0.4", + "jest-snapshot": "30.0.4", "jest-util": "30.0.2", "jest-validate": "30.0.2", - "jest-watcher": "30.0.2", + "jest-watcher": "30.0.4", "micromatch": "^4.0.8", "pretty-format": "30.0.2", "slash": "^3.0.0" @@ -1841,42 +1912,21 @@ } } }, - "node_modules/@jest/core/node_modules/@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", + "node_modules/@jest/core/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.34.0" + "color-convert": "^2.0.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/core/node_modules/@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" + "node": ">=8" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jest/core/node_modules/@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", - "license": "MIT" - }, "node_modules/@jest/core/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1893,15 +1943,34 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@jest/create-cache-key-function": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", - "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==", + "node_modules/@jest/core/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3" + "color-name": "~1.1.4" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=7.0.0" + } + }, + "node_modules/@jest/core/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/@jest/create-cache-key-function": { + "version": "30.0.2", + "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-30.0.2.tgz", + "integrity": "sha512-AwlDAHwEHDi+etw9vKWx9HeIApVos8GD/sSTpHtDkqhm9OWuEUPKKPP6EaS17yv0GSzBB3TeeJFLyJ5LPjRqWg==", + "license": "MIT", + "dependencies": { + "@jest/types": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/diff-sequences": { @@ -1914,12 +1983,12 @@ } }, "node_modules/@jest/environment": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.0.2.tgz", - "integrity": "sha512-hRLhZRJNxBiOhxIKSq2UkrlhMt3/zVFQOAi5lvS8T9I03+kxsbflwHJEF+eXEYXCrRGRhHwECT7CDk6DyngsRA==", + "version": "30.0.4", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.0.4.tgz", + "integrity": "sha512-5NT+sr7ZOb8wW7C4r7wOKnRQ8zmRWQT2gW4j73IXAKp5/PX1Z8MCStBLQDYfIG3n1Sw0NRfYGdp0iIPVooBAFQ==", "license": "MIT", "dependencies": { - "@jest/fake-timers": "30.0.2", + "@jest/fake-timers": "30.0.4", "@jest/types": "30.0.1", "@types/node": "*", "jest-mock": "30.0.2" @@ -1928,75 +1997,23 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/environment/node_modules/@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.34.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/environment/node_modules/@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/environment/node_modules/@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", - "license": "MIT" - }, - "node_modules/@jest/environment/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/@jest/expect": { - "version": "30.0.3", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.0.3.tgz", - "integrity": "sha512-73BVLqfCeWjYWPEQoYjiRZ4xuQRhQZU0WdgvbyXGRHItKQqg5e6mt2y1kVhzLSuZpmUnccZHbGynoaL7IcLU3A==", + "version": "30.0.4", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.0.4.tgz", + "integrity": "sha512-Z/DL7t67LBHSX4UzDyeYKqOxE/n7lbrrgEwWM3dGiH5Dgn35nk+YtgzKudmfIrBI8DRRrKYY5BCo3317HZV1Fw==", "license": "MIT", "dependencies": { - "expect": "30.0.3", - "jest-snapshot": "30.0.3" + "expect": "30.0.4", + "jest-snapshot": "30.0.4" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "30.0.3", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.0.3.tgz", - "integrity": "sha512-SMtBvf2sfX2agcT0dA9pXwcUrKvOSDqBY4e4iRfT+Hya33XzV35YVg+98YQFErVGA/VR1Gto5Y2+A6G9LSQ3Yg==", + "version": "30.0.4", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.0.4.tgz", + "integrity": "sha512-EgXecHDNfANeqOkcak0DxsoVI4qkDUsR7n/Lr2vtmTBjwLPBnnPOF71S11Q8IObWzxm2QgQoY6f9hzrRD3gHRA==", "license": "MIT", "dependencies": { "@jest/get-type": "30.0.1" @@ -2006,9 +2023,9 @@ } }, "node_modules/@jest/fake-timers": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.0.2.tgz", - "integrity": "sha512-jfx0Xg7l0gmphTY9UKm5RtH12BlLYj/2Plj6wXjVW5Era4FZKfXeIvwC67WX+4q8UCFxYS20IgnMcFBcEU0DtA==", + "version": "30.0.4", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.0.4.tgz", + "integrity": "sha512-qZ7nxOcL5+gwBO6LErvwVy5k06VsX/deqo2XnVUSTV0TNC9lrg8FC3dARbi+5lmrr5VyX5drragK+xLcOjvjYw==", "license": "MIT", "dependencies": { "@jest/types": "30.0.1", @@ -2022,58 +2039,6 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/fake-timers/node_modules/@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.34.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/fake-timers/node_modules/@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/fake-timers/node_modules/@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", - "license": "MIT" - }, - "node_modules/@jest/fake-timers/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/@jest/get-type": { "version": "30.0.1", "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.0.1.tgz", @@ -2084,13 +2049,13 @@ } }, "node_modules/@jest/globals": { - "version": "30.0.3", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.0.3.tgz", - "integrity": "sha512-fIduqNyYpMeeSr5iEAiMn15KxCzvrmxl7X7VwLDRGj7t5CoHtbF+7K3EvKk32mOUIJ4kIvFRlaixClMH2h/Vaw==", + "version": "30.0.4", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.0.4.tgz", + "integrity": "sha512-avyZuxEHF2EUhFF6NEWVdxkRRV6iXXcIES66DLhuLlU7lXhtFG/ySq/a8SRZmEJSsLkNAFX6z6mm8KWyXe9OEA==", "license": "MIT", "dependencies": { - "@jest/environment": "30.0.2", - "@jest/expect": "30.0.3", + "@jest/environment": "30.0.4", + "@jest/expect": "30.0.4", "@jest/types": "30.0.1", "jest-mock": "30.0.2" }, @@ -2098,58 +2063,6 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/globals/node_modules/@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.34.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/globals/node_modules/@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/globals/node_modules/@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", - "license": "MIT" - }, - "node_modules/@jest/globals/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/@jest/pattern": { "version": "30.0.1", "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", @@ -2164,15 +2077,15 @@ } }, "node_modules/@jest/reporters": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.0.2.tgz", - "integrity": "sha512-l4QzS/oKf57F8WtPZK+vvF4Io6ukplc6XgNFu4Hd/QxaLEO9f+8dSFzUua62Oe0HKlCUjKHpltKErAgDiMJKsA==", + "version": "30.0.4", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.0.4.tgz", + "integrity": "sha512-6ycNmP0JSJEEys1FbIzHtjl9BP0tOZ/KN6iMeAKrdvGmUsa1qfRdlQRUDKJ4P84hJ3xHw1yTqJt4fvPNHhyE+g==", "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.0.2", - "@jest/test-result": "30.0.2", - "@jest/transform": "30.0.2", + "@jest/console": "30.0.4", + "@jest/test-result": "30.0.4", + "@jest/transform": "30.0.4", "@jest/types": "30.0.1", "@jridgewell/trace-mapping": "^0.3.25", "@types/node": "*", @@ -2205,42 +2118,21 @@ } } }, - "node_modules/@jest/reporters/node_modules/@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", + "node_modules/@jest/reporters/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.34.0" + "color-convert": "^2.0.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" + "node": ">=8" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jest/reporters/node_modules/@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", - "license": "MIT" - }, "node_modules/@jest/reporters/node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -2266,6 +2158,24 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/@jest/reporters/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/@jest/reporters/node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -2339,32 +2249,6 @@ } }, "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/snapshot-utils": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.0.1.tgz", - "integrity": "sha512-6Dpv7vdtoRiISEFwYF8/c7LIvqXD7xDXtLPNzC2xqAfBznKip0MQM+rkseKwUPUpv2PJ7KW/YsnwWXrIL2xF+A==", - "license": "MIT", - "dependencies": { - "@jest/types": "30.0.1", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "natural-compare": "^1.4.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/snapshot-utils/node_modules/@jest/schemas": { "version": "30.0.1", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", @@ -2376,29 +2260,35 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/snapshot-utils/node_modules/@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", + "node_modules/@jest/snapshot-utils": { + "version": "30.0.4", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.0.4.tgz", + "integrity": "sha512-BEpX8M/Y5lG7MI3fmiO+xCnacOrVsnbqVrcDZIT8aSGkKV1w2WwvRQxSWw5SIS8ozg7+h8tSj5EO1Riqqxcdag==", "license": "MIT", "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" + "@jest/types": "30.0.1", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "natural-compare": "^1.4.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/snapshot-utils/node_modules/@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", - "license": "MIT" + "node_modules/@jest/snapshot-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } }, "node_modules/@jest/snapshot-utils/node_modules/chalk": { "version": "4.1.2", @@ -2416,6 +2306,24 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/@jest/snapshot-utils/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/snapshot-utils/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/@jest/source-map": { "version": "30.0.1", "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", @@ -2431,12 +2339,12 @@ } }, "node_modules/@jest/test-result": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.0.2.tgz", - "integrity": "sha512-KKMuBKkkZYP/GfHMhI+cH2/P3+taMZS3qnqqiPC1UXZTJskkCS+YU/ILCtw5anw1+YsTulDHFpDo70mmCedW8w==", + "version": "30.0.4", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.0.4.tgz", + "integrity": "sha512-Mfpv8kjyKTHqsuu9YugB6z1gcdB3TSSOaKlehtVaiNlClMkEHY+5ZqCY2CrEE3ntpBMlstX/ShDAf84HKWsyIw==", "license": "MIT", "dependencies": { - "@jest/console": "30.0.2", + "@jest/console": "30.0.4", "@jest/types": "30.0.1", "@types/istanbul-lib-coverage": "^2.0.6", "collect-v8-coverage": "^1.0.2" @@ -2445,65 +2353,13 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/test-result/node_modules/@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.34.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/test-result/node_modules/@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/test-result/node_modules/@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", - "license": "MIT" - }, - "node_modules/@jest/test-result/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/@jest/test-sequencer": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.0.2.tgz", - "integrity": "sha512-fbyU5HPka0rkalZ3MXVvq0hwZY8dx3Y6SCqR64zRmh+xXlDeFl0IdL4l9e7vp4gxEXTYHbwLFA1D+WW5CucaSw==", + "version": "30.0.4", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.0.4.tgz", + "integrity": "sha512-bj6ePmqi4uxAE8EHE0Slmk5uBYd9Vd/PcVt06CsBxzH4bbA8nGsI1YbXl/NH+eii4XRtyrRx+Cikub0x8H4vDg==", "license": "MIT", "dependencies": { - "@jest/test-result": "30.0.2", + "@jest/test-result": "30.0.4", "graceful-fs": "^4.2.11", "jest-haste-map": "30.0.2", "slash": "^3.0.0" @@ -2513,9 +2369,9 @@ } }, "node_modules/@jest/transform": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.0.2.tgz", - "integrity": "sha512-kJIuhLMTxRF7sc0gPzPtCDib/V9KwW3I2U25b+lYCYMVqHHSrcZopS8J8H+znx9yixuFv+Iozl8raLt/4MoxrA==", + "version": "30.0.4", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.0.4.tgz", + "integrity": "sha512-atvy4hRph/UxdCIBp+UB2jhEA/jJiUeGZ7QPgBi9jUUKNgi3WEoMXGNG7zbbELG2+88PMabUNCDchmqgJy3ELg==", "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", @@ -2538,42 +2394,21 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/transform/node_modules/@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", + "node_modules/@jest/transform/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.34.0" + "color-convert": "^2.0.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/@jest/transform/node_modules/@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" + "node": ">=8" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jest/transform/node_modules/@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", - "license": "MIT" - }, "node_modules/@jest/transform/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2590,20 +2425,55 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "node_modules/@jest/transform/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "color-name": "~1.1.4" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=7.0.0" + } + }, + "node_modules/@jest/transform/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/@jest/types": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", + "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.1", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/types/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/@jest/types/node_modules/chalk": { @@ -2622,10 +2492,28 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/@jest/types/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/types/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.10.tgz", - "integrity": "sha512-HM2F4B9N4cA0RH2KQiIZOHAZqtP4xGS4IZ+SFe1SIbO4dyjf9MTY2Bo3vHYnm0hglWfXqBrzUBSa+cJfl3Xvrg==", + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -2642,15 +2530,15 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.2.tgz", - "integrity": "sha512-gKYheCylLIedI+CSZoDtGkFV9YEBxRRVcfCH7OfAqh4TyUyRjEE6WVE/aXDXX0p8BIe/QgLcaAoI0220KRRFgg==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.27", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.27.tgz", - "integrity": "sha512-VO95AxtSFMelbg3ouljAYnfvTEwSWVt/2YLf+U5Ejd8iT5mXE2Sa/1LGyvySMne2CGsepGLI7KpF3EzE3Aq9Mg==", + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -2658,15 +2546,15 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.11.tgz", - "integrity": "sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==", + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", "license": "MIT", "optional": true, "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.9.0" + "@tybys/wasm-util": "^0.10.0" } }, "node_modules/@nodelib/fs.scandir": { @@ -2708,6 +2596,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ntlab/sfetch/-/sfetch-1.2.0.tgz", "integrity": "sha512-9SE4NnqWo8l6mG0rnAkgng6ozSamIpF3EC+GOTQGGa6eAC0tNJvzrylMz6YRjjEGH6mOfn7ZBAuKj5WIZUul6A==", + "license": "MIT", "dependencies": { "axios": "^1.7.9" } @@ -2722,9 +2611,9 @@ } }, "node_modules/@octokit/core": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.2.tgz", - "integrity": "sha512-ODsoD39Lq6vR6aBgvjTnA3nZGliknKboc9Gtxr7E4WDNqY24MxANKcuDQSF0jzapvGb3KWOEDrKfve4HoWGK+g==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.3.tgz", + "integrity": "sha512-oNXsh2ywth5aowwIa7RKtawnkdH6LgU1ztfP9AIUCQCvzysB+WeU8o2kyyosDPwBZutPpjZDKPQGIzzrfTWweQ==", "license": "MIT", "dependencies": { "@octokit/auth-token": "^6.0.0", @@ -2803,9 +2692,9 @@ } }, "node_modules/@octokit/request": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.2.tgz", - "integrity": "sha512-iYj4SJG/2bbhh+iIpFmG5u49DtJ4lipQ+aPakjL9OKpsGY93wM8w06gvFbEQxcMsZcCvk5th5KkIm2m8o14aWA==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.3.tgz", + "integrity": "sha512-V6jhKokg35vk098iBqp2FBKunk3kMTXlmq+PtbV9Gl3TfskWlebSofU9uunVKhUN7xl+0+i5vt0TGTG8/p/7HA==", "license": "MIT", "dependencies": { "@octokit/endpoint": "^11.0.0", @@ -2839,6 +2728,110 @@ "@octokit/openapi-types": "^25.1.0" } }, + "node_modules/@oxlint/darwin-arm64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@oxlint/darwin-arm64/-/darwin-arm64-1.7.0.tgz", + "integrity": "sha512-51vhCSQO4NSkedwEwOyqThiYqV0DAUkwNdqMQK0d29j5zmtNJJJRRBLeQuLGdstNmn3F7WMQ75Ci0/3Nq4ff8A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxlint/darwin-x64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@oxlint/darwin-x64/-/darwin-x64-1.7.0.tgz", + "integrity": "sha512-c0GN52yehYZ4TYuh4lBH9wYbBOI/RDOxZhJdBsttG0GwfvKYg/tiPNrNEsPzu0/rd1j6x3yT0zt6vezDMeC1sQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxlint/linux-arm64-gnu": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@oxlint/linux-arm64-gnu/-/linux-arm64-gnu-1.7.0.tgz", + "integrity": "sha512-pam/lbzbzVMDzc3f1hoRPtnUMEIqkn0dynlB5nUll/MVBSIvIPLS9kJLrRA48lrlqbkS9LGiF37JvpwXA58A9A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxlint/linux-arm64-musl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@oxlint/linux-arm64-musl/-/linux-arm64-musl-1.7.0.tgz", + "integrity": "sha512-LTyPy9FYS3SZ2XxJx+ITvlAq/ek5PtZK9Z2m3W72TA8hchGhJy5eQ+aotYjd/YVXOpGRpB12RdOpOTsZRu50bA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxlint/linux-x64-gnu": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@oxlint/linux-x64-gnu/-/linux-x64-gnu-1.7.0.tgz", + "integrity": "sha512-YtZ4DiAgjaEiqUiwnvtJ/znZMAAVPKR7pnsi6lqbA3BfXJ/IwMaNpdoGlCGVdDGeN4BuGCwnFtBVqKVvVg3DDg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxlint/linux-x64-musl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@oxlint/linux-x64-musl/-/linux-x64-musl-1.7.0.tgz", + "integrity": "sha512-5aIpemNUBvwMMk4MCx1V3M6R9eMB1/SS6/24Orax9FqaI1lDX08tySdv696sr4Lms9ocA+rotxIPW9NP9439vA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxlint/win32-arm64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@oxlint/win32-arm64/-/win32-arm64-1.7.0.tgz", + "integrity": "sha512-fpFpkHwbAu0NcR5bc1WapCPcM9qSYi5lCRVOp1WwDoFLKI2b9/UWB8OEg8UHWV5dnBu7HZAWH/SEslYGkZNsbQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxlint/win32-x64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@oxlint/win32-x64/-/win32-x64-1.7.0.tgz", + "integrity": "sha512-0EPWBWOiD3wZHgeWDlTUaiFzhzIonXykxYUC+NRerPQFkO/G+bd9uLMJddHDKqfP/7g8s3E5V6KvBvvFpb7U6g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -2850,9 +2843,9 @@ } }, "node_modules/@pkgr/core": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz", - "integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", "license": "MIT", "engines": { "node": "^12.20.0 || ^14.18.0 || >=16.0.0" @@ -2881,6 +2874,21 @@ "ws": "~7.5.10" } }, + "node_modules/@pm2/agent/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/@pm2/agent/node_modules/chalk": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", @@ -2894,12 +2902,47 @@ "node": ">=8" } }, + "node_modules/@pm2/agent/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@pm2/agent/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/@pm2/agent/node_modules/dayjs": { "version": "1.8.36", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.36.tgz", "integrity": "sha512-3VmRXEtw7RZKAf+4Tv1Ym9AGeo8r8+CjDi26x+7SYQil1UqtqdaokhzoEJohqlzt0m5kacJSDhJQkG/LWhpRBw==", "license": "MIT" }, + "node_modules/@pm2/agent/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/@pm2/agent/node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -2961,6 +3004,23 @@ "lodash": "^4.17.14" } }, + "node_modules/@pm2/io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/@pm2/io/node_modules/eventemitter2": { "version": "6.4.9", "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", @@ -2994,11 +3054,11 @@ "node": ">=10" } }, - "node_modules/@pm2/io/node_modules/tslib": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", - "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", - "license": "Apache-2.0" + "node_modules/@pm2/io/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" }, "node_modules/@pm2/io/node_modules/yallist": { "version": "4.0.0", @@ -3010,6 +3070,7 @@ "version": "0.8.0", "resolved": "https://registry.npmjs.org/@pm2/js-api/-/js-api-0.8.0.tgz", "integrity": "sha512-nmWzrA/BQZik3VBz+npRcNIu01kdBhWL0mxKmP1ciF/gTcujPTQqt027N9fc1pK9ERM8RipFhymw7RcmCyOEYA==", + "license": "Apache-2", "dependencies": { "async": "^2.6.3", "debug": "~4.3.1", @@ -3025,19 +3086,39 @@ "version": "2.6.4", "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "license": "MIT", "dependencies": { "lodash": "^4.17.14" } }, + "node_modules/@pm2/js-api/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/@pm2/js-api/node_modules/eventemitter2": { "version": "6.4.9", "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", - "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==" + "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==", + "license": "MIT" }, "node_modules/@pm2/pm2-version-check": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@pm2/pm2-version-check/-/pm2-version-check-1.0.4.tgz", "integrity": "sha512-SXsM27SGH3yTWKc2fKR4SYNxsmnvuBQ9dd6QHtEWmiZ/VqaOYPAIlS8+vMcn27YLtAEBGvNRSh3TPNvtjZgfqA==", + "license": "MIT", "dependencies": { "debug": "^4.3.1" } @@ -3048,20 +3129,21 @@ "integrity": "sha512-qv3jnwoakeax2razYaMsGI/luWdliBLHTdC6jU55hQt1hcFqzauH/HsBollQ7IR4ySTtYhT+xyHoijpA16C+tA==" }, "node_modules/@seald-io/nedb": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@seald-io/nedb/-/nedb-4.1.1.tgz", - "integrity": "sha512-u7fVfzKQ/3ZaIOnYQONf2lPZtGUeQtMPjfcaQkCw/GZv5dzn20qKW6sfN0NkVbr0ksJMlWcFXNGcXYsQSb1a1g==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@seald-io/nedb/-/nedb-4.1.2.tgz", + "integrity": "sha512-bDr6TqjBVS2rDyYM9CPxAnotj5FuNL9NF8o7h7YyFXM7yruqT4ddr+PkSb2mJvvw991bqdftazkEo38gykvaww==", "license": "MIT", "dependencies": { "@seald-io/binary-search-tree": "^1.0.3", - "localforage": "^1.9.0", - "util": "^0.12.4" + "localforage": "^1.10.0", + "util": "^0.12.5" } }, "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==" + "version": "0.34.38", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.38.tgz", + "integrity": "sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA==", + "license": "MIT" }, "node_modules/@sinonjs/commons": { "version": "3.0.1", @@ -3082,9 +3164,9 @@ } }, "node_modules/@swc/core": { - "version": "1.12.7", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.12.7.tgz", - "integrity": "sha512-bcpllEihyUSnqp0UtXTvXc19CT4wp3tGWLENhWnjr4B5iEOkzqMu+xHGz1FI5IBatjfqOQb29tgIfv6IL05QaA==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.1.tgz", + "integrity": "sha512-jEKKErLC6uwSqA+p6bmZR08usZM5Fpc+HdEu5CAzvye0q43yf1si1kjhHEa9XMkz0A2SAaal3eKCg/YYmtOsCA==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -3099,16 +3181,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.12.7", - "@swc/core-darwin-x64": "1.12.7", - "@swc/core-linux-arm-gnueabihf": "1.12.7", - "@swc/core-linux-arm64-gnu": "1.12.7", - "@swc/core-linux-arm64-musl": "1.12.7", - "@swc/core-linux-x64-gnu": "1.12.7", - "@swc/core-linux-x64-musl": "1.12.7", - "@swc/core-win32-arm64-msvc": "1.12.7", - "@swc/core-win32-ia32-msvc": "1.12.7", - "@swc/core-win32-x64-msvc": "1.12.7" + "@swc/core-darwin-arm64": "1.13.1", + "@swc/core-darwin-x64": "1.13.1", + "@swc/core-linux-arm-gnueabihf": "1.13.1", + "@swc/core-linux-arm64-gnu": "1.13.1", + "@swc/core-linux-arm64-musl": "1.13.1", + "@swc/core-linux-x64-gnu": "1.13.1", + "@swc/core-linux-x64-musl": "1.13.1", + "@swc/core-win32-arm64-msvc": "1.13.1", + "@swc/core-win32-ia32-msvc": "1.13.1", + "@swc/core-win32-x64-msvc": "1.13.1" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" @@ -3120,9 +3202,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.12.7", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.12.7.tgz", - "integrity": "sha512-w6BBT0hBRS56yS+LbReVym0h+iB7/PpCddqrn1ha94ra4rZ4R/A91A/rkv+LnQlPqU/+fhqdlXtCJU9mrhCBtA==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.13.1.tgz", + "integrity": "sha512-zO6SW/jSMTUORPm6dUZFPUwf+EFWZsaXWMGXadRG6akCofYpoQb8pcY2QZkVr43z8TMka6BtXpyoD/DJ0iOPHQ==", "cpu": [ "arm64" ], @@ -3136,9 +3218,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.12.7", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.12.7.tgz", - "integrity": "sha512-jN6LhFfGOpm4DY2mXPgwH4aa9GLOwublwMVFFZ/bGnHYYCRitLZs9+JWBbyWs7MyGcA246Ew+EREx36KVEAxjA==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.13.1.tgz", + "integrity": "sha512-8RjaTZYxrlYKE5PgzZYWSOT4mAsyhIuh30Nu4dnn/2r0Ef68iNCbvX4ynGnFMhOIhqunjQbJf+mJKpwTwdHXhw==", "cpu": [ "x64" ], @@ -3152,9 +3234,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.12.7", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.12.7.tgz", - "integrity": "sha512-rHn8XXi7G2StEtZRAeJ6c7nhJPDnqsHXmeNrAaYwk8Tvpa6ZYG2nT9E1OQNXj1/dfbSFTjdiA8M8ZvGYBlpBoA==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.13.1.tgz", + "integrity": "sha512-jEqK6pECs2m4BpL2JA/4CCkq04p6iFOEtVNXTisO+lJ3zwmxlnIEm9UfJZG6VSu8GS9MHRKGB0ieZ1tEdN1qDA==", "cpu": [ "arm" ], @@ -3168,9 +3250,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.12.7", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.12.7.tgz", - "integrity": "sha512-N15hKizSSh+hkZ2x3TDVrxq0TDcbvDbkQJi2ZrLb9fK+NdFUV/x+XF16ZDPlbxtrGXl1CT7VD439SNaMN9F7qw==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.13.1.tgz", + "integrity": "sha512-PbkuIOYXO/gQbWQ7NnYIwm59ygNqmUcF8LBeoKvxhx1VtOwE+9KiTfoplOikkPLhMiTzKsd8qentTslbITIg+Q==", "cpu": [ "arm64" ], @@ -3184,9 +3266,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.12.7", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.12.7.tgz", - "integrity": "sha512-jxyINtBezpxd3eIUDiDXv7UQ87YWlPsM9KumOwJk09FkFSO4oYxV2RT+Wu+Nt5tVWue4N0MdXT/p7SQsDEk4YA==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.13.1.tgz", + "integrity": "sha512-JaqFdBCarIBKiMu5bbAp+kWPMNGg97ej+7KzbKOzWP5pRptqKi86kCDZT3WmjPe8hNG6dvBwbm7Y8JNry5LebQ==", "cpu": [ "arm64" ], @@ -3200,9 +3282,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.12.7", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.12.7.tgz", - "integrity": "sha512-PR4tPVwU1BQBfFDk2XfzXxsEIjF3x/bOV1BzZpYvrlkU0TKUDbR4t2wzvsYwD/coW7/yoQmlL70/qnuPtTp1Zw==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.13.1.tgz", + "integrity": "sha512-t4cLkku10YECDaakWUH0452WJHIZtrLPRwezt6BdoMntVMwNjvXRX7C8bGuYcKC3YxRW7enZKFpozLhQIQ37oA==", "cpu": [ "x64" ], @@ -3216,9 +3298,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.12.7", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.12.7.tgz", - "integrity": "sha512-zy7JWfQtQItgMfUjSbbcS3DZqQUn2d9VuV0LSGpJxtTXwgzhRpF1S2Sj7cU9hGpbM27Y8RJ4DeFb3qbAufjbrw==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.13.1.tgz", + "integrity": "sha512-fSMwZOaG+3ukUucbEbzz9GhzGhUhXoCPqHe9qW0/Vc2IZRp538xalygKyZynYweH5d9EHux1aj3+IO8/xBaoiA==", "cpu": [ "x64" ], @@ -3232,9 +3314,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.12.7", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.12.7.tgz", - "integrity": "sha512-52PeF0tyX04ZFD8nibNhy/GjMFOZWTEWPmIB3wpD1vIJ1po+smtBnEdRRll5WIXITKoiND8AeHlBNBPqcsdcwA==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.13.1.tgz", + "integrity": "sha512-tweCXK/79vAwj1NhAsYgICy8T1z2QEairmN2BFEBYFBFNMEB1iI1YlXwBkBtuihRvgZrTh1ORusKa4jLYzLCZA==", "cpu": [ "arm64" ], @@ -3248,9 +3330,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.12.7", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.12.7.tgz", - "integrity": "sha512-WzQwkNMuhB1qQShT9uUgz/mX2j7NIEPExEtzvGsBT7TlZ9j1kGZ8NJcZH/fwOFcSJL4W7DnkL7nAhx6DBlSPaA==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.13.1.tgz", + "integrity": "sha512-zi7hO9D+2R2yQN9D7T10/CAI9KhuXkNkz8tcJOW6+dVPtAk/gsIC5NoGPELjgrAlLL9CS38ZQpLDslLfpP15ng==", "cpu": [ "ia32" ], @@ -3264,9 +3346,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.12.7", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.12.7.tgz", - "integrity": "sha512-R52ivBi2lgjl+Bd3XCPum0YfgbZq/W1AUExITysddP9ErsNSwnreYyNB3exEijiazWGcqHEas2ChiuMOP7NYrA==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.13.1.tgz", + "integrity": "sha512-KubYjzqs/nz3H69ncX/XHKsC8c1xqc7UvonQAj26BhbL22HBsqdAaVutZ+Obho6RMpd3F5qQ95ldavUTWskRrw==", "cpu": [ "x64" ], @@ -3282,15 +3364,16 @@ "node_modules/@swc/counter": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==" + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "license": "Apache-2.0" }, "node_modules/@swc/jest": { - "version": "0.2.38", - "resolved": "https://registry.npmjs.org/@swc/jest/-/jest-0.2.38.tgz", - "integrity": "sha512-HMoZgXWMqChJwffdDjvplH53g9G2ALQes3HKXDEdliB/b85OQ0CTSbxG8VSeCwiAn7cOaDVEt4mwmZvbHcS52w==", + "version": "0.2.39", + "resolved": "https://registry.npmjs.org/@swc/jest/-/jest-0.2.39.tgz", + "integrity": "sha512-eyokjOwYd0Q8RnMHri+8/FS1HIrIUKK/sRrFp8c1dThUOfNeCWbLmBP1P5VsKdvmkd25JaH+OKYwEYiAYg9YAA==", "license": "MIT", "dependencies": { - "@jest/create-cache-key-function": "^29.7.0", + "@jest/create-cache-key-function": "^30.0.0", "@swc/counter": "^0.1.3", "jsonc-parser": "^3.2.0" }, @@ -3317,15 +3400,22 @@ "license": "MIT" }, "node_modules/@tybys/wasm-util": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", - "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz", + "integrity": "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==", "license": "MIT", "optional": true, "dependencies": { "tslib": "^2.4.0" } }, + "node_modules/@tybys/wasm-util/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -3377,9 +3467,10 @@ } }, "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" }, "node_modules/@types/fs-extra": { "version": "11.0.4", @@ -3408,9 +3499,10 @@ "license": "MIT" }, "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "license": "MIT", "dependencies": { "@types/istanbul-lib-coverage": "*" } @@ -3455,16 +3547,10 @@ "integrity": "sha512-DIUKT4mkbTBxSrX6lmnQR888ObeFVVo1uNEqBH5/ddQHpnG4CA24DibpK7aO8QAcJEZUTcIx0F96TWuzVT9Z4g==", "license": "MIT" }, - "node_modules/@types/lodash": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-NYqRyg/hIQrYPT9lbOeYc3kIRabJDn/k4qQHIXUpx88CBDww2fD15Sg5kbXlW86zm2XEW4g0QxkTI3/Kfkc7xQ==", - "license": "MIT" - }, "node_modules/@types/node": { - "version": "24.0.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.7.tgz", - "integrity": "sha512-YIEUUr4yf8q8oQoXPpSlnvKNVKDQlPMWrmOcgzoduo7kvA2UF0/BwJ/eMKFTiTtkNL17I0M6Xe2tvwFU7be6iw==", + "version": "24.0.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.15.tgz", + "integrity": "sha512-oaeTSbCef7U/z7rDeJA138xpG3NuKc64/rZ2qmUFkFJmnMsAPaluIifqyWd8hSSMxyP9oie3dLAqYPblag9KgA==", "license": "MIT", "dependencies": { "undici-types": "~7.8.0" @@ -3489,13 +3575,20 @@ "license": "MIT" }, "node_modules/@types/through": { - "version": "0.0.31", - "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.31.tgz", - "integrity": "sha512-LpKpmb7FGevYgXnBXYs6HWnmiFyVG07Pt1cnbgM1IhEacITTiUaBXXvOR3Y50ksaJWGSfhbEvQFivQEFGCC55w==", + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.33.tgz", + "integrity": "sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==", + "license": "MIT", "dependencies": { "@types/node": "*" } }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" + }, "node_modules/@types/yargs": { "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", @@ -3506,21 +3599,22 @@ } }, "node_modules/@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.0.tgz", - "integrity": "sha512-ijItUYaiWuce0N1SoSMrEd0b6b6lYkYt99pqCPfybd+HKVXtEvYhICfLdwp42MhiI5mp0oq7PKEL+g1cNiz/Eg==", + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.37.0.tgz", + "integrity": "sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA==", "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.35.0", - "@typescript-eslint/type-utils": "8.35.0", - "@typescript-eslint/utils": "8.35.0", - "@typescript-eslint/visitor-keys": "8.35.0", + "@typescript-eslint/scope-manager": "8.37.0", + "@typescript-eslint/type-utils": "8.37.0", + "@typescript-eslint/utils": "8.37.0", + "@typescript-eslint/visitor-keys": "8.37.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -3534,7 +3628,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.35.0", + "@typescript-eslint/parser": "^8.37.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } @@ -3549,15 +3643,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.35.0.tgz", - "integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==", + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.37.0.tgz", + "integrity": "sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA==", "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.35.0", - "@typescript-eslint/types": "8.35.0", - "@typescript-eslint/typescript-estree": "8.35.0", - "@typescript-eslint/visitor-keys": "8.35.0", + "@typescript-eslint/scope-manager": "8.37.0", + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/typescript-estree": "8.37.0", + "@typescript-eslint/visitor-keys": "8.37.0", "debug": "^4.3.4" }, "engines": { @@ -3573,13 +3667,13 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.0.tgz", - "integrity": "sha512-41xatqRwWZuhUMF/aZm2fcUsOFKNcG28xqRSS6ZVr9BVJtGExosLAm5A1OxTjRMagx8nJqva+P5zNIGt8RIgbQ==", + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.37.0.tgz", + "integrity": "sha512-BIUXYsbkl5A1aJDdYJCBAo8rCEbAvdquQ8AnLb6z5Lp1u3x5PNgSSx9A/zqYc++Xnr/0DVpls8iQ2cJs/izTXA==", "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.35.0", - "@typescript-eslint/types": "^8.35.0", + "@typescript-eslint/tsconfig-utils": "^8.37.0", + "@typescript-eslint/types": "^8.37.0", "debug": "^4.3.4" }, "engines": { @@ -3594,13 +3688,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.0.tgz", - "integrity": "sha512-+AgL5+mcoLxl1vGjwNfiWq5fLDZM1TmTPYs2UkyHfFhgERxBbqHlNjRzhThJqz+ktBqTChRYY6zwbMwy0591AA==", + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.37.0.tgz", + "integrity": "sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.35.0", - "@typescript-eslint/visitor-keys": "8.35.0" + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/visitor-keys": "8.37.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3611,9 +3705,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.0.tgz", - "integrity": "sha512-04k/7247kZzFraweuEirmvUj+W3bJLI9fX6fbo1Qm2YykuBvEhRTPl8tcxlYO8kZZW+HIXfkZNoasVb8EV4jpA==", + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.37.0.tgz", + "integrity": "sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3627,13 +3721,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.35.0.tgz", - "integrity": "sha512-ceNNttjfmSEoM9PW87bWLDEIaLAyR+E6BoYJQ5PfaDau37UGca9Nyq3lBk8Bw2ad0AKvYabz6wxc7DMTO2jnNA==", + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.37.0.tgz", + "integrity": "sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow==", "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.35.0", - "@typescript-eslint/utils": "8.35.0", + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/typescript-estree": "8.37.0", + "@typescript-eslint/utils": "8.37.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -3650,9 +3745,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.0.tgz", - "integrity": "sha512-0mYH3emanku0vHw2aRLNGqe7EXh9WHEhi7kZzscrMDf6IIRUQ5Jk4wp1QrledE/36KtdZrVfKnE32eZCf/vaVQ==", + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.37.0.tgz", + "integrity": "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3663,15 +3758,15 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.0.tgz", - "integrity": "sha512-F+BhnaBemgu1Qf8oHrxyw14wq6vbL8xwWKKMwTMwYIRmFFY/1n/9T/jpbobZL8vp7QyEUcC6xGrnAO4ua8Kp7w==", + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.37.0.tgz", + "integrity": "sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg==", "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.35.0", - "@typescript-eslint/tsconfig-utils": "8.35.0", - "@typescript-eslint/types": "8.35.0", - "@typescript-eslint/visitor-keys": "8.35.0", + "@typescript-eslint/project-service": "8.37.0", + "@typescript-eslint/tsconfig-utils": "8.37.0", + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/visitor-keys": "8.37.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -3715,15 +3810,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.0.tgz", - "integrity": "sha512-nqoMu7WWM7ki5tPgLVsmPM8CkqtoPUG6xXGeefM5t4x3XumOEKMoUZPdi+7F+/EotukN4R9OWdmDxN80fqoZeg==", + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.37.0.tgz", + "integrity": "sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A==", "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.35.0", - "@typescript-eslint/types": "8.35.0", - "@typescript-eslint/typescript-estree": "8.35.0" + "@typescript-eslint/scope-manager": "8.37.0", + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/typescript-estree": "8.37.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3738,12 +3833,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.0.tgz", - "integrity": "sha512-zTh2+1Y8ZpmeQaQVIc/ZZxsx8UzgKJyNg1PTvjzC7WMhPSVS8bfDX34k1SrwOf016qd5RU3az2UxUNue3IfQ5g==", + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.37.0.tgz", + "integrity": "sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/types": "8.37.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -3773,9 +3868,9 @@ "license": "ISC" }, "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.9.2.tgz", - "integrity": "sha512-tS+lqTU3N0kkthU+rYp0spAYq15DU8ld9kXkaKg9sbQqJNF+WPMuNHZQGCgdxrUOEO0j22RKMwRVhF1HTl+X8A==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", "cpu": [ "arm" ], @@ -3786,9 +3881,9 @@ ] }, "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.9.2.tgz", - "integrity": "sha512-MffGiZULa/KmkNjHeuuflLVqfhqLv1vZLm8lWIyeADvlElJ/GLSOkoUX+5jf4/EGtfwrNFcEaB8BRas03KT0/Q==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", "cpu": [ "arm64" ], @@ -3799,9 +3894,9 @@ ] }, "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.9.2.tgz", - "integrity": "sha512-dzJYK5rohS1sYl1DHdJ3mwfwClJj5BClQnQSyAgEfggbUwA9RlROQSSbKBLqrGfsiC/VyrDPtbO8hh56fnkbsQ==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", "cpu": [ "arm64" ], @@ -3812,9 +3907,9 @@ ] }, "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.9.2.tgz", - "integrity": "sha512-gaIMWK+CWtXcg9gUyznkdV54LzQ90S3X3dn8zlh+QR5Xy7Y+Efqw4Rs4im61K1juy4YNb67vmJsCDAGOnIeffQ==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", "cpu": [ "x64" ], @@ -3825,9 +3920,9 @@ ] }, "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.9.2.tgz", - "integrity": "sha512-S7QpkMbVoVJb0xwHFwujnwCAEDe/596xqY603rpi/ioTn9VDgBHnCCxh+UFrr5yxuMH+dliHfjwCZJXOPJGPnw==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", "cpu": [ "x64" ], @@ -3838,9 +3933,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.9.2.tgz", - "integrity": "sha512-+XPUMCuCCI80I46nCDFbGum0ZODP5NWGiwS3Pj8fOgsG5/ctz+/zzuBlq/WmGa+EjWZdue6CF0aWWNv84sE1uw==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", "cpu": [ "arm" ], @@ -3851,9 +3946,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.9.2.tgz", - "integrity": "sha512-sqvUyAd1JUpwbz33Ce2tuTLJKM+ucSsYpPGl2vuFwZnEIg0CmdxiZ01MHQ3j6ExuRqEDUCy8yvkDKvjYFPb8Zg==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", "cpu": [ "arm" ], @@ -3864,9 +3959,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.9.2.tgz", - "integrity": "sha512-UYA0MA8ajkEDCFRQdng/FVx3F6szBvk3EPnkTTQuuO9lV1kPGuTB+V9TmbDxy5ikaEgyWKxa4CI3ySjklZ9lFA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", "cpu": [ "arm64" ], @@ -3877,9 +3972,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.9.2.tgz", - "integrity": "sha512-P/CO3ODU9YJIHFqAkHbquKtFst0COxdphc8TKGL5yCX75GOiVpGqd1d15ahpqu8xXVsqP4MGFP2C3LRZnnL5MA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", "cpu": [ "arm64" ], @@ -3890,9 +3985,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.9.2.tgz", - "integrity": "sha512-uKStFlOELBxBum2s1hODPtgJhY4NxYJE9pAeyBgNEzHgTqTiVBPjfTlPFJkfxyTjQEuxZbbJlJnMCrRgD7ubzw==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", "cpu": [ "ppc64" ], @@ -3903,9 +3998,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.9.2.tgz", - "integrity": "sha512-LkbNnZlhINfY9gK30AHs26IIVEZ9PEl9qOScYdmY2o81imJYI4IMnJiW0vJVtXaDHvBvxeAgEy5CflwJFIl3tQ==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", "cpu": [ "riscv64" ], @@ -3916,9 +4011,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.9.2.tgz", - "integrity": "sha512-vI+e6FzLyZHSLFNomPi+nT+qUWN4YSj8pFtQZSFTtmgFoxqB6NyjxSjAxEC1m93qn6hUXhIsh8WMp+fGgxCoRg==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", "cpu": [ "riscv64" ], @@ -3929,9 +4024,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.9.2.tgz", - "integrity": "sha512-sSO4AlAYhSM2RAzBsRpahcJB1msc6uYLAtP6pesPbZtptF8OU/CbCPhSRW6cnYOGuVmEmWVW5xVboAqCnWTeHQ==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", "cpu": [ "s390x" ], @@ -3942,9 +4037,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.9.2.tgz", - "integrity": "sha512-jkSkwch0uPFva20Mdu8orbQjv2A3G88NExTN2oPTI1AJ+7mZfYW3cDCTyoH6OnctBKbBVeJCEqh0U02lTkqD5w==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", "cpu": [ "x64" ], @@ -3955,9 +4050,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.9.2.tgz", - "integrity": "sha512-Uk64NoiTpQbkpl+bXsbeyOPRpUoMdcUqa+hDC1KhMW7aN1lfW8PBlBH4mJ3n3Y47dYE8qi0XTxy1mBACruYBaw==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", "cpu": [ "x64" ], @@ -3968,9 +4063,9 @@ ] }, "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.9.2.tgz", - "integrity": "sha512-EpBGwkcjDicjR/ybC0g8wO5adPNdVuMrNalVgYcWi+gYtC1XYNuxe3rufcO7dA76OHGeVabcO6cSkPJKVcbCXQ==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", "cpu": [ "wasm32" ], @@ -3984,9 +4079,9 @@ } }, "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.9.2.tgz", - "integrity": "sha512-EdFbGn7o1SxGmN6aZw9wAkehZJetFPao0VGZ9OMBwKx6TkvDuj6cNeLimF/Psi6ts9lMOe+Dt6z19fZQ9Ye2fw==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", "cpu": [ "arm64" ], @@ -3997,9 +4092,9 @@ ] }, "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.9.2.tgz", - "integrity": "sha512-JY9hi1p7AG+5c/dMU8o2kWemM8I6VZxfGwn1GCtf3c5i+IKcMo2NQ8OjZ4Z3/itvY/Si3K10jOBQn7qsD/whUA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", "cpu": [ "ia32" ], @@ -4010,9 +4105,9 @@ ] }, "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.9.2.tgz", - "integrity": "sha512-ryoo+EB19lMxAd80ln9BVf8pdOAxLb97amrQ3SFN9OCRn/5M5wvwDgAe4i8ZjhpbiHoDeP8yavcTEnpKBo7lZg==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", "cpu": [ "x64" ], @@ -4025,12 +4120,14 @@ "node_modules/@zeit/schemas": { "version": "2.36.0", "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.36.0.tgz", - "integrity": "sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==" + "integrity": "sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==", + "license": "MIT" }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" @@ -4055,14 +4152,16 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", "engines": { "node": ">= 14" } @@ -4071,6 +4170,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -4101,6 +4201,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "license": "ISC", "dependencies": { "string-width": "^4.1.0" } @@ -4109,6 +4210,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "license": "MIT", "engines": { "node": ">=6" } @@ -4117,6 +4219,7 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "license": "MIT", "dependencies": { "type-fest": "^0.21.3" }, @@ -4128,22 +4231,24 @@ } }, "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" @@ -4162,6 +4267,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -4187,21 +4293,20 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" }, "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" }, "node_modules/ast-types": { "version": "0.13.4", @@ -4215,6 +4320,12 @@ "node": ">=4" } }, + "node_modules/ast-types/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -4224,7 +4335,8 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" }, "node_modules/available-typed-arrays": { "version": "1.0.7", @@ -4253,25 +4365,32 @@ } }, "node_modules/axios-cache-interceptor": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/axios-cache-interceptor/-/axios-cache-interceptor-0.10.3.tgz", - "integrity": "sha512-oyHlhmA6zzZJDk/ZMPWPNmO3z8gBU3mWIqAZy+GIUsvwpmwyPlC2XvZ3PTOZHgpWI2kEocMUhk3+w9VwMXfZ4w==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/axios-cache-interceptor/-/axios-cache-interceptor-1.8.0.tgz", + "integrity": "sha512-cTNnPGJyQkxnWp0EWvE3NRvgURU5cWw/Qx3dIhXyHSM4Ip0c7EEe0I3an0Jwa549m1CAOg57ibj27YRNLmQCcg==", + "license": "MIT", "dependencies": { - "cache-parser": "^1.2.4", - "fast-defer": "^1.1.7", - "object-code": "^1.2.2" + "cache-parser": "1.2.5", + "fast-defer": "1.1.8", + "object-code": "1.3.3" + }, + "engines": { + "node": ">=12" }, "funding": { - "url": "https://github.com/ArthurFiorette/axios-cache-interceptor?sponsor=1" + "url": "https://github.com/arthurfiorette/axios-cache-interceptor?sponsor=1" + }, + "peerDependencies": { + "axios": "^1" } }, "node_modules/axios-cookiejar-support": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/axios-cookiejar-support/-/axios-cookiejar-support-6.0.2.tgz", - "integrity": "sha512-UO/g6DKfVoxnZkZz1NN669bMDjGV3snZnAZGZqIwEd8FdvFI17/rXLyMBm1j1cgtb2O6Jyi4MJ7ll49NPBEMNg==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/axios-cookiejar-support/-/axios-cookiejar-support-6.0.4.tgz", + "integrity": "sha512-4Bzj+l63eGwnWDBFdJHeGS6Ij3ytpyqvo//ocsb5kCLN/rKthzk27Afh2iSkZtuudOBkHUWWIcyCb4GKhXqovQ==", "license": "MIT", "dependencies": { - "http-cookie-agent": "^7.0.1" + "http-cookie-agent": "^7.0.2" }, "engines": { "node": ">=20.0.0" @@ -4284,50 +4403,13 @@ "tough-cookie": ">=4.0.0" } }, - "node_modules/axios-cookiejar-support/node_modules/http-cookie-agent": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/http-cookie-agent/-/http-cookie-agent-7.0.1.tgz", - "integrity": "sha512-lZHFZUdPTw64PdksQac5xbUd4NWjUbyDYnvR//2sbLpcC4UqEUW0x/6O+rDntVzJzJ07QvhtL5XZSC+c5EK+IQ==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.3" - }, - "engines": { - "node": ">=20.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/3846masa" - }, - "peerDependencies": { - "tough-cookie": "^4.0.0 || ^5.0.0", - "undici": "^7.0.0" - }, - "peerDependenciesMeta": { - "undici": { - "optional": true - } - } - }, - "node_modules/axios-mock-adapter": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/axios-mock-adapter/-/axios-mock-adapter-1.20.0.tgz", - "integrity": "sha512-shZRhTjLP0WWdcvHKf3rH3iW9deb3UdKbdnKUoHmmsnBhVXN3sjPJM6ZvQ2r/ywgvBVQrMnjrSyQab60G1sr2w==", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "is-blob": "^2.1.0", - "is-buffer": "^2.0.5" - }, - "peerDependencies": { - "axios": ">= 0.9.0" - } - }, "node_modules/babel-jest": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.0.2.tgz", - "integrity": "sha512-A5kqR1/EUTidM2YC2YMEUDP2+19ppgOwK0IAd9Swc3q2KqFb5f9PtRUXVeZcngu0z5mDMyZ9zH2huJZSOMLiTQ==", + "version": "30.0.4", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.0.4.tgz", + "integrity": "sha512-UjG2j7sAOqsp2Xua1mS/e+ekddkSu3wpf4nZUSvXNHuVWdaOUXQ77+uyjJLDE9i0atm5x4kds8K9yb5lRsRtcA==", "license": "MIT", "dependencies": { - "@jest/transform": "30.0.2", + "@jest/transform": "30.0.4", "@types/babel__core": "^7.20.5", "babel-plugin-istanbul": "^7.0.0", "babel-preset-jest": "30.0.1", @@ -4342,6 +4424,21 @@ "@babel/core": "^7.11.0" } }, + "node_modules/babel-jest/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/babel-jest/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -4358,6 +4455,24 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/babel-jest/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/babel-jest/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/babel-plugin-istanbul": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.0.tgz", @@ -4433,7 +4548,8 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" }, "node_modules/basic-ftp": { "version": "5.0.5", @@ -4454,6 +4570,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", "engines": { "node": ">=8" }, @@ -4465,6 +4582,7 @@ "version": "0.1.81", "resolved": "https://registry.npmjs.org/blessed/-/blessed-0.1.81.tgz", "integrity": "sha512-LoF5gae+hlmfORcG1M5+5XZi4LBmvlXTzwJWzUlPryN/SJdSflZvROM2TwkT0GMpq7oqT48NRd4GS7BiVBc5OQ==", + "license": "MIT", "bin": { "blessed": "bin/tput.js" }, @@ -4475,17 +4593,20 @@ "node_modules/bodec": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/bodec/-/bodec-0.1.0.tgz", - "integrity": "sha512-Ylo+MAo5BDUq1KA3f3R/MFhh+g8cnHmo8bz3YPGhI1znrMaf77ol1sfvYJzsw3nTE+Y2GryfDxBaR+AqpAkEHQ==" + "integrity": "sha512-Ylo+MAo5BDUq1KA3f3R/MFhh+g8cnHmo8bz3YPGhI1znrMaf77ol1sfvYJzsw3nTE+Y2GryfDxBaR+AqpAkEHQ==", + "license": "MIT" }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" }, "node_modules/boxen": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.0.tgz", "integrity": "sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==", + "license": "MIT", "dependencies": { "ansi-align": "^3.0.1", "camelcase": "^7.0.0", @@ -4503,21 +4624,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/boxen/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, "node_modules/boxen/node_modules/ansi-styles": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -4529,6 +4640,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", + "license": "MIT", "engines": { "node": ">=14.16" }, @@ -4539,12 +4651,14 @@ "node_modules/boxen/node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" }, "node_modules/boxen/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -4557,24 +4671,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/boxen/node_modules/strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/boxen/node_modules/type-fest": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=12.20" }, @@ -4586,6 +4687,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -4612,6 +4714,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", "dependencies": { "fill-range": "^7.1.1" }, @@ -4663,20 +4766,23 @@ "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" }, "node_modules/bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/cache-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/cache-parser/-/cache-parser-1.2.4.tgz", - "integrity": "sha512-O0KwuHuJnbHUrghHi2kGp0SxnWSIBXTYt7M8WVhW0kbPRUNUKoE/Of6e1rRD6AAxmfxFunKnt90yEK09D+sc5g==" + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/cache-parser/-/cache-parser-1.2.5.tgz", + "integrity": "sha512-Md/4VhAHByQ9frQ15WD6LrMNiVw9AEl/J7vWIXw+sxT6fSOpbtt6LHTp76vy8+bOESPBO94117Hm2bIjlI7XjA==", + "license": "MIT" }, "node_modules/call-bind": { "version": "1.0.8", @@ -4729,6 +4835,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", "engines": { "node": ">=6" } @@ -4743,9 +4850,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001726", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", - "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", + "version": "1.0.30001727", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", + "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==", "funding": [ { "type": "opencollective", @@ -4765,7 +4872,8 @@ "node_modules/cdata": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/cdata/-/cdata-0.1.3.tgz", - "integrity": "sha512-z0R4cT5357OEAVkP1CEFTHz1egpu2gYiWm2WJOY/sQDhojEXUYL4m3v2kYi5wER3PkMRL+GgfDhed2kGzrHSZA==" + "integrity": "sha512-z0R4cT5357OEAVkP1CEFTHz1egpu2gYiWm2WJOY/sQDhojEXUYL4m3v2kYi5wER3PkMRL+GgfDhed2kGzrHSZA==", + "license": "MIT" }, "node_modules/chalk": { "version": "5.4.1", @@ -4783,6 +4891,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", + "license": "MIT", "dependencies": { "chalk": "^4.1.2" }, @@ -4793,6 +4902,21 @@ "url": "https://github.com/chalk/chalk-template?sponsor=1" } }, + "node_modules/chalk-template/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/chalk-template/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -4809,6 +4933,24 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chalk-template/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/chalk-template/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/char-regex": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", @@ -4827,7 +4969,8 @@ "node_modules/charm": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/charm/-/charm-0.1.2.tgz", - "integrity": "sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==" + "integrity": "sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==", + "license": "MIT/X11" }, "node_modules/cheerio": { "version": "1.1.0", @@ -4858,6 +5001,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", "css-select": "^5.1.0", @@ -4874,6 +5018,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -4897,6 +5042,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, @@ -4905,9 +5051,9 @@ } }, "node_modules/ci-info": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.2.0.tgz", - "integrity": "sha512-cYY9mypksY8NRqgDB1XD1RiJL338v/551niynFTGkZOO2LHuB2OmOYxDIe/ttN9AHwrqdum1360G3ald0W9kCg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", + "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==", "funding": [ { "type": "github", @@ -4929,6 +5075,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -4940,6 +5087,7 @@ "version": "3.12.0", "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", + "license": "MIT", "dependencies": { "string-width": "^4.2.3" }, @@ -4958,10 +5106,26 @@ "node": ">=8.10.0" } }, + "node_modules/cli-tableau/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/cli-tableau/node_modules/chalk": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4970,6 +5134,24 @@ "node": ">=8" } }, + "node_modules/cli-tableau/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/cli-tableau/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/cli-width": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", @@ -4983,6 +5165,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-3.0.0.tgz", "integrity": "sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==", + "license": "MIT", "dependencies": { "arch": "^2.2.0", "execa": "^5.1.1", @@ -5009,6 +5192,60 @@ "node": ">=12" } }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/cliui/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/cliui/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -5046,53 +5283,42 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "license": "MIT", "dependencies": { "color-convert": "^1.9.3", "color-string": "^1.6.0" } }, "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "color-name": "1.1.3" } }, "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" }, "node_modules/color-string": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, - "node_modules/color/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, "node_modules/colorspace": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "license": "MIT", "dependencies": { "color": "^3.1.3", "text-hex": "1.0.x" @@ -5102,6 +5328,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -5122,6 +5349,7 @@ "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", "dependencies": { "mime-db": ">= 1.43.0 < 2" }, @@ -5133,6 +5361,7 @@ "version": "1.7.4", "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "license": "MIT", "dependencies": { "accepts": "~1.3.5", "bytes": "3.0.0", @@ -5150,6 +5379,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -5157,12 +5387,20 @@ "node_modules/compression/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" }, "node_modules/consola": { "version": "3.4.2", @@ -5177,6 +5415,7 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -5190,7 +5429,8 @@ "node_modules/croner": { "version": "4.1.97", "resolved": "https://registry.npmjs.org/croner/-/croner-4.1.97.tgz", - "integrity": "sha512-/f6gpQuxDaqXu+1kwQYSckUglPaOrHdbIlBAu0YuW8/Cdb45XwXYNUBXg3r/9Mo6n540Kn/smKcZWko5x99KrQ==" + "integrity": "sha512-/f6gpQuxDaqXu+1kwQYSckUglPaOrHdbIlBAu0YuW8/Cdb45XwXYNUBXg3r/9Mo6n540Kn/smKcZWko5x99KrQ==", + "license": "MIT" }, "node_modules/cross-env": { "version": "7.0.3", @@ -5214,6 +5454,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -5224,9 +5465,10 @@ } }, "node_modules/css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", @@ -5239,9 +5481,10 @@ } }, "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", "engines": { "node": ">= 6" }, @@ -5264,12 +5507,14 @@ "node_modules/culvert": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/culvert/-/culvert-0.1.2.tgz", - "integrity": "sha512-yi1x3EAWKjQTreYWeSd98431AV+IEE0qoDyOoaHJ7KJ21gv6HtBXHVLX74opVSGqcR8/AbjJBHAHpcOy2bj5Gg==" + "integrity": "sha512-yi1x3EAWKjQTreYWeSd98431AV+IEE0qoDyOoaHJ7KJ21gv6HtBXHVLX74opVSGqcR8/AbjJBHAHpcOy2bj5Gg==", + "license": "MIT" }, "node_modules/curl-generator": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/curl-generator/-/curl-generator-0.2.0.tgz", - "integrity": "sha512-KKTRYPMX3LnX45phiklGA+rv2W5mG0KD8sirV0yjtM7aliGMp5PIwqC5n74AFlwIHGMVsD9NKlyKpcYFA8bPog==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/curl-generator/-/curl-generator-0.4.2.tgz", + "integrity": "sha512-YD74vaPyL46XYNbyRCJV91EhYGDrE/EBiW0X/NUIrNZ23jD0Uwr/6vMrCmobYi5KrjtrqN4SnmMhQNYh3qaULw==", + "license": "MIT", "dependencies": { "ms": "^2.0.0" } @@ -5278,6 +5523,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/cwait/-/cwait-1.1.2.tgz", "integrity": "sha512-kIx8zE5jJ1iBgZytTr01aj57HdC+thPsg8W9Tw0gbf30/F7wfRRUS+BiXT90Dn+A0oGtF0xLT5293Ua4w/ZsNA==", + "license": "MIT", "dependencies": { "cdata": "^0.1.1" } @@ -5298,11 +5544,12 @@ "license": "MIT" }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -5313,11 +5560,6 @@ } } }, - "node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, "node_modules/dedent": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", @@ -5336,6 +5578,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", "engines": { "node": ">=4.0.0" } @@ -5343,7 +5586,8 @@ "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "license": "MIT" }, "node_modules/deepmerge": { "version": "4.3.1", @@ -5388,7 +5632,8 @@ "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -5406,6 +5651,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", @@ -5424,12 +5670,14 @@ "type": "github", "url": "https://github.com/sponsors/fb55" } - ] + ], + "license": "BSD-2-Clause" }, "node_modules/domhandler": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", "dependencies": { "domelementtype": "^2.3.0" }, @@ -5471,12 +5719,13 @@ "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.177", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.177.tgz", - "integrity": "sha512-7EH2G59nLsEMj97fpDuvVcYi6lwTcM1xuWw3PssD8xzboAW7zj7iB3COEEEATUfjLHrs5uKBLQT03V/8URx06g==", + "version": "1.5.187", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.187.tgz", + "integrity": "sha512-cl5Jc9I0KGUoOoSbxvTywTa40uspGJt/BDBoDLoxJRSBpWh4FFXBsjNRHfQrONsV/OoEjDfHUmZQa2d6Ze4YgA==", "license": "ISC" }, "node_modules/emittery": { @@ -5494,12 +5743,14 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" }, "node_modules/enabled": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", - "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "license": "MIT" }, "node_modules/encoding-sniffer": { "version": "0.2.1", @@ -5518,6 +5769,7 @@ "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "license": "MIT", "dependencies": { "ansi-colors": "^4.1.1" }, @@ -5526,9 +5778,10 @@ } }, "node_modules/entities": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", - "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", "engines": { "node": ">=0.12" }, @@ -5537,26 +5790,24 @@ } }, "node_modules/epg-grabber": { - "version": "0.38.0", - "resolved": "https://registry.npmjs.org/epg-grabber/-/epg-grabber-0.38.0.tgz", - "integrity": "sha512-jbwTgi6G7e+zrb2oNC0C7mcQYoRkFnvhXCurexeICaEy4avRB6WS5rD/yfqYoiqaXOM3x1BNBpCKFYoS7Ob5YA==", + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/epg-grabber/-/epg-grabber-0.41.0.tgz", + "integrity": "sha512-975WApyNb/ICZWYHxE2i/MDSDQOWZN0F+VARWZR1eFJovAs2CffQXJnG2Qx28RG312M3I+B+GkyPv2GxKxhQCA==", + "license": "MIT", "dependencies": { "axios": "^1.6.1", - "axios-cache-interceptor": "^0.10.3", - "axios-mock-adapter": "^1.20.0", - "commander": "^7.1.0", - "curl-generator": "^0.2.0", + "axios-cache-interceptor": "^1.8.0", + "commander": "^14.0.0", + "curl-generator": "^0.4.2", "cwait": "^1.1.2", - "dayjs": "^1.10.4", - "epg-parser": "^0.1.6", - "fs-extra": "^11.1.1", - "glob": "^7.1.6", - "http-cookie-agent": "^6.0.8", + "dayjs": "^1.11.13", + "epg-parser": "^0.3.1", + "fs-extra": "^11.3.0", + "glob": "^11.0.3", "lodash": "^4.17.21", "node-gzip": "^1.1.2", "socks-proxy-agent": "^8.0.5", - "tough-cookie": "^5.0.0", - "winston": "^3.3.3", + "winston": "^3.17.0", "xml-js": "^1.6.11" }, "bin": { @@ -5566,47 +5817,11 @@ "node": ">=10.0.0" } }, - "node_modules/epg-grabber/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "engines": { - "node": ">= 10" - } - }, - "node_modules/epg-grabber/node_modules/epg-parser": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/epg-parser/-/epg-parser-0.1.6.tgz", - "integrity": "sha512-g6AxKOvs0E4bTGPdIUh8/FDKdrVjbf4DVK0jIFuChDt7wBRJmMVyqbLeS8NApf6M2wpCRLBpIenXOCS88w0Rqw==", - "dependencies": { - "xml-js": "^1.6.11" - } - }, - "node_modules/epg-grabber/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/epg-parser": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/epg-parser/-/epg-parser-0.3.1.tgz", "integrity": "sha512-y131hXfDthUdSeKbN0Ru1wiFF5er4t/TLT+IaAnHF2CYB0cnygHTJteQMDYIlHWHDsGj+z9ejm1cU3saFNF3nQ==", + "license": "MIT", "dependencies": { "dayjs": "^1.11.6", "lodash": "^4.17.21", @@ -5617,6 +5832,7 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" } @@ -5667,10 +5883,11 @@ } }, "node_modules/esbuild": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", - "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", + "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", "hasInstallScript": true, + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, @@ -5678,31 +5895,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.2", - "@esbuild/android-arm": "0.25.2", - "@esbuild/android-arm64": "0.25.2", - "@esbuild/android-x64": "0.25.2", - "@esbuild/darwin-arm64": "0.25.2", - "@esbuild/darwin-x64": "0.25.2", - "@esbuild/freebsd-arm64": "0.25.2", - "@esbuild/freebsd-x64": "0.25.2", - "@esbuild/linux-arm": "0.25.2", - "@esbuild/linux-arm64": "0.25.2", - "@esbuild/linux-ia32": "0.25.2", - "@esbuild/linux-loong64": "0.25.2", - "@esbuild/linux-mips64el": "0.25.2", - "@esbuild/linux-ppc64": "0.25.2", - "@esbuild/linux-riscv64": "0.25.2", - "@esbuild/linux-s390x": "0.25.2", - "@esbuild/linux-x64": "0.25.2", - "@esbuild/netbsd-arm64": "0.25.2", - "@esbuild/netbsd-x64": "0.25.2", - "@esbuild/openbsd-arm64": "0.25.2", - "@esbuild/openbsd-x64": "0.25.2", - "@esbuild/sunos-x64": "0.25.2", - "@esbuild/win32-arm64": "0.25.2", - "@esbuild/win32-ia32": "0.25.2", - "@esbuild/win32-x64": "0.25.2" + "@esbuild/aix-ppc64": "0.25.8", + "@esbuild/android-arm": "0.25.8", + "@esbuild/android-arm64": "0.25.8", + "@esbuild/android-x64": "0.25.8", + "@esbuild/darwin-arm64": "0.25.8", + "@esbuild/darwin-x64": "0.25.8", + "@esbuild/freebsd-arm64": "0.25.8", + "@esbuild/freebsd-x64": "0.25.8", + "@esbuild/linux-arm": "0.25.8", + "@esbuild/linux-arm64": "0.25.8", + "@esbuild/linux-ia32": "0.25.8", + "@esbuild/linux-loong64": "0.25.8", + "@esbuild/linux-mips64el": "0.25.8", + "@esbuild/linux-ppc64": "0.25.8", + "@esbuild/linux-riscv64": "0.25.8", + "@esbuild/linux-s390x": "0.25.8", + "@esbuild/linux-x64": "0.25.8", + "@esbuild/netbsd-arm64": "0.25.8", + "@esbuild/netbsd-x64": "0.25.8", + "@esbuild/openbsd-arm64": "0.25.8", + "@esbuild/openbsd-x64": "0.25.8", + "@esbuild/openharmony-arm64": "0.25.8", + "@esbuild/sunos-x64": "0.25.8", + "@esbuild/win32-arm64": "0.25.8", + "@esbuild/win32-ia32": "0.25.8", + "@esbuild/win32-x64": "0.25.8" } }, "node_modules/escalade": { @@ -5715,11 +5933,15 @@ } }, "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", "engines": { - "node": ">=0.8.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/escodegen": { @@ -5744,18 +5966,18 @@ } }, "node_modules/eslint": { - "version": "9.30.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.30.0.tgz", - "integrity": "sha512-iN/SiPxmQu6EVkf+m1qpBxzUhE12YqFLOSySuOyVLJLEF9nzTf+h/1AJYc1JWzCnktggeNrjvQGLngDzXirU6g==", + "version": "9.31.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz", + "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==", "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", "@eslint/config-helpers": "^0.3.0", - "@eslint/core": "^0.14.0", + "@eslint/core": "^0.15.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.30.0", + "@eslint/js": "9.31.0", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -5804,9 +6026,9 @@ } }, "node_modules/eslint-config-prettier": { - "version": "10.1.5", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz", - "integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==", + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "license": "MIT", "bin": { "eslint-config-prettier": "bin/cli.js" @@ -5838,6 +6060,7 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -5845,6 +6068,21 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/eslint/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -5861,17 +6099,24 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "engines": { - "node": ">=10" + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=7.0.0" } }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/eslint/node_modules/eslint-visitor-keys": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", @@ -5884,49 +6129,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/espree": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", @@ -5973,6 +6175,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" }, @@ -5996,6 +6199,7 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -6004,6 +6208,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } @@ -6018,6 +6223,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "license": "MIT", "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", @@ -6036,6 +6242,12 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, "node_modules/exit-x": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", @@ -6046,14 +6258,14 @@ } }, "node_modules/expect": { - "version": "30.0.3", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.0.3.tgz", - "integrity": "sha512-HXg6NvK35/cSYZCUKAtmlgCFyqKM4frEPbzrav5hRqb0GMz0E0lS5hfzYjSaiaE5ysnp/qI2aeZkeyeIAOeXzQ==", + "version": "30.0.4", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.0.4.tgz", + "integrity": "sha512-dDLGjnP2cKbEppxVICxI/Uf4YemmGMPNy0QytCbfafbpYk9AFQsxb8Uyrxii0RPK7FWgLGlSem+07WirwS3cFQ==", "license": "MIT", "dependencies": { - "@jest/expect-utils": "30.0.3", + "@jest/expect-utils": "30.0.4", "@jest/get-type": "30.0.1", - "jest-matcher-utils": "30.0.3", + "jest-matcher-utils": "30.0.4", "jest-message-util": "30.0.2", "jest-mock": "30.0.2", "jest-util": "30.0.2" @@ -6092,6 +6304,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/extrareqp2/-/extrareqp2-1.0.0.tgz", "integrity": "sha512-Gum0g1QYb6wpPJCVypWP3bbIuaibcFiJcpuPM10YSXp/tzqi84x9PJageob+eN4xVRIOto4wjSGNLyMD54D2xA==", + "license": "MIT", "dependencies": { "follow-redirects": "^1.14.0" } @@ -6115,12 +6328,14 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" }, "node_modules/fast-defer": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/fast-defer/-/fast-defer-1.1.7.tgz", - "integrity": "sha512-tJ01ulDWT2WhqxMKS20nXX6wyX2iInBYpbN3GO7yjKwXMY4qvkdBRxak9IFwBLlFDESox+SwSvqMCZDfe1tqeg==" + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/fast-defer/-/fast-defer-1.1.8.tgz", + "integrity": "sha512-lEJeOH5VL5R09j6AA0D4Uvq7AgsHw0dAImQQ+F3iSyHZuAxyQfWobsagGpTcOPvJr3urmKRHrs+Gs9hV+/Qm/Q==", + "license": "MIT" }, "node_modules/fast-glob": { "version": "3.3.3", @@ -6159,12 +6374,14 @@ "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "license": "MIT" }, "node_modules/fastq": { "version": "1.19.1", @@ -6193,12 +6410,35 @@ "node_modules/fecha": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", - "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, + "node_modules/figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/figures/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "license": "MIT", "dependencies": { "flat-cache": "^4.0.0" }, @@ -6210,6 +6450,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -6218,22 +6459,26 @@ } }, "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "license": "MIT", "dependencies": { - "locate-path": "^5.0.0", + "locate-path": "^6.0.0", "path-exists": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "license": "MIT", "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" @@ -6243,25 +6488,28 @@ } }, "node_modules/flatted": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", - "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==" + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "license": "ISC" }, "node_modules/fn.name": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", - "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "license": "MIT" }, "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", "funding": [ { "type": "individual", "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -6302,22 +6550,10 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/form-data": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", - "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -6355,6 +6591,7 @@ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -6440,6 +6677,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -6448,9 +6686,10 @@ } }, "node_modules/get-tsconfig": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", - "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "license": "MIT", "dependencies": { "resolve-pkg-maps": "^1.0.0" }, @@ -6459,9 +6698,9 @@ } }, "node_modules/get-uri": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz", - "integrity": "sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", "license": "MIT", "dependencies": { "basic-ftp": "^5.0.2", @@ -6475,12 +6714,14 @@ "node_modules/git-node-fs": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/git-node-fs/-/git-node-fs-1.0.0.tgz", - "integrity": "sha512-bLQypt14llVXBg0S0u8q8HmU7g9p3ysH+NvVlae5vILuUvs759665HvmR5+wb04KjHyjFcDRxdYb4kyNnluMUQ==" + "integrity": "sha512-bLQypt14llVXBg0S0u8q8HmU7g9p3ysH+NvVlae5vILuUvs759665HvmR5+wb04KjHyjFcDRxdYb4kyNnluMUQ==", + "license": "MIT" }, "node_modules/git-sha1": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/git-sha1/-/git-sha1-0.1.2.tgz", - "integrity": "sha512-2e/nZezdVlyCopOCYHeW0onkbZg7xP1Ad6pndPy1rCygeRykefUS6r7oA5cJRGEFvseiaz5a/qUHFVX1dd6Isg==" + "integrity": "sha512-2e/nZezdVlyCopOCYHeW0onkbZg7xP1Ad6pndPy1rCygeRykefUS6r7oA5cJRGEFvseiaz5a/qUHFVX1dd6Isg==", + "license": "MIT" }, "node_modules/glob": { "version": "11.0.3", @@ -6509,6 +6750,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "license": "ISC", "dependencies": { "is-glob": "^4.0.3" }, @@ -6532,9 +6774,9 @@ } }, "node_modules/globals": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.2.0.tgz", - "integrity": "sha512-O+7l9tPdHCU320IigZZPj5zmRCFG9xHmx9cU8FqU2Rp+JN714seHV+2S9+JslCpY4gJwU2vOGox0wzgae/MCEg==", + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz", + "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==", "license": "MIT", "engines": { "node": ">=18" @@ -6564,12 +6806,14 @@ "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "license": "MIT" }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", "engines": { "node": ">=8" } @@ -6663,21 +6907,22 @@ } }, "node_modules/http-cookie-agent": { - "version": "6.0.8", - "resolved": "https://registry.npmjs.org/http-cookie-agent/-/http-cookie-agent-6.0.8.tgz", - "integrity": "sha512-qnYh3yLSr2jBsTYkw11elq+T361uKAJaZ2dR4cfYZChw1dt9uL5t3zSUwehoqqVb4oldk1BpkXKm2oat8zV+oA==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-cookie-agent/-/http-cookie-agent-7.0.2.tgz", + "integrity": "sha512-aHaES6SOFtnSlmWu0yEaaQvu+QexUG2gscSAvMhJ7auzW8r/jYOgGrzuAm9G9nHbksuhz7Lw4zOwDHmfQaxZvw==", + "license": "MIT", "dependencies": { - "agent-base": "^7.1.3" + "agent-base": "^7.1.4" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "funding": { "url": "https://github.com/sponsors/3846masa" }, "peerDependencies": { "tough-cookie": "^4.0.0 || ^5.0.0", - "undici": "^5.11.0 || ^6.0.0" + "undici": "^7.0.0" }, "peerDependenciesMeta": { "undici": { @@ -6715,6 +6960,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "license": "Apache-2.0", "engines": { "node": ">=10.17.0" } @@ -6723,6 +6969,7 @@ "version": "9.1.7", "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "license": "MIT", "bin": { "husky": "bin.js" }, @@ -6749,6 +6996,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "license": "MIT", "engines": { "node": ">= 4" } @@ -6760,9 +7008,10 @@ "license": "MIT" }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -6774,14 +7023,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "engines": { - "node": ">=4" - } - }, "node_modules/import-local": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", @@ -6804,7 +7045,8 @@ "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "license": "MIT", "engines": { "node": ">=0.8.19" } @@ -6823,25 +7065,27 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" }, "node_modules/ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" }, "node_modules/inquirer": { - "version": "12.6.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-12.6.3.tgz", - "integrity": "sha512-eX9beYAjr1MqYsIjx1vAheXsRk1jbZRvHLcBu5nA9wX0rXR1IfCZLnVLp4Ym4mrhqmh7AuANwcdtgQ291fZDfQ==", + "version": "12.7.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-12.7.0.tgz", + "integrity": "sha512-KKFRc++IONSyE2UYw9CJ1V0IWx5yQKomwB+pp3cWomWs+v2+ZsG11G2OVfAjFS6WWCppKw+RfKmpqGfSzD5QBQ==", "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.13", - "@inquirer/prompts": "^7.5.3", + "@inquirer/core": "^10.1.14", + "@inquirer/prompts": "^7.6.0", "@inquirer/type": "^3.0.7", "ansi-escapes": "^4.3.2", "mute-stream": "^2.0.0", - "run-async": "^3.0.0", + "run-async": "^4.0.4", "rxjs": "^7.8.2" }, "engines": { @@ -6856,18 +7100,11 @@ } } }, - "node_modules/inquirer/node_modules/mute-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", - "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, "node_modules/ip-address": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "license": "MIT", "dependencies": { "jsbn": "1.1.0", "sprintf-js": "^1.1.3" @@ -6879,7 +7116,8 @@ "node_modules/ip-address/node_modules/sprintf-js": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "license": "BSD-3-Clause" }, "node_modules/is-arguments": { "version": "1.2.0", @@ -6900,12 +7138,14 @@ "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" }, @@ -6913,39 +7153,6 @@ "node": ">=8" } }, - "node_modules/is-blob": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-blob/-/is-blob-2.1.0.tgz", - "integrity": "sha512-SZ/fTft5eUhQM6oF/ZaASFDEdbFVe89Imltn9uZr03wdKMcWNVYSMjQPFtg05QuNkt5l5c135ElvXEQG0rk4tw==", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "engines": { - "node": ">=4" - } - }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -6977,6 +7184,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", "bin": { "is-docker": "cli.js" }, @@ -6991,6 +7199,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -6999,6 +7208,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", "engines": { "node": ">=8" } @@ -7034,6 +7244,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" }, @@ -7045,6 +7256,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -7053,6 +7265,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-4.0.0.tgz", "integrity": "sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==", + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, @@ -7082,6 +7295,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", "engines": { "node": ">=8" }, @@ -7108,6 +7322,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", "dependencies": { "is-docker": "^2.0.0" }, @@ -7118,7 +7333,8 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", @@ -7202,15 +7418,15 @@ } }, "node_modules/jest": { - "version": "30.0.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.0.3.tgz", - "integrity": "sha512-Uy8xfeE/WpT2ZLGDXQmaYNzw2v8NUKuYeKGtkS6sDxwsdQihdgYCXaKIYnph1h95DN5H35ubFDm0dfmsQnjn4Q==", + "version": "30.0.4", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.0.4.tgz", + "integrity": "sha512-9QE0RS4WwTj/TtTC4h/eFVmFAhGNVerSB9XpJh8sqaXlP73ILcPcZ7JWjjEtJJe2m8QyBLKKfPQuK+3F+Xij/g==", "license": "MIT", "dependencies": { - "@jest/core": "30.0.3", + "@jest/core": "30.0.4", "@jest/types": "30.0.1", "import-local": "^3.2.0", - "jest-cli": "30.0.3" + "jest-cli": "30.0.4" }, "bin": { "jest": "bin/jest.js" @@ -7242,14 +7458,14 @@ } }, "node_modules/jest-circus": { - "version": "30.0.3", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.0.3.tgz", - "integrity": "sha512-rD9qq2V28OASJHJWDRVdhoBdRs6k3u3EmBzDYcyuMby8XCO3Ll1uq9kyqM41ZcC4fMiPulMVh3qMw0cBvDbnyg==", + "version": "30.0.4", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.0.4.tgz", + "integrity": "sha512-o6UNVfbXbmzjYgmVPtSQrr5xFZCtkDZGdTlptYvGFSN80RuOOlTe73djvMrs+QAuSERZWcHBNIOMH+OEqvjWuw==", "license": "MIT", "dependencies": { - "@jest/environment": "30.0.2", - "@jest/expect": "30.0.3", - "@jest/test-result": "30.0.2", + "@jest/environment": "30.0.4", + "@jest/expect": "30.0.4", + "@jest/test-result": "30.0.4", "@jest/types": "30.0.1", "@types/node": "*", "chalk": "^4.1.2", @@ -7257,10 +7473,10 @@ "dedent": "^1.6.0", "is-generator-fn": "^2.1.0", "jest-each": "30.0.2", - "jest-matcher-utils": "30.0.3", + "jest-matcher-utils": "30.0.4", "jest-message-util": "30.0.2", - "jest-runtime": "30.0.3", - "jest-snapshot": "30.0.3", + "jest-runtime": "30.0.4", + "jest-snapshot": "30.0.4", "jest-util": "30.0.2", "p-limit": "^3.1.0", "pretty-format": "30.0.2", @@ -7272,42 +7488,21 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-circus/node_modules/@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", + "node_modules/jest-circus/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.34.0" + "color-convert": "^2.0.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-circus/node_modules/@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" + "node": ">=8" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-circus/node_modules/@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", - "license": "MIT" - }, "node_modules/jest-circus/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -7324,19 +7519,37 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-cli": { - "version": "30.0.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.0.3.tgz", - "integrity": "sha512-UWDSj0ayhumEAxpYRlqQLrssEi29kdQ+kddP94AuHhZknrE+mT0cR0J+zMHKFe9XPfX3dKQOc2TfWki3WhFTsA==", + "node_modules/jest-circus/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "license": "MIT", "dependencies": { - "@jest/core": "30.0.3", - "@jest/test-result": "30.0.2", + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-circus/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/jest-cli": { + "version": "30.0.4", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.0.4.tgz", + "integrity": "sha512-3dOrP3zqCWBkjoVG1zjYJpD9143N9GUCbwaF2pFF5brnIgRLHmKcCIw+83BvF1LxggfMWBA0gxkn6RuQVuRhIQ==", + "license": "MIT", + "dependencies": { + "@jest/core": "30.0.4", + "@jest/test-result": "30.0.4", "@jest/types": "30.0.1", "chalk": "^4.1.2", "exit-x": "^0.2.2", "import-local": "^3.2.0", - "jest-config": "30.0.3", + "jest-config": "30.0.4", "jest-util": "30.0.2", "jest-validate": "30.0.2", "yargs": "^17.7.2" @@ -7356,42 +7569,21 @@ } } }, - "node_modules/jest-cli/node_modules/@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", + "node_modules/jest-cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.34.0" + "color-convert": "^2.0.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-cli/node_modules/@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" + "node": ">=8" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-cli/node_modules/@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", - "license": "MIT" - }, "node_modules/jest-cli/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -7408,29 +7600,47 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/jest-cli/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-cli/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/jest-config": { - "version": "30.0.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.0.3.tgz", - "integrity": "sha512-j0L4oRCtJwNyZktXIqwzEiDVQXBbQ4dqXuLD/TZdn++hXIcIfZmjHgrViEy5s/+j4HvITmAXbexVZpQ/jnr0bg==", + "version": "30.0.4", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.0.4.tgz", + "integrity": "sha512-3dzbO6sh34thAGEjJIW0fgT0GA0EVlkski6ZzMcbW6dzhenylXAE/Mj2MI4HonroWbkKc6wU6bLVQ8dvBSZ9lA==", "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", "@jest/get-type": "30.0.1", "@jest/pattern": "30.0.1", - "@jest/test-sequencer": "30.0.2", + "@jest/test-sequencer": "30.0.4", "@jest/types": "30.0.1", - "babel-jest": "30.0.2", + "babel-jest": "30.0.4", "chalk": "^4.1.2", "ci-info": "^4.2.0", "deepmerge": "^4.3.1", "glob": "^10.3.10", "graceful-fs": "^4.2.11", - "jest-circus": "30.0.3", + "jest-circus": "30.0.4", "jest-docblock": "30.0.1", - "jest-environment-node": "30.0.2", + "jest-environment-node": "30.0.4", "jest-regex-util": "30.0.1", "jest-resolve": "30.0.2", - "jest-runner": "30.0.3", + "jest-runner": "30.0.4", "jest-util": "30.0.2", "jest-validate": "30.0.2", "micromatch": "^4.0.8", @@ -7459,42 +7669,21 @@ } } }, - "node_modules/jest-config/node_modules/@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", + "node_modules/jest-config/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.34.0" + "color-convert": "^2.0.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-config/node_modules/@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" + "node": ">=8" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-config/node_modules/@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", - "license": "MIT" - }, "node_modules/jest-config/node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -7520,6 +7709,24 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/jest-config/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-config/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/jest-config/node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -7593,9 +7800,9 @@ } }, "node_modules/jest-diff": { - "version": "30.0.3", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.0.3.tgz", - "integrity": "sha512-Q1TAV0cUcBTic57SVnk/mug0/ASyAqtSIOkr7RAlxx97llRYsM74+E8N5WdGJUlwCKwgxPAkVjKh653h1+HA9A==", + "version": "30.0.4", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.0.4.tgz", + "integrity": "sha512-TSjceIf6797jyd+R64NXqicttROD+Qf98fex7CowmlSn7f8+En0da1Dglwr1AXxDtVizoxXYZBlUQwNhoOXkNw==", "license": "MIT", "dependencies": { "@jest/diff-sequences": "30.0.1", @@ -7607,6 +7814,21 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/jest-diff/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -7623,6 +7845,24 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/jest-diff/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-diff/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/jest-docblock": { "version": "30.0.1", "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.0.1.tgz", @@ -7651,42 +7891,21 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-each/node_modules/@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", + "node_modules/jest-each/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.34.0" + "color-convert": "^2.0.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-each/node_modules/@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" + "node": ">=8" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-each/node_modules/@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", - "license": "MIT" - }, "node_modules/jest-each/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -7703,14 +7922,32 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-environment-node": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.0.2.tgz", - "integrity": "sha512-XsGtZ0H+a70RsxAQkKuIh0D3ZlASXdZdhpOSBq9WRPq6lhe0IoQHGW0w9ZUaPiZQ/CpkIdprvlfV1QcXcvIQLQ==", + "node_modules/jest-each/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "license": "MIT", "dependencies": { - "@jest/environment": "30.0.2", - "@jest/fake-timers": "30.0.2", + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-each/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/jest-environment-node": { + "version": "30.0.4", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.0.4.tgz", + "integrity": "sha512-p+rLEzC2eThXqiNh9GHHTC0OW5Ca4ZfcURp7scPjYBcmgpR9HG6750716GuUipYf2AcThU3k20B31USuiaaIEg==", + "license": "MIT", + "dependencies": { + "@jest/environment": "30.0.4", + "@jest/fake-timers": "30.0.4", "@jest/types": "30.0.1", "@types/node": "*", "jest-mock": "30.0.2", @@ -7721,58 +7958,6 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-environment-node/node_modules/@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.34.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", - "license": "MIT" - }, - "node_modules/jest-environment-node/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/jest-haste-map": { "version": "30.0.2", "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.0.2.tgz", @@ -7797,58 +7982,6 @@ "fsevents": "^2.3.3" } }, - "node_modules/jest-haste-map/node_modules/@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.34.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-haste-map/node_modules/@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-haste-map/node_modules/@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", - "license": "MIT" - }, - "node_modules/jest-haste-map/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/jest-leak-detector": { "version": "30.0.2", "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.0.2.tgz", @@ -7863,20 +7996,35 @@ } }, "node_modules/jest-matcher-utils": { - "version": "30.0.3", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.0.3.tgz", - "integrity": "sha512-hMpVFGFOhYmIIRGJ0HgM9htC5qUiJ00famcc9sRFchJJiLZbbVKrAztcgE6VnXLRxA3XZ0bvNA7hQWh3oHXo/A==", + "version": "30.0.4", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.0.4.tgz", + "integrity": "sha512-ubCewJ54YzeAZ2JeHHGVoU+eDIpQFsfPQs0xURPWoNiO42LGJ+QGgfSf+hFIRplkZDkhH5MOvuxHKXRTUU3dUQ==", "license": "MIT", "dependencies": { "@jest/get-type": "30.0.1", "chalk": "^4.1.2", - "jest-diff": "30.0.3", + "jest-diff": "30.0.4", "pretty-format": "30.0.2" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/jest-matcher-utils/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -7893,6 +8041,24 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/jest-matcher-utils/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/jest-message-util": { "version": "30.0.2", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.0.2.tgz", @@ -7913,42 +8079,21 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-message-util/node_modules/@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.34.0" + "color-convert": "^2.0.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-message-util/node_modules/@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" + "node": ">=8" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-message-util/node_modules/@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==", - "license": "MIT" - }, "node_modules/jest-message-util/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -7965,6 +8110,24 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/jest-message-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-message-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/jest-mock": { "version": "30.0.2", "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.2.tgz", @@ -7979,62 +8142,11 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-mock/node_modules/@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.34.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-mock/node_modules/@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-mock/node_modules/@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==", - "license": "MIT" - }, - "node_modules/jest-mock/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/jest-offline": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/jest-offline/-/jest-offline-1.0.1.tgz", "integrity": "sha512-pcYJ8rVxWP3SS9de15iSQY87ErLGGgMC4qtVcRLb/qemrefI1IgnAzOusp0eemGu7JoAGlb4oBGnZorehu95KA==", + "license": "Apache 2.0", "dependencies": { "mitm": "^1.3.2" } @@ -8085,18 +8197,33 @@ } }, "node_modules/jest-resolve-dependencies": { - "version": "30.0.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.0.3.tgz", - "integrity": "sha512-FlL6u7LiHbF0Oe27k7DHYMq2T2aNpPhxnNo75F7lEtu4A6sSw+TKkNNUGNcVckdFoL0RCWREJsC1HsKDwKRZzQ==", + "version": "30.0.4", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.0.4.tgz", + "integrity": "sha512-EQBYow19B/hKr4gUTn+l8Z+YLlP2X0IoPyp0UydOtrcPbIOYzJ8LKdFd+yrbwztPQvmlBFUwGPPEzHH1bAvFAw==", "license": "MIT", "dependencies": { "jest-regex-util": "30.0.1", - "jest-snapshot": "30.0.3" + "jest-snapshot": "30.0.4" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-resolve/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/jest-resolve/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -8113,16 +8240,34 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-runner": { - "version": "30.0.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.0.3.tgz", - "integrity": "sha512-CxYBzu9WStOBBXAKkLXGoUtNOWsiS1RRmUQb6SsdUdTcqVncOau7m8AJ4cW3Mz+YL1O9pOGPSYLyvl8HBdFmkQ==", + "node_modules/jest-resolve/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "license": "MIT", "dependencies": { - "@jest/console": "30.0.2", - "@jest/environment": "30.0.2", - "@jest/test-result": "30.0.2", - "@jest/transform": "30.0.2", + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-resolve/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/jest-runner": { + "version": "30.0.4", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.0.4.tgz", + "integrity": "sha512-mxY0vTAEsowJwvFJo5pVivbCpuu6dgdXRmt3v3MXjBxFly7/lTk3Td0PaMyGOeNQUFmSuGEsGYqhbn7PA9OekQ==", + "license": "MIT", + "dependencies": { + "@jest/console": "30.0.4", + "@jest/environment": "30.0.4", + "@jest/test-result": "30.0.4", + "@jest/transform": "30.0.4", "@jest/types": "30.0.1", "@types/node": "*", "chalk": "^4.1.2", @@ -8130,14 +8275,14 @@ "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", "jest-docblock": "30.0.1", - "jest-environment-node": "30.0.2", + "jest-environment-node": "30.0.4", "jest-haste-map": "30.0.2", "jest-leak-detector": "30.0.2", "jest-message-util": "30.0.2", "jest-resolve": "30.0.2", - "jest-runtime": "30.0.3", + "jest-runtime": "30.0.4", "jest-util": "30.0.2", - "jest-watcher": "30.0.2", + "jest-watcher": "30.0.4", "jest-worker": "30.0.2", "p-limit": "^3.1.0", "source-map-support": "0.5.13" @@ -8146,42 +8291,21 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-runner/node_modules/@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", + "node_modules/jest-runner/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.34.0" + "color-convert": "^2.0.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-runner/node_modules/@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" + "node": ">=8" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-runner/node_modules/@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", - "license": "MIT" - }, "node_modules/jest-runner/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -8198,18 +8322,36 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-runtime": { - "version": "30.0.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.0.3.tgz", - "integrity": "sha512-Xjosq0C48G9XEQOtmgrjXJwPaUPaq3sPJwHDRaiC+5wi4ZWxO6Lx6jNkizK/0JmTulVNuxP8iYwt77LGnfg3/w==", + "node_modules/jest-runner/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "license": "MIT", "dependencies": { - "@jest/environment": "30.0.2", - "@jest/fake-timers": "30.0.2", - "@jest/globals": "30.0.3", + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-runner/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/jest-runtime": { + "version": "30.0.4", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.0.4.tgz", + "integrity": "sha512-tUQrZ8+IzoZYIHoPDQEB4jZoPyzBjLjq7sk0KVyd5UPRjRDOsN7o6UlvaGF8ddpGsjznl9PW+KRgWqCNO+Hn7w==", + "license": "MIT", + "dependencies": { + "@jest/environment": "30.0.4", + "@jest/fake-timers": "30.0.4", + "@jest/globals": "30.0.4", "@jest/source-map": "30.0.1", - "@jest/test-result": "30.0.2", - "@jest/transform": "30.0.2", + "@jest/test-result": "30.0.4", + "@jest/transform": "30.0.4", "@jest/types": "30.0.1", "@types/node": "*", "chalk": "^4.1.2", @@ -8222,7 +8364,7 @@ "jest-mock": "30.0.2", "jest-regex-util": "30.0.1", "jest-resolve": "30.0.2", - "jest-snapshot": "30.0.3", + "jest-snapshot": "30.0.4", "jest-util": "30.0.2", "slash": "^3.0.0", "strip-bom": "^4.0.0" @@ -8231,42 +8373,21 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-runtime/node_modules/@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", + "node_modules/jest-runtime/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.34.0" + "color-convert": "^2.0.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-runtime/node_modules/@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" + "node": ">=8" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-runtime/node_modules/@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", - "license": "MIT" - }, "node_modules/jest-runtime/node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -8292,6 +8413,24 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/jest-runtime/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-runtime/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/jest-runtime/node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -8365,9 +8504,9 @@ } }, "node_modules/jest-snapshot": { - "version": "30.0.3", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.0.3.tgz", - "integrity": "sha512-F05JCohd3OA1N9+5aEPXA6I0qOfZDGIx0zTq5Z4yMBg2i1p5ELfBusjYAWwTkC12c7dHcbyth4QAfQbS7cRjow==", + "version": "30.0.4", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.0.4.tgz", + "integrity": "sha512-S/8hmSkeUib8WRUq9pWEb5zMfsOjiYWDWzFzKnjX7eDyKKgimsu9hcmsUEg8a7dPAw8s/FacxsXquq71pDgPjQ==", "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", @@ -8375,17 +8514,17 @@ "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.0.3", + "@jest/expect-utils": "30.0.4", "@jest/get-type": "30.0.1", - "@jest/snapshot-utils": "30.0.1", - "@jest/transform": "30.0.2", + "@jest/snapshot-utils": "30.0.4", + "@jest/transform": "30.0.4", "@jest/types": "30.0.1", "babel-preset-current-node-syntax": "^1.1.0", "chalk": "^4.1.2", - "expect": "30.0.3", + "expect": "30.0.4", "graceful-fs": "^4.2.11", - "jest-diff": "30.0.3", - "jest-matcher-utils": "30.0.3", + "jest-diff": "30.0.4", + "jest-matcher-utils": "30.0.4", "jest-message-util": "30.0.2", "jest-util": "30.0.2", "pretty-format": "30.0.2", @@ -8396,42 +8535,21 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-snapshot/node_modules/@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", + "node_modules/jest-snapshot/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.34.0" + "color-convert": "^2.0.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" + "node": ">=8" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-snapshot/node_modules/@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", - "license": "MIT" - }, "node_modules/jest-snapshot/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -8448,6 +8566,24 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/jest-snapshot/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/jest-util": { "version": "30.0.2", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.2.tgz", @@ -8465,42 +8601,21 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-util/node_modules/@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", + "node_modules/jest-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.34.0" + "color-convert": "^2.0.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-util/node_modules/@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" + "node": ">=8" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-util/node_modules/@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==", - "license": "MIT" - }, "node_modules/jest-util/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -8517,10 +8632,28 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/jest-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/jest-util/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", "engines": { "node": ">=12" @@ -8546,42 +8679,21 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-validate/node_modules/@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", + "node_modules/jest-validate/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.34.0" + "color-convert": "^2.0.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-validate/node_modules/@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" + "node": ">=8" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-validate/node_modules/@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", - "license": "MIT" - }, "node_modules/jest-validate/node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -8610,13 +8722,31 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/jest-watcher": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.0.2.tgz", - "integrity": "sha512-vYO5+E7jJuF+XmONr6CrbXdlYrgvZqtkn6pdkgjt/dU64UAdc0v1cAVaAeWtAfUUMScxNmnUjKPUMdCpNVASwg==", + "node_modules/jest-validate/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "license": "MIT", "dependencies": { - "@jest/test-result": "30.0.2", + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-validate/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/jest-watcher": { + "version": "30.0.4", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.0.4.tgz", + "integrity": "sha512-YESbdHDs7aQOCSSKffG8jXqOKFqw4q4YqR+wHYpR5GWEQioGvL0BfbcjvKIvPEM0XGfsfJrka7jJz3Cc3gI4VQ==", + "license": "MIT", + "dependencies": { + "@jest/test-result": "30.0.4", "@jest/types": "30.0.1", "@types/node": "*", "ansi-escapes": "^4.3.2", @@ -8629,42 +8759,21 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-watcher/node_modules/@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", + "node_modules/jest-watcher/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.34.0" + "color-convert": "^2.0.1" }, "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest-watcher/node_modules/@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" + "node": ">=8" }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-watcher/node_modules/@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", - "license": "MIT" - }, "node_modules/jest-watcher/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -8681,6 +8790,24 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/jest-watcher/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-watcher/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/jest-worker": { "version": "30.0.2", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.0.2.tgz", @@ -8712,62 +8839,11 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/jest/node_modules/@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.34.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest/node_modules/@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "license": "MIT", - "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/jest/node_modules/@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==", - "license": "MIT" - }, - "node_modules/jest/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/js-git": { "version": "0.7.8", "resolved": "https://registry.npmjs.org/js-git/-/js-git-0.7.8.tgz", "integrity": "sha512-+E5ZH/HeRnoc/LW0AmAyhU+mNcWBzAKE+30+IDMLSLbbK+Tdt02AdkOKq9u15rlJsDEGFqtgckc8ZM59LhhiUA==", + "license": "MIT", "dependencies": { "bodec": "^0.1.0", "culvert": "^0.1.2", @@ -8778,7 +8854,8 @@ "node_modules/js-git/node_modules/pako": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", - "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==" + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "license": "MIT" }, "node_modules/js-tokens": { "version": "4.0.0", @@ -8787,13 +8864,12 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "license": "MIT", "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" @@ -8802,7 +8878,8 @@ "node_modules/jsbn": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "license": "MIT" }, "node_modules/jsesc": { "version": "3.1.0", @@ -8819,12 +8896,14 @@ "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "license": "MIT" }, "node_modules/json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "license": "MIT" }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", @@ -8835,17 +8914,20 @@ "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "license": "MIT" }, "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC", "optional": true }, "node_modules/json5": { @@ -8863,12 +8945,14 @@ "node_modules/jsonc-parser": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", - "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==" + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "license": "MIT" }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "license": "MIT", "dependencies": { "universalify": "^2.0.0" }, @@ -8880,6 +8964,7 @@ "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } @@ -8887,12 +8972,14 @@ "node_modules/kuler": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", - "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" }, "node_modules/langs": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/langs/-/langs-2.0.0.tgz", - "integrity": "sha512-v4pxOBEQVN1WBTfB1crhTtxzNLZU9HPWgadlwzWKISJtt6Ku/CnpBrwVy+jFv8StjxsPfwPFzO0CMwdZLJ0/BA==" + "integrity": "sha512-v4pxOBEQVN1WBTfB1crhTtxzNLZU9HPWgadlwzWKISJtt6Ku/CnpBrwVy+jFv8StjxsPfwPFzO0CMwdZLJ0/BA==", + "license": "MIT" }, "node_modules/leven": { "version": "3.1.0", @@ -8907,6 +8994,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -8919,6 +9007,7 @@ "version": "0.5.0", "resolved": "https://registry.npmjs.org/libxml2-wasm/-/libxml2-wasm-0.5.0.tgz", "integrity": "sha512-ANq8aMCg/+pYJv3QqgrvYzJldvm2P2V2T08303AVyzjdeCuOAOjxPUSazQj/NA2+rOcS9BMx/HTTtq1I2g8foQ==", + "license": "MIT", "engines": { "node": ">=16" } @@ -8941,7 +9030,8 @@ "node_modules/load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "license": "MIT", "dependencies": { "graceful-fs": "^4.1.2", "parse-json": "^4.0.0", @@ -8955,7 +9045,8 @@ "node_modules/load-json-file/node_modules/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "license": "MIT", "dependencies": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" @@ -8964,18 +9055,11 @@ "node": ">=4" } }, - "node_modules/load-json-file/node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "engines": { - "node": ">=4" - } - }, "node_modules/load-json-file/node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "license": "MIT", "engines": { "node": ">=4" } @@ -8990,37 +9074,47 @@ } }, "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "license": "MIT", "dependencies": { - "p-locate": "^4.1.0" + "p-locate": "^5.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "license": "MIT" }, "node_modules/logform": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.4.0.tgz", - "integrity": "sha512-CPSJw4ftjf517EhXZGGvTHHkYobo7ZCc0kvwUoOYcjfR2UVrI66RHj8MCrfAdEitdmFqbu2BYdYs8FHHZSb6iw==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", "dependencies": { - "@colors/colors": "1.5.0", + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", "fecha": "^4.2.0", "ms": "^2.1.1", "safe-stable-stringify": "^2.3.1", "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" } }, "node_modules/lru-cache": { @@ -9033,9 +9127,9 @@ } }, "node_modules/luxon": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.6.1.tgz", - "integrity": "sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==", + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.1.tgz", + "integrity": "sha512-RkRWjA926cTvz5rAb1BqyWkKbbjzCGchDUIKMCUvNi17j6f6j8uHGDV82Aqcqtzd+icoYpELmG3ksgGiFNNcNg==", "license": "MIT", "engines": { "node": ">=12" @@ -9077,7 +9171,8 @@ "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" }, "node_modules/merge2": { "version": "1.4.1", @@ -9105,6 +9200,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -9113,6 +9209,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -9124,6 +9221,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", "engines": { "node": ">=6" } @@ -9132,6 +9230,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -9140,9 +9239,13 @@ } }, "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/minipass": { "version": "7.1.2", @@ -9157,6 +9260,7 @@ "version": "1.7.3", "resolved": "https://registry.npmjs.org/mitm/-/mitm-1.7.3.tgz", "integrity": "sha512-linie/mGisDH73C7aiW6JmstA5XskXd15JBJAEeNQBdH3/L0dJdE/yZ+rw/y2zT7Fcib5KAnL5OvxYOOFQbsgw==", + "license": "AGPL-3.0-or-later WITH GPL-3.0-linking-exception", "dependencies": { "semver": ">= 5 < 6" }, @@ -9168,6 +9272,7 @@ "version": "5.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "license": "ISC", "bin": { "semver": "bin/semver" } @@ -9176,6 +9281,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", "bin": { "mkdirp": "bin/cmd.js" }, @@ -9186,7 +9292,8 @@ "node_modules/mockdate": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/mockdate/-/mockdate-3.0.5.tgz", - "integrity": "sha512-iniQP4rj1FhBdBYS/+eQv7j1tadJ9lJtdzgOpvsOHng/GbcDh2Fhdeq+ZRldrPYdXvCyfFUmFeEwEGXZB5I/AQ==" + "integrity": "sha512-iniQP4rj1FhBdBYS/+eQv7j1tadJ9lJtdzgOpvsOHng/GbcDh2Fhdeq+ZRldrPYdXvCyfFUmFeEwEGXZB5I/AQ==", + "license": "MIT" }, "node_modules/module-details-from-path": { "version": "1.0.4", @@ -9197,17 +9304,22 @@ "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, "node_modules/napi-postinstall": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.2.5.tgz", - "integrity": "sha512-kmsgUvCRIJohHjbZ3V8avP0I1Pekw329MVAMDzVxsrkjgdnqiwvMX5XwR+hWV66vsAtZ+iM+fVnq8RTQawUmCQ==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.2.tgz", + "integrity": "sha512-tWVJxJHmBWLy69PvO96TZMZDrzmw5KeiZBz3RHmiM2XZ9grBJ2WgMAFVVg25nqp3ZjTFUs2Ftw1JhscL3Teliw==", "license": "MIT", "bin": { "napi-postinstall": "lib/cli.js" @@ -9222,7 +9334,8 @@ "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=" + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "license": "MIT" }, "node_modules/natural-orderby": { "version": "5.0.0", @@ -9246,6 +9359,7 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==", + "license": "MIT", "dependencies": { "debug": "^3.2.6", "iconv-lite": "^0.4.4", @@ -9262,6 +9376,7 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", "dependencies": { "ms": "^2.1.1" } @@ -9282,6 +9397,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -9298,17 +9414,20 @@ "node_modules/node-cleanup": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/node-cleanup/-/node-cleanup-2.1.2.tgz", - "integrity": "sha1-esGavSl+Caf3KnFUXZUbUX5N3iw=" + "integrity": "sha512-qN8v/s2PAJwGUtr1/hYTpNKlD6Y9rc4p8KSmJXyGdYGZsDGKXrGThikLFP9OCHFeLeEpQzPwiAtdIvBLqm//Hw==", + "license": "MIT" }, "node_modules/node-ensure": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/node-ensure/-/node-ensure-0.0.0.tgz", - "integrity": "sha1-7K52QVDemYYexcgQ/V0Jaxg5Mqc=" + "integrity": "sha512-DRI60hzo2oKN1ma0ckc6nQWlHU69RH6xN0sjQTjMpChPfTYvKZdcQFfdYK2RWbJcKyUizSIy/l8OTGxMAM1QDw==", + "license": "MIT" }, "node_modules/node-gzip": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/node-gzip/-/node-gzip-1.1.2.tgz", - "integrity": "sha512-ZB6zWpfZHGtxZnPMrJSKHVPrRjURoUzaDbLFj3VO70mpLTW5np96vXyHwft4Id0o+PYIzgDkBUjIzaNHhQ8srw==" + "integrity": "sha512-ZB6zWpfZHGtxZnPMrJSKHVPrRjURoUzaDbLFj3VO70mpLTW5np96vXyHwft4Id0o+PYIzgDkBUjIzaNHhQ8srw==", + "license": "MIT" }, "node_modules/node-int64": { "version": "0.4.0", @@ -9326,6 +9445,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -9334,6 +9454,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -9345,6 +9466,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "license": "MIT", "dependencies": { "path-key": "^3.0.0" }, @@ -9356,6 +9478,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0" }, @@ -9367,19 +9490,22 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/numeral/-/numeral-2.0.6.tgz", "integrity": "sha512-qaKRmtYPZ5qdw4jWJD6bxEf1FJEqllJrwxCLIm0sQU/A7v2/czigzOb+C2uSiFsa9lBUzeH7M1oK+Q+OLxL3kA==", + "license": "MIT", "engines": { "node": "*" } }, "node_modules/object-code": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/object-code/-/object-code-1.2.2.tgz", - "integrity": "sha512-ZSbEQdei4ElzuDM4BmazKSwINacocBf3/8rte25aNqXzvT/8dSaNVY9egsjAaBL/UhW55JNxAvXOKPIsL2MwWQ==" + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/object-code/-/object-code-1.3.3.tgz", + "integrity": "sha512-/Ds4Xd5xzrtUOJ+xJQ57iAy0BZsZltOHssnDgcZ8DOhgh41q1YJCnTPnWdWSLkNGNnxYzhYChjc5dgC9mEERCA==", + "license": "MIT" }, "node_modules/object-treeify": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-2.1.1.tgz", "integrity": "sha512-ofXhazOvXTYWbbibExMiS+asaTbYG/ZWopVroXFFOdjmc8ehXMq9R2VUaTx/C3CnZkQbT52wAZT4DrBLK/nQfw==", + "license": "MIT", "engines": { "node": ">= 12" } @@ -9388,6 +9514,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -9405,6 +9532,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "license": "MIT", "dependencies": { "fn.name": "1.x.x" } @@ -9413,6 +9541,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" }, @@ -9424,16 +9553,17 @@ } }, "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "license": "MIT", "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" }, "engines": { "node": ">= 0.8.0" @@ -9448,10 +9578,37 @@ "node": ">=0.10.0" } }, + "node_modules/oxlint": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.7.0.tgz", + "integrity": "sha512-krJN1fIRhs3xK1FyVyPtYIV9tkT4WDoIwI7eiMEKBuCjxqjQt5ZemQm1htPvHqNDOaWFRFt4btcwFdU8bbwgvA==", + "license": "MIT", + "bin": { + "oxc_language_server": "bin/oxc_language_server", + "oxlint": "bin/oxlint" + }, + "engines": { + "node": ">=8.*" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxlint/darwin-arm64": "1.7.0", + "@oxlint/darwin-x64": "1.7.0", + "@oxlint/linux-arm64-gnu": "1.7.0", + "@oxlint/linux-arm64-musl": "1.7.0", + "@oxlint/linux-x64-gnu": "1.7.0", + "@oxlint/linux-x64-musl": "1.7.0", + "@oxlint/win32-arm64": "1.7.0", + "@oxlint/win32-x64": "1.7.0" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" }, @@ -9463,27 +9620,15 @@ } }, "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "license": "MIT", "dependencies": { - "p-limit": "^2.2.0" + "p-limit": "^3.0.2" }, "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -9539,12 +9684,14 @@ "node_modules/pako": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", - "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "license": "(MIT AND Zlib)" }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -9629,6 +9776,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", "engines": { "node": ">=8" } @@ -9645,12 +9793,14 @@ "node_modules/path-is-inside": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==" + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "license": "(WTFPL OR MIT)" }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", "engines": { "node": ">=8" } @@ -9689,12 +9839,14 @@ "node_modules/path-to-regexp": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", - "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==" + "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", + "license": "MIT" }, "node_modules/pdf-parse": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pdf-parse/-/pdf-parse-1.1.1.tgz", "integrity": "sha512-v6ZJ/efsBpGrGGknjtq9J/oC8tZWq0KWL5vQrk2GlzLEQPUDB1ex+13Rmidl1neNN358Jn9EHZw5y07FFtaC7A==", + "license": "MIT", "dependencies": { "debug": "^3.1.0", "node-ensure": "^0.0.0" @@ -9707,6 +9859,7 @@ "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", "dependencies": { "ms": "^2.1.1" } @@ -9721,6 +9874,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -9732,6 +9886,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-3.0.2.tgz", "integrity": "sha512-g0VU+y08pKw5M8EZ2rIGiEBaB8wrQMjYGFfW2QVIfyT8V+fq8YFLkvlz4bz5ljvFDJYNFCWT3PWqcRr2FKO81w==", + "license": "MIT", "dependencies": { "safe-buffer": "^5.2.1" }, @@ -9739,24 +9894,14 @@ "node": ">=10" } }, - "node_modules/pidusage/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "license": "MIT", + "engines": { + "node": ">=4" + } }, "node_modules/pirates": { "version": "4.0.7", @@ -9770,7 +9915,8 @@ "node_modules/pkg-conf": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", - "integrity": "sha1-ISZRTKbyq/69FoWW3xi6V4Z/AFg=", + "integrity": "sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==", + "license": "MIT", "dependencies": { "find-up": "^2.0.0", "load-json-file": "^4.0.0" @@ -9782,7 +9928,8 @@ "node_modules/pkg-conf/node_modules/find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "license": "MIT", "dependencies": { "locate-path": "^2.0.0" }, @@ -9793,7 +9940,8 @@ "node_modules/pkg-conf/node_modules/locate-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "license": "MIT", "dependencies": { "p-locate": "^2.0.0", "path-exists": "^3.0.0" @@ -9806,6 +9954,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "license": "MIT", "dependencies": { "p-try": "^1.0.0" }, @@ -9816,7 +9965,8 @@ "node_modules/pkg-conf/node_modules/p-locate": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "license": "MIT", "dependencies": { "p-limit": "^1.1.0" }, @@ -9827,7 +9977,8 @@ "node_modules/pkg-conf/node_modules/p-try": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "license": "MIT", "engines": { "node": ">=4" } @@ -9835,7 +9986,8 @@ "node_modules/pkg-conf/node_modules/path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "license": "MIT", "engines": { "node": ">=4" } @@ -9852,6 +10004,58 @@ "node": ">=8" } }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/pm2": { "version": "6.0.8", "resolved": "https://registry.npmjs.org/pm2/-/pm2-6.0.8.tgz", @@ -9928,22 +10132,11 @@ "node": ">=5" } }, - "node_modules/pm2-axon/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/pm2-deploy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pm2-deploy/-/pm2-deploy-1.0.2.tgz", "integrity": "sha512-YJx6RXKrVrWaphEYf++EdOOx9EH18vM8RSZN/P1Y+NokTKqYAca/ejXwVLyiEpNju4HPZEk3Y2uZouwMqUlcgg==", + "license": "MIT", "dependencies": { "run-series": "^1.1.8", "tv4": "^1.3.0" @@ -9956,6 +10149,7 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/pm2-multimeter/-/pm2-multimeter-0.1.2.tgz", "integrity": "sha512-S+wT6XfyKfd7SJIBqRgOctGxaBzUOmVQzTAS+cg04TsEUObJVreha7lvCfX8zzGVr871XwCSnHUU7DQQ5xEsfA==", + "license": "MIT/X11", "dependencies": { "charm": "~0.1.1" } @@ -9964,6 +10158,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/pm2-sysmonit/-/pm2-sysmonit-1.2.8.tgz", "integrity": "sha512-ACOhlONEXdCTVwKieBIQLSi2tQZ8eKinhcr9JpZSUAL8Qy0ajIgRtsLxG/lwPOW3JEKqPyw/UaHmTWhUzpP4kA==", + "license": "Apache", "optional": true, "dependencies": { "async": "^3.2.0", @@ -9977,6 +10172,7 @@ "version": "2.0.21", "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-2.0.21.tgz", "integrity": "sha512-cv3xAQos+pugVX+BfXpHsbyz/dLzX+lr44zNMsYiGxUw+kV5sgQCIcLd1z+0vq+KyC7dJ+/ts2PsfgWfSC3WXA==", + "license": "MIT", "optional": true, "dependencies": { "safe-buffer": "^5.2.1" @@ -9985,78 +10181,22 @@ "node": ">=8" } }, - "node_modules/pm2-sysmonit/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "optional": true - }, - "node_modules/pm2/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, "node_modules/pm2/node_modules/commander": { "version": "2.15.1", "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==" - }, - "node_modules/pm2/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/pm2/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "license": "MIT" }, "node_modules/pm2/node_modules/source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, - "node_modules/pm2/node_modules/sprintf-js": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", - "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==" - }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -10070,10 +10210,26 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "license": "MIT", "engines": { "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-format": { "version": "30.0.2", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.2.tgz", @@ -10088,40 +10244,11 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/pretty-format/node_modules/@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.34.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/pretty-format/node_modules/@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==", - "license": "MIT" - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/promptly": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/promptly/-/promptly-2.2.0.tgz", "integrity": "sha512-aC9j+BZsRSSzEsXBNBwDnAxujdx19HycZoKgRgzWnS8eOHg1asuf9heuLprfbe739zY3IdUQx+Egv6Jn135WHA==", + "license": "MIT", "dependencies": { "read": "^1.0.4" } @@ -10157,12 +10284,14 @@ "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", "engines": { "node": ">=6" } @@ -10207,6 +10336,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -10215,6 +10345,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -10229,6 +10360,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -10243,6 +10375,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", + "license": "ISC", "dependencies": { "mute-stream": "~0.0.4" }, @@ -10250,10 +10383,17 @@ "node": ">=0.8" } }, + "node_modules/read/node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "license": "ISC" + }, "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -10267,6 +10407,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", "dependencies": { "picomatch": "^2.2.1" }, @@ -10277,12 +10418,14 @@ "node_modules/readline": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz", - "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==" + "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==", + "license": "BSD" }, "node_modules/registry-auth-token": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", + "license": "MIT", "dependencies": { "rc": "^1.1.6", "safe-buffer": "^5.0.1" @@ -10292,6 +10435,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==", + "license": "MIT", "dependencies": { "rc": "^1.0.1" }, @@ -10312,6 +10456,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -10362,7 +10507,7 @@ "node": ">=8" } }, - "node_modules/resolve-from": { + "node_modules/resolve-cwd/node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", @@ -10371,10 +10516,20 @@ "node": ">=8" } }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "license": "MIT", "funding": { "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } @@ -10390,9 +10545,14 @@ } }, "node_modules/run-async": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", - "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-4.0.4.tgz", + "integrity": "sha512-2cgeRHnV11lSXBEhq7sN7a5UVjTKm9JTb9x8ApIT//16D7QL96AgnNeWSGoB4gIHc0iYw/Ha0Z+waBaCYZVNhg==", + "license": "MIT", + "dependencies": { + "oxlint": "^1.2.0", + "prettier": "^3.5.3" + }, "engines": { "node": ">=0.12.0" } @@ -10424,6 +10584,7 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/run-script-os/-/run-script-os-1.1.6.tgz", "integrity": "sha512-ql6P2LzhBTTDfzKts+Qo4H94VUKpxKDFz6QxxwaUZN0mwvi7L3lpOI7BqPCq7lgDh3XLl0dpeXwfcVIitlrYrw==", + "license": "MIT", "bin": { "run-os": "index.js", "run-script-os": "index.js" @@ -10446,20 +10607,43 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/rxjs": { "version": "7.8.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" } }, + "node_modules/rxjs/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" }, "node_modules/safe-regex-test": { "version": "1.1.0", @@ -10479,9 +10663,10 @@ } }, "node_modules/safe-stable-stringify": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.3.1.tgz", - "integrity": "sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", "engines": { "node": ">=10" } @@ -10489,12 +10674,14 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" }, "node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "license": "ISC" }, "node_modules/semver": { "version": "7.7.2", @@ -10512,6 +10699,7 @@ "version": "14.2.4", "resolved": "https://registry.npmjs.org/serve/-/serve-14.2.4.tgz", "integrity": "sha512-qy1S34PJ/fcY8gjVGszDB3EXiPSk5FKhUa7tQe0UPRddxRidc2V6cNHPNewbE1D7MAkgLuWEt3Vw56vYy73tzQ==", + "license": "MIT", "dependencies": { "@zeit/schemas": "2.36.0", "ajv": "8.12.0", @@ -10536,6 +10724,7 @@ "version": "6.1.6", "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz", "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==", + "license": "MIT", "dependencies": { "bytes": "3.0.0", "content-disposition": "0.5.2", @@ -10550,6 +10739,7 @@ "version": "1.33.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -10558,6 +10748,7 @@ "version": "2.1.18", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "license": "MIT", "dependencies": { "mime-db": "~1.33.0" }, @@ -10569,6 +10760,7 @@ "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -10584,6 +10776,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==", + "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, @@ -10594,7 +10787,8 @@ "node_modules/serve/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" }, "node_modules/set-function-length": { "version": "1.2.2", @@ -10617,6 +10811,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" }, @@ -10628,6 +10823,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", "engines": { "node": ">=8" } @@ -10639,14 +10835,22 @@ "license": "BSD-2-Clause" }, "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/signale": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/signale/-/signale-1.4.0.tgz", "integrity": "sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==", + "license": "MIT", "dependencies": { "chalk": "^2.3.2", "figures": "^2.0.0", @@ -10660,6 +10864,7 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "license": "MIT", "dependencies": { "color-convert": "^1.9.0" }, @@ -10671,6 +10876,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -10680,34 +10886,20 @@ "node": ">=4" } }, - "node_modules/signale/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/signale/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "node_modules/signale/node_modules/figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, + "node_modules/signale/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", "engines": { - "node": ">=4" + "node": ">=0.8.0" } }, "node_modules/signale/node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", "engines": { "node": ">=4" } @@ -10716,6 +10908,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", "dependencies": { "has-flag": "^3.0.0" }, @@ -10726,7 +10919,8 @@ "node_modules/simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", "dependencies": { "is-arrayish": "^0.3.1" } @@ -10734,13 +10928,15 @@ "node_modules/simple-swizzle/node_modules/is-arrayish": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" }, "node_modules/skip-postinstall": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/skip-postinstall/-/skip-postinstall-1.0.0.tgz", "integrity": "sha512-IUVEmm4v7Ubzrp9JDG15oTzMB+abJdHcduXMRzBlHnHRrmpQ/QoPtYCRaorP+abAULTGEh87gPPyyMK5H1X1Dg==", "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT", "bin": { "skip-postinstall": "index.js" } @@ -10758,15 +10954,17 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", "engines": { "node": ">= 6.0.0", "npm": ">= 3.0.0" } }, "node_modules/socks": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", - "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.6.tgz", + "integrity": "sha512-pe4Y2yzru68lXCb38aAqRf5gvN8YdjP1lok5o0J7BOHljkyCGKVz7H3vpVIXKD27rj2giOJ7DwVyk/GWrPHDWA==", + "license": "MIT", "dependencies": { "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" @@ -10780,6 +10978,7 @@ "version": "8.0.5", "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "license": "MIT", "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", @@ -10793,6 +10992,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -10808,9 +11008,9 @@ } }, "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", "license": "BSD-3-Clause" }, "node_modules/srcset": { @@ -10828,7 +11028,8 @@ "node_modules/stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", "engines": { "node": "*" } @@ -10858,29 +11059,11 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" } }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -10894,10 +11077,32 @@ "node": ">=10" } }, + "node_modules/string-length/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-length/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -10922,10 +11127,20 @@ "node": ">=8" } }, - "node_modules/strip-ansi": { + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -10933,6 +11148,42 @@ "node": ">=8" } }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/strip-ansi-cjs": { "name": "strip-ansi", "version": "6.0.1", @@ -10946,6 +11197,15 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -10959,6 +11219,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "license": "MIT", "engines": { "node": ">=6" } @@ -10967,6 +11228,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "license": "MIT", "engines": { "node": ">=8" }, @@ -10978,6 +11240,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -10998,12 +11261,12 @@ } }, "node_modules/synckit": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.8.tgz", - "integrity": "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==", + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", + "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", "license": "MIT", "dependencies": { - "@pkgr/core": "^0.2.4" + "@pkgr/core": "^0.2.9" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -11013,9 +11276,10 @@ } }, "node_modules/systeminformation": { - "version": "5.25.11", - "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.25.11.tgz", - "integrity": "sha512-jI01fn/t47rrLTQB0FTlMCC+5dYx8o0RRF+R4BPiUNsvg5OdY0s9DKMFmJGrx5SwMZQ4cag0Gl6v8oycso9b/g==", + "version": "5.27.7", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.27.7.tgz", + "integrity": "sha512-saaqOoVEEFaux4v0K8Q7caiauRwjXC4XbD2eH60dxHXbpKxQ8kH9Rf7Jh+nryKpOUSEFxtCdBlSUx0/lO6rwRg==", + "license": "MIT", "optional": true, "os": [ "darwin", @@ -11042,6 +11306,7 @@ "version": "0.0.2", "resolved": "https://registry.npmjs.org/table2array/-/table2array-0.0.2.tgz", "integrity": "sha512-jSYrVGJL1q7IEuPUSsyteLY9zAKWO8XUaqwBQmX2jWHm9RS3cj+gb69lI2JkKA3ZXjhEODeBcf5APHyBCIcJuA==", + "license": "MIT", "dependencies": { "cheerio": "^1.0.0-rc.12" } @@ -11096,7 +11361,8 @@ "node_modules/text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", - "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "license": "MIT" }, "node_modules/timer-node": { "version": "5.0.9", @@ -11105,20 +11371,22 @@ "license": "MIT" }, "node_modules/tldts": { - "version": "6.1.68", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.68.tgz", - "integrity": "sha512-JKF17jROiYkjJPT73hUTEiTp2OBCf+kAlB+1novk8i6Q6dWjHsgEjw9VLiipV4KTJavazXhY1QUXyQFSem2T7w==", + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "license": "MIT", "dependencies": { - "tldts-core": "^6.1.68" + "tldts-core": "^6.1.86" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "6.1.68", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.68.tgz", - "integrity": "sha512-85TdlS/DLW/gVdf2oyyzqp3ocS30WxjaL4la85EArl9cHUR/nizifKAJPziWewSZjDZS71U517/i6ciUeqtB5Q==" + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "license": "MIT" }, "node_modules/tmp": { "version": "0.0.33", @@ -11142,6 +11410,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -11178,9 +11447,13 @@ } }, "node_modules/triple-beam": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", - "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } }, "node_modules/ts-api-utils": { "version": "2.1.0", @@ -11195,9 +11468,10 @@ } }, "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "license": "Apache-2.0" }, "node_modules/tsx": { "version": "4.20.3", @@ -11222,6 +11496,16 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/tv4/-/tv4-1.3.0.tgz", "integrity": "sha512-afizzfpJgvPr+eDkREK4MxJ/+r8nEEHcmitwgnPUqpaP+FpwQyadnxNoSACbgc/b1LsZYtODGoPiFxQrgJgjvw==", + "license": [ + { + "type": "Public Domain", + "url": "http://geraintluff.github.io/tv4/LICENSE.txt" + }, + { + "type": "MIT", + "url": "http://jsonary.com/LICENSE.txt" + } + ], "engines": { "node": ">= 0.8.0" } @@ -11230,6 +11514,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/tx2/-/tx2-1.0.5.tgz", "integrity": "sha512-sJ24w0y03Md/bxzK4FU8J8JveYYUbSs2FViLJ2D/8bytSiyPRbuE3DyL/9UKYXTZlV3yXq0L8GLlhobTnekCVg==", + "license": "MIT", "optional": true, "dependencies": { "json-stringify-safe": "^5.0.1" @@ -11239,6 +11524,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" }, @@ -11259,6 +11545,7 @@ "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -11280,9 +11567,9 @@ } }, "node_modules/undici": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.10.0.tgz", - "integrity": "sha512-u5otvFBOBZvmdjWLVW+5DAc9Nkq8f24g0O9oY7qw2JVIF1VocIFoyz9JFkuVOS2j41AufeO0xnlweJ2RLT8nGw==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.12.0.tgz", + "integrity": "sha512-GrKEsc3ughskmGA9jevVlIOPMiiAHJ4OFUtaAH+NhfTUSiZ1wMPIQqQvAJUrJspFXJt3EBWgpAeoHEDVT1IBug==", "license": "MIT", "engines": { "node": ">=20.18.1" @@ -11301,45 +11588,46 @@ "license": "ISC" }, "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", "engines": { "node": ">= 10.0.0" } }, "node_modules/unrs-resolver": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.9.2.tgz", - "integrity": "sha512-VUyWiTNQD7itdiMuJy+EuLEErLj3uwX/EpHQF8EOf33Dq3Ju6VW1GXm+swk6+1h7a49uv9fKZ+dft9jU7esdLA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", "hasInstallScript": true, "license": "MIT", "dependencies": { - "napi-postinstall": "^0.2.4" + "napi-postinstall": "^0.3.0" }, "funding": { "url": "https://opencollective.com/unrs-resolver" }, "optionalDependencies": { - "@unrs/resolver-binding-android-arm-eabi": "1.9.2", - "@unrs/resolver-binding-android-arm64": "1.9.2", - "@unrs/resolver-binding-darwin-arm64": "1.9.2", - "@unrs/resolver-binding-darwin-x64": "1.9.2", - "@unrs/resolver-binding-freebsd-x64": "1.9.2", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.9.2", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.9.2", - "@unrs/resolver-binding-linux-arm64-gnu": "1.9.2", - "@unrs/resolver-binding-linux-arm64-musl": "1.9.2", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.9.2", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.9.2", - "@unrs/resolver-binding-linux-riscv64-musl": "1.9.2", - "@unrs/resolver-binding-linux-s390x-gnu": "1.9.2", - "@unrs/resolver-binding-linux-x64-gnu": "1.9.2", - "@unrs/resolver-binding-linux-x64-musl": "1.9.2", - "@unrs/resolver-binding-wasm32-wasi": "1.9.2", - "@unrs/resolver-binding-win32-arm64-msvc": "1.9.2", - "@unrs/resolver-binding-win32-ia32-msvc": "1.9.2", - "@unrs/resolver-binding-win32-x64-msvc": "1.9.2" + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" } }, "node_modules/unzipit": { @@ -11388,6 +11676,7 @@ "version": "1.5.4", "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.4.tgz", "integrity": "sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==", + "license": "MIT", "dependencies": { "registry-auth-token": "3.3.2", "registry-url": "3.1.0" @@ -11397,6 +11686,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } @@ -11417,7 +11707,8 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" }, "node_modules/uuid": { "version": "11.1.0", @@ -11435,7 +11726,8 @@ "node_modules/uzip-module": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/uzip-module/-/uzip-module-1.0.3.tgz", - "integrity": "sha512-AMqwWZaknLM77G+VPYNZLEruMGWGzyigPK3/Whg99B3S6vGHuqsyl5ZrOv1UUF3paGK1U6PM0cnayioaryg/fA==" + "integrity": "sha512-AMqwWZaknLM77G+VPYNZLEruMGWGzyigPK3/Whg99B3S6vGHuqsyl5ZrOv1UUF3paGK1U6PM0cnayioaryg/fA==", + "license": "MIT" }, "node_modules/v8-to-istanbul": { "version": "9.3.0", @@ -11455,6 +11747,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -11463,6 +11756,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/vizion/-/vizion-2.2.1.tgz", "integrity": "sha512-sfAcO2yeSU0CSPFI/DmZp3FsFE9T+8913nv1xWBOyzODv13fwkn6Vl7HqxGpkr9F608M+8SuFId3s+BlZqfXww==", + "license": "Apache-2.0", "dependencies": { "async": "^2.6.3", "git-node-fs": "^1.0.0", @@ -11477,6 +11771,7 @@ "version": "2.6.4", "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "license": "MIT", "dependencies": { "lodash": "^4.17.14" } @@ -11515,6 +11810,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -11550,6 +11846,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "license": "MIT", "dependencies": { "string-width": "^5.0.1" }, @@ -11560,26 +11857,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/widest-line/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, "node_modules/widest-line/node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" }, "node_modules/widest-line/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -11592,20 +11880,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/widest-line/node_modules/strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/wildcard-match": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/wildcard-match/-/wildcard-match-5.1.4.tgz", @@ -11613,36 +11887,48 @@ "license": "ISC" }, "node_modules/winston": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.7.2.tgz", - "integrity": "sha512-QziIqtojHBoyzUOdQvQiar1DH0Xp9nF1A1y7NVy2DGEsz82SBDtOalS0ulTRGVT14xPX3WRWkCsdcJKqNflKng==", + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz", + "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", + "license": "MIT", "dependencies": { + "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.2", "async": "^3.2.3", "is-stream": "^2.0.0", - "logform": "^2.4.0", + "logform": "^2.7.0", "one-time": "^1.0.0", "readable-stream": "^3.4.0", "safe-stable-stringify": "^2.3.1", "stack-trace": "0.0.x", "triple-beam": "^1.3.0", - "winston-transport": "^4.5.0" + "winston-transport": "^4.9.0" }, "engines": { "node": ">= 12.0.0" } }, "node_modules/winston-transport": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz", - "integrity": "sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "license": "MIT", "dependencies": { - "logform": "^2.3.2", - "readable-stream": "^3.6.0", + "logform": "^2.7.0", + "readable-stream": "^3.6.2", "triple-beam": "^1.3.0" }, "engines": { - "node": ">= 6.4.0" + "node": ">= 12.0.0" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, "node_modules/wrap-ansi": { @@ -11677,6 +11963,114 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -11696,22 +12090,11 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/write-file-atomic/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/ws": { "version": "7.5.10", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", "engines": { "node": ">=8.3.0" }, @@ -11732,6 +12115,7 @@ "version": "1.6.11", "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", + "license": "MIT", "dependencies": { "sax": "^1.2.4" }, @@ -11785,6 +12169,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -11804,7786 +12189,5 @@ "url": "https://github.com/sponsors/sindresorhus" } } - }, - "dependencies": { - "@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==" - }, - "@alex_neo/jest-expect-message": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@alex_neo/jest-expect-message/-/jest-expect-message-1.0.5.tgz", - "integrity": "sha512-1eBykZCd0pPGl5qKtV6Z5ARA6yuhXzHsVN2h5GH5/H6svYa37Jr7vMio5OFpiw1LBHtscrZs7amSkZkcwm0cvQ==" - }, - "@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "requires": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "requires": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - } - }, - "@babel/compat-data": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.7.tgz", - "integrity": "sha512-xgu/ySj2mTiUFmdE9yCMfBxLp4DHd5DwmbbD05YAuICfodYT3VvRxbrh81LGQ/8UpSdtMdfKMn3KouYDX59DGQ==" - }, - "@babel/core": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.7.tgz", - "integrity": "sha512-BU2f9tlKQ5CAthiMIgpzAh4eDTLWo1mqi9jqE2OxMG0E/OM199VJt2q8BztTxpnSW0i1ymdwLXRJnYzvDM5r2w==", - "requires": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.6", - "@babel/parser": "^7.27.7", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.27.7", - "@babel/types": "^7.27.7", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "dependencies": { - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" - } - } - }, - "@babel/generator": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", - "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", - "requires": { - "@babel/parser": "^7.27.5", - "@babel/types": "^7.27.3", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^3.0.2" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", - "requires": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "dependencies": { - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" - } - } - }, - "@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "requires": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - } - }, - "@babel/helper-module-transforms": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", - "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", - "requires": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.3" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==" - }, - "@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==" - }, - "@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==" - }, - "@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==" - }, - "@babel/helpers": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", - "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", - "requires": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.6" - } - }, - "@babel/parser": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.7.tgz", - "integrity": "sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q==", - "requires": { - "@babel/types": "^7.27.7" - } - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", - "requires": { - "@babel/helper-plugin-utils": "^7.27.1" - } - }, - "@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", - "requires": { - "@babel/helper-plugin-utils": "^7.27.1" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.27.1" - } - }, - "@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "requires": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - } - }, - "@babel/traverse": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.7.tgz", - "integrity": "sha512-X6ZlfR/O/s5EQ/SnUSLzr+6kGnkg8HXGMzpgsMsrJVcfDtH1vIp6ctCN4eZ1LS5c0+te5Cb6Y514fASjMRJ1nw==", - "requires": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.5", - "@babel/parser": "^7.27.7", - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.7", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "dependencies": { - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" - } - } - }, - "@babel/types": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.7.tgz", - "integrity": "sha512-8OLQgDScAOHXnAz2cV+RfzzNMipuLVBz2biuAJFMV9bfkNf393je3VM8CLkjQodW5+iWsSJdSgSWT6rsZoXHPw==", - "requires": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" - } - }, - "@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==" - }, - "@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==" - }, - "@dabh/diagnostics": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", - "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", - "requires": { - "colorspace": "1.1.x", - "enabled": "2.0.x", - "kuler": "^2.0.0" - } - }, - "@emnapi/core": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz", - "integrity": "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==", - "optional": true, - "requires": { - "@emnapi/wasi-threads": "1.0.2", - "tslib": "^2.4.0" - } - }, - "@emnapi/runtime": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", - "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", - "optional": true, - "requires": { - "tslib": "^2.4.0" - } - }, - "@emnapi/wasi-threads": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz", - "integrity": "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==", - "optional": true, - "requires": { - "tslib": "^2.4.0" - } - }, - "@esbuild/aix-ppc64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", - "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", - "optional": true - }, - "@esbuild/android-arm": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", - "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", - "optional": true - }, - "@esbuild/android-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", - "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", - "optional": true - }, - "@esbuild/android-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", - "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", - "optional": true - }, - "@esbuild/darwin-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", - "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", - "optional": true - }, - "@esbuild/darwin-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", - "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", - "optional": true - }, - "@esbuild/freebsd-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", - "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", - "optional": true - }, - "@esbuild/freebsd-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", - "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", - "optional": true - }, - "@esbuild/linux-arm": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", - "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", - "optional": true - }, - "@esbuild/linux-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", - "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", - "optional": true - }, - "@esbuild/linux-ia32": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", - "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", - "optional": true - }, - "@esbuild/linux-loong64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", - "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", - "optional": true - }, - "@esbuild/linux-mips64el": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", - "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", - "optional": true - }, - "@esbuild/linux-ppc64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", - "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", - "optional": true - }, - "@esbuild/linux-riscv64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", - "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", - "optional": true - }, - "@esbuild/linux-s390x": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", - "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", - "optional": true - }, - "@esbuild/linux-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", - "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", - "optional": true - }, - "@esbuild/netbsd-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", - "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", - "optional": true - }, - "@esbuild/netbsd-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", - "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", - "optional": true - }, - "@esbuild/openbsd-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", - "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", - "optional": true - }, - "@esbuild/openbsd-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", - "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", - "optional": true - }, - "@esbuild/sunos-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", - "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", - "optional": true - }, - "@esbuild/win32-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", - "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", - "optional": true - }, - "@esbuild/win32-ia32": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", - "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", - "optional": true - }, - "@esbuild/win32-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", - "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", - "optional": true - }, - "@eslint-community/eslint-utils": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", - "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", - "requires": { - "eslint-visitor-keys": "^3.4.3" - } - }, - "@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==" - }, - "@eslint/config-array": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", - "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", - "requires": { - "@eslint/object-schema": "^2.1.6", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - } - }, - "@eslint/config-helpers": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", - "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==" - }, - "@eslint/core": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", - "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", - "requires": { - "@types/json-schema": "^7.0.15" - } - }, - "@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", - "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==" - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "requires": { - "argparse": "^2.0.1" - } - } - } - }, - "@eslint/js": { - "version": "9.30.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.30.0.tgz", - "integrity": "sha512-Wzw3wQwPvc9sHM+NjakWTcPx11mbZyiYHuwWa/QfZ7cIRX7WK54PSk7bdyXDaoaopUcMatv1zaQvOAAO8hCdww==" - }, - "@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==" - }, - "@eslint/plugin-kit": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.2.tgz", - "integrity": "sha512-4SaFZCNfJqvk/kenHpI8xvN42DMaoycy4PzKc5otHxRswww1kAt82OlBuwRVLofCACCTZEcla2Ydxv8scMXaTg==", - "requires": { - "@eslint/core": "^0.15.0", - "levn": "^0.4.1" - }, - "dependencies": { - "@eslint/core": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.0.tgz", - "integrity": "sha512-b7ePw78tEWWkpgZCDYkbqDOP8dmM6qe+AOC6iuJqlq1R/0ahMAeH3qynpnqKFGkMltrp44ohV4ubGyvLX28tzw==", - "requires": { - "@types/json-schema": "^7.0.15" - } - } - } - }, - "@freearhey/core": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/@freearhey/core/-/core-0.8.2.tgz", - "integrity": "sha512-jlb1XUbhUf3lqD3B9Wmx3c8qYG4+s1I0cr2FFQfiMpJh4nMvfUNdJr2OhH31S/dbNP12ycT6RPVoZ2j2G3+mXA==", - "requires": { - "consola": "^3.4.2", - "dayjs": "^1.11.13", - "fs-extra": "^11.3.0", - "glob": "^11.0.1", - "lodash": "^4.17.21", - "natural-orderby": "^5.0.0", - "normalize-url": "^6.1.0", - "object-treeify": "^2.1.1", - "pako": "^2.1.0", - "timer-node": "^5.0.9" - } - }, - "@freearhey/search-js": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@freearhey/search-js/-/search-js-0.1.2.tgz", - "integrity": "sha512-F2o+xpGCXOK4OsZfKEHfXNNkAZmny2eBnPOp+P0iyV20ja7gJGfTFaEc6okcuEo6OB6P7LnSxTvISkoArFtlfg==", - "requires": { - "lodash": "^4.17.21" - } - }, - "@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==" - }, - "@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", - "requires": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" - }, - "dependencies": { - "@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==" - } - } - }, - "@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==" - }, - "@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==" - }, - "@inquirer/checkbox": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.1.8.tgz", - "integrity": "sha512-d/QAsnwuHX2OPolxvYcgSj7A9DO9H6gVOy2DvBTx+P2LH2iRTo/RSGV3iwCzW024nP9hw98KIuDmdyhZQj1UQg==", - "requires": { - "@inquirer/core": "^10.1.13", - "@inquirer/figures": "^1.0.12", - "@inquirer/type": "^3.0.7", - "ansi-escapes": "^4.3.2", - "yoctocolors-cjs": "^2.1.2" - } - }, - "@inquirer/confirm": { - "version": "5.1.12", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.12.tgz", - "integrity": "sha512-dpq+ielV9/bqgXRUbNH//KsY6WEw9DrGPmipkpmgC1Y46cwuBTNx7PXFWTjc3MQ+urcc0QxoVHcMI0FW4Ok0hg==", - "requires": { - "@inquirer/core": "^10.1.13", - "@inquirer/type": "^3.0.7" - } - }, - "@inquirer/core": { - "version": "10.1.13", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.13.tgz", - "integrity": "sha512-1viSxebkYN2nJULlzCxES6G9/stgHSepZ9LqqfdIGPHj5OHhiBUXVS0a6R0bEC2A+VL4D9w6QB66ebCr6HGllA==", - "requires": { - "@inquirer/figures": "^1.0.12", - "@inquirer/type": "^3.0.7", - "ansi-escapes": "^4.3.2", - "cli-width": "^4.1.0", - "mute-stream": "^2.0.0", - "signal-exit": "^4.1.0", - "wrap-ansi": "^6.2.0", - "yoctocolors-cjs": "^2.1.2" - }, - "dependencies": { - "mute-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", - "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==" - }, - "signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" - } - } - }, - "@inquirer/editor": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.13.tgz", - "integrity": "sha512-WbicD9SUQt/K8O5Vyk9iC2ojq5RHoCLK6itpp2fHsWe44VxxcA9z3GTWlvjSTGmMQpZr+lbVmrxdHcumJoLbMA==", - "requires": { - "@inquirer/core": "^10.1.13", - "@inquirer/type": "^3.0.7", - "external-editor": "^3.1.0" - } - }, - "@inquirer/expand": { - "version": "4.0.15", - "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.15.tgz", - "integrity": "sha512-4Y+pbr/U9Qcvf+N/goHzPEXiHH8680lM3Dr3Y9h9FFw4gHS+zVpbj8LfbKWIb/jayIB4aSO4pWiBTrBYWkvi5A==", - "requires": { - "@inquirer/core": "^10.1.13", - "@inquirer/type": "^3.0.7", - "yoctocolors-cjs": "^2.1.2" - } - }, - "@inquirer/figures": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.12.tgz", - "integrity": "sha512-MJttijd8rMFcKJC8NYmprWr6hD3r9Gd9qUC0XwPNwoEPWSMVJwA2MlXxF+nhZZNMY+HXsWa+o7KY2emWYIn0jQ==" - }, - "@inquirer/input": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.1.12.tgz", - "integrity": "sha512-xJ6PFZpDjC+tC1P8ImGprgcsrzQRsUh9aH3IZixm1lAZFK49UGHxM3ltFfuInN2kPYNfyoPRh+tU4ftsjPLKqQ==", - "requires": { - "@inquirer/core": "^10.1.13", - "@inquirer/type": "^3.0.7" - } - }, - "@inquirer/number": { - "version": "3.0.15", - "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.15.tgz", - "integrity": "sha512-xWg+iYfqdhRiM55MvqiTCleHzszpoigUpN5+t1OMcRkJrUrw7va3AzXaxvS+Ak7Gny0j2mFSTv2JJj8sMtbV2g==", - "requires": { - "@inquirer/core": "^10.1.13", - "@inquirer/type": "^3.0.7" - } - }, - "@inquirer/password": { - "version": "4.0.15", - "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.15.tgz", - "integrity": "sha512-75CT2p43DGEnfGTaqFpbDC2p2EEMrq0S+IRrf9iJvYreMy5mAWj087+mdKyLHapUEPLjN10mNvABpGbk8Wdraw==", - "requires": { - "@inquirer/core": "^10.1.13", - "@inquirer/type": "^3.0.7", - "ansi-escapes": "^4.3.2" - } - }, - "@inquirer/prompts": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.5.3.tgz", - "integrity": "sha512-8YL0WiV7J86hVAxrh3fE5mDCzcTDe1670unmJRz6ArDgN+DBK1a0+rbnNWp4DUB5rPMwqD5ZP6YHl9KK1mbZRg==", - "requires": { - "@inquirer/checkbox": "^4.1.8", - "@inquirer/confirm": "^5.1.12", - "@inquirer/editor": "^4.2.13", - "@inquirer/expand": "^4.0.15", - "@inquirer/input": "^4.1.12", - "@inquirer/number": "^3.0.15", - "@inquirer/password": "^4.0.15", - "@inquirer/rawlist": "^4.1.3", - "@inquirer/search": "^3.0.15", - "@inquirer/select": "^4.2.3" - } - }, - "@inquirer/rawlist": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.3.tgz", - "integrity": "sha512-7XrV//6kwYumNDSsvJIPeAqa8+p7GJh7H5kRuxirct2cgOcSWwwNGoXDRgpNFbY/MG2vQ4ccIWCi8+IXXyFMZA==", - "requires": { - "@inquirer/core": "^10.1.13", - "@inquirer/type": "^3.0.7", - "yoctocolors-cjs": "^2.1.2" - } - }, - "@inquirer/search": { - "version": "3.0.15", - "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.0.15.tgz", - "integrity": "sha512-YBMwPxYBrADqyvP4nNItpwkBnGGglAvCLVW8u4pRmmvOsHUtCAUIMbUrLX5B3tFL1/WsLGdQ2HNzkqswMs5Uaw==", - "requires": { - "@inquirer/core": "^10.1.13", - "@inquirer/figures": "^1.0.12", - "@inquirer/type": "^3.0.7", - "yoctocolors-cjs": "^2.1.2" - } - }, - "@inquirer/select": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.2.3.tgz", - "integrity": "sha512-OAGhXU0Cvh0PhLz9xTF/kx6g6x+sP+PcyTiLvCrewI99P3BBeexD+VbuwkNDvqGkk3y2h5ZiWLeRP7BFlhkUDg==", - "requires": { - "@inquirer/core": "^10.1.13", - "@inquirer/figures": "^1.0.12", - "@inquirer/type": "^3.0.7", - "ansi-escapes": "^4.3.2", - "yoctocolors-cjs": "^2.1.2" - } - }, - "@inquirer/type": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.7.tgz", - "integrity": "sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==", - "requires": {} - }, - "@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==" - }, - "@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", - "requires": { - "@isaacs/balanced-match": "^4.0.1" - } - }, - "@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "requires": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==" - }, - "ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==" - }, - "emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" - }, - "string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "requires": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - } - }, - "strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "requires": { - "ansi-regex": "^6.0.1" - } - }, - "wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "requires": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - } - } - } - }, - "@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "requires": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - } - }, - "@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==" - }, - "@jest/console": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.0.2.tgz", - "integrity": "sha512-krGElPU0FipAqpVZ/BRZOy0MZh/ARdJ0Nj+PiH1ykFY1+VpBlYNLjdjVA5CFKxnKR6PFqFutO4Z7cdK9BlGiDA==", - "requires": { - "@jest/types": "30.0.1", - "@types/node": "*", - "chalk": "^4.1.2", - "jest-message-util": "30.0.2", - "jest-util": "30.0.2", - "slash": "^3.0.0" - }, - "dependencies": { - "@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "requires": { - "@sinclair/typebox": "^0.34.0" - } - }, - "@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "requires": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - } - }, - "@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "@jest/core": { - "version": "30.0.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.0.3.tgz", - "integrity": "sha512-Mgs1N+NSHD3Fusl7bOq1jyxv1JDAUwjy+0DhVR93Q6xcBP9/bAQ+oZhXb5TTnP5sQzAHgb7ROCKQ2SnovtxYtg==", - "requires": { - "@jest/console": "30.0.2", - "@jest/pattern": "30.0.1", - "@jest/reporters": "30.0.2", - "@jest/test-result": "30.0.2", - "@jest/transform": "30.0.2", - "@jest/types": "30.0.1", - "@types/node": "*", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "exit-x": "^0.2.2", - "graceful-fs": "^4.2.11", - "jest-changed-files": "30.0.2", - "jest-config": "30.0.3", - "jest-haste-map": "30.0.2", - "jest-message-util": "30.0.2", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.0.2", - "jest-resolve-dependencies": "30.0.3", - "jest-runner": "30.0.3", - "jest-runtime": "30.0.3", - "jest-snapshot": "30.0.3", - "jest-util": "30.0.2", - "jest-validate": "30.0.2", - "jest-watcher": "30.0.2", - "micromatch": "^4.0.8", - "pretty-format": "30.0.2", - "slash": "^3.0.0" - }, - "dependencies": { - "@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "requires": { - "@sinclair/typebox": "^0.34.0" - } - }, - "@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "requires": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - } - }, - "@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "@jest/create-cache-key-function": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", - "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==", - "requires": { - "@jest/types": "^29.6.3" - } - }, - "@jest/diff-sequences": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", - "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==" - }, - "@jest/environment": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.0.2.tgz", - "integrity": "sha512-hRLhZRJNxBiOhxIKSq2UkrlhMt3/zVFQOAi5lvS8T9I03+kxsbflwHJEF+eXEYXCrRGRhHwECT7CDk6DyngsRA==", - "requires": { - "@jest/fake-timers": "30.0.2", - "@jest/types": "30.0.1", - "@types/node": "*", - "jest-mock": "30.0.2" - }, - "dependencies": { - "@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "requires": { - "@sinclair/typebox": "^0.34.0" - } - }, - "@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "requires": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - } - }, - "@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "@jest/expect": { - "version": "30.0.3", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.0.3.tgz", - "integrity": "sha512-73BVLqfCeWjYWPEQoYjiRZ4xuQRhQZU0WdgvbyXGRHItKQqg5e6mt2y1kVhzLSuZpmUnccZHbGynoaL7IcLU3A==", - "requires": { - "expect": "30.0.3", - "jest-snapshot": "30.0.3" - } - }, - "@jest/expect-utils": { - "version": "30.0.3", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.0.3.tgz", - "integrity": "sha512-SMtBvf2sfX2agcT0dA9pXwcUrKvOSDqBY4e4iRfT+Hya33XzV35YVg+98YQFErVGA/VR1Gto5Y2+A6G9LSQ3Yg==", - "requires": { - "@jest/get-type": "30.0.1" - } - }, - "@jest/fake-timers": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.0.2.tgz", - "integrity": "sha512-jfx0Xg7l0gmphTY9UKm5RtH12BlLYj/2Plj6wXjVW5Era4FZKfXeIvwC67WX+4q8UCFxYS20IgnMcFBcEU0DtA==", - "requires": { - "@jest/types": "30.0.1", - "@sinonjs/fake-timers": "^13.0.0", - "@types/node": "*", - "jest-message-util": "30.0.2", - "jest-mock": "30.0.2", - "jest-util": "30.0.2" - }, - "dependencies": { - "@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "requires": { - "@sinclair/typebox": "^0.34.0" - } - }, - "@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "requires": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - } - }, - "@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "@jest/get-type": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.0.1.tgz", - "integrity": "sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw==" - }, - "@jest/globals": { - "version": "30.0.3", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.0.3.tgz", - "integrity": "sha512-fIduqNyYpMeeSr5iEAiMn15KxCzvrmxl7X7VwLDRGj7t5CoHtbF+7K3EvKk32mOUIJ4kIvFRlaixClMH2h/Vaw==", - "requires": { - "@jest/environment": "30.0.2", - "@jest/expect": "30.0.3", - "@jest/types": "30.0.1", - "jest-mock": "30.0.2" - }, - "dependencies": { - "@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "requires": { - "@sinclair/typebox": "^0.34.0" - } - }, - "@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "requires": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - } - }, - "@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "@jest/pattern": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", - "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", - "requires": { - "@types/node": "*", - "jest-regex-util": "30.0.1" - } - }, - "@jest/reporters": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.0.2.tgz", - "integrity": "sha512-l4QzS/oKf57F8WtPZK+vvF4Io6ukplc6XgNFu4Hd/QxaLEO9f+8dSFzUua62Oe0HKlCUjKHpltKErAgDiMJKsA==", - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.0.2", - "@jest/test-result": "30.0.2", - "@jest/transform": "30.0.2", - "@jest/types": "30.0.1", - "@jridgewell/trace-mapping": "^0.3.25", - "@types/node": "*", - "chalk": "^4.1.2", - "collect-v8-coverage": "^1.0.2", - "exit-x": "^0.2.2", - "glob": "^10.3.10", - "graceful-fs": "^4.2.11", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^5.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "30.0.2", - "jest-util": "30.0.2", - "jest-worker": "30.0.2", - "slash": "^3.0.0", - "string-length": "^4.0.2", - "v8-to-istanbul": "^9.0.1" - }, - "dependencies": { - "@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "requires": { - "@sinclair/typebox": "^0.34.0" - } - }, - "@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "requires": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - } - }, - "@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" - }, - "brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "requires": { - "balanced-match": "^1.0.0" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "requires": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - } - }, - "jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "requires": { - "@isaacs/cliui": "^8.0.2", - "@pkgjs/parseargs": "^0.11.0" - } - }, - "lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" - }, - "minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "requires": { - "brace-expansion": "^2.0.1" - } - }, - "path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "requires": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - } - } - } - }, - "@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "requires": { - "@sinclair/typebox": "^0.27.8" - } - }, - "@jest/snapshot-utils": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.0.1.tgz", - "integrity": "sha512-6Dpv7vdtoRiISEFwYF8/c7LIvqXD7xDXtLPNzC2xqAfBznKip0MQM+rkseKwUPUpv2PJ7KW/YsnwWXrIL2xF+A==", - "requires": { - "@jest/types": "30.0.1", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "natural-compare": "^1.4.0" - }, - "dependencies": { - "@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "requires": { - "@sinclair/typebox": "^0.34.0" - } - }, - "@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "requires": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - } - }, - "@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "@jest/source-map": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", - "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", - "requires": { - "@jridgewell/trace-mapping": "^0.3.25", - "callsites": "^3.1.0", - "graceful-fs": "^4.2.11" - } - }, - "@jest/test-result": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.0.2.tgz", - "integrity": "sha512-KKMuBKkkZYP/GfHMhI+cH2/P3+taMZS3qnqqiPC1UXZTJskkCS+YU/ILCtw5anw1+YsTulDHFpDo70mmCedW8w==", - "requires": { - "@jest/console": "30.0.2", - "@jest/types": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "collect-v8-coverage": "^1.0.2" - }, - "dependencies": { - "@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "requires": { - "@sinclair/typebox": "^0.34.0" - } - }, - "@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "requires": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - } - }, - "@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "@jest/test-sequencer": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.0.2.tgz", - "integrity": "sha512-fbyU5HPka0rkalZ3MXVvq0hwZY8dx3Y6SCqR64zRmh+xXlDeFl0IdL4l9e7vp4gxEXTYHbwLFA1D+WW5CucaSw==", - "requires": { - "@jest/test-result": "30.0.2", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.0.2", - "slash": "^3.0.0" - } - }, - "@jest/transform": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.0.2.tgz", - "integrity": "sha512-kJIuhLMTxRF7sc0gPzPtCDib/V9KwW3I2U25b+lYCYMVqHHSrcZopS8J8H+znx9yixuFv+Iozl8raLt/4MoxrA==", - "requires": { - "@babel/core": "^7.27.4", - "@jest/types": "30.0.1", - "@jridgewell/trace-mapping": "^0.3.25", - "babel-plugin-istanbul": "^7.0.0", - "chalk": "^4.1.2", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.0.2", - "jest-regex-util": "30.0.1", - "jest-util": "30.0.2", - "micromatch": "^4.0.8", - "pirates": "^4.0.7", - "slash": "^3.0.0", - "write-file-atomic": "^5.0.1" - }, - "dependencies": { - "@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "requires": { - "@sinclair/typebox": "^0.34.0" - } - }, - "@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "requires": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - } - }, - "@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "requires": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "dependencies": { - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "@jridgewell/gen-mapping": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.10.tgz", - "integrity": "sha512-HM2F4B9N4cA0RH2KQiIZOHAZqtP4xGS4IZ+SFe1SIbO4dyjf9MTY2Bo3vHYnm0hglWfXqBrzUBSa+cJfl3Xvrg==", - "requires": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==" - }, - "@jridgewell/sourcemap-codec": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.2.tgz", - "integrity": "sha512-gKYheCylLIedI+CSZoDtGkFV9YEBxRRVcfCH7OfAqh4TyUyRjEE6WVE/aXDXX0p8BIe/QgLcaAoI0220KRRFgg==" - }, - "@jridgewell/trace-mapping": { - "version": "0.3.27", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.27.tgz", - "integrity": "sha512-VO95AxtSFMelbg3ouljAYnfvTEwSWVt/2YLf+U5Ejd8iT5mXE2Sa/1LGyvySMne2CGsepGLI7KpF3EzE3Aq9Mg==", - "requires": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "@napi-rs/wasm-runtime": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.11.tgz", - "integrity": "sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==", - "optional": true, - "requires": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.9.0" - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@ntlab/sfetch": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ntlab/sfetch/-/sfetch-1.2.0.tgz", - "integrity": "sha512-9SE4NnqWo8l6mG0rnAkgng6ozSamIpF3EC+GOTQGGa6eAC0tNJvzrylMz6YRjjEGH6mOfn7ZBAuKj5WIZUul6A==", - "requires": { - "axios": "^1.7.9" - } - }, - "@octokit/auth-token": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", - "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==" - }, - "@octokit/core": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.2.tgz", - "integrity": "sha512-ODsoD39Lq6vR6aBgvjTnA3nZGliknKboc9Gtxr7E4WDNqY24MxANKcuDQSF0jzapvGb3KWOEDrKfve4HoWGK+g==", - "requires": { - "@octokit/auth-token": "^6.0.0", - "@octokit/graphql": "^9.0.1", - "@octokit/request": "^10.0.2", - "@octokit/request-error": "^7.0.0", - "@octokit/types": "^14.0.0", - "before-after-hook": "^4.0.0", - "universal-user-agent": "^7.0.0" - } - }, - "@octokit/endpoint": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.0.tgz", - "integrity": "sha512-hoYicJZaqISMAI3JfaDr1qMNi48OctWuOih1m80bkYow/ayPw6Jj52tqWJ6GEoFTk1gBqfanSoI1iY99Z5+ekQ==", - "requires": { - "@octokit/types": "^14.0.0", - "universal-user-agent": "^7.0.2" - } - }, - "@octokit/graphql": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.1.tgz", - "integrity": "sha512-j1nQNU1ZxNFx2ZtKmL4sMrs4egy5h65OMDmSbVyuCzjOcwsHq6EaYjOTGXPQxgfiN8dJ4CriYHk6zF050WEULg==", - "requires": { - "@octokit/request": "^10.0.2", - "@octokit/types": "^14.0.0", - "universal-user-agent": "^7.0.0" - } - }, - "@octokit/openapi-types": { - "version": "25.1.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", - "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==" - }, - "@octokit/plugin-paginate-rest": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-13.1.1.tgz", - "integrity": "sha512-q9iQGlZlxAVNRN2jDNskJW/Cafy7/XE52wjZ5TTvyhyOD904Cvx//DNyoO3J/MXJ0ve3rPoNWKEg5iZrisQSuw==", - "requires": { - "@octokit/types": "^14.1.0" - } - }, - "@octokit/plugin-rest-endpoint-methods": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-16.0.0.tgz", - "integrity": "sha512-kJVUQk6/dx/gRNLWUnAWKFs1kVPn5O5CYZyssyEoNYaFedqZxsfYs7DwI3d67hGz4qOwaJ1dpm07hOAD1BXx6g==", - "requires": { - "@octokit/types": "^14.1.0" - } - }, - "@octokit/request": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.2.tgz", - "integrity": "sha512-iYj4SJG/2bbhh+iIpFmG5u49DtJ4lipQ+aPakjL9OKpsGY93wM8w06gvFbEQxcMsZcCvk5th5KkIm2m8o14aWA==", - "requires": { - "@octokit/endpoint": "^11.0.0", - "@octokit/request-error": "^7.0.0", - "@octokit/types": "^14.0.0", - "fast-content-type-parse": "^3.0.0", - "universal-user-agent": "^7.0.2" - } - }, - "@octokit/request-error": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.0.0.tgz", - "integrity": "sha512-KRA7VTGdVyJlh0cP5Tf94hTiYVVqmt2f3I6mnimmaVz4UG3gQV/k4mDJlJv3X67iX6rmN7gSHCF8ssqeMnmhZg==", - "requires": { - "@octokit/types": "^14.0.0" - } - }, - "@octokit/types": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", - "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", - "requires": { - "@octokit/openapi-types": "^25.1.0" - } - }, - "@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "optional": true - }, - "@pkgr/core": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz", - "integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==" - }, - "@pm2/agent": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@pm2/agent/-/agent-2.1.1.tgz", - "integrity": "sha512-0V9ckHWd/HSC8BgAbZSoq8KXUG81X97nSkAxmhKDhmF8vanyaoc1YXwc2KVkbWz82Rg4gjd2n9qiT3i7bdvGrQ==", - "requires": { - "async": "~3.2.0", - "chalk": "~3.0.0", - "dayjs": "~1.8.24", - "debug": "~4.3.1", - "eventemitter2": "~5.0.1", - "fast-json-patch": "^3.1.0", - "fclone": "~1.0.11", - "pm2-axon": "~4.0.1", - "pm2-axon-rpc": "~0.7.0", - "proxy-agent": "~6.4.0", - "semver": "~7.5.0", - "ws": "~7.5.10" - }, - "dependencies": { - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "dayjs": { - "version": "1.8.36", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.36.tgz", - "integrity": "sha512-3VmRXEtw7RZKAf+4Tv1Ym9AGeo8r8+CjDi26x+7SYQil1UqtqdaokhzoEJohqlzt0m5kacJSDhJQkG/LWhpRBw==" - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - } - } - }, - "@pm2/io": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@pm2/io/-/io-6.1.0.tgz", - "integrity": "sha512-IxHuYURa3+FQ6BKePlgChZkqABUKFYH6Bwbw7V/pWU1pP6iR1sCI26l7P9ThUEB385ruZn/tZS3CXDUF5IA1NQ==", - "requires": { - "async": "~2.6.1", - "debug": "~4.3.1", - "eventemitter2": "^6.3.1", - "require-in-the-middle": "^5.0.0", - "semver": "~7.5.4", - "shimmer": "^1.2.0", - "signal-exit": "^3.0.3", - "tslib": "1.9.3" - }, - "dependencies": { - "async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "requires": { - "lodash": "^4.17.14" - } - }, - "eventemitter2": { - "version": "6.4.9", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", - "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==" - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "requires": { - "lru-cache": "^6.0.0" - } - }, - "tslib": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", - "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - } - } - }, - "@pm2/js-api": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@pm2/js-api/-/js-api-0.8.0.tgz", - "integrity": "sha512-nmWzrA/BQZik3VBz+npRcNIu01kdBhWL0mxKmP1ciF/gTcujPTQqt027N9fc1pK9ERM8RipFhymw7RcmCyOEYA==", - "requires": { - "async": "^2.6.3", - "debug": "~4.3.1", - "eventemitter2": "^6.3.1", - "extrareqp2": "^1.0.0", - "ws": "^7.0.0" - }, - "dependencies": { - "async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "requires": { - "lodash": "^4.17.14" - } - }, - "eventemitter2": { - "version": "6.4.9", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", - "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==" - } - } - }, - "@pm2/pm2-version-check": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@pm2/pm2-version-check/-/pm2-version-check-1.0.4.tgz", - "integrity": "sha512-SXsM27SGH3yTWKc2fKR4SYNxsmnvuBQ9dd6QHtEWmiZ/VqaOYPAIlS8+vMcn27YLtAEBGvNRSh3TPNvtjZgfqA==", - "requires": { - "debug": "^4.3.1" - } - }, - "@seald-io/binary-search-tree": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@seald-io/binary-search-tree/-/binary-search-tree-1.0.3.tgz", - "integrity": "sha512-qv3jnwoakeax2razYaMsGI/luWdliBLHTdC6jU55hQt1hcFqzauH/HsBollQ7IR4ySTtYhT+xyHoijpA16C+tA==" - }, - "@seald-io/nedb": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@seald-io/nedb/-/nedb-4.1.1.tgz", - "integrity": "sha512-u7fVfzKQ/3ZaIOnYQONf2lPZtGUeQtMPjfcaQkCw/GZv5dzn20qKW6sfN0NkVbr0ksJMlWcFXNGcXYsQSb1a1g==", - "requires": { - "@seald-io/binary-search-tree": "^1.0.3", - "localforage": "^1.9.0", - "util": "^0.12.4" - } - }, - "@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==" - }, - "@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "13.0.5", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", - "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", - "requires": { - "@sinonjs/commons": "^3.0.1" - } - }, - "@swc/core": { - "version": "1.12.7", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.12.7.tgz", - "integrity": "sha512-bcpllEihyUSnqp0UtXTvXc19CT4wp3tGWLENhWnjr4B5iEOkzqMu+xHGz1FI5IBatjfqOQb29tgIfv6IL05QaA==", - "requires": { - "@swc/core-darwin-arm64": "1.12.7", - "@swc/core-darwin-x64": "1.12.7", - "@swc/core-linux-arm-gnueabihf": "1.12.7", - "@swc/core-linux-arm64-gnu": "1.12.7", - "@swc/core-linux-arm64-musl": "1.12.7", - "@swc/core-linux-x64-gnu": "1.12.7", - "@swc/core-linux-x64-musl": "1.12.7", - "@swc/core-win32-arm64-msvc": "1.12.7", - "@swc/core-win32-ia32-msvc": "1.12.7", - "@swc/core-win32-x64-msvc": "1.12.7", - "@swc/counter": "^0.1.3", - "@swc/types": "^0.1.23" - } - }, - "@swc/core-darwin-arm64": { - "version": "1.12.7", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.12.7.tgz", - "integrity": "sha512-w6BBT0hBRS56yS+LbReVym0h+iB7/PpCddqrn1ha94ra4rZ4R/A91A/rkv+LnQlPqU/+fhqdlXtCJU9mrhCBtA==", - "optional": true - }, - "@swc/core-darwin-x64": { - "version": "1.12.7", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.12.7.tgz", - "integrity": "sha512-jN6LhFfGOpm4DY2mXPgwH4aa9GLOwublwMVFFZ/bGnHYYCRitLZs9+JWBbyWs7MyGcA246Ew+EREx36KVEAxjA==", - "optional": true - }, - "@swc/core-linux-arm-gnueabihf": { - "version": "1.12.7", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.12.7.tgz", - "integrity": "sha512-rHn8XXi7G2StEtZRAeJ6c7nhJPDnqsHXmeNrAaYwk8Tvpa6ZYG2nT9E1OQNXj1/dfbSFTjdiA8M8ZvGYBlpBoA==", - "optional": true - }, - "@swc/core-linux-arm64-gnu": { - "version": "1.12.7", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.12.7.tgz", - "integrity": "sha512-N15hKizSSh+hkZ2x3TDVrxq0TDcbvDbkQJi2ZrLb9fK+NdFUV/x+XF16ZDPlbxtrGXl1CT7VD439SNaMN9F7qw==", - "optional": true - }, - "@swc/core-linux-arm64-musl": { - "version": "1.12.7", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.12.7.tgz", - "integrity": "sha512-jxyINtBezpxd3eIUDiDXv7UQ87YWlPsM9KumOwJk09FkFSO4oYxV2RT+Wu+Nt5tVWue4N0MdXT/p7SQsDEk4YA==", - "optional": true - }, - "@swc/core-linux-x64-gnu": { - "version": "1.12.7", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.12.7.tgz", - "integrity": "sha512-PR4tPVwU1BQBfFDk2XfzXxsEIjF3x/bOV1BzZpYvrlkU0TKUDbR4t2wzvsYwD/coW7/yoQmlL70/qnuPtTp1Zw==", - "optional": true - }, - "@swc/core-linux-x64-musl": { - "version": "1.12.7", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.12.7.tgz", - "integrity": "sha512-zy7JWfQtQItgMfUjSbbcS3DZqQUn2d9VuV0LSGpJxtTXwgzhRpF1S2Sj7cU9hGpbM27Y8RJ4DeFb3qbAufjbrw==", - "optional": true - }, - "@swc/core-win32-arm64-msvc": { - "version": "1.12.7", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.12.7.tgz", - "integrity": "sha512-52PeF0tyX04ZFD8nibNhy/GjMFOZWTEWPmIB3wpD1vIJ1po+smtBnEdRRll5WIXITKoiND8AeHlBNBPqcsdcwA==", - "optional": true - }, - "@swc/core-win32-ia32-msvc": { - "version": "1.12.7", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.12.7.tgz", - "integrity": "sha512-WzQwkNMuhB1qQShT9uUgz/mX2j7NIEPExEtzvGsBT7TlZ9j1kGZ8NJcZH/fwOFcSJL4W7DnkL7nAhx6DBlSPaA==", - "optional": true - }, - "@swc/core-win32-x64-msvc": { - "version": "1.12.7", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.12.7.tgz", - "integrity": "sha512-R52ivBi2lgjl+Bd3XCPum0YfgbZq/W1AUExITysddP9ErsNSwnreYyNB3exEijiazWGcqHEas2ChiuMOP7NYrA==", - "optional": true - }, - "@swc/counter": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==" - }, - "@swc/jest": { - "version": "0.2.38", - "resolved": "https://registry.npmjs.org/@swc/jest/-/jest-0.2.38.tgz", - "integrity": "sha512-HMoZgXWMqChJwffdDjvplH53g9G2ALQes3HKXDEdliB/b85OQ0CTSbxG8VSeCwiAn7cOaDVEt4mwmZvbHcS52w==", - "requires": { - "@jest/create-cache-key-function": "^29.7.0", - "@swc/counter": "^0.1.3", - "jsonc-parser": "^3.2.0" - } - }, - "@swc/types": { - "version": "0.1.23", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.23.tgz", - "integrity": "sha512-u1iIVZV9Q0jxY+yM2vw/hZGDNudsN85bBpTqzAQ9rzkxW9D+e3aEM4Han+ow518gSewkXgjmEK0BD79ZcNVgPw==", - "requires": { - "@swc/counter": "^0.1.3" - } - }, - "@tootallnate/quickjs-emscripten": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", - "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==" - }, - "@tybys/wasm-util": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", - "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", - "optional": true, - "requires": { - "tslib": "^2.4.0" - } - }, - "@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "requires": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", - "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", - "requires": { - "@babel/types": "^7.20.7" - } - }, - "@types/cli-progress": { - "version": "3.11.6", - "resolved": "https://registry.npmjs.org/@types/cli-progress/-/cli-progress-3.11.6.tgz", - "integrity": "sha512-cE3+jb9WRlu+uOSAugewNpITJDt1VF8dHOopPO4IABFc3SXYL5WE/+PTz/FCdZRRfIujiWW3n3aMbv1eIGVRWA==", - "requires": { - "@types/node": "*" - } - }, - "@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" - }, - "@types/fs-extra": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz", - "integrity": "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==", - "requires": { - "@types/jsonfile": "*", - "@types/node": "*" - } - }, - "@types/inquirer": { - "version": "9.0.8", - "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-9.0.8.tgz", - "integrity": "sha512-CgPD5kFGWsb8HJ5K7rfWlifao87m4ph8uioU7OTncJevmE/VLIqAAjfQtko578JZg7/f69K4FgqYym3gNr7DeA==", - "requires": { - "@types/through": "*", - "rxjs": "^7.2.0" - } - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==" - }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "@types/jest": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", - "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", - "requires": { - "expect": "^30.0.0", - "pretty-format": "^30.0.0" - } - }, - "@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" - }, - "@types/jsonfile": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz", - "integrity": "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==", - "requires": { - "@types/node": "*" - } - }, - "@types/langs": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/langs/-/langs-2.0.5.tgz", - "integrity": "sha512-DIUKT4mkbTBxSrX6lmnQR888ObeFVVo1uNEqBH5/ddQHpnG4CA24DibpK7aO8QAcJEZUTcIx0F96TWuzVT9Z4g==" - }, - "@types/lodash": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-NYqRyg/hIQrYPT9lbOeYc3kIRabJDn/k4qQHIXUpx88CBDww2fD15Sg5kbXlW86zm2XEW4g0QxkTI3/Kfkc7xQ==" - }, - "@types/node": { - "version": "24.0.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.7.tgz", - "integrity": "sha512-YIEUUr4yf8q8oQoXPpSlnvKNVKDQlPMWrmOcgzoduo7kvA2UF0/BwJ/eMKFTiTtkNL17I0M6Xe2tvwFU7be6iw==", - "requires": { - "undici-types": "~7.8.0" - } - }, - "@types/node-cleanup": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@types/node-cleanup/-/node-cleanup-2.1.5.tgz", - "integrity": "sha512-+82RAk5uYiqiMoEv2fPeh03AL4pB5d3TL+Pf+hz31Mme6ECFI1kRlgmxYjdSlHzDbJ9yLorTnKi4Op5FA54kQQ==" - }, - "@types/numeral": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/numeral/-/numeral-2.0.5.tgz", - "integrity": "sha512-kH8I7OSSwQu9DS9JYdFWbuvhVzvFRoCPCkGxNwoGgaPeDfEPJlcxNvEOypZhQ3XXHsGbfIuYcxcJxKUfJHnRfw==" - }, - "@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==" - }, - "@types/through": { - "version": "0.0.31", - "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.31.tgz", - "integrity": "sha512-LpKpmb7FGevYgXnBXYs6HWnmiFyVG07Pt1cnbgM1IhEacITTiUaBXXvOR3Y50ksaJWGSfhbEvQFivQEFGCC55w==", - "requires": { - "@types/node": "*" - } - }, - "@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" - }, - "@typescript-eslint/eslint-plugin": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.0.tgz", - "integrity": "sha512-ijItUYaiWuce0N1SoSMrEd0b6b6lYkYt99pqCPfybd+HKVXtEvYhICfLdwp42MhiI5mp0oq7PKEL+g1cNiz/Eg==", - "requires": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.35.0", - "@typescript-eslint/type-utils": "8.35.0", - "@typescript-eslint/utils": "8.35.0", - "@typescript-eslint/visitor-keys": "8.35.0", - "graphemer": "^1.4.0", - "ignore": "^7.0.0", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.1.0" - }, - "dependencies": { - "ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==" - } - } - }, - "@typescript-eslint/parser": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.35.0.tgz", - "integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==", - "requires": { - "@typescript-eslint/scope-manager": "8.35.0", - "@typescript-eslint/types": "8.35.0", - "@typescript-eslint/typescript-estree": "8.35.0", - "@typescript-eslint/visitor-keys": "8.35.0", - "debug": "^4.3.4" - } - }, - "@typescript-eslint/project-service": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.0.tgz", - "integrity": "sha512-41xatqRwWZuhUMF/aZm2fcUsOFKNcG28xqRSS6ZVr9BVJtGExosLAm5A1OxTjRMagx8nJqva+P5zNIGt8RIgbQ==", - "requires": { - "@typescript-eslint/tsconfig-utils": "^8.35.0", - "@typescript-eslint/types": "^8.35.0", - "debug": "^4.3.4" - } - }, - "@typescript-eslint/scope-manager": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.0.tgz", - "integrity": "sha512-+AgL5+mcoLxl1vGjwNfiWq5fLDZM1TmTPYs2UkyHfFhgERxBbqHlNjRzhThJqz+ktBqTChRYY6zwbMwy0591AA==", - "requires": { - "@typescript-eslint/types": "8.35.0", - "@typescript-eslint/visitor-keys": "8.35.0" - } - }, - "@typescript-eslint/tsconfig-utils": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.0.tgz", - "integrity": "sha512-04k/7247kZzFraweuEirmvUj+W3bJLI9fX6fbo1Qm2YykuBvEhRTPl8tcxlYO8kZZW+HIXfkZNoasVb8EV4jpA==", - "requires": {} - }, - "@typescript-eslint/type-utils": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.35.0.tgz", - "integrity": "sha512-ceNNttjfmSEoM9PW87bWLDEIaLAyR+E6BoYJQ5PfaDau37UGca9Nyq3lBk8Bw2ad0AKvYabz6wxc7DMTO2jnNA==", - "requires": { - "@typescript-eslint/typescript-estree": "8.35.0", - "@typescript-eslint/utils": "8.35.0", - "debug": "^4.3.4", - "ts-api-utils": "^2.1.0" - } - }, - "@typescript-eslint/types": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.0.tgz", - "integrity": "sha512-0mYH3emanku0vHw2aRLNGqe7EXh9WHEhi7kZzscrMDf6IIRUQ5Jk4wp1QrledE/36KtdZrVfKnE32eZCf/vaVQ==" - }, - "@typescript-eslint/typescript-estree": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.0.tgz", - "integrity": "sha512-F+BhnaBemgu1Qf8oHrxyw14wq6vbL8xwWKKMwTMwYIRmFFY/1n/9T/jpbobZL8vp7QyEUcC6xGrnAO4ua8Kp7w==", - "requires": { - "@typescript-eslint/project-service": "8.35.0", - "@typescript-eslint/tsconfig-utils": "8.35.0", - "@typescript-eslint/types": "8.35.0", - "@typescript-eslint/visitor-keys": "8.35.0", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.1.0" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "@typescript-eslint/utils": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.0.tgz", - "integrity": "sha512-nqoMu7WWM7ki5tPgLVsmPM8CkqtoPUG6xXGeefM5t4x3XumOEKMoUZPdi+7F+/EotukN4R9OWdmDxN80fqoZeg==", - "requires": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.35.0", - "@typescript-eslint/types": "8.35.0", - "@typescript-eslint/typescript-estree": "8.35.0" - } - }, - "@typescript-eslint/visitor-keys": { - "version": "8.35.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.0.tgz", - "integrity": "sha512-zTh2+1Y8ZpmeQaQVIc/ZZxsx8UzgKJyNg1PTvjzC7WMhPSVS8bfDX34k1SrwOf016qd5RU3az2UxUNue3IfQ5g==", - "requires": { - "@typescript-eslint/types": "8.35.0", - "eslint-visitor-keys": "^4.2.1" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==" - } - } - }, - "@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==" - }, - "@unrs/resolver-binding-android-arm-eabi": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.9.2.tgz", - "integrity": "sha512-tS+lqTU3N0kkthU+rYp0spAYq15DU8ld9kXkaKg9sbQqJNF+WPMuNHZQGCgdxrUOEO0j22RKMwRVhF1HTl+X8A==", - "optional": true - }, - "@unrs/resolver-binding-android-arm64": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.9.2.tgz", - "integrity": "sha512-MffGiZULa/KmkNjHeuuflLVqfhqLv1vZLm8lWIyeADvlElJ/GLSOkoUX+5jf4/EGtfwrNFcEaB8BRas03KT0/Q==", - "optional": true - }, - "@unrs/resolver-binding-darwin-arm64": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.9.2.tgz", - "integrity": "sha512-dzJYK5rohS1sYl1DHdJ3mwfwClJj5BClQnQSyAgEfggbUwA9RlROQSSbKBLqrGfsiC/VyrDPtbO8hh56fnkbsQ==", - "optional": true - }, - "@unrs/resolver-binding-darwin-x64": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.9.2.tgz", - "integrity": "sha512-gaIMWK+CWtXcg9gUyznkdV54LzQ90S3X3dn8zlh+QR5Xy7Y+Efqw4Rs4im61K1juy4YNb67vmJsCDAGOnIeffQ==", - "optional": true - }, - "@unrs/resolver-binding-freebsd-x64": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.9.2.tgz", - "integrity": "sha512-S7QpkMbVoVJb0xwHFwujnwCAEDe/596xqY603rpi/ioTn9VDgBHnCCxh+UFrr5yxuMH+dliHfjwCZJXOPJGPnw==", - "optional": true - }, - "@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.9.2.tgz", - "integrity": "sha512-+XPUMCuCCI80I46nCDFbGum0ZODP5NWGiwS3Pj8fOgsG5/ctz+/zzuBlq/WmGa+EjWZdue6CF0aWWNv84sE1uw==", - "optional": true - }, - "@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.9.2.tgz", - "integrity": "sha512-sqvUyAd1JUpwbz33Ce2tuTLJKM+ucSsYpPGl2vuFwZnEIg0CmdxiZ01MHQ3j6ExuRqEDUCy8yvkDKvjYFPb8Zg==", - "optional": true - }, - "@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.9.2.tgz", - "integrity": "sha512-UYA0MA8ajkEDCFRQdng/FVx3F6szBvk3EPnkTTQuuO9lV1kPGuTB+V9TmbDxy5ikaEgyWKxa4CI3ySjklZ9lFA==", - "optional": true - }, - "@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.9.2.tgz", - "integrity": "sha512-P/CO3ODU9YJIHFqAkHbquKtFst0COxdphc8TKGL5yCX75GOiVpGqd1d15ahpqu8xXVsqP4MGFP2C3LRZnnL5MA==", - "optional": true - }, - "@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.9.2.tgz", - "integrity": "sha512-uKStFlOELBxBum2s1hODPtgJhY4NxYJE9pAeyBgNEzHgTqTiVBPjfTlPFJkfxyTjQEuxZbbJlJnMCrRgD7ubzw==", - "optional": true - }, - "@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.9.2.tgz", - "integrity": "sha512-LkbNnZlhINfY9gK30AHs26IIVEZ9PEl9qOScYdmY2o81imJYI4IMnJiW0vJVtXaDHvBvxeAgEy5CflwJFIl3tQ==", - "optional": true - }, - "@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.9.2.tgz", - "integrity": "sha512-vI+e6FzLyZHSLFNomPi+nT+qUWN4YSj8pFtQZSFTtmgFoxqB6NyjxSjAxEC1m93qn6hUXhIsh8WMp+fGgxCoRg==", - "optional": true - }, - "@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.9.2.tgz", - "integrity": "sha512-sSO4AlAYhSM2RAzBsRpahcJB1msc6uYLAtP6pesPbZtptF8OU/CbCPhSRW6cnYOGuVmEmWVW5xVboAqCnWTeHQ==", - "optional": true - }, - "@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.9.2.tgz", - "integrity": "sha512-jkSkwch0uPFva20Mdu8orbQjv2A3G88NExTN2oPTI1AJ+7mZfYW3cDCTyoH6OnctBKbBVeJCEqh0U02lTkqD5w==", - "optional": true - }, - "@unrs/resolver-binding-linux-x64-musl": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.9.2.tgz", - "integrity": "sha512-Uk64NoiTpQbkpl+bXsbeyOPRpUoMdcUqa+hDC1KhMW7aN1lfW8PBlBH4mJ3n3Y47dYE8qi0XTxy1mBACruYBaw==", - "optional": true - }, - "@unrs/resolver-binding-wasm32-wasi": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.9.2.tgz", - "integrity": "sha512-EpBGwkcjDicjR/ybC0g8wO5adPNdVuMrNalVgYcWi+gYtC1XYNuxe3rufcO7dA76OHGeVabcO6cSkPJKVcbCXQ==", - "optional": true, - "requires": { - "@napi-rs/wasm-runtime": "^0.2.11" - } - }, - "@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.9.2.tgz", - "integrity": "sha512-EdFbGn7o1SxGmN6aZw9wAkehZJetFPao0VGZ9OMBwKx6TkvDuj6cNeLimF/Psi6ts9lMOe+Dt6z19fZQ9Ye2fw==", - "optional": true - }, - "@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.9.2.tgz", - "integrity": "sha512-JY9hi1p7AG+5c/dMU8o2kWemM8I6VZxfGwn1GCtf3c5i+IKcMo2NQ8OjZ4Z3/itvY/Si3K10jOBQn7qsD/whUA==", - "optional": true - }, - "@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.9.2.tgz", - "integrity": "sha512-ryoo+EB19lMxAd80ln9BVf8pdOAxLb97amrQ3SFN9OCRn/5M5wvwDgAe4i8ZjhpbiHoDeP8yavcTEnpKBo7lZg==", - "optional": true - }, - "@zeit/schemas": { - "version": "2.36.0", - "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.36.0.tgz", - "integrity": "sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==" - }, - "accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - } - }, - "acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==" - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "requires": {} - }, - "agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==" - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "amp": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/amp/-/amp-0.3.1.tgz", - "integrity": "sha512-OwIuC4yZaRogHKiuU5WlMR5Xk/jAcpPtawWL05Gj8Lvm2F6mwoJt4O/bHI+DHwG79vWd+8OFYM4/BzYqyRd3qw==" - }, - "amp-message": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/amp-message/-/amp-message-0.1.2.tgz", - "integrity": "sha512-JqutcFwoU1+jhv7ArgW38bqrE+LQdcRv4NxNw0mp0JHQyB6tXesWRjtYKlDgHRY2o3JE5UTaBGUK8kSWUdxWUg==", - "requires": { - "amp": "0.3.1" - } - }, - "ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "requires": { - "string-width": "^4.1.0" - } - }, - "ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==" - }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "requires": { - "type-fest": "^0.21.3" - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "ansis": { - "version": "4.0.0-node10", - "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.0.0-node10.tgz", - "integrity": "sha512-BRrU0Bo1X9dFGw6KgGz6hWrqQuOlVEDOzkb0QSLZY9sXHqA7pNj7yHPVJRz7y/rj4EOJ3d/D5uxH+ee9leYgsg==" - }, - "anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "arch": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", - "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==" - }, - "arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "ast-types": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", - "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", - "requires": { - "tslib": "^2.0.1" - } - }, - "async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "requires": { - "possible-typed-array-names": "^1.0.0" - } - }, - "axios": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", - "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", - "requires": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "axios-cache-interceptor": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/axios-cache-interceptor/-/axios-cache-interceptor-0.10.3.tgz", - "integrity": "sha512-oyHlhmA6zzZJDk/ZMPWPNmO3z8gBU3mWIqAZy+GIUsvwpmwyPlC2XvZ3PTOZHgpWI2kEocMUhk3+w9VwMXfZ4w==", - "requires": { - "cache-parser": "^1.2.4", - "fast-defer": "^1.1.7", - "object-code": "^1.2.2" - } - }, - "axios-cookiejar-support": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/axios-cookiejar-support/-/axios-cookiejar-support-6.0.2.tgz", - "integrity": "sha512-UO/g6DKfVoxnZkZz1NN669bMDjGV3snZnAZGZqIwEd8FdvFI17/rXLyMBm1j1cgtb2O6Jyi4MJ7ll49NPBEMNg==", - "requires": { - "http-cookie-agent": "^7.0.1" - }, - "dependencies": { - "http-cookie-agent": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/http-cookie-agent/-/http-cookie-agent-7.0.1.tgz", - "integrity": "sha512-lZHFZUdPTw64PdksQac5xbUd4NWjUbyDYnvR//2sbLpcC4UqEUW0x/6O+rDntVzJzJ07QvhtL5XZSC+c5EK+IQ==", - "requires": { - "agent-base": "^7.1.3" - } - } - } - }, - "axios-mock-adapter": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/axios-mock-adapter/-/axios-mock-adapter-1.20.0.tgz", - "integrity": "sha512-shZRhTjLP0WWdcvHKf3rH3iW9deb3UdKbdnKUoHmmsnBhVXN3sjPJM6ZvQ2r/ywgvBVQrMnjrSyQab60G1sr2w==", - "requires": { - "fast-deep-equal": "^3.1.3", - "is-blob": "^2.1.0", - "is-buffer": "^2.0.5" - } - }, - "babel-jest": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.0.2.tgz", - "integrity": "sha512-A5kqR1/EUTidM2YC2YMEUDP2+19ppgOwK0IAd9Swc3q2KqFb5f9PtRUXVeZcngu0z5mDMyZ9zH2huJZSOMLiTQ==", - "requires": { - "@jest/transform": "30.0.2", - "@types/babel__core": "^7.20.5", - "babel-plugin-istanbul": "^7.0.0", - "babel-preset-jest": "30.0.1", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "slash": "^3.0.0" - }, - "dependencies": { - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "babel-plugin-istanbul": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.0.tgz", - "integrity": "sha512-C5OzENSx/A+gt7t4VH1I2XsflxyPUmXRFPKBxt33xncdOmq7oROVM3bZv9Ysjjkv8OJYDMa+tKuKMvqU/H3xdw==", - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-instrument": "^6.0.2", - "test-exclude": "^6.0.0" - } - }, - "babel-plugin-jest-hoist": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.0.1.tgz", - "integrity": "sha512-zTPME3pI50NsFW8ZBaVIOeAxzEY7XHlmWeXXu9srI+9kNfzCUTy8MFan46xOGZY8NZThMqq+e3qZUKsvXbasnQ==", - "requires": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3", - "@types/babel__core": "^7.20.5" - } - }, - "babel-preset-current-node-syntax": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", - "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", - "requires": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5" - } - }, - "babel-preset-jest": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.0.1.tgz", - "integrity": "sha512-+YHejD5iTWI46cZmcc/YtX4gaKBtdqCHCVfuVinizVpbmyjO3zYmeuyFdfA8duRqQZfgCAMlsfmkVbJ+e2MAJw==", - "requires": { - "babel-plugin-jest-hoist": "30.0.1", - "babel-preset-current-node-syntax": "^1.1.0" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "basic-ftp": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", - "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==" - }, - "before-after-hook": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", - "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==" - }, - "binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==" - }, - "blessed": { - "version": "0.1.81", - "resolved": "https://registry.npmjs.org/blessed/-/blessed-0.1.81.tgz", - "integrity": "sha512-LoF5gae+hlmfORcG1M5+5XZi4LBmvlXTzwJWzUlPryN/SJdSflZvROM2TwkT0GMpq7oqT48NRd4GS7BiVBc5OQ==" - }, - "bodec": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/bodec/-/bodec-0.1.0.tgz", - "integrity": "sha512-Ylo+MAo5BDUq1KA3f3R/MFhh+g8cnHmo8bz3YPGhI1znrMaf77ol1sfvYJzsw3nTE+Y2GryfDxBaR+AqpAkEHQ==" - }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" - }, - "boxen": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.0.tgz", - "integrity": "sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==", - "requires": { - "ansi-align": "^3.0.1", - "camelcase": "^7.0.0", - "chalk": "^5.0.1", - "cli-boxes": "^3.0.0", - "string-width": "^5.1.2", - "type-fest": "^2.13.0", - "widest-line": "^4.0.1", - "wrap-ansi": "^8.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==" - }, - "ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==" - }, - "camelcase": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", - "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==" - }, - "emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" - }, - "string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "requires": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - } - }, - "strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", - "requires": { - "ansi-regex": "^6.0.1" - } - }, - "type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==" - }, - "wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "requires": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - } - } - } - }, - "brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "requires": { - "fill-range": "^7.1.1" - } - }, - "browserslist": { - "version": "4.25.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", - "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", - "requires": { - "caniuse-lite": "^1.0.30001726", - "electron-to-chromium": "^1.5.173", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - } - }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "requires": { - "node-int64": "^0.4.0" - } - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" - }, - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==" - }, - "cache-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/cache-parser/-/cache-parser-1.2.4.tgz", - "integrity": "sha512-O0KwuHuJnbHUrghHi2kGp0SxnWSIBXTYt7M8WVhW0kbPRUNUKoE/Of6e1rRD6AAxmfxFunKnt90yEK09D+sc5g==" - }, - "call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "requires": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - } - }, - "call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "requires": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - } - }, - "call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "requires": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" - }, - "caniuse-lite": { - "version": "1.0.30001726", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", - "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==" - }, - "cdata": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/cdata/-/cdata-0.1.3.tgz", - "integrity": "sha512-z0R4cT5357OEAVkP1CEFTHz1egpu2gYiWm2WJOY/sQDhojEXUYL4m3v2kYi5wER3PkMRL+GgfDhed2kGzrHSZA==" - }, - "chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==" - }, - "chalk-template": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", - "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", - "requires": { - "chalk": "^4.1.2" - }, - "dependencies": { - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==" - }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" - }, - "charm": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/charm/-/charm-0.1.2.tgz", - "integrity": "sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==" - }, - "cheerio": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.0.tgz", - "integrity": "sha512-+0hMx9eYhJvWbgpKV9hN7jg0JcwydpopZE4hgi+KvQtByZXPp04NiCWU0LzcAbP63abZckIHkTQaXVF52mX3xQ==", - "requires": { - "cheerio-select": "^2.1.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.3", - "domutils": "^3.2.2", - "encoding-sniffer": "^0.2.0", - "htmlparser2": "^10.0.0", - "parse5": "^7.3.0", - "parse5-htmlparser2-tree-adapter": "^7.1.0", - "parse5-parser-stream": "^7.1.2", - "undici": "^7.10.0", - "whatwg-mimetype": "^4.0.0" - } - }, - "cheerio-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", - "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", - "requires": { - "boolbase": "^1.0.0", - "css-select": "^5.1.0", - "css-what": "^6.1.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1" - } - }, - "chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "dependencies": { - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "requires": { - "is-glob": "^4.0.1" - } - } - } - }, - "ci-info": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.2.0.tgz", - "integrity": "sha512-cYY9mypksY8NRqgDB1XD1RiJL338v/551niynFTGkZOO2LHuB2OmOYxDIe/ttN9AHwrqdum1360G3ald0W9kCg==" - }, - "cjs-module-lexer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.1.0.tgz", - "integrity": "sha512-UX0OwmYRYQQetfrLEZeewIFFI+wSTofC+pMBLNuH3RUuu/xzG1oz84UCEDOSoQlN3fZ4+AzmV50ZYvGqkMh9yA==" - }, - "cli-boxes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", - "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==" - }, - "cli-progress": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", - "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", - "requires": { - "string-width": "^4.2.3" - } - }, - "cli-tableau": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/cli-tableau/-/cli-tableau-2.0.1.tgz", - "integrity": "sha512-he+WTicka9cl0Fg/y+YyxcN6/bfQ/1O3QmgxRXDhABKqLzvoOSM4fMzp39uMyLBulAFuywD2N7UaoQE7WaADxQ==", - "requires": { - "chalk": "3.0.0" - }, - "dependencies": { - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "cli-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", - "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==" - }, - "clipboardy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-3.0.0.tgz", - "integrity": "sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==", - "requires": { - "arch": "^2.2.0", - "execa": "^5.1.1", - "is-wsl": "^2.2.0" - } - }, - "cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "dependencies": { - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - } - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==" - }, - "collect-v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", - "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==" - }, - "color": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", - "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", - "requires": { - "color-convert": "^1.9.3", - "color-string": "^1.6.0" - }, - "dependencies": { - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - } - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "requires": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "colorspace": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", - "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", - "requires": { - "color": "^3.1.3", - "text-hex": "1.0.x" - } - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", - "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==" - }, - "compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "requires": { - "mime-db": ">= 1.43.0 < 2" - } - }, - "compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "requires": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "consola": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", - "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==" - }, - "content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==" - }, - "convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" - }, - "croner": { - "version": "4.1.97", - "resolved": "https://registry.npmjs.org/croner/-/croner-4.1.97.tgz", - "integrity": "sha512-/f6gpQuxDaqXu+1kwQYSckUglPaOrHdbIlBAu0YuW8/Cdb45XwXYNUBXg3r/9Mo6n540Kn/smKcZWko5x99KrQ==" - }, - "cross-env": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", - "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", - "requires": { - "cross-spawn": "^7.0.1" - } - }, - "cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", - "requires": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - } - }, - "css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==" - }, - "csv-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/csv-parser/-/csv-parser-3.2.0.tgz", - "integrity": "sha512-fgKbp+AJbn1h2dcAHKIdKNSSjfp43BZZykXsCjzALjKy80VXQNHPFJ6T9Afwdzoj24aMkq8GwDS7KGcDPpejrA==" - }, - "culvert": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/culvert/-/culvert-0.1.2.tgz", - "integrity": "sha512-yi1x3EAWKjQTreYWeSd98431AV+IEE0qoDyOoaHJ7KJ21gv6HtBXHVLX74opVSGqcR8/AbjJBHAHpcOy2bj5Gg==" - }, - "curl-generator": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/curl-generator/-/curl-generator-0.2.0.tgz", - "integrity": "sha512-KKTRYPMX3LnX45phiklGA+rv2W5mG0KD8sirV0yjtM7aliGMp5PIwqC5n74AFlwIHGMVsD9NKlyKpcYFA8bPog==", - "requires": { - "ms": "^2.0.0" - } - }, - "cwait": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cwait/-/cwait-1.1.2.tgz", - "integrity": "sha512-kIx8zE5jJ1iBgZytTr01aj57HdC+thPsg8W9Tw0gbf30/F7wfRRUS+BiXT90Dn+A0oGtF0xLT5293Ua4w/ZsNA==", - "requires": { - "cdata": "^0.1.1" - } - }, - "data-uri-to-buffer": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", - "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==" - }, - "dayjs": { - "version": "1.11.13", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", - "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==" - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, - "dedent": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", - "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", - "requires": {} - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" - }, - "deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" - }, - "define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "requires": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - } - }, - "degenerator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", - "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", - "requires": { - "ast-types": "^0.13.4", - "escodegen": "^2.1.0", - "esprima": "^4.0.1" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==" - }, - "dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "requires": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - } - }, - "domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" - }, - "domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "requires": { - "domelementtype": "^2.3.0" - } - }, - "domutils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "requires": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - } - }, - "dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "requires": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - } - }, - "eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" - }, - "electron-to-chromium": { - "version": "1.5.177", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.177.tgz", - "integrity": "sha512-7EH2G59nLsEMj97fpDuvVcYi6lwTcM1xuWw3PssD8xzboAW7zj7iB3COEEEATUfjLHrs5uKBLQT03V/8URx06g==" - }, - "emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==" - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "enabled": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", - "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" - }, - "encoding-sniffer": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", - "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", - "requires": { - "iconv-lite": "^0.6.3", - "whatwg-encoding": "^3.1.1" - } - }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "requires": { - "ansi-colors": "^4.1.1" - } - }, - "entities": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", - "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==" - }, - "epg-grabber": { - "version": "0.38.0", - "resolved": "https://registry.npmjs.org/epg-grabber/-/epg-grabber-0.38.0.tgz", - "integrity": "sha512-jbwTgi6G7e+zrb2oNC0C7mcQYoRkFnvhXCurexeICaEy4avRB6WS5rD/yfqYoiqaXOM3x1BNBpCKFYoS7Ob5YA==", - "requires": { - "axios": "^1.6.1", - "axios-cache-interceptor": "^0.10.3", - "axios-mock-adapter": "^1.20.0", - "commander": "^7.1.0", - "curl-generator": "^0.2.0", - "cwait": "^1.1.2", - "dayjs": "^1.10.4", - "epg-parser": "^0.1.6", - "fs-extra": "^11.1.1", - "glob": "^7.1.6", - "http-cookie-agent": "^6.0.8", - "lodash": "^4.17.21", - "node-gzip": "^1.1.2", - "socks-proxy-agent": "^8.0.5", - "tough-cookie": "^5.0.0", - "winston": "^3.3.3", - "xml-js": "^1.6.11" - }, - "dependencies": { - "commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==" - }, - "epg-parser": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/epg-parser/-/epg-parser-0.1.6.tgz", - "integrity": "sha512-g6AxKOvs0E4bTGPdIUh8/FDKdrVjbf4DVK0jIFuChDt7wBRJmMVyqbLeS8NApf6M2wpCRLBpIenXOCS88w0Rqw==", - "requires": { - "xml-js": "^1.6.11" - } - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, - "epg-parser": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/epg-parser/-/epg-parser-0.3.1.tgz", - "integrity": "sha512-y131hXfDthUdSeKbN0Ru1wiFF5er4t/TLT+IaAnHF2CYB0cnygHTJteQMDYIlHWHDsGj+z9ejm1cU3saFNF3nQ==", - "requires": { - "dayjs": "^1.11.6", - "lodash": "^4.17.21", - "xml-js": "^1.6.11" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" - }, - "es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" - }, - "es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "requires": { - "es-errors": "^1.3.0" - } - }, - "es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "requires": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - } - }, - "esbuild": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", - "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", - "requires": { - "@esbuild/aix-ppc64": "0.25.2", - "@esbuild/android-arm": "0.25.2", - "@esbuild/android-arm64": "0.25.2", - "@esbuild/android-x64": "0.25.2", - "@esbuild/darwin-arm64": "0.25.2", - "@esbuild/darwin-x64": "0.25.2", - "@esbuild/freebsd-arm64": "0.25.2", - "@esbuild/freebsd-x64": "0.25.2", - "@esbuild/linux-arm": "0.25.2", - "@esbuild/linux-arm64": "0.25.2", - "@esbuild/linux-ia32": "0.25.2", - "@esbuild/linux-loong64": "0.25.2", - "@esbuild/linux-mips64el": "0.25.2", - "@esbuild/linux-ppc64": "0.25.2", - "@esbuild/linux-riscv64": "0.25.2", - "@esbuild/linux-s390x": "0.25.2", - "@esbuild/linux-x64": "0.25.2", - "@esbuild/netbsd-arm64": "0.25.2", - "@esbuild/netbsd-x64": "0.25.2", - "@esbuild/openbsd-arm64": "0.25.2", - "@esbuild/openbsd-x64": "0.25.2", - "@esbuild/sunos-x64": "0.25.2", - "@esbuild/win32-arm64": "0.25.2", - "@esbuild/win32-ia32": "0.25.2", - "@esbuild/win32-x64": "0.25.2" - } - }, - "escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "requires": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "source-map": "~0.6.1" - } - }, - "eslint": { - "version": "9.30.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.30.0.tgz", - "integrity": "sha512-iN/SiPxmQu6EVkf+m1qpBxzUhE12YqFLOSySuOyVLJLEF9nzTf+h/1AJYc1JWzCnktggeNrjvQGLngDzXirU6g==", - "requires": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.0", - "@eslint/config-helpers": "^0.3.0", - "@eslint/core": "^0.14.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.30.0", - "@eslint/plugin-kit": "^0.3.1", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "dependencies": { - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" - }, - "eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==" - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "requires": { - "p-locate": "^5.0.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "requires": { - "p-limit": "^3.0.2" - } - } - } - }, - "eslint-config-prettier": { - "version": "10.1.5", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz", - "integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==", - "requires": {} - }, - "eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==" - }, - "espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "requires": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==" - } - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - }, - "esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "requires": { - "estraverse": "^5.1.0" - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "requires": { - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" - }, - "eventemitter2": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-5.0.1.tgz", - "integrity": "sha512-5EM1GHXycJBS6mauYAbVKT1cVs7POKWb2NXD4Vyt8dDqeZa7LaDK1/sjtL+Zb0lzTpSNil4596Dyu97hz37QLg==" - }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, - "exit-x": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", - "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==" - }, - "expect": { - "version": "30.0.3", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.0.3.tgz", - "integrity": "sha512-HXg6NvK35/cSYZCUKAtmlgCFyqKM4frEPbzrav5hRqb0GMz0E0lS5hfzYjSaiaE5ysnp/qI2aeZkeyeIAOeXzQ==", - "requires": { - "@jest/expect-utils": "30.0.3", - "@jest/get-type": "30.0.1", - "jest-matcher-utils": "30.0.3", - "jest-message-util": "30.0.2", - "jest-mock": "30.0.2", - "jest-util": "30.0.2" - } - }, - "external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "dependencies": { - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - } - } - }, - "extrareqp2": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/extrareqp2/-/extrareqp2-1.0.0.tgz", - "integrity": "sha512-Gum0g1QYb6wpPJCVypWP3bbIuaibcFiJcpuPM10YSXp/tzqi84x9PJageob+eN4xVRIOto4wjSGNLyMD54D2xA==", - "requires": { - "follow-redirects": "^1.14.0" - } - }, - "fast-content-type-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", - "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==" - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "fast-defer": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/fast-defer/-/fast-defer-1.1.7.tgz", - "integrity": "sha512-tJ01ulDWT2WhqxMKS20nXX6wyX2iInBYpbN3GO7yjKwXMY4qvkdBRxak9IFwBLlFDESox+SwSvqMCZDfe1tqeg==" - }, - "fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "dependencies": { - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "requires": { - "is-glob": "^4.0.1" - } - } - } - }, - "fast-json-patch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", - "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==" - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" - }, - "fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "requires": { - "reusify": "^1.0.4" - } - }, - "fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "requires": { - "bser": "2.1.1" - } - }, - "fclone": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/fclone/-/fclone-1.0.11.tgz", - "integrity": "sha512-GDqVQezKzRABdeqflsgMr7ktzgF9CyS+p2oe0jJqUY6izSSbhPIQJDpoU4PtGcD7VPM9xh/dVrTu6z1nwgmEGw==" - }, - "fecha": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", - "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" - }, - "file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "requires": { - "flat-cache": "^4.0.0" - } - }, - "fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "requires": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - } - }, - "flatted": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", - "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==" - }, - "fn.name": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", - "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" - }, - "follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==" - }, - "for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "requires": { - "is-callable": "^1.2.7" - } - }, - "foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "requires": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "dependencies": { - "signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" - } - } - }, - "form-data": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", - "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - } - }, - "fs-extra": { - "version": "11.3.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", - "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "optional": true - }, - "function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" - }, - "get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "requires": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - } - }, - "get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==" - }, - "get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "requires": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - } - }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==" - }, - "get-tsconfig": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", - "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==", - "requires": { - "resolve-pkg-maps": "^1.0.0" - } - }, - "get-uri": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz", - "integrity": "sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==", - "requires": { - "basic-ftp": "^5.0.2", - "data-uri-to-buffer": "^6.0.2", - "debug": "^4.3.4" - } - }, - "git-node-fs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/git-node-fs/-/git-node-fs-1.0.0.tgz", - "integrity": "sha512-bLQypt14llVXBg0S0u8q8HmU7g9p3ysH+NvVlae5vILuUvs759665HvmR5+wb04KjHyjFcDRxdYb4kyNnluMUQ==" - }, - "git-sha1": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/git-sha1/-/git-sha1-0.1.2.tgz", - "integrity": "sha512-2e/nZezdVlyCopOCYHeW0onkbZg7xP1Ad6pndPy1rCygeRykefUS6r7oA5cJRGEFvseiaz5a/qUHFVX1dd6Isg==" - }, - "glob": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", - "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", - "requires": { - "foreground-child": "^3.3.1", - "jackspeak": "^4.1.1", - "minimatch": "^10.0.3", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" - }, - "dependencies": { - "minimatch": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", - "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", - "requires": { - "@isaacs/brace-expansion": "^5.0.0" - } - } - } - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "requires": { - "is-glob": "^4.0.3" - } - }, - "globals": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.2.0.tgz", - "integrity": "sha512-O+7l9tPdHCU320IigZZPj5zmRCFG9xHmx9cU8FqU2Rp+JN714seHV+2S9+JslCpY4gJwU2vOGox0wzgae/MCEg==" - }, - "gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" - }, - "graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" - }, - "graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "requires": { - "es-define-property": "^1.0.0" - } - }, - "has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" - }, - "has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "requires": { - "has-symbols": "^1.0.3" - } - }, - "hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "requires": { - "function-bind": "^1.1.2" - } - }, - "html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==" - }, - "htmlparser2": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", - "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", - "requires": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.2.1", - "entities": "^6.0.0" - }, - "dependencies": { - "entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==" - } - } - }, - "http-cookie-agent": { - "version": "6.0.8", - "resolved": "https://registry.npmjs.org/http-cookie-agent/-/http-cookie-agent-6.0.8.tgz", - "integrity": "sha512-qnYh3yLSr2jBsTYkw11elq+T361uKAJaZ2dR4cfYZChw1dt9uL5t3zSUwehoqqVb4oldk1BpkXKm2oat8zV+oA==", - "requires": { - "agent-base": "^7.1.3" - } - }, - "http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "requires": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - } - }, - "https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "requires": { - "agent-base": "^7.1.2", - "debug": "4" - } - }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==" - }, - "husky": { - "version": "9.1.7", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", - "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==" - }, - "iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - } - }, - "ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==" - }, - "immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" - } - } - }, - "import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" - }, - "inquirer": { - "version": "12.6.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-12.6.3.tgz", - "integrity": "sha512-eX9beYAjr1MqYsIjx1vAheXsRk1jbZRvHLcBu5nA9wX0rXR1IfCZLnVLp4Ym4mrhqmh7AuANwcdtgQ291fZDfQ==", - "requires": { - "@inquirer/core": "^10.1.13", - "@inquirer/prompts": "^7.5.3", - "@inquirer/type": "^3.0.7", - "ansi-escapes": "^4.3.2", - "mute-stream": "^2.0.0", - "run-async": "^3.0.0", - "rxjs": "^7.8.2" - }, - "dependencies": { - "mute-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", - "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==" - } - } - }, - "ip-address": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", - "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", - "requires": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" - }, - "dependencies": { - "sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" - } - } - }, - "is-arguments": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", - "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", - "requires": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-blob": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-blob/-/is-blob-2.1.0.tgz", - "integrity": "sha512-SZ/fTft5eUhQM6oF/ZaASFDEdbFVe89Imltn9uZr03wdKMcWNVYSMjQPFtg05QuNkt5l5c135ElvXEQG0rk4tw==" - }, - "is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==" - }, - "is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" - }, - "is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "requires": { - "hasown": "^2.0.2" - } - }, - "is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==" - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==" - }, - "is-generator-function": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", - "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", - "requires": { - "call-bound": "^1.0.3", - "get-proto": "^1.0.0", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - } - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" - }, - "is-port-reachable": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-4.0.0.tgz", - "integrity": "sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==" - }, - "is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "requires": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - } - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" - }, - "is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "requires": { - "which-typed-array": "^1.1.16" - } - }, - "is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "requires": { - "is-docker": "^2.0.0" - } - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - }, - "istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==" - }, - "istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "requires": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - } - }, - "istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - } - }, - "istanbul-lib-source-maps": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", - "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", - "requires": { - "@jridgewell/trace-mapping": "^0.3.23", - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0" - } - }, - "istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "jackspeak": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", - "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", - "requires": { - "@isaacs/cliui": "^8.0.2" - } - }, - "jest": { - "version": "30.0.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.0.3.tgz", - "integrity": "sha512-Uy8xfeE/WpT2ZLGDXQmaYNzw2v8NUKuYeKGtkS6sDxwsdQihdgYCXaKIYnph1h95DN5H35ubFDm0dfmsQnjn4Q==", - "requires": { - "@jest/core": "30.0.3", - "@jest/types": "30.0.1", - "import-local": "^3.2.0", - "jest-cli": "30.0.3" - }, - "dependencies": { - "@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "requires": { - "@sinclair/typebox": "^0.34.0" - } - }, - "@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "requires": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - } - }, - "@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==" - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "jest-changed-files": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.0.2.tgz", - "integrity": "sha512-Ius/iRST9FKfJI+I+kpiDh8JuUlAISnRszF9ixZDIqJF17FckH5sOzKC8a0wd0+D+8em5ADRHA5V5MnfeDk2WA==", - "requires": { - "execa": "^5.1.1", - "jest-util": "30.0.2", - "p-limit": "^3.1.0" - } - }, - "jest-circus": { - "version": "30.0.3", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.0.3.tgz", - "integrity": "sha512-rD9qq2V28OASJHJWDRVdhoBdRs6k3u3EmBzDYcyuMby8XCO3Ll1uq9kyqM41ZcC4fMiPulMVh3qMw0cBvDbnyg==", - "requires": { - "@jest/environment": "30.0.2", - "@jest/expect": "30.0.3", - "@jest/test-result": "30.0.2", - "@jest/types": "30.0.1", - "@types/node": "*", - "chalk": "^4.1.2", - "co": "^4.6.0", - "dedent": "^1.6.0", - "is-generator-fn": "^2.1.0", - "jest-each": "30.0.2", - "jest-matcher-utils": "30.0.3", - "jest-message-util": "30.0.2", - "jest-runtime": "30.0.3", - "jest-snapshot": "30.0.3", - "jest-util": "30.0.2", - "p-limit": "^3.1.0", - "pretty-format": "30.0.2", - "pure-rand": "^7.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" - }, - "dependencies": { - "@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "requires": { - "@sinclair/typebox": "^0.34.0" - } - }, - "@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "requires": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - } - }, - "@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "jest-cli": { - "version": "30.0.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.0.3.tgz", - "integrity": "sha512-UWDSj0ayhumEAxpYRlqQLrssEi29kdQ+kddP94AuHhZknrE+mT0cR0J+zMHKFe9XPfX3dKQOc2TfWki3WhFTsA==", - "requires": { - "@jest/core": "30.0.3", - "@jest/test-result": "30.0.2", - "@jest/types": "30.0.1", - "chalk": "^4.1.2", - "exit-x": "^0.2.2", - "import-local": "^3.2.0", - "jest-config": "30.0.3", - "jest-util": "30.0.2", - "jest-validate": "30.0.2", - "yargs": "^17.7.2" - }, - "dependencies": { - "@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "requires": { - "@sinclair/typebox": "^0.34.0" - } - }, - "@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "requires": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - } - }, - "@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "jest-config": { - "version": "30.0.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.0.3.tgz", - "integrity": "sha512-j0L4oRCtJwNyZktXIqwzEiDVQXBbQ4dqXuLD/TZdn++hXIcIfZmjHgrViEy5s/+j4HvITmAXbexVZpQ/jnr0bg==", - "requires": { - "@babel/core": "^7.27.4", - "@jest/get-type": "30.0.1", - "@jest/pattern": "30.0.1", - "@jest/test-sequencer": "30.0.2", - "@jest/types": "30.0.1", - "babel-jest": "30.0.2", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "deepmerge": "^4.3.1", - "glob": "^10.3.10", - "graceful-fs": "^4.2.11", - "jest-circus": "30.0.3", - "jest-docblock": "30.0.1", - "jest-environment-node": "30.0.2", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.0.2", - "jest-runner": "30.0.3", - "jest-util": "30.0.2", - "jest-validate": "30.0.2", - "micromatch": "^4.0.8", - "parse-json": "^5.2.0", - "pretty-format": "30.0.2", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "requires": { - "@sinclair/typebox": "^0.34.0" - } - }, - "@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "requires": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - } - }, - "@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" - }, - "brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "requires": { - "balanced-match": "^1.0.0" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "requires": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - } - }, - "jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "requires": { - "@isaacs/cliui": "^8.0.2", - "@pkgjs/parseargs": "^0.11.0" - } - }, - "lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" - }, - "minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "requires": { - "brace-expansion": "^2.0.1" - } - }, - "path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "requires": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - } - } - } - }, - "jest-diff": { - "version": "30.0.3", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.0.3.tgz", - "integrity": "sha512-Q1TAV0cUcBTic57SVnk/mug0/ASyAqtSIOkr7RAlxx97llRYsM74+E8N5WdGJUlwCKwgxPAkVjKh653h1+HA9A==", - "requires": { - "@jest/diff-sequences": "30.0.1", - "@jest/get-type": "30.0.1", - "chalk": "^4.1.2", - "pretty-format": "30.0.2" - }, - "dependencies": { - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "jest-docblock": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.0.1.tgz", - "integrity": "sha512-/vF78qn3DYphAaIc3jy4gA7XSAz167n9Bm/wn/1XhTLW7tTBIzXtCJpb/vcmc73NIIeeohCbdL94JasyXUZsGA==", - "requires": { - "detect-newline": "^3.1.0" - } - }, - "jest-each": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.0.2.tgz", - "integrity": "sha512-ZFRsTpe5FUWFQ9cWTMguCaiA6kkW5whccPy9JjD1ezxh+mJeqmz8naL8Fl/oSbNJv3rgB0x87WBIkA5CObIUZQ==", - "requires": { - "@jest/get-type": "30.0.1", - "@jest/types": "30.0.1", - "chalk": "^4.1.2", - "jest-util": "30.0.2", - "pretty-format": "30.0.2" - }, - "dependencies": { - "@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "requires": { - "@sinclair/typebox": "^0.34.0" - } - }, - "@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "requires": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - } - }, - "@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "jest-environment-node": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.0.2.tgz", - "integrity": "sha512-XsGtZ0H+a70RsxAQkKuIh0D3ZlASXdZdhpOSBq9WRPq6lhe0IoQHGW0w9ZUaPiZQ/CpkIdprvlfV1QcXcvIQLQ==", - "requires": { - "@jest/environment": "30.0.2", - "@jest/fake-timers": "30.0.2", - "@jest/types": "30.0.1", - "@types/node": "*", - "jest-mock": "30.0.2", - "jest-util": "30.0.2", - "jest-validate": "30.0.2" - }, - "dependencies": { - "@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "requires": { - "@sinclair/typebox": "^0.34.0" - } - }, - "@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "requires": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - } - }, - "@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "jest-haste-map": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.0.2.tgz", - "integrity": "sha512-telJBKpNLeCb4MaX+I5k496556Y2FiKR/QLZc0+MGBYl4k3OO0472drlV2LUe7c1Glng5HuAu+5GLYp//GpdOQ==", - "requires": { - "@jest/types": "30.0.1", - "@types/node": "*", - "anymatch": "^3.1.3", - "fb-watchman": "^2.0.2", - "fsevents": "^2.3.3", - "graceful-fs": "^4.2.11", - "jest-regex-util": "30.0.1", - "jest-util": "30.0.2", - "jest-worker": "30.0.2", - "micromatch": "^4.0.8", - "walker": "^1.0.8" - }, - "dependencies": { - "@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "requires": { - "@sinclair/typebox": "^0.34.0" - } - }, - "@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "requires": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - } - }, - "@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "jest-leak-detector": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.0.2.tgz", - "integrity": "sha512-U66sRrAYdALq+2qtKffBLDWsQ/XoNNs2Lcr83sc9lvE/hEpNafJlq2lXCPUBMNqamMECNxSIekLfe69qg4KMIQ==", - "requires": { - "@jest/get-type": "30.0.1", - "pretty-format": "30.0.2" - } - }, - "jest-matcher-utils": { - "version": "30.0.3", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.0.3.tgz", - "integrity": "sha512-hMpVFGFOhYmIIRGJ0HgM9htC5qUiJ00famcc9sRFchJJiLZbbVKrAztcgE6VnXLRxA3XZ0bvNA7hQWh3oHXo/A==", - "requires": { - "@jest/get-type": "30.0.1", - "chalk": "^4.1.2", - "jest-diff": "30.0.3", - "pretty-format": "30.0.2" - }, - "dependencies": { - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "jest-message-util": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.0.2.tgz", - "integrity": "sha512-vXywcxmr0SsKXF/bAD7t7nMamRvPuJkras00gqYeB1V0WllxZrbZ0paRr3XqpFU2sYYjD0qAaG2fRyn/CGZ0aw==", - "requires": { - "@babel/code-frame": "^7.27.1", - "@jest/types": "30.0.1", - "@types/stack-utils": "^2.0.3", - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "micromatch": "^4.0.8", - "pretty-format": "30.0.2", - "slash": "^3.0.0", - "stack-utils": "^2.0.6" - }, - "dependencies": { - "@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "requires": { - "@sinclair/typebox": "^0.34.0" - } - }, - "@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "requires": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - } - }, - "@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==" - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "jest-mock": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.2.tgz", - "integrity": "sha512-PnZOHmqup/9cT/y+pXIVbbi8ID6U1XHRmbvR7MvUy4SLqhCbwpkmXhLbsWbGewHrV5x/1bF7YDjs+x24/QSvFA==", - "requires": { - "@jest/types": "30.0.1", - "@types/node": "*", - "jest-util": "30.0.2" - }, - "dependencies": { - "@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "requires": { - "@sinclair/typebox": "^0.34.0" - } - }, - "@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "requires": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - } - }, - "@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==" - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "jest-offline": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/jest-offline/-/jest-offline-1.0.1.tgz", - "integrity": "sha512-pcYJ8rVxWP3SS9de15iSQY87ErLGGgMC4qtVcRLb/qemrefI1IgnAzOusp0eemGu7JoAGlb4oBGnZorehu95KA==", - "requires": { - "mitm": "^1.3.2" - } - }, - "jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "requires": {} - }, - "jest-regex-util": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", - "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==" - }, - "jest-resolve": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.0.2.tgz", - "integrity": "sha512-q/XT0XQvRemykZsvRopbG6FQUT6/ra+XV6rPijyjT6D0msOyCvR2A5PlWZLd+fH0U8XWKZfDiAgrUNDNX2BkCw==", - "requires": { - "chalk": "^4.1.2", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.0.2", - "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.0.2", - "jest-validate": "30.0.2", - "slash": "^3.0.0", - "unrs-resolver": "^1.7.11" - }, - "dependencies": { - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "jest-resolve-dependencies": { - "version": "30.0.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.0.3.tgz", - "integrity": "sha512-FlL6u7LiHbF0Oe27k7DHYMq2T2aNpPhxnNo75F7lEtu4A6sSw+TKkNNUGNcVckdFoL0RCWREJsC1HsKDwKRZzQ==", - "requires": { - "jest-regex-util": "30.0.1", - "jest-snapshot": "30.0.3" - } - }, - "jest-runner": { - "version": "30.0.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.0.3.tgz", - "integrity": "sha512-CxYBzu9WStOBBXAKkLXGoUtNOWsiS1RRmUQb6SsdUdTcqVncOau7m8AJ4cW3Mz+YL1O9pOGPSYLyvl8HBdFmkQ==", - "requires": { - "@jest/console": "30.0.2", - "@jest/environment": "30.0.2", - "@jest/test-result": "30.0.2", - "@jest/transform": "30.0.2", - "@jest/types": "30.0.1", - "@types/node": "*", - "chalk": "^4.1.2", - "emittery": "^0.13.1", - "exit-x": "^0.2.2", - "graceful-fs": "^4.2.11", - "jest-docblock": "30.0.1", - "jest-environment-node": "30.0.2", - "jest-haste-map": "30.0.2", - "jest-leak-detector": "30.0.2", - "jest-message-util": "30.0.2", - "jest-resolve": "30.0.2", - "jest-runtime": "30.0.3", - "jest-util": "30.0.2", - "jest-watcher": "30.0.2", - "jest-worker": "30.0.2", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "dependencies": { - "@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "requires": { - "@sinclair/typebox": "^0.34.0" - } - }, - "@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "requires": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - } - }, - "@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "jest-runtime": { - "version": "30.0.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.0.3.tgz", - "integrity": "sha512-Xjosq0C48G9XEQOtmgrjXJwPaUPaq3sPJwHDRaiC+5wi4ZWxO6Lx6jNkizK/0JmTulVNuxP8iYwt77LGnfg3/w==", - "requires": { - "@jest/environment": "30.0.2", - "@jest/fake-timers": "30.0.2", - "@jest/globals": "30.0.3", - "@jest/source-map": "30.0.1", - "@jest/test-result": "30.0.2", - "@jest/transform": "30.0.2", - "@jest/types": "30.0.1", - "@types/node": "*", - "chalk": "^4.1.2", - "cjs-module-lexer": "^2.1.0", - "collect-v8-coverage": "^1.0.2", - "glob": "^10.3.10", - "graceful-fs": "^4.2.11", - "jest-haste-map": "30.0.2", - "jest-message-util": "30.0.2", - "jest-mock": "30.0.2", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.0.2", - "jest-snapshot": "30.0.3", - "jest-util": "30.0.2", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "dependencies": { - "@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "requires": { - "@sinclair/typebox": "^0.34.0" - } - }, - "@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "requires": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - } - }, - "@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" - }, - "brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "requires": { - "balanced-match": "^1.0.0" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "requires": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - } - }, - "jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "requires": { - "@isaacs/cliui": "^8.0.2", - "@pkgjs/parseargs": "^0.11.0" - } - }, - "lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" - }, - "minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "requires": { - "brace-expansion": "^2.0.1" - } - }, - "path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "requires": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - } - } - } - }, - "jest-snapshot": { - "version": "30.0.3", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.0.3.tgz", - "integrity": "sha512-F05JCohd3OA1N9+5aEPXA6I0qOfZDGIx0zTq5Z4yMBg2i1p5ELfBusjYAWwTkC12c7dHcbyth4QAfQbS7cRjow==", - "requires": { - "@babel/core": "^7.27.4", - "@babel/generator": "^7.27.5", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.27.1", - "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.0.3", - "@jest/get-type": "30.0.1", - "@jest/snapshot-utils": "30.0.1", - "@jest/transform": "30.0.2", - "@jest/types": "30.0.1", - "babel-preset-current-node-syntax": "^1.1.0", - "chalk": "^4.1.2", - "expect": "30.0.3", - "graceful-fs": "^4.2.11", - "jest-diff": "30.0.3", - "jest-matcher-utils": "30.0.3", - "jest-message-util": "30.0.2", - "jest-util": "30.0.2", - "pretty-format": "30.0.2", - "semver": "^7.7.2", - "synckit": "^0.11.8" - }, - "dependencies": { - "@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "requires": { - "@sinclair/typebox": "^0.34.0" - } - }, - "@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "requires": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - } - }, - "@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "jest-util": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.2.tgz", - "integrity": "sha512-8IyqfKS4MqprBuUpZNlFB5l+WFehc8bfCe1HSZFHzft2mOuND8Cvi9r1musli+u6F3TqanCZ/Ik4H4pXUolZIg==", - "requires": { - "@jest/types": "30.0.1", - "@types/node": "*", - "chalk": "^4.1.2", - "ci-info": "^4.2.0", - "graceful-fs": "^4.2.11", - "picomatch": "^4.0.2" - }, - "dependencies": { - "@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "requires": { - "@sinclair/typebox": "^0.34.0" - } - }, - "@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "requires": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - } - }, - "@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==" - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==" - } - } - }, - "jest-validate": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.0.2.tgz", - "integrity": "sha512-noOvul+SFER4RIvNAwGn6nmV2fXqBq67j+hKGHKGFCmK4ks/Iy1FSrqQNBLGKlu4ZZIRL6Kg1U72N1nxuRCrGQ==", - "requires": { - "@jest/get-type": "30.0.1", - "@jest/types": "30.0.1", - "camelcase": "^6.3.0", - "chalk": "^4.1.2", - "leven": "^3.1.0", - "pretty-format": "30.0.2" - }, - "dependencies": { - "@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "requires": { - "@sinclair/typebox": "^0.34.0" - } - }, - "@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "requires": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - } - }, - "@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" - }, - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==" - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "jest-watcher": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.0.2.tgz", - "integrity": "sha512-vYO5+E7jJuF+XmONr6CrbXdlYrgvZqtkn6pdkgjt/dU64UAdc0v1cAVaAeWtAfUUMScxNmnUjKPUMdCpNVASwg==", - "requires": { - "@jest/test-result": "30.0.2", - "@jest/types": "30.0.1", - "@types/node": "*", - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "emittery": "^0.13.1", - "jest-util": "30.0.2", - "string-length": "^4.0.2" - }, - "dependencies": { - "@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "requires": { - "@sinclair/typebox": "^0.34.0" - } - }, - "@jest/types": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", - "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", - "requires": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.1", - "@types/istanbul-lib-coverage": "^2.0.6", - "@types/istanbul-reports": "^3.0.4", - "@types/node": "*", - "@types/yargs": "^17.0.33", - "chalk": "^4.1.2" - } - }, - "@sinclair/typebox": { - "version": "0.34.37", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", - "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==" - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - } - } - }, - "jest-worker": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.0.2.tgz", - "integrity": "sha512-RN1eQmx7qSLFA+o9pfJKlqViwL5wt+OL3Vff/A+/cPsmuw7NPwfgl33AP+/agRmHzPOFgXviRycR9kYwlcRQXg==", - "requires": { - "@types/node": "*", - "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.0.2", - "merge-stream": "^2.0.0", - "supports-color": "^8.1.1" - }, - "dependencies": { - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "js-git": { - "version": "0.7.8", - "resolved": "https://registry.npmjs.org/js-git/-/js-git-0.7.8.tgz", - "integrity": "sha512-+E5ZH/HeRnoc/LW0AmAyhU+mNcWBzAKE+30+IDMLSLbbK+Tdt02AdkOKq9u15rlJsDEGFqtgckc8ZM59LhhiUA==", - "requires": { - "bodec": "^0.1.0", - "culvert": "^0.1.2", - "git-sha1": "^0.1.2", - "pako": "^0.2.5" - }, - "dependencies": { - "pako": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", - "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==" - } - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsbn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" - }, - "jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==" - }, - "json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "optional": true - }, - "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==" - }, - "jsonc-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", - "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==" - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "requires": { - "json-buffer": "3.0.1" - } - }, - "kuler": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", - "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" - }, - "langs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/langs/-/langs-2.0.0.tgz", - "integrity": "sha512-v4pxOBEQVN1WBTfB1crhTtxzNLZU9HPWgadlwzWKISJtt6Ku/CnpBrwVy+jFv8StjxsPfwPFzO0CMwdZLJ0/BA==" - }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==" - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "libxml2-wasm": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/libxml2-wasm/-/libxml2-wasm-0.5.0.tgz", - "integrity": "sha512-ANq8aMCg/+pYJv3QqgrvYzJldvm2P2V2T08303AVyzjdeCuOAOjxPUSazQj/NA2+rOcS9BMx/HTTtq1I2g8foQ==" - }, - "lie": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", - "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", - "requires": { - "immediate": "~3.0.5" - } - }, - "lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" - }, - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - }, - "dependencies": { - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" - } - } - }, - "localforage": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", - "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", - "requires": { - "lie": "3.1.1" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "requires": { - "p-locate": "^4.1.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" - }, - "logform": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.4.0.tgz", - "integrity": "sha512-CPSJw4ftjf517EhXZGGvTHHkYobo7ZCc0kvwUoOYcjfR2UVrI66RHj8MCrfAdEitdmFqbu2BYdYs8FHHZSb6iw==", - "requires": { - "@colors/colors": "1.5.0", - "fecha": "^4.2.0", - "ms": "^2.1.1", - "safe-stable-stringify": "^2.3.1", - "triple-beam": "^1.3.0" - } - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "requires": { - "yallist": "^3.0.2" - } - }, - "luxon": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.6.1.tgz", - "integrity": "sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==" - }, - "make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "requires": { - "semver": "^7.5.3" - } - }, - "makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "requires": { - "tmpl": "1.0.5" - } - }, - "math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" - }, - "micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "requires": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - } - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "requires": { - "mime-db": "1.52.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" - }, - "minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==" - }, - "mitm": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/mitm/-/mitm-1.7.3.tgz", - "integrity": "sha512-linie/mGisDH73C7aiW6JmstA5XskXd15JBJAEeNQBdH3/L0dJdE/yZ+rw/y2zT7Fcib5KAnL5OvxYOOFQbsgw==", - "requires": { - "semver": ">= 5 < 6" - }, - "dependencies": { - "semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==" - } - } - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" - }, - "mockdate": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/mockdate/-/mockdate-3.0.5.tgz", - "integrity": "sha512-iniQP4rj1FhBdBYS/+eQv7j1tadJ9lJtdzgOpvsOHng/GbcDh2Fhdeq+ZRldrPYdXvCyfFUmFeEwEGXZB5I/AQ==" - }, - "module-details-from-path": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", - "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==" - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" - }, - "napi-postinstall": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.2.5.tgz", - "integrity": "sha512-kmsgUvCRIJohHjbZ3V8avP0I1Pekw329MVAMDzVxsrkjgdnqiwvMX5XwR+hWV66vsAtZ+iM+fVnq8RTQawUmCQ==" - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=" - }, - "natural-orderby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/natural-orderby/-/natural-orderby-5.0.0.tgz", - "integrity": "sha512-kKHJhxwpR/Okycz4HhQKKlhWe4ASEfPgkSWNmKFHd7+ezuQlxkA5cM3+XkBPvm1gmHen3w53qsYAv+8GwRrBlg==" - }, - "nedb-promises": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/nedb-promises/-/nedb-promises-6.2.3.tgz", - "integrity": "sha512-enq0IjNyBz9Qy9W/QPCcLGh/QORGBjXbIeZeWvIjO3OMLyAvlKT3hiJubP2BKEiFniUlR3L01o18ktqgn5jxqA==", - "requires": { - "@seald-io/nedb": "^4.0.2" - } - }, - "needle": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", - "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==", - "requires": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "requires": { - "ms": "^2.1.1" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - } - } - }, - "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" - }, - "netmask": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", - "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==" - }, - "node-cleanup": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/node-cleanup/-/node-cleanup-2.1.2.tgz", - "integrity": "sha1-esGavSl+Caf3KnFUXZUbUX5N3iw=" - }, - "node-ensure": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/node-ensure/-/node-ensure-0.0.0.tgz", - "integrity": "sha1-7K52QVDemYYexcgQ/V0Jaxg5Mqc=" - }, - "node-gzip": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/node-gzip/-/node-gzip-1.1.2.tgz", - "integrity": "sha512-ZB6zWpfZHGtxZnPMrJSKHVPrRjURoUzaDbLFj3VO70mpLTW5np96vXyHwft4Id0o+PYIzgDkBUjIzaNHhQ8srw==" - }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==" - }, - "node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==" - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" - }, - "normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "requires": { - "path-key": "^3.0.0" - } - }, - "nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "requires": { - "boolbase": "^1.0.0" - } - }, - "numeral": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/numeral/-/numeral-2.0.6.tgz", - "integrity": "sha512-qaKRmtYPZ5qdw4jWJD6bxEf1FJEqllJrwxCLIm0sQU/A7v2/czigzOb+C2uSiFsa9lBUzeH7M1oK+Q+OLxL3kA==" - }, - "object-code": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/object-code/-/object-code-1.2.2.tgz", - "integrity": "sha512-ZSbEQdei4ElzuDM4BmazKSwINacocBf3/8rte25aNqXzvT/8dSaNVY9egsjAaBL/UhW55JNxAvXOKPIsL2MwWQ==" - }, - "object-treeify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-2.1.1.tgz", - "integrity": "sha512-ofXhazOvXTYWbbibExMiS+asaTbYG/ZWopVroXFFOdjmc8ehXMq9R2VUaTx/C3CnZkQbT52wAZT4DrBLK/nQfw==" - }, - "on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "requires": { - "wrappy": "1" - } - }, - "one-time": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", - "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", - "requires": { - "fn.name": "1.x.x" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", - "requires": { - "@aashutoshrathi/word-wrap": "^1.2.3", - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==" - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "requires": { - "p-limit": "^2.2.0" - }, - "dependencies": { - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - } - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" - }, - "pac-proxy-agent": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", - "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", - "requires": { - "@tootallnate/quickjs-emscripten": "^0.23.0", - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "get-uri": "^6.0.1", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.6", - "pac-resolver": "^7.0.1", - "socks-proxy-agent": "^8.0.5" - } - }, - "pac-resolver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", - "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", - "requires": { - "degenerator": "^5.0.0", - "netmask": "^2.0.2" - } - }, - "package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" - }, - "pako": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", - "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-duration": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/parse-duration/-/parse-duration-2.1.4.tgz", - "integrity": "sha512-b98m6MsCh+akxfyoz9w9dt0AlH2dfYLOBss5SdDsr9pkhKNvkWBXU/r8A4ahmIGByBOLV2+4YwfCuFxbDDaGyg==" - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "requires": { - "entities": "^6.0.0" - }, - "dependencies": { - "entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==" - } - } - }, - "parse5-htmlparser2-tree-adapter": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", - "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", - "requires": { - "domhandler": "^5.0.3", - "parse5": "^7.0.0" - } - }, - "parse5-parser-stream": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", - "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", - "requires": { - "parse5": "^7.0.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==" - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "path-scurry": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", - "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", - "requires": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "dependencies": { - "lru-cache": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", - "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==" - } - } - }, - "path-to-regexp": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", - "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==" - }, - "pdf-parse": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pdf-parse/-/pdf-parse-1.1.1.tgz", - "integrity": "sha512-v6ZJ/efsBpGrGGknjtq9J/oC8tZWq0KWL5vQrk2GlzLEQPUDB1ex+13Rmidl1neNN358Jn9EHZw5y07FFtaC7A==", - "requires": { - "debug": "^3.1.0", - "node-ensure": "^0.0.0" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" - }, - "pidusage": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-3.0.2.tgz", - "integrity": "sha512-g0VU+y08pKw5M8EZ2rIGiEBaB8wrQMjYGFfW2QVIfyT8V+fq8YFLkvlz4bz5ljvFDJYNFCWT3PWqcRr2FKO81w==", - "requires": { - "safe-buffer": "^5.2.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - } - } - }, - "pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==" - }, - "pkg-conf": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", - "integrity": "sha1-ISZRTKbyq/69FoWW3xi6V4Z/AFg=", - "requires": { - "find-up": "^2.0.0", - "load-json-file": "^4.0.0" - }, - "dependencies": { - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "requires": { - "locate-path": "^2.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" - } - } - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "requires": { - "find-up": "^4.0.0" - } - }, - "pm2": { - "version": "6.0.8", - "resolved": "https://registry.npmjs.org/pm2/-/pm2-6.0.8.tgz", - "integrity": "sha512-y7sO+UuGjfESK/ChRN+efJKAsHrBd95GY2p1GQfjVTtOfFtUfiW0NOuUhP5dN5QTF2F0EWcepgkLqbF32j90Iw==", - "requires": { - "@pm2/agent": "~2.1.1", - "@pm2/io": "~6.1.0", - "@pm2/js-api": "~0.8.0", - "@pm2/pm2-version-check": "latest", - "ansis": "4.0.0-node10", - "async": "~3.2.6", - "blessed": "0.1.81", - "chokidar": "^3.5.3", - "cli-tableau": "^2.0.0", - "commander": "2.15.1", - "croner": "~4.1.92", - "dayjs": "~1.11.13", - "debug": "^4.3.7", - "enquirer": "2.3.6", - "eventemitter2": "5.0.1", - "fclone": "1.0.11", - "js-yaml": "~4.1.0", - "mkdirp": "1.0.4", - "needle": "2.4.0", - "pidusage": "~3.0", - "pm2-axon": "~4.0.1", - "pm2-axon-rpc": "~0.7.1", - "pm2-deploy": "~1.0.2", - "pm2-multimeter": "^0.1.2", - "pm2-sysmonit": "^1.2.8", - "promptly": "^2", - "semver": "^7.6.2", - "source-map-support": "0.5.21", - "sprintf-js": "1.1.2", - "vizion": "~2.2.1" - }, - "dependencies": { - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==" - }, - "debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "requires": { - "ms": "^2.1.3" - } - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "requires": { - "argparse": "^2.0.1" - } - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "sprintf-js": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", - "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==" - } - } - }, - "pm2-axon": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pm2-axon/-/pm2-axon-4.0.1.tgz", - "integrity": "sha512-kES/PeSLS8orT8dR5jMlNl+Yu4Ty3nbvZRmaAtROuVm9nYYGiaoXqqKQqQYzWQzMYWUKHMQTvBlirjE5GIIxqg==", - "requires": { - "amp": "~0.3.1", - "amp-message": "~0.1.1", - "debug": "^4.3.1", - "escape-string-regexp": "^4.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" - } - } - }, - "pm2-axon-rpc": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/pm2-axon-rpc/-/pm2-axon-rpc-0.7.1.tgz", - "integrity": "sha512-FbLvW60w+vEyvMjP/xom2UPhUN/2bVpdtLfKJeYM3gwzYhoTEEChCOICfFzxkxuoEleOlnpjie+n1nue91bDQw==", - "requires": { - "debug": "^4.3.1" - } - }, - "pm2-deploy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pm2-deploy/-/pm2-deploy-1.0.2.tgz", - "integrity": "sha512-YJx6RXKrVrWaphEYf++EdOOx9EH18vM8RSZN/P1Y+NokTKqYAca/ejXwVLyiEpNju4HPZEk3Y2uZouwMqUlcgg==", - "requires": { - "run-series": "^1.1.8", - "tv4": "^1.3.0" - } - }, - "pm2-multimeter": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/pm2-multimeter/-/pm2-multimeter-0.1.2.tgz", - "integrity": "sha512-S+wT6XfyKfd7SJIBqRgOctGxaBzUOmVQzTAS+cg04TsEUObJVreha7lvCfX8zzGVr871XwCSnHUU7DQQ5xEsfA==", - "requires": { - "charm": "~0.1.1" - } - }, - "pm2-sysmonit": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/pm2-sysmonit/-/pm2-sysmonit-1.2.8.tgz", - "integrity": "sha512-ACOhlONEXdCTVwKieBIQLSi2tQZ8eKinhcr9JpZSUAL8Qy0ajIgRtsLxG/lwPOW3JEKqPyw/UaHmTWhUzpP4kA==", - "optional": true, - "requires": { - "async": "^3.2.0", - "debug": "^4.3.1", - "pidusage": "^2.0.21", - "systeminformation": "^5.7", - "tx2": "~1.0.4" - }, - "dependencies": { - "pidusage": { - "version": "2.0.21", - "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-2.0.21.tgz", - "integrity": "sha512-cv3xAQos+pugVX+BfXpHsbyz/dLzX+lr44zNMsYiGxUw+kV5sgQCIcLd1z+0vq+KyC7dJ+/ts2PsfgWfSC3WXA==", - "optional": true, - "requires": { - "safe-buffer": "^5.2.1" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "optional": true - } - } - }, - "possible-typed-array-names": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==" - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" - }, - "pretty-format": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.2.tgz", - "integrity": "sha512-yC5/EBSOrTtqhCKfLHqoUIAXVRZnukHPwWBJWR7h84Q3Be1DRQZLncwcfLoPA5RPQ65qfiCMqgYwdUuQ//eVpg==", - "requires": { - "@jest/schemas": "30.0.1", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "dependencies": { - "@jest/schemas": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", - "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", - "requires": { - "@sinclair/typebox": "^0.34.0" - } - }, - "@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==" - }, - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" - } - } - }, - "promptly": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/promptly/-/promptly-2.2.0.tgz", - "integrity": "sha512-aC9j+BZsRSSzEsXBNBwDnAxujdx19HycZoKgRgzWnS8eOHg1asuf9heuLprfbe739zY3IdUQx+Egv6Jn135WHA==", - "requires": { - "read": "^1.0.4" - } - }, - "proxy-agent": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", - "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", - "requires": { - "agent-base": "^7.0.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.1", - "https-proxy-agent": "^7.0.3", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.1", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.2" - }, - "dependencies": { - "lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==" - } - } - }, - "proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, - "punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" - }, - "pure-rand": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", - "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==" - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" - }, - "range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==" - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==" - } - } - }, - "react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" - }, - "read": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", - "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", - "requires": { - "mute-stream": "~0.0.4" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "requires": { - "picomatch": "^2.2.1" - } - }, - "readline": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz", - "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==" - }, - "registry-auth-token": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", - "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", - "requires": { - "rc": "^1.1.6", - "safe-buffer": "^5.0.1" - } - }, - "registry-url": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", - "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==", - "requires": { - "rc": "^1.0.1" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" - }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" - }, - "require-in-the-middle": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-5.2.0.tgz", - "integrity": "sha512-efCx3b+0Z69/LGJmm9Yvi4cqEdxnoGnxYxGxBghkkTTFeXRtTCmmhO0AnAfHz59k957uTSuy8WaHqOs8wbYUWg==", - "requires": { - "debug": "^4.1.1", - "module-details-from-path": "^1.0.3", - "resolve": "^1.22.1" - } - }, - "resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "requires": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "requires": { - "resolve-from": "^5.0.0" - } - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" - }, - "resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==" - }, - "reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==" - }, - "run-async": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", - "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==" - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "run-script-os": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/run-script-os/-/run-script-os-1.1.6.tgz", - "integrity": "sha512-ql6P2LzhBTTDfzKts+Qo4H94VUKpxKDFz6QxxwaUZN0mwvi7L3lpOI7BqPCq7lgDh3XLl0dpeXwfcVIitlrYrw==" - }, - "run-series": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/run-series/-/run-series-1.1.9.tgz", - "integrity": "sha512-Arc4hUN896vjkqCYrUXquBFtRZdv1PfLbTYP71efP6butxyQ0kWpiNJyAgsxscmQg1cqvHY32/UCBzXedTpU2g==" - }, - "rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "requires": { - "tslib": "^2.1.0" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safe-regex-test": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "requires": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - } - }, - "safe-stable-stringify": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.3.1.tgz", - "integrity": "sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, - "semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==" - }, - "serve": { - "version": "14.2.4", - "resolved": "https://registry.npmjs.org/serve/-/serve-14.2.4.tgz", - "integrity": "sha512-qy1S34PJ/fcY8gjVGszDB3EXiPSk5FKhUa7tQe0UPRddxRidc2V6cNHPNewbE1D7MAkgLuWEt3Vw56vYy73tzQ==", - "requires": { - "@zeit/schemas": "2.36.0", - "ajv": "8.12.0", - "arg": "5.0.2", - "boxen": "7.0.0", - "chalk": "5.0.1", - "chalk-template": "0.4.0", - "clipboardy": "3.0.0", - "compression": "1.7.4", - "is-port-reachable": "4.0.0", - "serve-handler": "6.1.6", - "update-check": "1.5.4" - }, - "dependencies": { - "ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "chalk": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", - "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==" - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - } - } - }, - "serve-handler": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz", - "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==", - "requires": { - "bytes": "3.0.0", - "content-disposition": "0.5.2", - "mime-types": "2.1.18", - "minimatch": "3.1.2", - "path-is-inside": "1.0.2", - "path-to-regexp": "3.3.0", - "range-parser": "1.2.0" - }, - "dependencies": { - "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" - }, - "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", - "requires": { - "mime-db": "~1.33.0" - } - } - } - }, - "set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "requires": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" - }, - "shimmer": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", - "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==" - }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - }, - "signale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/signale/-/signale-1.4.0.tgz", - "integrity": "sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==", - "requires": { - "chalk": "^2.3.2", - "figures": "^2.0.0", - "pkg-conf": "^2.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", - "requires": { - "is-arrayish": "^0.3.1" - }, - "dependencies": { - "is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" - } - } - }, - "skip-postinstall": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/skip-postinstall/-/skip-postinstall-1.0.0.tgz", - "integrity": "sha512-IUVEmm4v7Ubzrp9JDG15oTzMB+abJdHcduXMRzBlHnHRrmpQ/QoPtYCRaorP+abAULTGEh87gPPyyMK5H1X1Dg==" - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" - }, - "smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" - }, - "socks": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", - "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", - "requires": { - "ip-address": "^9.0.5", - "smart-buffer": "^4.2.0" - } - }, - "socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", - "requires": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" - }, - "srcset": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/srcset/-/srcset-5.0.1.tgz", - "integrity": "sha512-/P1UYbGfJVlxZag7aABNRrulEXAwCSDo7fklafOQrantuPTDmYgijJMks2zusPCVzgW9+4P69mq7w6pYuZpgxw==" - }, - "stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" - }, - "stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "requires": { - "escape-string-regexp": "^2.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==" - } - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - } - } - }, - "string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "requires": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "string-width-cjs": { - "version": "npm:string-width@4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-ansi-cjs": { - "version": "npm:strip-ansi@6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==" - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==" - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" - }, - "synckit": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.8.tgz", - "integrity": "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==", - "requires": { - "@pkgr/core": "^0.2.4" - } - }, - "systeminformation": { - "version": "5.25.11", - "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.25.11.tgz", - "integrity": "sha512-jI01fn/t47rrLTQB0FTlMCC+5dYx8o0RRF+R4BPiUNsvg5OdY0s9DKMFmJGrx5SwMZQ4cag0Gl6v8oycso9b/g==", - "optional": true - }, - "table2array": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/table2array/-/table2array-0.0.2.tgz", - "integrity": "sha512-jSYrVGJL1q7IEuPUSsyteLY9zAKWO8XUaqwBQmX2jWHm9RS3cj+gb69lI2JkKA3ZXjhEODeBcf5APHyBCIcJuA==", - "requires": { - "cheerio": "^1.0.0-rc.12" - } - }, - "tabletojson": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/tabletojson/-/tabletojson-4.1.6.tgz", - "integrity": "sha512-5wMfcIJ9N45EO4HwgpYpsJGXVCtMiEYastQkmyc3D2Jz4w+CJ9Up8WmoOGreguFInxhcchB1atIll20ZCbOf+w==", - "requires": { - "cheerio": "^1.0.0" - } - }, - "test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "dependencies": { - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, - "text-hex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", - "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" - }, - "timer-node": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/timer-node/-/timer-node-5.0.9.tgz", - "integrity": "sha512-zXxCE/5/YDi0hY9pygqgRqjRbrFRzigYxOudG0I3syaqAAmX9/w9sxex1bNFCN6c1S66RwPtEIJv65dN+1psew==" - }, - "tldts": { - "version": "6.1.68", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.68.tgz", - "integrity": "sha512-JKF17jROiYkjJPT73hUTEiTp2OBCf+kAlB+1novk8i6Q6dWjHsgEjw9VLiipV4KTJavazXhY1QUXyQFSem2T7w==", - "requires": { - "tldts-core": "^6.1.68" - } - }, - "tldts-core": { - "version": "6.1.68", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.68.tgz", - "integrity": "sha512-85TdlS/DLW/gVdf2oyyzqp3ocS30WxjaL4la85EArl9cHUR/nizifKAJPziWewSZjDZS71U517/i6ciUeqtB5Q==" - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "requires": { - "os-tmpdir": "~1.0.2" - } - }, - "tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==" - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "requires": { - "is-number": "^7.0.0" - } - }, - "tough-cookie": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", - "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", - "requires": { - "tldts": "^6.1.32" - } - }, - "transliteration": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/transliteration/-/transliteration-2.3.5.tgz", - "integrity": "sha512-HAGI4Lq4Q9dZ3Utu2phaWgtm3vB6PkLUFqWAScg/UW+1eZ/Tg6Exo4oC0/3VUol/w4BlefLhUUSVBr/9/ZGQOw==", - "requires": { - "yargs": "^17.5.1" - } - }, - "triple-beam": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", - "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" - }, - "ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", - "requires": {} - }, - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - }, - "tsx": { - "version": "4.20.3", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.3.tgz", - "integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==", - "requires": { - "esbuild": "~0.25.0", - "fsevents": "~2.3.3", - "get-tsconfig": "^4.7.5" - } - }, - "tv4": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/tv4/-/tv4-1.3.0.tgz", - "integrity": "sha512-afizzfpJgvPr+eDkREK4MxJ/+r8nEEHcmitwgnPUqpaP+FpwQyadnxNoSACbgc/b1LsZYtODGoPiFxQrgJgjvw==" - }, - "tx2": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tx2/-/tx2-1.0.5.tgz", - "integrity": "sha512-sJ24w0y03Md/bxzK4FU8J8JveYYUbSs2FViLJ2D/8bytSiyPRbuE3DyL/9UKYXTZlV3yXq0L8GLlhobTnekCVg==", - "optional": true, - "requires": { - "json-stringify-safe": "^5.0.1" - } - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" - }, - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==" - }, - "typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==" - }, - "undici": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.10.0.tgz", - "integrity": "sha512-u5otvFBOBZvmdjWLVW+5DAc9Nkq8f24g0O9oY7qw2JVIF1VocIFoyz9JFkuVOS2j41AufeO0xnlweJ2RLT8nGw==" - }, - "undici-types": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", - "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==" - }, - "universal-user-agent": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", - "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==" - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" - }, - "unrs-resolver": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.9.2.tgz", - "integrity": "sha512-VUyWiTNQD7itdiMuJy+EuLEErLj3uwX/EpHQF8EOf33Dq3Ju6VW1GXm+swk6+1h7a49uv9fKZ+dft9jU7esdLA==", - "requires": { - "@unrs/resolver-binding-android-arm-eabi": "1.9.2", - "@unrs/resolver-binding-android-arm64": "1.9.2", - "@unrs/resolver-binding-darwin-arm64": "1.9.2", - "@unrs/resolver-binding-darwin-x64": "1.9.2", - "@unrs/resolver-binding-freebsd-x64": "1.9.2", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.9.2", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.9.2", - "@unrs/resolver-binding-linux-arm64-gnu": "1.9.2", - "@unrs/resolver-binding-linux-arm64-musl": "1.9.2", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.9.2", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.9.2", - "@unrs/resolver-binding-linux-riscv64-musl": "1.9.2", - "@unrs/resolver-binding-linux-s390x-gnu": "1.9.2", - "@unrs/resolver-binding-linux-x64-gnu": "1.9.2", - "@unrs/resolver-binding-linux-x64-musl": "1.9.2", - "@unrs/resolver-binding-wasm32-wasi": "1.9.2", - "@unrs/resolver-binding-win32-arm64-msvc": "1.9.2", - "@unrs/resolver-binding-win32-ia32-msvc": "1.9.2", - "@unrs/resolver-binding-win32-x64-msvc": "1.9.2", - "napi-postinstall": "^0.2.4" - } - }, - "unzipit": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/unzipit/-/unzipit-1.4.3.tgz", - "integrity": "sha512-gsq2PdJIWWGhx5kcdWStvNWit9FVdTewm4SEG7gFskWs+XCVaULt9+BwuoBtJiRE8eo3L1IPAOrbByNLtLtIlg==", - "requires": { - "uzip-module": "^1.0.2" - } - }, - "update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "requires": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - } - }, - "update-check": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.4.tgz", - "integrity": "sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==", - "requires": { - "registry-auth-token": "3.3.2", - "registry-url": "3.1.0" - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "requires": { - "punycode": "^2.1.0" - } - }, - "util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "requires": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "uuid": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", - "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==" - }, - "uzip-module": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/uzip-module/-/uzip-module-1.0.3.tgz", - "integrity": "sha512-AMqwWZaknLM77G+VPYNZLEruMGWGzyigPK3/Whg99B3S6vGHuqsyl5ZrOv1UUF3paGK1U6PM0cnayioaryg/fA==" - }, - "v8-to-istanbul": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", - "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", - "requires": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - } - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" - }, - "vizion": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/vizion/-/vizion-2.2.1.tgz", - "integrity": "sha512-sfAcO2yeSU0CSPFI/DmZp3FsFE9T+8913nv1xWBOyzODv13fwkn6Vl7HqxGpkr9F608M+8SuFId3s+BlZqfXww==", - "requires": { - "async": "^2.6.3", - "git-node-fs": "^1.0.0", - "ini": "^1.3.5", - "js-git": "^0.7.8" - }, - "dependencies": { - "async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "requires": { - "lodash": "^4.17.14" - } - } - } - }, - "walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "requires": { - "makeerror": "1.0.12" - } - }, - "whatwg-encoding": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", - "requires": { - "iconv-lite": "0.6.3" - } - }, - "whatwg-mimetype": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==" - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "requires": { - "isexe": "^2.0.0" - } - }, - "which-typed-array": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", - "requires": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - } - }, - "widest-line": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", - "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", - "requires": { - "string-width": "^5.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==" - }, - "emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" - }, - "string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "requires": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - } - }, - "strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", - "requires": { - "ansi-regex": "^6.0.1" - } - } - } - }, - "wildcard-match": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/wildcard-match/-/wildcard-match-5.1.4.tgz", - "integrity": "sha512-wldeCaczs8XXq7hj+5d/F38JE2r7EXgb6WQDM84RVwxy81T/sxB5e9+uZLK9Q9oNz1mlvjut+QtvgaOQFPVq/g==" - }, - "winston": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.7.2.tgz", - "integrity": "sha512-QziIqtojHBoyzUOdQvQiar1DH0Xp9nF1A1y7NVy2DGEsz82SBDtOalS0ulTRGVT14xPX3WRWkCsdcJKqNflKng==", - "requires": { - "@dabh/diagnostics": "^2.0.2", - "async": "^3.2.3", - "is-stream": "^2.0.0", - "logform": "^2.4.0", - "one-time": "^1.0.0", - "readable-stream": "^3.4.0", - "safe-stable-stringify": "^2.3.1", - "stack-trace": "0.0.x", - "triple-beam": "^1.3.0", - "winston-transport": "^4.5.0" - } - }, - "winston-transport": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz", - "integrity": "sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==", - "requires": { - "logform": "^2.3.2", - "readable-stream": "^3.6.0", - "triple-beam": "^1.3.0" - } - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "wrap-ansi-cjs": { - "version": "npm:wrap-ansi@7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "write-file-atomic": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", - "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", - "requires": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" - }, - "dependencies": { - "signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" - } - } - }, - "ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "requires": {} - }, - "xml-js": { - "version": "1.6.11", - "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", - "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", - "requires": { - "sax": "^1.2.4" - } - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - }, - "yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - } - }, - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" - }, - "yoctocolors-cjs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", - "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==" - } } } diff --git a/package.json b/package.json index adfadf145..af6070bc5 100644 --- a/package.json +++ b/package.json @@ -39,28 +39,27 @@ "dependencies": { "@alex_neo/jest-expect-message": "^1.0.5", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "^9.30.0", - "@freearhey/core": "^0.8.2", + "@eslint/js": "^9.31.0", + "@freearhey/core": "^0.10.2", "@freearhey/search-js": "^0.1.2", "@ntlab/sfetch": "^1.2.0", - "@octokit/core": "^7.0.2", + "@octokit/core": "^7.0.3", "@octokit/plugin-paginate-rest": "^13.1.1", "@octokit/plugin-rest-endpoint-methods": "^16.0.0", - "@swc/core": "^1.12.7", - "@swc/jest": "^0.2.38", + "@swc/core": "^1.13.0", + "@swc/jest": "^0.2.39", "@types/cli-progress": "^3.11.6", "@types/fs-extra": "^11.0.4", "@types/inquirer": "^9.0.8", "@types/jest": "^30.0.0", "@types/langs": "^2.0.5", - "@types/lodash": "^4.17.19", - "@types/node": "^24.0.7", + "@types/node": "^24.0.15", "@types/node-cleanup": "^2.1.5", "@types/numeral": "^2.0.5", - "@typescript-eslint/eslint-plugin": "^8.35.0", - "@typescript-eslint/parser": "^8.35.0", + "@typescript-eslint/eslint-plugin": "^8.37.0", + "@typescript-eslint/parser": "^8.37.0", "axios": "^1.10.0", - "axios-cookiejar-support": "^6.0.2", + "axios-cookiejar-support": "^6.0.4", "chalk": "^5.4.1", "cheerio": "^1.1.0", "cli-progress": "^3.12.0", @@ -70,23 +69,22 @@ "csv-parser": "^3.2.0", "cwait": "^1.1.2", "dayjs": "^1.11.13", - "epg-grabber": "^0.38.0", + "epg-grabber": "^0.41.0", "epg-parser": "^0.3.1", - "eslint": "^9.30.0", - "eslint-config-prettier": "^10.1.5", - "form-data": "^4.0.3", + "eslint": "^9.31.0", + "eslint-config-prettier": "^10.1.8", + "form-data": "^4.0.4", "fs-extra": "^11.3.0", "glob": "^11.0.3", - "globals": "^16.2.0", + "globals": "^16.3.0", "husky": "^9.1.7", "iconv-lite": "^0.6.3", - "inquirer": "^12.6.3", - "jest": "^30.0.3", + "inquirer": "^12.7.0", + "jest": "^30.0.4", "jest-offline": "^1.0.1", "langs": "^2.0.0", "libxml2-wasm": "^0.5.0", - "lodash": "^4.17.21", - "luxon": "^3.6.1", + "luxon": "^3.7.1", "mockdate": "^3.0.5", "nedb-promises": "^6.2.3", "node-cleanup": "^2.1.2", @@ -112,6 +110,5 @@ "unzipit": "^1.4.3", "uuid": "^11.1.0", "wildcard-match": "^5.1.4" - }, - "packageManager": "yarn@4.9.2" + } } diff --git a/sites/rtp.pt/rtp.pt.config.js b/sites/rtp.pt/rtp.pt.config.js index 0108e255d..99f233909 100644 --- a/sites/rtp.pt/rtp.pt.config.js +++ b/sites/rtp.pt/rtp.pt.config.js @@ -1,4 +1,3 @@ -const _ = require('lodash') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const timezone = require('dayjs/plugin/timezone') @@ -62,5 +61,5 @@ function parseItems(content) { if (!content) return [] const data = JSON.parse(content) - return _.flatten(Object.values(data.result)) + return Object.values(data.result).flat() } diff --git a/sites/sky.com/sky.com.config.js b/sites/sky.com/sky.com.config.js index bed9a107f..3ee678374 100644 --- a/sites/sky.com/sky.com.config.js +++ b/sites/sky.com/sky.com.config.js @@ -3,7 +3,7 @@ const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const doFetch = require('@ntlab/sfetch') const debug = require('debug')('site:sky.com') -const _ = require('lodash') +const { sortBy } = require('../../scripts/functions') dayjs.extend(utc) @@ -26,7 +26,7 @@ module.exports = { .filter(schedule => schedule.sid === channel.site_id) .forEach(schedule => { if (Array.isArray(schedule.events)) { - _.sortBy(schedule.events, 'st').forEach(event => { + sortBy(schedule.events, p => p.st).forEach(event => { const start = dayjs.utc(event.st * 1000) if (start.isSame(date, 'd')) { const image = `https://images.metadata.sky.com/pd-image/${event.programmeuuid}/16-9/640` diff --git a/sites/tvcesoir.fr/tvcesoir.fr.config.js b/sites/tvcesoir.fr/tvcesoir.fr.config.js index 306f819f1..900cfc9f2 100644 --- a/sites/tvcesoir.fr/tvcesoir.fr.config.js +++ b/sites/tvcesoir.fr/tvcesoir.fr.config.js @@ -3,6 +3,7 @@ const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const timezone = require('dayjs/plugin/timezone') const customParseFormat = require('dayjs/plugin/customParseFormat') +const uniqBy = require('../../scripts/functions') dayjs.extend(utc) dayjs.extend(timezone) @@ -42,7 +43,6 @@ module.exports = { }, async channels() { const axios = require('axios') - const _ = require('lodash') const providers = ['-1', '-2', '-3', '-4', '-5'] @@ -77,7 +77,7 @@ module.exports = { }) } - return _.uniqBy(channels, 'site_id') + return uniqBy(channels, x => x.site_id) } } diff --git a/sites/tvhebdo.com/tvhebdo.com.config.js b/sites/tvhebdo.com/tvhebdo.com.config.js index be4cf8b45..7506ba98e 100644 --- a/sites/tvhebdo.com/tvhebdo.com.config.js +++ b/sites/tvhebdo.com/tvhebdo.com.config.js @@ -1,6 +1,7 @@ const cheerio = require('cheerio') const axios = require('axios') const { DateTime } = require('luxon') +const { uniqBy } = require('../../scripts/functions') module.exports = { site: 'tvhebdo.com', @@ -34,7 +35,6 @@ module.exports = { return programs }, async channels() { - const _ = require('lodash') let items = [] const offsets = [ @@ -72,7 +72,7 @@ module.exports = { }) }) - return _.uniqBy(channels, 'site_id') + return uniqBy(channels, x => x.site_id) } } diff --git a/sites/tvireland.ie/tvireland.ie.config.js b/sites/tvireland.ie/tvireland.ie.config.js index c28eb8c86..be43fa9f9 100644 --- a/sites/tvireland.ie/tvireland.ie.config.js +++ b/sites/tvireland.ie/tvireland.ie.config.js @@ -3,6 +3,7 @@ const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const timezone = require('dayjs/plugin/timezone') const customParseFormat = require('dayjs/plugin/customParseFormat') +const { uniqBy } = require('../../scripts/functions') dayjs.extend(utc) dayjs.extend(timezone) @@ -42,7 +43,6 @@ module.exports = { }, async channels() { const axios = require('axios') - const _ = require('lodash') const providers = ['-9000019', '-8000019', '-1000019', '-2000019', '-7000019'] @@ -77,7 +77,7 @@ module.exports = { }) } - return _.uniqBy(channels, 'site_id') + return uniqBy(channels, x => x.site_id) } } diff --git a/sites/tvmusor.hu/tvmusor.hu.config.js b/sites/tvmusor.hu/tvmusor.hu.config.js index 5b3ec2ca4..ea420299e 100644 --- a/sites/tvmusor.hu/tvmusor.hu.config.js +++ b/sites/tvmusor.hu/tvmusor.hu.config.js @@ -1,6 +1,6 @@ const axios = require('axios') const dayjs = require('dayjs') -const _ = require('lodash') +const { uniqBy } = require('../../scripts/functions') module.exports = { site: 'tvmusor.hu', @@ -77,5 +77,5 @@ function parseItems(content, channel, date) { const blockId = `${channel.site_id}_${date.format('YYYY-MM-DD')}` if (!Array.isArray(blocks[blockId])) return [] - return _.uniqBy(_.uniqBy(blocks[blockId], 'e'), 'b') + return uniqBy(uniqBy(blocks[blockId], a => a.e), b => b.b) } diff --git a/sites/web.magentatv.de/web.magentatv.de.config.js b/sites/web.magentatv.de/web.magentatv.de.config.js index 371df0c10..3a38685ab 100644 --- a/sites/web.magentatv.de/web.magentatv.de.config.js +++ b/sites/web.magentatv.de/web.magentatv.de.config.js @@ -2,7 +2,6 @@ const axios = require('axios') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') -const { upperCase } = require('lodash') let X_CSRFTOKEN let Cookie @@ -56,7 +55,7 @@ module.exports = { directors: parseDirectors(item), producers: parseProducers(item), adapters: parseAdapters(item), - country: upperCase(item.country), + country: item.country?.toUpperCase(), date: item.producedate, urls: parseUrls(item) }) From 218c905e66472aa9330b01f59b6baa47168a46b8 Mon Sep 17 00:00:00 2001 From: theofficialomega <30985701+BellezaEmporium@users.noreply.github.com> Date: Sun, 20 Jul 2025 19:14:52 +0200 Subject: [PATCH 09/32] updated channel list, updated config --- sites/tv.dir.bg/tv.dir.bg.channels.xml | 211 ++++++++++++------------- sites/tv.dir.bg/tv.dir.bg.config.js | 57 ++++--- 2 files changed, 128 insertions(+), 140 deletions(-) diff --git a/sites/tv.dir.bg/tv.dir.bg.channels.xml b/sites/tv.dir.bg/tv.dir.bg.channels.xml index af9fcd6a3..8488cbdd9 100644 --- a/sites/tv.dir.bg/tv.dir.bg.channels.xml +++ b/sites/tv.dir.bg/tv.dir.bg.channels.xml @@ -1,114 +1,103 @@ - MTV 00s - History - MTV Live HD - Eurosport HD - History HD - Nick HD - Планета HD - Disney Junior - Nat Geo HD - MTV 80s - Comedy Central - ID Xtra - MovieSTAR - FilmBox - MTV 90s - 24kitchen - ТВ 7/8 - Алфа - AMC - Animal Planet - AXN Black - AXN - AXN White - Балканика МТВ - Bloomberg TV Bulgaria - БНТ1 - БНТ2 - БНТ3 - БНТ4 - bTV - bTV Action - bTV Cinema - bTV Comedy - bTV Lady - Bulgaria ON AIR - Cartoon Network - CBS Reality - Первый канал - Cinemax2 - Cinemax - Crime + Investigation - Da Vinci - Diema - Diema Family - Diema Sport 2 - Diema Sport 3 - Diema Sport - Discovery Channel - Discovery Science - Disney Channel - DocuBox - E-Kids - Epic Drama - Евроком - Eurosport 1 - Eurosport 2 - Extreme Sports Channel - Фен Фолк - Фен - FilmBox Extra - FilmBox+ - Food Network - UA TV - HBO2 - HBO3 - HBO - HG TV - Хоби - Investigation Discovery - JimJam - Kino Nova - Love Nature - MAX Sport 1 - MAX Sport 2 - MAX Sport 3 - MAX Sport 4 - MCM - National Geographic - Nat Geo Wild - Nickelodeon - Nick Jr. - NOVA - Nova News - Nova Sport - НТВ Мир - Охота и рыбалка - Планета Фолк - Планета - Тракия (Пловдив) - RING - RM TV - Скат - FOX - FOX Crime - FOX Life - Телемедиа - TLC - Travel Channel - TV1 - Euronews Bulgaria - Враца - Viasat Explore - Viasat History - Viasat Nature - TV 1000 - Fashion TV - FightBox - Fuel TV - Mezzo Live HD - MTV Hits - Trace Sport Stars + 24 Kitchen + 7/8 TV + Al Jazeera + Animal Planet + AXN + AXN Black + AXN White + Baby TV + BBC News (former BBC World News) + Bloomberg TV Bulgaria + BNT1 (БНТ1) + BNT2 (БНТ2) + BNT3 (БНТ3) + BNT4 (БНТ4) + bTV + bTV Action + bTV Cinema + bTV Comedy + bTV Story (f.k.a bTV Lady) + Bulgaria ON AIR (България Он Еър) + Cartoon Network Bulgaria + Cartoonito (f.k.a Boomerang TV) + Cinemania + Cinemax + Cinemax 2 + CineStar TV + CineStar TV Action&Thriller + CNN + Code Fashion TV + Code Health TV + Crime & Investigation + Diema + Diema Family + Diema Sport + Diema Sport 2 + Diema Sport 3 + Discovery Channel + Disney Channel + Dizi (Timeless Drama Channel) + Duck TV + DW TV + Epic Drama + Eurocom + Euronews + Euronews Bulgaria (f.k.a Evropa TV) + Eurosport + Eurosport 2 + Eurosport 4K + Fightklub HD (Bulgaria) + FilmBox Basic + FilmBox Extra + FilmBox Stars (FilmBox Plus) + Food Network HD + France 24 + HBO + HBO2 + HBO3 + HGTV (Discovery Home & Garden) + History Bulgaria + ID (Investigation Discovery) + Kanal 3 (Канал 3) + Kanal 4 (Канал 4) + Kino Nova + Love Nature + Magic TV + MAX Sport 1 + MAX Sport 2 + MAX Sport 3 + MAX Sport 4 + MovieSTAR + MTV Europe + National Geographic + National Geographic Wild + Nick Jr + Nickelodeon + Nicktoons + Nostalgia TV + Nova News HD + NOVA Sport + NOVA TV + Ring.bg (bTV Sport) + RTL + SKAT TV + Skyshowtime 1 + Skyshowtime 2 + STAR Channel (f.k.a. FOX) + STAR Crime (f.k.a FOX Crime) + STAR Life (f.k.a. FOX Life) + Super Toons + The History Channel 2 + The Voice TV + TLC + Travel Channel + TV1 Bulgaria + Viasat Explore + Viasat History + Viasat Kino (TV1000) + Viasat Nature + Viasat True Crime + Vivacom Arena (Виваком Арена) diff --git a/sites/tv.dir.bg/tv.dir.bg.config.js b/sites/tv.dir.bg/tv.dir.bg.config.js index 0e73bec10..9cb5f5a54 100644 --- a/sites/tv.dir.bg/tv.dir.bg.config.js +++ b/sites/tv.dir.bg/tv.dir.bg.config.js @@ -6,35 +6,6 @@ const { DateTime } = require('luxon') let cachedToken = null let tokenExpiry = null - -async function getToken() { - if (cachedToken && tokenExpiry && DateTime.now() < tokenExpiry) { - return cachedToken - } - - try { - const response = await axios.get('https://tv.dir.bg/init') - - // Check different possible locations for the token - let token = null - if (response.data && response.data.csrfToken) { - token = response.data.csrfToken - } - - if (token) { - cachedToken = token - tokenExpiry = DateTime.now().plus({ hours: 1 }) - return token - } else { - console.error('CSRF token not found in response structure:', Object.keys(response.data || {})) - return null - } - } catch (error) { - console.error('Error fetching token:', error.message) - return null - } -} - module.exports = { site: 'tv.dir.bg', days: 2, @@ -123,6 +94,34 @@ module.exports = { } } +async function getToken() { + if (cachedToken && tokenExpiry && DateTime.now() < tokenExpiry) { + return cachedToken + } + + try { + const response = await axios.get('https://tv.dir.bg/init', { headers: {'X-Requested-With': 'XMLHttpRequest'} }) + + // Check different possible locations for the token + let token = null + if (response.data && response.data.csrfToken) { + token = response.data.csrfToken + } + + if (token) { + cachedToken = token + tokenExpiry = DateTime.now().plus({ hours: 1 }) + return token + } else { + console.error('CSRF token not found in response structure:', Object.keys(response.data || {})) + return null + } + } catch (error) { + console.error('Error fetching token:', error.message) + return null + } +} + function parseStart($item, date) { const timeText = $item('.broadcast-time').text().trim() if (!timeText) return null From 427015e801ad913cf951c97208ddeb3a632b18e1 Mon Sep 17 00:00:00 2001 From: theofficialomega <30985701+BellezaEmporium@users.noreply.github.com> Date: Sun, 20 Jul 2025 19:15:09 +0200 Subject: [PATCH 10/32] fix lint --- sites/tv.dir.bg/tv.dir.bg.channels.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sites/tv.dir.bg/tv.dir.bg.channels.xml b/sites/tv.dir.bg/tv.dir.bg.channels.xml index 8488cbdd9..130fe1a45 100644 --- a/sites/tv.dir.bg/tv.dir.bg.channels.xml +++ b/sites/tv.dir.bg/tv.dir.bg.channels.xml @@ -89,8 +89,8 @@ STAR Crime (f.k.a FOX Crime) STAR Life (f.k.a. FOX Life) Super Toons - The History Channel 2 - The Voice TV + The History Channel 2 + The Voice TV TLC Travel Channel TV1 Bulgaria @@ -98,6 +98,6 @@ Viasat History Viasat Kino (TV1000) Viasat Nature - Viasat True Crime + Viasat True Crime Vivacom Arena (Виваком Арена) From 4b827195f16aa63acc93f0eef0997d0b61812365 Mon Sep 17 00:00:00 2001 From: theofficialomega <30985701+BellezaEmporium@users.noreply.github.com> Date: Sun, 20 Jul 2025 19:17:29 +0200 Subject: [PATCH 11/32] Bulgary -> Bulgaria --- sites/tvprofil.com/tvprofil.com.channels.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sites/tvprofil.com/tvprofil.com.channels.xml b/sites/tvprofil.com/tvprofil.com.channels.xml index 0f204a403..bd09d7a76 100644 --- a/sites/tvprofil.com/tvprofil.com.channels.xml +++ b/sites/tvprofil.com/tvprofil.com.channels.xml @@ -408,7 +408,7 @@ Viasat Nature CEE VOX ZDF - 24Kitchen BG + 24Kitchen BG AGRO TV BG Alfa BG AXN Black BG @@ -438,7 +438,7 @@ Евроком Фен Фолк ТВ ФЕН ТВ - FilmBox Stars BG + FilmBox Stars BG HBO BG Kino Nova Magic TV BG @@ -3270,7 +3270,7 @@ La7 Laudato TV Life TV HU - Living HD + Living HD Pink LOL Lov i ribolov M1 FILM From bb14bf5a1516ce5c15df91def6a161d79664adb5 Mon Sep 17 00:00:00 2001 From: theofficialomega <30985701+BellezaEmporium@users.noreply.github.com> Date: Sun, 20 Jul 2025 19:25:04 +0200 Subject: [PATCH 12/32] fixed wrong XML tv IDs. --- .../tv2go.t-2.net/tv2go.t-2.net.channels.xml | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/sites/tv2go.t-2.net/tv2go.t-2.net.channels.xml b/sites/tv2go.t-2.net/tv2go.t-2.net.channels.xml index 3fcab5e15..172c09fab 100644 --- a/sites/tv2go.t-2.net/tv2go.t-2.net.channels.xml +++ b/sites/tv2go.t-2.net/tv2go.t-2.net.channels.xml @@ -95,7 +95,7 @@ ATM Kranjska Gora B92 Baby TV - Balkan Erotic + Balkan Erotic Balkanika Music TV TV Balkan Trip BBC Earth @@ -132,7 +132,7 @@ Duna World Dusk Elta 2 - Elta TV + Elta TV Epic Drama ePosavje TV Erox @@ -145,7 +145,7 @@ Eurosport EWTN Europe Exodus - Extreme + Extreme Extreme Sports Fashionbox Fashion TV @@ -155,8 +155,8 @@ FEN TV Fightbox Filmbox Art House - Filmbox Extra - Filmbox Stars + Filmbox Extra + Filmbox Stars STAR STAR Crime STAR Life @@ -176,7 +176,7 @@ HGTV H2 Hot Pleasure - Hot XXL + Hot XXL HRT 1 HRT 2 Hustler TV @@ -195,7 +195,7 @@ M5 Mezzo Mezzo Live - Milf TV + Milf TV Minimax Mreža TV MTV 1 @@ -240,14 +240,14 @@ Planet Eva Planet TV 2 Planet TV - Play House + Play House POP TV Pro 7 Prva RAI 1 RAI 2 RAI 3 - RED xxx + RED xxx RT RTK Kosova RTL 2 HR @@ -266,7 +266,7 @@ SAT1 Sci Fi Servus TV - Sexation + Sexation SIP TV TV Sitel Sky News @@ -287,7 +287,7 @@ Trace Sport Stars Trace Urban Travel Channel - Travelxp + Travelxp 4K Travelxp Tring Action Tring Shqip @@ -324,7 +324,7 @@ Viasat Nature TV1000 Vitel - Vivid Red + Vivid Red Vivid TV Tring Vizion+ VOX From 2a658185d3d8bcf474d0fa22ab29bcff4a675700 Mon Sep 17 00:00:00 2001 From: theofficialomega <30985701+BellezaEmporium@users.noreply.github.com> Date: Sat, 26 Jul 2025 18:48:23 +0200 Subject: [PATCH 13/32] patch. --- scripts/commands/channels/validate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/commands/channels/validate.ts b/scripts/commands/channels/validate.ts index 43af23e5c..358e1168b 100644 --- a/scripts/commands/channels/validate.ts +++ b/scripts/commands/channels/validate.ts @@ -9,7 +9,7 @@ import { program } from 'commander' import chalk from 'chalk' import langs from 'langs' -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) type ValidationError = { type: 'duplicate' | 'wrong_channel_id' | 'wrong_feed_id' | 'wrong_lang' From d6884090dfd7a8121c1c325948c9fa95f5e63745 Mon Sep 17 00:00:00 2001 From: theofficialomega <30985701+BellezaEmporium@users.noreply.github.com> Date: Sun, 27 Jul 2025 16:14:59 +0200 Subject: [PATCH 14/32] updating dependencies & fixed tv.dir.bg --- package-lock.json | 2621 +++++++++++++++++----- package.json | 22 +- sites/tv.dir.bg/__data__/content.html | 1 - sites/tv.dir.bg/__data__/content.json | 4 + sites/tv.dir.bg/__data__/no_content.json | 4 + sites/tv.dir.bg/__data__/no_data.html | 1 - sites/tv.dir.bg/tv.dir.bg.config.js | 219 +- sites/tv.dir.bg/tv.dir.bg.test.js | 40 +- 8 files changed, 2268 insertions(+), 644 deletions(-) delete mode 100644 sites/tv.dir.bg/__data__/content.html create mode 100644 sites/tv.dir.bg/__data__/content.json create mode 100644 sites/tv.dir.bg/__data__/no_content.json delete mode 100644 sites/tv.dir.bg/__data__/no_data.html diff --git a/package-lock.json b/package-lock.json index 05218b456..857082785 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "@alex_neo/jest-expect-message": "^1.0.5", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "^9.31.0", + "@eslint/js": "^9.32.0", "@freearhey/chronos": "^0.0.1", "@freearhey/core": "^0.10.2", "@freearhey/search-js": "^0.1.2", @@ -18,32 +18,32 @@ "@octokit/core": "^7.0.3", "@octokit/plugin-paginate-rest": "^13.1.1", "@octokit/plugin-rest-endpoint-methods": "^16.0.0", - "@swc/core": "^1.13.0", + "@swc/core": "^1.13.2", "@swc/jest": "^0.2.39", "@types/cli-progress": "^3.11.6", "@types/fs-extra": "^11.0.4", "@types/inquirer": "^9.0.8", "@types/jest": "^30.0.0", "@types/langs": "^2.0.5", - "@types/node": "^24.0.15", + "@types/node": "^24.1.0", "@types/node-cleanup": "^2.1.5", "@types/numeral": "^2.0.5", - "@typescript-eslint/eslint-plugin": "^8.37.0", - "@typescript-eslint/parser": "^8.37.0", - "axios": "^1.10.0", + "@typescript-eslint/eslint-plugin": "^8.38.0", + "@typescript-eslint/parser": "^8.38.0", + "axios": "^1.11.0", "axios-cookiejar-support": "^6.0.4", "chalk": "^5.4.1", - "cheerio": "^1.1.0", + "cheerio": "^1.1.2", "cli-progress": "^3.12.0", "commander": "^14.0.0", "consola": "^3.4.2", - "cross-env": "^7.0.3", + "cross-env": "^10.0.0", "csv-parser": "^3.2.0", "cwait": "^1.1.2", "dayjs": "^1.11.13", "epg-grabber": "^0.41.0", "epg-parser": "^0.3.1", - "eslint": "^9.31.0", + "eslint": "^9.32.0", "eslint-config-prettier": "^10.1.8", "form-data": "^4.0.4", "fs-extra": "^11.3.0", @@ -51,8 +51,8 @@ "globals": "^16.3.0", "husky": "^9.1.7", "iconv-lite": "^0.6.3", - "inquirer": "^12.7.0", - "jest": "^30.0.4", + "inquirer": "^12.8.2", + "jest": "^30.0.5", "jest-offline": "^1.0.1", "langs": "^2.0.0", "libxml2-wasm": "^0.5.0", @@ -282,13 +282,13 @@ } }, "node_modules/@babel/helpers": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", - "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.2.tgz", + "integrity": "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==", "license": "MIT", "dependencies": { "@babel/template": "^7.27.2", - "@babel/types": "^7.27.6" + "@babel/types": "^7.28.2" }, "engines": { "node": ">=6.9.0" @@ -564,9 +564,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.1.tgz", - "integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -602,6 +602,64 @@ "kuler": "^2.0.0" } }, + "node_modules/@emnapi/core": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.5.tgz", + "integrity": "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.0.4", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/core/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, + "node_modules/@emnapi/runtime": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz", + "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.4.tgz", + "integrity": "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, + "node_modules/@epic-web/invariant": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz", + "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==", + "license": "MIT" + }, "node_modules/@esbuild/win32-x64": { "version": "0.25.8", "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", @@ -716,9 +774,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.31.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz", - "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==", + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.32.0.tgz", + "integrity": "sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -737,9 +795,9 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.3.tgz", - "integrity": "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz", + "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==", "license": "Apache-2.0", "dependencies": { "@eslint/core": "^0.15.1", @@ -855,14 +913,14 @@ } }, "node_modules/@inquirer/checkbox": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.1.9.tgz", - "integrity": "sha512-DBJBkzI5Wx4jFaYm221LHvAhpKYkhVS0k9plqHwaHhofGNxvYB7J3Bz8w+bFJ05zaMb0sZNHo4KdmENQFlNTuQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.2.0.tgz", + "integrity": "sha512-fdSw07FLJEU5vbpOPzXo5c6xmMGDzbZE2+niuDHX5N6mc6V0Ebso/q3xiHra4D73+PMsC8MJmcaZKuAAoaQsSA==", "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.14", - "@inquirer/figures": "^1.0.12", - "@inquirer/type": "^3.0.7", + "@inquirer/core": "^10.1.15", + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", "ansi-escapes": "^4.3.2", "yoctocolors-cjs": "^2.1.2" }, @@ -879,13 +937,13 @@ } }, "node_modules/@inquirer/confirm": { - "version": "5.1.13", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.13.tgz", - "integrity": "sha512-EkCtvp67ICIVVzjsquUiVSd+V5HRGOGQfsqA4E4vMWhYnB7InUL0pa0TIWt1i+OfP16Gkds8CdIu6yGZwOM1Yw==", + "version": "5.1.14", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.14.tgz", + "integrity": "sha512-5yR4IBfe0kXe59r1YCTG8WXkUbl7Z35HK87Sw+WUyGD8wNUx7JvY7laahzeytyE1oLn74bQnL7hstctQxisQ8Q==", "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.14", - "@inquirer/type": "^3.0.7" + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8" }, "engines": { "node": ">=18" @@ -900,13 +958,13 @@ } }, "node_modules/@inquirer/core": { - "version": "10.1.14", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.14.tgz", - "integrity": "sha512-Ma+ZpOJPewtIYl6HZHZckeX1STvDnHTCB2GVINNUlSEn2Am6LddWwfPkIGY0IUFVjUUrr/93XlBwTK6mfLjf0A==", + "version": "10.1.15", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.15.tgz", + "integrity": "sha512-8xrp836RZvKkpNbVvgWUlxjT4CraKk2q+I3Ksy+seI2zkcE+y6wNs1BVhgcv8VyImFecUhdQrYLdW32pAjwBdA==", "license": "MIT", "dependencies": { - "@inquirer/figures": "^1.0.12", - "@inquirer/type": "^3.0.7", + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", "ansi-escapes": "^4.3.2", "cli-width": "^4.1.0", "mute-stream": "^2.0.0", @@ -927,13 +985,13 @@ } }, "node_modules/@inquirer/editor": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.14.tgz", - "integrity": "sha512-yd2qtLl4QIIax9DTMZ1ZN2pFrrj+yL3kgIWxm34SS6uwCr0sIhsNyudUjAo5q3TqI03xx4SEBkUJqZuAInp9uA==", + "version": "4.2.15", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.15.tgz", + "integrity": "sha512-wst31XT8DnGOSS4nNJDIklGKnf+8shuauVrWzgKegWUe28zfCftcWZ2vktGdzJgcylWSS2SrDnYUb6alZcwnCQ==", "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.14", - "@inquirer/type": "^3.0.7", + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8", "external-editor": "^3.1.0" }, "engines": { @@ -949,13 +1007,13 @@ } }, "node_modules/@inquirer/expand": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.16.tgz", - "integrity": "sha512-oiDqafWzMtofeJyyGkb1CTPaxUkjIcSxePHHQCfif8t3HV9pHcw1Kgdw3/uGpDvaFfeTluwQtWiqzPVjAqS3zA==", + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.17.tgz", + "integrity": "sha512-PSqy9VmJx/VbE3CT453yOfNa+PykpKg/0SYP7odez1/NWBGuDXgPhp4AeGYYKjhLn5lUUavVS/JbeYMPdH50Mw==", "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.14", - "@inquirer/type": "^3.0.7", + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8", "yoctocolors-cjs": "^2.1.2" }, "engines": { @@ -971,22 +1029,22 @@ } }, "node_modules/@inquirer/figures": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.12.tgz", - "integrity": "sha512-MJttijd8rMFcKJC8NYmprWr6hD3r9Gd9qUC0XwPNwoEPWSMVJwA2MlXxF+nhZZNMY+HXsWa+o7KY2emWYIn0jQ==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.13.tgz", + "integrity": "sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==", "license": "MIT", "engines": { "node": ">=18" } }, "node_modules/@inquirer/input": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.2.0.tgz", - "integrity": "sha512-opqpHPB1NjAmDISi3uvZOTrjEEU5CWVu/HBkDby8t93+6UxYX0Z7Ps0Ltjm5sZiEbWenjubwUkivAEYQmy9xHw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.2.1.tgz", + "integrity": "sha512-tVC+O1rBl0lJpoUZv4xY+WGWY8V5b0zxU1XDsMsIHYregdh7bN5X5QnIONNBAl0K765FYlAfNHS2Bhn7SSOVow==", "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.14", - "@inquirer/type": "^3.0.7" + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8" }, "engines": { "node": ">=18" @@ -1001,13 +1059,13 @@ } }, "node_modules/@inquirer/number": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.16.tgz", - "integrity": "sha512-kMrXAaKGavBEoBYUCgualbwA9jWUx2TjMA46ek+pEKy38+LFpL9QHlTd8PO2kWPUgI/KB+qi02o4y2rwXbzr3Q==", + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.17.tgz", + "integrity": "sha512-GcvGHkyIgfZgVnnimURdOueMk0CztycfC8NZTiIY9arIAkeOgt6zG57G+7vC59Jns3UX27LMkPKnKWAOF5xEYg==", "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.14", - "@inquirer/type": "^3.0.7" + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8" }, "engines": { "node": ">=18" @@ -1022,13 +1080,13 @@ } }, "node_modules/@inquirer/password": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.16.tgz", - "integrity": "sha512-g8BVNBj5Zeb5/Y3cSN+hDUL7CsIFDIuVxb9EPty3lkxBaYpjL5BNRKSYOF9yOLe+JOcKFd+TSVeADQ4iSY7rbg==", + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.17.tgz", + "integrity": "sha512-DJolTnNeZ00E1+1TW+8614F7rOJJCM4y4BAGQ3Gq6kQIG+OJ4zr3GLjIjVVJCbKsk2jmkmv6v2kQuN/vriHdZA==", "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.14", - "@inquirer/type": "^3.0.7", + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8", "ansi-escapes": "^4.3.2" }, "engines": { @@ -1044,21 +1102,21 @@ } }, "node_modules/@inquirer/prompts": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.6.0.tgz", - "integrity": "sha512-jAhL7tyMxB3Gfwn4HIJ0yuJ5pvcB5maYUcouGcgd/ub79f9MqZ+aVnBtuFf+VC2GTkCBF+R+eo7Vi63w5VZlzw==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.7.1.tgz", + "integrity": "sha512-XDxPrEWeWUBy8scAXzXuFY45r/q49R0g72bUzgQXZ1DY/xEFX+ESDMkTQolcb5jRBzaNJX2W8XQl6krMNDTjaA==", "license": "MIT", "dependencies": { - "@inquirer/checkbox": "^4.1.9", - "@inquirer/confirm": "^5.1.13", - "@inquirer/editor": "^4.2.14", - "@inquirer/expand": "^4.0.16", - "@inquirer/input": "^4.2.0", - "@inquirer/number": "^3.0.16", - "@inquirer/password": "^4.0.16", - "@inquirer/rawlist": "^4.1.4", - "@inquirer/search": "^3.0.16", - "@inquirer/select": "^4.2.4" + "@inquirer/checkbox": "^4.2.0", + "@inquirer/confirm": "^5.1.14", + "@inquirer/editor": "^4.2.15", + "@inquirer/expand": "^4.0.17", + "@inquirer/input": "^4.2.1", + "@inquirer/number": "^3.0.17", + "@inquirer/password": "^4.0.17", + "@inquirer/rawlist": "^4.1.5", + "@inquirer/search": "^3.0.17", + "@inquirer/select": "^4.3.1" }, "engines": { "node": ">=18" @@ -1073,13 +1131,13 @@ } }, "node_modules/@inquirer/rawlist": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.4.tgz", - "integrity": "sha512-5GGvxVpXXMmfZNtvWw4IsHpR7RzqAR624xtkPd1NxxlV5M+pShMqzL4oRddRkg8rVEOK9fKdJp1jjVML2Lr7TQ==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.5.tgz", + "integrity": "sha512-R5qMyGJqtDdi4Ht521iAkNqyB6p2UPuZUbMifakg1sWtu24gc2Z8CJuw8rP081OckNDMgtDCuLe42Q2Kr3BolA==", "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.14", - "@inquirer/type": "^3.0.7", + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8", "yoctocolors-cjs": "^2.1.2" }, "engines": { @@ -1095,14 +1153,14 @@ } }, "node_modules/@inquirer/search": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.0.16.tgz", - "integrity": "sha512-POCmXo+j97kTGU6aeRjsPyuCpQQfKcMXdeTMw708ZMtWrj5aykZvlUxH4Qgz3+Y1L/cAVZsSpA+UgZCu2GMOMg==", + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.0.17.tgz", + "integrity": "sha512-CuBU4BAGFqRYors4TNCYzy9X3DpKtgIW4Boi0WNkm4Ei1hvY9acxKdBdyqzqBCEe4YxSdaQQsasJlFlUJNgojw==", "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.14", - "@inquirer/figures": "^1.0.12", - "@inquirer/type": "^3.0.7", + "@inquirer/core": "^10.1.15", + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", "yoctocolors-cjs": "^2.1.2" }, "engines": { @@ -1118,14 +1176,14 @@ } }, "node_modules/@inquirer/select": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.2.4.tgz", - "integrity": "sha512-unTppUcTjmnbl/q+h8XeQDhAqIOmwWYWNyiiP2e3orXrg6tOaa5DHXja9PChCSbChOsktyKgOieRZFnajzxoBg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.3.1.tgz", + "integrity": "sha512-Gfl/5sqOF5vS/LIrSndFgOh7jgoe0UXEizDqahFRkq5aJBLegZ6WjuMh/hVEJwlFQjyLq1z9fRtvUMkb7jM1LA==", "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.14", - "@inquirer/figures": "^1.0.12", - "@inquirer/type": "^3.0.7", + "@inquirer/core": "^10.1.15", + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", "ansi-escapes": "^4.3.2", "yoctocolors-cjs": "^2.1.2" }, @@ -1142,9 +1200,9 @@ } }, "node_modules/@inquirer/type": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.7.tgz", - "integrity": "sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.8.tgz", + "integrity": "sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==", "license": "MIT", "engines": { "node": ">=18" @@ -1363,22 +1421,52 @@ } }, "node_modules/@jest/console": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.0.4.tgz", - "integrity": "sha512-tMLCDvBJBwPqMm4OAiuKm2uF5y5Qe26KgcMn+nrDSWpEW+eeFmqA0iO4zJfL16GP7gE3bUUQ3hIuUJ22AqVRnw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.0.5.tgz", + "integrity": "sha512-xY6b0XiL0Nav3ReresUarwl2oIz1gTnxGbGpho9/rbUWsLH0f1OD/VT84xs8c7VmH7MChnLb0pag6PhZhAdDiA==", "license": "MIT", "dependencies": { - "@jest/types": "30.0.1", + "@jest/types": "30.0.5", "@types/node": "*", "chalk": "^4.1.2", - "jest-message-util": "30.0.2", - "jest-util": "30.0.2", + "jest-message-util": "30.0.5", + "jest-util": "30.0.5", "slash": "^3.0.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/@jest/console/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/console/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/console/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -1429,38 +1517,38 @@ "license": "MIT" }, "node_modules/@jest/core": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.0.4.tgz", - "integrity": "sha512-MWScSO9GuU5/HoWjpXAOBs6F/iobvK1XlioelgOM9St7S0Z5WTI9kjCQLPeo4eQRRYusyLW25/J7J5lbFkrYXw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.0.5.tgz", + "integrity": "sha512-fKD0OulvRsXF1hmaFgHhVJzczWzA1RXMMo9LTPuFXo9q/alDbME3JIyWYqovWsUBWSoBcsHaGPSLF9rz4l9Qeg==", "license": "MIT", "dependencies": { - "@jest/console": "30.0.4", + "@jest/console": "30.0.5", "@jest/pattern": "30.0.1", - "@jest/reporters": "30.0.4", - "@jest/test-result": "30.0.4", - "@jest/transform": "30.0.4", - "@jest/types": "30.0.1", + "@jest/reporters": "30.0.5", + "@jest/test-result": "30.0.5", + "@jest/transform": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "ci-info": "^4.2.0", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", - "jest-changed-files": "30.0.2", - "jest-config": "30.0.4", - "jest-haste-map": "30.0.2", - "jest-message-util": "30.0.2", + "jest-changed-files": "30.0.5", + "jest-config": "30.0.5", + "jest-haste-map": "30.0.5", + "jest-message-util": "30.0.5", "jest-regex-util": "30.0.1", - "jest-resolve": "30.0.2", - "jest-resolve-dependencies": "30.0.4", - "jest-runner": "30.0.4", - "jest-runtime": "30.0.4", - "jest-snapshot": "30.0.4", - "jest-util": "30.0.2", - "jest-validate": "30.0.2", - "jest-watcher": "30.0.4", + "jest-resolve": "30.0.5", + "jest-resolve-dependencies": "30.0.5", + "jest-runner": "30.0.5", + "jest-runtime": "30.0.5", + "jest-snapshot": "30.0.5", + "jest-util": "30.0.5", + "jest-validate": "30.0.5", + "jest-watcher": "30.0.5", "micromatch": "^4.0.8", - "pretty-format": "30.0.2", + "pretty-format": "30.0.5", "slash": "^3.0.0" }, "engines": { @@ -1475,6 +1563,36 @@ } } }, + "node_modules/@jest/core/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/core/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/core/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -1546,37 +1664,116 @@ } }, "node_modules/@jest/environment": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.0.4.tgz", - "integrity": "sha512-5NT+sr7ZOb8wW7C4r7wOKnRQ8zmRWQT2gW4j73IXAKp5/PX1Z8MCStBLQDYfIG3n1Sw0NRfYGdp0iIPVooBAFQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.0.5.tgz", + "integrity": "sha512-aRX7WoaWx1oaOkDQvCWImVQ8XNtdv5sEWgk4gxR6NXb7WBUnL5sRak4WRzIQRZ1VTWPvV4VI4mgGjNL9TeKMYA==", "license": "MIT", "dependencies": { - "@jest/fake-timers": "30.0.4", - "@jest/types": "30.0.1", + "@jest/fake-timers": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", - "jest-mock": "30.0.2" + "jest-mock": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/expect": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.0.4.tgz", - "integrity": "sha512-Z/DL7t67LBHSX4UzDyeYKqOxE/n7lbrrgEwWM3dGiH5Dgn35nk+YtgzKudmfIrBI8DRRrKYY5BCo3317HZV1Fw==", + "node_modules/@jest/environment/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", "license": "MIT", "dependencies": { - "expect": "30.0.4", - "jest-snapshot": "30.0.4" + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/environment/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/environment/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/environment/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/@jest/expect": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.0.5.tgz", + "integrity": "sha512-6udac8KKrtTtC+AXZ2iUN/R7dp7Ydry+Fo6FPFnDG54wjVMnb6vW/XNlf7Xj8UDjAE3aAVAsR4KFyKk3TCXmTA==", + "license": "MIT", + "dependencies": { + "expect": "30.0.5", + "jest-snapshot": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.0.4.tgz", - "integrity": "sha512-EgXecHDNfANeqOkcak0DxsoVI4qkDUsR7n/Lr2vtmTBjwLPBnnPOF71S11Q8IObWzxm2QgQoY6f9hzrRD3gHRA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.0.5.tgz", + "integrity": "sha512-F3lmTT7CXWYywoVUGTCmom0vXq3HTTkaZyTAzIy+bXSBizB7o5qzlC9VCtq0arOa8GqmNsbg/cE9C6HLn7Szew==", "license": "MIT", "dependencies": { "@jest/get-type": "30.0.1" @@ -1586,22 +1783,101 @@ } }, "node_modules/@jest/fake-timers": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.0.4.tgz", - "integrity": "sha512-qZ7nxOcL5+gwBO6LErvwVy5k06VsX/deqo2XnVUSTV0TNC9lrg8FC3dARbi+5lmrr5VyX5drragK+xLcOjvjYw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.0.5.tgz", + "integrity": "sha512-ZO5DHfNV+kgEAeP3gK3XlpJLL4U3Sz6ebl/n68Uwt64qFFs5bv4bfEEjyRGK5uM0C90ewooNgFuKMdkbEoMEXw==", "license": "MIT", "dependencies": { - "@jest/types": "30.0.1", + "@jest/types": "30.0.5", "@sinonjs/fake-timers": "^13.0.0", "@types/node": "*", - "jest-message-util": "30.0.2", - "jest-mock": "30.0.2", - "jest-util": "30.0.2" + "jest-message-util": "30.0.5", + "jest-mock": "30.0.5", + "jest-util": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/@jest/fake-timers/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/fake-timers/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/fake-timers/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/fake-timers/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/fake-timers/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/fake-timers/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/@jest/get-type": { "version": "30.0.1", "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.0.1.tgz", @@ -1612,20 +1888,99 @@ } }, "node_modules/@jest/globals": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.0.4.tgz", - "integrity": "sha512-avyZuxEHF2EUhFF6NEWVdxkRRV6iXXcIES66DLhuLlU7lXhtFG/ySq/a8SRZmEJSsLkNAFX6z6mm8KWyXe9OEA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.0.5.tgz", + "integrity": "sha512-7oEJT19WW4oe6HR7oLRvHxwlJk2gev0U9px3ufs8sX9PoD1Eza68KF0/tlN7X0dq/WVsBScXQGgCldA1V9Y/jA==", "license": "MIT", "dependencies": { - "@jest/environment": "30.0.4", - "@jest/expect": "30.0.4", - "@jest/types": "30.0.1", - "jest-mock": "30.0.2" + "@jest/environment": "30.0.5", + "@jest/expect": "30.0.5", + "@jest/types": "30.0.5", + "jest-mock": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/@jest/globals/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/globals/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/globals/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/globals/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/globals/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/globals/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/@jest/pattern": { "version": "30.0.1", "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", @@ -1640,16 +1995,16 @@ } }, "node_modules/@jest/reporters": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.0.4.tgz", - "integrity": "sha512-6ycNmP0JSJEEys1FbIzHtjl9BP0tOZ/KN6iMeAKrdvGmUsa1qfRdlQRUDKJ4P84hJ3xHw1yTqJt4fvPNHhyE+g==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.0.5.tgz", + "integrity": "sha512-mafft7VBX4jzED1FwGC1o/9QUM2xebzavImZMeqnsklgcyxBto8mV4HzNSzUrryJ+8R9MFOM3HgYuDradWR+4g==", "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.0.4", - "@jest/test-result": "30.0.4", - "@jest/transform": "30.0.4", - "@jest/types": "30.0.1", + "@jest/console": "30.0.5", + "@jest/test-result": "30.0.5", + "@jest/transform": "30.0.5", + "@jest/types": "30.0.5", "@jridgewell/trace-mapping": "^0.3.25", "@types/node": "*", "chalk": "^4.1.2", @@ -1662,9 +2017,9 @@ "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^5.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "30.0.2", - "jest-util": "30.0.2", - "jest-worker": "30.0.2", + "jest-message-util": "30.0.5", + "jest-util": "30.0.5", + "jest-worker": "30.0.5", "slash": "^3.0.0", "string-length": "^4.0.2", "v8-to-istanbul": "^9.0.1" @@ -1681,6 +2036,36 @@ } } }, + "node_modules/@jest/reporters/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/reporters/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -1824,12 +2209,12 @@ } }, "node_modules/@jest/snapshot-utils": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.0.4.tgz", - "integrity": "sha512-BEpX8M/Y5lG7MI3fmiO+xCnacOrVsnbqVrcDZIT8aSGkKV1w2WwvRQxSWw5SIS8ozg7+h8tSj5EO1Riqqxcdag==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.0.5.tgz", + "integrity": "sha512-XcCQ5qWHLvi29UUrowgDFvV4t7ETxX91CbDczMnoqXPOIcZOxyNdSjm6kV5XMc8+HkxfRegU/MUmnTbJRzGrUQ==", "license": "MIT", "dependencies": { - "@jest/types": "30.0.1", + "@jest/types": "30.0.5", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "natural-compare": "^1.4.0" @@ -1838,6 +2223,36 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/@jest/snapshot-utils/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/snapshot-utils/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/snapshot-utils/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -1902,13 +2317,13 @@ } }, "node_modules/@jest/test-result": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.0.4.tgz", - "integrity": "sha512-Mfpv8kjyKTHqsuu9YugB6z1gcdB3TSSOaKlehtVaiNlClMkEHY+5ZqCY2CrEE3ntpBMlstX/ShDAf84HKWsyIw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.0.5.tgz", + "integrity": "sha512-wPyztnK0gbDMQAJZ43tdMro+qblDHH1Ru/ylzUo21TBKqt88ZqnKKK2m30LKmLLoKtR2lxdpCC/P3g1vfKcawQ==", "license": "MIT", "dependencies": { - "@jest/console": "30.0.4", - "@jest/types": "30.0.1", + "@jest/console": "30.0.5", + "@jest/types": "30.0.5", "@types/istanbul-lib-coverage": "^2.0.6", "collect-v8-coverage": "^1.0.2" }, @@ -1916,15 +2331,94 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/test-sequencer": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.0.4.tgz", - "integrity": "sha512-bj6ePmqi4uxAE8EHE0Slmk5uBYd9Vd/PcVt06CsBxzH4bbA8nGsI1YbXl/NH+eii4XRtyrRx+Cikub0x8H4vDg==", + "node_modules/@jest/test-result/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", "license": "MIT", "dependencies": { - "@jest/test-result": "30.0.4", + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-result/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-result/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/test-result/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/test-result/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/test-result/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/@jest/test-sequencer": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.0.5.tgz", + "integrity": "sha512-Aea/G1egWoIIozmDD7PBXUOxkekXl7ueGzrsGGi1SbeKgQqCYCIf+wfbflEbf2LiPxL8j2JZGLyrzZagjvW4YQ==", + "license": "MIT", + "dependencies": { + "@jest/test-result": "30.0.5", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.0.2", + "jest-haste-map": "30.0.5", "slash": "^3.0.0" }, "engines": { @@ -1932,22 +2426,22 @@ } }, "node_modules/@jest/transform": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.0.4.tgz", - "integrity": "sha512-atvy4hRph/UxdCIBp+UB2jhEA/jJiUeGZ7QPgBi9jUUKNgi3WEoMXGNG7zbbELG2+88PMabUNCDchmqgJy3ELg==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.0.5.tgz", + "integrity": "sha512-Vk8amLQCmuZyy6GbBht1Jfo9RSdBtg7Lks+B0PecnjI8J+PCLQPGh7uI8Q/2wwpW2gLdiAfiHNsmekKlywULqg==", "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", - "@jest/types": "30.0.1", + "@jest/types": "30.0.5", "@jridgewell/trace-mapping": "^0.3.25", "babel-plugin-istanbul": "^7.0.0", "chalk": "^4.1.2", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.0.2", + "jest-haste-map": "30.0.5", "jest-regex-util": "30.0.1", - "jest-util": "30.0.2", + "jest-util": "30.0.5", "micromatch": "^4.0.8", "pirates": "^4.0.7", "slash": "^3.0.0", @@ -1957,6 +2451,36 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/@jest/transform/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/transform/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/transform/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -2108,6 +2632,18 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2279,19 +2815,6 @@ "@octokit/openapi-types": "^25.1.0" } }, - "node_modules/@oxlint/win32-x64": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@oxlint/win32-x64/-/win32-x64-1.7.0.tgz", - "integrity": "sha512-0EPWBWOiD3wZHgeWDlTUaiFzhzIonXykxYUC+NRerPQFkO/G+bd9uLMJddHDKqfP/7g8s3E5V6KvBvvFpb7U6g==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -2624,9 +3147,9 @@ } }, "node_modules/@swc/core": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.1.tgz", - "integrity": "sha512-jEKKErLC6uwSqA+p6bmZR08usZM5Fpc+HdEu5CAzvye0q43yf1si1kjhHEa9XMkz0A2SAaal3eKCg/YYmtOsCA==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.2.tgz", + "integrity": "sha512-YWqn+0IKXDhqVLKoac4v2tV6hJqB/wOh8/Br8zjqeqBkKa77Qb0Kw2i7LOFzjFNZbZaPH6AlMGlBwNrxaauaAg==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -2641,16 +3164,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.13.1", - "@swc/core-darwin-x64": "1.13.1", - "@swc/core-linux-arm-gnueabihf": "1.13.1", - "@swc/core-linux-arm64-gnu": "1.13.1", - "@swc/core-linux-arm64-musl": "1.13.1", - "@swc/core-linux-x64-gnu": "1.13.1", - "@swc/core-linux-x64-musl": "1.13.1", - "@swc/core-win32-arm64-msvc": "1.13.1", - "@swc/core-win32-ia32-msvc": "1.13.1", - "@swc/core-win32-x64-msvc": "1.13.1" + "@swc/core-darwin-arm64": "1.13.2", + "@swc/core-darwin-x64": "1.13.2", + "@swc/core-linux-arm-gnueabihf": "1.13.2", + "@swc/core-linux-arm64-gnu": "1.13.2", + "@swc/core-linux-arm64-musl": "1.13.2", + "@swc/core-linux-x64-gnu": "1.13.2", + "@swc/core-linux-x64-musl": "1.13.2", + "@swc/core-win32-arm64-msvc": "1.13.2", + "@swc/core-win32-ia32-msvc": "1.13.2", + "@swc/core-win32-x64-msvc": "1.13.2" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" @@ -2661,10 +3184,154 @@ } } }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.13.2.tgz", + "integrity": "sha512-44p7ivuLSGFJ15Vly4ivLJjg3ARo4879LtEBAabcHhSZygpmkP8eyjyWxrH3OxkY1eRZSIJe8yRZPFw4kPXFPw==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.13.2.tgz", + "integrity": "sha512-Lb9EZi7X2XDAVmuUlBm2UvVAgSCbD3qKqDCxSI4jEOddzVOpNCnyZ/xEampdngUIyDDhhJLYU9duC+Mcsv5Y+A==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.13.2.tgz", + "integrity": "sha512-9TDe/92ee1x57x+0OqL1huG4BeljVx0nWW4QOOxp8CCK67Rpc/HHl2wciJ0Kl9Dxf2NvpNtkPvqj9+BUmM9WVA==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.13.2.tgz", + "integrity": "sha512-KJUSl56DBk7AWMAIEcU83zl5mg3vlQYhLELhjwRFkGFMvghQvdqQ3zFOYa4TexKA7noBZa3C8fb24rI5sw9Exg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.13.2.tgz", + "integrity": "sha512-teU27iG1oyWpNh9CzcGQ48ClDRt/RCem7mYO7ehd2FY102UeTws2+OzLESS1TS1tEZipq/5xwx3FzbVgiolCiQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.13.2.tgz", + "integrity": "sha512-dRPsyPyqpLD0HMRCRpYALIh4kdOir8pPg4AhNQZLehKowigRd30RcLXGNVZcc31Ua8CiPI4QSgjOIxK+EQe4LQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.13.2.tgz", + "integrity": "sha512-CCxETW+KkYEQDqz1SYC15YIWYheqFC+PJVOW76Maa/8yu8Biw+HTAcblKf2isrlUtK8RvrQN94v3UXkC2NzCEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.13.2.tgz", + "integrity": "sha512-Wv/QTA6PjyRLlmKcN6AmSI4jwSMRl0VTLGs57PHTqYRwwfwd7y4s2fIPJVBNbAlXd795dOEP6d/bGSQSyhOX3A==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.13.2.tgz", + "integrity": "sha512-PuCdtNynEkUNbUXX/wsyUC+t4mamIU5y00lT5vJcAvco3/r16Iaxl5UCzhXYaWZSNVZMzPp9qN8NlSL8M5pPxw==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.13.1.tgz", - "integrity": "sha512-KubYjzqs/nz3H69ncX/XHKsC8c1xqc7UvonQAj26BhbL22HBsqdAaVutZ+Obho6RMpd3F5qQ95ldavUTWskRrw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.13.2.tgz", + "integrity": "sha512-qlmMkFZJus8cYuBURx1a3YAG2G7IW44i+FEYV5/32ylKkzGNAr9tDJSA53XNnNXkAB5EXSPsOz7bn5C3JlEtdQ==", "cpu": [ "x64" ], @@ -2715,6 +3382,23 @@ "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", "license": "MIT" }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz", + "integrity": "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tybys/wasm-util/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -2847,9 +3531,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.0.15", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.15.tgz", - "integrity": "sha512-oaeTSbCef7U/z7rDeJA138xpG3NuKc64/rZ2qmUFkFJmnMsAPaluIifqyWd8hSSMxyP9oie3dLAqYPblag9KgA==", + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz", + "integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==", "license": "MIT", "dependencies": { "undici-types": "~7.8.0" @@ -2904,16 +3588,16 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.37.0.tgz", - "integrity": "sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.38.0.tgz", + "integrity": "sha512-CPoznzpuAnIOl4nhj4tRr4gIPj5AfKgkiJmGQDaq+fQnRJTYlcBjbX3wbciGmpoPf8DREufuPRe1tNMZnGdanA==", "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.37.0", - "@typescript-eslint/type-utils": "8.37.0", - "@typescript-eslint/utils": "8.37.0", - "@typescript-eslint/visitor-keys": "8.37.0", + "@typescript-eslint/scope-manager": "8.38.0", + "@typescript-eslint/type-utils": "8.38.0", + "@typescript-eslint/utils": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -2927,7 +3611,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.37.0", + "@typescript-eslint/parser": "^8.38.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } @@ -2942,15 +3626,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.37.0.tgz", - "integrity": "sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.38.0.tgz", + "integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==", "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.37.0", - "@typescript-eslint/types": "8.37.0", - "@typescript-eslint/typescript-estree": "8.37.0", - "@typescript-eslint/visitor-keys": "8.37.0", + "@typescript-eslint/scope-manager": "8.38.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/typescript-estree": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0", "debug": "^4.3.4" }, "engines": { @@ -2966,13 +3650,13 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.37.0.tgz", - "integrity": "sha512-BIUXYsbkl5A1aJDdYJCBAo8rCEbAvdquQ8AnLb6z5Lp1u3x5PNgSSx9A/zqYc++Xnr/0DVpls8iQ2cJs/izTXA==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.38.0.tgz", + "integrity": "sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==", "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.37.0", - "@typescript-eslint/types": "^8.37.0", + "@typescript-eslint/tsconfig-utils": "^8.38.0", + "@typescript-eslint/types": "^8.38.0", "debug": "^4.3.4" }, "engines": { @@ -2987,13 +3671,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.37.0.tgz", - "integrity": "sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.38.0.tgz", + "integrity": "sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.37.0", - "@typescript-eslint/visitor-keys": "8.37.0" + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3004,9 +3688,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.37.0.tgz", - "integrity": "sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz", + "integrity": "sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3020,14 +3704,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.37.0.tgz", - "integrity": "sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.38.0.tgz", + "integrity": "sha512-c7jAvGEZVf0ao2z+nnz8BUaHZD09Agbh+DY7qvBQqLiz8uJzRgVPj5YvOh8I8uEiH8oIUGIfHzMwUcGVco/SJg==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.37.0", - "@typescript-eslint/typescript-estree": "8.37.0", - "@typescript-eslint/utils": "8.37.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/typescript-estree": "8.38.0", + "@typescript-eslint/utils": "8.38.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -3044,9 +3728,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.37.0.tgz", - "integrity": "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.38.0.tgz", + "integrity": "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3057,15 +3741,15 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.37.0.tgz", - "integrity": "sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz", + "integrity": "sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==", "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.37.0", - "@typescript-eslint/tsconfig-utils": "8.37.0", - "@typescript-eslint/types": "8.37.0", - "@typescript-eslint/visitor-keys": "8.37.0", + "@typescript-eslint/project-service": "8.38.0", + "@typescript-eslint/tsconfig-utils": "8.38.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -3109,15 +3793,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.37.0.tgz", - "integrity": "sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.38.0.tgz", + "integrity": "sha512-hHcMA86Hgt+ijJlrD8fX0j1j8w4C92zue/8LOPAFioIno+W0+L7KqE8QZKCcPGc/92Vs9x36w/4MPTJhqXdyvg==", "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.37.0", - "@typescript-eslint/types": "8.37.0", - "@typescript-eslint/typescript-estree": "8.37.0" + "@typescript-eslint/scope-manager": "8.38.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/typescript-estree": "8.38.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3132,12 +3816,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.37.0.tgz", - "integrity": "sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.38.0.tgz", + "integrity": "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/types": "8.38.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -3166,6 +3850,243 @@ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "license": "ISC" }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@unrs/resolver-binding-win32-x64-msvc": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", @@ -3416,13 +4337,13 @@ } }, "node_modules/axios": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", - "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", + "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, @@ -3466,12 +4387,12 @@ } }, "node_modules/babel-jest": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.0.4.tgz", - "integrity": "sha512-UjG2j7sAOqsp2Xua1mS/e+ekddkSu3wpf4nZUSvXNHuVWdaOUXQ77+uyjJLDE9i0atm5x4kds8K9yb5lRsRtcA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.0.5.tgz", + "integrity": "sha512-mRijnKimhGDMsizTvBTWotwNpzrkHr+VvZUQBof2AufXKB8NXrL1W69TG20EvOz7aevx6FTJIaBuBkYxS8zolg==", "license": "MIT", "dependencies": { - "@jest/transform": "30.0.4", + "@jest/transform": "30.0.5", "@types/babel__core": "^7.20.5", "babel-plugin-istanbul": "^7.0.0", "babel-preset-jest": "30.0.1", @@ -4035,25 +4956,25 @@ "license": "MIT/X11" }, "node_modules/cheerio": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.0.tgz", - "integrity": "sha512-+0hMx9eYhJvWbgpKV9hN7jg0JcwydpopZE4hgi+KvQtByZXPp04NiCWU0LzcAbP63abZckIHkTQaXVF52mX3xQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.2.tgz", + "integrity": "sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==", "license": "MIT", "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", - "encoding-sniffer": "^0.2.0", + "encoding-sniffer": "^0.2.1", "htmlparser2": "^10.0.0", "parse5": "^7.3.0", "parse5-htmlparser2-tree-adapter": "^7.1.0", "parse5-parser-stream": "^7.1.2", - "undici": "^7.10.0", + "undici": "^7.12.0", "whatwg-mimetype": "^4.0.0" }, "engines": { - "node": ">=18.17" + "node": ">=20.18.1" }, "funding": { "url": "https://github.com/cheeriojs/cheerio?sponsor=1" @@ -4495,21 +5416,20 @@ "license": "MIT" }, "node_modules/cross-env": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", - "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.0.0.tgz", + "integrity": "sha512-aU8qlEK/nHYtVuN4p7UQgAwVljzMg8hB4YK5ThRqD2l/ziSnryncPNn7bMLt5cFYsKVKBh8HqLqyCoTupEUu7Q==", "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.1" + "@epic-web/invariant": "^1.0.0", + "cross-spawn": "^7.0.6" }, "bin": { - "cross-env": "src/bin/cross-env.js", - "cross-env-shell": "src/bin/cross-env-shell.js" + "cross-env": "dist/bin/cross-env.js", + "cross-env-shell": "dist/bin/cross-env-shell.js" }, "engines": { - "node": ">=10.14", - "npm": ">=6", - "yarn": ">=1" + "node": ">=20" } }, "node_modules/cross-spawn": { @@ -4785,9 +5705,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.187", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.187.tgz", - "integrity": "sha512-cl5Jc9I0KGUoOoSbxvTywTa40uspGJt/BDBoDLoxJRSBpWh4FFXBsjNRHfQrONsV/OoEjDfHUmZQa2d6Ze4YgA==", + "version": "1.5.191", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.191.tgz", + "integrity": "sha512-xcwe9ELcuxYLUFqZZxL19Z6HVKcvNkIwhbHUz7L3us6u12yR+7uY89dSl570f/IqNthx8dAw3tojG7i4Ni4tDA==", "license": "ISC" }, "node_modules/emittery": { @@ -5028,9 +5948,9 @@ } }, "node_modules/eslint": { - "version": "9.31.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz", - "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==", + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.32.0.tgz", + "integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==", "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", @@ -5039,8 +5959,8 @@ "@eslint/config-helpers": "^0.3.0", "@eslint/core": "^0.15.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.31.0", - "@eslint/plugin-kit": "^0.3.1", + "@eslint/js": "9.32.0", + "@eslint/plugin-kit": "^0.3.4", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -5320,17 +6240,17 @@ } }, "node_modules/expect": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.0.4.tgz", - "integrity": "sha512-dDLGjnP2cKbEppxVICxI/Uf4YemmGMPNy0QytCbfafbpYk9AFQsxb8Uyrxii0RPK7FWgLGlSem+07WirwS3cFQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.0.5.tgz", + "integrity": "sha512-P0te2pt+hHI5qLJkIR+iMvS+lYUZml8rKKsohVHAGY+uClp9XVbdyYNJOIjSRpHVp8s8YqxJCiHUkSYZGr8rtQ==", "license": "MIT", "dependencies": { - "@jest/expect-utils": "30.0.4", + "@jest/expect-utils": "30.0.5", "@jest/get-type": "30.0.1", - "jest-matcher-utils": "30.0.4", - "jest-message-util": "30.0.2", - "jest-mock": "30.0.2", - "jest-util": "30.0.2" + "jest-matcher-utils": "30.0.5", + "jest-message-util": "30.0.5", + "jest-mock": "30.0.5", + "jest-util": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -5648,6 +6568,20 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -6123,17 +7057,17 @@ "license": "ISC" }, "node_modules/inquirer": { - "version": "12.7.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-12.7.0.tgz", - "integrity": "sha512-KKFRc++IONSyE2UYw9CJ1V0IWx5yQKomwB+pp3cWomWs+v2+ZsG11G2OVfAjFS6WWCppKw+RfKmpqGfSzD5QBQ==", + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-12.8.2.tgz", + "integrity": "sha512-oBDL9f4+cDambZVJdfJu2M5JQfvaug9lbo6fKDlFV40i8t3FGA1Db67ov5Hp5DInG4zmXhHWTSnlXBntnJ7GMA==", "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.14", - "@inquirer/prompts": "^7.6.0", - "@inquirer/type": "^3.0.7", + "@inquirer/core": "^10.1.15", + "@inquirer/prompts": "^7.7.1", + "@inquirer/type": "^3.0.8", "ansi-escapes": "^4.3.2", "mute-stream": "^2.0.0", - "run-async": "^4.0.4", + "run-async": "^4.0.5", "rxjs": "^7.8.2" }, "engines": { @@ -6466,15 +7400,15 @@ } }, "node_modules/jest": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.0.4.tgz", - "integrity": "sha512-9QE0RS4WwTj/TtTC4h/eFVmFAhGNVerSB9XpJh8sqaXlP73ILcPcZ7JWjjEtJJe2m8QyBLKKfPQuK+3F+Xij/g==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.0.5.tgz", + "integrity": "sha512-y2mfcJywuTUkvLm2Lp1/pFX8kTgMO5yyQGq/Sk/n2mN7XWYp4JsCZ/QXW34M8YScgk8bPZlREH04f6blPnoHnQ==", "license": "MIT", "dependencies": { - "@jest/core": "30.0.4", - "@jest/types": "30.0.1", + "@jest/core": "30.0.5", + "@jest/types": "30.0.5", "import-local": "^3.2.0", - "jest-cli": "30.0.4" + "jest-cli": "30.0.5" }, "bin": { "jest": "bin/jest.js" @@ -6492,13 +7426,13 @@ } }, "node_modules/jest-changed-files": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.0.2.tgz", - "integrity": "sha512-Ius/iRST9FKfJI+I+kpiDh8JuUlAISnRszF9ixZDIqJF17FckH5sOzKC8a0wd0+D+8em5ADRHA5V5MnfeDk2WA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.0.5.tgz", + "integrity": "sha512-bGl2Ntdx0eAwXuGpdLdVYVr5YQHnSZlQ0y9HVDu565lCUAe9sj6JOtBbMmBBikGIegne9piDDIOeiLVoqTkz4A==", "license": "MIT", "dependencies": { "execa": "^5.1.1", - "jest-util": "30.0.2", + "jest-util": "30.0.5", "p-limit": "^3.1.0" }, "engines": { @@ -6506,28 +7440,28 @@ } }, "node_modules/jest-circus": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.0.4.tgz", - "integrity": "sha512-o6UNVfbXbmzjYgmVPtSQrr5xFZCtkDZGdTlptYvGFSN80RuOOlTe73djvMrs+QAuSERZWcHBNIOMH+OEqvjWuw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.0.5.tgz", + "integrity": "sha512-h/sjXEs4GS+NFFfqBDYT7y5Msfxh04EwWLhQi0F8kuWpe+J/7tICSlswU8qvBqumR3kFgHbfu7vU6qruWWBPug==", "license": "MIT", "dependencies": { - "@jest/environment": "30.0.4", - "@jest/expect": "30.0.4", - "@jest/test-result": "30.0.4", - "@jest/types": "30.0.1", + "@jest/environment": "30.0.5", + "@jest/expect": "30.0.5", + "@jest/test-result": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", "chalk": "^4.1.2", "co": "^4.6.0", "dedent": "^1.6.0", "is-generator-fn": "^2.1.0", - "jest-each": "30.0.2", - "jest-matcher-utils": "30.0.4", - "jest-message-util": "30.0.2", - "jest-runtime": "30.0.4", - "jest-snapshot": "30.0.4", - "jest-util": "30.0.2", + "jest-each": "30.0.5", + "jest-matcher-utils": "30.0.5", + "jest-message-util": "30.0.5", + "jest-runtime": "30.0.5", + "jest-snapshot": "30.0.5", + "jest-util": "30.0.5", "p-limit": "^3.1.0", - "pretty-format": "30.0.2", + "pretty-format": "30.0.5", "pure-rand": "^7.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" @@ -6536,6 +7470,36 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-circus/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-circus/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/jest-circus/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -6586,20 +7550,20 @@ "license": "MIT" }, "node_modules/jest-cli": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.0.4.tgz", - "integrity": "sha512-3dOrP3zqCWBkjoVG1zjYJpD9143N9GUCbwaF2pFF5brnIgRLHmKcCIw+83BvF1LxggfMWBA0gxkn6RuQVuRhIQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.0.5.tgz", + "integrity": "sha512-Sa45PGMkBZzF94HMrlX4kUyPOwUpdZasaliKN3mifvDmkhLYqLLg8HQTzn6gq7vJGahFYMQjXgyJWfYImKZzOw==", "license": "MIT", "dependencies": { - "@jest/core": "30.0.4", - "@jest/test-result": "30.0.4", - "@jest/types": "30.0.1", + "@jest/core": "30.0.5", + "@jest/test-result": "30.0.5", + "@jest/types": "30.0.5", "chalk": "^4.1.2", "exit-x": "^0.2.2", "import-local": "^3.2.0", - "jest-config": "30.0.4", - "jest-util": "30.0.2", - "jest-validate": "30.0.2", + "jest-config": "30.0.5", + "jest-util": "30.0.5", + "jest-validate": "30.0.5", "yargs": "^17.7.2" }, "bin": { @@ -6617,6 +7581,36 @@ } } }, + "node_modules/jest-cli/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-cli/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/jest-cli/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -6667,33 +7661,33 @@ "license": "MIT" }, "node_modules/jest-config": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.0.4.tgz", - "integrity": "sha512-3dzbO6sh34thAGEjJIW0fgT0GA0EVlkski6ZzMcbW6dzhenylXAE/Mj2MI4HonroWbkKc6wU6bLVQ8dvBSZ9lA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.0.5.tgz", + "integrity": "sha512-aIVh+JNOOpzUgzUnPn5FLtyVnqc3TQHVMupYtyeURSb//iLColiMIR8TxCIDKyx9ZgjKnXGucuW68hCxgbrwmA==", "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", "@jest/get-type": "30.0.1", "@jest/pattern": "30.0.1", - "@jest/test-sequencer": "30.0.4", - "@jest/types": "30.0.1", - "babel-jest": "30.0.4", + "@jest/test-sequencer": "30.0.5", + "@jest/types": "30.0.5", + "babel-jest": "30.0.5", "chalk": "^4.1.2", "ci-info": "^4.2.0", "deepmerge": "^4.3.1", "glob": "^10.3.10", "graceful-fs": "^4.2.11", - "jest-circus": "30.0.4", + "jest-circus": "30.0.5", "jest-docblock": "30.0.1", - "jest-environment-node": "30.0.4", + "jest-environment-node": "30.0.5", "jest-regex-util": "30.0.1", - "jest-resolve": "30.0.2", - "jest-runner": "30.0.4", - "jest-util": "30.0.2", - "jest-validate": "30.0.2", + "jest-resolve": "30.0.5", + "jest-runner": "30.0.5", + "jest-util": "30.0.5", + "jest-validate": "30.0.5", "micromatch": "^4.0.8", "parse-json": "^5.2.0", - "pretty-format": "30.0.2", + "pretty-format": "30.0.5", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -6717,6 +7711,36 @@ } } }, + "node_modules/jest-config/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-config/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/jest-config/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -6848,15 +7872,15 @@ } }, "node_modules/jest-diff": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.0.4.tgz", - "integrity": "sha512-TSjceIf6797jyd+R64NXqicttROD+Qf98fex7CowmlSn7f8+En0da1Dglwr1AXxDtVizoxXYZBlUQwNhoOXkNw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.0.5.tgz", + "integrity": "sha512-1UIqE9PoEKaHcIKvq2vbibrCog4Y8G0zmOxgQUVEiTqwR5hJVMCoDsN1vFvI5JvwD37hjueZ1C4l2FyGnfpE0A==", "license": "MIT", "dependencies": { "@jest/diff-sequences": "30.0.1", "@jest/get-type": "30.0.1", "chalk": "^4.1.2", - "pretty-format": "30.0.2" + "pretty-format": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -6924,16 +7948,46 @@ } }, "node_modules/jest-each": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.0.2.tgz", - "integrity": "sha512-ZFRsTpe5FUWFQ9cWTMguCaiA6kkW5whccPy9JjD1ezxh+mJeqmz8naL8Fl/oSbNJv3rgB0x87WBIkA5CObIUZQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.0.5.tgz", + "integrity": "sha512-dKjRsx1uZ96TVyejD3/aAWcNKy6ajMaN531CwWIsrazIqIoXI9TnnpPlkrEYku/8rkS3dh2rbH+kMOyiEIv0xQ==", "license": "MIT", "dependencies": { "@jest/get-type": "30.0.1", - "@jest/types": "30.0.1", + "@jest/types": "30.0.5", "chalk": "^4.1.2", - "jest-util": "30.0.2", - "pretty-format": "30.0.2" + "jest-util": "30.0.5", + "pretty-format": "30.0.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-each/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-each/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -6989,37 +8043,116 @@ "license": "MIT" }, "node_modules/jest-environment-node": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.0.4.tgz", - "integrity": "sha512-p+rLEzC2eThXqiNh9GHHTC0OW5Ca4ZfcURp7scPjYBcmgpR9HG6750716GuUipYf2AcThU3k20B31USuiaaIEg==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.0.5.tgz", + "integrity": "sha512-ppYizXdLMSvciGsRsMEnv/5EFpvOdXBaXRBzFUDPWrsfmog4kYrOGWXarLllz6AXan6ZAA/kYokgDWuos1IKDA==", "license": "MIT", "dependencies": { - "@jest/environment": "30.0.4", - "@jest/fake-timers": "30.0.4", - "@jest/types": "30.0.1", + "@jest/environment": "30.0.5", + "@jest/fake-timers": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", - "jest-mock": "30.0.2", - "jest-util": "30.0.2", - "jest-validate": "30.0.2" + "jest-mock": "30.0.5", + "jest-util": "30.0.5", + "jest-validate": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-haste-map": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.0.2.tgz", - "integrity": "sha512-telJBKpNLeCb4MaX+I5k496556Y2FiKR/QLZc0+MGBYl4k3OO0472drlV2LUe7c1Glng5HuAu+5GLYp//GpdOQ==", + "node_modules/jest-environment-node/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", "license": "MIT", "dependencies": { - "@jest/types": "30.0.1", + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-node/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-node/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-environment-node/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-environment-node/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-environment-node/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/jest-haste-map": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.0.5.tgz", + "integrity": "sha512-dkmlWNlsTSR0nH3nRfW5BKbqHefLZv0/6LCccG0xFCTWcJu8TuEwG+5Cm75iBfjVoockmO6J35o5gxtFSn5xeg==", + "license": "MIT", + "dependencies": { + "@jest/types": "30.0.5", "@types/node": "*", "anymatch": "^3.1.3", "fb-watchman": "^2.0.2", "graceful-fs": "^4.2.11", "jest-regex-util": "30.0.1", - "jest-util": "30.0.2", - "jest-worker": "30.0.2", + "jest-util": "30.0.5", + "jest-worker": "30.0.5", "micromatch": "^4.0.8", "walker": "^1.0.8" }, @@ -7030,29 +8163,108 @@ "fsevents": "^2.3.3" } }, + "node_modules/jest-haste-map/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-haste-map/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-haste-map/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-haste-map/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-haste-map/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-haste-map/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/jest-leak-detector": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.0.2.tgz", - "integrity": "sha512-U66sRrAYdALq+2qtKffBLDWsQ/XoNNs2Lcr83sc9lvE/hEpNafJlq2lXCPUBMNqamMECNxSIekLfe69qg4KMIQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.0.5.tgz", + "integrity": "sha512-3Uxr5uP8jmHMcsOtYMRB/zf1gXN3yUIc+iPorhNETG54gErFIiUhLvyY/OggYpSMOEYqsmRxmuU4ZOoX5jpRFg==", "license": "MIT", "dependencies": { "@jest/get-type": "30.0.1", - "pretty-format": "30.0.2" + "pretty-format": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.0.4.tgz", - "integrity": "sha512-ubCewJ54YzeAZ2JeHHGVoU+eDIpQFsfPQs0xURPWoNiO42LGJ+QGgfSf+hFIRplkZDkhH5MOvuxHKXRTUU3dUQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.0.5.tgz", + "integrity": "sha512-uQgGWt7GOrRLP1P7IwNWwK1WAQbq+m//ZY0yXygyfWp0rJlksMSLQAA4wYQC3b6wl3zfnchyTx+k3HZ5aPtCbQ==", "license": "MIT", "dependencies": { "@jest/get-type": "30.0.1", "chalk": "^4.1.2", - "jest-diff": "30.0.4", - "pretty-format": "30.0.2" + "jest-diff": "30.0.5", + "pretty-format": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -7108,18 +8320,18 @@ "license": "MIT" }, "node_modules/jest-message-util": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.0.2.tgz", - "integrity": "sha512-vXywcxmr0SsKXF/bAD7t7nMamRvPuJkras00gqYeB1V0WllxZrbZ0paRr3XqpFU2sYYjD0qAaG2fRyn/CGZ0aw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.0.5.tgz", + "integrity": "sha512-NAiDOhsK3V7RU0Aa/HnrQo+E4JlbarbmI3q6Pi4KcxicdtjV82gcIUrejOtczChtVQR4kddu1E1EJlW6EN9IyA==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@jest/types": "30.0.1", + "@jest/types": "30.0.5", "@types/stack-utils": "^2.0.3", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "micromatch": "^4.0.8", - "pretty-format": "30.0.2", + "pretty-format": "30.0.5", "slash": "^3.0.0", "stack-utils": "^2.0.6" }, @@ -7127,6 +8339,36 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-message-util/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/jest-message-util/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -7177,19 +8419,98 @@ "license": "MIT" }, "node_modules/jest-mock": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.2.tgz", - "integrity": "sha512-PnZOHmqup/9cT/y+pXIVbbi8ID6U1XHRmbvR7MvUy4SLqhCbwpkmXhLbsWbGewHrV5x/1bF7YDjs+x24/QSvFA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.5.tgz", + "integrity": "sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ==", "license": "MIT", "dependencies": { - "@jest/types": "30.0.1", + "@jest/types": "30.0.5", "@types/node": "*", - "jest-util": "30.0.2" + "jest-util": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-mock/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-mock/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-mock/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-mock/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-mock/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-mock/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/jest-offline": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/jest-offline/-/jest-offline-1.0.1.tgz", @@ -7226,17 +8547,17 @@ } }, "node_modules/jest-resolve": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.0.2.tgz", - "integrity": "sha512-q/XT0XQvRemykZsvRopbG6FQUT6/ra+XV6rPijyjT6D0msOyCvR2A5PlWZLd+fH0U8XWKZfDiAgrUNDNX2BkCw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.0.5.tgz", + "integrity": "sha512-d+DjBQ1tIhdz91B79mywH5yYu76bZuE96sSbxj8MkjWVx5WNdt1deEFRONVL4UkKLSrAbMkdhb24XN691yDRHg==", "license": "MIT", "dependencies": { "chalk": "^4.1.2", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.0.2", + "jest-haste-map": "30.0.5", "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.0.2", - "jest-validate": "30.0.2", + "jest-util": "30.0.5", + "jest-validate": "30.0.5", "slash": "^3.0.0", "unrs-resolver": "^1.7.11" }, @@ -7245,13 +8566,13 @@ } }, "node_modules/jest-resolve-dependencies": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.0.4.tgz", - "integrity": "sha512-EQBYow19B/hKr4gUTn+l8Z+YLlP2X0IoPyp0UydOtrcPbIOYzJ8LKdFd+yrbwztPQvmlBFUwGPPEzHH1bAvFAw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.0.5.tgz", + "integrity": "sha512-/xMvBR4MpwkrHW4ikZIWRttBBRZgWK4d6xt3xW1iRDSKt4tXzYkMkyPfBnSCgv96cpkrctfXs6gexeqMYqdEpw==", "license": "MIT", "dependencies": { "jest-regex-util": "30.0.1", - "jest-snapshot": "30.0.4" + "jest-snapshot": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -7307,31 +8628,31 @@ "license": "MIT" }, "node_modules/jest-runner": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.0.4.tgz", - "integrity": "sha512-mxY0vTAEsowJwvFJo5pVivbCpuu6dgdXRmt3v3MXjBxFly7/lTk3Td0PaMyGOeNQUFmSuGEsGYqhbn7PA9OekQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.0.5.tgz", + "integrity": "sha512-JcCOucZmgp+YuGgLAXHNy7ualBx4wYSgJVWrYMRBnb79j9PD0Jxh0EHvR5Cx/r0Ce+ZBC4hCdz2AzFFLl9hCiw==", "license": "MIT", "dependencies": { - "@jest/console": "30.0.4", - "@jest/environment": "30.0.4", - "@jest/test-result": "30.0.4", - "@jest/transform": "30.0.4", - "@jest/types": "30.0.1", + "@jest/console": "30.0.5", + "@jest/environment": "30.0.5", + "@jest/test-result": "30.0.5", + "@jest/transform": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", "chalk": "^4.1.2", "emittery": "^0.13.1", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", "jest-docblock": "30.0.1", - "jest-environment-node": "30.0.4", - "jest-haste-map": "30.0.2", - "jest-leak-detector": "30.0.2", - "jest-message-util": "30.0.2", - "jest-resolve": "30.0.2", - "jest-runtime": "30.0.4", - "jest-util": "30.0.2", - "jest-watcher": "30.0.4", - "jest-worker": "30.0.2", + "jest-environment-node": "30.0.5", + "jest-haste-map": "30.0.5", + "jest-leak-detector": "30.0.5", + "jest-message-util": "30.0.5", + "jest-resolve": "30.0.5", + "jest-runtime": "30.0.5", + "jest-util": "30.0.5", + "jest-watcher": "30.0.5", + "jest-worker": "30.0.5", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, @@ -7339,6 +8660,36 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-runner/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runner/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/jest-runner/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -7389,31 +8740,31 @@ "license": "MIT" }, "node_modules/jest-runtime": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.0.4.tgz", - "integrity": "sha512-tUQrZ8+IzoZYIHoPDQEB4jZoPyzBjLjq7sk0KVyd5UPRjRDOsN7o6UlvaGF8ddpGsjznl9PW+KRgWqCNO+Hn7w==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.0.5.tgz", + "integrity": "sha512-7oySNDkqpe4xpX5PPiJTe5vEa+Ak/NnNz2bGYZrA1ftG3RL3EFlHaUkA1Cjx+R8IhK0Vg43RML5mJedGTPNz3A==", "license": "MIT", "dependencies": { - "@jest/environment": "30.0.4", - "@jest/fake-timers": "30.0.4", - "@jest/globals": "30.0.4", + "@jest/environment": "30.0.5", + "@jest/fake-timers": "30.0.5", + "@jest/globals": "30.0.5", "@jest/source-map": "30.0.1", - "@jest/test-result": "30.0.4", - "@jest/transform": "30.0.4", - "@jest/types": "30.0.1", + "@jest/test-result": "30.0.5", + "@jest/transform": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", "chalk": "^4.1.2", "cjs-module-lexer": "^2.1.0", "collect-v8-coverage": "^1.0.2", "glob": "^10.3.10", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.0.2", - "jest-message-util": "30.0.2", - "jest-mock": "30.0.2", + "jest-haste-map": "30.0.5", + "jest-message-util": "30.0.5", + "jest-mock": "30.0.5", "jest-regex-util": "30.0.1", - "jest-resolve": "30.0.2", - "jest-snapshot": "30.0.4", - "jest-util": "30.0.2", + "jest-resolve": "30.0.5", + "jest-snapshot": "30.0.5", + "jest-util": "30.0.5", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -7421,6 +8772,36 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-runtime/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runtime/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/jest-runtime/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -7552,9 +8933,9 @@ } }, "node_modules/jest-snapshot": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.0.4.tgz", - "integrity": "sha512-S/8hmSkeUib8WRUq9pWEb5zMfsOjiYWDWzFzKnjX7eDyKKgimsu9hcmsUEg8a7dPAw8s/FacxsXquq71pDgPjQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.0.5.tgz", + "integrity": "sha512-T00dWU/Ek3LqTp4+DcW6PraVxjk28WY5Ua/s+3zUKSERZSNyxTqhDXCWKG5p2HAJ+crVQ3WJ2P9YVHpj1tkW+g==", "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", @@ -7562,20 +8943,20 @@ "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.0.4", + "@jest/expect-utils": "30.0.5", "@jest/get-type": "30.0.1", - "@jest/snapshot-utils": "30.0.4", - "@jest/transform": "30.0.4", - "@jest/types": "30.0.1", + "@jest/snapshot-utils": "30.0.5", + "@jest/transform": "30.0.5", + "@jest/types": "30.0.5", "babel-preset-current-node-syntax": "^1.1.0", "chalk": "^4.1.2", - "expect": "30.0.4", + "expect": "30.0.5", "graceful-fs": "^4.2.11", - "jest-diff": "30.0.4", - "jest-matcher-utils": "30.0.4", - "jest-message-util": "30.0.2", - "jest-util": "30.0.2", - "pretty-format": "30.0.2", + "jest-diff": "30.0.5", + "jest-matcher-utils": "30.0.5", + "jest-message-util": "30.0.5", + "jest-util": "30.0.5", + "pretty-format": "30.0.5", "semver": "^7.7.2", "synckit": "^0.11.8" }, @@ -7583,6 +8964,36 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-snapshot/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/jest-snapshot/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -7633,12 +9044,12 @@ "license": "MIT" }, "node_modules/jest-util": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.2.tgz", - "integrity": "sha512-8IyqfKS4MqprBuUpZNlFB5l+WFehc8bfCe1HSZFHzft2mOuND8Cvi9r1musli+u6F3TqanCZ/Ik4H4pXUolZIg==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.5.tgz", + "integrity": "sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==", "license": "MIT", "dependencies": { - "@jest/types": "30.0.1", + "@jest/types": "30.0.5", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", @@ -7649,6 +9060,36 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-util/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/jest-util/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -7711,17 +9152,47 @@ } }, "node_modules/jest-validate": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.0.2.tgz", - "integrity": "sha512-noOvul+SFER4RIvNAwGn6nmV2fXqBq67j+hKGHKGFCmK4ks/Iy1FSrqQNBLGKlu4ZZIRL6Kg1U72N1nxuRCrGQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.0.5.tgz", + "integrity": "sha512-ouTm6VFHaS2boyl+k4u+Qip4TSH7Uld5tyD8psQ8abGgt2uYYB8VwVfAHWHjHc0NWmGGbwO5h0sCPOGHHevefw==", "license": "MIT", "dependencies": { "@jest/get-type": "30.0.1", - "@jest/types": "30.0.1", + "@jest/types": "30.0.5", "camelcase": "^6.3.0", "chalk": "^4.1.2", "leven": "^3.1.0", - "pretty-format": "30.0.2" + "pretty-format": "30.0.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-validate/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-validate/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -7789,24 +9260,54 @@ "license": "MIT" }, "node_modules/jest-watcher": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.0.4.tgz", - "integrity": "sha512-YESbdHDs7aQOCSSKffG8jXqOKFqw4q4YqR+wHYpR5GWEQioGvL0BfbcjvKIvPEM0XGfsfJrka7jJz3Cc3gI4VQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.0.5.tgz", + "integrity": "sha512-z9slj/0vOwBDBjN3L4z4ZYaA+pG56d6p3kTUhFRYGvXbXMWhXmb/FIxREZCD06DYUwDKKnj2T80+Pb71CQ0KEg==", "license": "MIT", "dependencies": { - "@jest/test-result": "30.0.4", - "@jest/types": "30.0.1", + "@jest/test-result": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "emittery": "^0.13.1", - "jest-util": "30.0.2", + "jest-util": "30.0.5", "string-length": "^4.0.2" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-watcher/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-watcher/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/jest-watcher/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -7857,14 +9358,14 @@ "license": "MIT" }, "node_modules/jest-worker": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.0.2.tgz", - "integrity": "sha512-RN1eQmx7qSLFA+o9pfJKlqViwL5wt+OL3Vff/A+/cPsmuw7NPwfgl33AP+/agRmHzPOFgXviRycR9kYwlcRQXg==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.0.5.tgz", + "integrity": "sha512-ojRXsWzEP16NdUuBw/4H/zkZdHOa7MMYCk4E430l+8fELeLg/mqmMlRhjL7UNZvQrDmnovWZV4DxX03fZF48fQ==", "license": "MIT", "dependencies": { "@types/node": "*", "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.0.2", + "jest-util": "30.0.5", "merge-stream": "^2.0.0", "supports-color": "^8.1.1" }, @@ -7887,6 +9388,85 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jest/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/js-git": { "version": "0.7.8", "resolved": "https://registry.npmjs.org/js-git/-/js-git-0.7.8.tgz", @@ -8635,32 +10215,6 @@ "node": ">=0.10.0" } }, - "node_modules/oxlint": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.7.0.tgz", - "integrity": "sha512-krJN1fIRhs3xK1FyVyPtYIV9tkT4WDoIwI7eiMEKBuCjxqjQt5ZemQm1htPvHqNDOaWFRFt4btcwFdU8bbwgvA==", - "license": "MIT", - "bin": { - "oxc_language_server": "bin/oxc_language_server", - "oxlint": "bin/oxlint" - }, - "engines": { - "node": ">=8.*" - }, - "funding": { - "url": "https://github.com/sponsors/Boshen" - }, - "optionalDependencies": { - "@oxlint/darwin-arm64": "1.7.0", - "@oxlint/darwin-x64": "1.7.0", - "@oxlint/linux-arm64-gnu": "1.7.0", - "@oxlint/linux-arm64-musl": "1.7.0", - "@oxlint/linux-x64-gnu": "1.7.0", - "@oxlint/linux-x64-musl": "1.7.0", - "@oxlint/win32-arm64": "1.7.0", - "@oxlint/win32-x64": "1.7.0" - } - }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -9272,28 +10826,13 @@ "node": ">= 0.8.0" } }, - "node_modules/prettier": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", - "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, "node_modules/pretty-format": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.2.tgz", - "integrity": "sha512-yC5/EBSOrTtqhCKfLHqoUIAXVRZnukHPwWBJWR7h84Q3Be1DRQZLncwcfLoPA5RPQ65qfiCMqgYwdUuQ//eVpg==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz", + "integrity": "sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==", "license": "MIT", "dependencies": { - "@jest/schemas": "30.0.1", + "@jest/schemas": "30.0.5", "ansi-styles": "^5.2.0", "react-is": "^18.3.1" }, @@ -9301,6 +10840,18 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/pretty-format/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/promptly": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/promptly/-/promptly-2.2.0.tgz", @@ -9602,14 +11153,10 @@ } }, "node_modules/run-async": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-4.0.4.tgz", - "integrity": "sha512-2cgeRHnV11lSXBEhq7sN7a5UVjTKm9JTb9x8ApIT//16D7QL96AgnNeWSGoB4gIHc0iYw/Ha0Z+waBaCYZVNhg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-4.0.5.tgz", + "integrity": "sha512-oN9GTgxUNDBumHTTDmQ8dep6VIJbgj9S3dPP+9XylVLIK4xB9XTXtKWROd5pnhdXR9k0EgO1JRcNh0T+Ny2FsA==", "license": "MIT", - "dependencies": { - "oxlint": "^1.2.0", - "prettier": "^3.5.3" - }, "engines": { "node": ">=0.12.0" } diff --git a/package.json b/package.json index 84c90133e..40fad1ec0 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "dependencies": { "@alex_neo/jest-expect-message": "^1.0.5", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "^9.31.0", + "@eslint/js": "^9.32.0", "@freearhey/chronos": "^0.0.1", "@freearhey/core": "^0.10.2", "@freearhey/search-js": "^0.1.2", @@ -46,32 +46,32 @@ "@octokit/core": "^7.0.3", "@octokit/plugin-paginate-rest": "^13.1.1", "@octokit/plugin-rest-endpoint-methods": "^16.0.0", - "@swc/core": "^1.13.0", + "@swc/core": "^1.13.2", "@swc/jest": "^0.2.39", "@types/cli-progress": "^3.11.6", "@types/fs-extra": "^11.0.4", "@types/inquirer": "^9.0.8", "@types/jest": "^30.0.0", "@types/langs": "^2.0.5", - "@types/node": "^24.0.15", + "@types/node": "^24.1.0", "@types/node-cleanup": "^2.1.5", "@types/numeral": "^2.0.5", - "@typescript-eslint/eslint-plugin": "^8.37.0", - "@typescript-eslint/parser": "^8.37.0", - "axios": "^1.10.0", + "@typescript-eslint/eslint-plugin": "^8.38.0", + "@typescript-eslint/parser": "^8.38.0", + "axios": "^1.11.0", "axios-cookiejar-support": "^6.0.4", "chalk": "^5.4.1", - "cheerio": "^1.1.0", + "cheerio": "^1.1.2", "cli-progress": "^3.12.0", "commander": "^14.0.0", "consola": "^3.4.2", - "cross-env": "^7.0.3", + "cross-env": "^10.0.0", "csv-parser": "^3.2.0", "cwait": "^1.1.2", "dayjs": "^1.11.13", "epg-grabber": "^0.41.0", "epg-parser": "^0.3.1", - "eslint": "^9.31.0", + "eslint": "^9.32.0", "eslint-config-prettier": "^10.1.8", "form-data": "^4.0.4", "fs-extra": "^11.3.0", @@ -79,8 +79,8 @@ "globals": "^16.3.0", "husky": "^9.1.7", "iconv-lite": "^0.6.3", - "inquirer": "^12.7.0", - "jest": "^30.0.4", + "inquirer": "^12.8.2", + "jest": "^30.0.5", "jest-offline": "^1.0.1", "langs": "^2.0.0", "libxml2-wasm": "^0.5.0", diff --git a/sites/tv.dir.bg/__data__/content.html b/sites/tv.dir.bg/__data__/content.html deleted file mode 100644 index b369c353f..000000000 --- a/sites/tv.dir.bg/__data__/content.html +++ /dev/null @@ -1 +0,0 @@ -
    \n
    \n
    \n

    \n \u0412\u0447\u0435\u0440\u0430\n <\/p>\n

    \n
    \n
    \n \"\u041a\u0443\u043f\u0430\n <\/div>\n
    \n 08:00\n <\/div>\n
    \n \u041a\u0443\u043f\u0430 \u043d\u0430 \u0424\u0440\u0430\u043d\u0446\u0438\u044f: \u0415\u0441\u043f\u0430\u043b\u0438 - \u041f\u0430\u0440\u0438 \u0421\u0435\u043d \u0416\u0435\u0440\u043c\u0435\u043d\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 10:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u041b\u0435\u0433\u0430\u043d\u0435\u0441 - \u0420\u0435\u0430\u043b \u0421\u043e\u0441\u0438\u0435\u0434\u0430\u0434\n <\/div>\n <\/div>\n
    \n
    \n \""\u041f\u0440\u0435\u0434\n <\/div>\n
    \n 12:00\n <\/div>\n
    \n "\u041f\u0440\u0435\u0434 \u0421\u0442\u0430\u0434\u0438\u043e\u043d\u0430" - \u0441\u043f\u043e\u0440\u0442\u043d\u043e \u0448\u043e\u0443\n <\/div>\n <\/div>\n
    \n
    \n \"\u041f\u0420\u042f\u041a\u041e,\n <\/div>\n
    \n 13:00\n <\/div>\n
    \n \u041f\u0420\u042f\u041a\u041e, \u0413\u043e\u043b\u0444: Italian Open\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 18:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0416\u0438\u0440\u043e\u043d\u0430 - \u0420\u0435\u0430\u043b \u041c\u0430\u0434\u0440\u0438\u0434\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 20:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0411\u0435\u0442\u0438\u0441 - \u0411\u0430\u0440\u0441\u0435\u043b\u043e\u043d\u0430\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 22:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0412\u0430\u043b\u0435\u043d\u0441\u0438\u044f - \u0420\u0430\u0439\u043e \u0412\u0430\u043b\u0435\u043a\u0430\u043d\u043e\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 00:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u041e\u0431\u0437\u043e\u0440 \u043d\u0430 \u0441\u0435\u0437\u043e\u043d\u0430\n <\/div>\n <\/div>\n
    \n
    \n \"\u041f\u0420\u042f\u041a\u041e,\n <\/div>\n
    \n 01:00\n <\/div>\n
    \n \u041f\u0420\u042f\u041a\u041e, \u041c\u0435\u0439\u0434\u0436\u044a\u0440 \u041b\u0438\u0439\u0433 \u0421\u043e\u043a\u044a\u0440: \u0424\u0438\u043b\u0430\u0434\u0435\u043b\u0444\u0438\u044f \u042e\u043d\u0438\u044a\u043d - \u041a\u044a\u043b\u044a\u043c\u0431\u044a\u0441 \u041a\u0440\u044e\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 03:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0410\u0442\u043b\u0435\u0442\u0438\u043a\u043e \u041c\u0430\u0434\u0440\u0438\u0434 - \u0421\u0435\u0432\u0438\u043b\u044f\n <\/div>\n <\/div>\n
    \n
    \n \"Trans\n <\/div>\n
    \n 05:00\n <\/div>\n
    \n Trans World Sport\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n
    \n
    \n

    \n \u0414\u043d\u0435\u0441\n <\/p>\n

    \n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 06:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0416\u0438\u0440\u043e\u043d\u0430 - \u0420\u0435\u0430\u043b \u041c\u0430\u0434\u0440\u0438\u0434\n <\/div>\n <\/div>\n
    \n
    \n \"\u041a\u0443\u043f\u0430\n <\/div>\n
    \n 08:00\n <\/div>\n
    \n \u041a\u0443\u043f\u0430 \u043d\u0430 \u0424\u0440\u0430\u043d\u0446\u0438\u044f: \u041b\u044c\u043e \u041c\u0430\u043d - \u041f\u0430\u0440\u0438 \u0421\u0435\u043d \u0416\u0435\u0440\u043c\u0435\u043d\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 10:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0412\u0430\u043b\u044f\u0434\u043e\u043b\u0438\u0434 - \u0412\u0430\u043b\u0435\u043d\u0441\u0438\u044f\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 12:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0421\u0435\u0432\u0438\u043b\u044f - \u0421\u0435\u043b\u0442\u0430\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 14:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0420\u0430\u0439\u043e \u0412\u0430\u043b\u0435\u043a\u0430\u043d\u043e - \u0420\u0435\u0430\u043b \u041c\u0430\u0434\u0440\u0438\u0434\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 16:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0410\u0442\u043b\u0435\u0442\u0438\u043a\u043e \u041c\u0430\u0434\u0440\u0438\u0434 - \u0425\u0435\u0442\u0430\u0444\u0435\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 18:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0411\u0430\u0440\u0441\u0435\u043b\u043e\u043d\u0430 - \u041b\u0435\u0433\u0430\u043d\u0435\u0441\n <\/div>\n <\/div>\n
    \n
    \n \"\u041f\u0420\u042f\u041a\u041e,\n <\/div>\n
    \n 20:00\n <\/div>\n
    \n \u041f\u0420\u042f\u041a\u041e, "\u041f\u0440\u0435\u0434 \u0421\u0442\u0430\u0434\u0438\u043e\u043d\u0430" - \u0441\u043f\u043e\u0440\u0442\u043d\u043e \u0448\u043e\u0443\n <\/div>\n <\/div>\n
    \n
    \n \"\u041c\u0435\u0439\u0434\u0436\u044a\u0440\n <\/div>\n
    \n 21:30\n <\/div>\n
    \n \u041c\u0435\u0439\u0434\u0436\u044a\u0440 \u041b\u0438\u0439\u0433 \u0421\u043e\u043a\u044a\u0440: \u0424\u0438\u043b\u0430\u0434\u0435\u043b\u0444\u0438\u044f \u042e\u043d\u0438\u044a\u043d - \u041a\u044a\u043b\u044a\u043c\u0431\u044a\u0441 \u041a\u0440\u044e\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 23:30\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0420\u0430\u0439\u043e \u0412\u0430\u043b\u0435\u043a\u0430\u043d\u043e - \u0420\u0435\u0430\u043b \u041c\u0430\u0434\u0440\u0438\u0434\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 01:30\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0421\u0435\u0432\u0438\u043b\u044f - \u0421\u0435\u043b\u0442\u0430\n <\/div>\n <\/div>\n
    \n
    \n \""\u041f\u0440\u0435\u0434\n <\/div>\n
    \n 03:30\n <\/div>\n
    \n "\u041f\u0440\u0435\u0434 \u0421\u0442\u0430\u0434\u0438\u043e\u043d\u0430" - \u0441\u043f\u043e\u0440\u0442\u043d\u043e \u0448\u043e\u0443\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 05:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0410\u0442\u043b\u0435\u0442\u0438\u043a\u043e \u041c\u0430\u0434\u0440\u0438\u0434 - \u0425\u0435\u0442\u0430\u0444\u0435\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n
    \n
    \n

    \n \u0423\u0442\u0440\u0435\n <\/p>\n

    \n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 07:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: "\u0428\u0430\u043c\u043f\u0438\u043e\u043d\u044a\u0442"\n <\/div>\n <\/div>\n
    \n
    \n \"\u041a\u0443\u043f\u0430\n <\/div>\n
    \n 08:00\n <\/div>\n
    \n \u041a\u0443\u043f\u0430 \u043d\u0430 \u0424\u0440\u0430\u043d\u0446\u0438\u044f: \u0411\u0440\u0435\u0441\u0442 - \u0414\u044e\u043d\u043a\u0435\u0440\u043a\n <\/div>\n <\/div>\n
    \n
    \n \""\u041f\u0440\u0435\u0434\n <\/div>\n
    \n 10:00\n <\/div>\n
    \n "\u041f\u0440\u0435\u0434 \u0421\u0442\u0430\u0434\u0438\u043e\u043d\u0430" - \u0441\u043f\u043e\u0440\u0442\u043d\u043e \u0448\u043e\u0443\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 11:30\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: "\u0413\u043e\u043b\u043c\u0430\u0439\u0441\u0442\u043e\u0440\u044a\u0442 \u043d\u0430 \u041b\u0430 \u041b\u0438\u0433\u0430"\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 12:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0411\u0430\u0440\u0441\u0435\u043b\u043e\u043d\u0430 - \u041b\u0435\u0433\u0430\u043d\u0435\u0441\n <\/div>\n <\/div>\n
    \n
    \n \"\u041c\u0435\u0439\u0434\u0436\u044a\u0440\n <\/div>\n
    \n 14:00\n <\/div>\n
    \n \u041c\u0435\u0439\u0434\u0436\u044a\u0440 \u041b\u0438\u0439\u0433 \u0421\u043e\u043a\u044a\u0440: \u0424\u0438\u043b\u0430\u0434\u0435\u043b\u0444\u0438\u044f \u042e\u043d\u0438\u044a\u043d - \u041a\u044a\u043b\u044a\u043c\u0431\u044a\u0441 \u041a\u0440\u044e\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 16:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0420\u0430\u0439\u043e \u0412\u0430\u043b\u0435\u043a\u0430\u043d\u043e - \u0420\u0435\u0430\u043b \u041c\u0430\u0434\u0440\u0438\u0434\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 18:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430 2: \u041c\u0438\u0440\u0430\u043d\u0434\u0435\u0441 - \u041e\u0432\u0438\u0435\u0434\u043e\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 20:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: "\u041d\u0430\u0439-\u0434\u043e\u0431\u0440\u0438\u044f\u0442 \u0432\u0440\u0430\u0442\u0430\u0440"\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 20:30\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430 2: \u041e\u0432\u0438\u0435\u0434\u043e - \u041c\u0438\u0440\u0430\u043d\u0434\u0435\u0441\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 23:15\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0421\u0435\u0432\u0438\u043b\u044f - \u0421\u0435\u043b\u0442\u0430\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 01:15\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0410\u0442\u043b\u0435\u0442\u0438\u043a\u043e \u041c\u0430\u0434\u0440\u0438\u0434 - \u0425\u0435\u0442\u0430\u0444\u0435\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 03:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0411\u0430\u0440\u0441\u0435\u043b\u043e\u043d\u0430 - \u041b\u0435\u0433\u0430\u043d\u0435\u0441\n <\/div>\n <\/div>\n
    \n
    \n \"\u041b\u0430\n <\/div>\n
    \n 05:00\n <\/div>\n
    \n \u041b\u0430 \u041b\u0438\u0433\u0430: \u0420\u0430\u0439\u043e \u0412\u0430\u043b\u0435\u043a\u0430\u043d\u043e - \u0420\u0435\u0430\u043b \u041c\u0430\u0434\u0440\u0438\u0434\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <\/div> \ No newline at end of file diff --git a/sites/tv.dir.bg/__data__/content.json b/sites/tv.dir.bg/__data__/content.json new file mode 100644 index 000000000..1c75a02e1 --- /dev/null +++ b/sites/tv.dir.bg/__data__/content.json @@ -0,0 +1,4 @@ +{ + "status": true, + "html": "
    \n
    \n
    \n

    \n 29.06\n

    \n
    \n
    \n
    \n \"Светът\n
    \n
    \n 06:00\n
    \n
    \n Светът на здравето\n
    \n
    \n
    \n
    \n \"Сестра\n
    \n
    \n 06:30\n
    \n
    \n Сестра Бети\n , сезон 7\n , епизод 12\n
    \n
    \n
    \n
    \n \"Тази\n
    \n
    \n 07:30\n
    \n
    \n Тази събота и неделя\n
    \n
    \n
    \n
    \n \"Богатствата\n
    \n
    \n 11:00\n
    \n
    \n Богатствата на България\n
    \n
    \n
    \n
    \n \"Светът\n
    \n
    \n 11:30\n
    \n
    \n Светът на здравето\n
    \n
    \n
    \n
    \n \"bTV\n
    \n
    \n 11:59\n
    \n
    \n bTV Новините\n
    \n
    \n
    \n
    \n \"НепознатиТЕ\"\n
    \n
    \n 12:30\n
    \n
    \n НепознатиТЕ\n
    \n
    \n
    \n
    \n \"MasterChef\"\n
    \n
    \n 13:00\n
    \n
    \n MasterChef\n , сезон 8\n , епизод 8\n
    \n
    \n
    \n
    \n \"Бригада\n
    \n
    \n 15:00\n
    \n
    \n Бригада Нов дом\n
    \n
    \n
    \n
    \n \"120\n
    \n
    \n 16:30\n
    \n
    \n 120 минути\n
    \n
    \n
    \n
    \n \"bTV\n
    \n
    \n 18:55\n
    \n
    \n bTV Новините\n
    \n
    \n
    \n
    \n \"Защо,\n
    \n
    \n 19:40\n
    \n
    \n Защо, господин министър?\n
    \n
    \n
    \n
    \n \"Аз\n
    \n
    \n 20:00\n
    \n
    \n Аз обичам България!\n
    \n
    \n
    \n
    \n \"Мафия\n
    \n
    \n 22:30\n
    \n
    \n Мафия Мама\n
    \n
    \n
    \n
    \n \"Мъртвите\n
    \n
    \n 00:30\n
    \n
    \n Мъртвите не умират\n
    \n
    \n
    \n
    \n \"120\n
    \n
    \n 02:40\n
    \n
    \n 120 минути\n
    \n
    \n
    \n
    \n \"Убийства\n
    \n
    \n 05:00\n
    \n
    \n Убийства в Рая\n , сезон 1\n , епизод 5\n
    \n
    \n
    \n
    \n
    \n
    \n
    \n

    \n 30.06\n

    \n
    \n
    \n
    \n \"Лице\n
    \n
    \n 06:15\n
    \n
    \n Лице в лице\n
    \n
    \n
    \n
    \n \"Тази\n
    \n
    \n 06:57\n
    \n
    \n Тази сутрин\n
    \n
    \n
    \n
    \n \"Преди\n
    \n
    \n 09:30\n
    \n
    \n Преди обед\n
    \n
    \n
    \n
    \n \"Хороскопът\n
    \n
    \n 11:52\n
    \n
    \n Хороскопът на Алена\n
    \n
    \n
    \n
    \n \"bTV\n
    \n
    \n 11:59\n
    \n
    \n bTV Новините\n
    \n
    \n
    \n
    \n \"Комиците\n
    \n
    \n 12:45\n
    \n
    \n Комиците и приятели\n
    \n
    \n
    \n
    \n \"Вражда\"\n
    \n
    \n 13:20\n
    \n
    \n Вражда\n , сезон 1\n , епизод 15\n
    \n
    \n
    \n
    \n \"Плен\"\n
    \n
    \n 14:50\n
    \n
    \n Плен\n , сезон 2\n , епизод 47\n
    \n
    \n
    \n
    \n \"Моите\n
    \n
    \n 15:55\n
    \n
    \n Моите братя и сестри\n , сезон 2\n , епизод 118\n
    \n
    \n
    \n
    \n \"bTV\n
    \n
    \n 16:59\n
    \n
    \n bTV Новините\n
    \n
    \n
    \n
    \n \"Лице\n
    \n
    \n 17:20\n
    \n
    \n Лице в лице\n
    \n
    \n
    \n
    \n \"Стани\n
    \n
    \n 18:00\n
    \n
    \n Стани богат\n , сезон 6\n , епизод 207\n
    \n
    \n
    \n
    \n \"bTV\n
    \n
    \n 18:55\n
    \n
    \n bTV Новините\n
    \n
    \n
    \n
    \n \"Лице\n
    \n
    \n 19:50\n
    \n
    \n Лице в лице след Новините\n
    \n
    \n
    \n
    \n \"Кой\n
    \n
    \n 20:00\n
    \n
    \n Кой да знае?\n , сезон 4\n , епизод 40\n
    \n
    \n
    \n
    \n \"Колко\n
    \n
    \n 21:30\n
    \n
    \n Колко ми даваш?\n , сезон 2\n , епизод 40\n
    \n
    \n
    \n
    \n \"bTV\n
    \n
    \n 22:30\n
    \n
    \n bTV Новините\n
    \n
    \n
    \n
    \n \"От\n
    \n
    \n 23:00\n
    \n
    \n От местопрестъплението: Маями\n , сезон 2\n , епизод 21\n
    \n
    \n
    \n
    \n \"Убийства\n
    \n
    \n 00:00\n
    \n
    \n Убийства в Рая\n , сезон 1\n , епизод 8\n
    \n
    \n
    \n
    \n \"Естествен\n
    \n
    \n 01:00\n
    \n
    \n Естествен интелект\n , сезон 2\n , епизод 4\n
    \n
    \n
    \n
    \n \"bTV\n
    \n
    \n 02:10\n
    \n
    \n bTV Новините\n
    \n
    \n
    \n
    \n \"Преди\n
    \n
    \n 02:50\n
    \n
    \n Преди обед\n
    \n
    \n
    \n
    \n \"Убийства\n
    \n
    \n 05:00\n
    \n
    \n Убийства в Рая\n , сезон 1\n , епизод 6\n
    \n
    \n
    \n
    \n
    \n
    \n
    \n

    \n 01.07\n

    \n
    \n
    \n
    \n \"Лице\n
    \n
    \n 06:15\n
    \n
    \n Лице в лице\n
    \n
    \n
    \n
    \n \"Тази\n
    \n
    \n 06:57\n
    \n
    \n Тази сутрин\n
    \n
    \n
    \n
    \n \"Преди\n
    \n
    \n 09:30\n
    \n
    \n Преди обед\n
    \n
    \n
    \n
    \n \"Хороскопът\n
    \n
    \n 11:52\n
    \n
    \n Хороскопът на Алена\n
    \n
    \n
    \n
    \n \"bTV\n
    \n
    \n 11:59\n
    \n
    \n bTV Новините\n
    \n
    \n
    \n
    \n \"Комиците\n
    \n
    \n 12:45\n
    \n
    \n Комиците и приятели\n
    \n
    \n
    \n
    \n \"Вражда\"\n
    \n
    \n 13:20\n
    \n
    \n Вражда\n , сезон 1\n , епизод 16\n
    \n
    \n
    \n
    \n \"Плен\"\n
    \n
    \n 14:50\n
    \n
    \n Плен\n , сезон 2\n , епизод 48\n
    \n
    \n
    \n
    \n \"Моите\n
    \n
    \n 15:55\n
    \n
    \n Моите братя и сестри\n , сезон 2\n , епизод 119\n
    \n
    \n
    \n
    \n \"bTV\n
    \n
    \n 16:59\n
    \n
    \n bTV Новините\n
    \n
    \n
    \n
    \n \"Лице\n
    \n
    \n 17:20\n
    \n
    \n Лице в лице\n
    \n
    \n
    \n
    \n \"Стани\n
    \n
    \n 18:00\n
    \n
    \n Стани богат\n , сезон 6\n , епизод 208\n
    \n
    \n
    \n
    \n \"bTV\n
    \n
    \n 18:55\n
    \n
    \n bTV Новините\n
    \n
    \n
    \n
    \n \"Лице\n
    \n
    \n 19:50\n
    \n
    \n Лице в лице след Новините\n
    \n
    \n
    \n
    \n \"Кой\n
    \n
    \n 20:00\n
    \n
    \n Кой да знае?\n , сезон 4\n , епизод 41\n
    \n
    \n
    \n
    \n \"Колко\n
    \n
    \n 21:30\n
    \n
    \n Колко ми даваш?\n , сезон 2\n , епизод 41\n
    \n
    \n
    \n
    \n \"bTV\n
    \n
    \n 22:30\n
    \n
    \n bTV Новините\n
    \n
    \n
    \n
    \n \"От\n
    \n
    \n 23:00\n
    \n
    \n От местопрестъплението: Маями\n , сезон 2\n , епизод 22\n
    \n
    \n
    \n
    \n \"Убийства\n
    \n
    \n 00:00\n
    \n
    \n Убийства в Рая\n , сезон 2\n , епизод 1\n
    \n
    \n
    \n
    \n \"Естествен\n
    \n
    \n 01:00\n
    \n
    \n Естествен интелект\n , сезон 2\n , епизод 5\n
    \n
    \n
    \n
    \n \"bTV\n
    \n
    \n 02:10\n
    \n
    \n bTV Новините\n
    \n
    \n
    \n
    \n \"Преди\n
    \n
    \n 02:50\n
    \n
    \n Преди обед\n
    \n
    \n
    \n
    \n \"Убийства\n
    \n
    \n 05:00\n
    \n
    \n Убийства в Рая\n , сезон 1\n , епизод 7\n
    \n
    \n
    \n
    \n
    \n
    " +} \ No newline at end of file diff --git a/sites/tv.dir.bg/__data__/no_content.json b/sites/tv.dir.bg/__data__/no_content.json new file mode 100644 index 000000000..b47923e38 --- /dev/null +++ b/sites/tv.dir.bg/__data__/no_content.json @@ -0,0 +1,4 @@ +{ + "status": true, + "html": "
    \n
    \n
    \n

    \n 29.07\n

    \n
    \n
    \n
    \n
    \n
    \n
    \n

    \n 30.07\n

    \n
    \n
    \n
    \n
    \n
    \n
    \n

    \n 31.07\n

    \n
    \n
    \n
    \n
    \n
    " +} \ No newline at end of file diff --git a/sites/tv.dir.bg/__data__/no_data.html b/sites/tv.dir.bg/__data__/no_data.html deleted file mode 100644 index 5f0823d88..000000000 --- a/sites/tv.dir.bg/__data__/no_data.html +++ /dev/null @@ -1 +0,0 @@ -
    \n
    \n
    \n

    \n 19.02\n

    \n
    \n
    \n
    \n
    \n
    \n
    \n

    \n 20.02\n

    \n
    \n
    \n
    \n
    \n
    \n
    \n

    \n 21.02\n

    \n
    \n
    \n
    \n
    \n
    \ No newline at end of file diff --git a/sites/tv.dir.bg/tv.dir.bg.config.js b/sites/tv.dir.bg/tv.dir.bg.config.js index 9cb5f5a54..e62e97e13 100644 --- a/sites/tv.dir.bg/tv.dir.bg.config.js +++ b/sites/tv.dir.bg/tv.dir.bg.config.js @@ -1,52 +1,134 @@ const axios = require('axios') const cheerio = require('cheerio') -const url = require('url') -const { DateTime } = require('luxon') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') -let cachedToken = null -let tokenExpiry = null +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +let sessionCache = null + +async function getSession(forceRefresh = false) { + if (sessionCache && !forceRefresh) { + return sessionCache + } + + try { + const initResponse = await axios.get('https://tv.dir.bg/init') + + if (!initResponse.data) { + throw new Error('No response data from init endpoint') + } + + // Extract cookies from response headers + const setCookieHeader = initResponse.headers['set-cookie'] + let xsrfToken = null + let dirSessionCookie = null + + if (setCookieHeader) { + setCookieHeader.forEach(cookie => { + // Extract XSRF token from cookie + const xsrfMatch = cookie.match(/XSRF-TOKEN=([^;]+)/) + if (xsrfMatch) { + xsrfToken = decodeURIComponent(xsrfMatch[1]) + } + + // Extract dir_session cookie + const sessionMatch = cookie.match(/dir_session=([^;]+)/) + if (sessionMatch) { + dirSessionCookie = sessionMatch[1] + } + }) + } + + const csrfToken = initResponse.data.csrfToken + + if (!csrfToken) { + throw new Error('No CSRF/XSRF token found in response') + } + + // Build cookie string + let cookieString = '' + if (xsrfToken) { + cookieString += `XSRF-TOKEN=${encodeURIComponent(xsrfToken)}` + } + if (dirSessionCookie) { + if (cookieString) cookieString += '; ' + cookieString += `dir_session=${dirSessionCookie}` + } + + sessionCache = { + csrfToken, + cookieString, + timestamp: Date.now() + } + + return sessionCache + + } catch (error) { + console.error('Error getting session:', error.message) + throw error + } +} module.exports = { site: 'tv.dir.bg', days: 2, - async url({ channel, date }) { - const token = await getToken() - if (!token) { - throw new Error('Unable to retrieve CSRF token') - } - - const form = new url.URLSearchParams({ - _token: token, - channel: channel.site_id, - day: date.format('YYYY-MM-DD') - }) - - return axios.post('https://tv.dir.bg/load/programs', form.toString(), { - headers: { - 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', - 'X-Requested-With': 'XMLHttpRequest' + url: 'https://tv.dir.bg/load/programs', + request: { + maxContentLength: 125000000, // 10 MB + method: 'POST', + async headers() { + try { + const session = await getSession() + return { + 'Cookie': session.cookieString, + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + 'X-Requested-With': 'XMLHttpRequest' + } + + } catch (error) { + console.error('Error getting headers:', error.message) + throw error } - }) + }, + async data({ channel, date }) { + try { + const session = await getSession() + + const params = new URLSearchParams() + params.append('_token', session.csrfToken) + params.append('channel', channel.site_id) + params.append('day', date.format('YYYY-MM-DD')) + + return params + + } catch (error) { + console.error('Error preparing request data:', error.message) + throw error + } + }, }, parser({ content, date }) { const programs = [] const items = parseItems(content) - + items.forEach(item => { - const $item = cheerio.load(item) const prev = programs[programs.length - 1] + const $item = cheerio.load(item) let start = parseStart($item, date) - if (!start) return if (prev) { - if (start < prev.start) { - start = start.plus({ days: 1 }) - date = date.plus({ days: 1 }) + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') } prev.stop = start } - const stop = start.plus({ minutes: 30 }) + const stop = start.add(30, 'm') programs.push({ title: parseTitle($item), start, @@ -56,6 +138,7 @@ module.exports = { return programs }, + async channels() { try { const response = await axios.get('https://tv.dir.bg/channels') @@ -63,7 +146,7 @@ module.exports = { const channels = [] - $('.channel_cont').each((index, element) => { + $('.channel_cont').each((_index, element) => { const $element = $(element) const $link = $element.find('a.channel_link') @@ -79,63 +162,55 @@ module.exports = { channels.push({ lang: 'bg', site_id: site_id, - name: name, - logo: logo + name: name.trim(), + logo: logo ? (logo.startsWith('http') ? logo : `https://tv.dir.bg${logo}`) : null }) } }) - return channels + return channels - } catch (error) { - console.error('Error fetching channels:', error) - return [] - } -} -} - -async function getToken() { - if (cachedToken && tokenExpiry && DateTime.now() < tokenExpiry) { - return cachedToken - } - - try { - const response = await axios.get('https://tv.dir.bg/init', { headers: {'X-Requested-With': 'XMLHttpRequest'} }) - - // Check different possible locations for the token - let token = null - if (response.data && response.data.csrfToken) { - token = response.data.csrfToken + } catch (error) { + console.error('Error fetching channels:', error.message) + return [] } - - if (token) { - cachedToken = token - tokenExpiry = DateTime.now().plus({ hours: 1 }) - return token - } else { - console.error('CSRF token not found in response structure:', Object.keys(response.data || {})) - return null - } - } catch (error) { - console.error('Error fetching token:', error.message) - return null + }, + + clearSession() { + sessionCache = null } } function parseStart($item, date) { - const timeText = $item('.broadcast-time').text().trim() - if (!timeText) return null + const time = $item('.broadcast-time').text().trim() + const dateString = `${date.format('YYYY-MM-DD')} ${time}` - const [hours, minutes] = timeText.split(':').map(Number) - const dateTime = date.isValid ? date : DateTime.fromISO(date) - return dateTime.set({ hour: hours, minute: minutes, second: 0, millisecond: 0 }) + return dayjs.tz(dateString, 'YYYY-MM-DD HH:mm', 'Europe/Sofia') } + function parseTitle($item) { - return $item('.broadcast-title').text().trim() + return $item('.broadcast-title').text() + .replace(/\s+/g, ' ') + .trim() } function parseItems(content) { - const $ = cheerio.load(content) - return $('.broadcast-item').toArray() -} + try { + const json = JSON.parse(content) + + if (!json || json.status !== true) { + return [] + } + + const $ = cheerio.load(json.html) + const items = $('.broadcast-item').toArray() + + return items + + } catch (error) { + console.error('❌ Error parsing items:', error.message) + console.error('Error stack:', error.stack) + return [] + } +} \ No newline at end of file diff --git a/sites/tv.dir.bg/tv.dir.bg.test.js b/sites/tv.dir.bg/tv.dir.bg.test.js index b54e7d88c..eac5d01d1 100644 --- a/sites/tv.dir.bg/tv.dir.bg.test.js +++ b/sites/tv.dir.bg/tv.dir.bg.test.js @@ -9,46 +9,42 @@ dayjs.extend(utc) const date = dayjs.utc('2025-06-30', 'YYYY-MM-DD').startOf('d') const channel = { - site_id: '12', + site_id: '61', xmltv_id: 'BTV.bg' } it('can generate valid url', () => { - expect(url({ channel, date })).toBe('https://tv.dir.bg/programa/12') + expect(url).toBe('https://tv.dir.bg/load/programs') }) it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - const result = parser({ content, date }).map(p => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const results = parser({ content, date }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() return p }) - expect(result).toMatchObject([ - { - start: '2025-06-30T08:00:00.000Z', - stop: '2025-06-30T10:00:00.000Z', - title: 'Купа на Франция: Еспали - Пари Сен Жермен' - }, - { - start: '2025-06-30T10:00:00.000Z', - stop: '2025-06-30T12:00:00.000Z', - title: 'Ла Лига: Леганес - Реал Сосиедад' - }, - { - start: '2025-06-30T12:00:00.000Z', - stop: '2025-06-30T13:00:00.000Z', - title: 'Пред Стадиона" - спортно шоу' - } - ]) + expect(results.length).toBe(63) + + expect(results[0]).toMatchObject({ + start: '2025-06-30T03:00:00.000Z', + stop: '2025-06-30T03:30:00.000Z', + title: 'Светът на здравето' + }) + + expect(results[62]).toMatchObject({ + start: '2025-07-01T02:00:00.000Z', + stop: '2025-07-01T02:30:00.000Z', + title: 'Убийства в Рая , сезон 1 , епизод 7' + }) }) it('can handle empty guide', () => { const result = parser({ date, channel, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_data.html')) + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) }) expect(result).toMatchObject([]) }) From 9d2cf08e2e2e95f246759bad0d4320c5e3759bbc Mon Sep 17 00:00:00 2001 From: theofficialomega <30985701+BellezaEmporium@users.noreply.github.com> Date: Sun, 27 Jul 2025 16:30:07 +0200 Subject: [PATCH 15/32] linting fixes all files --- sites/mi.tv/mi.tv.config.js | 12 +++++------ sites/tataplay.com/tataplay.com.config.js | 2 +- sites/tvinsider.com/tvinsider.com.config.js | 22 ++++++++++----------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/sites/mi.tv/mi.tv.config.js b/sites/mi.tv/mi.tv.config.js index fb8ec6058..4f53f2a92 100644 --- a/sites/mi.tv/mi.tv.config.js +++ b/sites/mi.tv/mi.tv.config.js @@ -7,12 +7,12 @@ dayjs.extend(utc) dayjs.extend(customParseFormat) const headers = { - "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", - "accept-language": "en", - "sec-fetch-site": "same-origin", - "sec-fetch-user": "?1", - "upgrade-insecure-requests": "1", - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36" + 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', + 'accept-language': 'en', + 'sec-fetch-site': 'same-origin', + 'sec-fetch-user': '?1', + 'upgrade-insecure-requests': '1', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36' } module.exports = { diff --git a/sites/tataplay.com/tataplay.com.config.js b/sites/tataplay.com/tataplay.com.config.js index e8a8ed43a..6cf3fda04 100644 --- a/sites/tataplay.com/tataplay.com.config.js +++ b/sites/tataplay.com/tataplay.com.config.js @@ -4,7 +4,7 @@ module.exports = { site: 'tataplay.com', days: 1, - url({ channel, date }) { + url({ date }) { return `https://tm.tapi.videoready.tv/content-detail/pub/api/v2/channels/schedule?date=${date.format('DD-MM-YYYY')}` }, diff --git a/sites/tvinsider.com/tvinsider.com.config.js b/sites/tvinsider.com/tvinsider.com.config.js index 9162c3bdb..2ac33b164 100644 --- a/sites/tvinsider.com/tvinsider.com.config.js +++ b/sites/tvinsider.com/tvinsider.com.config.js @@ -14,7 +14,7 @@ module.exports = { items.forEach(item => { const prev = programs[programs.length - 1] const $item = cheerio.load(item) - const episodeInfo = parseEP($item); + const episodeInfo = parseEP($item) let start = parseStart($item, date) if (!start) return if (prev) { @@ -68,15 +68,15 @@ function parseTitle($item) { return $item('h3').text().trim() } function parseEP($item){ - const text = $item('h6').text().trim(); - const match = text.match(/Season\s+(\d+)\s*•\s*Episode\s+(\d+)/i); + const text = $item('h6').text().trim() + const match = text.match(/Season\s+(\d+)\s*•\s*Episode\s+(\d+)/i) - if (!match) return {}; // Return an empty object if no match, so properties are undefined later + if (!match) return {} // Return an empty object if no match, so properties are undefined later - const season = parseInt(match[1], 10); - const episode = parseInt(match[2], 10); + const season = parseInt(match[1], 10) + const episode = parseInt(match[2], 10) - return { season, episode }; // Return an object with season and episode + return { season, episode } // Return an object with season and episode } function parseSubtitle($item) { @@ -84,13 +84,13 @@ function parseSubtitle($item) { } function parsePreviously($item){ - const h3Text = $item('h3').text().trim(); - const isNewShow = /New$/.test(h3Text); + const h3Text = $item('h3').text().trim() + const isNewShow = /New$/.test(h3Text) if (isNewShow) { - return null; + return null } else { - return {}; + return {} } } From 4c952a6c1395e90a6ba771fcd8b37f3f46a4ba83 Mon Sep 17 00:00:00 2001 From: theofficialomega <30985701+BellezaEmporium@users.noreply.github.com> Date: Sun, 27 Jul 2025 16:34:04 +0200 Subject: [PATCH 16/32] add specific check for linebreak linting --- eslint.config.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 57f9e877e..e8b875b6b 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -36,7 +36,7 @@ export default [ '@typescript-eslint/no-require-imports': 'off', '@typescript-eslint/no-var-requires': 'off', 'no-case-declarations': 'off', - 'linebreak-style': ['error', 'windows'], + 'linebreak-style': ['error', process.platform === 'win32' ? 'windows' : 'unix'], quotes: [ 'error', From 5be8899520dfc1dbfeb211f05e356d89f942ba99 Mon Sep 17 00:00:00 2001 From: theofficialomega <30985701+BellezaEmporium@users.noreply.github.com> Date: Sun, 27 Jul 2025 16:41:01 +0200 Subject: [PATCH 17/32] revert, whoopsie for me. --- eslint.config.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index e8b875b6b..57f9e877e 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -36,7 +36,7 @@ export default [ '@typescript-eslint/no-require-imports': 'off', '@typescript-eslint/no-var-requires': 'off', 'no-case-declarations': 'off', - 'linebreak-style': ['error', process.platform === 'win32' ? 'windows' : 'unix'], + 'linebreak-style': ['error', 'windows'], quotes: [ 'error', From f3a3a8c404ea006fa468ef9f8c18db7c0dc2ed97 Mon Sep 17 00:00:00 2001 From: theofficialomega <30985701+BellezaEmporium@users.noreply.github.com> Date: Sun, 27 Jul 2025 18:10:22 +0200 Subject: [PATCH 18/32] fix programetv.ro checking, continue uniformizing --- sites/programetv.ro/__data__/content.html | 15 +++++++ sites/programetv.ro/__data__/no_content.html | 13 ++++++ sites/programetv.ro/programetv.ro.config.js | 4 +- sites/programetv.ro/programetv.ro.test.js | 36 +++-------------- .../__data__/content.json | 1 + .../__data__/content_1.json | 1 + .../__data__/content_2.json | 1 + .../__data__/no_content.json | 1 + .../programme-tv.vini.pf.test.js | 21 +++++----- sites/programtv.onet.pl/__data__/content.html | 1 + .../__data__/no_content.html | 1 + .../programtv.onet.pl.test.js | 7 ++-- sites/raiplay.it/__data__/content.json | 1 + sites/raiplay.it/__data__/no_content.json | 1 + sites/raiplay.it/raiplay.it.test.js | 9 ++--- sites/rikstv.no/__data__/content.json | 21 ++++++++++ sites/rikstv.no/rikstv.no.test.js | 20 ++-------- .../rtmklik.rtm.gov.my/__data__/content.json | 1 + .../__data__/no_content.json | 1 + .../rtmklik.rtm.gov.my.test.js | 8 ++-- sites/s.mxtv.jp/__data__/content.json | 1 + sites/s.mxtv.jp/s.mxtv.jp.test.js | 5 ++- sites/shahid.mbc.net/__data__/content.json | 1 + sites/shahid.mbc.net/shahid.mbc.net.test.js | 5 ++- sites/siba.com.co/__data__/content.json | 1 + sites/siba.com.co/__data__/no_content.json | 1 + sites/siba.com.co/siba.com.co.test.js | 8 ++-- sites/sky.de/__data__/content.json | 1 + sites/sky.de/sky.de.test.js | 6 +-- sites/stod2.is/__data__/content.json | 34 ++++++++++++++++ sites/stod2.is/stod2.is.test.js | 40 ++----------------- sites/taiwanplus.com/__data__/content.json | 1 + sites/taiwanplus.com/taiwanplus.com.test.js | 5 ++- sites/tapdmv.com/__data__/content.json | 1 + sites/tapdmv.com/__data__/no_content.json | 1 + sites/tapdmv.com/tapdmv.com.test.js | 7 ++-- sites/tataplay.com/__data__/content.json | 22 ++++++++++ sites/tataplay.com/tataplay.com.test.js | 25 ++---------- sites/teliatv.ee/__data__/content.json | 1 + sites/teliatv.ee/__data__/no_content.json | 1 + sites/teliatv.ee/teliatv.ee.test.js | 7 ++-- sites/tvim.tv/__data__/content.json | 1 + sites/tvim.tv/__data__/no_content.json | 1 + sites/tvim.tv/tvim.tv.test.js | 7 ++-- 44 files changed, 193 insertions(+), 154 deletions(-) create mode 100644 sites/programetv.ro/__data__/content.html create mode 100644 sites/programetv.ro/__data__/no_content.html create mode 100644 sites/programme-tv.vini.pf/__data__/content.json create mode 100644 sites/programme-tv.vini.pf/__data__/content_1.json create mode 100644 sites/programme-tv.vini.pf/__data__/content_2.json create mode 100644 sites/programme-tv.vini.pf/__data__/no_content.json create mode 100644 sites/programtv.onet.pl/__data__/content.html create mode 100644 sites/programtv.onet.pl/__data__/no_content.html create mode 100644 sites/raiplay.it/__data__/content.json create mode 100644 sites/raiplay.it/__data__/no_content.json create mode 100644 sites/rikstv.no/__data__/content.json create mode 100644 sites/rtmklik.rtm.gov.my/__data__/content.json create mode 100644 sites/rtmklik.rtm.gov.my/__data__/no_content.json create mode 100644 sites/s.mxtv.jp/__data__/content.json create mode 100644 sites/shahid.mbc.net/__data__/content.json create mode 100644 sites/siba.com.co/__data__/content.json create mode 100644 sites/siba.com.co/__data__/no_content.json create mode 100644 sites/sky.de/__data__/content.json create mode 100644 sites/stod2.is/__data__/content.json create mode 100644 sites/taiwanplus.com/__data__/content.json create mode 100644 sites/tapdmv.com/__data__/content.json create mode 100644 sites/tapdmv.com/__data__/no_content.json create mode 100644 sites/tataplay.com/__data__/content.json create mode 100644 sites/teliatv.ee/__data__/content.json create mode 100644 sites/teliatv.ee/__data__/no_content.json create mode 100644 sites/tvim.tv/__data__/content.json create mode 100644 sites/tvim.tv/__data__/no_content.json diff --git a/sites/programetv.ro/__data__/content.html b/sites/programetv.ro/__data__/content.html new file mode 100644 index 000000000..0eba125be --- /dev/null +++ b/sites/programetv.ro/__data__/content.html @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/sites/programetv.ro/__data__/no_content.html b/sites/programetv.ro/__data__/no_content.html new file mode 100644 index 000000000..4589c8b27 --- /dev/null +++ b/sites/programetv.ro/__data__/no_content.html @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/sites/programetv.ro/programetv.ro.config.js b/sites/programetv.ro/programetv.ro.config.js index 4a14f0e58..344e65b3d 100644 --- a/sites/programetv.ro/programetv.ro.config.js +++ b/sites/programetv.ro/programetv.ro.config.js @@ -72,8 +72,8 @@ function parseStop(item) { } function parseContent(content) { - const [, data] = content.match(/var pageData = ({.+});\n/) || [null, null] - + const [, data] = content.match(/var pageData = ({.+?});/) || [null, null] + return data ? JSON.parse(data) : {} } diff --git a/sites/programetv.ro/programetv.ro.test.js b/sites/programetv.ro/programetv.ro.test.js index 03be79559..22731737d 100644 --- a/sites/programetv.ro/programetv.ro.test.js +++ b/sites/programetv.ro/programetv.ro.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./programetv.ro.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -7,22 +9,6 @@ dayjs.extend(utc) const date = dayjs.utc('2021-10-24', 'YYYY-MM-DD').startOf('d') const channel = { site_id: 'pro-tv', xmltv_id: 'ProTV.ro' } -const content = ` - - - - - -` it('can generate valid url', () => { const result = url({ date, channel }) @@ -30,6 +16,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') const result = parser({ date, channel, content }) expect(result).toMatchObject([ { @@ -48,21 +35,8 @@ it('can handle empty guide', () => { const result = parser({ date, channel, - content: ` - - - - - - -` + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') + }) expect(result).toMatchObject([]) }) diff --git a/sites/programme-tv.vini.pf/__data__/content.json b/sites/programme-tv.vini.pf/__data__/content.json new file mode 100644 index 000000000..41e8baccd --- /dev/null +++ b/sites/programme-tv.vini.pf/__data__/content.json @@ -0,0 +1 @@ +{"programmes":[{"nid":"8857261","src":"https://programme-tv.vini.pf/sites/default/files/img-icones/192.png","alt":"","title":"","url":"/tf1","programmes":[{"nidP":"24162436","categorieP":"Magazine","categorieTIDP":"1033","episodeP":"-1","titreP":"Reportages découverte","heureP":"13:50","timestampDeb":1637452200,"timestampFin":1637457000,"altP":"","titleP":"","legendeP":"La coloc ne connaît pas la crise","desc":"Pour faire face à la crise du logement, aux loyers toujours plus élevés, à la solitude ou pour les gardes d'enfants, les colocations ont le vent en poupe, Pour mieux comprendre ce nouveau phénomène, une équipe a partagé le quotidien de quatre foyers : une retraitée qui héberge des étudiants, des mamans solos, enceintes, qui partagent un appartement associatif, trois générations de la même famille sur un domaine viticole et une étudiante qui intègre une colocation XXL.","srcP":"https://programme-tv.vini.pf/sites/default/files/img-icones/52ada51ed86b7e7bc11eaee83ff2192785989d77.jpg","urlP":"/reportages-decouverte-20112021-1350","width":58.333333333333,"active":false,"progression":0,"test":0,"nowphp":1637509179},{"nidP":"24162437","categorieP":"Magazine","categorieTIDP":"1033","episodeP":"-1","titreP":"Les docs du week-end","heureP":"15:10","timestampDeb":1637457000,"timestampFin":1637461800,"altP":"","titleP":"","legendeP":"Que sont-ils devenus ? L'incroyable destin des stars des émissions de télécrochet","desc":"Un documentaire français réalisé en 2019, Cindy Sander, Myriam Abel, Mario, Michal ou encore Magali Vaé ont fait les grandes heures des premières émissions de télécrochet modernes, dans les années 2000, Des années après leur passage, que reste-t-il de leur notoriété ? Comment ces candidats ont-ils vécu leur soudaine médiatisation ? Quels rapports entretenaient-ils avec les autres participants et les membres du jury, souvent intransigeants ?","srcP":"https://programme-tv.vini.pf/sites/default/files/img-icones/6e64cfbc55c1f4cbd11e3011401403d4dc08c6d2.jpg","urlP":"/les-docs-du-week-end-20112021-1510","width":41.666666666667,"active":false,"progression":0,"test":0,"nowphp":1637509179}]}]} \ No newline at end of file diff --git a/sites/programme-tv.vini.pf/__data__/content_1.json b/sites/programme-tv.vini.pf/__data__/content_1.json new file mode 100644 index 000000000..b8c311d02 --- /dev/null +++ b/sites/programme-tv.vini.pf/__data__/content_1.json @@ -0,0 +1 @@ +{"programmes":[{"nid":"8857261","src":"https://programme-tv.vini.pf/sites/default/files/img-icones/192.png","alt":"","title":"","url":"/tf1","programmes":[{"nidP":"24162437","categorieP":"Magazine","categorieTIDP":"1033","episodeP":"-1","titreP":"Les docs du week-end","heureP":"15:10","timestampDeb":1637457000,"timestampFin":1637461800,"altP":"","titleP":"","legendeP":"Que sont-ils devenus ? L'incroyable destin des stars des émissions de télécrochet","desc":"Un documentaire français réalisé en 2019, Cindy Sander, Myriam Abel, Mario, Michal ou encore Magali Vaé ont fait les grandes heures des premières émissions de télécrochet modernes, dans les années 2000, Des années après leur passage, que reste-t-il de leur notoriété ? Comment ces candidats ont-ils vécu leur soudaine médiatisation ? Quels rapports entretenaient-ils avec les autres participants et les membres du jury, souvent intransigeants ?","srcP":"https://programme-tv.vini.pf/sites/default/files/img-icones/6e64cfbc55c1f4cbd11e3011401403d4dc08c6d2.jpg","urlP":"/les-docs-du-week-end-20112021-1510","width":25,"active":false,"progression":0,"test":0,"nowphp":1637509998},{"nidP":"24162438","categorieP":"Magazine","categorieTIDP":"1033","episodeP":"-1","titreP":"50mn Inside","heureP":"16:30","timestampDeb":1637461800,"timestampFin":1637466300,"altP":"","titleP":"","legendeP":"L'actu","desc":"50'INSIDE, c'est toute l'actualité des stars résumée, chaque samedi, Le rendez-vous glamour pour retrouver toujours,,","srcP":"https://programme-tv.vini.pf/sites/default/files/img-icones/3d7e252312dacb5fb7a1a786fa0022ca1be15499.jpg","urlP":"/50mn-inside-20112021-1630","width":62.5,"active":false,"progression":0,"test":0,"nowphp":1637509998}]}]} \ No newline at end of file diff --git a/sites/programme-tv.vini.pf/__data__/content_2.json b/sites/programme-tv.vini.pf/__data__/content_2.json new file mode 100644 index 000000000..874680b9a --- /dev/null +++ b/sites/programme-tv.vini.pf/__data__/content_2.json @@ -0,0 +1 @@ +{"programmes":[{"nid":"8857261","src":"https://programme-tv.vini.pf/sites/default/files/img-icones/192.png","alt":"","title":"","url":"/tf1","programmes":[]}]} \ No newline at end of file diff --git a/sites/programme-tv.vini.pf/__data__/no_content.json b/sites/programme-tv.vini.pf/__data__/no_content.json new file mode 100644 index 000000000..874680b9a --- /dev/null +++ b/sites/programme-tv.vini.pf/__data__/no_content.json @@ -0,0 +1 @@ +{"programmes":[{"nid":"8857261","src":"https://programme-tv.vini.pf/sites/default/files/img-icones/192.png","alt":"","title":"","url":"/tf1","programmes":[]}]} \ No newline at end of file diff --git a/sites/programme-tv.vini.pf/programme-tv.vini.pf.test.js b/sites/programme-tv.vini.pf/programme-tv.vini.pf.test.js index 013682c90..ccbb8e416 100644 --- a/sites/programme-tv.vini.pf/programme-tv.vini.pf.test.js +++ b/sites/programme-tv.vini.pf/programme-tv.vini.pf.test.js @@ -1,4 +1,6 @@ const { parser, url, request } = require('./programme-tv.vini.pf.config.js') +const fs = require('fs') +const path = require('path') const axios = require('axios') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') @@ -31,21 +33,16 @@ it('can parse response', done => { axios.post.mockImplementation((url, data) => { if (data.dateDebut === '2021-11-20T16:00:00-10:00') { return Promise.resolve({ - data: Buffer.from( - '{"programmes":[{"nid":"8857261","src":"https://programme-tv.vini.pf/sites/default/files/img-icones/192.png","alt":"","title":"","url":"/tf1","programmes":[{"nidP":"24162437","categorieP":"Magazine","categorieTIDP":"1033","episodeP":"-1","titreP":"Les docs du week-end","heureP":"15:10","timestampDeb":1637457000,"timestampFin":1637461800,"altP":"","titleP":"","legendeP":"Que sont-ils devenus ? L\'incroyable destin des stars des émissions de télécrochet","desc":"Un documentaire français réalisé en 2019, Cindy Sander, Myriam Abel, Mario, Michal ou encore Magali Vaé ont fait les grandes heures des premières émissions de télécrochet modernes, dans les années 2000, Des années après leur passage, que reste-t-il de leur notoriété ? Comment ces candidats ont-ils vécu leur soudaine médiatisation ? Quels rapports entretenaient-ils avec les autres participants et les membres du jury, souvent intransigeants ?","srcP":"https://programme-tv.vini.pf/sites/default/files/img-icones/6e64cfbc55c1f4cbd11e3011401403d4dc08c6d2.jpg","urlP":"/les-docs-du-week-end-20112021-1510","width":25,"active":false,"progression":0,"test":0,"nowphp":1637509998},{"nidP":"24162438","categorieP":"Magazine","categorieTIDP":"1033","episodeP":"-1","titreP":"50mn Inside","heureP":"16:30","timestampDeb":1637461800,"timestampFin":1637466300,"altP":"","titleP":"","legendeP":"L\'actu","desc":"50\'INSIDE, c\'est toute l\'actualité des stars résumée, chaque samedi, Le rendez-vous glamour pour retrouver toujours,,","srcP":"https://programme-tv.vini.pf/sites/default/files/img-icones/3d7e252312dacb5fb7a1a786fa0022ca1be15499.jpg","urlP":"/50mn-inside-20112021-1630","width":62.5,"active":false,"progression":0,"test":0,"nowphp":1637509998}]}]}' - ) + data: Buffer.from(fs.readFileSync(path.resolve(__dirname, '__data__/content_1.json'))) }) } else { return Promise.resolve({ - data: Buffer.from( - '{"programmes":[{"nid":"8857261","src":"https://programme-tv.vini.pf/sites/default/files/img-icones/192.png","alt":"","title":"","url":"/tf1","programmes":[]}]}' - ) - }) - } - }) + data: Buffer.from(fs.readFileSync(path.resolve(__dirname, '__data__/content_2.json'))) + }) + } +}) - const content = - '{"programmes":[{"nid":"8857261","src":"https://programme-tv.vini.pf/sites/default/files/img-icones/192.png","alt":"","title":"","url":"/tf1","programmes":[{"nidP":"24162436","categorieP":"Magazine","categorieTIDP":"1033","episodeP":"-1","titreP":"Reportages découverte","heureP":"13:50","timestampDeb":1637452200,"timestampFin":1637457000,"altP":"","titleP":"","legendeP":"La coloc ne connaît pas la crise","desc":"Pour faire face à la crise du logement, aux loyers toujours plus élevés, à la solitude ou pour les gardes d\'enfants, les colocations ont le vent en poupe, Pour mieux comprendre ce nouveau phénomène, une équipe a partagé le quotidien de quatre foyers : une retraitée qui héberge des étudiants, des mamans solos, enceintes, qui partagent un appartement associatif, trois générations de la même famille sur un domaine viticole et une étudiante qui intègre une colocation XXL.","srcP":"https://programme-tv.vini.pf/sites/default/files/img-icones/52ada51ed86b7e7bc11eaee83ff2192785989d77.jpg","urlP":"/reportages-decouverte-20112021-1350","width":58.333333333333,"active":false,"progression":0,"test":0,"nowphp":1637509179},{"nidP":"24162437","categorieP":"Magazine","categorieTIDP":"1033","episodeP":"-1","titreP":"Les docs du week-end","heureP":"15:10","timestampDeb":1637457000,"timestampFin":1637461800,"altP":"","titleP":"","legendeP":"Que sont-ils devenus ? L\'incroyable destin des stars des émissions de télécrochet","desc":"Un documentaire français réalisé en 2019, Cindy Sander, Myriam Abel, Mario, Michal ou encore Magali Vaé ont fait les grandes heures des premières émissions de télécrochet modernes, dans les années 2000, Des années après leur passage, que reste-t-il de leur notoriété ? Comment ces candidats ont-ils vécu leur soudaine médiatisation ? Quels rapports entretenaient-ils avec les autres participants et les membres du jury, souvent intransigeants ?","srcP":"https://programme-tv.vini.pf/sites/default/files/img-icones/6e64cfbc55c1f4cbd11e3011401403d4dc08c6d2.jpg","urlP":"/les-docs-du-week-end-20112021-1510","width":41.666666666667,"active":false,"progression":0,"test":0,"nowphp":1637509179}]}]}' + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) parser({ content, channel, date }) .then(result => { @@ -98,7 +95,7 @@ it('can handle empty guide', done => { date, channel, content: - '{"programmes":[{"nid":"8857261","src":"https://programme-tv.vini.pf/sites/default/files/img-icones/192.png","alt":"","title":"","url":"/tf1","programmes":[]}]}' + '' }) .then(result => { expect(result).toMatchObject([]) diff --git a/sites/programtv.onet.pl/__data__/content.html b/sites/programtv.onet.pl/__data__/content.html new file mode 100644 index 000000000..fa2d325ba --- /dev/null +++ b/sites/programtv.onet.pl/__data__/content.html @@ -0,0 +1 @@ +
    13th Street
    • 03:20
      Law & Order, odc. 15: Letzte Worte Krimiserie

      Bei einer Reality-TV-Show stirbt einer der Teilnehmer. Zunächst tappen Briscoe (Jerry Orbach) und Green (Jesse L....

    • 23:30
      Navy CIS, odc. 1: New Orleans Krimiserie

      Der Abgeordnete Dan McLane, ein ehemaliger Vorgesetzter von Gibbs, wird in New Orleans ermordet. In den 90er Jahren...

    • 01:00
      Navy CIS: L.A, odc. 13: High Society Krimiserie

      Die Zahl der Drogentoten ist gestiegen. Das Team des NCIS glaubt, dass sich Terroristen durch den zunehmenden...

    \ No newline at end of file diff --git a/sites/programtv.onet.pl/__data__/no_content.html b/sites/programtv.onet.pl/__data__/no_content.html new file mode 100644 index 000000000..6fedfd4c7 --- /dev/null +++ b/sites/programtv.onet.pl/__data__/no_content.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sites/programtv.onet.pl/programtv.onet.pl.test.js b/sites/programtv.onet.pl/programtv.onet.pl.test.js index 68f3a34ae..3cfca2bfe 100644 --- a/sites/programtv.onet.pl/programtv.onet.pl.test.js +++ b/sites/programtv.onet.pl/programtv.onet.pl.test.js @@ -1,5 +1,7 @@ const MockDate = require('mockdate') const { parser, url } = require('./programtv.onet.pl.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -11,8 +13,6 @@ const channel = { site_id: '13th-street-250', xmltv_id: '13thStreet.de' } -const content = - '
    13th Street
    • 03:20
      Law & Order, odc. 15: Letzte Worte Krimiserie

      Bei einer Reality-TV-Show stirbt einer der Teilnehmer. Zunächst tappen Briscoe (Jerry Orbach) und Green (Jesse L....

    • 23:30
      Navy CIS, odc. 1: New Orleans Krimiserie

      Der Abgeordnete Dan McLane, ein ehemaliger Vorgesetzter von Gibbs, wird in New Orleans ermordet. In den 90er Jahren...

    • 01:00
      Navy CIS: L.A, odc. 13: High Society Krimiserie

      Die Zahl der Drogentoten ist gestiegen. Das Team des NCIS glaubt, dass sich Terroristen durch den zunehmenden...

    ' it('can generate valid url', () => { MockDate.set(dayjs.utc('2021-11-24', 'YYYY-MM-DD').startOf('d')) @@ -31,6 +31,7 @@ it('can generate valid url for next day', () => { }) it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) const result = parser({ content, date }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() @@ -69,7 +70,7 @@ it('can handle empty guide', () => { const result = parser({ date, channel, - content: '' + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) }) expect(result).toMatchObject([]) }) diff --git a/sites/raiplay.it/__data__/content.json b/sites/raiplay.it/__data__/content.json new file mode 100644 index 000000000..95d30306f --- /dev/null +++ b/sites/raiplay.it/__data__/content.json @@ -0,0 +1 @@ +{ "id": "Page-e120a813-1b92-4057-a214-15943d95aa68", "title": "Pagina Palinsesto", "channel": "Rai 2", "date": "03-05-2022", "events": [ { "id": "ContentItem-2f81030d-803b-456a-9ea5-40233234fd9d", "name": "The Good Doctor S3E5 - La prima volta", "episode_title": "La prima volta", "episode": "5", "season": "3", "description": "Shaun affronta il suo primo intervento. Il caso si rivela complicato e, nonostante Shaun abbia un'idea geniale, sarà Andrews a portare a termine l'operazione.", "channel": "Rai 2", "date": "03/05/2022", "hour": "19:40", "duration": "00:50:00", "duration_in_minutes": "50 min", "path_id": "", "weblink": "", "event_weblink": "/dirette/rai2/The-Good-Doctor-S3E5---La-prima-volta-2f81030d-803b-456a-9ea5-40233234fd9d.html", "has_video": false, "image": "/dl/img/2020/03/09/1583748471860_dddddd.jpg", "playlist_id": "11430689", "program": { "name": "The Good Doctor", "path_id": "/programmi/thegooddoctor.json", "info_url": "/programmi/info/757edeac-6fff-4dea-afcd-0bcb39f9ea83.json", "weblink": "/programmi/thegooddoctor" } } ], "track_info": { "id": "", "domain": "raiplay", "platform": "[platform]", "media_type": "", "page_type": "", "editor": "raiplay", "year": "2019", "edit_year": "", "section": "guida tv", "sub_section": "rai 2", "content": "guida tv", "title": "", "channel": "", "date": "2019-09-08", "typology": "", "genres": [], "sub_genres": [], "program_title": "", "program_typology": "", "program_genres": [], "program_sub_genres": [], "edition": "", "season": "", "episode_number": "", "episode_title": "", "form": "", "listaDateMo": [], "dfp": {} }} \ No newline at end of file diff --git a/sites/raiplay.it/__data__/no_content.json b/sites/raiplay.it/__data__/no_content.json new file mode 100644 index 000000000..33ba57bf0 --- /dev/null +++ b/sites/raiplay.it/__data__/no_content.json @@ -0,0 +1 @@ +{"events":[],"total":0} \ No newline at end of file diff --git a/sites/raiplay.it/raiplay.it.test.js b/sites/raiplay.it/raiplay.it.test.js index 057ca8bfe..167008634 100644 --- a/sites/raiplay.it/raiplay.it.test.js +++ b/sites/raiplay.it/raiplay.it.test.js @@ -1,6 +1,6 @@ -// npm run grab --- --site=raiplay.it - const { parser, url } = require('./raiplay.it.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -18,8 +18,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - const content = - '{ "id": "Page-e120a813-1b92-4057-a214-15943d95aa68", "title": "Pagina Palinsesto", "channel": "Rai 2", "date": "03-05-2022", "events": [ { "id": "ContentItem-2f81030d-803b-456a-9ea5-40233234fd9d", "name": "The Good Doctor S3E5 - La prima volta", "episode_title": "La prima volta", "episode": "5", "season": "3", "description": "Shaun affronta il suo primo intervento. Il caso si rivela complicato e, nonostante Shaun abbia un\'idea geniale, sarà Andrews a portare a termine l\'operazione.", "channel": "Rai 2", "date": "03/05/2022", "hour": "19:40", "duration": "00:50:00", "duration_in_minutes": "50 min", "path_id": "", "weblink": "", "event_weblink": "/dirette/rai2/The-Good-Doctor-S3E5---La-prima-volta-2f81030d-803b-456a-9ea5-40233234fd9d.html", "has_video": false, "image": "/dl/img/2020/03/09/1583748471860_dddddd.jpg", "playlist_id": "11430689", "program": { "name": "The Good Doctor", "path_id": "/programmi/thegooddoctor.json", "info_url": "/programmi/info/757edeac-6fff-4dea-afcd-0bcb39f9ea83.json", "weblink": "/programmi/thegooddoctor" } } ], "track_info": { "id": "", "domain": "raiplay", "platform": "[platform]", "media_type": "", "page_type": "", "editor": "raiplay", "year": "2019", "edit_year": "", "section": "guida tv", "sub_section": "rai 2", "content": "guida tv", "title": "", "channel": "", "date": "2019-09-08", "typology": "", "genres": [], "sub_genres": [], "program_title": "", "program_typology": "", "program_genres": [], "program_sub_genres": [], "edition": "", "season": "", "episode_number": "", "episode_title": "", "form": "", "listaDateMo": [], "dfp": {} }}' + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const result = parser({ content, date }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() @@ -45,7 +44,7 @@ it('can parse response', () => { it('can handle empty guide', () => { const result = parser({ - content: '{"events":[],"total":0}' + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) }) expect(result).toMatchObject([]) }) diff --git a/sites/rikstv.no/__data__/content.json b/sites/rikstv.no/__data__/content.json new file mode 100644 index 000000000..7d2d5cb34 --- /dev/null +++ b/sites/rikstv.no/__data__/content.json @@ -0,0 +1,21 @@ +[ + { + "seriesName": "Vakre og ville Oman", + "name": "Vakre og ville Oman", + "description": "Oman er eit arabisk skattkammer av unike habitat og variert dyreliv. Rev, kvalhai, reptil og skjelpadder er blant skapningane du finn her.", + "season": 1, + "episode": 1, + "genres": [ + "Dokumentar", + "Fakta", + "Natur" + ], + "actors": [ + "Gergana Muskalla" + ], + "director": "Stefania Muller", + "imagePackUri": "https://imageservice.rikstv.no/hash/EC206C374F42287C0BDF850A7D3CB4D3.jpg", + "broadcastedTime": "2025-01-13T23:00:00Z", + "broadcastedTimeEnd": "2025-01-13T23:55:00Z" + } +] \ No newline at end of file diff --git a/sites/rikstv.no/rikstv.no.test.js b/sites/rikstv.no/rikstv.no.test.js index ce0f09595..0c79c07ce 100644 --- a/sites/rikstv.no/rikstv.no.test.js +++ b/sites/rikstv.no/rikstv.no.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./rikstv.no.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -22,23 +24,7 @@ describe('rikstv.no Module Tests', () => { }) it('can parse response', () => { - const content = JSON.stringify([ - { - seriesName: 'Vakre og ville Oman', - name: 'Vakre og ville Oman', - description: - 'Oman er eit arabisk skattkammer av unike habitat og variert dyreliv. Rev, kvalhai, reptil og skjelpadder er blant skapningane du finn her.', - season: 1, - episode: 1, - genres: ['Dokumentar', 'Fakta', 'Natur'], - actors: ['Gergana Muskalla'], - director: 'Stefania Muller', - imagePackUri: 'https://imageservice.rikstv.no/hash/EC206C374F42287C0BDF850A7D3CB4D3.jpg', - broadcastedTime: '2025-01-13T23:00:00Z', - broadcastedTimeEnd: '2025-01-13T23:55:00Z' - } - ]) - + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const result = parser({ content }).map(p => { p.start = dayjs(p.start).toISOString() p.stop = dayjs(p.stop).toISOString() diff --git a/sites/rtmklik.rtm.gov.my/__data__/content.json b/sites/rtmklik.rtm.gov.my/__data__/content.json new file mode 100644 index 000000000..9ed7c3294 --- /dev/null +++ b/sites/rtmklik.rtm.gov.my/__data__/content.json @@ -0,0 +1 @@ +{"id":2,"channel":"TV2","channelId":"2","image":"/live_channel/tv2_Trans.png","idAuthor":9,"type":"TV","timezone":8,"dateCreated":"2022-07-08T01:22:33.233","dateModified":"2022-07-21T21:58:39.77","itemCount":30,"prev":"https://rtm-admin.glueapi.io/v3/epg/2/ChannelSchedule?dateStart=2022-09-03&dateEnd=2022-09-03","next":"https://rtm-admin.glueapi.io/v3/epg/2/ChannelSchedule?dateStart=2022-09-05&dateEnd=2022-09-05","schedule":[{"idEPGProgramSchedule":109303,"dateTimeStart":"2022-09-04T19:00:00","dateTimeEnd":"2022-09-04T20:00:00","scheduleProgramTitle":"Hope Of Life","scheduleProgramDescription":"Kisah kehidupan 3 pakar bedah yang berbeza status dan latar belakang, namun mereka komited dengan kerjaya mereka sebagai doktor. Lakonan : Johnson Low, Elvis Chin, Mayjune Tan, Kelvin Liew, Jacky Kam dan Katrina Ho.","scheduleEpisodeNumber":0,"scheduleSeries":0,"duration":3600,"idEPGProgram":3603,"programTitle":"Hope Of Life","description":"Kisah kehidupan 3 pakar bedah yang berbeza status dan latar belakang, namun mereka komited dengan kerjaya mereka sebagai doktor. Lakonan : Johnson Low, Elvis Chin, Mayjune Tan, Kelvin Liew, Jacky Kam dan Katrina Ho.","episodeNumber":0,"series":0,"repeat":"Never","dateModified":"2022-08-29T02:14:56.647","dateCreated":"0001-01-01T00:00:00"}]} \ No newline at end of file diff --git a/sites/rtmklik.rtm.gov.my/__data__/no_content.json b/sites/rtmklik.rtm.gov.my/__data__/no_content.json new file mode 100644 index 000000000..908d1528b --- /dev/null +++ b/sites/rtmklik.rtm.gov.my/__data__/no_content.json @@ -0,0 +1 @@ +{"id":2,"channel":"TV2","channelId":"2","schedule":[]} \ No newline at end of file diff --git a/sites/rtmklik.rtm.gov.my/rtmklik.rtm.gov.my.test.js b/sites/rtmklik.rtm.gov.my/rtmklik.rtm.gov.my.test.js index ee59eca26..095868359 100644 --- a/sites/rtmklik.rtm.gov.my/rtmklik.rtm.gov.my.test.js +++ b/sites/rtmklik.rtm.gov.my/rtmklik.rtm.gov.my.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./rtmklik.rtm.gov.my.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -18,9 +20,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - const content = - '{"id":2,"channel":"TV2","channelId":"2","image":"/live_channel/tv2_Trans.png","idAuthor":9,"type":"TV","timezone":8,"dateCreated":"2022-07-08T01:22:33.233","dateModified":"2022-07-21T21:58:39.77","itemCount":30,"prev":"https://rtm-admin.glueapi.io/v3/epg/2/ChannelSchedule?dateStart=2022-09-03&dateEnd=2022-09-03","next":"https://rtm-admin.glueapi.io/v3/epg/2/ChannelSchedule?dateStart=2022-09-05&dateEnd=2022-09-05","schedule":[{"idEPGProgramSchedule":109303,"dateTimeStart":"2022-09-04T19:00:00","dateTimeEnd":"2022-09-04T20:00:00","scheduleProgramTitle":"Hope Of Life","scheduleProgramDescription":"Kisah kehidupan 3 pakar bedah yang berbeza status dan latar belakang, namun mereka komited dengan kerjaya mereka sebagai doktor. Lakonan : Johnson Low, Elvis Chin, Mayjune Tan, Kelvin Liew, Jacky Kam dan Katrina Ho.","scheduleEpisodeNumber":0,"scheduleSeries":0,"duration":3600,"idEPGProgram":3603,"programTitle":"Hope Of Life","description":"Kisah kehidupan 3 pakar bedah yang berbeza status dan latar belakang, namun mereka komited dengan kerjaya mereka sebagai doktor. Lakonan : Johnson Low, Elvis Chin, Mayjune Tan, Kelvin Liew, Jacky Kam dan Katrina Ho.","episodeNumber":0,"series":0,"repeat":"Never","dateModified":"2022-08-29T02:14:56.647","dateCreated":"0001-01-01T00:00:00"}]}' - + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const result = parser({ content, channel, date }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() @@ -42,7 +42,7 @@ it('can handle empty guide', () => { const result = parser({ date, channel, - content: '{"id":2,"channel":"TV2","channelId":"2","schedule":[]}' + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) }) expect(result).toMatchObject([]) }) diff --git a/sites/s.mxtv.jp/__data__/content.json b/sites/s.mxtv.jp/__data__/content.json new file mode 100644 index 000000000..c67c06632 --- /dev/null +++ b/sites/s.mxtv.jp/__data__/content.json @@ -0,0 +1 @@ +[{ "Event_id": "0x6a57", "Start_time": "2024年07月27日05時00分00秒", "Duration": "01:00:00", "Event_name": "ヒーリングタイム&ヘッドラインニュース", "Event_text": "ねこの足跡", "Component": "480i 16:9 パンベクトルなし", "Sound": "ステレオ", "Event_detail": ""}] \ No newline at end of file diff --git a/sites/s.mxtv.jp/s.mxtv.jp.test.js b/sites/s.mxtv.jp/s.mxtv.jp.test.js index b6891abca..16fd4bb7b 100644 --- a/sites/s.mxtv.jp/s.mxtv.jp.test.js +++ b/sites/s.mxtv.jp/s.mxtv.jp.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./s.mxtv.jp.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -11,8 +13,6 @@ const channel = { name: 'Tokyo MX2', xmltv_id: 'TokyoMX2.jp' } -const content = - '[{ "Event_id": "0x6a57", "Start_time": "2024年07月27日05時00分00秒", "Duration": "01:00:00", "Event_name": "ヒーリングタイム&ヘッドラインニュース", "Event_text": "ねこの足跡", "Component": "480i 16:9 パンベクトルなし", "Sound": "ステレオ", "Event_detail": ""}]' it('can generate valid url', () => { const result = url({ date, channel }) @@ -20,6 +20,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const result = parser({ date, channel, content }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() diff --git a/sites/shahid.mbc.net/__data__/content.json b/sites/shahid.mbc.net/__data__/content.json new file mode 100644 index 000000000..eb889329e --- /dev/null +++ b/sites/shahid.mbc.net/__data__/content.json @@ -0,0 +1 @@ +{"items":[{"channelId":"996520","items":[{"actualFrom":"2023-11-11T00:00:00.000+00:00","actualTo":"2023-11-11T00:30:00.000+00:00","description":"The presenter reviews the most prominent episodes of news programs produced by the channel's team on a weekly basis, which include the most important global updates and developments at all levels.","duration":null,"emptySlot":false,"episodeNumber":194,"from":"2023-11-11T00:00:00.000+00:00","genres":["TV Show"],"productId":null,"productionYear":null,"productPoster":"https://imagesmbc.whatsonindia.com/dasimages/landscape/1920x1080/F968D4A39DB25793E9EED1BDAFBAD2EA8A8F9B30Z.jpg","productSubType":null,"productType":null,"replay":false,"restritectContent":null,"seasonId":null,"seasonNumber":"1","showId":null,"streamInfo":null,"title":"Menassaatona Fi Osboo'","to":"2023-11-11T00:30:00.000+00:00"}]}]} \ No newline at end of file diff --git a/sites/shahid.mbc.net/shahid.mbc.net.test.js b/sites/shahid.mbc.net/shahid.mbc.net.test.js index 7cc03032e..2effd5f70 100644 --- a/sites/shahid.mbc.net/shahid.mbc.net.test.js +++ b/sites/shahid.mbc.net/shahid.mbc.net.test.js @@ -1,4 +1,6 @@ const { url, parser } = require('./shahid.mbc.net.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') @@ -18,8 +20,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - const content = - '{"items":[{"channelId":"996520","items":[{"actualFrom":"2023-11-11T00:00:00.000+00:00","actualTo":"2023-11-11T00:30:00.000+00:00","description":"The presenter reviews the most prominent episodes of news programs produced by the channel\'s team on a weekly basis, which include the most important global updates and developments at all levels.","duration":null,"emptySlot":false,"episodeNumber":194,"from":"2023-11-11T00:00:00.000+00:00","genres":["TV Show"],"productId":null,"productionYear":null,"productPoster":"https://imagesmbc.whatsonindia.com/dasimages/landscape/1920x1080/F968D4A39DB25793E9EED1BDAFBAD2EA8A8F9B30Z.jpg","productSubType":null,"productType":null,"replay":false,"restritectContent":null,"seasonId":null,"seasonNumber":"1","showId":null,"streamInfo":null,"title":"Menassaatona Fi Osboo\'","to":"2023-11-11T00:30:00.000+00:00"}]}]}' + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const result = parser({ content, channel, date }) expect(result).toMatchObject([ diff --git a/sites/siba.com.co/__data__/content.json b/sites/siba.com.co/__data__/content.json new file mode 100644 index 000000000..eaddd574d --- /dev/null +++ b/sites/siba.com.co/__data__/content.json @@ -0,0 +1 @@ +{"list":[{"id":"395","nom":"CANAL CLARO","num":"102","logo":"7c4b9e8566a6e867d1db4c7ce845f1f4.jpg","cat":"Exclusivos Claro","prog":[{"id":"665724465","nom":"Worst Cooks In America","ini":1636588800,"fin":1636592400}]}],"error":null} \ No newline at end of file diff --git a/sites/siba.com.co/__data__/no_content.json b/sites/siba.com.co/__data__/no_content.json new file mode 100644 index 000000000..978507677 --- /dev/null +++ b/sites/siba.com.co/__data__/no_content.json @@ -0,0 +1 @@ +{"list":[],"error":null} \ No newline at end of file diff --git a/sites/siba.com.co/siba.com.co.test.js b/sites/siba.com.co/siba.com.co.test.js index d63a03462..12d2a5711 100644 --- a/sites/siba.com.co/siba.com.co.test.js +++ b/sites/siba.com.co/siba.com.co.test.js @@ -1,4 +1,6 @@ const { parser, url, request } = require('./siba.com.co.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -10,8 +12,6 @@ const channel = { site_id: '395', xmltv_id: 'CanalClaro.cl' } -const content = - '{"list":[{"id":"395","nom":"CANAL CLARO","num":"102","logo":"7c4b9e8566a6e867d1db4c7ce845f1f4.jpg","cat":"Exclusivos Claro","prog":[{"id":"665724465","nom":"Worst Cooks In America","ini":1636588800,"fin":1636592400}]}],"error":null}' it('can generate valid url', () => { expect(url).toBe('http://devportal.siba.com.co/index.php?action=grilla') @@ -32,6 +32,7 @@ it('can generate valid request data', () => { }) it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const result = parser({ date, channel, content }) expect(result).toMatchObject([ { @@ -46,7 +47,8 @@ it('can handle empty guide', () => { const result = parser({ date, channel, - content: '{"list":[],"error":null}' + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + }) expect(result).toMatchObject([]) }) diff --git a/sites/sky.de/__data__/content.json b/sites/sky.de/__data__/content.json new file mode 100644 index 000000000..89a208b09 --- /dev/null +++ b/sites/sky.de/__data__/content.json @@ -0,0 +1 @@ +{"cl":[{"ci":522,"el":[{"ei":122309300,"bsdt":1645916700000,"bst":"00:05","bedt":1645918200000,"len":25,"et":"King of Queens","ec":"Comedyserie","cop":"USA","yop":2001,"fsk":"ab 0 Jahre","epit":"Der Experte","sn":"4","en":"11","pu":"/static/img/program_guide/1522936_s.jpg"},{"ei":122309301,"bsdt":1645918200000,"bst":"00:30","bedt":1645919700000,"len":25,"et":"King of Queens","ec":"Comedyserie","cop":"USA","yop":2001,"fsk":"ab 0 Jahre","epit":"Speedy Gonzales","sn":"4","en":"12","pu":"/static/img/program_guide/1522937_s.jpg"}]}]} \ No newline at end of file diff --git a/sites/sky.de/sky.de.test.js b/sites/sky.de/sky.de.test.js index 248359190..bdcb8a15d 100644 --- a/sites/sky.de/sky.de.test.js +++ b/sites/sky.de/sky.de.test.js @@ -1,4 +1,6 @@ const { parser, url, request } = require('./sky.de.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') dayjs.extend(utc) @@ -9,9 +11,6 @@ const channel = { xmltv_id: 'WarnerTVComedyHD.de' } -const content = - '{"cl":[{"ci":522,"el":[{"ei":122309300,"bsdt":1645916700000,"bst":"00:05","bedt":1645918200000,"len":25,"et":"King of Queens","ec":"Comedyserie","cop":"USA","yop":2001,"fsk":"ab 0 Jahre","epit":"Der Experte","sn":"4","en":"11","pu":"/static/img/program_guide/1522936_s.jpg"},{"ei":122309301,"bsdt":1645918200000,"bst":"00:30","bedt":1645919700000,"len":25,"et":"King of Queens","ec":"Comedyserie","cop":"USA","yop":2001,"fsk":"ab 0 Jahre","epit":"Speedy Gonzales","sn":"4","en":"12","pu":"/static/img/program_guide/1522937_s.jpg"}]}]}' - it('can generate valid url', () => { expect(url).toBe('https://www.sky.de/sgtvg/service/getBroadcastsForGrid') }) @@ -28,6 +27,7 @@ it('can generate valid request data', () => { }) it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const result = parser({ content, channel }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() diff --git a/sites/stod2.is/__data__/content.json b/sites/stod2.is/__data__/content.json new file mode 100644 index 000000000..a08167b2b --- /dev/null +++ b/sites/stod2.is/__data__/content.json @@ -0,0 +1,34 @@ +[ + { + "midill": "STOD2", + "midill_heiti": "Stöð 2", + "dagsetning": "2025-01-03T00:00:00Z", + "upphaf": "2025-01-03T08:00:00Z", + "titill": "Telma Borgþórsdóttir", + "isltitill": "Heimsókn", + "undirtitill": "Telma Borgþórsdóttir", + "seria": 8, + "thattur": 5, + "thattafjoldi": 10, + "birta_thatt": 1, + "opin": 0, + "beint": 0, + "frumsyning": 0, + "framundan_i_beinni": 0, + "tegund": "SER", + "flokkur": "Icelandic", + "adalhlutverk": "", + "leikstjori": "", + "ar": "2019", + "bannad": "Green", + "recidefni": 592645105, + "recidlidur": 592645184, + "recidsyning": null, + "refno": null, + "frelsi": 0, + "netdagar": 0, + "lysing": "Frábærir þættir með Sindra Sindrasyni sem lítur inn hjá íslenskum fagurkerum. Heimilin eru jafn ólík og þau eru mörg en eiga það þó eitt sameiginlegt að vera sett saman af alúð og smekklegheitum. Sindri hefur líka einstakt lag á að ná fram því besta í viðmælendum sínum.", + "slott": 15, + "slotlengd": "00:15" + } +] \ No newline at end of file diff --git a/sites/stod2.is/stod2.is.test.js b/sites/stod2.is/stod2.is.test.js index 4dba91506..c8ff849dc 100644 --- a/sites/stod2.is/stod2.is.test.js +++ b/sites/stod2.is/stod2.is.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./stod2.is.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -11,49 +13,13 @@ dayjs.extend(timezone) const date = dayjs.utc('2025-01-03', 'YYYY-MM-DD').startOf('day') const channel = { site_id: 'stod2', xmltv_id: 'Stod2.is' } -const mockEpgData = JSON.stringify([ - { - midill: 'STOD2', - midill_heiti: 'Stöð 2', - dagsetning: '2025-01-03T00:00:00Z', - upphaf: '2025-01-03T08:00:00Z', - titill: 'Telma Borgþórsdóttir', - isltitill: 'Heimsókn', - undirtitill: 'Telma Borgþórsdóttir', - seria: 8, - thattur: 5, - thattafjoldi: 10, - birta_thatt: 1, - opin: 0, - beint: 0, - frumsyning: 0, - framundan_i_beinni: 0, - tegund: 'SER', - flokkur: 'Icelandic', - adalhlutverk: '', - leikstjori: '', - ar: '2019', - bannad: 'Green', - recidefni: 592645105, - recidlidur: 592645184, - recidsyning: null, - refno: null, - frelsi: 0, - netdagar: 0, - lysing: - 'Frábærir þættir með Sindra Sindrasyni sem lítur inn hjá íslenskum fagurkerum. Heimilin eru jafn ólík og þau eru mörg en eiga það þó eitt sameiginlegt að vera sett saman af alúð og smekklegheitum. Sindri hefur líka einstakt lag á að ná fram því besta í viðmælendum sínum.', - slott: 15, - slotlengd: '00:15' - } -]) - it('can generate valid url', () => { const generatedUrl = url({ date, channel }) expect(generatedUrl).toBe('https://api.stod2.is/dagskra/api/stod2/2025-01-03') }) it('can parse response', () => { - const content = mockEpgData + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const result = parser({ content }).map(p => { p.start = p.start.toISOString() p.stop = p.stop.toISOString() diff --git a/sites/taiwanplus.com/__data__/content.json b/sites/taiwanplus.com/__data__/content.json new file mode 100644 index 000000000..021decf94 --- /dev/null +++ b/sites/taiwanplus.com/__data__/content.json @@ -0,0 +1 @@ +{"data":[{"date":"2023/08/20","weekday":"SUN","schedule":[{"programId":30668,"dateTime":"2023/08/20 00:00","time":"00:00","image":"https://prod-img.taiwanplus.com/live-schedule/Single/S30668_20230810104937.webp","title":"Master Class","shortDescription":"From blockchain to Buddha statues, Taiwan’s culture is a kaleidoscope of old and new just waiting to be discovered.","description":"From blockchain to Buddha statues, Taiwan’s culture is a kaleidoscope of old and new just waiting to be discovered.","ageRating":"0+","programWebSiteType":"4","url":"","vodId":null,"categoryId":90000474,"categoryType":2,"categoryName":"TaiwanPlus ✕ Discovery","categoryFullPath":"Originals/TaiwanPlus ✕ Discovery","encodedCategoryFullPath":"originals/taiwanplus-discovery"}]}],"success":true,"code":"0000","message":""} \ No newline at end of file diff --git a/sites/taiwanplus.com/taiwanplus.com.test.js b/sites/taiwanplus.com/taiwanplus.com.test.js index 57784598a..6ca257976 100644 --- a/sites/taiwanplus.com/taiwanplus.com.test.js +++ b/sites/taiwanplus.com/taiwanplus.com.test.js @@ -1,4 +1,6 @@ const { url, parser } = require('./taiwanplus.com.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') dayjs.extend(utc) @@ -16,8 +18,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - const content = - '{"data":[{"date":"2023/08/20","weekday":"SUN","schedule":[{"programId":30668,"dateTime":"2023/08/20 00:00","time":"00:00","image":"https://prod-img.taiwanplus.com/live-schedule/Single/S30668_20230810104937.webp","title":"Master Class","shortDescription":"From blockchain to Buddha statues, Taiwan’s culture is a kaleidoscope of old and new just waiting to be discovered.","description":"From blockchain to Buddha statues, Taiwan’s culture is a kaleidoscope of old and new just waiting to be discovered.","ageRating":"0+","programWebSiteType":"4","url":"","vodId":null,"categoryId":90000474,"categoryType":2,"categoryName":"TaiwanPlus ✕ Discovery","categoryFullPath":"Originals/TaiwanPlus ✕ Discovery","encodedCategoryFullPath":"originals/taiwanplus-discovery"}]}],"success":true,"code":"0000","message":""}' + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const results = parser({ content, date }) diff --git a/sites/tapdmv.com/__data__/content.json b/sites/tapdmv.com/__data__/content.json new file mode 100644 index 000000000..989997a52 --- /dev/null +++ b/sites/tapdmv.com/__data__/content.json @@ -0,0 +1 @@ +[{"id":"0afc3cc0-eab8-4960-a8b5-55d76edeb8f0","program":"The Bourne Ultimatum","episode":"The Bourne Ultimatum","description":"Jason Bourne dodges a ruthless C.I.A. official and his Agents from a new assassination program while searching for the origins of his life as a trained killer.","genre":"Action","thumbnailImage":"https://s3.ap-southeast-1.amazonaws.com/epg.tapdmv.com/tapactionflix.png","startTime":"2022-10-03T23:05:00.000Z","endTime":"2022-10-04T01:00:00.000Z","fileId":"94b7db9b-5bbd-47d3-a2d3-ce792342a756","createdAt":"2022-09-30T13:02:10.586Z","updatedAt":"2022-09-30T13:02:10.586Z"},{"id":"8dccd5e0-ab88-44b6-a2af-18d31c6e9ed7","program":"The Devil Inside ","episode":"The Devil Inside ","description":"In Italy, a woman becomes involved in a series of unauthorized exorcisms during her mission to discover what happened to her mother, who allegedly murdered three people during her own exorcism.","genre":"Horror","thumbnailImage":"https://s3.ap-southeast-1.amazonaws.com/epg.tapdmv.com/tapactionflix.png","startTime":"2022-10-04T01:00:00.000Z","endTime":"2022-10-04T02:25:00.000Z","fileId":"94b7db9b-5bbd-47d3-a2d3-ce792342a756","createdAt":"2022-09-30T13:02:24.031Z","updatedAt":"2022-09-30T13:02:24.031Z"}] \ No newline at end of file diff --git a/sites/tapdmv.com/__data__/no_content.json b/sites/tapdmv.com/__data__/no_content.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/sites/tapdmv.com/__data__/no_content.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/sites/tapdmv.com/tapdmv.com.test.js b/sites/tapdmv.com/tapdmv.com.test.js index be9d8d03a..55e117723 100644 --- a/sites/tapdmv.com/tapdmv.com.test.js +++ b/sites/tapdmv.com/tapdmv.com.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./tapdmv.com.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -18,8 +20,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - const content = - '[{"id":"0afc3cc0-eab8-4960-a8b5-55d76edeb8f0","program":"The Bourne Ultimatum","episode":"The Bourne Ultimatum","description":"Jason Bourne dodges a ruthless C.I.A. official and his Agents from a new assassination program while searching for the origins of his life as a trained killer.","genre":"Action","thumbnailImage":"https://s3.ap-southeast-1.amazonaws.com/epg.tapdmv.com/tapactionflix.png","startTime":"2022-10-03T23:05:00.000Z","endTime":"2022-10-04T01:00:00.000Z","fileId":"94b7db9b-5bbd-47d3-a2d3-ce792342a756","createdAt":"2022-09-30T13:02:10.586Z","updatedAt":"2022-09-30T13:02:10.586Z"},{"id":"8dccd5e0-ab88-44b6-a2af-18d31c6e9ed7","program":"The Devil Inside ","episode":"The Devil Inside ","description":"In Italy, a woman becomes involved in a series of unauthorized exorcisms during her mission to discover what happened to her mother, who allegedly murdered three people during her own exorcism.","genre":"Horror","thumbnailImage":"https://s3.ap-southeast-1.amazonaws.com/epg.tapdmv.com/tapactionflix.png","startTime":"2022-10-04T01:00:00.000Z","endTime":"2022-10-04T02:25:00.000Z","fileId":"94b7db9b-5bbd-47d3-a2d3-ce792342a756","createdAt":"2022-09-30T13:02:24.031Z","updatedAt":"2022-09-30T13:02:24.031Z"}]' + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const result = parser({ content, date }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() @@ -41,7 +42,7 @@ it('can parse response', () => { it('can handle empty guide', () => { const result = parser({ - content: '[]', + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')), date }) expect(result).toMatchObject([]) diff --git a/sites/tataplay.com/__data__/content.json b/sites/tataplay.com/__data__/content.json new file mode 100644 index 000000000..07b9a7b21 --- /dev/null +++ b/sites/tataplay.com/__data__/content.json @@ -0,0 +1,22 @@ +{ + "data": { + "epg": [ + { + "title": "Yeh Rishta Kya Kehlata Hai", + "startTime": "2025-06-09T18:00:00.000Z", + "endTime": "2025-06-09T18:30:00.000Z", + "desc": "The story of the Rajshri family and their journey through life.", + "category": "Drama", + "boxCoverImage": "https://img.tataplay.com/thumbnails/1001/yeh-rishta.jpg" + }, + { + "title": "Anupamaa", + "startTime": "2025-06-09T18:30:00.000Z", + "endTime": "2025-06-09T19:00:00.000Z", + "desc": "The story of Anupamaa, a housewife who rediscovers herself.", + "category": "Drama", + "boxCoverImage": "https://img.tataplay.com/thumbnails/1001/anupamaa.jpg" + } + ] + } +} \ No newline at end of file diff --git a/sites/tataplay.com/tataplay.com.test.js b/sites/tataplay.com/tataplay.com.test.js index 5262f6cd4..b7adff1ce 100644 --- a/sites/tataplay.com/tataplay.com.test.js +++ b/sites/tataplay.com/tataplay.com.test.js @@ -1,4 +1,6 @@ const { parser, url, channels } = require('./tataplay.com.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -13,28 +15,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - const content = JSON.stringify({ - data: { - epg: [ - { - title: 'Yeh Rishta Kya Kehlata Hai', - startTime: '2025-06-09T18:00:00.000Z', - endTime: '2025-06-09T18:30:00.000Z', - desc: 'The story of the Rajshri family and their journey through life.', - category: 'Drama', - boxCoverImage: 'https://img.tataplay.com/thumbnails/1001/yeh-rishta.jpg' - }, - { - title: 'Anupamaa', - startTime: '2025-06-09T18:30:00.000Z', - endTime: '2025-06-09T19:00:00.000Z', - desc: 'The story of Anupamaa, a housewife who rediscovers herself.', - category: 'Drama', - boxCoverImage: 'https://img.tataplay.com/thumbnails/1001/anupamaa.jpg' - } - ] - } - }) + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const results = parser({ content, date }) diff --git a/sites/teliatv.ee/__data__/content.json b/sites/teliatv.ee/__data__/content.json new file mode 100644 index 000000000..b65aebbf8 --- /dev/null +++ b/sites/teliatv.ee/__data__/content.json @@ -0,0 +1 @@ +{"categoryItems":{"1":[{"id":136227,"type":"epgSeries","name":"Inimjaht","originalName":"Manhunt","price":null,"owner":"ETV","ownerId":1,"images":{"webGuideItemLarge":"/resized/ri93Qj4OLXXvg7QAsUOcKMnIb3g=/570x330/filters:format(jpeg)/inet-static.mw.elion.ee/epg_images/9/b/17e48b3966e65c02.jpg"},"packetIds":[30,34,38,129,130,162,191,242,243,244,447,483,484,485,486],"related":{"programmeIds":[27224371]}}]},"relations":{"programmes":{"27224371":{"id":27224371,"startAt":"2021-11-20T00:05:00+02:00","endAt":"2021-11-20T00:55:00+02:00","publicTo":"2021-12-04T02:05:00+02:00","status":"default","channelId":1,"broadcastId":78248901,"hasMarkers":false,"catchup":false}}}} \ No newline at end of file diff --git a/sites/teliatv.ee/__data__/no_content.json b/sites/teliatv.ee/__data__/no_content.json new file mode 100644 index 000000000..8ef893c41 --- /dev/null +++ b/sites/teliatv.ee/__data__/no_content.json @@ -0,0 +1 @@ +{"categoryItems":{},"relations":{}} \ No newline at end of file diff --git a/sites/teliatv.ee/teliatv.ee.test.js b/sites/teliatv.ee/teliatv.ee.test.js index 2643046c8..322f9591b 100644 --- a/sites/teliatv.ee/teliatv.ee.test.js +++ b/sites/teliatv.ee/teliatv.ee.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./teliatv.ee.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -30,8 +32,7 @@ it('can generate valid url with different language', () => { }) it('can parse response', () => { - const content = - '{"categoryItems":{"1":[{"id":136227,"type":"epgSeries","name":"Inimjaht","originalName":"Manhunt","price":null,"owner":"ETV","ownerId":1,"images":{"webGuideItemLarge":"/resized/ri93Qj4OLXXvg7QAsUOcKMnIb3g=/570x330/filters:format(jpeg)/inet-static.mw.elion.ee/epg_images/9/b/17e48b3966e65c02.jpg"},"packetIds":[30,34,38,129,130,162,191,242,243,244,447,483,484,485,486],"related":{"programmeIds":[27224371]}}]},"relations":{"programmes":{"27224371":{"id":27224371,"startAt":"2021-11-20T00:05:00+02:00","endAt":"2021-11-20T00:55:00+02:00","publicTo":"2021-12-04T02:05:00+02:00","status":"default","channelId":1,"broadcastId":78248901,"hasMarkers":false,"catchup":false}}}}' + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const result = parser({ content, channel }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() @@ -53,7 +54,7 @@ it('can handle empty guide', () => { const result = parser({ date, channel, - content: '{"categoryItems":{},"relations":{}}' + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) }) expect(result).toMatchObject([]) }) diff --git a/sites/tvim.tv/__data__/content.json b/sites/tvim.tv/__data__/content.json new file mode 100644 index 000000000..a18c491b1 --- /dev/null +++ b/sites/tvim.tv/__data__/content.json @@ -0,0 +1 @@ +{"response":"ok","data":{"thumb":"https://mobile-api.tvim.tv/images/chan_logos/70x25/T7.png","thumb_rel":"https://mobile-api.tvim.tv/images/chan_logos/70x25/T7.png","thumb_large_rel":"https://mobile-api.tvim.tv/images/chan_logos/120x60/T7.png","thumb_http":"http://mobile-api.tvim.tv/images/chan_logos/70x25/T7.png","thumb_large":"http://mobile-api.tvim.tv/images/chan_logos/120x60/T7.png","server_time":1635100951,"catchup_length":2,"_id":"T73","ind":2,"genre":"national","name":"T7","epg_id":"T7","chan":"T7","prog":[{"id":"T7-1635026400","title":"Programi i T7","from":1635026400,"end":1635040800,"starting":"00:00","from_utc":1635026400,"end_utc":1635040800,"desc":"Programi i T7","genre":"test","chan":"T7","epg_id":"T7","eng":""}]}} \ No newline at end of file diff --git a/sites/tvim.tv/__data__/no_content.json b/sites/tvim.tv/__data__/no_content.json new file mode 100644 index 000000000..7033b039e --- /dev/null +++ b/sites/tvim.tv/__data__/no_content.json @@ -0,0 +1 @@ +{"response":"ok","data":{"server_time":1635100927}} \ No newline at end of file diff --git a/sites/tvim.tv/tvim.tv.test.js b/sites/tvim.tv/tvim.tv.test.js index 9232cf386..b7c221cdc 100644 --- a/sites/tvim.tv/tvim.tv.test.js +++ b/sites/tvim.tv/tvim.tv.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./tvim.tv.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -7,8 +9,7 @@ dayjs.extend(utc) const date = dayjs.utc('2021-10-24', 'YYYY-MM-DD').startOf('d') const channel = { site_id: 'T7', xmltv_id: 'T7.rs' } -const content = - '{"response":"ok","data":{"thumb":"https://mobile-api.tvim.tv/images/chan_logos/70x25/T7.png","thumb_rel":"https://mobile-api.tvim.tv/images/chan_logos/70x25/T7.png","thumb_large_rel":"https://mobile-api.tvim.tv/images/chan_logos/120x60/T7.png","thumb_http":"http://mobile-api.tvim.tv/images/chan_logos/70x25/T7.png","thumb_large":"http://mobile-api.tvim.tv/images/chan_logos/120x60/T7.png","server_time":1635100951,"catchup_length":2,"_id":"T73","ind":2,"genre":"national","name":"T7","epg_id":"T7","chan":"T7","prog":[{"id":"T7-1635026400","title":"Programi i T7","from":1635026400,"end":1635040800,"starting":"00:00","from_utc":1635026400,"end_utc":1635040800,"desc":"Programi i T7","genre":"test","chan":"T7","epg_id":"T7","eng":""}]}}' +const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) it('can generate valid url', () => { const result = url({ date, channel }) @@ -34,7 +35,7 @@ it('can handle empty guide', () => { const result = parser({ date, channel, - content: '{"response":"ok","data":{"server_time":1635100927}}' + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) }) expect(result).toMatchObject([]) }) From f6738a9629d95299682ad2576c3abd7071442724 Mon Sep 17 00:00:00 2001 From: theofficialomega <30985701+BellezaEmporium@users.noreply.github.com> Date: Sun, 27 Jul 2025 18:43:59 +0200 Subject: [PATCH 19/32] fix mi.tv image scraping, continue uniformizing tests --- sites/mi.tv/__data__/content.html | 1 + sites/mi.tv/__data__/no_content.html | 1 + sites/mi.tv/mi.tv.config.js | 36 ++++++++-- sites/mi.tv/mi.tv.test.js | 7 +- sites/moji.id/__data__/content.html | 1 + sites/moji.id/moji.id.test.js | 6 +- sites/mysky.com.ph/__data__/content.json | 1 + sites/mysky.com.ph/mysky.com.ph.test.js | 5 +- sites/neo.io/__data__/content.json | 64 +++++++++++++++++ sites/neo.io/__data__/no_content.json | 1 + sites/neo.io/neo.io.test.js | 71 ++----------------- sites/novacyprus.com/__data__/content.json | 1 + sites/novacyprus.com/__data__/no_content.json | 1 + sites/novacyprus.com/novacyprus.com.test.js | 7 +- sites/nowplayer.now.com/__data__/content.json | 1 + .../nowplayer.now.com.test.js | 5 +- sites/ontvtonight.com/__data__/content.html | 1 + .../ontvtonight.com/__data__/no_content.html | 1 + sites/ontvtonight.com/ontvtonight.com.test.js | 7 +- sites/pbsguam.org/__data__/content.html | 5 ++ sites/pbsguam.org/__data__/no_content.html | 1 + sites/pbsguam.org/pbsguam.org.test.js | 10 ++- 22 files changed, 141 insertions(+), 93 deletions(-) create mode 100644 sites/mi.tv/__data__/content.html create mode 100644 sites/mi.tv/__data__/no_content.html create mode 100644 sites/moji.id/__data__/content.html create mode 100644 sites/mysky.com.ph/__data__/content.json create mode 100644 sites/neo.io/__data__/content.json create mode 100644 sites/neo.io/__data__/no_content.json create mode 100644 sites/novacyprus.com/__data__/content.json create mode 100644 sites/novacyprus.com/__data__/no_content.json create mode 100644 sites/nowplayer.now.com/__data__/content.json create mode 100644 sites/ontvtonight.com/__data__/content.html create mode 100644 sites/ontvtonight.com/__data__/no_content.html create mode 100644 sites/pbsguam.org/__data__/content.html create mode 100644 sites/pbsguam.org/__data__/no_content.html diff --git a/sites/mi.tv/__data__/content.html b/sites/mi.tv/__data__/content.html new file mode 100644 index 000000000..920a8d23e --- /dev/null +++ b/sites/mi.tv/__data__/content.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sites/mi.tv/__data__/no_content.html b/sites/mi.tv/__data__/no_content.html new file mode 100644 index 000000000..6fedfd4c7 --- /dev/null +++ b/sites/mi.tv/__data__/no_content.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sites/mi.tv/mi.tv.config.js b/sites/mi.tv/mi.tv.config.js index 4f53f2a92..569259bb1 100644 --- a/sites/mi.tv/mi.tv.config.js +++ b/sites/mi.tv/mi.tv.config.js @@ -102,13 +102,41 @@ function parseDescription($item) { return $item('a > div.content > p.synopsis').text().trim() } -function parseImage($item) { - const backgroundImage = $item('a > div.image-parent > div.image').css('background-image') - const [, image] = backgroundImage.match(/url\('(.*)'\)/) || [null, null] - return image +function parseImage($item) { + const styleAttr = $item('a > div.image-parent > div.image').attr('style') + + if (styleAttr) { + const match = styleAttr.match(/background-image:\s*url\(['"]?(.*?)['"]?\)/) + if (match) { + return cleanUrl(match[1]) + } + } + + const backgroundImage = $item('a > div.image-parent > div.image').css('background-image') + + if (backgroundImage && backgroundImage !== 'none') { + const match = backgroundImage.match(/url\(['"]?(.*?)['"]?\)/) + if (match) { + return cleanUrl(match[1]) + } + } + + return null } +function cleanUrl(url) { + if (!url) return null + + return url + .replace(/^['"`\\]+/, '') + .replace(/['"`\\]+$/, '') + .replace(/\\'/g, "'") + .replace(/\\"/g, '"') + .replace(/\\\\/g, '\\') +} + + function parseItems(content) { const $ = cheerio.load(content) diff --git a/sites/mi.tv/mi.tv.test.js b/sites/mi.tv/mi.tv.test.js index 5bd1a9f3e..ec5652e02 100644 --- a/sites/mi.tv/mi.tv.test.js +++ b/sites/mi.tv/mi.tv.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./mi.tv.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -10,8 +12,6 @@ const channel = { site_id: 'ar#24-7-canal-de-noticias', xmltv_id: '247CanaldeNoticias.ar' } -const content = - '' it('can generate valid url', () => { expect(url({ channel, date })).toBe( @@ -20,6 +20,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') const result = parser({ content, date }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() @@ -60,7 +61,7 @@ it('can handle empty guide', () => { const result = parser({ date, channel, - content: '' + content: '' }) expect(result).toMatchObject([]) }) diff --git a/sites/moji.id/__data__/content.html b/sites/moji.id/__data__/content.html new file mode 100644 index 000000000..1870f9725 --- /dev/null +++ b/sites/moji.id/__data__/content.html @@ -0,0 +1 @@ +

    schedule

    FriAug 18
    SatAug 19
    SunAug 20
    Jam TayangProgram
    00:00TRUST
    Informasi seputar menjaga vitalitas pria
    00:302023 AVC CHALLENGE CUP FOR WOMEN (RECORDED)
    India Vs. Vietnam
    02:30ONE CHAMPIONSHIP 2021
    Siaran laga-laga pertandingan tinju gaya bebas internasional. Meyuguhkan pertarungan sengit dari para petarung profeisional kelas dunia.
    03:30VOLLEYBALL NATION\'S LEAGUE 2023 (RECORDED)
    TURKI vs BRAZIL
    05:00MOJI SPORT
    MOJI SPORT
    06:15LIPUTAN 6 PAGI MOJI
    Kompilasi ragam berita hard news dan soft news baik dari dalam negeri maupun internasional juga info prediksi cuaca di wilayah Indonesia
    07:00UNGKAP
    Liputan investigasi seputar berbagai topik dan peristiwa hangat serta kontroversial yang terjadi di Indonesia
    08:00PIALA KAPOLRI 2023 PUTRI (LIVE)
    PIALA KAPOLRI 2023 PUTRI (LIVE)
    10:30SERIES PAGI
    GANTENG GANTENG SERIGALA
    12:30DIAM-DIAM SUKA
    DIAM-DIAM SUKA
    13:30PIALA KAPOLRI 2023 PUTRA (LIVE)
    PIALA KAPOLRI 2023 PUTRA (LIVE)
    16:00PIALA KAPOLRI 2023 PUTRI (LIVE)
    PIALA KAPOLRI 2023 PUTRI (LIVE)
    18:00PIALA KAPOLRI 2023 PUTRA (LIVE)
    PIALA KAPOLRI 2023 PUTRA (LIVE)
    20:00MOJI DRAMA (CHHOTI SARDARNI)
    CHHOTI SARDARNI
    21:30SINEMA MALAM (BIDADARI CANTIK DI RUMAH KOST)
    (BIDADARI CANTIK DI RUMAH KOST
    23:00TRUST
    Informasi seputar menjaga vitalitas pria
    23:30TRUST
    Informasi seputar menjaga vitalitas pria
    \ No newline at end of file diff --git a/sites/moji.id/moji.id.test.js b/sites/moji.id/moji.id.test.js index 452a70722..7e487374f 100644 --- a/sites/moji.id/moji.id.test.js +++ b/sites/moji.id/moji.id.test.js @@ -1,19 +1,19 @@ const { parser } = require('./moji.id.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') dayjs.extend(utc) const date = dayjs.utc('2023-08-18', 'YYYY-MM-DD').startOf('d') -const content = - '

    schedule

    FriAug 18
    SatAug 19
    SunAug 20
    Jam TayangProgram
    00:00TRUST
    Informasi seputar menjaga vitalitas pria
    00:302023 AVC CHALLENGE CUP FOR WOMEN (RECORDED)
    India Vs. Vietnam
    02:30ONE CHAMPIONSHIP 2021
    Siaran laga-laga pertandingan tinju gaya bebas internasional. Meyuguhkan pertarungan sengit dari para petarung profeisional kelas dunia.
    03:30VOLLEYBALL NATION\'S LEAGUE 2023 (RECORDED)
    TURKI vs BRAZIL
    05:00MOJI SPORT
    MOJI SPORT
    06:15LIPUTAN 6 PAGI MOJI
    Kompilasi ragam berita hard news dan soft news baik dari dalam negeri maupun internasional juga info prediksi cuaca di wilayah Indonesia
    07:00UNGKAP
    Liputan investigasi seputar berbagai topik dan peristiwa hangat serta kontroversial yang terjadi di Indonesia
    08:00PIALA KAPOLRI 2023 PUTRI (LIVE)
    PIALA KAPOLRI 2023 PUTRI (LIVE)
    10:30SERIES PAGI
    GANTENG GANTENG SERIGALA
    12:30DIAM-DIAM SUKA
    DIAM-DIAM SUKA
    13:30PIALA KAPOLRI 2023 PUTRA (LIVE)
    PIALA KAPOLRI 2023 PUTRA (LIVE)
    16:00PIALA KAPOLRI 2023 PUTRI (LIVE)
    PIALA KAPOLRI 2023 PUTRI (LIVE)
    18:00PIALA KAPOLRI 2023 PUTRA (LIVE)
    PIALA KAPOLRI 2023 PUTRA (LIVE)
    20:00MOJI DRAMA (CHHOTI SARDARNI)
    CHHOTI SARDARNI
    21:30SINEMA MALAM (BIDADARI CANTIK DI RUMAH KOST)
    (BIDADARI CANTIK DI RUMAH KOST
    23:00TRUST
    Informasi seputar menjaga vitalitas pria
    23:30TRUST
    Informasi seputar menjaga vitalitas pria
    ' - it('can handle empty guide', () => { const results = parser({ content: '' }) expect(results).toMatchObject([]) }) it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') const results = parser({ content, date }).map(p => { p.start = p.start.year(2023).toJSON() p.stop = p.stop.year(2023).toJSON() diff --git a/sites/mysky.com.ph/__data__/content.json b/sites/mysky.com.ph/__data__/content.json new file mode 100644 index 000000000..0cc6dd0f1 --- /dev/null +++ b/sites/mysky.com.ph/__data__/content.json @@ -0,0 +1 @@ +{"events":[{"name":"TV PATROL","location":"8","start":"2022/10/04 19:00","end":"2022/10/04 20:00","userData":{"description":"Description example"}},{"name":"DARNA","location":"8","start":"2022/10/05 20:00","end":"2022/10/05 20:45","userData":{"description":""}},{"name":"Zoe Bakes S1","location":"22","start":"2022/10/04 20:30","end":"2022/10/04 21:00","userData":{"description":"Zo Franois Dad is a beekeeper. So for his birthday, she bakes him a special beehiveshaped cake."}}]} \ No newline at end of file diff --git a/sites/mysky.com.ph/mysky.com.ph.test.js b/sites/mysky.com.ph/mysky.com.ph.test.js index 1890037b2..fba104724 100644 --- a/sites/mysky.com.ph/mysky.com.ph.test.js +++ b/sites/mysky.com.ph/mysky.com.ph.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./mysky.com.ph.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -16,8 +18,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - const content = - '{"events":[{"name":"TV PATROL","location":"8","start":"2022/10/04 19:00","end":"2022/10/04 20:00","userData":{"description":"Description example"}},{"name":"DARNA","location":"8","start":"2022/10/05 20:00","end":"2022/10/05 20:45","userData":{"description":""}},{"name":"Zoe Bakes S1","location":"22","start":"2022/10/04 20:30","end":"2022/10/04 21:00","userData":{"description":"Zo Franois Dad is a beekeeper. So for his birthday, she bakes him a special beehiveshaped cake."}}]}' + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const result = parser({ content, channel, date }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() diff --git a/sites/neo.io/__data__/content.json b/sites/neo.io/__data__/content.json new file mode 100644 index 000000000..92aa32fe8 --- /dev/null +++ b/sites/neo.io/__data__/content.json @@ -0,0 +1,64 @@ +{ + "shows": [ + { + "title": "Napovedujemo", + "show_start": 1735185900, + "show_end": 1735192200, + "timestamp": "5:05 - 6:50", + "show_id": "CUP_IECOM_SLO1_10004660", + "thumbnail": "https://ngimg.siol.tv/sioltv/mtcmsprod/52/0/0/5200d01a-fe5f-487e-835a-274e77227a6b.jpg", + "is_adult": false, + "friendly_id": "napovedujemo_db48", + "pg": "", + "genres": [ + "napovednik" + ], + "year": 0, + "summary": "Vabilo k ogledu naših oddaj.", + "categories": "Ostalo", + "stb_only": false, + "is_live": false, + "original_title": "Napovedujemo" + }, + { + "title": "S0E0 - Hrabri zajčki: Prvi sneg", + "show_start": 1735192200, + "show_end": 1735192800, + "timestamp": "6:50 - 7:00", + "show_id": "CUP_IECOM_SLO1_79637910", + "thumbnail": "https://ngimg.siol.tv/sioltv/mtcmsprod/d6/4/5/d6456f4a-4f0a-4825-90c1-1749abd59688.jpg", + "is_adult": false, + "friendly_id": "hrabri_zajcki_prvi_sneg_1619", + "pg": "", + "genres": [ + "risanka" + ], + "year": 2020, + "summary": "Hrabri zajčki so prispeli v borov gozd in izkusili prvi sneg. Bob in Bu še nikoli nista videla snega. Mami kuha korenčkov kakav, Bu in Bob pa kmalu spoznata novega prijatelja, losa Danija.", + "categories": "Otroški/Mladinski", + "stb_only": false, + "is_live": false, + "original_title": "S0E0 - Brave Bunnies" + }, + { + "title": "Dobro jutro", + "show_start": 1735192800, + "show_end": 1735203900, + "timestamp": "7:00 - 10:05", + "show_id": "CUP_IECOM_SLO1_79637911", + "thumbnail": "https://ngimg.siol.tv/sioltv/mtcmsprod/e1/2/d/e12d8eb4-693a-43d3-89d4-fd96dade9f0f.jpg", + "is_adult": false, + "friendly_id": "dobro_jutro_2f10", + "pg": "", + "genres": [ + "zabavna oddaja" + ], + "year": 2024, + "summary": "Oddaja Dobro jutro poleg informativnih in zabavnih vsebin podaja koristne nasvete o najrazličnejših tematikah iz vsakdanjega življenja.", + "categories": "Razvedrilni program", + "stb_only": false, + "is_live": false, + "original_title": "Dobro jutro" + } + ] + } \ No newline at end of file diff --git a/sites/neo.io/__data__/no_content.json b/sites/neo.io/__data__/no_content.json new file mode 100644 index 000000000..78743050d --- /dev/null +++ b/sites/neo.io/__data__/no_content.json @@ -0,0 +1 @@ +{"shows":[]} \ No newline at end of file diff --git a/sites/neo.io/neo.io.test.js b/sites/neo.io/neo.io.test.js index 295120f7a..be6e23a41 100644 --- a/sites/neo.io/neo.io.test.js +++ b/sites/neo.io/neo.io.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./neo.io.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -18,72 +20,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - const content = ` - { - "shows": [ - { - "title": "Napovedujemo", - "show_start": 1735185900, - "show_end": 1735192200, - "timestamp": "5:05 - 6:50", - "show_id": "CUP_IECOM_SLO1_10004660", - "thumbnail": "https://ngimg.siol.tv/sioltv/mtcmsprod/52/0/0/5200d01a-fe5f-487e-835a-274e77227a6b.jpg", - "is_adult": false, - "friendly_id": "napovedujemo_db48", - "pg": "", - "genres": [ - "napovednik" - ], - "year": 0, - "summary": "Vabilo k ogledu naših oddaj.", - "categories": "Ostalo", - "stb_only": false, - "is_live": false, - "original_title": "Napovedujemo" - }, - { - "title": "S0E0 - Hrabri zajčki: Prvi sneg", - "show_start": 1735192200, - "show_end": 1735192800, - "timestamp": "6:50 - 7:00", - "show_id": "CUP_IECOM_SLO1_79637910", - "thumbnail": "https://ngimg.siol.tv/sioltv/mtcmsprod/d6/4/5/d6456f4a-4f0a-4825-90c1-1749abd59688.jpg", - "is_adult": false, - "friendly_id": "hrabri_zajcki_prvi_sneg_1619", - "pg": "", - "genres": [ - "risanka" - ], - "year": 2020, - "summary": "Hrabri zajčki so prispeli v borov gozd in izkusili prvi sneg. Bob in Bu še nikoli nista videla snega. Mami kuha korenčkov kakav, Bu in Bob pa kmalu spoznata novega prijatelja, losa Danija.", - "categories": "Otroški/Mladinski", - "stb_only": false, - "is_live": false, - "original_title": "S0E0 - Brave Bunnies" - }, - { - "title": "Dobro jutro", - "show_start": 1735192800, - "show_end": 1735203900, - "timestamp": "7:00 - 10:05", - "show_id": "CUP_IECOM_SLO1_79637911", - "thumbnail": "https://ngimg.siol.tv/sioltv/mtcmsprod/e1/2/d/e12d8eb4-693a-43d3-89d4-fd96dade9f0f.jpg", - "is_adult": false, - "friendly_id": "dobro_jutro_2f10", - "pg": "", - "genres": [ - "zabavna oddaja" - ], - "year": 2024, - "summary": "Oddaja Dobro jutro poleg informativnih in zabavnih vsebin podaja koristne nasvete o najrazličnejših tematikah iz vsakdanjega življenja.", - "categories": "Razvedrilni program", - "stb_only": false, - "is_live": false, - "original_title": "Dobro jutro" - } - ] - }` - + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const result = parser({ content, channel }) expect(result).toMatchObject([ @@ -118,7 +55,7 @@ it('can parse response', () => { it('can handle empty guide', () => { const result = parser({ - content: '{"shows":[]}' + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) }) expect(result).toMatchObject([]) }) diff --git a/sites/novacyprus.com/__data__/content.json b/sites/novacyprus.com/__data__/content.json new file mode 100644 index 000000000..f3504a962 --- /dev/null +++ b/sites/novacyprus.com/__data__/content.json @@ -0,0 +1 @@ +{"nodes":[{"datetime":"2021-11-17 06:20:00","day":"Wednesday","numDay":17,"numMonth":11,"month":"November","channelName":"Cyprus Novacinema1HD","channelLog":"https://ssl2.novago.gr/EPG/jsp/images/universal/film/logo/20200210/000100/XTV100000762/d6a2f5e0-dbc0-49c7-9843-e3161ca5ae5d.png","cid":"42","ChannelId":"614","startingTime":"06:20","endTime":"08:10","title":"Δεσμοί Αίματος","description":"Θρίλερ Μυστηρίου","duration":"109","slotDuration":"110","bref":"COMMOBLOOX","mediaItems":[{"MediaListTypeId":"6","CdnUrl":"http://cache-forthnet.secure.footprint.net/linear/3/0/305608_COMMOBLOOX_GUIDE_STILL.jpg"},{"MediaListTypeId":"7","CdnUrl":"http://cache-forthnet.secure.footprint.net/linear/3/0/305608_COMMOBLOOX_POSTER_CROSS.jpg"},{"MediaListTypeId":"8","CdnUrl":"http://cache-forthnet.secure.footprint.net/linear/3/0/305608_COMMOBLOOX_ICON_CYP.jpg"},{"MediaListTypeId":"9","CdnUrl":"http://cache-forthnet.secure.footprint.net/linear/3/0/305608_COMMOBLOOX_POSTER_CYP.jpg"},{"MediaListTypeId":"10","CdnUrl":"http://cache-forthnet.secure.footprint.net/linear/3/0/305608_COMMOBLOOX_BACKGROUND_CYP.jpg"}]},{"datetime":"2021-11-17 06:00:00","day":"Wednesday","numDay":17,"numMonth":11,"month":"November","channelName":"Cyprus Novacinema2HD","channelLog":"https://ssl2.novago.gr/EPG/jsp/images/universal/film/logo/20200210/000100/XTV100000763/24e05354-d6ad-4949-bcb3-a81d1c1d2cba.png","cid":"62","ChannelId":"653","startingTime":"06:00","endTime":"07:40","title":"Ανυπόφοροι Γείτονες","description":"Κωμωδία","duration":"93","slotDuration":"100","bref":"NEIGHBORSX","mediaItems":[{"MediaListTypeId":"7","CdnUrl":"http://cache-forthnet.secure.footprint.net/linear/3/1/312582_NEIGHBORSX_POSTER_CROSS.jpg"},{"MediaListTypeId":"8","CdnUrl":"http://cache-forthnet.secure.footprint.net/linear/3/1/312582_NEIGHBORSX_ICON_CYP.jpg"},{"MediaListTypeId":"9","CdnUrl":"http://cache-forthnet.secure.footprint.net/linear/3/1/312582_NEIGHBORSX_POSTER_CYP.jpg"},{"MediaListTypeId":"10","CdnUrl":"http://cache-forthnet.secure.footprint.net/linear/3/1/312582_NEIGHBORSX_BACKGROUND_CYP.jpg"}]}]} \ No newline at end of file diff --git a/sites/novacyprus.com/__data__/no_content.json b/sites/novacyprus.com/__data__/no_content.json new file mode 100644 index 000000000..a0b8012cf --- /dev/null +++ b/sites/novacyprus.com/__data__/no_content.json @@ -0,0 +1 @@ +{"nodes":[],"total":0,"pages":0} \ No newline at end of file diff --git a/sites/novacyprus.com/novacyprus.com.test.js b/sites/novacyprus.com/novacyprus.com.test.js index 095b5998f..0f4de631a 100644 --- a/sites/novacyprus.com/novacyprus.com.test.js +++ b/sites/novacyprus.com/novacyprus.com.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./novacyprus.com.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -18,8 +20,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - const content = - '{"nodes":[{"datetime":"2021-11-17 06:20:00","day":"Wednesday","numDay":17,"numMonth":11,"month":"November","channelName":"Cyprus Novacinema1HD","channelLog":"https://ssl2.novago.gr/EPG/jsp/images/universal/film/logo/20200210/000100/XTV100000762/d6a2f5e0-dbc0-49c7-9843-e3161ca5ae5d.png","cid":"42","ChannelId":"614","startingTime":"06:20","endTime":"08:10","title":"Δεσμοί Αίματος","description":"Θρίλερ Μυστηρίου","duration":"109","slotDuration":"110","bref":"COMMOBLOOX","mediaItems":[{"MediaListTypeId":"6","CdnUrl":"http://cache-forthnet.secure.footprint.net/linear/3/0/305608_COMMOBLOOX_GUIDE_STILL.jpg"},{"MediaListTypeId":"7","CdnUrl":"http://cache-forthnet.secure.footprint.net/linear/3/0/305608_COMMOBLOOX_POSTER_CROSS.jpg"},{"MediaListTypeId":"8","CdnUrl":"http://cache-forthnet.secure.footprint.net/linear/3/0/305608_COMMOBLOOX_ICON_CYP.jpg"},{"MediaListTypeId":"9","CdnUrl":"http://cache-forthnet.secure.footprint.net/linear/3/0/305608_COMMOBLOOX_POSTER_CYP.jpg"},{"MediaListTypeId":"10","CdnUrl":"http://cache-forthnet.secure.footprint.net/linear/3/0/305608_COMMOBLOOX_BACKGROUND_CYP.jpg"}]},{"datetime":"2021-11-17 06:00:00","day":"Wednesday","numDay":17,"numMonth":11,"month":"November","channelName":"Cyprus Novacinema2HD","channelLog":"https://ssl2.novago.gr/EPG/jsp/images/universal/film/logo/20200210/000100/XTV100000763/24e05354-d6ad-4949-bcb3-a81d1c1d2cba.png","cid":"62","ChannelId":"653","startingTime":"06:00","endTime":"07:40","title":"Ανυπόφοροι Γείτονες","description":"Κωμωδία","duration":"93","slotDuration":"100","bref":"NEIGHBORSX","mediaItems":[{"MediaListTypeId":"7","CdnUrl":"http://cache-forthnet.secure.footprint.net/linear/3/1/312582_NEIGHBORSX_POSTER_CROSS.jpg"},{"MediaListTypeId":"8","CdnUrl":"http://cache-forthnet.secure.footprint.net/linear/3/1/312582_NEIGHBORSX_ICON_CYP.jpg"},{"MediaListTypeId":"9","CdnUrl":"http://cache-forthnet.secure.footprint.net/linear/3/1/312582_NEIGHBORSX_POSTER_CYP.jpg"},{"MediaListTypeId":"10","CdnUrl":"http://cache-forthnet.secure.footprint.net/linear/3/1/312582_NEIGHBORSX_BACKGROUND_CYP.jpg"}]}]}' + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const result = parser({ content, channel }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() @@ -42,7 +43,7 @@ it('can handle empty guide', () => { const result = parser({ date, channel, - content: '{"nodes":[],"total":0,"pages":0}' + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) }) expect(result).toMatchObject([]) }) diff --git a/sites/nowplayer.now.com/__data__/content.json b/sites/nowplayer.now.com/__data__/content.json new file mode 100644 index 000000000..eafb20207 --- /dev/null +++ b/sites/nowplayer.now.com/__data__/content.json @@ -0,0 +1 @@ +[[{"key":"key_202111174524739","vimProgramId":"202111174524739","name":"ViuTVsix Station Closing","start":1637690400000,"end":1637715600000,"date":"20211124","startTime":"02:00AM","endTime":"09:00AM","duration":420,"recordable":false,"restartTv":false,"npvrProg":false,"npvrStartTime":0,"npvrEndTime":0,"cid":"viutvsix station closing","cc":"","isInWatchlist":false}]] \ No newline at end of file diff --git a/sites/nowplayer.now.com/nowplayer.now.com.test.js b/sites/nowplayer.now.com/nowplayer.now.com.test.js index 35d0528a2..39e90077a 100644 --- a/sites/nowplayer.now.com/nowplayer.now.com.test.js +++ b/sites/nowplayer.now.com/nowplayer.now.com.test.js @@ -1,4 +1,6 @@ const { parser, url, request } = require('./nowplayer.now.com.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -32,8 +34,7 @@ it('can generate valid request headers', () => { }) it('can parse response', () => { - const content = - '[[{"key":"key_202111174524739","vimProgramId":"202111174524739","name":"ViuTVsix Station Closing","start":1637690400000,"end":1637715600000,"date":"20211124","startTime":"02:00AM","endTime":"09:00AM","duration":420,"recordable":false,"restartTv":false,"npvrProg":false,"npvrStartTime":0,"npvrEndTime":0,"cid":"viutvsix station closing","cc":"","isInWatchlist":false}]]' + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const result = parser({ content }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() diff --git a/sites/ontvtonight.com/__data__/content.html b/sites/ontvtonight.com/__data__/content.html new file mode 100644 index 000000000..00f26fb4b --- /dev/null +++ b/sites/ontvtonight.com/__data__/content.html @@ -0,0 +1 @@ +
    7TWO
    12:10 am
    What A Carry On
    12:50 am
    Bones
    The Devil In The Details
    10:50 pm
    Inspector Morse: The Remorseful Day
    \ No newline at end of file diff --git a/sites/ontvtonight.com/__data__/no_content.html b/sites/ontvtonight.com/__data__/no_content.html new file mode 100644 index 000000000..6fedfd4c7 --- /dev/null +++ b/sites/ontvtonight.com/__data__/no_content.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sites/ontvtonight.com/ontvtonight.com.test.js b/sites/ontvtonight.com/ontvtonight.com.test.js index 8ad6b96b0..9f1eaf151 100644 --- a/sites/ontvtonight.com/ontvtonight.com.test.js +++ b/sites/ontvtonight.com/ontvtonight.com.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./ontvtonight.com.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -10,8 +12,6 @@ const channel = { site_id: 'au#1692/7two', xmltv_id: '7two.au' } -const content = - '
    7TWO
    12:10 am
    What A Carry On
    12:50 am
    Bones
    The Devil In The Details
    10:50 pm
    Inspector Morse: The Remorseful Day
    ' it('can generate valid url', () => { expect(url({ channel, date })).toBe( @@ -20,6 +20,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') const result = parser({ content, channel, date }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() @@ -50,7 +51,7 @@ it('can handle empty guide', () => { const result = parser({ date, channel, - content: '' + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') }) expect(result).toMatchObject([]) }) diff --git a/sites/pbsguam.org/__data__/content.html b/sites/pbsguam.org/__data__/content.html new file mode 100644 index 000000000..e946e965f --- /dev/null +++ b/sites/pbsguam.org/__data__/content.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/sites/pbsguam.org/__data__/no_content.html b/sites/pbsguam.org/__data__/no_content.html new file mode 100644 index 000000000..8ef664e27 --- /dev/null +++ b/sites/pbsguam.org/__data__/no_content.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sites/pbsguam.org/pbsguam.org.test.js b/sites/pbsguam.org/pbsguam.org.test.js index 70a166801..5921d3ccf 100644 --- a/sites/pbsguam.org/pbsguam.org.test.js +++ b/sites/pbsguam.org/pbsguam.org.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./pbsguam.org.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -16,11 +18,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - const content = ` ` + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') const result = parser({ date, content }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() @@ -40,7 +38,7 @@ it('can handle empty guide', () => { const result = parser({ date, channel, - content: ' ' + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') }) expect(result).toMatchObject([]) }) From 851aba2438124fb102af8d660940a93c9535dff4 Mon Sep 17 00:00:00 2001 From: theofficialomega <30985701+BellezaEmporium@users.noreply.github.com> Date: Sun, 27 Jul 2025 19:49:44 +0200 Subject: [PATCH 20/32] finish uniformizing tests --- sites/9tv.co.il/9tv.co.il.test.js | 7 +- sites/9tv.co.il/__data__/content.html | 1 + sites/9tv.co.il/__data__/no_content.html | 1 + sites/allente.dk/__data__/content.json | 1 + sites/allente.dk/__data__/no_content.json | 1 + sites/allente.dk/allente.dk.test.js | 2 +- sites/allente.fi/__data__/content.json | 1 + sites/allente.fi/__data__/no_content.json | 1 + sites/allente.fi/allente.fi.test.js | 7 +- sites/allente.no/__data__/content.json | 1 + sites/allente.no/__data__/no_content.json | 1 + sites/allente.no/allente.no.test.js | 7 +- sites/allente.se/__data__/content.json | 1 + sites/allente.se/__data__/no_content.json | 1 + sites/allente.se/allente.se.test.js | 7 +- .../arianaafgtv.com.channels.xml | 4 - .../arianaafgtv.com/arianaafgtv.com.config.js | 82 ------------------- sites/arianaafgtv.com/readme.md | 15 ---- .../__data__/content.html | 1 + .../__data__/no_content.html | 1 + .../arianatelevision.com.test.js | 8 +- sites/artonline.tv/__data__/content.json | 1 + sites/artonline.tv/artonline.tv.test.js | 5 +- sites/chada.ma/__data__/content.html | 14 ++++ sites/chada.ma/__data__/no_content.html | 1 + sites/chada.ma/chada.ma.test.js | 24 +----- .../__data__/content.json | 1 + .../__data__/no_content.json | 1 + .../chaines-tv.orange.fr.test.js | 8 +- sites/cosmotetv.gr/__data__/content.json | 20 +++++ sites/cosmotetv.gr/__data__/no_content.json | 1 + sites/cosmotetv.gr/cosmotetv.gr.test.js | 29 +------ sites/cubmu.com/__data__/content.json | 1 + sites/cubmu.com/cubmu.com.test.js | 6 +- sites/directv.com.ar/__data__/content.json | 1 + sites/directv.com.ar/directv.com.ar.test.js | 5 +- sites/firstmedia.com/__data__/content.json | 1 + sites/firstmedia.com/firstmedia.com.test.js | 5 +- sites/foxsports.com.au/__data__/content.json | 1 + .../foxsports.com.au/__data__/no_content.json | 1 + .../foxsports.com.au/foxsports.com.au.test.js | 13 +-- sites/freetv.tv/__data__/content.json | 1 + sites/freetv.tv/freetv.tv.test.js | 5 +- sites/frikanalen.no/__data__/content.json | 1 + sites/frikanalen.no/__data__/no_content.json | 1 + sites/frikanalen.no/frikanalen.no.test.js | 7 +- sites/galamtv.kz/__data__/content.json | 21 +++++ sites/galamtv.kz/__data__/no_content.json | 1 + sites/galamtv.kz/galamtv.kz.test.js | 27 +----- sites/guidatv.sky.it/__data__/content.json | 1 + sites/guidatv.sky.it/__data__/no_content.json | 1 + sites/guidatv.sky.it/guidatv.sky.it.test.js | 7 +- sites/horizon.tv/__data__/content.json | 1 + sites/horizon.tv/__data__/content_1.json | 1 + sites/horizon.tv/__data__/content_2.json | 1 + sites/horizon.tv/__data__/content_3.json | 1 + .../__data__/content_listings_1.json | 1 + .../__data__/content_listings_2.json | 1 + .../__data__/content_listings_3.json | 1 + .../__data__/content_listings_4.json | 1 + sites/horizon.tv/__data__/no_content.json | 1 + sites/horizon.tv/horizon.tv.test.js | 47 ++++------- sites/hoy.tv/__data__/content.xml | 73 +++++++++++++++++ sites/hoy.tv/hoy.tv.test.js | 77 +---------------- sites/i24news.tv/__data__/content.json | 1 + sites/i24news.tv/i24news.tv.test.js | 5 +- sites/indihometv.com/__data__/content.html | 1 + sites/indihometv.com/__data__/no_content.html | 1 + sites/indihometv.com/indihometv.com.test.js | 7 +- sites/ipko.tv/__data__/content.json | 58 +++++++++++++ sites/ipko.tv/__data__/no_content.json | 1 + sites/ipko.tv/ipko.tv.test.js | 65 +-------------- sites/kan.org.il/__data__/content.json | 1 + sites/kan.org.il/kan.org.il.test.js | 5 +- sites/magticom.ge/__data__/content.json | 1 + sites/magticom.ge/magticom.ge.test.js | 5 +- sites/mako.co.il/__data__/content.json | 1 + sites/mako.co.il/mako.co.il.test.js | 5 +- sites/maxtvgo.mk/__data__/content.json | 1 + .../__data__/content_no_description.json | 1 + sites/maxtvgo.mk/__data__/no_content.json | 1 + sites/maxtvgo.mk/maxtvgo.mk.test.js | 10 +-- sites/melita.com/__data__/content.json | 1 + sites/melita.com/melita.com.test.js | 5 +- sites/mewatch.sg/__data__/content.json | 1 + sites/mewatch.sg/__data__/no_content.json | 1 + sites/mewatch.sg/mewatch.sg.test.js | 7 +- 87 files changed, 351 insertions(+), 403 deletions(-) create mode 100644 sites/9tv.co.il/__data__/content.html create mode 100644 sites/9tv.co.il/__data__/no_content.html create mode 100644 sites/allente.dk/__data__/content.json create mode 100644 sites/allente.dk/__data__/no_content.json create mode 100644 sites/allente.fi/__data__/content.json create mode 100644 sites/allente.fi/__data__/no_content.json create mode 100644 sites/allente.no/__data__/content.json create mode 100644 sites/allente.no/__data__/no_content.json create mode 100644 sites/allente.se/__data__/content.json create mode 100644 sites/allente.se/__data__/no_content.json delete mode 100644 sites/arianaafgtv.com/arianaafgtv.com.channels.xml delete mode 100644 sites/arianaafgtv.com/arianaafgtv.com.config.js delete mode 100644 sites/arianaafgtv.com/readme.md create mode 100644 sites/arianatelevision.com/__data__/content.html create mode 100644 sites/arianatelevision.com/__data__/no_content.html create mode 100644 sites/artonline.tv/__data__/content.json create mode 100644 sites/chada.ma/__data__/content.html create mode 100644 sites/chada.ma/__data__/no_content.html create mode 100644 sites/chaines-tv.orange.fr/__data__/content.json create mode 100644 sites/chaines-tv.orange.fr/__data__/no_content.json create mode 100644 sites/cosmotetv.gr/__data__/content.json create mode 100644 sites/cosmotetv.gr/__data__/no_content.json create mode 100644 sites/cubmu.com/__data__/content.json create mode 100644 sites/directv.com.ar/__data__/content.json create mode 100644 sites/firstmedia.com/__data__/content.json create mode 100644 sites/foxsports.com.au/__data__/content.json create mode 100644 sites/foxsports.com.au/__data__/no_content.json create mode 100644 sites/freetv.tv/__data__/content.json create mode 100644 sites/frikanalen.no/__data__/content.json create mode 100644 sites/frikanalen.no/__data__/no_content.json create mode 100644 sites/galamtv.kz/__data__/content.json create mode 100644 sites/galamtv.kz/__data__/no_content.json create mode 100644 sites/guidatv.sky.it/__data__/content.json create mode 100644 sites/guidatv.sky.it/__data__/no_content.json create mode 100644 sites/horizon.tv/__data__/content.json create mode 100644 sites/horizon.tv/__data__/content_1.json create mode 100644 sites/horizon.tv/__data__/content_2.json create mode 100644 sites/horizon.tv/__data__/content_3.json create mode 100644 sites/horizon.tv/__data__/content_listings_1.json create mode 100644 sites/horizon.tv/__data__/content_listings_2.json create mode 100644 sites/horizon.tv/__data__/content_listings_3.json create mode 100644 sites/horizon.tv/__data__/content_listings_4.json create mode 100644 sites/horizon.tv/__data__/no_content.json create mode 100644 sites/hoy.tv/__data__/content.xml create mode 100644 sites/i24news.tv/__data__/content.json create mode 100644 sites/indihometv.com/__data__/content.html create mode 100644 sites/indihometv.com/__data__/no_content.html create mode 100644 sites/ipko.tv/__data__/content.json create mode 100644 sites/ipko.tv/__data__/no_content.json create mode 100644 sites/kan.org.il/__data__/content.json create mode 100644 sites/magticom.ge/__data__/content.json create mode 100644 sites/mako.co.il/__data__/content.json create mode 100644 sites/maxtvgo.mk/__data__/content.json create mode 100644 sites/maxtvgo.mk/__data__/content_no_description.json create mode 100644 sites/maxtvgo.mk/__data__/no_content.json create mode 100644 sites/melita.com/__data__/content.json create mode 100644 sites/mewatch.sg/__data__/content.json create mode 100644 sites/mewatch.sg/__data__/no_content.json diff --git a/sites/9tv.co.il/9tv.co.il.test.js b/sites/9tv.co.il/9tv.co.il.test.js index 0672f1dd7..18ffe7fe4 100644 --- a/sites/9tv.co.il/9tv.co.il.test.js +++ b/sites/9tv.co.il/9tv.co.il.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./9tv.co.il.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -18,8 +20,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - const content = - '
  • 06:30

    Слепая

    Она не очень любит говорить о себе или о том, кто и зачем к ней обращается. Живет уединенно, в глуши. Но тех, кто приходит -принимает. Она видит судьбы. 
  • 09:10

    Орел и решка. Морской сезон

    Орел и решка. Морской сезон. Ведущие -Алина Астровская и Коля Серга.
  • ' + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') const result = parser({ content, date }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() @@ -49,7 +50,7 @@ it('can handle empty guide', () => { const result = parser({ date, channel, - content: '' + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') }) expect(result).toMatchObject([]) }) diff --git a/sites/9tv.co.il/__data__/content.html b/sites/9tv.co.il/__data__/content.html new file mode 100644 index 000000000..290eb76e6 --- /dev/null +++ b/sites/9tv.co.il/__data__/content.html @@ -0,0 +1 @@ +
  • 06:30

    Слепая

    Она не очень любит говорить о себе или о том, кто и зачем к ней обращается. Живет уединенно, в глуши. Но тех, кто приходит -принимает. Она видит судьбы. 
  • 09:10

    Орел и решка. Морской сезон

    Орел и решка. Морской сезон. Ведущие -Алина Астровская и Коля Серга.
  • \ No newline at end of file diff --git a/sites/9tv.co.il/__data__/no_content.html b/sites/9tv.co.il/__data__/no_content.html new file mode 100644 index 000000000..6fedfd4c7 --- /dev/null +++ b/sites/9tv.co.il/__data__/no_content.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sites/allente.dk/__data__/content.json b/sites/allente.dk/__data__/content.json new file mode 100644 index 000000000..bdac9a557 --- /dev/null +++ b/sites/allente.dk/__data__/content.json @@ -0,0 +1 @@ +{"channels":[{"id":"0148","icon":"//images.ctfassets.net/989y85n5kcxs/5uT9g9pdQWRZeDPQXVI9g6/9cc44da567f591822ed645c99ecdcb64/SVT_1_black_new__2_.png","name":"SVT1 HD (T)","events":[{"id":"0086202208220710","live":false,"time":"2022-08-22T07:10:00Z","title":"Hemmagympa med Sofia","details":{"title":"Hemmagympa med Sofia","image":"https://viasatps.api.comspace.se/PS/channeldate/image/viasat.ps/21/2022-08-22/se.cs.svt1.event.A_41214031600.jpg?size=2560x1440","description":"Svenskt träningsprogram från 2021. Styrka. Sofia Åhman leder SVT:s hemmagympapass. Denna gång fokuserar vi på styrka.","season":4,"episode":1,"categories":["other"],"duration":"20"}}]}]} \ No newline at end of file diff --git a/sites/allente.dk/__data__/no_content.json b/sites/allente.dk/__data__/no_content.json new file mode 100644 index 000000000..793e8706f --- /dev/null +++ b/sites/allente.dk/__data__/no_content.json @@ -0,0 +1 @@ +{"date":"2001-11-17","categories":[],"channels":[]} \ No newline at end of file diff --git a/sites/allente.dk/allente.dk.test.js b/sites/allente.dk/allente.dk.test.js index c3594d9d2..99192ef92 100644 --- a/sites/allente.dk/allente.dk.test.js +++ b/sites/allente.dk/allente.dk.test.js @@ -17,7 +17,7 @@ it('can generate valid url', () => { it('can parse response', () => { const content = - '{"channels":[{"id":"0148","icon":"//images.ctfassets.net/989y85n5kcxs/5uT9g9pdQWRZeDPQXVI9g6/9cc44da567f591822ed645c99ecdcb64/SVT_1_black_new__2_.png","name":"SVT1 HD (T)","events":[{"id":"0086202208220710","live":false,"time":"2022-08-22T07:10:00Z","title":"Hemmagympa med Sofia","details":{"title":"Hemmagympa med Sofia","image":"https://viasatps.api.comspace.se/PS/channeldate/image/viasat.ps/21/2022-08-22/se.cs.svt1.event.A_41214031600.jpg?size=2560x1440","description":"Svenskt träningsprogram från 2021. Styrka. Sofia Åhman leder SVT:s hemmagympapass. Denna gång fokuserar vi på styrka.","season":4,"episode":1,"categories":["other"],"duration":"20"}}]}]}' + '' const result = parser({ content, channel }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() diff --git a/sites/allente.fi/__data__/content.json b/sites/allente.fi/__data__/content.json new file mode 100644 index 000000000..bdac9a557 --- /dev/null +++ b/sites/allente.fi/__data__/content.json @@ -0,0 +1 @@ +{"channels":[{"id":"0148","icon":"//images.ctfassets.net/989y85n5kcxs/5uT9g9pdQWRZeDPQXVI9g6/9cc44da567f591822ed645c99ecdcb64/SVT_1_black_new__2_.png","name":"SVT1 HD (T)","events":[{"id":"0086202208220710","live":false,"time":"2022-08-22T07:10:00Z","title":"Hemmagympa med Sofia","details":{"title":"Hemmagympa med Sofia","image":"https://viasatps.api.comspace.se/PS/channeldate/image/viasat.ps/21/2022-08-22/se.cs.svt1.event.A_41214031600.jpg?size=2560x1440","description":"Svenskt träningsprogram från 2021. Styrka. Sofia Åhman leder SVT:s hemmagympapass. Denna gång fokuserar vi på styrka.","season":4,"episode":1,"categories":["other"],"duration":"20"}}]}]} \ No newline at end of file diff --git a/sites/allente.fi/__data__/no_content.json b/sites/allente.fi/__data__/no_content.json new file mode 100644 index 000000000..793e8706f --- /dev/null +++ b/sites/allente.fi/__data__/no_content.json @@ -0,0 +1 @@ +{"date":"2001-11-17","categories":[],"channels":[]} \ No newline at end of file diff --git a/sites/allente.fi/allente.fi.test.js b/sites/allente.fi/allente.fi.test.js index 6e069a94f..48d32022d 100644 --- a/sites/allente.fi/allente.fi.test.js +++ b/sites/allente.fi/allente.fi.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./allente.fi.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -16,8 +18,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - const content = - '{"channels":[{"id":"0148","icon":"//images.ctfassets.net/989y85n5kcxs/5uT9g9pdQWRZeDPQXVI9g6/9cc44da567f591822ed645c99ecdcb64/SVT_1_black_new__2_.png","name":"SVT1 HD (T)","events":[{"id":"0086202208220710","live":false,"time":"2022-08-22T07:10:00Z","title":"Hemmagympa med Sofia","details":{"title":"Hemmagympa med Sofia","image":"https://viasatps.api.comspace.se/PS/channeldate/image/viasat.ps/21/2022-08-22/se.cs.svt1.event.A_41214031600.jpg?size=2560x1440","description":"Svenskt träningsprogram från 2021. Styrka. Sofia Åhman leder SVT:s hemmagympapass. Denna gång fokuserar vi på styrka.","season":4,"episode":1,"categories":["other"],"duration":"20"}}]}]}' + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const result = parser({ content, channel }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() @@ -44,7 +45,7 @@ it('can handle empty guide', () => { const result = parser({ date, channel, - content: '{"date":"2001-11-17","categories":[],"channels":[]}' + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) }) expect(result).toMatchObject([]) }) diff --git a/sites/allente.no/__data__/content.json b/sites/allente.no/__data__/content.json new file mode 100644 index 000000000..bdac9a557 --- /dev/null +++ b/sites/allente.no/__data__/content.json @@ -0,0 +1 @@ +{"channels":[{"id":"0148","icon":"//images.ctfassets.net/989y85n5kcxs/5uT9g9pdQWRZeDPQXVI9g6/9cc44da567f591822ed645c99ecdcb64/SVT_1_black_new__2_.png","name":"SVT1 HD (T)","events":[{"id":"0086202208220710","live":false,"time":"2022-08-22T07:10:00Z","title":"Hemmagympa med Sofia","details":{"title":"Hemmagympa med Sofia","image":"https://viasatps.api.comspace.se/PS/channeldate/image/viasat.ps/21/2022-08-22/se.cs.svt1.event.A_41214031600.jpg?size=2560x1440","description":"Svenskt träningsprogram från 2021. Styrka. Sofia Åhman leder SVT:s hemmagympapass. Denna gång fokuserar vi på styrka.","season":4,"episode":1,"categories":["other"],"duration":"20"}}]}]} \ No newline at end of file diff --git a/sites/allente.no/__data__/no_content.json b/sites/allente.no/__data__/no_content.json new file mode 100644 index 000000000..793e8706f --- /dev/null +++ b/sites/allente.no/__data__/no_content.json @@ -0,0 +1 @@ +{"date":"2001-11-17","categories":[],"channels":[]} \ No newline at end of file diff --git a/sites/allente.no/allente.no.test.js b/sites/allente.no/allente.no.test.js index 3ca1fbfcc..a58715e9a 100644 --- a/sites/allente.no/allente.no.test.js +++ b/sites/allente.no/allente.no.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./allente.no.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -16,8 +18,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - const content = - '{"channels":[{"id":"0148","icon":"//images.ctfassets.net/989y85n5kcxs/5uT9g9pdQWRZeDPQXVI9g6/9cc44da567f591822ed645c99ecdcb64/SVT_1_black_new__2_.png","name":"SVT1 HD (T)","events":[{"id":"0086202208220710","live":false,"time":"2022-08-22T07:10:00Z","title":"Hemmagympa med Sofia","details":{"title":"Hemmagympa med Sofia","image":"https://viasatps.api.comspace.se/PS/channeldate/image/viasat.ps/21/2022-08-22/se.cs.svt1.event.A_41214031600.jpg?size=2560x1440","description":"Svenskt träningsprogram från 2021. Styrka. Sofia Åhman leder SVT:s hemmagympapass. Denna gång fokuserar vi på styrka.","season":4,"episode":1,"categories":["other"],"duration":"20"}}]}]}' + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const result = parser({ content, channel }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() @@ -44,7 +45,7 @@ it('can handle empty guide', () => { const result = parser({ date, channel, - content: '{"date":"2001-11-17","categories":[],"channels":[]}' + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) }) expect(result).toMatchObject([]) }) diff --git a/sites/allente.se/__data__/content.json b/sites/allente.se/__data__/content.json new file mode 100644 index 000000000..bdac9a557 --- /dev/null +++ b/sites/allente.se/__data__/content.json @@ -0,0 +1 @@ +{"channels":[{"id":"0148","icon":"//images.ctfassets.net/989y85n5kcxs/5uT9g9pdQWRZeDPQXVI9g6/9cc44da567f591822ed645c99ecdcb64/SVT_1_black_new__2_.png","name":"SVT1 HD (T)","events":[{"id":"0086202208220710","live":false,"time":"2022-08-22T07:10:00Z","title":"Hemmagympa med Sofia","details":{"title":"Hemmagympa med Sofia","image":"https://viasatps.api.comspace.se/PS/channeldate/image/viasat.ps/21/2022-08-22/se.cs.svt1.event.A_41214031600.jpg?size=2560x1440","description":"Svenskt träningsprogram från 2021. Styrka. Sofia Åhman leder SVT:s hemmagympapass. Denna gång fokuserar vi på styrka.","season":4,"episode":1,"categories":["other"],"duration":"20"}}]}]} \ No newline at end of file diff --git a/sites/allente.se/__data__/no_content.json b/sites/allente.se/__data__/no_content.json new file mode 100644 index 000000000..793e8706f --- /dev/null +++ b/sites/allente.se/__data__/no_content.json @@ -0,0 +1 @@ +{"date":"2001-11-17","categories":[],"channels":[]} \ No newline at end of file diff --git a/sites/allente.se/allente.se.test.js b/sites/allente.se/allente.se.test.js index 5c6e90cd1..de8a88512 100644 --- a/sites/allente.se/allente.se.test.js +++ b/sites/allente.se/allente.se.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./allente.se.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -16,8 +18,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - const content = - '{"channels":[{"id":"0148","icon":"//images.ctfassets.net/989y85n5kcxs/5uT9g9pdQWRZeDPQXVI9g6/9cc44da567f591822ed645c99ecdcb64/SVT_1_black_new__2_.png","name":"SVT1 HD (T)","events":[{"id":"0086202208220710","live":false,"time":"2022-08-22T07:10:00Z","title":"Hemmagympa med Sofia","details":{"title":"Hemmagympa med Sofia","image":"https://viasatps.api.comspace.se/PS/channeldate/image/viasat.ps/21/2022-08-22/se.cs.svt1.event.A_41214031600.jpg?size=2560x1440","description":"Svenskt träningsprogram från 2021. Styrka. Sofia Åhman leder SVT:s hemmagympapass. Denna gång fokuserar vi på styrka.","season":4,"episode":1,"categories":["other"],"duration":"20"}}]}]}' + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const result = parser({ content, channel }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() @@ -44,7 +45,7 @@ it('can handle empty guide', () => { const result = parser({ date, channel, - content: '{"date":"2001-11-17","categories":[],"channels":[]}' + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) }) expect(result).toMatchObject([]) }) diff --git a/sites/arianaafgtv.com/arianaafgtv.com.channels.xml b/sites/arianaafgtv.com/arianaafgtv.com.channels.xml deleted file mode 100644 index fa678c98d..000000000 --- a/sites/arianaafgtv.com/arianaafgtv.com.channels.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - Ariana Afghanistan Television - \ No newline at end of file diff --git a/sites/arianaafgtv.com/arianaafgtv.com.config.js b/sites/arianaafgtv.com/arianaafgtv.com.config.js deleted file mode 100644 index 17e7011c1..000000000 --- a/sites/arianaafgtv.com/arianaafgtv.com.config.js +++ /dev/null @@ -1,82 +0,0 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'arianaafgtv.com', - days: 2, - url: 'https://www.arianaafgtv.com/index.html', - parser({ content, date }) { - const programs = [] - const items = parseItems(content, date) - items.forEach(item => { - const title = item.title - const start = parseStart(item, date) - const stop = parseStop(item, date) - programs.push({ - title, - start, - stop - }) - }) - - return programs - } -} - -function parseStop(item, date) { - const time = `${date.format('MM/DD/YYYY')} ${item.end.toUpperCase()}` - - return dayjs.tz(time, 'MM/DD/YYYY hh:mm A', 'Asia/Kabul') -} - -function parseStart(item, date) { - const time = `${date.format('MM/DD/YYYY')} ${item.start.toUpperCase()}` - - return dayjs.tz(time, 'MM/DD/YYYY hh:mm A', 'Asia/Kabul') -} - -function parseItems(content, date) { - const $ = cheerio.load(content) - const dayOfWeek = date.format('dddd') - const column = $('.H4') - .filter((i, el) => { - return $(el).text() === dayOfWeek - }) - .first() - .parent() - - const rows = column - .find('.Paragraph') - .map((i, el) => { - return $(el).html() - }) - .toArray() - .map(r => (r === ' ' ? '|' : r)) - .join(' ') - .split('|') - - const items = [] - rows.forEach(row => { - row = row.trim() - if (row) { - const found = row.match(/(\d+(|:\d+)(a|p)m-\d+(|:\d+)(a|p)m)/gi) - if (!found) return - const time = found[0] - let start = time.match(/(\d+(|:\d+)(a|p)m)-/i)[1] - start = dayjs(start.toUpperCase(), ['hh:mmA', 'h:mmA', 'hA']).format('hh:mm A') - let end = time.match(/-(\d+(|:\d+)(a|p)m)/i)[1] - end = dayjs(end.toUpperCase(), ['hh:mmA', 'h:mmA', 'hA']).format('hh:mm A') - const title = row.replace(time, '').replace(' ', '').trim() - items.push({ start, end, title }) - } - }) - - return items -} diff --git a/sites/arianaafgtv.com/readme.md b/sites/arianaafgtv.com/readme.md deleted file mode 100644 index a75d4db18..000000000 --- a/sites/arianaafgtv.com/readme.md +++ /dev/null @@ -1,15 +0,0 @@ -# arianaafgtv.com - -https://arianaafgtv.com/#ariana-afghanistan-television-tv-guide - -### Download the guide - -```sh -npm run grab --- --site=arianaafgtv.com -``` - -### Test - -```sh -npm test --- arianaafgtv.com -``` diff --git a/sites/arianatelevision.com/__data__/content.html b/sites/arianatelevision.com/__data__/content.html new file mode 100644 index 000000000..66bfcfb7e --- /dev/null +++ b/sites/arianatelevision.com/__data__/content.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sites/arianatelevision.com/__data__/no_content.html b/sites/arianatelevision.com/__data__/no_content.html new file mode 100644 index 000000000..42addf67a --- /dev/null +++ b/sites/arianatelevision.com/__data__/no_content.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sites/arianatelevision.com/arianatelevision.com.test.js b/sites/arianatelevision.com/arianatelevision.com.test.js index 901a55078..71e011035 100644 --- a/sites/arianatelevision.com/arianatelevision.com.test.js +++ b/sites/arianatelevision.com/arianatelevision.com.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./arianatelevision.com.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -16,8 +18,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - const content = - '' + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') const result = parser({ content, date }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() @@ -52,8 +53,7 @@ it('can handle empty guide', () => { const result = parser({ date, channel, - content: - '' + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') }) expect(result).toMatchObject([]) }) diff --git a/sites/artonline.tv/__data__/content.json b/sites/artonline.tv/__data__/content.json new file mode 100644 index 000000000..9abba0095 --- /dev/null +++ b/sites/artonline.tv/__data__/content.json @@ -0,0 +1 @@ +[{"id":158963,"eventid":null,"duration":"01:34:00","lang":"Arabic","title":"الراقصه و السياسي","description":"تقرر الراقصه سونيا انشاء دار حضانه للأطفال اليتامى و عندما تتقدم بمشورعها للمسئول يرفض فتتحداه ، تلجأ للوزير عبد الحميد رأفت تربطه بها علاقة قديمة ، يخشى على مركزه و يرفض مساعدتها فتقرر كتابة مذكراتها بمساعدة أحد الصحفيين ، يتخوف عبد الحميد و المسئولين ثم يفاجأ عبد الحميد بحصول سونيا على الموافقه للمشورع و البدء في تنفيذه و ذلك لعلاقتها بأحد كبار المسئولين .","thumbnail":"/UploadImages/Channel/ARTAFLAM1/03/AlRaqesaWaAlSeyasi.jpg","image":"0","start_Time":"00:30","adddate":"3/4/2022 12:00:00 AM","repeat1":null,"iD_genre":0,"iD_Show_Type":0,"iD_Channel":77,"iD_country":0,"iD_rating":0,"end_time":"02:04","season_Number":0,"epoisode_Number":0,"hasCatchup":0,"cmsid":0,"containerID":0,"imagePath":"../../UploadImages/Channel/ARTAFLAM1/3/","youtube":"0","published_at":"0","directed_by":"0","composition":"0","cast":"0","timeShow":null,"short_description":"تقرر الراقصه سونيا انشاء دار حضانه للأطفال اليتامى و عندما تتقدم بمشورعها للمسئول يرفض فتتحداه ، تلجأ للوزير عبد الحميد رأفت تربطه بها علاقة قديمة ، يخشى على مركزه و يرفض مساعدتها فتقرر كتابة مذكراتها بمساعدة أحد الصحفيين ، يتخوف عبد الحميد و المسئولين ثم يفاجأ عبد الحميد بحصول سونيا على الموافقه للمشورع و البدء في تنفيذه و ذلك لعلاقتها بأحد كبار المسئولين .","seOdescription":null,"tagseo":null,"channel_name":null,"pathimage":null,"pathThumbnail":null}] \ No newline at end of file diff --git a/sites/artonline.tv/artonline.tv.test.js b/sites/artonline.tv/artonline.tv.test.js index 121f4ad52..ff3e093b0 100644 --- a/sites/artonline.tv/artonline.tv.test.js +++ b/sites/artonline.tv/artonline.tv.test.js @@ -1,4 +1,6 @@ const { parser, url, request } = require('./artonline.tv.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -37,8 +39,7 @@ it('can generate valid request data for tomorrow', () => { }) it('can parse response', () => { - const content = - '[{"id":158963,"eventid":null,"duration":"01:34:00","lang":"Arabic","title":"الراقصه و السياسي","description":"تقرر الراقصه سونيا انشاء دار حضانه للأطفال اليتامى و عندما تتقدم بمشورعها للمسئول يرفض فتتحداه ، تلجأ للوزير عبد الحميد رأفت تربطه بها علاقة قديمة ، يخشى على مركزه و يرفض مساعدتها فتقرر كتابة مذكراتها بمساعدة أحد الصحفيين ، يتخوف عبد الحميد و المسئولين ثم يفاجأ عبد الحميد بحصول سونيا على الموافقه للمشورع و البدء في تنفيذه و ذلك لعلاقتها بأحد كبار المسئولين .","thumbnail":"/UploadImages/Channel/ARTAFLAM1/03/AlRaqesaWaAlSeyasi.jpg","image":"0","start_Time":"00:30","adddate":"3/4/2022 12:00:00 AM","repeat1":null,"iD_genre":0,"iD_Show_Type":0,"iD_Channel":77,"iD_country":0,"iD_rating":0,"end_time":"02:04","season_Number":0,"epoisode_Number":0,"hasCatchup":0,"cmsid":0,"containerID":0,"imagePath":"../../UploadImages/Channel/ARTAFLAM1/3/","youtube":"0","published_at":"0","directed_by":"0","composition":"0","cast":"0","timeShow":null,"short_description":"تقرر الراقصه سونيا انشاء دار حضانه للأطفال اليتامى و عندما تتقدم بمشورعها للمسئول يرفض فتتحداه ، تلجأ للوزير عبد الحميد رأفت تربطه بها علاقة قديمة ، يخشى على مركزه و يرفض مساعدتها فتقرر كتابة مذكراتها بمساعدة أحد الصحفيين ، يتخوف عبد الحميد و المسئولين ثم يفاجأ عبد الحميد بحصول سونيا على الموافقه للمشورع و البدء في تنفيذه و ذلك لعلاقتها بأحد كبار المسئولين .","seOdescription":null,"tagseo":null,"channel_name":null,"pathimage":null,"pathThumbnail":null}]' + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const result = parser({ content }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() diff --git a/sites/chada.ma/__data__/content.html b/sites/chada.ma/__data__/content.html new file mode 100644 index 000000000..66ecac9b1 --- /dev/null +++ b/sites/chada.ma/__data__/content.html @@ -0,0 +1,14 @@ +
    +

    Programmes d'Aujourd'hui

    +
    +

    00:00 - 09:00

    +
    + + + +
    +

    Bloc Prime + Clips

    +
    +
    +
    +
    \ No newline at end of file diff --git a/sites/chada.ma/__data__/no_content.html b/sites/chada.ma/__data__/no_content.html new file mode 100644 index 000000000..ba01a46c2 --- /dev/null +++ b/sites/chada.ma/__data__/no_content.html @@ -0,0 +1 @@ +
    \ No newline at end of file diff --git a/sites/chada.ma/chada.ma.test.js b/sites/chada.ma/chada.ma.test.js index 51f715d48..56ac192cf 100644 --- a/sites/chada.ma/chada.ma.test.js +++ b/sites/chada.ma/chada.ma.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./chada.ma.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const timezone = require('dayjs/plugin/timezone') @@ -10,30 +12,12 @@ dayjs.extend(customParseFormat) jest.mock('axios') -const mockHtmlContent = ` -
    -

    Programmes d'Aujourd'hui

    -
    -

    00:00 - 09:00

    -
    - - - -
    -

    Bloc Prime + Clips

    -
    -
    -
    -
    -` - it('can generate valid url', () => { expect(url()).toBe('https://chada.ma/fr/chada-tv/grille-tv/') }) it('can parse response', () => { - const content = mockHtmlContent - + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') const result = parser({ content }).map(p => { p.start = dayjs(p.start).tz('Africa/Casablanca').format('YYYY-MM-DDTHH:mm:ssZ') p.stop = dayjs(p.stop).tz('Africa/Casablanca').format('YYYY-MM-DDTHH:mm:ssZ') @@ -52,7 +36,7 @@ it('can parse response', () => { it('can handle empty guide', () => { const result = parser({ - content: '
    ' + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') }) expect(result).toMatchObject([]) }) diff --git a/sites/chaines-tv.orange.fr/__data__/content.json b/sites/chaines-tv.orange.fr/__data__/content.json new file mode 100644 index 000000000..bee0f79e2 --- /dev/null +++ b/sites/chaines-tv.orange.fr/__data__/content.json @@ -0,0 +1 @@ +{"192":[{"id":1635062528017,"programType":"EPISODE","title":"Tête de liste","channelId":"192","channelZappingNumber":11,"covers":[{"format":"RATIO_16_9","url":"https://proxymedia.woopic.com/340/p/169_EMI_9697669.jpg"},{"format":"RATIO_4_3","url":"https://proxymedia.woopic.com/340/p/43_EMI_9697669.jpg"}],"diffusionDate":1636328100,"duration":2700,"csa":2,"synopsis":"Un tueur en série prend un plaisir pervers à prévenir les autorités de Tallahassee avant chaque nouveau meurtre. Rossi apprend le décès d'un de ses vieux amis.","languageVersion":"VM","hearingImpaired":true,"audioDescription":false,"season":{"number":10,"episodesCount":23,"serie":{"title":"Esprits criminels"}},"episodeNumber":12,"definition":"SD","links":[{"rel":"SELF","href":"https://rp-live.orange.fr/live-webapp/v3/applications/STB4PC/programs/1635062528017"}],"dayPart":"OTHER","catchupId":null,"genre":"Série","genreDetailed":"Série Suspense"}]} \ No newline at end of file diff --git a/sites/chaines-tv.orange.fr/__data__/no_content.json b/sites/chaines-tv.orange.fr/__data__/no_content.json new file mode 100644 index 000000000..025f8b928 --- /dev/null +++ b/sites/chaines-tv.orange.fr/__data__/no_content.json @@ -0,0 +1 @@ +{"code":60,"message":"Resource not found","param":{},"description":"L'URI demandé ou la ressource demandée n'existe pas.","stackTrace":null} \ No newline at end of file diff --git a/sites/chaines-tv.orange.fr/chaines-tv.orange.fr.test.js b/sites/chaines-tv.orange.fr/chaines-tv.orange.fr.test.js index 9a07e69f2..ae77a27fa 100644 --- a/sites/chaines-tv.orange.fr/chaines-tv.orange.fr.test.js +++ b/sites/chaines-tv.orange.fr/chaines-tv.orange.fr.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./chaines-tv.orange.fr.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -10,8 +12,6 @@ const channel = { site_id: '192', xmltv_id: 'TF1.fr' } -const content = - '{"192":[{"id":1635062528017,"programType":"EPISODE","title":"Tête de liste","channelId":"192","channelZappingNumber":11,"covers":[{"format":"RATIO_16_9","url":"https://proxymedia.woopic.com/340/p/169_EMI_9697669.jpg"},{"format":"RATIO_4_3","url":"https://proxymedia.woopic.com/340/p/43_EMI_9697669.jpg"}],"diffusionDate":1636328100,"duration":2700,"csa":2,"synopsis":"Un tueur en série prend un plaisir pervers à prévenir les autorités de Tallahassee avant chaque nouveau meurtre. Rossi apprend le décès d\'un de ses vieux amis.","languageVersion":"VM","hearingImpaired":true,"audioDescription":false,"season":{"number":10,"episodesCount":23,"serie":{"title":"Esprits criminels"}},"episodeNumber":12,"definition":"SD","links":[{"rel":"SELF","href":"https://rp-live.orange.fr/live-webapp/v3/applications/STB4PC/programs/1635062528017"}],"dayPart":"OTHER","catchupId":null,"genre":"Série","genreDetailed":"Série Suspense"}]}' it('can generate valid url', () => { const result = url({ channel, date }) @@ -21,6 +21,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const result = parser({ date, channel, content }) expect(result).toMatchObject([ { @@ -42,8 +43,7 @@ it('can handle empty guide', () => { const result = parser({ date, channel, - content: - '{"code":60,"message":"Resource not found","param":{},"description":"L\'URI demandé ou la ressource demandée n\'existe pas.","stackTrace":null}' + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) }) expect(result).toMatchObject([]) }) diff --git a/sites/cosmotetv.gr/__data__/content.json b/sites/cosmotetv.gr/__data__/content.json new file mode 100644 index 000000000..392590046 --- /dev/null +++ b/sites/cosmotetv.gr/__data__/content.json @@ -0,0 +1,20 @@ +{ + "channels": [ + { + "items": [ + { + "startTime": "2024-12-26T23:00:00+00:00", + "endTime": "2024-12-27T00:00:00+00:00", + "title": "Τι Λέει ο Νόμος", + "description": "νημερωτική εκπομπή. Συζήτηση με τους εισηγητές των κομμάτων για το νομοθετικό έργο.", + "qoe": { + "genre": "Special" + }, + "thumbnails": { + "standard": "https://gr-ermou-prod-cache05.static.cdn.cosmotetvott.gr/ote-prod/70/280/040029714812000800_1734415727199.jpg" + } + } + ] + } + ] +} \ No newline at end of file diff --git a/sites/cosmotetv.gr/__data__/no_content.json b/sites/cosmotetv.gr/__data__/no_content.json new file mode 100644 index 000000000..029ebb50b --- /dev/null +++ b/sites/cosmotetv.gr/__data__/no_content.json @@ -0,0 +1 @@ +{"date":"2024-12-26","categories":[],"channels":[]} \ No newline at end of file diff --git a/sites/cosmotetv.gr/cosmotetv.gr.test.js b/sites/cosmotetv.gr/cosmotetv.gr.test.js index 405e3d9b0..07beb9f94 100644 --- a/sites/cosmotetv.gr/cosmotetv.gr.test.js +++ b/sites/cosmotetv.gr/cosmotetv.gr.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./cosmotetv.gr.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -13,29 +15,6 @@ jest.mock('axios') const date = dayjs.utc('2024-12-26', 'YYYY-MM-DD').startOf('d') const channel = { site_id: 'vouli', xmltv_id: 'HellenicParliamentTV.gr' } -const mockEpgData = { - channels: [ - { - items: [ - { - startTime: '2024-12-26T23:00:00+00:00', - endTime: '2024-12-27T00:00:00+00:00', - title: 'Τι Λέει ο Νόμος', - description: - 'νημερωτική εκπομπή. Συζήτηση με τους εισηγητές των κομμάτων για το νομοθετικό έργο.', - qoe: { - genre: 'Special' - }, - thumbnails: { - standard: - 'https://gr-ermou-prod-cache05.static.cdn.cosmotetvott.gr/ote-prod/70/280/040029714812000800_1734415727199.jpg' - } - } - ] - } - ] -} - it('can generate valid url', () => { const startOfDay = dayjs(date).startOf('day').utc().unix() const endOfDay = dayjs(date).endOf('day').utc().unix() @@ -45,7 +24,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - const content = JSON.stringify(mockEpgData) + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') const result = parser({ date, content }).map(p => { p.start = dayjs(p.start).toISOString() p.stop = dayjs(p.stop).toISOString() @@ -70,7 +49,7 @@ it('can handle empty guide', () => { const result = parser({ date, channel, - content: '{"date":"2024-12-26","categories":[],"channels":[]}' + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json'), 'utf8') }) expect(result).toMatchObject([]) }) diff --git a/sites/cubmu.com/__data__/content.json b/sites/cubmu.com/__data__/content.json new file mode 100644 index 000000000..d27a2baa6 --- /dev/null +++ b/sites/cubmu.com/__data__/content.json @@ -0,0 +1 @@ +{"result":[{"channel_id":"4028c68574537fcd0174be43042758d8","channel_name":"Trans TV","scehedule_title":"CNN Tech News","schedule_date":"2023-11-05 01:30:00","schedule_end_time":"02:00:00","schedule_json":{"availability":0,"channelId":"4028c68574537fcd0174be43042758d8","channelName":"Trans TV","duration":1800,"editable":true,"episodeName":"","imageUrl":"https://cdnjkt2.transvision.co.id:1001/catchup/schedule/thumbnail/4028c68574537fcd0174be43042758d8/4028c6858b8b3621018b9330e3701a7e/458x640","imageUrlWide":"https://cdnjkt2.transvision.co.id:1001/catchup/schedule/thumbnail/4028c68574537fcd0174be43042758d8/4028c6858b8b3621018b9330e3701a7e/320x180","name":"CNN Tech News","ottImageUrl":"","primarySynopsis":"CNN Indonesia Tech News adalah berita teknologi yang membawa pemirsa ke dunia teknologi yang penuh dengan informasi, pendidikan, hiburan sampai informasi kesehatan terkini.","scheduleId":"4028c6858b8b3621018b9330e3701a7e","scheduleTime":"18:30:00","secondarySynopsis":"CNN Indonesia Tech News is tech news brings viewers into the world of technology that provides information, education, entertainment to the latest health information.","startDt":"20231104183000","url":""},"schedule_start_time":"01:30:00"}]} \ No newline at end of file diff --git a/sites/cubmu.com/cubmu.com.test.js b/sites/cubmu.com/cubmu.com.test.js index cdf5cde34..a12f9e75c 100644 --- a/sites/cubmu.com/cubmu.com.test.js +++ b/sites/cubmu.com/cubmu.com.test.js @@ -1,4 +1,6 @@ const { url, parser } = require('./cubmu.com.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') dayjs.extend(utc) @@ -14,9 +16,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - const content = - '{"result":[{"channel_id":"4028c68574537fcd0174be43042758d8","channel_name":"Trans TV","scehedule_title":"CNN Tech News","schedule_date":"2023-11-05 01:30:00","schedule_end_time":"02:00:00","schedule_json":{"availability":0,"channelId":"4028c68574537fcd0174be43042758d8","channelName":"Trans TV","duration":1800,"editable":true,"episodeName":"","imageUrl":"https://cdnjkt2.transvision.co.id:1001/catchup/schedule/thumbnail/4028c68574537fcd0174be43042758d8/4028c6858b8b3621018b9330e3701a7e/458x640","imageUrlWide":"https://cdnjkt2.transvision.co.id:1001/catchup/schedule/thumbnail/4028c68574537fcd0174be43042758d8/4028c6858b8b3621018b9330e3701a7e/320x180","name":"CNN Tech News","ottImageUrl":"","primarySynopsis":"CNN Indonesia Tech News adalah berita teknologi yang membawa pemirsa ke dunia teknologi yang penuh dengan informasi, pendidikan, hiburan sampai informasi kesehatan terkini.","scheduleId":"4028c6858b8b3621018b9330e3701a7e","scheduleTime":"18:30:00","secondarySynopsis":"CNN Indonesia Tech News is tech news brings viewers into the world of technology that provides information, education, entertainment to the latest health information.","startDt":"20231104183000","url":""},"schedule_start_time":"01:30:00"}]}' - + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') const idResults = parser({ content, channel }) expect(idResults).toMatchObject([ { diff --git a/sites/directv.com.ar/__data__/content.json b/sites/directv.com.ar/__data__/content.json new file mode 100644 index 000000000..fe1cea5c1 --- /dev/null +++ b/sites/directv.com.ar/__data__/content.json @@ -0,0 +1 @@ +{"d":[{"ChannelSection":"","ChannelFullName":"A&E HD","IsFavorite":false,"ChannelName":"A&EHD","ChannelNumber":207,"ProgramList":[{"_channelSection":"","eventId":"120289890767","titleId":"SH0110397700000001","title":"Chicas guapas","programId":null,"description":"Un espacio destinado a la belleza y los distintos estilos de vida, que muestra el trabajo inspiracional de la moda latinoamericana.","episodeTitle":null,"channelNumber":120,"channelName":"AME2","channelFullName":"América TV (ARG)","channelSection":"","contentChannelID":120,"startTime":"/Date(-62135578800000)/","endTime":"/Date(-62135578800000)/","GMTstartTime":"/Date(-62135578800000)/","GMTendTime":"/Date(-62135578800000)/","css":16,"language":null,"tmsId":"SH0110397700000001","rating":"NR","categoryId":"Tipos de Programas","categoryName":0,"subCategoryId":0,"subCategoryName":"Series","serviceExpiration":"/Date(-62135578800000)/","crId":null,"promoUrl1":null,"promoUrl2":null,"price":0,"isPurchasable":"N","videoUrl":"","imageUrl":"https://dnqt2wx2urq99.cloudfront.net/ondirectv/LOGOS/Canales/AR/120.png","titleSecond":"Chicas guapas","isHD":"N","DetailsURL":null,"BuyURL":null,"ProgramServiceId":null,"SearchDateTime":null,"startTimeString":"6/19/2022 12:00:00 AM","endTimeString":"6/19/2022 12:15:00 AM","DurationInMinutes":null,"castDetails":null,"scheduleDetails":null,"seriesDetails":null,"processedSeasonDetails":null}]}]} \ No newline at end of file diff --git a/sites/directv.com.ar/directv.com.ar.test.js b/sites/directv.com.ar/directv.com.ar.test.js index 8128380a7..786950a16 100644 --- a/sites/directv.com.ar/directv.com.ar.test.js +++ b/sites/directv.com.ar/directv.com.ar.test.js @@ -1,4 +1,6 @@ const { parser, url, request } = require('./directv.com.ar.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -45,8 +47,7 @@ it('can generate valid request data', () => { }) it('can parse response', () => { - const content = - '{"d":[{"ChannelSection":"","ChannelFullName":"A&E HD","IsFavorite":false,"ChannelName":"A&EHD","ChannelNumber":207,"ProgramList":[{"_channelSection":"","eventId":"120289890767","titleId":"SH0110397700000001","title":"Chicas guapas","programId":null,"description":"Un espacio destinado a la belleza y los distintos estilos de vida, que muestra el trabajo inspiracional de la moda latinoamericana.","episodeTitle":null,"channelNumber":120,"channelName":"AME2","channelFullName":"América TV (ARG)","channelSection":"","contentChannelID":120,"startTime":"/Date(-62135578800000)/","endTime":"/Date(-62135578800000)/","GMTstartTime":"/Date(-62135578800000)/","GMTendTime":"/Date(-62135578800000)/","css":16,"language":null,"tmsId":"SH0110397700000001","rating":"NR","categoryId":"Tipos de Programas","categoryName":0,"subCategoryId":0,"subCategoryName":"Series","serviceExpiration":"/Date(-62135578800000)/","crId":null,"promoUrl1":null,"promoUrl2":null,"price":0,"isPurchasable":"N","videoUrl":"","imageUrl":"https://dnqt2wx2urq99.cloudfront.net/ondirectv/LOGOS/Canales/AR/120.png","titleSecond":"Chicas guapas","isHD":"N","DetailsURL":null,"BuyURL":null,"ProgramServiceId":null,"SearchDateTime":null,"startTimeString":"6/19/2022 12:00:00 AM","endTimeString":"6/19/2022 12:15:00 AM","DurationInMinutes":null,"castDetails":null,"scheduleDetails":null,"seriesDetails":null,"processedSeasonDetails":null}]}]}' + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const result = parser({ content, channel }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() diff --git a/sites/firstmedia.com/__data__/content.json b/sites/firstmedia.com/__data__/content.json new file mode 100644 index 000000000..c6ec2901d --- /dev/null +++ b/sites/firstmedia.com/__data__/content.json @@ -0,0 +1 @@ +{"data":{"entries":{"243":[{"createdAt":"2023-11-05T17:09:34.000Z","updatedAt":"2023-11-05T17:09:34.000Z","id":"009f3a34-8164-4ff9-b981-9dcab1a518fc","channelNo":"243","programmeId":null,"title":"News Live","episode":null,"slug":"news-live","date":"2023-11-08 17:00:00","startTime":"2023-11-08 20:00:00","endTime":"2023-11-08 20:30:00","length":1800,"description":"News Live","long_description":"Up to date news and analysis from around the world.","status":true,"channel":{"id":"7fd7a9a6-af32-c861-d2b0-4ddc7846fad2","key":"AljaInt","no":243,"name":"Al Jazeera International","slug":"al-jazeera-international","website":null,"description":"

    An international 24-hour English-language It is the first English-language news channel brings you the latest global news stories, analysis from the Middle East & worldwide.

    ","shortDescription":null,"logo":"files/logos/channels/11-NEWS/AlJazeera Int SD-FirstMedia-Chl-243.jpg","externalId":"132","type":"radio","status":true,"chanel":"SD","locale":"id","relationId":"5a6ea4ae-a008-4889-9c68-7a6f1838e81d","onlyfm":null,"genress":[{"id":"1db3bb43-b00d-49af-b272-6c058a8c0b49","name":"International Free View"},{"id":"2e81a4bd-9719-4186-820a-7e035e07be13","name":"News"}]}}]}}} \ No newline at end of file diff --git a/sites/firstmedia.com/firstmedia.com.test.js b/sites/firstmedia.com/firstmedia.com.test.js index 19292b3f8..21bdd7b87 100644 --- a/sites/firstmedia.com/firstmedia.com.test.js +++ b/sites/firstmedia.com/firstmedia.com.test.js @@ -1,4 +1,6 @@ const { url, parser } = require('./firstmedia.com.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') dayjs.extend(utc) @@ -13,8 +15,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - const content = - '{"data":{"entries":{"243":[{"createdAt":"2023-11-05T17:09:34.000Z","updatedAt":"2023-11-05T17:09:34.000Z","id":"009f3a34-8164-4ff9-b981-9dcab1a518fc","channelNo":"243","programmeId":null,"title":"News Live","episode":null,"slug":"news-live","date":"2023-11-08 17:00:00","startTime":"2023-11-08 20:00:00","endTime":"2023-11-08 20:30:00","length":1800,"description":"News Live","long_description":"Up to date news and analysis from around the world.","status":true,"channel":{"id":"7fd7a9a6-af32-c861-d2b0-4ddc7846fad2","key":"AljaInt","no":243,"name":"Al Jazeera International","slug":"al-jazeera-international","website":null,"description":"

    An international 24-hour English-language It is the first English-language news channel brings you the latest global news stories, analysis from the Middle East & worldwide.

    ","shortDescription":null,"logo":"files/logos/channels/11-NEWS/AlJazeera Int SD-FirstMedia-Chl-243.jpg","externalId":"132","type":"radio","status":true,"chanel":"SD","locale":"id","relationId":"5a6ea4ae-a008-4889-9c68-7a6f1838e81d","onlyfm":null,"genress":[{"id":"1db3bb43-b00d-49af-b272-6c058a8c0b49","name":"International Free View"},{"id":"2e81a4bd-9719-4186-820a-7e035e07be13","name":"News"}]}}]}}}' + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') const results = parser({ content, channel, date }) // All time in Asia/Jakarta diff --git a/sites/foxsports.com.au/__data__/content.json b/sites/foxsports.com.au/__data__/content.json new file mode 100644 index 000000000..a2affed50 --- /dev/null +++ b/sites/foxsports.com.au/__data__/content.json @@ -0,0 +1 @@ +{"channel-programme":[{"id":"31cc8b4c-3711-49f0-bf22-2ec3993b0a07","programmeTitle":"NRL","title":"Eels v Titans","startTime":"2022-12-14T00:00:00+11:00","endTime":"2022-12-14T01:00:00+11:00","duration":60,"live":false,"genreId":"5c389cf4-8db7-4b52-9773-52355bd28559","channelId":2,"channelName":"FOX League","channelAbbreviation":"LEAGUE","programmeUID":235220,"round":"R1","statsMatchId":null,"closedCaptioned":true,"statsFixtureId":10207,"genreTitle":"Rugby League","parentGenreId":"a953f929-2d12-41a4-b0e9-97f401afff11","parentGenreTitle":"Sport","pmgId":"PMG01306944","statsSport":"league","type":"GAME","hiDef":true,"widescreen":true,"classification":"","synopsis":"The Eels and Titans have plenty of motivation this season after heartbreaking Finals losses in 2021. Parramatta has won their past five against Gold Coast.","preGameStartTime":null,"closeCaptioned":true}]} \ No newline at end of file diff --git a/sites/foxsports.com.au/__data__/no_content.json b/sites/foxsports.com.au/__data__/no_content.json new file mode 100644 index 000000000..572d87104 --- /dev/null +++ b/sites/foxsports.com.au/__data__/no_content.json @@ -0,0 +1 @@ +{"channel-programme":[]} \ No newline at end of file diff --git a/sites/foxsports.com.au/foxsports.com.au.test.js b/sites/foxsports.com.au/foxsports.com.au.test.js index eecfffd00..0cf3431d3 100644 --- a/sites/foxsports.com.au/foxsports.com.au.test.js +++ b/sites/foxsports.com.au/foxsports.com.au.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./foxsports.com.au.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') dayjs.extend(utc) @@ -15,9 +17,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - const content = - '{"channel-programme":[{"id":"31cc8b4c-3711-49f0-bf22-2ec3993b0a07","programmeTitle":"NRL","title":"Eels v Titans","startTime":"2022-12-14T00:00:00+11:00","endTime":"2022-12-14T01:00:00+11:00","duration":60,"live":false,"genreId":"5c389cf4-8db7-4b52-9773-52355bd28559","channelId":2,"channelName":"FOX League","channelAbbreviation":"LEAGUE","programmeUID":235220,"round":"R1","statsMatchId":null,"closedCaptioned":true,"statsFixtureId":10207,"genreTitle":"Rugby League","parentGenreId":"a953f929-2d12-41a4-b0e9-97f401afff11","parentGenreTitle":"Sport","pmgId":"PMG01306944","statsSport":"league","type":"GAME","hiDef":true,"widescreen":true,"classification":"","synopsis":"The Eels and Titans have plenty of motivation this season after heartbreaking Finals losses in 2021. Parramatta has won their past five against Gold Coast.","preGameStartTime":null,"closeCaptioned":true}]}' - + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const result = parser({ content, channel }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() @@ -38,11 +38,6 @@ it('can parse response', () => { }) it('can handle empty guide', () => { - const result = parser( - { - content: '{"channel-programme":[]}' - }, - channel - ) + const result = parser({content: ''}, channel) expect(result).toMatchObject([]) }) diff --git a/sites/freetv.tv/__data__/content.json b/sites/freetv.tv/__data__/content.json new file mode 100644 index 000000000..3548be091 --- /dev/null +++ b/sites/freetv.tv/__data__/content.json @@ -0,0 +1 @@ +[{"id":5856194,"publicUid":"f17b5a1d-3f2a-42a0-a11b-a08e5c9785fd","title":"בוש 4 - פרק 3","lead":"עונה 4 חדשה לדרמה הבלשית. 3. השטן בתוך הבית: הכוח המיוחד מנסה לחקור, ומגלה אליבי שקרי עם השלכות מרעישות. הבלש סנטיאגו רוברטסון צריך לשים את קשריו האישיים בצד למען החקירה. בוש זוכה לביקור פתע לילי.כ עב","description":"עונה 4 חדשה לדרמה הבלשית. 3. השטן בתוך הבית: הכוח המיוחד מנסה לחקור, ומגלה אליבי שקרי עם השלכות מרעישות. הבלש סנטיאגו רוברטסון צריך לשים את קשריו האישיים בצד למען החקירה. בוש זוכה לביקור פתע לילי.כ עב","rating":14,"ratingEmbedded":false,"type":"PROGRAMME","uhd":false,"since":"2025-03-27T21:26:00Z","till":"2025-03-27T22:17:00Z","images":{"16x9":[{"url":"//d1zqtf09wb8nt5.cloudfront.net/scale/oil/freetv/upload/programme/1361162/COVER/images/1361162_1736767668746.jpg?dsth=177&dstw=315&srcmode=0&srcx=0&srcy=0&quality=65&type=1&srcw=1/1&srch=1/1","templateUrl":"//d1zqtf09wb8nt5.cloudfront.net/scale/oil/freetv/upload/programme/1361162/COVER/images/1361162_1736767668746.jpg?srcmode=0&srcx=0&srcy=0&quality=65&type=1&srcw=1/1&srch=1/1&dsth={height:177}&dstw={width:315}"}]},"genres":[],"webUrl":"https://web.freetv.tv/news,1991/rw-11,3370462/2025-03-27/bw-4---prq-3,5856194","trailer":false,"live":{"type_":"LIVE","id":3370462},"available":true,"timeshiftAvailable":false,"startoverAvailable":false,"catchupTill":"2025-04-10T21:26:00Z","npvrTill":"2025-04-10T21:26:00Z","programRecordingId":7068534,"audio":false,"video":true,"showRecommendations":false,"recommended":false,"premiere":false,"liveBroadcast":false,"slug":"bw-4---prq-3"},{"id":5858584,"publicUid":"1dc296c9-0c28-4202-8b20-cbef56a763f5","title":"אבא משתדל - 5. חבר","lead":"סדרה קומית. יוסי מכיר אב לילד עם צרכים מיוחדים ובין השניים מתפתח קשר בסגנון חיזור גורלי שמערער את יוסי. כ עב.","description":"סדרה קומית. יוסי מכיר אב לילד עם צרכים מיוחדים ובין השניים מתפתח קשר בסגנון חיזור גורלי שמערער את יוסי. כ עב.","ratingEmbedded":false,"type":"PROGRAMME","uhd":false,"since":"2025-03-27T22:17:00Z","till":"2025-03-27T22:43:00Z","images":{"16x9":[{"url":"//d1zqtf09wb8nt5.cloudfront.net/scale/oil/freetv/upload/programme/1070668/COVER/images/1070668_1742202219830.jpg?dsth=177&dstw=315&srcmode=0&srcx=0&srcy=0&quality=65&type=1&srcw=1/1&srch=1/1","templateUrl":"//d1zqtf09wb8nt5.cloudfront.net/scale/oil/freetv/upload/programme/1070668/COVER/images/1070668_1742202219830.jpg?srcmode=0&srcx=0&srcy=0&quality=65&type=1&srcw=1/1&srch=1/1&dsth={height:177}&dstw={width:315}"}]},"genres":[],"webUrl":"https://web.freetv.tv/news,1991/rw-11,3370462/2025-03-27/b-mdl---5-br,5858584","trailer":false,"live":{"type_":"LIVE","id":3370462},"available":true,"timeshiftAvailable":false,"startoverAvailable":false,"catchupTill":"2025-04-10T22:17:00Z","npvrTill":"2025-04-10T22:17:00Z","programRecordingId":7071052,"audio":false,"video":true,"showRecommendations":false,"recommended":false,"premiere":false,"liveBroadcast":false,"slug":"b-mdl---5-br"}] \ No newline at end of file diff --git a/sites/freetv.tv/freetv.tv.test.js b/sites/freetv.tv/freetv.tv.test.js index e65c3cfe5..cbb3a4437 100644 --- a/sites/freetv.tv/freetv.tv.test.js +++ b/sites/freetv.tv/freetv.tv.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./freetv.tv.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -16,8 +18,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - const content = '[{"id":5856194,"publicUid":"f17b5a1d-3f2a-42a0-a11b-a08e5c9785fd","title":"בוש 4 - פרק 3","lead":"עונה 4 חדשה לדרמה הבלשית. 3. השטן בתוך הבית: הכוח המיוחד מנסה לחקור, ומגלה אליבי שקרי עם השלכות מרעישות. הבלש סנטיאגו רוברטסון צריך לשים את קשריו האישיים בצד למען החקירה. בוש זוכה לביקור פתע לילי.כ עב","description":"עונה 4 חדשה לדרמה הבלשית. 3. השטן בתוך הבית: הכוח המיוחד מנסה לחקור, ומגלה אליבי שקרי עם השלכות מרעישות. הבלש סנטיאגו רוברטסון צריך לשים את קשריו האישיים בצד למען החקירה. בוש זוכה לביקור פתע לילי.כ עב","rating":14,"ratingEmbedded":false,"type":"PROGRAMME","uhd":false,"since":"2025-03-27T21:26:00Z","till":"2025-03-27T22:17:00Z","images":{"16x9":[{"url":"//d1zqtf09wb8nt5.cloudfront.net/scale/oil/freetv/upload/programme/1361162/COVER/images/1361162_1736767668746.jpg?dsth=177&dstw=315&srcmode=0&srcx=0&srcy=0&quality=65&type=1&srcw=1/1&srch=1/1","templateUrl":"//d1zqtf09wb8nt5.cloudfront.net/scale/oil/freetv/upload/programme/1361162/COVER/images/1361162_1736767668746.jpg?srcmode=0&srcx=0&srcy=0&quality=65&type=1&srcw=1/1&srch=1/1&dsth={height:177}&dstw={width:315}"}]},"genres":[],"webUrl":"https://web.freetv.tv/news,1991/rw-11,3370462/2025-03-27/bw-4---prq-3,5856194","trailer":false,"live":{"type_":"LIVE","id":3370462},"available":true,"timeshiftAvailable":false,"startoverAvailable":false,"catchupTill":"2025-04-10T21:26:00Z","npvrTill":"2025-04-10T21:26:00Z","programRecordingId":7068534,"audio":false,"video":true,"showRecommendations":false,"recommended":false,"premiere":false,"liveBroadcast":false,"slug":"bw-4---prq-3"},{"id":5858584,"publicUid":"1dc296c9-0c28-4202-8b20-cbef56a763f5","title":"אבא משתדל - 5. חבר","lead":"סדרה קומית. יוסי מכיר אב לילד עם צרכים מיוחדים ובין השניים מתפתח קשר בסגנון חיזור גורלי שמערער את יוסי. כ עב.","description":"סדרה קומית. יוסי מכיר אב לילד עם צרכים מיוחדים ובין השניים מתפתח קשר בסגנון חיזור גורלי שמערער את יוסי. כ עב.","ratingEmbedded":false,"type":"PROGRAMME","uhd":false,"since":"2025-03-27T22:17:00Z","till":"2025-03-27T22:43:00Z","images":{"16x9":[{"url":"//d1zqtf09wb8nt5.cloudfront.net/scale/oil/freetv/upload/programme/1070668/COVER/images/1070668_1742202219830.jpg?dsth=177&dstw=315&srcmode=0&srcx=0&srcy=0&quality=65&type=1&srcw=1/1&srch=1/1","templateUrl":"//d1zqtf09wb8nt5.cloudfront.net/scale/oil/freetv/upload/programme/1070668/COVER/images/1070668_1742202219830.jpg?srcmode=0&srcx=0&srcy=0&quality=65&type=1&srcw=1/1&srch=1/1&dsth={height:177}&dstw={width:315}"}]},"genres":[],"webUrl":"https://web.freetv.tv/news,1991/rw-11,3370462/2025-03-27/b-mdl---5-br,5858584","trailer":false,"live":{"type_":"LIVE","id":3370462},"available":true,"timeshiftAvailable":false,"startoverAvailable":false,"catchupTill":"2025-04-10T22:17:00Z","npvrTill":"2025-04-10T22:17:00Z","programRecordingId":7071052,"audio":false,"video":true,"showRecommendations":false,"recommended":false,"premiere":false,"liveBroadcast":false,"slug":"b-mdl---5-br"}]' - + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const results = parser({ content }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() diff --git a/sites/frikanalen.no/__data__/content.json b/sites/frikanalen.no/__data__/content.json new file mode 100644 index 000000000..0f01cdcd0 --- /dev/null +++ b/sites/frikanalen.no/__data__/content.json @@ -0,0 +1 @@ +{"count":83,"next":null,"previous":null,"results":[{"id":135605,"video":{"id":626094,"name":"FSCONS 2017 - Keynote: TBA - Linda Sandvik","header":"Linda Sandvik's keynote at FSCONS 2017\\r\\n\\r\\nRecorded by NUUG for FSCONS.","description":null,"creator":"davidwnoble@gmail.com","organization":{"id":82,"name":"NUUG","homepage":"https://www.nuug.no/","description":"Forening NUUG er for alle som er interessert i fri programvare, åpne standarder og Unix-lignende operativsystemer.","postalAddress":"","streetAddress":"","editorId":2148,"editorName":"David Noble","editorEmail":"davidwnoble@gmail.com","editorMsisdn":"","fkmember":true},"duration":"00:57:55.640000","categories":["Samfunn"]},"schedulereason":5,"starttime":"2022-01-19T00:47:00+01:00","endtime":"2022-01-19T01:44:55.640000+01:00","duration":"00:57:55.640000"}]} \ No newline at end of file diff --git a/sites/frikanalen.no/__data__/no_content.json b/sites/frikanalen.no/__data__/no_content.json new file mode 100644 index 000000000..1401014b5 --- /dev/null +++ b/sites/frikanalen.no/__data__/no_content.json @@ -0,0 +1 @@ +{"count":0,"next":null,"previous":null,"results":[]} \ No newline at end of file diff --git a/sites/frikanalen.no/frikanalen.no.test.js b/sites/frikanalen.no/frikanalen.no.test.js index ff758ddac..6c4617925 100644 --- a/sites/frikanalen.no/frikanalen.no.test.js +++ b/sites/frikanalen.no/frikanalen.no.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./frikanalen.no.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -18,8 +20,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - const content = - '{"count":83,"next":null,"previous":null,"results":[{"id":135605,"video":{"id":626094,"name":"FSCONS 2017 - Keynote: TBA - Linda Sandvik","header":"Linda Sandvik\'s keynote at FSCONS 2017\\r\\n\\r\\nRecorded by NUUG for FSCONS.","description":null,"creator":"davidwnoble@gmail.com","organization":{"id":82,"name":"NUUG","homepage":"https://www.nuug.no/","description":"Forening NUUG er for alle som er interessert i fri programvare, åpne standarder og Unix-lignende operativsystemer.","postalAddress":"","streetAddress":"","editorId":2148,"editorName":"David Noble","editorEmail":"davidwnoble@gmail.com","editorMsisdn":"","fkmember":true},"duration":"00:57:55.640000","categories":["Samfunn"]},"schedulereason":5,"starttime":"2022-01-19T00:47:00+01:00","endtime":"2022-01-19T01:44:55.640000+01:00","duration":"00:57:55.640000"}]}' + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const result = parser({ content }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() @@ -41,7 +42,7 @@ it('can handle empty guide', () => { const result = parser({ date, channel, - content: '{"count":0,"next":null,"previous":null,"results":[]}' + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) }) expect(result).toMatchObject([]) }) diff --git a/sites/galamtv.kz/__data__/content.json b/sites/galamtv.kz/__data__/content.json new file mode 100644 index 000000000..2fedf8bb2 --- /dev/null +++ b/sites/galamtv.kz/__data__/content.json @@ -0,0 +1,21 @@ +{ + "programs": [ + { + "metaInfo": { + "title": "Гимн", + "description": "Государственный гимн Республики Казахстан" + }, + "scheduleInfo": { + "start": 1736470800, + "end": 1736471100 + }, + "mediaInfo": { + "thumbnails": [ + { + "url": "http://galam.server-img.lfstrm.tv:80/image/aHR0cDovL2dhbGFtLmltZy1vcmlnaW5hbHMubGZzdHJtLnR2OjgwL3R2aW1hZ2VzL3RodW1iL2YyNWFmYWY2ZDkzYjU5YjdkMjBiZDNiODhiZjg4NWI0X29yaWcuanBn" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/sites/galamtv.kz/__data__/no_content.json b/sites/galamtv.kz/__data__/no_content.json new file mode 100644 index 000000000..bfa842bba --- /dev/null +++ b/sites/galamtv.kz/__data__/no_content.json @@ -0,0 +1 @@ +{"programs":[]} \ No newline at end of file diff --git a/sites/galamtv.kz/galamtv.kz.test.js b/sites/galamtv.kz/galamtv.kz.test.js index 8842e6543..8caca3f96 100644 --- a/sites/galamtv.kz/galamtv.kz.test.js +++ b/sites/galamtv.kz/galamtv.kz.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./galamtv.kz.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -21,28 +23,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - const content = JSON.stringify({ - programs: [ - { - metaInfo: { - title: 'Гимн', - description: 'Государственный гимн Республики Казахстан' - }, - scheduleInfo: { - start: 1736470800, - end: 1736471100 - }, - mediaInfo: { - thumbnails: [ - { - url: 'http://galam.server-img.lfstrm.tv:80/image/aHR0cDovL2dhbGFtLmltZy1vcmlnaW5hbHMubGZzdHJtLnR2OjgwL3R2aW1hZ2VzL3RodW1iL2YyNWFmYWY2ZDkzYjU5YjdkMjBiZDNiODhiZjg4NWI0X29yaWcuanBn' - } - ] - } - } - ] - }) - + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const result = parser({ content, channel }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() @@ -65,7 +46,7 @@ it('can handle empty guide', () => { const result = parser({ date, channel, - content: '{"programs":[]}' + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) }) expect(result).toMatchObject([]) }) diff --git a/sites/guidatv.sky.it/__data__/content.json b/sites/guidatv.sky.it/__data__/content.json new file mode 100644 index 000000000..a391e8bf8 --- /dev/null +++ b/sites/guidatv.sky.it/__data__/content.json @@ -0,0 +1 @@ +{"events": [ { "channel": { "id": 10458, "logo": "/logo/545820mediasethd_Light_Fit.png", "logoPadding": "/logo/545820mediasethd_Light_Padding.png", "logoDark": "/logo/545820mediasethd_Dark_Fit.png", "logoDarkPadding": "/logo/545820mediasethd_Dark_Padding.png", "logoLight": "/logo/545820mediasethd_Light_Padding.png", "name": "20Mediaset HD", "number": 151, "category": { "id": 3, "name": "Intrattenimento" } }, "content": { "uuid": "77c630aa-4744-44cb-a88e-3e871c6b73d9", "contentTitle": "Distretto di Polizia", "episodeNumber": 26, "seasonNumber": 6, "url": "/serie-tv/distretto-di-polizia/stagione-6/episodio-26/77c630aa-4744-44cb-a88e-3e871c6b73d9", "genre": { "id": 1, "name": "Intrattenimento" }, "subgenre": { "id": 9, "name": "Fiction" }, "imagesMap": [ { "key": "background", "img": { "url": "/uuid/77c630aa-4744-44cb-a88e-3e871c6b73d9/background?md5ChecksumParam=88d3f48ce855316f4be25ab9bb846d32" } }, { "key": "cover", "img": { "url": "/uuid/77c630aa-4744-44cb-a88e-3e871c6b73d9/cover?md5ChecksumParam=61135b999a63e3d3f4a933b9edeb0c1b" } }, { "key": "scene", "img": { "url": "/uuid/77c630aa-4744-44cb-a88e-3e871c6b73d9/16-9?md5ChecksumParam=f41bfe414bec32505abdab19d00b8b43" } } ] }, "eventId": "139585132", "starttime": "2022-05-06T00:35:40Z", "endtime": "2022-05-06T01:15:40Z", "eventTitle": "Distretto di Polizia", "eventSynopsis": "S6 Ep26 La resa dei conti - Fino all'ultimo la sfida tra Ardenzi e Carrano, nemici di vecchia data, riserva clamorosi colpi di scena. E si scopre che non e' tutto come sembrava.", "epgEventTitle": "S6 Ep26 - Distretto di Polizia", "primeVision": false, "resolutions": [ { "resolutionType": "resolution4k", "value": false } ] }]} \ No newline at end of file diff --git a/sites/guidatv.sky.it/__data__/no_content.json b/sites/guidatv.sky.it/__data__/no_content.json new file mode 100644 index 000000000..33ba57bf0 --- /dev/null +++ b/sites/guidatv.sky.it/__data__/no_content.json @@ -0,0 +1 @@ +{"events":[],"total":0} \ No newline at end of file diff --git a/sites/guidatv.sky.it/guidatv.sky.it.test.js b/sites/guidatv.sky.it/guidatv.sky.it.test.js index a114c1e0b..68a7ebbe7 100644 --- a/sites/guidatv.sky.it/guidatv.sky.it.test.js +++ b/sites/guidatv.sky.it/guidatv.sky.it.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./guidatv.sky.it.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -18,8 +20,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - const content = - '{"events": [ { "channel": { "id": 10458, "logo": "/logo/545820mediasethd_Light_Fit.png", "logoPadding": "/logo/545820mediasethd_Light_Padding.png", "logoDark": "/logo/545820mediasethd_Dark_Fit.png", "logoDarkPadding": "/logo/545820mediasethd_Dark_Padding.png", "logoLight": "/logo/545820mediasethd_Light_Padding.png", "name": "20Mediaset HD", "number": 151, "category": { "id": 3, "name": "Intrattenimento" } }, "content": { "uuid": "77c630aa-4744-44cb-a88e-3e871c6b73d9", "contentTitle": "Distretto di Polizia", "episodeNumber": 26, "seasonNumber": 6, "url": "/serie-tv/distretto-di-polizia/stagione-6/episodio-26/77c630aa-4744-44cb-a88e-3e871c6b73d9", "genre": { "id": 1, "name": "Intrattenimento" }, "subgenre": { "id": 9, "name": "Fiction" }, "imagesMap": [ { "key": "background", "img": { "url": "/uuid/77c630aa-4744-44cb-a88e-3e871c6b73d9/background?md5ChecksumParam=88d3f48ce855316f4be25ab9bb846d32" } }, { "key": "cover", "img": { "url": "/uuid/77c630aa-4744-44cb-a88e-3e871c6b73d9/cover?md5ChecksumParam=61135b999a63e3d3f4a933b9edeb0c1b" } }, { "key": "scene", "img": { "url": "/uuid/77c630aa-4744-44cb-a88e-3e871c6b73d9/16-9?md5ChecksumParam=f41bfe414bec32505abdab19d00b8b43" } } ] }, "eventId": "139585132", "starttime": "2022-05-06T00:35:40Z", "endtime": "2022-05-06T01:15:40Z", "eventTitle": "Distretto di Polizia", "eventSynopsis": "S6 Ep26 La resa dei conti - Fino all\'ultimo la sfida tra Ardenzi e Carrano, nemici di vecchia data, riserva clamorosi colpi di scena. E si scopre che non e\' tutto come sembrava.", "epgEventTitle": "S6 Ep26 - Distretto di Polizia", "primeVision": false, "resolutions": [ { "resolutionType": "resolution4k", "value": false } ] }]}' + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const result = parser({ content }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() @@ -45,7 +46,7 @@ it('can parse response', () => { it('can handle empty guide', () => { const result = parser({ - content: '{"events":[],"total":0}' + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) }) expect(result).toMatchObject([]) }) diff --git a/sites/horizon.tv/__data__/content.json b/sites/horizon.tv/__data__/content.json new file mode 100644 index 000000000..663646ab6 --- /dev/null +++ b/sites/horizon.tv/__data__/content.json @@ -0,0 +1 @@ +{"entryCount":184,"totalResults":184,"updated":1675790518889,"expires":1675791343825,"title":"EPG","periods":4,"periodStartTime":1675724400000,"periodEndTime":1675746000000,"entries":[{"o":"lgi-obolite-sk-prod-master:10024","l":[{"i":"crid:~~2F~~2Fport.cs~~2F122941980,imi:7ca159c917344e0dd3fbe1cd8db5ff8043d96a78","t":"Avengement","s":1675719300000,"e":1675724700000,"c":"lgi-obolite-sk-prod-master:genre-9","a":false,"r":true,"rm":true,"rs":0,"re":604800,"rst":"cloud","ra":false,"ad":[],"sl":[]}]}]} \ No newline at end of file diff --git a/sites/horizon.tv/__data__/content_1.json b/sites/horizon.tv/__data__/content_1.json new file mode 100644 index 000000000..9dd620fb5 --- /dev/null +++ b/sites/horizon.tv/__data__/content_1.json @@ -0,0 +1 @@ +{"entryCount":184,"totalResults":184,"updated":1675790518889,"expires":1675791376097,"title":"EPG","periods":4,"periodStartTime":1675746000000,"periodEndTime":1675767600000,"entries":[{"o":"lgi-obolite-sk-prod-master:10024","l":[{"i":"crid:~~2F~~2Fport.cs~~2F248281986,imi:e85129f9d1e211406a521df7a36f22237c22651b","t":"Zoom In","s":1675744500000,"e":1675746000000,"c":"lgi-obolite-sk-prod-master:genre-21","a":false,"r":true,"rm":true,"rs":0,"re":604800,"rst":"cloud","ra":false,"ad":[],"sl":[]}]}]} \ No newline at end of file diff --git a/sites/horizon.tv/__data__/content_2.json b/sites/horizon.tv/__data__/content_2.json new file mode 100644 index 000000000..b2265e4c8 --- /dev/null +++ b/sites/horizon.tv/__data__/content_2.json @@ -0,0 +1 @@ +{"entryCount":184,"totalResults":184,"updated":1675789948804,"expires":1675791024984,"title":"EPG","periods":4,"periodStartTime":1675767600000,"periodEndTime":1675789200000,"entries":[{"o":"lgi-obolite-sk-prod-master:10024","l":[{"i":"crid:~~2F~~2Fport.cs~~2F1379541,imi:5f806a2a0bc13e9745e14907a27116c60ea2c6ad","t":"Studentka","s":1675761000000,"e":1675767600000,"c":"lgi-obolite-sk-prod-master:genre-14","a":false,"r":true,"rm":true,"rs":0,"re":604800,"rst":"cloud","ra":false,"ad":[],"sl":[]}]}]} \ No newline at end of file diff --git a/sites/horizon.tv/__data__/content_3.json b/sites/horizon.tv/__data__/content_3.json new file mode 100644 index 000000000..d09b3423b --- /dev/null +++ b/sites/horizon.tv/__data__/content_3.json @@ -0,0 +1 @@ +{"entryCount":184,"totalResults":184,"updated":1675789948804,"expires":1675790973469,"title":"EPG","periods":4,"periodStartTime":1675789200000,"periodEndTime":1675810800000,"entries":[{"o":"lgi-obolite-sk-prod-master:10024","l":[{"i":"crid:~~2F~~2Fport.cs~~2F71927954,imi:f1b4b0285b72cf44cba74e1c62322a4c682385c7","t":"Zilionáři","s":1675785900000,"e":1675791900000,"c":"lgi-obolite-sk-prod-master:genre-9","a":false,"r":true,"rm":true,"rs":0,"re":604800,"rst":"cloud","ra":false,"ad":[],"sl":[]}]}]} \ No newline at end of file diff --git a/sites/horizon.tv/__data__/content_listings_1.json b/sites/horizon.tv/__data__/content_listings_1.json new file mode 100644 index 000000000..991a55bd8 --- /dev/null +++ b/sites/horizon.tv/__data__/content_listings_1.json @@ -0,0 +1 @@ +{"id":"crid:~~2F~~2Fport.cs~~2F122941980,imi:7ca159c917344e0dd3fbe1cd8db5ff8043d96a78","startTime":1675719300000,"endTime":1675724700000,"actualStartTime":1675719300000,"actualEndTime":1675724700000,"expirationDate":1676324100000,"stationId":"lgi-obolite-sk-prod-master:10024","imi":"imi:7ca159c917344e0dd3fbe1cd8db5ff8043d96a78","scCridImi":"crid:~~2F~~2Fport.cs~~2F122941980,imi:7ca159c917344e0dd3fbe1cd8db5ff8043d96a78","mediaGroupId":"crid:~~2F~~2Fport.cs~~2F122941980","program":{"id":"crid:~~2F~~2Fport.cs~~2F122941980","title":"Avengement","description":"Během propustky z vězení za účelem návštěvy umírající matky v nemocnici zločinec Cain Burgess (Scott Adkins) unikne svým dozorcům a mizí v ulicích Londýna. Jde o epickou cestu krve a bolesti za...","longDescription":"Během propustky z vězení za účelem návštěvy umírající matky v nemocnici zločinec Cain Burgess (Scott Adkins) unikne svým dozorcům a mizí v ulicích Londýna. Jde o epickou cestu krve a bolesti za dosažením vytoužené pomsty na těch, kteří z něj udělali chladnokrevného vraha.","medium":"Movie","categories":[{"id":"lgi-obolite-sk-prod-master:genre-9","title":"Drama","scheme":"urn:libertyglobal:metadata:cs:ContentCS:2014_1"},{"id":"lgi-obolite-sk-prod-master:genre-33","title":"Akcia","scheme":"urn:libertyglobal:metadata:cs:ContentCS:2014_1"}],"isAdult":false,"parentalRating":"18","cast":["Scott Adkins","Craig Fairbrass","Thomas Turgoose","Nick Moran","Kierston Wareing","Leo Gregory","Mark Strange","Luke LaFontaine","Beau Fowler","Dan Styles","Christopher Sciueref","Matt Routledge","Jane Thorne","Louis Mandylor","Terence Maynard","Greg Burridge","Michael Higgs","Damian Gallagher","Daniel Adegboyega","John Ioannou","Sofie Golding-Spittle","Joe Egan","Darren Swain","Lee Charles","Dominic Kinnaird","Ross O'Hennessy","Teresa Mahoney","Andrew Dunkelberger","Sam Hardy","Ivan Moy","Mark Sears","Phillip Ray Tommy"],"directors":["Jesse V. Johnson"],"images":[{"assetType":"HighResLandscape","assetTypes":["HighResLandscape"],"url":"http://62.179.125.152/SK/Images/hrl_3fa8387df870473fdacb1024635b52b2496b159c.jpg"},{"assetType":"HighResPortrait","assetTypes":["HighResPortrait"],"url":"http://62.179.125.152/SK/Images/hrp_19e3a660e637cd39e31046c284a66b3a95d698e4.jpg"},{"assetType":"boxCover","assetTypes":["boxCover"],"url":"http://62.179.125.152/SK/Images/bc_939160772e45a783fb3a19970696f5ebcb6e568b.jpg"},{"assetType":"boxart-small","assetTypes":["boxart-small"],"url":"http://62.179.125.152/SK/Images/bc_939160772e45a783fb3a19970696f5ebcb6e568b.jpg?w=75&h=108&mode=box"},{"assetType":"boxart-medium","assetTypes":["boxart-medium"],"url":"http://62.179.125.152/SK/Images/bc_939160772e45a783fb3a19970696f5ebcb6e568b.jpg?w=110&h=159&mode=box"},{"assetType":"boxart-xlarge","assetTypes":["boxart-xlarge"],"url":"http://62.179.125.152/SK/Images/bc_939160772e45a783fb3a19970696f5ebcb6e568b.jpg?w=210&h=303&mode=box"},{"assetType":"boxart-large","assetTypes":["boxart-large"],"url":"http://62.179.125.152/SK/Images/bc_939160772e45a783fb3a19970696f5ebcb6e568b.jpg?w=180&h=260&mode=box"}],"rootId":"crid:~~2F~~2Fport.cs~~2F122941980","parentalRatingDescription":[],"resolutions":[],"mediaGroupId":"crid:~~2F~~2Fport.cs~~2F122941980","shortDescription":"Během propustky z vězení za účelem návštěvy umírající matky v nemocnici zločinec Cain Burgess (Scott Adkins) unikne svým dozorcům a mizí v ulicích Londýna. Jde o epickou cestu krve a bolesti za...","mediaType":"FeatureFilm","year":"2019","videos":[],"videoStreams":[],"entitlements":["VIP","_OPEN_"],"currentProductIds":[],"currentTvodProductIds":[]},"rootId":"crid:~~2F~~2Fport.cs~~2F122941980","replayTvAvailable":true,"audioTracks":[],"ratings":[],"offersLatestExpirationDate":1676247300000,"replayTvStartOffset":0,"replayTvEndOffset":604800,"replayEnabledOnMobileClients":true,"replaySource":"cloud","isGoReplayableViaExternalApp":false} \ No newline at end of file diff --git a/sites/horizon.tv/__data__/content_listings_2.json b/sites/horizon.tv/__data__/content_listings_2.json new file mode 100644 index 000000000..5b33c1250 --- /dev/null +++ b/sites/horizon.tv/__data__/content_listings_2.json @@ -0,0 +1 @@ +{"id":"crid:~~2F~~2Fport.cs~~2F248281986,imi:e85129f9d1e211406a521df7a36f22237c22651b","startTime":1675744500000,"endTime":1675746000000,"actualStartTime":1675744500000,"actualEndTime":1675746000000,"expirationDate":1676349300000,"stationId":"lgi-obolite-sk-prod-master:10024","imi":"imi:e85129f9d1e211406a521df7a36f22237c22651b","scCridImi":"crid:~~2F~~2Fport.cs~~2F248281986,imi:e85129f9d1e211406a521df7a36f22237c22651b","mediaGroupId":"crid:~~2F~~2Fport.cs~~2F41764266","program":{"id":"crid:~~2F~~2Fport.cs~~2F248281986","title":"Zoom In","description":"Film/Kino","longDescription":"Film/Kino","medium":"TV","categories":[{"id":"lgi-obolite-sk-prod-master:genre-21","title":"Hudba a umenie","scheme":"urn:libertyglobal:metadata:cs:ContentCS:2014_1"},{"id":"lgi-obolite-sk-prod-master:genre-14","title":"Film","scheme":"urn:libertyglobal:metadata:cs:ContentCS:2014_1"}],"isAdult":false,"parentalRating":"9","cast":[],"directors":[],"images":[{"assetType":"HighResLandscape","assetTypes":["HighResLandscape"],"url":"http://62.179.125.152/SK/Images/hrl_cbed64b557e83227a2292604cbcae2d193877b1c.jpg"},{"assetType":"HighResPortrait","assetTypes":["HighResPortrait"],"url":"http://62.179.125.152/SK/Images/hrp_cfe405e669385365846b69196e1e94caa3e60de0.jpg"},{"assetType":"boxCover","assetTypes":["boxCover"],"url":"http://62.179.125.152/SK/Images/bc_cfe405e669385365846b69196e1e94caa3e60de0.jpg"},{"assetType":"boxart-small","assetTypes":["boxart-small"],"url":"http://62.179.125.152/SK/Images/bc_cfe405e669385365846b69196e1e94caa3e60de0.jpg?w=75&h=108&mode=box"},{"assetType":"boxart-medium","assetTypes":["boxart-medium"],"url":"http://62.179.125.152/SK/Images/bc_cfe405e669385365846b69196e1e94caa3e60de0.jpg?w=110&h=159&mode=box"},{"assetType":"boxart-xlarge","assetTypes":["boxart-xlarge"],"url":"http://62.179.125.152/SK/Images/bc_cfe405e669385365846b69196e1e94caa3e60de0.jpg?w=210&h=303&mode=box"},{"assetType":"boxart-large","assetTypes":["boxart-large"],"url":"http://62.179.125.152/SK/Images/bc_cfe405e669385365846b69196e1e94caa3e60de0.jpg?w=180&h=260&mode=box"}],"parentId":"crid:~~2F~~2Fport.cs~~2F41764266_series","rootId":"crid:~~2F~~2Fport.cs~~2F41764266","parentalRatingDescription":[],"resolutions":[],"mediaGroupId":"crid:~~2F~~2Fport.cs~~2F41764266","shortDescription":"Film/Kino","mediaType":"Episode","year":"2010","seriesEpisodeNumber":"1302070535","seriesNumber":"1302080520","videos":[],"videoStreams":[],"entitlements":["VIP","_OPEN_"],"currentProductIds":[],"currentTvodProductIds":[]},"parentId":"crid:~~2F~~2Fport.cs~~2F41764266_series","rootId":"crid:~~2F~~2Fport.cs~~2F41764266","replayTvAvailable":true,"audioTracks":[],"ratings":[],"offersLatestExpirationDate":1675746000000,"replayTvStartOffset":0,"replayTvEndOffset":604800,"replayEnabledOnMobileClients":true,"replaySource":"cloud","isGoReplayableViaExternalApp":false} \ No newline at end of file diff --git a/sites/horizon.tv/__data__/content_listings_3.json b/sites/horizon.tv/__data__/content_listings_3.json new file mode 100644 index 000000000..367ab8bd0 --- /dev/null +++ b/sites/horizon.tv/__data__/content_listings_3.json @@ -0,0 +1 @@ +{"id":"crid:~~2F~~2Fport.cs~~2F1379541,imi:5f806a2a0bc13e9745e14907a27116c60ea2c6ad","startTime":1675761000000,"endTime":1675767600000,"actualStartTime":1675761000000,"actualEndTime":1675767600000,"expirationDate":1676365800000,"stationId":"lgi-obolite-sk-prod-master:10024","imi":"imi:5f806a2a0bc13e9745e14907a27116c60ea2c6ad","scCridImi":"crid:~~2F~~2Fport.cs~~2F1379541,imi:5f806a2a0bc13e9745e14907a27116c60ea2c6ad","mediaGroupId":"crid:~~2F~~2Fport.cs~~2F1379541","program":{"id":"crid:~~2F~~2Fport.cs~~2F1379541","title":"Studentka","description":"Ambiciózní vysokoškolačka Valentina (Sophie Marceau) studuje literaturu na pařížské Sorbonně a právě se připravuje k závěrečným zkouškám. Žádný odpočinek, žádné volno, žádné večírky, téměř žádný...","longDescription":"Ambiciózní vysokoškolačka Valentina (Sophie Marceau) studuje literaturu na pařížské Sorbonně a právě se připravuje k závěrečným zkouškám. Žádný odpočinek, žádné volno, žádné večírky, téměř žádný spánek a především a hlavně ... žádná láska! Věří, že jedině tak obstojí před zkušební komisí. Jednoho dne se však odehraje něco, s čím nepočítala. Potká charismatického hudebníka Neda - a bláznivě se zamiluje. V tuto chvíli stojí před osudovým rozhodnutím: zahodí roky obrovského studijního nasazení, nebo odmítne lásku? Nebo se snad dá obojí skloubit dohromady?","medium":"Movie","categories":[{"id":"lgi-obolite-sk-prod-master:genre-14","title":"Film","scheme":"urn:libertyglobal:metadata:cs:ContentCS:2014_1"},{"id":"lgi-obolite-sk-prod-master:genre-4","title":"Komédia","scheme":"urn:libertyglobal:metadata:cs:ContentCS:2014_1"}],"isAdult":false,"parentalRating":"9","cast":["Sophie Marceauová","Vincent Lindon","Elisabeth Vitali","Elena Pompei","Jean-Claude Leguay","Brigitte Chamarande","Christian Pereira","Gérard Dacier","Roberto Attias","Beppe Chierici","Nathalie Mann","Anne Macina","Janine Souchon","Virginie Demians","Hugues Leforestier","Jacqueline Noëlle","Marc-André Brunet","Isabelle Caubère","André Chazel","Med Salah Cheurfi","Guillaume Corea","Eric Denize","Gilles Gaston-Dreyfuss","Benoît Gourley","Marc Innocenti","Najim Laouriga","Laurent Ledermann","Philippe Maygal","Dominique Pifarely","Ysé Tran"],"directors":["Francis De Gueltz","Dominique Talmon","Claude Pinoteau"],"images":[{"assetType":"HighResLandscape","assetTypes":["HighResLandscape"],"url":"http://62.179.125.152/SK/Images/hrl_a8abceaa59bbb0aae8031dcdd5deba03aba8a100.jpg"},{"assetType":"HighResPortrait","assetTypes":["HighResPortrait"],"url":"http://62.179.125.152/SK/Images/hrp_72b11621270454812ac8474698fc75670db4a49d.jpg"},{"assetType":"boxCover","assetTypes":["boxCover"],"url":"http://62.179.125.152/SK/Images/bc_72b11621270454812ac8474698fc75670db4a49d.jpg"},{"assetType":"boxart-small","assetTypes":["boxart-small"],"url":"http://62.179.125.152/SK/Images/bc_72b11621270454812ac8474698fc75670db4a49d.jpg?w=75&h=108&mode=box"},{"assetType":"boxart-medium","assetTypes":["boxart-medium"],"url":"http://62.179.125.152/SK/Images/bc_72b11621270454812ac8474698fc75670db4a49d.jpg?w=110&h=159&mode=box"},{"assetType":"boxart-xlarge","assetTypes":["boxart-xlarge"],"url":"http://62.179.125.152/SK/Images/bc_72b11621270454812ac8474698fc75670db4a49d.jpg?w=210&h=303&mode=box"},{"assetType":"boxart-large","assetTypes":["boxart-large"],"url":"http://62.179.125.152/SK/Images/bc_72b11621270454812ac8474698fc75670db4a49d.jpg?w=180&h=260&mode=box"}],"rootId":"crid:~~2F~~2Fport.cs~~2F1379541","parentalRatingDescription":[],"resolutions":[],"mediaGroupId":"crid:~~2F~~2Fport.cs~~2F1379541","shortDescription":"Ambiciózní vysokoškolačka Valentina (Sophie Marceau) studuje literaturu na pařížské Sorbonně a právě se připravuje k závěrečným zkouškám. Žádný odpočinek, žádné volno, žádné večírky, téměř žádný...","mediaType":"FeatureFilm","year":"1988","videos":[],"videoStreams":[],"entitlements":["VIP","_OPEN_"],"currentProductIds":[],"currentTvodProductIds":[]},"rootId":"crid:~~2F~~2Fport.cs~~2F1379541","replayTvAvailable":true,"audioTracks":[],"ratings":[],"offersLatestExpirationDate":1675767600000,"replayTvStartOffset":0,"replayTvEndOffset":604800,"replayEnabledOnMobileClients":true,"replaySource":"cloud","isGoReplayableViaExternalApp":false} \ No newline at end of file diff --git a/sites/horizon.tv/__data__/content_listings_4.json b/sites/horizon.tv/__data__/content_listings_4.json new file mode 100644 index 000000000..a701660ba --- /dev/null +++ b/sites/horizon.tv/__data__/content_listings_4.json @@ -0,0 +1 @@ +{"id":"crid:~~2F~~2Fport.cs~~2F71927954,imi:f1b4b0285b72cf44cba74e1c62322a4c682385c7","startTime":1675785900000,"endTime":1675791900000,"actualStartTime":1675785900000,"actualEndTime":1675791900000,"expirationDate":1676390700000,"stationId":"lgi-obolite-sk-prod-master:10024","imi":"imi:f1b4b0285b72cf44cba74e1c62322a4c682385c7","scCridImi":"crid:~~2F~~2Fport.cs~~2F71927954,imi:f1b4b0285b72cf44cba74e1c62322a4c682385c7","mediaGroupId":"crid:~~2F~~2Fport.cs~~2F71927954","program":{"id":"crid:~~2F~~2Fport.cs~~2F71927954","title":"Zilionáři","description":"David (Zach Galifianakis) je nekomplikovaný muž, který uvízl v monotónním životě. Den co den usedá za volant svého obrněného automobilu, aby odvážel obrovské sumy peněz jiných lidí. Jediným...","longDescription":"David (Zach Galifianakis) je nekomplikovaný muž, který uvízl v monotónním životě. Den co den usedá za volant svého obrněného automobilu, aby odvážel obrovské sumy peněz jiných lidí. Jediným vzrušujícím momentem v jeho životě je flirtování s kolegyní Kelly (Kristen Wiig), která ho však brzy zatáhne do těžko uvěřitelného dobrodružství. Skupinka nepříliš inteligentních loserů, pod vedením Steva (Owen Wilson), plánuje vyloupit banku a David jim v tom má samozřejmě pomoci. Navzdory absolutně amatérskému plánu se ale stane nemožné a oni mají najednou v kapse 17 miliónů dolarů. A protože tato partička je opravdu bláznivá, začne je hned ve velkém roztáčet. Peníze létají vzduchem za luxusní a kolikrát i zbytečné věci, ale nedochází jim, že pro policii tak zanechávají jasné stopy...","medium":"Movie","categories":[{"id":"lgi-obolite-sk-prod-master:genre-9","title":"Drama","scheme":"urn:libertyglobal:metadata:cs:ContentCS:2014_1"},{"id":"lgi-obolite-sk-prod-master:genre-33","title":"Akcia","scheme":"urn:libertyglobal:metadata:cs:ContentCS:2014_1"}],"isAdult":false,"parentalRating":"15","cast":["Zach Galifianakis","Kristen Wiigová","Owen Wilson","Kate McKinnon","Leslie Jones","Jason Sudeikis","Ross Kimball","Devin Ratray","Mary Elizabeth Ellisová","Jon Daly","Ken Marino","Daniel Zacapa","Tom Werme","Njema Williams","Nils Cruz","Michael Fraguada","Christian Gonzalez","Candace Blanchard","Karsten Friske","Dallas Edwards","Barry Ratcliffe","Shelton Grant","Laura Palka","Reegus Flenory","Wynn Reichert","Jill Jane Clements","Joseph S. Wilson","Jee An","Rhoda Griffisová","Nicole Dupre Sobchack"],"directors":["Scott August","Richard L. Fox","Michelle Malley-Campos","Sebastian Mazzola","Steven Ritzi","Pete Waterman","Jared Hess"],"images":[{"assetType":"HighResLandscape","assetTypes":["HighResLandscape"],"url":"http://62.179.125.152/SK/Images/hrl_fd098116bac1429318aaf5fdae498ce76e258782.jpg"},{"assetType":"HighResPortrait","assetTypes":["HighResPortrait"],"url":"http://62.179.125.152/SK/Images/hrp_6f857ae9375b3bcceb6353a5b35775f52cd85302.jpg"},{"assetType":"boxCover","assetTypes":["boxCover"],"url":"http://62.179.125.152/SK/Images/bc_3f5a24412c7f4f434094fa1147a304aa6a5ebda6.jpg"},{"assetType":"boxart-small","assetTypes":["boxart-small"],"url":"http://62.179.125.152/SK/Images/bc_3f5a24412c7f4f434094fa1147a304aa6a5ebda6.jpg?w=75&h=108&mode=box"},{"assetType":"boxart-medium","assetTypes":["boxart-medium"],"url":"http://62.179.125.152/SK/Images/bc_3f5a24412c7f4f434094fa1147a304aa6a5ebda6.jpg?w=110&h=159&mode=box"},{"assetType":"boxart-xlarge","assetTypes":["boxart-xlarge"],"url":"http://62.179.125.152/SK/Images/bc_3f5a24412c7f4f434094fa1147a304aa6a5ebda6.jpg?w=210&h=303&mode=box"},{"assetType":"boxart-large","assetTypes":["boxart-large"],"url":"http://62.179.125.152/SK/Images/bc_3f5a24412c7f4f434094fa1147a304aa6a5ebda6.jpg?w=180&h=260&mode=box"}],"rootId":"crid:~~2F~~2Fport.cs~~2F71927954","parentalRatingDescription":[],"resolutions":[],"mediaGroupId":"crid:~~2F~~2Fport.cs~~2F71927954","shortDescription":"David (Zach Galifianakis) je nekomplikovaný muž, který uvízl v monotónním životě. Den co den usedá za volant svého obrněného automobilu, aby odvážel obrovské sumy peněz jiných lidí. Jediným...","mediaType":"FeatureFilm","year":"2016","videos":[],"videoStreams":[],"entitlements":["VIP","_OPEN_"],"currentProductIds":[],"currentTvodProductIds":[]},"rootId":"crid:~~2F~~2Fport.cs~~2F71927954","replayTvAvailable":true,"audioTracks":[],"ratings":[],"offersLatestExpirationDate":1676187900000,"replayTvStartOffset":0,"replayTvEndOffset":604800,"replayEnabledOnMobileClients":true,"replaySource":"cloud","isGoReplayableViaExternalApp":false} \ No newline at end of file diff --git a/sites/horizon.tv/__data__/no_content.json b/sites/horizon.tv/__data__/no_content.json new file mode 100644 index 000000000..105dad50c --- /dev/null +++ b/sites/horizon.tv/__data__/no_content.json @@ -0,0 +1 @@ +[{"type":"PATH_PARAM","code":"period","reason":"INVALID"}] \ No newline at end of file diff --git a/sites/horizon.tv/horizon.tv.test.js b/sites/horizon.tv/horizon.tv.test.js index 5f1c78d7f..923ea43a1 100644 --- a/sites/horizon.tv/horizon.tv.test.js +++ b/sites/horizon.tv/horizon.tv.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./horizon.tv.config.js') +const fs = require('fs') +const path = require('path') const axios = require('axios') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') @@ -21,69 +23,50 @@ it('can generate valid url', () => { }) it('can parse response', done => { - const content = - '{"entryCount":184,"totalResults":184,"updated":1675790518889,"expires":1675791343825,"title":"EPG","periods":4,"periodStartTime":1675724400000,"periodEndTime":1675746000000,"entries":[{"o":"lgi-obolite-sk-prod-master:10024","l":[{"i":"crid:~~2F~~2Fport.cs~~2F122941980,imi:7ca159c917344e0dd3fbe1cd8db5ff8043d96a78","t":"Avengement","s":1675719300000,"e":1675724700000,"c":"lgi-obolite-sk-prod-master:genre-9","a":false,"r":true,"rm":true,"rs":0,"re":604800,"rst":"cloud","ra":false,"ad":[],"sl":[]}]}]}' + const content = JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8')) axios.get.mockImplementation(url => { if ( url === 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/programschedules/20230207/2' ) { return Promise.resolve({ - data: JSON.parse( - '{"entryCount":184,"totalResults":184,"updated":1675790518889,"expires":1675791376097,"title":"EPG","periods":4,"periodStartTime":1675746000000,"periodEndTime":1675767600000,"entries":[{"o":"lgi-obolite-sk-prod-master:10024","l":[{"i":"crid:~~2F~~2Fport.cs~~2F248281986,imi:e85129f9d1e211406a521df7a36f22237c22651b","t":"Zoom In","s":1675744500000,"e":1675746000000,"c":"lgi-obolite-sk-prod-master:genre-21","a":false,"r":true,"rm":true,"rs":0,"re":604800,"rst":"cloud","ra":false,"ad":[],"sl":[]}]}]}' - ) + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/content_1.json'), 'utf8')) }) } else if ( url === 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/programschedules/20230207/3' ) { return Promise.resolve({ - data: JSON.parse( - '{"entryCount":184,"totalResults":184,"updated":1675789948804,"expires":1675791024984,"title":"EPG","periods":4,"periodStartTime":1675767600000,"periodEndTime":1675789200000,"entries":[{"o":"lgi-obolite-sk-prod-master:10024","l":[{"i":"crid:~~2F~~2Fport.cs~~2F1379541,imi:5f806a2a0bc13e9745e14907a27116c60ea2c6ad","t":"Studentka","s":1675761000000,"e":1675767600000,"c":"lgi-obolite-sk-prod-master:genre-14","a":false,"r":true,"rm":true,"rs":0,"re":604800,"rst":"cloud","ra":false,"ad":[],"sl":[]}]}]}' - ) + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/content_2.json'), 'utf8')) }) } else if ( url === 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/programschedules/20230207/4' ) { return Promise.resolve({ - data: JSON.parse( - '{"entryCount":184,"totalResults":184,"updated":1675789948804,"expires":1675790973469,"title":"EPG","periods":4,"periodStartTime":1675789200000,"periodEndTime":1675810800000,"entries":[{"o":"lgi-obolite-sk-prod-master:10024","l":[{"i":"crid:~~2F~~2Fport.cs~~2F71927954,imi:f1b4b0285b72cf44cba74e1c62322a4c682385c7","t":"Zilionáři","s":1675785900000,"e":1675791900000,"c":"lgi-obolite-sk-prod-master:genre-9","a":false,"r":true,"rm":true,"rs":0,"re":604800,"rst":"cloud","ra":false,"ad":[],"sl":[]}]}]}' - ) + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/content_3.json'), 'utf8')) }) } else if ( - url === - 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/listings/crid:~~2F~~2Fport.cs~~2F122941980,imi:7ca159c917344e0dd3fbe1cd8db5ff8043d96a78' + url === 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/listings/crid:~~2F~~2Fport.cs~~2F122941980,imi:7ca159c917344e0dd3fbe1cd8db5ff8043d96a78' ) { return Promise.resolve({ - data: JSON.parse( - '{"id":"crid:~~2F~~2Fport.cs~~2F122941980,imi:7ca159c917344e0dd3fbe1cd8db5ff8043d96a78","startTime":1675719300000,"endTime":1675724700000,"actualStartTime":1675719300000,"actualEndTime":1675724700000,"expirationDate":1676324100000,"stationId":"lgi-obolite-sk-prod-master:10024","imi":"imi:7ca159c917344e0dd3fbe1cd8db5ff8043d96a78","scCridImi":"crid:~~2F~~2Fport.cs~~2F122941980,imi:7ca159c917344e0dd3fbe1cd8db5ff8043d96a78","mediaGroupId":"crid:~~2F~~2Fport.cs~~2F122941980","program":{"id":"crid:~~2F~~2Fport.cs~~2F122941980","title":"Avengement","description":"Během propustky z vězení za účelem návštěvy umírající matky v nemocnici zločinec Cain Burgess (Scott Adkins) unikne svým dozorcům a mizí v ulicích Londýna. Jde o epickou cestu krve a bolesti za...","longDescription":"Během propustky z vězení za účelem návštěvy umírající matky v nemocnici zločinec Cain Burgess (Scott Adkins) unikne svým dozorcům a mizí v ulicích Londýna. Jde o epickou cestu krve a bolesti za dosažením vytoužené pomsty na těch, kteří z něj udělali chladnokrevného vraha.","medium":"Movie","categories":[{"id":"lgi-obolite-sk-prod-master:genre-9","title":"Drama","scheme":"urn:libertyglobal:metadata:cs:ContentCS:2014_1"},{"id":"lgi-obolite-sk-prod-master:genre-33","title":"Akcia","scheme":"urn:libertyglobal:metadata:cs:ContentCS:2014_1"}],"isAdult":false,"parentalRating":"18","cast":["Scott Adkins","Craig Fairbrass","Thomas Turgoose","Nick Moran","Kierston Wareing","Leo Gregory","Mark Strange","Luke LaFontaine","Beau Fowler","Dan Styles","Christopher Sciueref","Matt Routledge","Jane Thorne","Louis Mandylor","Terence Maynard","Greg Burridge","Michael Higgs","Damian Gallagher","Daniel Adegboyega","John Ioannou","Sofie Golding-Spittle","Joe Egan","Darren Swain","Lee Charles","Dominic Kinnaird","Ross O\'Hennessy","Teresa Mahoney","Andrew Dunkelberger","Sam Hardy","Ivan Moy","Mark Sears","Phillip Ray Tommy"],"directors":["Jesse V. Johnson"],"images":[{"assetType":"HighResLandscape","assetTypes":["HighResLandscape"],"url":"http://62.179.125.152/SK/Images/hrl_3fa8387df870473fdacb1024635b52b2496b159c.jpg"},{"assetType":"HighResPortrait","assetTypes":["HighResPortrait"],"url":"http://62.179.125.152/SK/Images/hrp_19e3a660e637cd39e31046c284a66b3a95d698e4.jpg"},{"assetType":"boxCover","assetTypes":["boxCover"],"url":"http://62.179.125.152/SK/Images/bc_939160772e45a783fb3a19970696f5ebcb6e568b.jpg"},{"assetType":"boxart-small","assetTypes":["boxart-small"],"url":"http://62.179.125.152/SK/Images/bc_939160772e45a783fb3a19970696f5ebcb6e568b.jpg?w=75&h=108&mode=box"},{"assetType":"boxart-medium","assetTypes":["boxart-medium"],"url":"http://62.179.125.152/SK/Images/bc_939160772e45a783fb3a19970696f5ebcb6e568b.jpg?w=110&h=159&mode=box"},{"assetType":"boxart-xlarge","assetTypes":["boxart-xlarge"],"url":"http://62.179.125.152/SK/Images/bc_939160772e45a783fb3a19970696f5ebcb6e568b.jpg?w=210&h=303&mode=box"},{"assetType":"boxart-large","assetTypes":["boxart-large"],"url":"http://62.179.125.152/SK/Images/bc_939160772e45a783fb3a19970696f5ebcb6e568b.jpg?w=180&h=260&mode=box"}],"rootId":"crid:~~2F~~2Fport.cs~~2F122941980","parentalRatingDescription":[],"resolutions":[],"mediaGroupId":"crid:~~2F~~2Fport.cs~~2F122941980","shortDescription":"Během propustky z vězení za účelem návštěvy umírající matky v nemocnici zločinec Cain Burgess (Scott Adkins) unikne svým dozorcům a mizí v ulicích Londýna. Jde o epickou cestu krve a bolesti za...","mediaType":"FeatureFilm","year":"2019","videos":[],"videoStreams":[],"entitlements":["VIP","_OPEN_"],"currentProductIds":[],"currentTvodProductIds":[]},"rootId":"crid:~~2F~~2Fport.cs~~2F122941980","replayTvAvailable":true,"audioTracks":[],"ratings":[],"offersLatestExpirationDate":1676247300000,"replayTvStartOffset":0,"replayTvEndOffset":604800,"replayEnabledOnMobileClients":true,"replaySource":"cloud","isGoReplayableViaExternalApp":false}' - ) + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/content_listings_1.json'), 'utf8')) }) } else if ( - url === - 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/listings/crid:~~2F~~2Fport.cs~~2F248281986,imi:e85129f9d1e211406a521df7a36f22237c22651b' + url === 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/listings/crid:~~2F~~2Fport.cs~~2F248281986,imi:e85129f9d1e211406a521df7a36f22237c22651b' ) { return Promise.resolve({ - data: JSON.parse( - '{"id":"crid:~~2F~~2Fport.cs~~2F248281986,imi:e85129f9d1e211406a521df7a36f22237c22651b","startTime":1675744500000,"endTime":1675746000000,"actualStartTime":1675744500000,"actualEndTime":1675746000000,"expirationDate":1676349300000,"stationId":"lgi-obolite-sk-prod-master:10024","imi":"imi:e85129f9d1e211406a521df7a36f22237c22651b","scCridImi":"crid:~~2F~~2Fport.cs~~2F248281986,imi:e85129f9d1e211406a521df7a36f22237c22651b","mediaGroupId":"crid:~~2F~~2Fport.cs~~2F41764266","program":{"id":"crid:~~2F~~2Fport.cs~~2F248281986","title":"Zoom In","description":"Film/Kino","longDescription":"Film/Kino","medium":"TV","categories":[{"id":"lgi-obolite-sk-prod-master:genre-21","title":"Hudba a umenie","scheme":"urn:libertyglobal:metadata:cs:ContentCS:2014_1"},{"id":"lgi-obolite-sk-prod-master:genre-14","title":"Film","scheme":"urn:libertyglobal:metadata:cs:ContentCS:2014_1"}],"isAdult":false,"parentalRating":"9","cast":[],"directors":[],"images":[{"assetType":"HighResLandscape","assetTypes":["HighResLandscape"],"url":"http://62.179.125.152/SK/Images/hrl_cbed64b557e83227a2292604cbcae2d193877b1c.jpg"},{"assetType":"HighResPortrait","assetTypes":["HighResPortrait"],"url":"http://62.179.125.152/SK/Images/hrp_cfe405e669385365846b69196e1e94caa3e60de0.jpg"},{"assetType":"boxCover","assetTypes":["boxCover"],"url":"http://62.179.125.152/SK/Images/bc_cfe405e669385365846b69196e1e94caa3e60de0.jpg"},{"assetType":"boxart-small","assetTypes":["boxart-small"],"url":"http://62.179.125.152/SK/Images/bc_cfe405e669385365846b69196e1e94caa3e60de0.jpg?w=75&h=108&mode=box"},{"assetType":"boxart-medium","assetTypes":["boxart-medium"],"url":"http://62.179.125.152/SK/Images/bc_cfe405e669385365846b69196e1e94caa3e60de0.jpg?w=110&h=159&mode=box"},{"assetType":"boxart-xlarge","assetTypes":["boxart-xlarge"],"url":"http://62.179.125.152/SK/Images/bc_cfe405e669385365846b69196e1e94caa3e60de0.jpg?w=210&h=303&mode=box"},{"assetType":"boxart-large","assetTypes":["boxart-large"],"url":"http://62.179.125.152/SK/Images/bc_cfe405e669385365846b69196e1e94caa3e60de0.jpg?w=180&h=260&mode=box"}],"parentId":"crid:~~2F~~2Fport.cs~~2F41764266_series","rootId":"crid:~~2F~~2Fport.cs~~2F41764266","parentalRatingDescription":[],"resolutions":[],"mediaGroupId":"crid:~~2F~~2Fport.cs~~2F41764266","shortDescription":"Film/Kino","mediaType":"Episode","year":"2010","seriesEpisodeNumber":"1302070535","seriesNumber":"1302080520","videos":[],"videoStreams":[],"entitlements":["VIP","_OPEN_"],"currentProductIds":[],"currentTvodProductIds":[]},"parentId":"crid:~~2F~~2Fport.cs~~2F41764266_series","rootId":"crid:~~2F~~2Fport.cs~~2F41764266","replayTvAvailable":true,"audioTracks":[],"ratings":[],"offersLatestExpirationDate":1675746000000,"replayTvStartOffset":0,"replayTvEndOffset":604800,"replayEnabledOnMobileClients":true,"replaySource":"cloud","isGoReplayableViaExternalApp":false}' - ) + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/content_listings_2.json'), 'utf8')) }) } else if ( - url === - 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/listings/crid:~~2F~~2Fport.cs~~2F1379541,imi:5f806a2a0bc13e9745e14907a27116c60ea2c6ad' + url === 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/listings/crid:~~2F~~2Fport.cs~~2F1379541,imi:5f806a2a0bc13e9745e14907a27116c60ea2c6ad' ) { return Promise.resolve({ - data: JSON.parse( - '{"id":"crid:~~2F~~2Fport.cs~~2F1379541,imi:5f806a2a0bc13e9745e14907a27116c60ea2c6ad","startTime":1675761000000,"endTime":1675767600000,"actualStartTime":1675761000000,"actualEndTime":1675767600000,"expirationDate":1676365800000,"stationId":"lgi-obolite-sk-prod-master:10024","imi":"imi:5f806a2a0bc13e9745e14907a27116c60ea2c6ad","scCridImi":"crid:~~2F~~2Fport.cs~~2F1379541,imi:5f806a2a0bc13e9745e14907a27116c60ea2c6ad","mediaGroupId":"crid:~~2F~~2Fport.cs~~2F1379541","program":{"id":"crid:~~2F~~2Fport.cs~~2F1379541","title":"Studentka","description":"Ambiciózní vysokoškolačka Valentina (Sophie Marceau) studuje literaturu na pařížské Sorbonně a právě se připravuje k závěrečným zkouškám. Žádný odpočinek, žádné volno, žádné večírky, téměř žádný...","longDescription":"Ambiciózní vysokoškolačka Valentina (Sophie Marceau) studuje literaturu na pařížské Sorbonně a právě se připravuje k závěrečným zkouškám. Žádný odpočinek, žádné volno, žádné večírky, téměř žádný spánek a především a hlavně ... žádná láska! Věří, že jedině tak obstojí před zkušební komisí. Jednoho dne se však odehraje něco, s čím nepočítala. Potká charismatického hudebníka Neda - a bláznivě se zamiluje. V tuto chvíli stojí před osudovým rozhodnutím: zahodí roky obrovského studijního nasazení, nebo odmítne lásku? Nebo se snad dá obojí skloubit dohromady?","medium":"Movie","categories":[{"id":"lgi-obolite-sk-prod-master:genre-14","title":"Film","scheme":"urn:libertyglobal:metadata:cs:ContentCS:2014_1"},{"id":"lgi-obolite-sk-prod-master:genre-4","title":"Komédia","scheme":"urn:libertyglobal:metadata:cs:ContentCS:2014_1"}],"isAdult":false,"parentalRating":"9","cast":["Sophie Marceauová","Vincent Lindon","Elisabeth Vitali","Elena Pompei","Jean-Claude Leguay","Brigitte Chamarande","Christian Pereira","Gérard Dacier","Roberto Attias","Beppe Chierici","Nathalie Mann","Anne Macina","Janine Souchon","Virginie Demians","Hugues Leforestier","Jacqueline Noëlle","Marc-André Brunet","Isabelle Caubère","André Chazel","Med Salah Cheurfi","Guillaume Corea","Eric Denize","Gilles Gaston-Dreyfuss","Benoît Gourley","Marc Innocenti","Najim Laouriga","Laurent Ledermann","Philippe Maygal","Dominique Pifarely","Ysé Tran"],"directors":["Francis De Gueltz","Dominique Talmon","Claude Pinoteau"],"images":[{"assetType":"HighResLandscape","assetTypes":["HighResLandscape"],"url":"http://62.179.125.152/SK/Images/hrl_a8abceaa59bbb0aae8031dcdd5deba03aba8a100.jpg"},{"assetType":"HighResPortrait","assetTypes":["HighResPortrait"],"url":"http://62.179.125.152/SK/Images/hrp_72b11621270454812ac8474698fc75670db4a49d.jpg"},{"assetType":"boxCover","assetTypes":["boxCover"],"url":"http://62.179.125.152/SK/Images/bc_72b11621270454812ac8474698fc75670db4a49d.jpg"},{"assetType":"boxart-small","assetTypes":["boxart-small"],"url":"http://62.179.125.152/SK/Images/bc_72b11621270454812ac8474698fc75670db4a49d.jpg?w=75&h=108&mode=box"},{"assetType":"boxart-medium","assetTypes":["boxart-medium"],"url":"http://62.179.125.152/SK/Images/bc_72b11621270454812ac8474698fc75670db4a49d.jpg?w=110&h=159&mode=box"},{"assetType":"boxart-xlarge","assetTypes":["boxart-xlarge"],"url":"http://62.179.125.152/SK/Images/bc_72b11621270454812ac8474698fc75670db4a49d.jpg?w=210&h=303&mode=box"},{"assetType":"boxart-large","assetTypes":["boxart-large"],"url":"http://62.179.125.152/SK/Images/bc_72b11621270454812ac8474698fc75670db4a49d.jpg?w=180&h=260&mode=box"}],"rootId":"crid:~~2F~~2Fport.cs~~2F1379541","parentalRatingDescription":[],"resolutions":[],"mediaGroupId":"crid:~~2F~~2Fport.cs~~2F1379541","shortDescription":"Ambiciózní vysokoškolačka Valentina (Sophie Marceau) studuje literaturu na pařížské Sorbonně a právě se připravuje k závěrečným zkouškám. Žádný odpočinek, žádné volno, žádné večírky, téměř žádný...","mediaType":"FeatureFilm","year":"1988","videos":[],"videoStreams":[],"entitlements":["VIP","_OPEN_"],"currentProductIds":[],"currentTvodProductIds":[]},"rootId":"crid:~~2F~~2Fport.cs~~2F1379541","replayTvAvailable":true,"audioTracks":[],"ratings":[],"offersLatestExpirationDate":1675767600000,"replayTvStartOffset":0,"replayTvEndOffset":604800,"replayEnabledOnMobileClients":true,"replaySource":"cloud","isGoReplayableViaExternalApp":false}' - ) + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/content_listings_3.json'), 'utf8')) }) } else if ( - url === - 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/listings/crid:~~2F~~2Fport.cs~~2F71927954,imi:f1b4b0285b72cf44cba74e1c62322a4c682385c7' + url === 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/listings/crid:~~2F~~2Fport.cs~~2F71927954,imi:f1b4b0285b72cf44cba74e1c62322a4c682385c7' ) { return Promise.resolve({ - data: JSON.parse( - '{"id":"crid:~~2F~~2Fport.cs~~2F71927954,imi:f1b4b0285b72cf44cba74e1c62322a4c682385c7","startTime":1675785900000,"endTime":1675791900000,"actualStartTime":1675785900000,"actualEndTime":1675791900000,"expirationDate":1676390700000,"stationId":"lgi-obolite-sk-prod-master:10024","imi":"imi:f1b4b0285b72cf44cba74e1c62322a4c682385c7","scCridImi":"crid:~~2F~~2Fport.cs~~2F71927954,imi:f1b4b0285b72cf44cba74e1c62322a4c682385c7","mediaGroupId":"crid:~~2F~~2Fport.cs~~2F71927954","program":{"id":"crid:~~2F~~2Fport.cs~~2F71927954","title":"Zilionáři","description":"David (Zach Galifianakis) je nekomplikovaný muž, který uvízl v monotónním životě. Den co den usedá za volant svého obrněného automobilu, aby odvážel obrovské sumy peněz jiných lidí. Jediným...","longDescription":"David (Zach Galifianakis) je nekomplikovaný muž, který uvízl v monotónním životě. Den co den usedá za volant svého obrněného automobilu, aby odvážel obrovské sumy peněz jiných lidí. Jediným vzrušujícím momentem v jeho životě je flirtování s kolegyní Kelly (Kristen Wiig), která ho však brzy zatáhne do těžko uvěřitelného dobrodružství. Skupinka nepříliš inteligentních loserů, pod vedením Steva (Owen Wilson), plánuje vyloupit banku a David jim v tom má samozřejmě pomoci. Navzdory absolutně amatérskému plánu se ale stane nemožné a oni mají najednou v kapse 17 miliónů dolarů. A protože tato partička je opravdu bláznivá, začne je hned ve velkém roztáčet. Peníze létají vzduchem za luxusní a kolikrát i zbytečné věci, ale nedochází jim, že pro policii tak zanechávají jasné stopy...","medium":"Movie","categories":[{"id":"lgi-obolite-sk-prod-master:genre-9","title":"Drama","scheme":"urn:libertyglobal:metadata:cs:ContentCS:2014_1"},{"id":"lgi-obolite-sk-prod-master:genre-33","title":"Akcia","scheme":"urn:libertyglobal:metadata:cs:ContentCS:2014_1"}],"isAdult":false,"parentalRating":"15","cast":["Zach Galifianakis","Kristen Wiigová","Owen Wilson","Kate McKinnon","Leslie Jones","Jason Sudeikis","Ross Kimball","Devin Ratray","Mary Elizabeth Ellisová","Jon Daly","Ken Marino","Daniel Zacapa","Tom Werme","Njema Williams","Nils Cruz","Michael Fraguada","Christian Gonzalez","Candace Blanchard","Karsten Friske","Dallas Edwards","Barry Ratcliffe","Shelton Grant","Laura Palka","Reegus Flenory","Wynn Reichert","Jill Jane Clements","Joseph S. Wilson","Jee An","Rhoda Griffisová","Nicole Dupre Sobchack"],"directors":["Scott August","Richard L. Fox","Michelle Malley-Campos","Sebastian Mazzola","Steven Ritzi","Pete Waterman","Jared Hess"],"images":[{"assetType":"HighResLandscape","assetTypes":["HighResLandscape"],"url":"http://62.179.125.152/SK/Images/hrl_fd098116bac1429318aaf5fdae498ce76e258782.jpg"},{"assetType":"HighResPortrait","assetTypes":["HighResPortrait"],"url":"http://62.179.125.152/SK/Images/hrp_6f857ae9375b3bcceb6353a5b35775f52cd85302.jpg"},{"assetType":"boxCover","assetTypes":["boxCover"],"url":"http://62.179.125.152/SK/Images/bc_3f5a24412c7f4f434094fa1147a304aa6a5ebda6.jpg"},{"assetType":"boxart-small","assetTypes":["boxart-small"],"url":"http://62.179.125.152/SK/Images/bc_3f5a24412c7f4f434094fa1147a304aa6a5ebda6.jpg?w=75&h=108&mode=box"},{"assetType":"boxart-medium","assetTypes":["boxart-medium"],"url":"http://62.179.125.152/SK/Images/bc_3f5a24412c7f4f434094fa1147a304aa6a5ebda6.jpg?w=110&h=159&mode=box"},{"assetType":"boxart-xlarge","assetTypes":["boxart-xlarge"],"url":"http://62.179.125.152/SK/Images/bc_3f5a24412c7f4f434094fa1147a304aa6a5ebda6.jpg?w=210&h=303&mode=box"},{"assetType":"boxart-large","assetTypes":["boxart-large"],"url":"http://62.179.125.152/SK/Images/bc_3f5a24412c7f4f434094fa1147a304aa6a5ebda6.jpg?w=180&h=260&mode=box"}],"rootId":"crid:~~2F~~2Fport.cs~~2F71927954","parentalRatingDescription":[],"resolutions":[],"mediaGroupId":"crid:~~2F~~2Fport.cs~~2F71927954","shortDescription":"David (Zach Galifianakis) je nekomplikovaný muž, který uvízl v monotónním životě. Den co den usedá za volant svého obrněného automobilu, aby odvážel obrovské sumy peněz jiných lidí. Jediným...","mediaType":"FeatureFilm","year":"2016","videos":[],"videoStreams":[],"entitlements":["VIP","_OPEN_"],"currentProductIds":[],"currentTvodProductIds":[]},"rootId":"crid:~~2F~~2Fport.cs~~2F71927954","replayTvAvailable":true,"audioTracks":[],"ratings":[],"offersLatestExpirationDate":1676187900000,"replayTvStartOffset":0,"replayTvEndOffset":604800,"replayEnabledOnMobileClients":true,"replaySource":"cloud","isGoReplayableViaExternalApp":false}' - ) + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/content_listings_4.json'), 'utf8')) }) } else { return Promise.resolve({ data: '' }) @@ -251,7 +234,7 @@ it('can parse response', done => { it('can handle empty guide', done => { parser({ - content: '[{"type":"PATH_PARAM","code":"period","reason":"INVALID"}]', + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')), channel, date }) diff --git a/sites/hoy.tv/__data__/content.xml b/sites/hoy.tv/__data__/content.xml new file mode 100644 index 000000000..696703f9e --- /dev/null +++ b/sites/hoy.tv/__data__/content.xml @@ -0,0 +1,73 @@ + + + + + 2024-09-13 11:30:00 + 2024-09-13 12:30:00 + [PG] + false + false + 2024-09-27 11:30:00 + + 0 + + 0 + + http://tv.fantv.hk/images/thumbnail_1920_1080_fantv.jpg + + + EQ00135 + 46 + 點講都係一家人 + + http://tv.fantv.hk/images/nosuchthumbnail.jpg + + + + 點講都係一家人 + 0 + EQ00135 + 點講都係一家人 Episode 46 + 1 + 20240913 + 1130 + 0001 + 3704000 + + + + 2024-09-13 12:30:00 + 2024-09-13 13:30:00 + + false + false + 2024-09-27 12:30:00 + + 0 + + 0 + + http://tv.fantv.hk/images/thumbnail_1920_1080_fantv.jpg + + + ED00311 + 0 + 麝香之路 + Ep. 2 .The Secret of disappeared kingdom.shows the mysterious disappearance of the ancient Tibetan kingdom which gained world + http://tv.fantv.hk/images/nosuchthumbnail.jpg + + + + 麝香之路 + 0 + ED00311 + 麝香之路 2024-09-13 + 1 + 20240913 + 1230 + 0001 + 3704000 + + + + \ No newline at end of file diff --git a/sites/hoy.tv/hoy.tv.test.js b/sites/hoy.tv/hoy.tv.test.js index 155c29636..543162b23 100644 --- a/sites/hoy.tv/hoy.tv.test.js +++ b/sites/hoy.tv/hoy.tv.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./hoy.tv.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') @@ -12,85 +14,14 @@ const channel = { xmltv_id: 'HOYIBC.hk', lang: 'zh' } -const content = ` - - - - 2024-09-13 11:30:00 - 2024-09-13 12:30:00 - [PG] - false - false - 2024-09-27 11:30:00 - - 0 - - 0 - - http://tv.fantv.hk/images/thumbnail_1920_1080_fantv.jpg - - - EQ00135 - 46 - 點講都係一家人 - - http://tv.fantv.hk/images/nosuchthumbnail.jpg - - - - 點講都係一家人 - 0 - EQ00135 - 點講都係一家人 Episode 46 - 1 - 20240913 - 1130 - 0001 - 3704000 - - - - 2024-09-13 12:30:00 - 2024-09-13 13:30:00 - - false - false - 2024-09-27 12:30:00 - - 0 - - 0 - - http://tv.fantv.hk/images/thumbnail_1920_1080_fantv.jpg - - - ED00311 - 0 - 麝香之路 - Ep. 2 .The Secret of disappeared kingdom.shows the mysterious disappearance of the ancient Tibetan kingdom which gained world - http://tv.fantv.hk/images/nosuchthumbnail.jpg - - - - 麝香之路 - 0 - ED00311 - 麝香之路 2024-09-13 - 1 - 20240913 - 1230 - 0001 - 3704000 - - - -` it('can generate valid url', () => { expect(url({ channel, date })).toBe('https://epg-file.hoy.tv/hoy/OTT7620240913.xml') }) it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.xml'), 'utf8') + const result = parser({ content, channel, date }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() diff --git a/sites/i24news.tv/__data__/content.json b/sites/i24news.tv/__data__/content.json new file mode 100644 index 000000000..64b486240 --- /dev/null +++ b/sites/i24news.tv/__data__/content.json @@ -0,0 +1 @@ +[{"id":348995,"startHour":"22:30","endHour":"23:00","day":5,"firstDiffusion":false,"override":false,"show":{"parsedBody":[{"type":"text","text":"Special Edition"}],"id":131,"title":"تغطية خاصة","body":"Special Edition","slug":"Special-Edition-تغطية-خاصة","visible":true,"image":{"id":1142467,"credit":"","legend":"","href":"https://cdn.i24news.tv/uploads/a1/be/85/20/69/6f/32/1c/ed/b0/f8/5c/f6/1c/40/f9/a1be8520696f321cedb0f85cf61c40f9.png"}}},{"id":349023,"startHour":"15:00","endHour":"15:28","day":6,"firstDiffusion":false,"override":false,"show":{"parsedBody":[{"type":"text","text":"Special Edition"}],"id":131,"title":"تغطية خاصة","body":"Special Edition","slug":"Special-Edition-تغطية-خاصة","visible":true,"image":{"id":1142467,"credit":"","legend":"","href":"https://cdn.i24news.tv/uploads/a1/be/85/20/69/6f/32/1c/ed/b0/f8/5c/f6/1c/40/f9/a1be8520696f321cedb0f85cf61c40f9.png"}}}] \ No newline at end of file diff --git a/sites/i24news.tv/i24news.tv.test.js b/sites/i24news.tv/i24news.tv.test.js index 2525cdd8d..6b8cd8692 100644 --- a/sites/i24news.tv/i24news.tv.test.js +++ b/sites/i24news.tv/i24news.tv.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./i24news.tv.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -16,8 +18,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - const content = - '[{"id":348995,"startHour":"22:30","endHour":"23:00","day":5,"firstDiffusion":false,"override":false,"show":{"parsedBody":[{"type":"text","text":"Special Edition"}],"id":131,"title":"تغطية خاصة","body":"Special Edition","slug":"Special-Edition-تغطية-خاصة","visible":true,"image":{"id":1142467,"credit":"","legend":"","href":"https://cdn.i24news.tv/uploads/a1/be/85/20/69/6f/32/1c/ed/b0/f8/5c/f6/1c/40/f9/a1be8520696f321cedb0f85cf61c40f9.png"}}},{"id":349023,"startHour":"15:00","endHour":"15:28","day":6,"firstDiffusion":false,"override":false,"show":{"parsedBody":[{"type":"text","text":"Special Edition"}],"id":131,"title":"تغطية خاصة","body":"Special Edition","slug":"Special-Edition-تغطية-خاصة","visible":true,"image":{"id":1142467,"credit":"","legend":"","href":"https://cdn.i24news.tv/uploads/a1/be/85/20/69/6f/32/1c/ed/b0/f8/5c/f6/1c/40/f9/a1be8520696f321cedb0f85cf61c40f9.png"}}}]' + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const result = parser({ content, date }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() diff --git a/sites/indihometv.com/__data__/content.html b/sites/indihometv.com/__data__/content.html new file mode 100644 index 000000000..9feda34de --- /dev/null +++ b/sites/indihometv.com/__data__/content.html @@ -0,0 +1 @@ +
    \ No newline at end of file diff --git a/sites/indihometv.com/__data__/no_content.html b/sites/indihometv.com/__data__/no_content.html new file mode 100644 index 000000000..6fedfd4c7 --- /dev/null +++ b/sites/indihometv.com/__data__/no_content.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sites/indihometv.com/indihometv.com.test.js b/sites/indihometv.com/indihometv.com.test.js index 8230a917b..d84c0c24e 100644 --- a/sites/indihometv.com/indihometv.com.test.js +++ b/sites/indihometv.com/indihometv.com.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./indihometv.com.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') dayjs.extend(utc) @@ -8,14 +10,13 @@ const channel = { site_id: 'metrotv', xmltv_id: 'MetroTV.id' } -const content = - '
    ' it('can generate valid url', () => { expect(url({ channel })).toBe('https://www.indihometv.com/livetv/metrotv') }) it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') const result = parser({ content, channel, date }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() @@ -50,7 +51,7 @@ it('can handle empty guide', () => { const result = parser({ date, channel, - content: '' + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') }) expect(result).toMatchObject([]) }) diff --git a/sites/ipko.tv/__data__/content.json b/sites/ipko.tv/__data__/content.json new file mode 100644 index 000000000..fff5a347e --- /dev/null +++ b/sites/ipko.tv/__data__/content.json @@ -0,0 +1,58 @@ +{ + "shows": [ + { + "title": "IPKO Promo", + "show_start": 1735012800, + "show_end": 1735020000, + "timestamp": "5:00 - 7:00", + "show_id": "EPG_TvProfil_IPKOPROMO_296105567", + "thumbnail": "https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg", + "is_adult": false, + "friendly_id": "ipko_promo_4cf3", + "pg": "", + "genres": [], + "year": 0, + "summary": "", + "categories": "Other", + "stb_only": false, + "is_live": false, + "original_title": "IPKO Promo" + }, + { + "title": "IPKO Promo", + "show_start": 1735020000, + "show_end": 1735027200, + "timestamp": "7:00 - 9:00", + "show_id": "EPG_TvProfil_IPKOPROMO_296105568", + "thumbnail": "https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg", + "is_adult": false, + "friendly_id": "ipko_promo_416b", + "pg": "", + "genres": [], + "year": 0, + "summary": "", + "categories": "Other", + "stb_only": false, + "is_live": false, + "original_title": "IPKO Promo" + }, + { + "title": "IPKO Promo", + "show_start": 1735027200, + "show_end": 1735034400, + "timestamp": "9:00 - 11:00", + "show_id": "EPG_TvProfil_IPKOPROMO_296105569", + "thumbnail": "https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg", + "is_adult": false, + "friendly_id": "ipko_promo_2e23", + "pg": "", + "genres": [], + "year": 0, + "summary": "", + "categories": "Other", + "stb_only": false, + "is_live": false, + "original_title": "IPKO Promo" + } + ] + } \ No newline at end of file diff --git a/sites/ipko.tv/__data__/no_content.json b/sites/ipko.tv/__data__/no_content.json new file mode 100644 index 000000000..78743050d --- /dev/null +++ b/sites/ipko.tv/__data__/no_content.json @@ -0,0 +1 @@ +{"shows":[]} \ No newline at end of file diff --git a/sites/ipko.tv/ipko.tv.test.js b/sites/ipko.tv/ipko.tv.test.js index 19a83e86d..238aca7f5 100644 --- a/sites/ipko.tv/ipko.tv.test.js +++ b/sites/ipko.tv/ipko.tv.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./ipko.tv.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -16,66 +18,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - const content = ` - { - "shows": [ - { - "title": "IPKO Promo", - "show_start": 1735012800, - "show_end": 1735020000, - "timestamp": "5:00 - 7:00", - "show_id": "EPG_TvProfil_IPKOPROMO_296105567", - "thumbnail": "https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg", - "is_adult": false, - "friendly_id": "ipko_promo_4cf3", - "pg": "", - "genres": [], - "year": 0, - "summary": "", - "categories": "Other", - "stb_only": false, - "is_live": false, - "original_title": "IPKO Promo" - }, - { - "title": "IPKO Promo", - "show_start": 1735020000, - "show_end": 1735027200, - "timestamp": "7:00 - 9:00", - "show_id": "EPG_TvProfil_IPKOPROMO_296105568", - "thumbnail": "https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg", - "is_adult": false, - "friendly_id": "ipko_promo_416b", - "pg": "", - "genres": [], - "year": 0, - "summary": "", - "categories": "Other", - "stb_only": false, - "is_live": false, - "original_title": "IPKO Promo" - }, - { - "title": "IPKO Promo", - "show_start": 1735027200, - "show_end": 1735034400, - "timestamp": "9:00 - 11:00", - "show_id": "EPG_TvProfil_IPKOPROMO_296105569", - "thumbnail": "https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg", - "is_adult": false, - "friendly_id": "ipko_promo_2e23", - "pg": "", - "genres": [], - "year": 0, - "summary": "", - "categories": "Other", - "stb_only": false, - "is_live": false, - "original_title": "IPKO Promo" - } - ] - }` - + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const result = parser({ content, channel }) expect(result).toMatchObject([ @@ -105,7 +48,7 @@ it('can parse response', () => { it('can handle empty guide', () => { const result = parser({ - content: '{"shows":[]}' + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) }) expect(result).toMatchObject([]) }) diff --git a/sites/kan.org.il/__data__/content.json b/sites/kan.org.il/__data__/content.json new file mode 100644 index 000000000..0037cfd42 --- /dev/null +++ b/sites/kan.org.il/__data__/content.json @@ -0,0 +1 @@ +[{"title":"ארץ מולדת - בין תורכיה לבריטניה","start_time":"2022-03-06T00:05:37","end_time":"2022-03-06T00:27:12","id":"2598","age_category_desc":"0","epg_name":"ארץ מולדת","title1":"ארץ מולדת - בין תורכיה לבריטניה","chapter_number":"9","live_desc":"קבוצת תלמידים מתארגנת בפרוץ מלחמת העולם הראשונה להגיש עזרה לישוב. באמצעות התלמידים לומד הצופה על בעיותיו של הישוב בתקופת המלחמה, והתלבטותו בין נאמנות לשלטון העות'מאני לבין תקוותיו מהבריטים הכובשים.","Station_Radio":"0","Station_Id":"20","stationUrlScheme":"kan11://plugin/?type=player&plugin_identifier=kan_player&ds=general-provider%3A%2F%2FfetchData%3Ftype%3DFEED_JSON%26url%3DaHR0cHM6Ly93d3cua2FuLm9yZy5pbC9hcHBLYW4vbGl2ZVN0YXRpb25zLmFzaHg%3D&id=4","program_code":"3671","picture_code":"https://kanweb.blob.core.windows.net/download/pictures/2021/1/20/imgid=45847_Z.jpeg","program_image":"","station_image":"Logo_Image_Logo20_img__8.jpg","program_id":"","timezone":"2"}] \ No newline at end of file diff --git a/sites/kan.org.il/kan.org.il.test.js b/sites/kan.org.il/kan.org.il.test.js index 4dfd2575b..f20c02093 100644 --- a/sites/kan.org.il/kan.org.il.test.js +++ b/sites/kan.org.il/kan.org.il.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./kan.org.il.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -18,8 +20,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - const content = - '[{"title":"ארץ מולדת - בין תורכיה לבריטניה","start_time":"2022-03-06T00:05:37","end_time":"2022-03-06T00:27:12","id":"2598","age_category_desc":"0","epg_name":"ארץ מולדת","title1":"ארץ מולדת - בין תורכיה לבריטניה","chapter_number":"9","live_desc":"קבוצת תלמידים מתארגנת בפרוץ מלחמת העולם הראשונה להגיש עזרה לישוב. באמצעות התלמידים לומד הצופה על בעיותיו של הישוב בתקופת המלחמה, והתלבטותו בין נאמנות לשלטון העות\'מאני לבין תקוותיו מהבריטים הכובשים.","Station_Radio":"0","Station_Id":"20","stationUrlScheme":"kan11://plugin/?type=player&plugin_identifier=kan_player&ds=general-provider%3A%2F%2FfetchData%3Ftype%3DFEED_JSON%26url%3DaHR0cHM6Ly93d3cua2FuLm9yZy5pbC9hcHBLYW4vbGl2ZVN0YXRpb25zLmFzaHg%3D&id=4","program_code":"3671","picture_code":"https://kanweb.blob.core.windows.net/download/pictures/2021/1/20/imgid=45847_Z.jpeg","program_image":"","station_image":"Logo_Image_Logo20_img__8.jpg","program_id":"","timezone":"2"}]' + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const result = parser({ content }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() diff --git a/sites/magticom.ge/__data__/content.json b/sites/magticom.ge/__data__/content.json new file mode 100644 index 000000000..08956956c --- /dev/null +++ b/sites/magticom.ge/__data__/content.json @@ -0,0 +1 @@ +[{"id":2313254118,"channelId":260,"startTimestamp":"2021-11-22T07:00:00","endTimestamp":"2021-11-22T09:00:00","duration":null,"title":"\u0425\/\u0444 \"\u041d\u0435\u0440\u0430\u0432\u043d\u044b\u0439 \u0431\u0440\u0430\u043a\".","subTitle":"\u0425\/\u0444 \"\u041d\u0435\u0440\u0430\u0432\u043d\u044b\u0439 \u0431\u0440\u0430\u043a\".","info":"\u0413\u0443\u0434\u0436\u0430\u0440\u0430\u0442\u0435\u0446 \u0425\u0430\u0441\u043c\u0443\u043a\u0445 \u041f\u0430\u0442\u0435\u043b \u043f\u043e\u0441\u0441\u043e\u0440\u0438\u043b\u0441\u044f \u0441 \u043d\u043e\u0432\u044b\u043c \u0441\u043e\u0441\u0435\u0434\u043e\u043c \u0413\u0443\u0433\u0433\u0438 \u0422\u0430\u043d\u0434\u043e\u043d\u043e\u043c. \u041d\u043e \u0438\u043c \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u043f\u043e\u043c\u0438\u0440\u0438\u0442\u044c\u0441\u044f, \u043a\u043e\u0433\u0434\u0430 \u0438\u0445 \u0434\u0435\u0442\u0438 \u0432\u043b\u044e\u0431\u043b\u044f\u044e\u0442\u0441\u044f \u0434\u0440\u0443\u0433 \u0432 \u0434\u0440\u0443\u0433\u0430. \u0420\u0435\u0436\u0438\u0441\u0441\u0435\u0440: \u0421\u0430\u043d\u0434\u0436\u0430\u0439 \u0427\u0445\u0435\u043b. \u0410\u043a\u0442\u0435\u0440\u044b: \u0420\u0438\u0448\u0438 \u041a\u0430\u043f\u0443\u0440, \u041f\u0430\u0440\u0435\u0448 \u0420\u0430\u0432\u0430\u043b, \u0412\u0438\u0440 \u0414\u0430\u0441. 2017 \u0433\u043e\u0434.","pg":null,"year":null,"country":null,"imageUrl":null,"createdBy":-200,"creationTimestamp":"2021-11-21T18:04:52","epgSourceId":8,"startDateStr":"20211122070000","genreByGenreId":null,"languageByLanguageId":{"id":3,"name":"\u10e0\u10e3\u10e1\u10e3\u10da\u10d8","orderIndex":3,"nameShort":"ru"},"externalId":"2021460000084132","programHumanById":[],"date":null,"time":null,"startDate":null,"endDate":null,"longInfo":"\u0413\u0443\u0434\u0436\u0430\u0440\u0430\u0442\u0435\u0446 \u0425\u0430\u0441\u043c\u0443\u043a\u0445 \u041f\u0430\u0442\u0435\u043b \u043f\u043e\u0441\u0441\u043e\u0440\u0438\u043b\u0441\u044f \u0441 \u043d\u043e\u0432\u044b\u043c \u0441\u043e\u0441\u0435\u0434\u043e\u043c \u0413\u0443\u0433\u0433\u0438 \u0422\u0430\u043d\u0434\u043e\u043d\u043e\u043c. \u041d\u043e \u0438\u043c \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u043f\u043e\u043c\u0438\u0440\u0438\u0442\u044c\u0441\u044f, \u043a\u043e\u0433\u0434\u0430 \u0438\u0445 \u0434\u0435\u0442\u0438 \u0432\u043b\u044e\u0431\u043b\u044f\u044e\u0442\u0441\u044f \u0434\u0440\u0443\u0433 \u0432 \u0434\u0440\u0443\u0433\u0430. \u0420\u0435\u0436\u0438\u0441\u0441\u0435\u0440: \u0421\u0430\u043d\u0434\u0436\u0430\u0439 \u0427\u0445\u0435\u043b. \u0410\u043a\u0442\u0435\u0440\u044b: \u0420\u0438\u0448\u0438 \u041a\u0430\u043f\u0443\u0440, \u041f\u0430\u0440\u0435\u0448 \u0420\u0430\u0432\u0430\u043b, \u0412\u0438\u0440 \u0414\u0430\u0441. 2017 \u0433\u043e\u0434."}] \ No newline at end of file diff --git a/sites/magticom.ge/magticom.ge.test.js b/sites/magticom.ge/magticom.ge.test.js index 59d4dee0e..5c857e3e8 100644 --- a/sites/magticom.ge/magticom.ge.test.js +++ b/sites/magticom.ge/magticom.ge.test.js @@ -1,4 +1,6 @@ const { parser, url, request } = require('./magticom.ge.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -34,8 +36,7 @@ it('can generate valid request data', () => { }) it('can parse response', () => { - const content = - '[{"id":2313254118,"channelId":260,"startTimestamp":"2021-11-22T07:00:00","endTimestamp":"2021-11-22T09:00:00","duration":null,"title":"\\u0425\\/\\u0444 \\"\\u041d\\u0435\\u0440\\u0430\\u0432\\u043d\\u044b\\u0439 \\u0431\\u0440\\u0430\\u043a\\".","subTitle":"\\u0425\\/\\u0444 \\"\\u041d\\u0435\\u0440\\u0430\\u0432\\u043d\\u044b\\u0439 \\u0431\\u0440\\u0430\\u043a\\".","info":"\\u0413\\u0443\\u0434\\u0436\\u0430\\u0440\\u0430\\u0442\\u0435\\u0446 \\u0425\\u0430\\u0441\\u043c\\u0443\\u043a\\u0445 \\u041f\\u0430\\u0442\\u0435\\u043b \\u043f\\u043e\\u0441\\u0441\\u043e\\u0440\\u0438\\u043b\\u0441\\u044f \\u0441 \\u043d\\u043e\\u0432\\u044b\\u043c \\u0441\\u043e\\u0441\\u0435\\u0434\\u043e\\u043c \\u0413\\u0443\\u0433\\u0433\\u0438 \\u0422\\u0430\\u043d\\u0434\\u043e\\u043d\\u043e\\u043c. \\u041d\\u043e \\u0438\\u043c \\u043f\\u0440\\u0438\\u0445\\u043e\\u0434\\u0438\\u0442\\u0441\\u044f \\u043f\\u043e\\u043c\\u0438\\u0440\\u0438\\u0442\\u044c\\u0441\\u044f, \\u043a\\u043e\\u0433\\u0434\\u0430 \\u0438\\u0445 \\u0434\\u0435\\u0442\\u0438 \\u0432\\u043b\\u044e\\u0431\\u043b\\u044f\\u044e\\u0442\\u0441\\u044f \\u0434\\u0440\\u0443\\u0433 \\u0432 \\u0434\\u0440\\u0443\\u0433\\u0430. \\u0420\\u0435\\u0436\\u0438\\u0441\\u0441\\u0435\\u0440: \\u0421\\u0430\\u043d\\u0434\\u0436\\u0430\\u0439 \\u0427\\u0445\\u0435\\u043b. \\u0410\\u043a\\u0442\\u0435\\u0440\\u044b: \\u0420\\u0438\\u0448\\u0438 \\u041a\\u0430\\u043f\\u0443\\u0440, \\u041f\\u0430\\u0440\\u0435\\u0448 \\u0420\\u0430\\u0432\\u0430\\u043b, \\u0412\\u0438\\u0440 \\u0414\\u0430\\u0441. 2017 \\u0433\\u043e\\u0434.","pg":null,"year":null,"country":null,"imageUrl":null,"createdBy":-200,"creationTimestamp":"2021-11-21T18:04:52","epgSourceId":8,"startDateStr":"20211122070000","genreByGenreId":null,"languageByLanguageId":{"id":3,"name":"\\u10e0\\u10e3\\u10e1\\u10e3\\u10da\\u10d8","orderIndex":3,"nameShort":"ru"},"externalId":"2021460000084132","programHumanById":[],"date":null,"time":null,"startDate":null,"endDate":null,"longInfo":"\\u0413\\u0443\\u0434\\u0436\\u0430\\u0440\\u0430\\u0442\\u0435\\u0446 \\u0425\\u0430\\u0441\\u043c\\u0443\\u043a\\u0445 \\u041f\\u0430\\u0442\\u0435\\u043b \\u043f\\u043e\\u0441\\u0441\\u043e\\u0440\\u0438\\u043b\\u0441\\u044f \\u0441 \\u043d\\u043e\\u0432\\u044b\\u043c \\u0441\\u043e\\u0441\\u0435\\u0434\\u043e\\u043c \\u0413\\u0443\\u0433\\u0433\\u0438 \\u0422\\u0430\\u043d\\u0434\\u043e\\u043d\\u043e\\u043c. \\u041d\\u043e \\u0438\\u043c \\u043f\\u0440\\u0438\\u0445\\u043e\\u0434\\u0438\\u0442\\u0441\\u044f \\u043f\\u043e\\u043c\\u0438\\u0440\\u0438\\u0442\\u044c\\u0441\\u044f, \\u043a\\u043e\\u0433\\u0434\\u0430 \\u0438\\u0445 \\u0434\\u0435\\u0442\\u0438 \\u0432\\u043b\\u044e\\u0431\\u043b\\u044f\\u044e\\u0442\\u0441\\u044f \\u0434\\u0440\\u0443\\u0433 \\u0432 \\u0434\\u0440\\u0443\\u0433\\u0430. \\u0420\\u0435\\u0436\\u0438\\u0441\\u0441\\u0435\\u0440: \\u0421\\u0430\\u043d\\u0434\\u0436\\u0430\\u0439 \\u0427\\u0445\\u0435\\u043b. \\u0410\\u043a\\u0442\\u0435\\u0440\\u044b: \\u0420\\u0438\\u0448\\u0438 \\u041a\\u0430\\u043f\\u0443\\u0440, \\u041f\\u0430\\u0440\\u0435\\u0448 \\u0420\\u0430\\u0432\\u0430\\u043b, \\u0412\\u0438\\u0440 \\u0414\\u0430\\u0441. 2017 \\u0433\\u043e\\u0434."}]' + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const result = parser({ content }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() diff --git a/sites/mako.co.il/__data__/content.json b/sites/mako.co.il/__data__/content.json new file mode 100644 index 000000000..f48d561b8 --- /dev/null +++ b/sites/mako.co.il/__data__/content.json @@ -0,0 +1 @@ +{"programs":[{"DisplayEndTime":"06:15","MakoTVURL":"","HouseNumber":"L17165475","StartTimeUTC":1646539200000,"DurationMs":900000,"DisplayStartTime":"06:00","MobilePicture":"https://img.mako.co.il/2017/01/01/placeHolder.jpg","StartTime":"06/03/2022 06:00:00","RerunBroadcast":false,"Duration":"00:15","ProgramName":"כותרות הבוקר","Date":"06/03/2022 06:00:00","MakoProgramsURL":"","LiveBroadcast":true,"ProgramCode":134987,"Episode":"","Picture":"https://img.mako.co.il//2021/08/04/hadshot_haboker_im_niv_raskin.jpg","MakoShortName":"","hebrewDate":"6 במרץ","Season":"","day":"הערב","EventDescription":"","EnglishName":"cotrot,EP 46"},{"DisplayEndTime":"02:39","MakoTVURL":"","HouseNumber":"A168960","StartTimeUTC":1646613480000,"DurationMs":60000,"DisplayStartTime":"02:38","MobilePicture":"https://img.mako.co.il/2017/01/01/placeHolder.jpg","StartTime":"07/03/2022 02:38:00","RerunBroadcast":true,"Duration":"00:01","ProgramName":"רוקדים עם כוכבים - בר זומר","Date":"07/03/2022 02:38:00","MakoProgramsURL":"","LiveBroadcast":false,"ProgramCode":135029,"Episode":"","Picture":"https://img.mako.co.il/2022/02/13/DancingWithStars2022_EPG.jpg","MakoShortName":"","hebrewDate":"7 במרץ","Season":"","day":"מחר","EventDescription":"מהדורת החדשות המרכזית של הבוקר, האנשים הפרשנויות והכותרות שיעשו את היום.","EnglishName":"rokdim,EP 10"}]} \ No newline at end of file diff --git a/sites/mako.co.il/mako.co.il.test.js b/sites/mako.co.il/mako.co.il.test.js index 70f1586eb..06be75366 100644 --- a/sites/mako.co.il/mako.co.il.test.js +++ b/sites/mako.co.il/mako.co.il.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./mako.co.il.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -12,8 +14,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - const content = - '{"programs":[{"DisplayEndTime":"06:15","MakoTVURL":"","HouseNumber":"L17165475","StartTimeUTC":1646539200000,"DurationMs":900000,"DisplayStartTime":"06:00","MobilePicture":"https://img.mako.co.il/2017/01/01/placeHolder.jpg","StartTime":"06/03/2022 06:00:00","RerunBroadcast":false,"Duration":"00:15","ProgramName":"כותרות הבוקר","Date":"06/03/2022 06:00:00","MakoProgramsURL":"","LiveBroadcast":true,"ProgramCode":134987,"Episode":"","Picture":"https://img.mako.co.il//2021/08/04/hadshot_haboker_im_niv_raskin.jpg","MakoShortName":"","hebrewDate":"6 במרץ","Season":"","day":"הערב","EventDescription":"","EnglishName":"cotrot,EP 46"},{"DisplayEndTime":"02:39","MakoTVURL":"","HouseNumber":"A168960","StartTimeUTC":1646613480000,"DurationMs":60000,"DisplayStartTime":"02:38","MobilePicture":"https://img.mako.co.il/2017/01/01/placeHolder.jpg","StartTime":"07/03/2022 02:38:00","RerunBroadcast":true,"Duration":"00:01","ProgramName":"רוקדים עם כוכבים - בר זומר","Date":"07/03/2022 02:38:00","MakoProgramsURL":"","LiveBroadcast":false,"ProgramCode":135029,"Episode":"","Picture":"https://img.mako.co.il/2022/02/13/DancingWithStars2022_EPG.jpg","MakoShortName":"","hebrewDate":"7 במרץ","Season":"","day":"מחר","EventDescription":"מהדורת החדשות המרכזית של הבוקר, האנשים הפרשנויות והכותרות שיעשו את היום.","EnglishName":"rokdim,EP 10"}]}' + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const result = parser({ content, date }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() diff --git a/sites/maxtvgo.mk/__data__/content.json b/sites/maxtvgo.mk/__data__/content.json new file mode 100644 index 000000000..9c1592315 --- /dev/null +++ b/sites/maxtvgo.mk/__data__/content.json @@ -0,0 +1 @@ +{"programme":[{"@attributes":{"channel":"105","id":"21949063","start":"20211116231000 +0100","stop":"20211117010000 +0100","disable_catchup":"0","is_adult":"0"},"title":"Палмето - игран филм","original-title":{"@attributes":{"lang":""}},"sub-title":{"@attributes":{"lang":""}},"category_id":"11","category":"Останато","desc":"Екстремниот рибар, Џереми Вејд, е во потрага по слатководни риби кои јадат човечко месо. Со форензички методи, Џереми им илустрира на гледачите како овие нови чудовишта се создадени да убиваат.","icon":{"@attributes":{"src":"https://prd-static-mkt.spectar.tv/rev-1636968170/image_transform.php/transform/1/epg_program_id/21949063/instance_id/1"}},"episode_num":{},"date":"0","star-rating":{"value":{}},"rating":{"@attributes":{"system":""},"value":"0+"},"linear_channel_rating":"0+","genres":{},"credits":{}}]} \ No newline at end of file diff --git a/sites/maxtvgo.mk/__data__/content_no_description.json b/sites/maxtvgo.mk/__data__/content_no_description.json new file mode 100644 index 000000000..ed5f49990 --- /dev/null +++ b/sites/maxtvgo.mk/__data__/content_no_description.json @@ -0,0 +1 @@ +{"programme":[{"@attributes":{"channel":"105","id":"21949063","start":"20211116231000 +0100","stop":"20211117010000 +0100","disable_catchup":"0","is_adult":"0"},"title":"Палмето - игран филм","original-title":{"@attributes":{"lang":""}},"sub-title":{"@attributes":{"lang":""}},"category_id":"11","category":"Останато","desc":{},"icon":{"@attributes":{"src":"https://prd-static-mkt.spectar.tv/rev-1636968170/image_transform.php/transform/1/epg_program_id/21949063/instance_id/1"}},"episode_num":{},"date":"0","star-rating":{"value":{}},"rating":{"@attributes":{"system":""},"value":"0+"},"linear_channel_rating":"0+","genres":{},"credits":{}}]} \ No newline at end of file diff --git a/sites/maxtvgo.mk/__data__/no_content.json b/sites/maxtvgo.mk/__data__/no_content.json new file mode 100644 index 000000000..d90445ea3 --- /dev/null +++ b/sites/maxtvgo.mk/__data__/no_content.json @@ -0,0 +1 @@ +{"@attributes":{"source-info-name":"maxtvgo.mk","generator-info-name":"spectar_epg"}} \ No newline at end of file diff --git a/sites/maxtvgo.mk/maxtvgo.mk.test.js b/sites/maxtvgo.mk/maxtvgo.mk.test.js index f218c3bae..336265fa2 100644 --- a/sites/maxtvgo.mk/maxtvgo.mk.test.js +++ b/sites/maxtvgo.mk/maxtvgo.mk.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./maxtvgo.mk.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -18,8 +20,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - const content = - '{"programme":[{"@attributes":{"channel":"105","id":"21949063","start":"20211116231000 +0100","stop":"20211117010000 +0100","disable_catchup":"0","is_adult":"0"},"title":"Палмето - игран филм","original-title":{"@attributes":{"lang":""}},"sub-title":{"@attributes":{"lang":""}},"category_id":"11","category":"Останато","desc":"Екстремниот рибар, Џереми Вејд, е во потрага по слатководни риби кои јадат човечко месо. Со форензички методи, Џереми им илустрира на гледачите како овие нови чудовишта се создадени да убиваат.","icon":{"@attributes":{"src":"https://prd-static-mkt.spectar.tv/rev-1636968170/image_transform.php/transform/1/epg_program_id/21949063/instance_id/1"}},"episode_num":{},"date":"0","star-rating":{"value":{}},"rating":{"@attributes":{"system":""},"value":"0+"},"linear_channel_rating":"0+","genres":{},"credits":{}}]}' + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const result = parser({ content }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() @@ -41,8 +42,7 @@ it('can parse response', () => { }) it('can parse response with no description', () => { - const content = - '{"programme":[{"@attributes":{"channel":"105","id":"21949063","start":"20211116231000 +0100","stop":"20211117010000 +0100","disable_catchup":"0","is_adult":"0"},"title":"Палмето - игран филм","original-title":{"@attributes":{"lang":""}},"sub-title":{"@attributes":{"lang":""}},"category_id":"11","category":"Останато","desc":{},"icon":{"@attributes":{"src":"https://prd-static-mkt.spectar.tv/rev-1636968170/image_transform.php/transform/1/epg_program_id/21949063/instance_id/1"}},"episode_num":{},"date":"0","star-rating":{"value":{}},"rating":{"@attributes":{"system":""},"value":"0+"},"linear_channel_rating":"0+","genres":{},"credits":{}}]}' + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_no_description.json')) const result = parser({ content }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() @@ -66,7 +66,7 @@ it('can handle empty guide', () => { const result = parser({ date, channel, - content: '{"@attributes":{"source-info-name":"maxtvgo.mk","generator-info-name":"spectar_epg"}}' + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) }) expect(result).toMatchObject([]) }) diff --git a/sites/melita.com/__data__/content.json b/sites/melita.com/__data__/content.json new file mode 100644 index 000000000..4d7529fde --- /dev/null +++ b/sites/melita.com/__data__/content.json @@ -0,0 +1 @@ +{"schedules":[{"id":"138dabff-131a-42a0-9373-203545933dd0","published":{"start":"2022-04-20T06:25:00Z","end":"2022-04-20T06:45:00Z"},"program":"ae52299a-3c99-4d34-9932-e21d383f9800","live":false,"blackouts":[]}],"programs":[{"id":"ae52299a-3c99-4d34-9932-e21d383f9800","title":"How I Met Your Mother","shortSynopsis":"Symphony of Illumination - Robin gets some bad news and decides to keep it to herself. Marshall decorates the house.","posterImage":"https://androme.melitacable.com/media/images/epg/bc/07/p8953134_e_h10_ad.jpg","episode":12,"episodeTitle":"Symphony of Illumination","season":"fdd6e42c-97f9-4d7a-aaca-78b53378f960","genres":["3.5.7.3"],"tags":["comedy"],"adult":false}],"seasons":[{"id":"fdd6e42c-97f9-4d7a-aaca-78b53378f960","title":"How I Met Your Mother","adult":false,"season":7,"series":"858c535a-abbb-451b-807a-94196997ea2d"}],"series":[{"id":"858c535a-abbb-451b-807a-94196997ea2d","title":"How I Met Your Mother","adult":false}]} \ No newline at end of file diff --git a/sites/melita.com/melita.com.test.js b/sites/melita.com/melita.com.test.js index 2cedf24e5..55869a74a 100644 --- a/sites/melita.com/melita.com.test.js +++ b/sites/melita.com/melita.com.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./melita.com.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -18,8 +20,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - const content = - '{"schedules":[{"id":"138dabff-131a-42a0-9373-203545933dd0","published":{"start":"2022-04-20T06:25:00Z","end":"2022-04-20T06:45:00Z"},"program":"ae52299a-3c99-4d34-9932-e21d383f9800","live":false,"blackouts":[]}],"programs":[{"id":"ae52299a-3c99-4d34-9932-e21d383f9800","title":"How I Met Your Mother","shortSynopsis":"Symphony of Illumination - Robin gets some bad news and decides to keep it to herself. Marshall decorates the house.","posterImage":"https://androme.melitacable.com/media/images/epg/bc/07/p8953134_e_h10_ad.jpg","episode":12,"episodeTitle":"Symphony of Illumination","season":"fdd6e42c-97f9-4d7a-aaca-78b53378f960","genres":["3.5.7.3"],"tags":["comedy"],"adult":false}],"seasons":[{"id":"fdd6e42c-97f9-4d7a-aaca-78b53378f960","title":"How I Met Your Mother","adult":false,"season":7,"series":"858c535a-abbb-451b-807a-94196997ea2d"}],"series":[{"id":"858c535a-abbb-451b-807a-94196997ea2d","title":"How I Met Your Mother","adult":false}]}' + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const result = parser({ content }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() diff --git a/sites/mewatch.sg/__data__/content.json b/sites/mewatch.sg/__data__/content.json new file mode 100644 index 000000000..81c223b32 --- /dev/null +++ b/sites/mewatch.sg/__data__/content.json @@ -0,0 +1 @@ +[{"channelId":"97098","startDate":"2022-06-11T21:00:00.000Z","endDate":"2022-06-12T21:00:00.000Z","schedules":[{"channelId":"97098","customId":"37040748","endDate":"2022-06-11T21:30:00Z","id":"788a7dd","live":false,"startDate":"2022-06-11T21:00:00Z","isGap":false,"InteractiveType":"0","item":{"type":"episode","title":"Open Homes S3 - EP 2","blackoutMessage":"Programme is not available for live streaming.","description":"Mike heads down to the Sydney beaches to visit a beachside renovation with all the bells and whistles, we see a kitchen tip and recipe anyone can do at home. We finish up in the prestigious Byron bay to visit a multi million dollar award winning home.","classification":{"code":"IMDA-G (Violence)","name":"G (Violence)"},"episodeNumber":2,"episodeTitle":"Collaroy, Sydney","seasonNumber":3,"images":{"wallpaper":"https://production.togglestatic.com/shain/v1/dataservice/ResizeImage/$value?Format='jpg'&Quality=85&ImageId='4853691'&EntityType='LinearSchedule'&EntityId='788a7dd9-9b12-446f-91b4-c8ac9fec95e5'&Width=1280&Height=720&device=web_browser&subscriptions=Anonymous&segmentationTags=all","tile":"https://production.togglestatic.com/shain/v1/dataservice/ResizeImage/$value?Format='jpg'&Quality=85&ImageId='4853697'&EntityType='LinearSchedule'&EntityId='788a7dd9-9b12-446f-91b4-c8ac9fec95e5'&Width=1280&Height=720&device=web_browser&subscriptions=Anonymous&segmentationTags=all"},"enableCatchUp":true,"enableStartOver":false,"enableSeeking":false,"programSource":"ACQUIRED","simulcast":"LOCAL","masterReferenceKey":"0CH50CH5A0105567800020A0000000000P3254400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}}]}] \ No newline at end of file diff --git a/sites/mewatch.sg/__data__/no_content.json b/sites/mewatch.sg/__data__/no_content.json new file mode 100644 index 000000000..72d022667 --- /dev/null +++ b/sites/mewatch.sg/__data__/no_content.json @@ -0,0 +1 @@ +[{"channelId":"9798","startDate":"2022-06-11T21:00:00.000Z","endDate":"2022-06-12T21:00:00.000Z","schedules":[]}] \ No newline at end of file diff --git a/sites/mewatch.sg/mewatch.sg.test.js b/sites/mewatch.sg/mewatch.sg.test.js index 6a9494438..f65ecb3f9 100644 --- a/sites/mewatch.sg/mewatch.sg.test.js +++ b/sites/mewatch.sg/mewatch.sg.test.js @@ -1,4 +1,6 @@ const { parser, url } = require('./mewatch.sg.config.js') +const fs = require('fs') +const path = require('path') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') @@ -18,8 +20,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - const content = - '[{"channelId":"97098","startDate":"2022-06-11T21:00:00.000Z","endDate":"2022-06-12T21:00:00.000Z","schedules":[{"channelId":"97098","customId":"37040748","endDate":"2022-06-11T21:30:00Z","id":"788a7dd","live":false,"startDate":"2022-06-11T21:00:00Z","isGap":false,"InteractiveType":"0","item":{"type":"episode","title":"Open Homes S3 - EP 2","blackoutMessage":"Programme is not available for live streaming.","description":"Mike heads down to the Sydney beaches to visit a beachside renovation with all the bells and whistles, we see a kitchen tip and recipe anyone can do at home. We finish up in the prestigious Byron bay to visit a multi million dollar award winning home.","classification":{"code":"IMDA-G (Violence)","name":"G (Violence)"},"episodeNumber":2,"episodeTitle":"Collaroy, Sydney","seasonNumber":3,"images":{"wallpaper":"https://production.togglestatic.com/shain/v1/dataservice/ResizeImage/$value?Format=\'jpg\'&Quality=85&ImageId=\'4853691\'&EntityType=\'LinearSchedule\'&EntityId=\'788a7dd9-9b12-446f-91b4-c8ac9fec95e5\'&Width=1280&Height=720&device=web_browser&subscriptions=Anonymous&segmentationTags=all","tile":"https://production.togglestatic.com/shain/v1/dataservice/ResizeImage/$value?Format=\'jpg\'&Quality=85&ImageId=\'4853697\'&EntityType=\'LinearSchedule\'&EntityId=\'788a7dd9-9b12-446f-91b4-c8ac9fec95e5\'&Width=1280&Height=720&device=web_browser&subscriptions=Anonymous&segmentationTags=all"},"enableCatchUp":true,"enableStartOver":false,"enableSeeking":false,"programSource":"ACQUIRED","simulcast":"LOCAL","masterReferenceKey":"0CH50CH5A0105567800020A0000000000P3254400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}}]}]' + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) const result = parser({ content, channel }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() @@ -48,7 +49,7 @@ it('can parse response', () => { it('can handle empty guide', () => { const result = parser({ content: - '[{"channelId":"9798","startDate":"2022-06-11T21:00:00.000Z","endDate":"2022-06-12T21:00:00.000Z","schedules":[]}]', + fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')), channel }) expect(result).toMatchObject([]) From 7afd3fe3feacdbe9e7bf5e196bd9963a906f7fa3 Mon Sep 17 00:00:00 2001 From: freearhey <7253922+freearhey@users.noreply.github.com> Date: Mon, 28 Jul 2025 04:07:40 +0300 Subject: [PATCH 21/32] Fix linter issues --- scripts/functions/functions.ts | 142 ++--- sites/guidetnt.com/guidetnt.com.config.js | 671 +++++++++++----------- sites/guidetnt.com/guidetnt.com.test.js | 168 +++--- sites/tataplay.com/tataplay.com.config.js | 160 +++--- sites/tataplay.com/tataplay.com.test.js | 176 +++--- 5 files changed, 671 insertions(+), 646 deletions(-) diff --git a/scripts/functions/functions.ts b/scripts/functions/functions.ts index 346c5c94c..f97b84f8b 100644 --- a/scripts/functions/functions.ts +++ b/scripts/functions/functions.ts @@ -1,65 +1,77 @@ -/** - * Sorts an array by the result of running each element through an iteratee function. - * Creates a shallow copy of the array before sorting to avoid mutating the original. - * - * @param {Array} arr - The array to sort - * @param {Function} fn - The iteratee function to compute sort values - * @returns {Array} A new sorted array - * - * @example - * const users = [{name: 'john', age: 30}, {name: 'jane', age: 25}]; - * sortBy(users, x => x.age); // [{name: 'jane', age: 25}, {name: 'john', age: 30}] - */ -export const sortBy = (arr: T[], fn: (item: T) => number | string): T[] => [...arr].sort((a, b) => fn(a) > fn(b) ? 1 : -1) - -/** - * Sorts an array by multiple criteria with customizable sort orders. - * Supports ascending (default) and descending order for each criterion. - * - * @param {Array} arr - The array to sort - * @param {Array} fns - Array of iteratee functions to compute sort values - * @param {Array} orders - Array of sort orders ('asc' or 'desc'), defaults to all 'asc' - * @returns {Array} A new sorted array - * - * @example - * const users = [{name: 'john', age: 30}, {name: 'jane', age: 25}, {name: 'bob', age: 30}]; - * orderBy(users, [x => x.age, x => x.name], ['desc', 'asc']); - * // [{name: 'bob', age: 30}, {name: 'john', age: 30}, {name: 'jane', age: 25}] - */ -export const orderBy = (arr: Array, fns: Array<(item: unknown) => string | number>, orders: Array = []): Array => [...arr].sort((a, b) => - fns.reduce((acc, fn, i) => - acc || ((orders[i] === 'desc' ? fn(b) > fn(a) : fn(a) > fn(b)) ? 1 : fn(a) === fn(b) ? 0 : -1), 0) -) - -/** - * Creates a duplicate-free version of an array using an iteratee function to generate - * the criterion by which uniqueness is computed. Only the first occurrence of each - * element is kept. - * - * @param {Array} arr - The array to inspect - * @param {Function} fn - The iteratee function to compute uniqueness criterion - * @returns {Array} A new duplicate-free array - * - * @example - * const users = [{id: 1, name: 'john'}, {id: 2, name: 'jane'}, {id: 1, name: 'john'}]; - * uniqBy(users, x => x.id); // [{id: 1, name: 'john'}, {id: 2, name: 'jane'}] - */ -export const uniqBy = (arr: T[], fn: (item: T) => unknown): T[] => arr.filter((item, index) => arr.findIndex(x => fn(x) === fn(item)) === index) - -/** - * Converts a string to start case (capitalizes the first letter of each word). - * Handles camelCase, snake_case, kebab-case, and regular spaces. - * - * @param {string} str - The string to convert - * @returns {string} The start case string - * - * @example - * startCase('hello_world'); // "Hello World" - * startCase('helloWorld'); // "Hello World" - * startCase('hello-world'); // "Hello World" - * startCase('hello world'); // "Hello World" - */ -export const startCase = (str: string): string => str - .replace(/([a-z])([A-Z])/g, '$1 $2') // Split camelCase - .replace(/[_-]/g, ' ') // Replace underscores and hyphens with spaces - .replace(/\b\w/g, c => c.toUpperCase()) // Capitalize first letter of each word \ No newline at end of file +/** + * Sorts an array by the result of running each element through an iteratee function. + * Creates a shallow copy of the array before sorting to avoid mutating the original. + * + * @param {Array} arr - The array to sort + * @param {Function} fn - The iteratee function to compute sort values + * @returns {Array} A new sorted array + * + * @example + * const users = [{name: 'john', age: 30}, {name: 'jane', age: 25}]; + * sortBy(users, x => x.age); // [{name: 'jane', age: 25}, {name: 'john', age: 30}] + */ +export const sortBy = (arr: T[], fn: (item: T) => number | string): T[] => + [...arr].sort((a, b) => (fn(a) > fn(b) ? 1 : -1)) + +/** + * Sorts an array by multiple criteria with customizable sort orders. + * Supports ascending (default) and descending order for each criterion. + * + * @param {Array} arr - The array to sort + * @param {Array} fns - Array of iteratee functions to compute sort values + * @param {Array} orders - Array of sort orders ('asc' or 'desc'), defaults to all 'asc' + * @returns {Array} A new sorted array + * + * @example + * const users = [{name: 'john', age: 30}, {name: 'jane', age: 25}, {name: 'bob', age: 30}]; + * orderBy(users, [x => x.age, x => x.name], ['desc', 'asc']); + * // [{name: 'bob', age: 30}, {name: 'john', age: 30}, {name: 'jane', age: 25}] + */ +export const orderBy = ( + arr: Array, + fns: Array<(item: unknown) => string | number>, + orders: Array = [] +): Array => + [...arr].sort((a, b) => + fns.reduce( + (acc, fn, i) => + acc || + ((orders[i] === 'desc' ? fn(b) > fn(a) : fn(a) > fn(b)) ? 1 : fn(a) === fn(b) ? 0 : -1), + 0 + ) + ) + +/** + * Creates a duplicate-free version of an array using an iteratee function to generate + * the criterion by which uniqueness is computed. Only the first occurrence of each + * element is kept. + * + * @param {Array} arr - The array to inspect + * @param {Function} fn - The iteratee function to compute uniqueness criterion + * @returns {Array} A new duplicate-free array + * + * @example + * const users = [{id: 1, name: 'john'}, {id: 2, name: 'jane'}, {id: 1, name: 'john'}]; + * uniqBy(users, x => x.id); // [{id: 1, name: 'john'}, {id: 2, name: 'jane'}] + */ +export const uniqBy = (arr: T[], fn: (item: T) => unknown): T[] => + arr.filter((item, index) => arr.findIndex(x => fn(x) === fn(item)) === index) + +/** + * Converts a string to start case (capitalizes the first letter of each word). + * Handles camelCase, snake_case, kebab-case, and regular spaces. + * + * @param {string} str - The string to convert + * @returns {string} The start case string + * + * @example + * startCase('hello_world'); // "Hello World" + * startCase('helloWorld'); // "Hello World" + * startCase('hello-world'); // "Hello World" + * startCase('hello world'); // "Hello World" + */ +export const startCase = (str: string): string => + str + .replace(/([a-z])([A-Z])/g, '$1 $2') // Split camelCase + .replace(/[_-]/g, ' ') // Replace underscores and hyphens with spaces + .replace(/\b\w/g, c => c.toUpperCase()) // Capitalize first letter of each word diff --git a/sites/guidetnt.com/guidetnt.com.config.js b/sites/guidetnt.com/guidetnt.com.config.js index 9e00934c1..092871f64 100755 --- a/sites/guidetnt.com/guidetnt.com.config.js +++ b/sites/guidetnt.com/guidetnt.com.config.js @@ -1,333 +1,338 @@ -const cheerio = require('cheerio') -const axios = require('axios') -const dayjs = require('dayjs') -const customParseFormat = require('dayjs/plugin/customParseFormat') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -require('dayjs/locale/fr') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) -dayjs.extend(timezone) - -const PARIS_TZ = 'Europe/Paris' - -module.exports = { - site: 'guidetnt.com', - days: 2, - url({ channel, date }) { - const now = dayjs() - const demain = now.add(1, 'd') - if (date && date.isSame(demain, 'day')) { - return `https://www.guidetnt.com/tv-demain/programme-${channel.site_id}` - } else if (!date || date.isSame(now, 'day')) { - return `https://www.guidetnt.com/tv/programme-${channel.site_id}` - } else { - return null - } - }, - async parser({ content, date }) { - const programs = [] - const allItems = parseItems(content) - const items = allItems?.rows - const itemDate = allItems?.formattedDate - for (const item of items) { - const prev = programs[programs.length - 1] - const $item = cheerio.load(item) - const title = parseTitle($item) - let start = parseStart($item, itemDate) - - if (!start || !title) return - if (prev) { - if (start.isBefore(prev.start)) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } - prev.stop = start - } - let stop = start.add(30, 'm') - - let itemDetails = null - let subTitle = null - //let duration = null - let country = null - let productionDate = null - let episode = null - let season = null - let category = parseCategory($item) - let description = parseDescription($item) - const itemDetailsURL = parseDescriptionURL($item) - if(itemDetailsURL) { - const url = 'https://www.guidetnt.com' + itemDetailsURL - try { - const response = await axios.get(url) - itemDetails = parseItemDetails(response.data) - } catch (err) { - console.error(`Erreur lors du fetch des détails pour l'item: ${url}`, err) - } - - const timeRange = parseTimeRange(itemDetails?.programHour, date.format('YYYY-MM-DD')) - start = timeRange?.start - stop = timeRange?.stop - - subTitle = itemDetails?.subTitle - if (title == subTitle) subTitle = null - description = itemDetails?.description - - const categoryDetails = parseCategoryText(itemDetails?.category) - //duration = categoryDetails?.duration - country = categoryDetails?.country - productionDate = categoryDetails?.productionDate - season = categoryDetails?.season - episode = categoryDetails?.episode - } - // See https://www.npmjs.com/package/epg-parser for parameters - programs.push({ - title, - subTitle: subTitle, - description: description, - image: itemDetails?.image, - category: category, - directors: itemDetails?.directorActors?.Réalisateur, - actors: itemDetails?.directorActors?.Acteur, - country: country, - date: productionDate, - //duration: duration, // Tried with length: too, but does not work ! (stop-start is not accurate because of Ads) - season: season, - episode: episode, - start, - stop - }) - } - - return programs - }, - async channels() { - const response = await axios.get('https://www.guidetnt.com') - const channels = [] - const $ = cheerio.load(response.data) - - // Look inside each .tvlogo container - $('.tvlogo').each((i, el) => { - // Find all descendants that have an alt attribute - $(el).find('[alt]').each((j, subEl) => { - const alt = $(subEl).attr('alt') - const href = $(subEl).attr('href') - if (href && alt && alt.trim() !== '') { - const name = alt.trim() - const site_id = href.replace(/^\/tv\/programme-/, '') - channels.push({ - lang: 'fr', - name, - site_id - }) - } - }) - }) - return channels - } -} - -function parseTimeRange(timeRange, baseDate) { - // Split times - const [startStr, endStr] = timeRange.split(' - ').map(s => s.trim()) - - // Parse with base date - const start = dayjs(`${baseDate} ${startStr}`, 'YYYY-MM-DD HH:mm') - let end = dayjs(`${baseDate} ${endStr}`, 'YYYY-MM-DD HH:mm') - - // Handle possible day wrap (e.g., 23:30 - 00:15) - if (end.isBefore(start)) { - end = end.add(1, 'day') - } - - // Calculate duration in minutes - const diffMinutes = end.diff(start, 'minute') - - return { - start: start.format(), - stop: end.format(), - duration: diffMinutes - } -} - -function parseItemDetails(itemDetails) { - const $ = cheerio.load(itemDetails) - - const program = $('.program-wrapper').first() - - const programHour = program.find('.program-hour').text().trim() - const programTitle = program.find('.program-title').text().trim() - const programElementBold = program.find('.program-element-bold').text().trim() - const programArea1 = program.find('.program-element.program-area-1').text().trim() - - let description = '' - const programElements = $('.program-element').filter((i, el) => { - const classAttr = $(el).attr('class') - // Return true only if it is exactly "program-element" (no extra classes) - return classAttr.trim() === 'program-element' - }) - - programElements.each((i, el) => { - description += $(el).text().trim() - }) - - const area2Node = $('.program-area-2').first() - const area2 = $(area2Node) - const data = {} - let currentLabel = null - let texts = [] - - area2.contents().each((i, node) => { - if (node.type === 'tag' && node.name === 'strong') { - // If we had collected some text for the previous label, save it - if (currentLabel && texts.length) { - data[currentLabel] = texts.join('').trim().replace(/,\s*$/, '') // Remove trailing comma - } - // New label - get text without colon - currentLabel = $(node).text().replace(/:$/, '').trim() - texts = [] - } else if (currentLabel) { - // Append the text content (text node or others) - if (node.type === 'text') { - texts.push(node.data) - } else if (node.type === 'tag' && node.name !== 'strong' && node.name !== 'br') { - texts.push($(node).text()) - } - } - }) - - // Save last label text - if (currentLabel && texts.length) { - data[currentLabel] = texts.join('').trim().replace(/,\s*$/, '') - } - - const imgSrc = program.find('div[style*="float:left"]')?.find('img')?.attr('src') || null - - return { - programHour, - title: programTitle, - subTitle: programElementBold, - category: programArea1, - description: description, - directorActors: data, - image: imgSrc - } -} - -function parseCategoryText(text) { - if (!text) return null - - const parts = text.split(',').map(s => s.trim()).filter(Boolean) - const len = parts.length - - const category = parts[0] || null - - if (len < 3) { - return { - category: category, - duration: null, - country: null, - productionDate: null, - season: null, - episode: null - } - } - - // Check last part: date if numeric - const dateCandidate = parts[len - 1] - const productionDate = /^\d{4}$/.test(dateCandidate) ? dateCandidate : null - - // Check for duration (first part containing "minutes") - let durationMinute = null - //let duration = null - let episode = null - let season = null - let durationIndex = -1 - for (let i = 0; i < len; i++) { - if (parts[i].toLowerCase().includes('minute')) { - durationMinute = parts[i].trim() - durationMinute = durationMinute.replace('minutes', '') - durationMinute = durationMinute.replace('minute', '') - //duration = [{ units: 'minutes', value: durationMinute }], - durationIndex = i - } else if (parts[i].toLowerCase().includes('épisode')) { - const match = text.match(/épisode\s+(\d+)(?:\/(\d+))?/i) - if (match) { - episode = parseInt(match[1], 10) - } - } else if (parts[i].toLowerCase().includes('saison')) { - season = parts[i].replace('saison', '').trim() - } - } - - // Country: second to last - const countryIndex = len - 2 - let country = (durationIndex === countryIndex) ? null : parts[countryIndex] - - return { - category, - durationMinute, - country, - productionDate, - season, - episode - } -} - -function parseTitle($item) { - return $item('.channel-programs-title a').text().trim() -} - -function parseDescription($item) { - return $item('#descr').text().trim() || null -} - -function parseDescriptionURL($item) { - const descrLink = $item('#descr a') - return descrLink.attr('href') || null -} - -function parseCategory($item) { - let type = null - $item('.channel-programs-title span').each((i, span) => { - const className = $item(span).attr('class') - if (className && className.startsWith('text_bg')) { - type = $item(span).text().trim() - } - }) - return type -} - -function parseStart($item, itemDate) { - const dt = $item('.channel-programs-time a').text().trim() - if (!dt) return null - - const datetimeStr = `${itemDate} ${dt}` - return dayjs.tz(datetimeStr, 'YYYY-MM-DD HH:mm', PARIS_TZ) -} - -function parseItems(content) { - const $ = cheerio.load(content) - - // Extract header information - const logoSrc = $('#logo img').attr('src') - const title = $('#title h1').text().trim() - const subtitle = $('#subtitle').text().trim() - const dateMatch = subtitle.match(/(\d{1,2} \w+ \d{4})/) - const dateStr = dateMatch ? dateMatch[1].toLowerCase() : null - - // Parse the French date string - const parsedDate = dayjs(dateStr, 'D MMMM YYYY', 'fr') - // Format it as YYYY-MM-DD - const formattedDate = parsedDate.format('YYYY-MM-DD') - - const rows = $('.channel-row').toArray() - - return { - rows, - logoSrc, - title, - formattedDate - } -} \ No newline at end of file +const cheerio = require('cheerio') +const axios = require('axios') +const dayjs = require('dayjs') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +require('dayjs/locale/fr') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) +dayjs.extend(timezone) + +const PARIS_TZ = 'Europe/Paris' + +module.exports = { + site: 'guidetnt.com', + days: 2, + url({ channel, date }) { + const now = dayjs() + const demain = now.add(1, 'd') + if (date && date.isSame(demain, 'day')) { + return `https://www.guidetnt.com/tv-demain/programme-${channel.site_id}` + } else if (!date || date.isSame(now, 'day')) { + return `https://www.guidetnt.com/tv/programme-${channel.site_id}` + } else { + return null + } + }, + async parser({ content, date }) { + const programs = [] + const allItems = parseItems(content) + const items = allItems?.rows + const itemDate = allItems?.formattedDate + for (const item of items) { + const prev = programs[programs.length - 1] + const $item = cheerio.load(item) + const title = parseTitle($item) + let start = parseStart($item, itemDate) + + if (!start || !title) return + if (prev) { + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } + prev.stop = start + } + let stop = start.add(30, 'm') + + let itemDetails = null + let subTitle = null + //let duration = null + let country = null + let productionDate = null + let episode = null + let season = null + let category = parseCategory($item) + let description = parseDescription($item) + const itemDetailsURL = parseDescriptionURL($item) + if (itemDetailsURL) { + const url = 'https://www.guidetnt.com' + itemDetailsURL + try { + const response = await axios.get(url) + itemDetails = parseItemDetails(response.data) + } catch (err) { + console.error(`Erreur lors du fetch des détails pour l'item: ${url}`, err) + } + + const timeRange = parseTimeRange(itemDetails?.programHour, date.format('YYYY-MM-DD')) + start = timeRange?.start + stop = timeRange?.stop + + subTitle = itemDetails?.subTitle + if (title == subTitle) subTitle = null + description = itemDetails?.description + + const categoryDetails = parseCategoryText(itemDetails?.category) + //duration = categoryDetails?.duration + country = categoryDetails?.country + productionDate = categoryDetails?.productionDate + season = categoryDetails?.season + episode = categoryDetails?.episode + } + // See https://www.npmjs.com/package/epg-parser for parameters + programs.push({ + title, + subTitle: subTitle, + description: description, + image: itemDetails?.image, + category: category, + directors: itemDetails?.directorActors?.Réalisateur, + actors: itemDetails?.directorActors?.Acteur, + country: country, + date: productionDate, + //duration: duration, // Tried with length: too, but does not work ! (stop-start is not accurate because of Ads) + season: season, + episode: episode, + start, + stop + }) + } + + return programs + }, + async channels() { + const response = await axios.get('https://www.guidetnt.com') + const channels = [] + const $ = cheerio.load(response.data) + + // Look inside each .tvlogo container + $('.tvlogo').each((i, el) => { + // Find all descendants that have an alt attribute + $(el) + .find('[alt]') + .each((j, subEl) => { + const alt = $(subEl).attr('alt') + const href = $(subEl).attr('href') + if (href && alt && alt.trim() !== '') { + const name = alt.trim() + const site_id = href.replace(/^\/tv\/programme-/, '') + channels.push({ + lang: 'fr', + name, + site_id + }) + } + }) + }) + return channels + } +} + +function parseTimeRange(timeRange, baseDate) { + // Split times + const [startStr, endStr] = timeRange.split(' - ').map(s => s.trim()) + + // Parse with base date + const start = dayjs(`${baseDate} ${startStr}`, 'YYYY-MM-DD HH:mm') + let end = dayjs(`${baseDate} ${endStr}`, 'YYYY-MM-DD HH:mm') + + // Handle possible day wrap (e.g., 23:30 - 00:15) + if (end.isBefore(start)) { + end = end.add(1, 'day') + } + + // Calculate duration in minutes + const diffMinutes = end.diff(start, 'minute') + + return { + start: start.format(), + stop: end.format(), + duration: diffMinutes + } +} + +function parseItemDetails(itemDetails) { + const $ = cheerio.load(itemDetails) + + const program = $('.program-wrapper').first() + + const programHour = program.find('.program-hour').text().trim() + const programTitle = program.find('.program-title').text().trim() + const programElementBold = program.find('.program-element-bold').text().trim() + const programArea1 = program.find('.program-element.program-area-1').text().trim() + + let description = '' + const programElements = $('.program-element').filter((i, el) => { + const classAttr = $(el).attr('class') + // Return true only if it is exactly "program-element" (no extra classes) + return classAttr.trim() === 'program-element' + }) + + programElements.each((i, el) => { + description += $(el).text().trim() + }) + + const area2Node = $('.program-area-2').first() + const area2 = $(area2Node) + const data = {} + let currentLabel = null + let texts = [] + + area2.contents().each((i, node) => { + if (node.type === 'tag' && node.name === 'strong') { + // If we had collected some text for the previous label, save it + if (currentLabel && texts.length) { + data[currentLabel] = texts.join('').trim().replace(/,\s*$/, '') // Remove trailing comma + } + // New label - get text without colon + currentLabel = $(node).text().replace(/:$/, '').trim() + texts = [] + } else if (currentLabel) { + // Append the text content (text node or others) + if (node.type === 'text') { + texts.push(node.data) + } else if (node.type === 'tag' && node.name !== 'strong' && node.name !== 'br') { + texts.push($(node).text()) + } + } + }) + + // Save last label text + if (currentLabel && texts.length) { + data[currentLabel] = texts.join('').trim().replace(/,\s*$/, '') + } + + const imgSrc = program.find('div[style*="float:left"]')?.find('img')?.attr('src') || null + + return { + programHour, + title: programTitle, + subTitle: programElementBold, + category: programArea1, + description: description, + directorActors: data, + image: imgSrc + } +} + +function parseCategoryText(text) { + if (!text) return null + + const parts = text + .split(',') + .map(s => s.trim()) + .filter(Boolean) + const len = parts.length + + const category = parts[0] || null + + if (len < 3) { + return { + category: category, + duration: null, + country: null, + productionDate: null, + season: null, + episode: null + } + } + + // Check last part: date if numeric + const dateCandidate = parts[len - 1] + const productionDate = /^\d{4}$/.test(dateCandidate) ? dateCandidate : null + + // Check for duration (first part containing "minutes") + let durationMinute = null + //let duration = null + let episode = null + let season = null + let durationIndex = -1 + for (let i = 0; i < len; i++) { + if (parts[i].toLowerCase().includes('minute')) { + durationMinute = parts[i].trim() + durationMinute = durationMinute.replace('minutes', '') + durationMinute = durationMinute.replace('minute', '') + //duration = [{ units: 'minutes', value: durationMinute }], + durationIndex = i + } else if (parts[i].toLowerCase().includes('épisode')) { + const match = text.match(/épisode\s+(\d+)(?:\/(\d+))?/i) + if (match) { + episode = parseInt(match[1], 10) + } + } else if (parts[i].toLowerCase().includes('saison')) { + season = parts[i].replace('saison', '').trim() + } + } + + // Country: second to last + const countryIndex = len - 2 + let country = durationIndex === countryIndex ? null : parts[countryIndex] + + return { + category, + durationMinute, + country, + productionDate, + season, + episode + } +} + +function parseTitle($item) { + return $item('.channel-programs-title a').text().trim() +} + +function parseDescription($item) { + return $item('#descr').text().trim() || null +} + +function parseDescriptionURL($item) { + const descrLink = $item('#descr a') + return descrLink.attr('href') || null +} + +function parseCategory($item) { + let type = null + $item('.channel-programs-title span').each((i, span) => { + const className = $item(span).attr('class') + if (className && className.startsWith('text_bg')) { + type = $item(span).text().trim() + } + }) + return type +} + +function parseStart($item, itemDate) { + const dt = $item('.channel-programs-time a').text().trim() + if (!dt) return null + + const datetimeStr = `${itemDate} ${dt}` + return dayjs.tz(datetimeStr, 'YYYY-MM-DD HH:mm', PARIS_TZ) +} + +function parseItems(content) { + const $ = cheerio.load(content) + + // Extract header information + const logoSrc = $('#logo img').attr('src') + const title = $('#title h1').text().trim() + const subtitle = $('#subtitle').text().trim() + const dateMatch = subtitle.match(/(\d{1,2} \w+ \d{4})/) + const dateStr = dateMatch ? dateMatch[1].toLowerCase() : null + + // Parse the French date string + const parsedDate = dayjs(dateStr, 'D MMMM YYYY', 'fr') + // Format it as YYYY-MM-DD + const formattedDate = parsedDate.format('YYYY-MM-DD') + + const rows = $('.channel-row').toArray() + + return { + rows, + logoSrc, + title, + formattedDate + } +} diff --git a/sites/guidetnt.com/guidetnt.com.test.js b/sites/guidetnt.com/guidetnt.com.test.js index 0ee3906a8..299f19cf0 100644 --- a/sites/guidetnt.com/guidetnt.com.test.js +++ b/sites/guidetnt.com/guidetnt.com.test.js @@ -1,83 +1,85 @@ -const { parser, url } = require('./guidetnt.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -const timezone = require('dayjs/plugin/timezone') -require('dayjs/locale/fr') -dayjs.extend(customParseFormat) -dayjs.extend(utc) -dayjs.extend(timezone) - -const date = dayjs.utc('2025-07-01', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'tf1', - xmltv_id: 'TF1.fr' -} - -it('can generate valid url', () => { - expect(url({ channel })).toBe('https://www.guidetnt.com/tv/programme-tf1') -}) - -it('can parse response', async () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - let results = await parser({ content, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(29) - expect(results[0]).toMatchObject({ - category: 'Série', - description: 'Grande effervescence pour toute l\'équipe du Camping Paradis, qui prépare les Olympiades. Côté arrivants, Hélène et sa fille Eva viennent passer quelques jours dans le but d\'optimiser les révisions d\'E...', - start: '2025-06-30T22:55:00.000Z', - stop: '2025-06-30T23:45:00.000Z', - title: 'Camping Paradis' - }) - expect(results[2]).toMatchObject({ - category: 'Magazine', - description: 'Retrouvez tous vos programmes de nuit.', - start: '2025-07-01T00:55:00.000Z', - stop: '2025-07-01T04:00:00.000Z', - title: 'Programmes de la nuit' - }) - expect(results[15]).toMatchObject({ - category: 'Téléfilm', - description: 'La vie quasi parfaite de Riley bascule brutalement lorsqu\'un accident de voiture lui coûte la vie, laissant derrière elle sa famille. Alors que l\'enquête débute, l\'affaire prend une tournure étrange l...', - start: '2025-07-01T12:25:00.000Z', - stop: '2025-07-01T14:00:00.000Z', - title: 'Trahie par l\'amour' - }) -}) - -it('can parse response for current day', async () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - let results = await parser({ content, date: dayjs.utc('2025-07-01', 'YYYY-MM-DD').startOf('d') }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - } - ) - - expect(results.length).toBe(29) - expect(results[0]).toMatchObject({ - category: 'Série', - description: 'Grande effervescence pour toute l\'équipe du Camping Paradis, qui prépare les Olympiades. Côté arrivants, Hélène et sa fille Eva viennent passer quelques jours dans le but d\'optimiser les révisions d\'E...', - start: '2025-06-30T22:55:00.000Z', - stop: '2025-06-30T23:45:00.000Z', - title: 'Camping Paradis' - }) -}) - -it('can handle empty guide', async () => { - const results = await parser({ - date, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) - }) - - expect(results).toEqual([]) -}) +const { parser, url } = require('./guidetnt.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const timezone = require('dayjs/plugin/timezone') +require('dayjs/locale/fr') +dayjs.extend(customParseFormat) +dayjs.extend(utc) +dayjs.extend(timezone) + +const date = dayjs.utc('2025-07-01', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'tf1', + xmltv_id: 'TF1.fr' +} + +it('can generate valid url', () => { + expect(url({ channel })).toBe('https://www.guidetnt.com/tv/programme-tf1') +}) + +it('can parse response', async () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + let results = await parser({ content, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(29) + expect(results[0]).toMatchObject({ + category: 'Série', + description: + "Grande effervescence pour toute l'équipe du Camping Paradis, qui prépare les Olympiades. Côté arrivants, Hélène et sa fille Eva viennent passer quelques jours dans le but d'optimiser les révisions d'E...", + start: '2025-06-30T22:55:00.000Z', + stop: '2025-06-30T23:45:00.000Z', + title: 'Camping Paradis' + }) + expect(results[2]).toMatchObject({ + category: 'Magazine', + description: 'Retrouvez tous vos programmes de nuit.', + start: '2025-07-01T00:55:00.000Z', + stop: '2025-07-01T04:00:00.000Z', + title: 'Programmes de la nuit' + }) + expect(results[15]).toMatchObject({ + category: 'Téléfilm', + description: + "La vie quasi parfaite de Riley bascule brutalement lorsqu'un accident de voiture lui coûte la vie, laissant derrière elle sa famille. Alors que l'enquête débute, l'affaire prend une tournure étrange l...", + start: '2025-07-01T12:25:00.000Z', + stop: '2025-07-01T14:00:00.000Z', + title: "Trahie par l'amour" + }) +}) + +it('can parse response for current day', async () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + let results = await parser({ content, date: dayjs.utc('2025-07-01', 'YYYY-MM-DD').startOf('d') }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(29) + expect(results[0]).toMatchObject({ + category: 'Série', + description: + "Grande effervescence pour toute l'équipe du Camping Paradis, qui prépare les Olympiades. Côté arrivants, Hélène et sa fille Eva viennent passer quelques jours dans le but d'optimiser les révisions d'E...", + start: '2025-06-30T22:55:00.000Z', + stop: '2025-06-30T23:45:00.000Z', + title: 'Camping Paradis' + }) +}) + +it('can handle empty guide', async () => { + const results = await parser({ + date, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) + }) + + expect(results).toEqual([]) +}) diff --git a/sites/tataplay.com/tataplay.com.config.js b/sites/tataplay.com/tataplay.com.config.js index 6cf3fda04..777634d81 100644 --- a/sites/tataplay.com/tataplay.com.config.js +++ b/sites/tataplay.com/tataplay.com.config.js @@ -1,78 +1,82 @@ -const axios = require('axios') - -module.exports = { - site: 'tataplay.com', - days: 1, - - url({ date }) { - return `https://tm.tapi.videoready.tv/content-detail/pub/api/v2/channels/schedule?date=${date.format('DD-MM-YYYY')}` - }, - - request: { - method: 'POST', - headers: { - 'Accept': '*/*', - 'Origin': 'https://watch.tataplay.com', - 'Referer': 'https://watch.tataplay.com/', - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', - 'content-type': 'application/json', - 'locale': 'ENG', - 'platform': 'web' - }, - data({ channel }) { - return { id: channel.site_id } - } - }, - - parser(context) { - let data = [] - try { - const json = JSON.parse(context.content) - const programs = json?.data?.epg || [] - - data = programs.map(program => ({ - title: program.title, - start: program.startTime, - stop: program.endTime, - description: program.desc, - category: program.category, - icon: program.boxCoverImage - })) - } catch { - data = [] - } - return data - }, - - async channels() { - const headers = { - 'Accept': '*/*', - 'Origin': 'https://watch.tataplay.com', - 'Referer': 'https://watch.tataplay.com/', - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', - 'content-type': 'application/json', - 'locale': 'ENG', - 'platform': 'web' - } - - const baseUrl = 'https://tm.tapi.videoready.tv/portal-search/pub/api/v1/channels/schedule' - const initialUrl = `${baseUrl}?date=&languageFilters=&genreFilters=&limit=20&offset=0` - const initialResponse = await axios.get(initialUrl, { headers }) - const total = initialResponse.data?.data?.total || 0 - const channels = [] - - for (let offset = 0; offset < total; offset += 20) { - const url = `${baseUrl}?date=&languageFilters=&genreFilters=&limit=20&offset=${offset}` - const response = await axios.get(url, { headers }) - const page = response.data?.data?.channelList || [] - channels.push(...page) - } - - return channels.map(channel => ({ - site_id: channel.id, - name: channel.title, - lang: 'en', - icon: channel.transparentImageUrl || channel.thumbnailImage - })) - } -} +const axios = require('axios') + +module.exports = { + site: 'tataplay.com', + days: 1, + + url({ date }) { + return `https://tm.tapi.videoready.tv/content-detail/pub/api/v2/channels/schedule?date=${date.format( + 'DD-MM-YYYY' + )}` + }, + + request: { + method: 'POST', + headers: { + Accept: '*/*', + Origin: 'https://watch.tataplay.com', + Referer: 'https://watch.tataplay.com/', + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + 'content-type': 'application/json', + locale: 'ENG', + platform: 'web' + }, + data({ channel }) { + return { id: channel.site_id } + } + }, + + parser(context) { + let data = [] + try { + const json = JSON.parse(context.content) + const programs = json?.data?.epg || [] + + data = programs.map(program => ({ + title: program.title, + start: program.startTime, + stop: program.endTime, + description: program.desc, + category: program.category, + icon: program.boxCoverImage + })) + } catch { + data = [] + } + return data + }, + + async channels() { + const headers = { + Accept: '*/*', + Origin: 'https://watch.tataplay.com', + Referer: 'https://watch.tataplay.com/', + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + 'content-type': 'application/json', + locale: 'ENG', + platform: 'web' + } + + const baseUrl = 'https://tm.tapi.videoready.tv/portal-search/pub/api/v1/channels/schedule' + const initialUrl = `${baseUrl}?date=&languageFilters=&genreFilters=&limit=20&offset=0` + const initialResponse = await axios.get(initialUrl, { headers }) + const total = initialResponse.data?.data?.total || 0 + const channels = [] + + for (let offset = 0; offset < total; offset += 20) { + const url = `${baseUrl}?date=&languageFilters=&genreFilters=&limit=20&offset=${offset}` + const response = await axios.get(url, { headers }) + const page = response.data?.data?.channelList || [] + channels.push(...page) + } + + return channels.map(channel => ({ + site_id: channel.id, + name: channel.title, + lang: 'en', + icon: channel.transparentImageUrl || channel.thumbnailImage + })) + } +} diff --git a/sites/tataplay.com/tataplay.com.test.js b/sites/tataplay.com/tataplay.com.test.js index b7adff1ce..2f5551af5 100644 --- a/sites/tataplay.com/tataplay.com.test.js +++ b/sites/tataplay.com/tataplay.com.test.js @@ -1,87 +1,89 @@ -const { parser, url, channels } = require('./tataplay.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-06-09', 'YYYY-MM-DD').startOf('d') -const channel = { site_id: '1001' } - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe('https://tm.tapi.videoready.tv/content-detail/pub/api/v2/channels/schedule?date=09-06-2025') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - - const results = parser({ content, date }) - - expect(results.length).toBe(2) - expect(results[0]).toMatchObject({ - title: 'Yeh Rishta Kya Kehlata Hai', - start: '2025-06-09T18:00:00.000Z', - stop: '2025-06-09T18:30:00.000Z', - description: 'The story of the Rajshri family and their journey through life.', - category: 'Drama', - icon: 'https://img.tataplay.com/thumbnails/1001/yeh-rishta.jpg' - }) - expect(results[1]).toMatchObject({ - title: 'Anupamaa', - start: '2025-06-09T18:30:00.000Z', - stop: '2025-06-09T19:00:00.000Z', - description: 'The story of Anupamaa, a housewife who rediscovers herself.', - category: 'Drama', - icon: 'https://img.tataplay.com/thumbnails/1001/anupamaa.jpg' - }) -}) - -it('can handle empty guide', () => { - const content = JSON.stringify({ data: { epg: [] } }) - const results = parser({ content, date }) - expect(results).toMatchObject([]) -}) - -it('can parse channel list', async () => { - const mockResponse = { - data: { - data: { - total: 2, - channelList: [ - { - id: '1001', - title: 'Star Plus', - transparentImageUrl: 'https://img.tataplay.com/channels/1001/logo.png' - }, - { - id: '1002', - title: 'Sony TV', - transparentImageUrl: 'https://img.tataplay.com/channels/1002/logo.png' - } - ] - } - } - } - - // Mock axios.get to return our test data - const axios = require('axios') - axios.get = jest.fn().mockResolvedValue(mockResponse) - - const results = await channels() - - expect(results.length).toBe(2) - expect(results[0]).toMatchObject({ - site_id: '1001', - name: 'Star Plus', - lang: 'en', - icon: 'https://img.tataplay.com/channels/1001/logo.png' - }) - expect(results[1]).toMatchObject({ - site_id: '1002', - name: 'Sony TV', - lang: 'en', - icon: 'https://img.tataplay.com/channels/1002/logo.png' - }) -}) \ No newline at end of file +const { parser, url, channels } = require('./tataplay.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-06-09', 'YYYY-MM-DD').startOf('d') +const channel = { site_id: '1001' } + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://tm.tapi.videoready.tv/content-detail/pub/api/v2/channels/schedule?date=09-06-2025' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + + const results = parser({ content, date }) + + expect(results.length).toBe(2) + expect(results[0]).toMatchObject({ + title: 'Yeh Rishta Kya Kehlata Hai', + start: '2025-06-09T18:00:00.000Z', + stop: '2025-06-09T18:30:00.000Z', + description: 'The story of the Rajshri family and their journey through life.', + category: 'Drama', + icon: 'https://img.tataplay.com/thumbnails/1001/yeh-rishta.jpg' + }) + expect(results[1]).toMatchObject({ + title: 'Anupamaa', + start: '2025-06-09T18:30:00.000Z', + stop: '2025-06-09T19:00:00.000Z', + description: 'The story of Anupamaa, a housewife who rediscovers herself.', + category: 'Drama', + icon: 'https://img.tataplay.com/thumbnails/1001/anupamaa.jpg' + }) +}) + +it('can handle empty guide', () => { + const content = JSON.stringify({ data: { epg: [] } }) + const results = parser({ content, date }) + expect(results).toMatchObject([]) +}) + +it('can parse channel list', async () => { + const mockResponse = { + data: { + data: { + total: 2, + channelList: [ + { + id: '1001', + title: 'Star Plus', + transparentImageUrl: 'https://img.tataplay.com/channels/1001/logo.png' + }, + { + id: '1002', + title: 'Sony TV', + transparentImageUrl: 'https://img.tataplay.com/channels/1002/logo.png' + } + ] + } + } + } + + // Mock axios.get to return our test data + const axios = require('axios') + axios.get = jest.fn().mockResolvedValue(mockResponse) + + const results = await channels() + + expect(results.length).toBe(2) + expect(results[0]).toMatchObject({ + site_id: '1001', + name: 'Star Plus', + lang: 'en', + icon: 'https://img.tataplay.com/channels/1001/logo.png' + }) + expect(results[1]).toMatchObject({ + site_id: '1002', + name: 'Sony TV', + lang: 'en', + icon: 'https://img.tataplay.com/channels/1002/logo.png' + }) +}) From 88652ab1aebd3693819bdf96b746ceeca5665949 Mon Sep 17 00:00:00 2001 From: theofficialomega <30985701+BellezaEmporium@users.noreply.github.com> Date: Mon, 28 Jul 2025 21:17:55 +0200 Subject: [PATCH 22/32] modify eslint configuration for CI issues --- eslint.config.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 57f9e877e..7d6726126 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -36,7 +36,7 @@ export default [ '@typescript-eslint/no-require-imports': 'off', '@typescript-eslint/no-var-requires': 'off', 'no-case-declarations': 'off', - 'linebreak-style': ['error', 'windows'], + 'linebreak-style': ['error', process.env.CI ? 'unix' : 'windows'], quotes: [ 'error', From e3c7a372f2793a9eacdf00bbc579a3eec0f54aa8 Mon Sep 17 00:00:00 2001 From: theofficialomega <30985701+BellezaEmporium@users.noreply.github.com> Date: Mon, 28 Jul 2025 22:28:48 +0200 Subject: [PATCH 23/32] stricter ESLint configuration, linebreak on stylistic per deprecation by ESLint, fixed changes. add attibutes to prevent blockade. --- .gitattributes | 2 + eslint.config.mjs | 112 +- package-lock.json | 45 + package.json | 1 + scripts/commands/channels/edit.ts | 432 +- scripts/commands/channels/parse.ts | 174 +- scripts/commands/channels/validate.ts | 184 +- scripts/commands/epg/grab.ts | 266 +- scripts/core/channelsParser.ts | 44 +- scripts/core/dataProcessor.ts | 111 +- scripts/core/grabber.ts | 210 +- scripts/core/guideManager.ts | 222 +- scripts/core/htmlTable.ts | 110 +- scripts/core/job.ts | 68 +- scripts/core/proxyParser.ts | 62 +- scripts/core/queue.ts | 90 +- scripts/core/queueCreator.ts | 126 +- scripts/functions/functions.ts | 154 +- scripts/models/channel.ts | 328 +- scripts/models/channelList.ts | 154 +- scripts/models/feed.ts | 248 +- scripts/models/guide.ts | 70 +- scripts/models/issue.ts | 48 +- scripts/models/logo.ts | 82 +- scripts/models/site.ts | 126 +- scripts/models/stream.ts | 116 +- scripts/types/channel.d.ts | 54 +- scripts/types/dataLoader.d.ts | 40 +- scripts/types/dataProcessor.d.ts | 32 +- scripts/types/feed.d.ts | 24 +- scripts/types/guide.d.ts | 16 +- scripts/types/logo.d.ts | 18 +- scripts/types/stream.d.ts | 20 +- sites/tvim.tv/tvim.tv.test.js | 82 +- sites/tvinsider.com/tvinsider.com.config.js | 254 +- sites/tvireland.ie/tvireland.ie.config.js | 198 +- sites/tvmusor.hu/tvmusor.hu.config.js | 162 +- sites/tvprofil.com/tvprofil.com.channels.xml | 11678 ++++++++-------- .../web.magentatv.de.config.js | 426 +- tests/commands/channels/lint.test.ts | 166 +- tests/commands/channels/validate.test.ts | 140 +- 41 files changed, 8471 insertions(+), 8424 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..f7cc30f08 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Enforce the usage of CRLF in GitHub Actions per ESLint configuration. +* text eol=crlf \ No newline at end of file diff --git a/eslint.config.mjs b/eslint.config.mjs index 7d6726126..7f8ffde68 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,55 +1,57 @@ -import typescriptEslint from '@typescript-eslint/eslint-plugin' -import globals from 'globals' -import tsParser from '@typescript-eslint/parser' -import path from 'node:path' -import { fileURLToPath } from 'node:url' -import js from '@eslint/js' -import { FlatCompat } from '@eslint/eslintrc' - -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) -const compat = new FlatCompat({ - baseDirectory: __dirname, - recommendedConfig: js.configs.recommended, - allConfig: js.configs.all -}) - -export default [ - ...compat.extends('eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'), - { - plugins: { - '@typescript-eslint': typescriptEslint - }, - - languageOptions: { - globals: { - ...globals.node, - ...globals.jest - }, - - parser: tsParser, - ecmaVersion: 'latest', - sourceType: 'module' - }, - - rules: { - '@typescript-eslint/no-require-imports': 'off', - '@typescript-eslint/no-var-requires': 'off', - 'no-case-declarations': 'off', - 'linebreak-style': ['error', process.env.CI ? 'unix' : 'windows'], - - quotes: [ - 'error', - 'single', - { - avoidEscape: true - } - ], - - semi: ['error', 'never'] - } - }, - { - ignores: ['tests/__data__/'] - } -] +import typescriptEslint from '@typescript-eslint/eslint-plugin' +import stylistic from '@stylistic/eslint-plugin' +import globals from 'globals' +import tsParser from '@typescript-eslint/parser' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import js from '@eslint/js' +import { FlatCompat } from '@eslint/eslintrc' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + allConfig: js.configs.all +}) + +export default [ + ...compat.extends('eslint:recommended', 'plugin:@typescript-eslint/strict', 'plugin:@typescript-eslint/stylistic', 'prettier'), + { + plugins: { + '@typescript-eslint': typescriptEslint, + '@stylistic': stylistic + }, + + languageOptions: { + globals: { + ...globals.node, + ...globals.jest + }, + + parser: tsParser, + ecmaVersion: 'latest', + sourceType: 'module' + }, + + rules: { + '@typescript-eslint/no-require-imports': 'off', + '@typescript-eslint/no-var-requires': 'off', + 'no-case-declarations': 'off', + '@stylistic/linebreak-style': ['error', 'windows'], + + quotes: [ + 'error', + 'single', + { + avoidEscape: true + } + ], + + semi: ['error', 'never'] + } + }, + { + ignores: ['tests/__data__/'] + } +] diff --git a/package-lock.json b/package-lock.json index 857082785..97f56541e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@octokit/core": "^7.0.3", "@octokit/plugin-paginate-rest": "^13.1.1", "@octokit/plugin-rest-endpoint-methods": "^16.0.0", + "@stylistic/eslint-plugin": "^5.2.2", "@swc/core": "^1.13.2", "@swc/jest": "^0.2.39", "@types/cli-progress": "^3.11.6", @@ -3146,6 +3147,50 @@ "@sinonjs/commons": "^3.0.1" } }, + "node_modules/@stylistic/eslint-plugin": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.2.2.tgz", + "integrity": "sha512-bE2DUjruqXlHYP3Q2Gpqiuj2bHq7/88FnuaS0FjeGGLCy+X6a07bGVuwtiOYnPSLHR6jmx5Bwdv+j7l8H+G97A==", + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/types": "^8.37.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "estraverse": "^5.3.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": ">=9.0.0" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@swc/core": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.2.tgz", diff --git a/package.json b/package.json index 40fad1ec0..d72355344 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "@octokit/core": "^7.0.3", "@octokit/plugin-paginate-rest": "^13.1.1", "@octokit/plugin-rest-endpoint-methods": "^16.0.0", + "@stylistic/eslint-plugin": "^5.2.2", "@swc/core": "^1.13.2", "@swc/jest": "^0.2.39", "@types/cli-progress": "^3.11.6", diff --git a/scripts/commands/channels/edit.ts b/scripts/commands/channels/edit.ts index 4a5e714a1..3618fbff8 100644 --- a/scripts/commands/channels/edit.ts +++ b/scripts/commands/channels/edit.ts @@ -1,216 +1,216 @@ -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 { ChannelsParser } from '../../core' -import { DATA_DIR } from '../../constants' -import nodeCleanup from 'node-cleanup' -import sjs from '@freearhey/search-js' -import epgGrabber from 'epg-grabber' -import { Command } from 'commander' -import readline from 'readline' - -type ChoiceValue = { type: string; value?: Feed | Channel } -type Choice = { name: string; short?: string; value: ChoiceValue; default?: boolean } - -if (process.platform === 'win32') { - readline - .createInterface({ - input: process.stdin, - output: process.stdout - }) - .on('SIGINT', function () { - process.emit('SIGINT') - }) -} - -const program = new Command() - -program.argument('', 'Path to *.channels.xml file to edit').parse(process.argv) - -const filepath = program.args[0] -const logger = new Logger() -const storage = new Storage() -let channelList = new ChannelList({ channels: [] }) - -main(filepath) -nodeCleanup(() => { - save(filepath, channelList) -}) - -export default async function main(filepath: string) { - if (!(await storage.exists(filepath))) { - throw new Error(`File "${filepath}" does not exists`) - } - - logger.info('loading data from api...') - const processor = new DataProcessor() - const dataStorage = new Storage(DATA_DIR) - const loader = new DataLoader({ storage: dataStorage }) - const data: DataLoaderData = await loader.load() - const { channels, channelsKeyById, feedsGroupedByChannelId }: DataProcessorData = - processor.process(data) - - logger.info('loading channels...') - const parser = new ChannelsParser({ storage }) - channelList = await parser.parse(filepath) - const parsedChannelsWithoutId = channelList.channels.filter( - (channel: epgGrabber.Channel) => !channel.xmltv_id - ) - - logger.info( - `found ${channelList.channels.count()} channels (including ${parsedChannelsWithoutId.count()} without ID)` - ) - - logger.info('creating search index...') - const items = channels.map((channel: Channel) => channel.getSearchable()).all() - const searchIndex = sjs.createIndex(items, { - searchable: ['name', 'altNames', 'guideNames', 'streamNames', 'feedFullNames'] - }) - - logger.info('starting...\n') - - for (const channel of parsedChannelsWithoutId.all()) { - try { - channel.xmltv_id = await selectChannel( - channel, - searchIndex, - feedsGroupedByChannelId, - channelsKeyById - ) - } catch (err) { - logger.info(err.message) - break - } - } - - parsedChannelsWithoutId.forEach((channel: epgGrabber.Channel) => { - if (channel.xmltv_id === '-') { - channel.xmltv_id = '' - } - }) -} - -async function selectChannel( - channel: epgGrabber.Channel, - searchIndex, - feedsGroupedByChannelId: Dictionary, - channelsKeyById: Dictionary -): Promise { - const query = escapeRegex(channel.name) - const similarChannels = searchIndex - .search(query) - .map((item: ChannelSearchableData) => channelsKeyById.get(item.id)) - - const selected: ChoiceValue = await select({ - message: `Select channel ID for "${channel.name}" (${channel.site_id}):`, - choices: getChannelChoises(new Collection(similarChannels)), - pageSize: 10 - }) - - switch (selected.type) { - case 'skip': - return '-' - case 'type': { - const typedChannelId = await input({ message: ' Channel ID:' }) - if (!typedChannelId) return '' - const selectedFeedId = await selectFeed(typedChannelId, feedsGroupedByChannelId) - if (selectedFeedId === '-') return typedChannelId - return [typedChannelId, selectedFeedId].join('@') - } - case 'channel': { - const selectedChannel = selected.value - if (!selectedChannel) return '' - const selectedFeedId = await selectFeed(selectedChannel.id || '', feedsGroupedByChannelId) - if (selectedFeedId === '-') return selectedChannel.id || '' - return [selectedChannel.id, selectedFeedId].join('@') - } - } - - return '' -} - -async function selectFeed(channelId: string, feedsGroupedByChannelId: Dictionary): Promise { - const channelFeeds = feedsGroupedByChannelId.has(channelId) - ? new Collection(feedsGroupedByChannelId.get(channelId)) - : new Collection() - const choices = getFeedChoises(channelFeeds) - - const selected: ChoiceValue = await select({ - message: `Select feed ID for "${channelId}":`, - choices, - pageSize: 10 - }) - - switch (selected.type) { - case 'skip': - return '-' - case 'type': - return await input({ message: ' Feed ID:', default: 'SD' }) - case 'feed': - const selectedFeed = selected.value - if (!selectedFeed) return '' - return selectedFeed.id || '' - } - - return '' -} - -function getChannelChoises(channels: Collection): Choice[] { - const choises: Choice[] = [] - - channels.forEach((channel: Channel) => { - const names = new Collection([channel.name, ...channel.getAltNames().all()]).uniq().join(', ') - - choises.push({ - value: { - type: 'channel', - value: channel - }, - name: `${channel.id} (${names})`, - short: `${channel.id}` - }) - }) - - choises.push({ name: 'Type...', value: { type: 'type' } }) - choises.push({ name: 'Skip', value: { type: 'skip' } }) - - return choises -} - -function getFeedChoises(feeds: Collection): Choice[] { - const choises: Choice[] = [] - - feeds.forEach((feed: Feed) => { - let name = `${feed.id} (${feed.name})` - if (feed.isMain) name += ' [main]' - - choises.push({ - value: { - type: 'feed', - value: feed - }, - default: feed.isMain, - name, - short: feed.id - }) - }) - - choises.push({ name: 'Type...', value: { type: 'type' } }) - choises.push({ name: 'Skip', value: { type: 'skip' } }) - - return choises -} - -function save(filepath: string, channelList: ChannelList) { - if (!storage.existsSync(filepath)) return - storage.saveSync(filepath, channelList.toString()) - logger.info(`\nFile '${filepath}' successfully saved`) -} - -function escapeRegex(string: string) { - return string.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&') -} +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 { ChannelsParser } from '../../core' +import { DATA_DIR } from '../../constants' +import nodeCleanup from 'node-cleanup' +import sjs from '@freearhey/search-js' +import epgGrabber from 'epg-grabber' +import { Command } from 'commander' +import readline from 'readline' + +interface ChoiceValue { type: string; value?: Feed | Channel } +interface Choice { name: string; short?: string; value: ChoiceValue; default?: boolean } + +if (process.platform === 'win32') { + readline + .createInterface({ + input: process.stdin, + output: process.stdout + }) + .on('SIGINT', function () { + process.emit('SIGINT') + }) +} + +const program = new Command() + +program.argument('', 'Path to *.channels.xml file to edit').parse(process.argv) + +const filepath = program.args[0] +const logger = new Logger() +const storage = new Storage() +let channelList = new ChannelList({ channels: [] }) + +main(filepath) +nodeCleanup(() => { + save(filepath, channelList) +}) + +export default async function main(filepath: string) { + if (!(await storage.exists(filepath))) { + throw new Error(`File "${filepath}" does not exists`) + } + + logger.info('loading data from api...') + const processor = new DataProcessor() + const dataStorage = new Storage(DATA_DIR) + const loader = new DataLoader({ storage: dataStorage }) + const data: DataLoaderData = await loader.load() + const { channels, channelsKeyById, feedsGroupedByChannelId }: DataProcessorData = + processor.process(data) + + logger.info('loading channels...') + const parser = new ChannelsParser({ storage }) + channelList = await parser.parse(filepath) + const parsedChannelsWithoutId = channelList.channels.filter( + (channel: epgGrabber.Channel) => !channel.xmltv_id + ) + + logger.info( + `found ${channelList.channels.count()} channels (including ${parsedChannelsWithoutId.count()} without ID)` + ) + + logger.info('creating search index...') + const items = channels.map((channel: Channel) => channel.getSearchable()).all() + const searchIndex = sjs.createIndex(items, { + searchable: ['name', 'altNames', 'guideNames', 'streamNames', 'feedFullNames'] + }) + + logger.info('starting...\n') + + for (const channel of parsedChannelsWithoutId.all()) { + try { + channel.xmltv_id = await selectChannel( + channel, + searchIndex, + feedsGroupedByChannelId, + channelsKeyById + ) + } catch (err) { + logger.info(err.message) + break + } + } + + parsedChannelsWithoutId.forEach((channel: epgGrabber.Channel) => { + if (channel.xmltv_id === '-') { + channel.xmltv_id = '' + } + }) +} + +async function selectChannel( + channel: epgGrabber.Channel, + searchIndex, + feedsGroupedByChannelId: Dictionary, + channelsKeyById: Dictionary +): Promise { + const query = escapeRegex(channel.name) + const similarChannels = searchIndex + .search(query) + .map((item: ChannelSearchableData) => channelsKeyById.get(item.id)) + + const selected: ChoiceValue = await select({ + message: `Select channel ID for "${channel.name}" (${channel.site_id}):`, + choices: getChannelChoises(new Collection(similarChannels)), + pageSize: 10 + }) + + switch (selected.type) { + case 'skip': + return '-' + case 'type': { + const typedChannelId = await input({ message: ' Channel ID:' }) + if (!typedChannelId) return '' + const selectedFeedId = await selectFeed(typedChannelId, feedsGroupedByChannelId) + if (selectedFeedId === '-') return typedChannelId + return [typedChannelId, selectedFeedId].join('@') + } + case 'channel': { + const selectedChannel = selected.value + if (!selectedChannel) return '' + const selectedFeedId = await selectFeed(selectedChannel.id || '', feedsGroupedByChannelId) + if (selectedFeedId === '-') return selectedChannel.id || '' + return [selectedChannel.id, selectedFeedId].join('@') + } + } + + return '' +} + +async function selectFeed(channelId: string, feedsGroupedByChannelId: Dictionary): Promise { + const channelFeeds = feedsGroupedByChannelId.has(channelId) + ? new Collection(feedsGroupedByChannelId.get(channelId)) + : new Collection() + const choices = getFeedChoises(channelFeeds) + + const selected: ChoiceValue = await select({ + message: `Select feed ID for "${channelId}":`, + choices, + pageSize: 10 + }) + + switch (selected.type) { + case 'skip': + return '-' + case 'type': + return await input({ message: ' Feed ID:', default: 'SD' }) + case 'feed': + const selectedFeed = selected.value + if (!selectedFeed) return '' + return selectedFeed.id || '' + } + + return '' +} + +function getChannelChoises(channels: Collection): Choice[] { + const choises: Choice[] = [] + + channels.forEach((channel: Channel) => { + const names = new Collection([channel.name, ...channel.getAltNames().all()]).uniq().join(', ') + + choises.push({ + value: { + type: 'channel', + value: channel + }, + name: `${channel.id} (${names})`, + short: `${channel.id}` + }) + }) + + choises.push({ name: 'Type...', value: { type: 'type' } }) + choises.push({ name: 'Skip', value: { type: 'skip' } }) + + return choises +} + +function getFeedChoises(feeds: Collection): Choice[] { + const choises: Choice[] = [] + + feeds.forEach((feed: Feed) => { + let name = `${feed.id} (${feed.name})` + if (feed.isMain) name += ' [main]' + + choises.push({ + value: { + type: 'feed', + value: feed + }, + default: feed.isMain, + name, + short: feed.id + }) + }) + + choises.push({ name: 'Type...', value: { type: 'type' } }) + choises.push({ name: 'Skip', value: { type: 'skip' } }) + + return choises +} + +function save(filepath: string, channelList: ChannelList) { + if (!storage.existsSync(filepath)) return + storage.saveSync(filepath, channelList.toString()) + logger.info(`\nFile '${filepath}' successfully saved`) +} + +function escapeRegex(string: string) { + return string.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&') +} diff --git a/scripts/commands/channels/parse.ts b/scripts/commands/channels/parse.ts index fb0e04475..58eccd68a 100644 --- a/scripts/commands/channels/parse.ts +++ b/scripts/commands/channels/parse.ts @@ -1,88 +1,86 @@ -import { Logger, File, Storage } from '@freearhey/core' -import { ChannelsParser } from '../../core' -import { ChannelList } from '../../models' -import { pathToFileURL } from 'node:url' -import epgGrabber from 'epg-grabber' -import { Command } from 'commander' - -const program = new Command() -program - .requiredOption('-c, --config ', 'Config file') - .option('-s, --set [args...]', 'Set custom arguments') - .option('-o, --output ', 'Output file') - .parse(process.argv) - -type ParseOptions = { - config: string - set?: string - output?: string - clean?: boolean -} - -const options: ParseOptions = program.opts() - -async function main() { - function isPromise(promise: object[] | Promise) { - return ( - !!promise && - typeof promise === 'object' && - typeof (promise as Promise).then === 'function' - ) - } - - const storage = new Storage() - const logger = new Logger() - const parser = new ChannelsParser({ storage }) - const file = new File(options.config) - const dir = file.dirname() - const config = (await import(pathToFileURL(options.config).toString())).default - const outputFilepath = options.output || `${dir}/${config.site}.channels.xml` - - let channelList = new ChannelList({ channels: [] }) - if (await storage.exists(outputFilepath)) { - channelList = await parser.parse(outputFilepath) - } - - const args: { - [key: string]: string - } = {} - - if (Array.isArray(options.set)) { - options.set.forEach((arg: string) => { - const [key, value] = arg.split(':') - args[key] = value - }) - } - - let parsedChannels = config.channels(args) - if (isPromise(parsedChannels)) { - parsedChannels = await parsedChannels - } - parsedChannels = parsedChannels.map((channel: epgGrabber.Channel) => { - channel.site = config.site - - return channel - }) - - const newChannelList = new ChannelList({ channels: [] }) - parsedChannels.forEach((channel: epgGrabber.Channel) => { - if (!channel.site_id) return - - const found: epgGrabber.Channel | undefined = channelList.get(channel.site_id) - - if (found) { - channel.xmltv_id = found.xmltv_id - channel.lang = found.lang - } - - newChannelList.add(channel) - }) - - newChannelList.sort() - - await storage.save(outputFilepath, newChannelList.toString()) - - logger.info(`File '${outputFilepath}' successfully saved`) -} - -main() +import { Logger, File, Storage } from '@freearhey/core' +import { ChannelsParser } from '../../core' +import { ChannelList } from '../../models' +import { pathToFileURL } from 'node:url' +import epgGrabber from 'epg-grabber' +import { Command } from 'commander' + +const program = new Command() +program + .requiredOption('-c, --config ', 'Config file') + .option('-s, --set [args...]', 'Set custom arguments') + .option('-o, --output ', 'Output file') + .parse(process.argv) + +interface ParseOptions { + config: string + set?: string + output?: string + clean?: boolean +} + +const options: ParseOptions = program.opts() + +async function main() { + function isPromise(promise: object[] | Promise) { + return ( + !!promise && + typeof promise === 'object' && + typeof (promise as Promise).then === 'function' + ) + } + + const storage = new Storage() + const logger = new Logger() + const parser = new ChannelsParser({ storage }) + const file = new File(options.config) + const dir = file.dirname() + const config = (await import(pathToFileURL(options.config).toString())).default + const outputFilepath = options.output || `${dir}/${config.site}.channels.xml` + + let channelList = new ChannelList({ channels: [] }) + if (await storage.exists(outputFilepath)) { + channelList = await parser.parse(outputFilepath) + } + + const args: Record = {} + + if (Array.isArray(options.set)) { + options.set.forEach((arg: string) => { + const [key, value] = arg.split(':') + args[key] = value + }) + } + + let parsedChannels = config.channels(args) + if (isPromise(parsedChannels)) { + parsedChannels = await parsedChannels + } + parsedChannels = parsedChannels.map((channel: epgGrabber.Channel) => { + channel.site = config.site + + return channel + }) + + const newChannelList = new ChannelList({ channels: [] }) + parsedChannels.forEach((channel: epgGrabber.Channel) => { + if (!channel.site_id) return + + const found: epgGrabber.Channel | undefined = channelList.get(channel.site_id) + + if (found) { + channel.xmltv_id = found.xmltv_id + channel.lang = found.lang + } + + newChannelList.add(channel) + }) + + newChannelList.sort() + + await storage.save(outputFilepath, newChannelList.toString()) + + logger.info(`File '${outputFilepath}' successfully saved`) +} + +main() diff --git a/scripts/commands/channels/validate.ts b/scripts/commands/channels/validate.ts index 358e1168b..b9809a627 100644 --- a/scripts/commands/channels/validate.ts +++ b/scripts/commands/channels/validate.ts @@ -1,92 +1,92 @@ -import { ChannelsParser, DataLoader, DataProcessor } from '../../core' -import { DataProcessorData } from '../../types/dataProcessor' -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 chalk from 'chalk' -import langs from 'langs' - -program.argument('[filepath...]', 'Path to *.channels.xml files to validate').parse(process.argv) - -type ValidationError = { - type: 'duplicate' | 'wrong_channel_id' | 'wrong_feed_id' | 'wrong_lang' - name: string - lang?: string - xmltv_id?: string - site_id?: string - logo?: string -} - -async function main() { - const processor = new DataProcessor() - const dataStorage = new Storage(DATA_DIR) - const loader = new DataLoader({ storage: dataStorage }) - const data: DataLoaderData = await loader.load() - const { channelsKeyById, feedsKeyByStreamId }: DataProcessorData = processor.process(data) - const parser = new ChannelsParser({ - storage: new Storage() - }) - - let totalFiles = 0 - let totalErrors = 0 - - const storage = new Storage() - const files = program.args.length ? program.args : await storage.list('sites/**/*.channels.xml') - for (const filepath of files) { - const file = new File(filepath) - if (file.extension() !== 'xml') continue - - const channelList: ChannelList = await parser.parse(filepath) - - const bufferBySiteId = new Dictionary() - const errors: ValidationError[] = [] - channelList.channels.forEach((channel: epgGrabber.Channel) => { - const bufferId: string = channel.site_id - if (bufferBySiteId.missing(bufferId)) { - bufferBySiteId.set(bufferId, true) - } else { - errors.push({ type: 'duplicate', ...channel }) - totalErrors++ - } - - if (!langs.where('1', channel.lang ?? '')) { - errors.push({ type: 'wrong_lang', ...channel }) - totalErrors++ - } - - if (!channel.xmltv_id) return - const [channelId, feedId] = channel.xmltv_id.split('@') - - const foundChannel = channelsKeyById.get(channelId) - if (!foundChannel) { - errors.push({ type: 'wrong_channel_id', ...channel }) - totalErrors++ - } - - if (feedId) { - const foundFeed = feedsKeyByStreamId.get(channel.xmltv_id) - if (!foundFeed) { - errors.push({ type: 'wrong_feed_id', ...channel }) - totalErrors++ - } - } - }) - - if (errors.length) { - console.log(chalk.underline(filepath)) - console.table(errors, ['type', 'lang', 'xmltv_id', 'site_id', 'name']) - console.log() - totalFiles++ - } - } - - if (totalErrors > 0) { - console.log(chalk.red(`${totalErrors} error(s) in ${totalFiles} file(s)`)) - process.exit(1) - } -} - -main() +import { ChannelsParser, DataLoader, DataProcessor } from '../../core' +import { DataProcessorData } from '../../types/dataProcessor' +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 chalk from 'chalk' +import langs from 'langs' + +program.argument('[filepath...]', 'Path to *.channels.xml files to validate').parse(process.argv) + +interface ValidationError { + type: 'duplicate' | 'wrong_channel_id' | 'wrong_feed_id' | 'wrong_lang' + name: string + lang?: string + xmltv_id?: string + site_id?: string + logo?: string +} + +async function main() { + const processor = new DataProcessor() + const dataStorage = new Storage(DATA_DIR) + const loader = new DataLoader({ storage: dataStorage }) + const data: DataLoaderData = await loader.load() + const { channelsKeyById, feedsKeyByStreamId }: DataProcessorData = processor.process(data) + const parser = new ChannelsParser({ + storage: new Storage() + }) + + let totalFiles = 0 + let totalErrors = 0 + + const storage = new Storage() + const files = program.args.length ? program.args : await storage.list('sites/**/*.channels.xml') + for (const filepath of files) { + const file = new File(filepath) + if (file.extension() !== 'xml') continue + + const channelList: ChannelList = await parser.parse(filepath) + + const bufferBySiteId = new Dictionary() + const errors: ValidationError[] = [] + channelList.channels.forEach((channel: epgGrabber.Channel) => { + const bufferId: string = channel.site_id + if (bufferBySiteId.missing(bufferId)) { + bufferBySiteId.set(bufferId, true) + } else { + errors.push({ type: 'duplicate', ...channel }) + totalErrors++ + } + + if (!langs.where('1', channel.lang ?? '')) { + errors.push({ type: 'wrong_lang', ...channel }) + totalErrors++ + } + + if (!channel.xmltv_id) return + const [channelId, feedId] = channel.xmltv_id.split('@') + + const foundChannel = channelsKeyById.get(channelId) + if (!foundChannel) { + errors.push({ type: 'wrong_channel_id', ...channel }) + totalErrors++ + } + + if (feedId) { + const foundFeed = feedsKeyByStreamId.get(channel.xmltv_id) + if (!foundFeed) { + errors.push({ type: 'wrong_feed_id', ...channel }) + totalErrors++ + } + } + }) + + if (errors.length) { + console.log(chalk.underline(filepath)) + console.table(errors, ['type', 'lang', 'xmltv_id', 'site_id', 'name']) + console.log() + totalFiles++ + } + } + + if (totalErrors > 0) { + console.log(chalk.red(`${totalErrors} error(s) in ${totalFiles} file(s)`)) + process.exit(1) + } +} + +main() diff --git a/scripts/commands/epg/grab.ts b/scripts/commands/epg/grab.ts index ebf94e703..9e1112947 100644 --- a/scripts/commands/epg/grab.ts +++ b/scripts/commands/epg/grab.ts @@ -1,133 +1,133 @@ -import { Logger, Timer, Storage, Collection } from '@freearhey/core' -import { QueueCreator, Job, ChannelsParser } from '../../core' -import { Option, program } from 'commander' -import { SITES_DIR } from '../../constants' -import { Channel } from 'epg-grabber' -import path from 'path' -import { ChannelList } from '../../models' - -program - .addOption(new Option('-s, --site ', 'Name of the site to parse')) - .addOption( - new Option( - '-c, --channels ', - 'Path to *.channels.xml file (required if the "--site" attribute is not specified)' - ) - ) - .addOption(new Option('-o, --output ', 'Path to output file').default('guide.xml')) - .addOption(new Option('-l, --lang ', 'Filter channels by languages (ISO 639-1 codes)')) - .addOption( - new Option('-t, --timeout ', 'Override the default timeout for each request').env( - 'TIMEOUT' - ) - ) - .addOption( - new Option('-d, --delay ', 'Override the default delay between request').env( - 'DELAY' - ) - ) - .addOption(new Option('-x, --proxy ', 'Use the specified proxy').env('PROXY')) - .addOption( - new Option( - '--days ', - 'Override the number of days for which the program will be loaded (defaults to the value from the site config)' - ) - .argParser(value => parseInt(value)) - .env('DAYS') - ) - .addOption( - new Option('--maxConnections ', 'Limit on the number of concurrent requests') - .default(1) - .env('MAX_CONNECTIONS') - ) - .addOption( - new Option('--gzip', 'Create a compressed version of the guide as well') - .default(false) - .env('GZIP') - ) - .addOption(new Option('--curl', 'Display each request as CURL').default(false).env('CURL')) - .parse() - -export type GrabOptions = { - site?: string - channels?: string - output: string - gzip: boolean - curl: boolean - maxConnections: number - timeout?: string - delay?: string - lang?: string - days?: number - proxy?: string -} - -const options: GrabOptions = program.opts() - -async function main() { - if (!options.site && !options.channels) - throw new Error('One of the arguments must be presented: `--site` or `--channels`') - - const logger = new Logger() - - logger.start('starting...') - - logger.info('config:') - logger.tree(options) - - logger.info('loading channels...') - const storage = new Storage() - const parser = new ChannelsParser({ storage }) - - let files: string[] = [] - if (options.site) { - let pattern = path.join(SITES_DIR, options.site, '*.channels.xml') - pattern = pattern.replace(/\\/g, '/') - files = await storage.list(pattern) - } else if (options.channels) { - files = await storage.list(options.channels) - } - - let channels = new Collection() - for (const filepath of files) { - const channelList: ChannelList = await parser.parse(filepath) - - channels = channels.concat(channelList.channels) - } - - if (options.lang) { - channels = channels.filter((channel: Channel) => { - if (!options.lang || !channel.lang) return true - - return options.lang.includes(channel.lang) - }) - } - - logger.info(` found ${channels.count()} channel(s)`) - - logger.info('run:') - runJob({ logger, channels }) -} - -main() - -async function runJob({ logger, channels }: { logger: Logger; channels: Collection }) { - const timer = new Timer() - timer.start() - - const queueCreator = new QueueCreator({ - channels, - logger, - options - }) - const queue = await queueCreator.create() - const job = new Job({ - queue, - logger, - options - }) - - await job.run() - - logger.success(` done in ${timer.format('HH[h] mm[m] ss[s]')}`) -} +import { Logger, Timer, Storage, Collection } from '@freearhey/core' +import { QueueCreator, Job, ChannelsParser } from '../../core' +import { Option, program } from 'commander' +import { SITES_DIR } from '../../constants' +import { Channel } from 'epg-grabber' +import path from 'path' +import { ChannelList } from '../../models' + +program + .addOption(new Option('-s, --site ', 'Name of the site to parse')) + .addOption( + new Option( + '-c, --channels ', + 'Path to *.channels.xml file (required if the "--site" attribute is not specified)' + ) + ) + .addOption(new Option('-o, --output ', 'Path to output file').default('guide.xml')) + .addOption(new Option('-l, --lang ', 'Filter channels by languages (ISO 639-1 codes)')) + .addOption( + new Option('-t, --timeout ', 'Override the default timeout for each request').env( + 'TIMEOUT' + ) + ) + .addOption( + new Option('-d, --delay ', 'Override the default delay between request').env( + 'DELAY' + ) + ) + .addOption(new Option('-x, --proxy ', 'Use the specified proxy').env('PROXY')) + .addOption( + new Option( + '--days ', + 'Override the number of days for which the program will be loaded (defaults to the value from the site config)' + ) + .argParser(value => parseInt(value)) + .env('DAYS') + ) + .addOption( + new Option('--maxConnections ', 'Limit on the number of concurrent requests') + .default(1) + .env('MAX_CONNECTIONS') + ) + .addOption( + new Option('--gzip', 'Create a compressed version of the guide as well') + .default(false) + .env('GZIP') + ) + .addOption(new Option('--curl', 'Display each request as CURL').default(false).env('CURL')) + .parse() + +export interface GrabOptions { + site?: string + channels?: string + output: string + gzip: boolean + curl: boolean + maxConnections: number + timeout?: string + delay?: string + lang?: string + days?: number + proxy?: string +} + +const options: GrabOptions = program.opts() + +async function main() { + if (!options.site && !options.channels) + throw new Error('One of the arguments must be presented: `--site` or `--channels`') + + const logger = new Logger() + + logger.start('starting...') + + logger.info('config:') + logger.tree(options) + + logger.info('loading channels...') + const storage = new Storage() + const parser = new ChannelsParser({ storage }) + + let files: string[] = [] + if (options.site) { + let pattern = path.join(SITES_DIR, options.site, '*.channels.xml') + pattern = pattern.replace(/\\/g, '/') + files = await storage.list(pattern) + } else if (options.channels) { + files = await storage.list(options.channels) + } + + let channels = new Collection() + for (const filepath of files) { + const channelList: ChannelList = await parser.parse(filepath) + + channels = channels.concat(channelList.channels) + } + + if (options.lang) { + channels = channels.filter((channel: Channel) => { + if (!options.lang || !channel.lang) return true + + return options.lang.includes(channel.lang) + }) + } + + logger.info(` found ${channels.count()} channel(s)`) + + logger.info('run:') + runJob({ logger, channels }) +} + +main() + +async function runJob({ logger, channels }: { logger: Logger; channels: Collection }) { + const timer = new Timer() + timer.start() + + const queueCreator = new QueueCreator({ + channels, + logger, + options + }) + const queue = await queueCreator.create() + const job = new Job({ + queue, + logger, + options + }) + + await job.run() + + logger.success(` done in ${timer.format('HH[h] mm[m] ss[s]')}`) +} diff --git a/scripts/core/channelsParser.ts b/scripts/core/channelsParser.ts index 43a5f28b3..d47aee7d8 100644 --- a/scripts/core/channelsParser.ts +++ b/scripts/core/channelsParser.ts @@ -1,22 +1,22 @@ -import { parseChannels } from 'epg-grabber' -import { Storage } from '@freearhey/core' -import { ChannelList } from '../models' - -type ChannelsParserProps = { - storage: Storage -} - -export class ChannelsParser { - storage: Storage - - constructor({ storage }: ChannelsParserProps) { - this.storage = storage - } - - async parse(filepath: string): Promise { - const content = await this.storage.load(filepath) - const parsed = parseChannels(content) - - return new ChannelList({ channels: parsed }) - } -} +import { parseChannels } from 'epg-grabber' +import { Storage } from '@freearhey/core' +import { ChannelList } from '../models' + +interface ChannelsParserProps { + storage: Storage +} + +export class ChannelsParser { + storage: Storage + + constructor({ storage }: ChannelsParserProps) { + this.storage = storage + } + + async parse(filepath: string): Promise { + const content = await this.storage.load(filepath) + const parsed = parseChannels(content) + + return new ChannelList({ channels: parsed }) + } +} diff --git a/scripts/core/dataProcessor.ts b/scripts/core/dataProcessor.ts index 1f0252fc8..a104e6750 100644 --- a/scripts/core/dataProcessor.ts +++ b/scripts/core/dataProcessor.ts @@ -1,56 +1,55 @@ -import { Channel, Feed, GuideChannel, Logo, Stream } from '../models' -import { DataLoaderData } from '../types/dataLoader' -import { Collection } from '@freearhey/core' - -export class DataProcessor { - constructor() {} - - process(data: DataLoaderData) { - let channels = new Collection(data.channels).map(data => new Channel(data)) - const channelsKeyById = channels.keyBy((channel: Channel) => channel.id) - - const guideChannels = new Collection(data.guides).map(data => new GuideChannel(data)) - const guideChannelsGroupedByStreamId = guideChannels.groupBy((channel: GuideChannel) => - channel.getStreamId() - ) - - const streams = new Collection(data.streams).map(data => new Stream(data)) - const streamsGroupedById = streams.groupBy((stream: Stream) => stream.getId()) - - let feeds = new Collection(data.feeds).map(data => - new Feed(data) - .withGuideChannels(guideChannelsGroupedByStreamId) - .withStreams(streamsGroupedById) - .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) - - channels = channels.map((channel: Channel) => - channel.withFeeds(feedsGroupedByChannelId).withLogos(logosGroupedByChannelId) - ) - - return { - guideChannelsGroupedByStreamId, - feedsGroupedByChannelId, - logosGroupedByChannelId, - logosGroupedByStreamId, - streamsGroupedById, - feedsKeyByStreamId, - channelsKeyById, - guideChannels, - channels, - streams, - feeds, - logos - } - } -} +import { Channel, Feed, GuideChannel, Logo, Stream } from '../models' +import { DataLoaderData } from '../types/dataLoader' +import { Collection } from '@freearhey/core' + +export class DataProcessor { + + process(data: DataLoaderData) { + let channels = new Collection(data.channels).map(data => new Channel(data)) + const channelsKeyById = channels.keyBy((channel: Channel) => channel.id) + + const guideChannels = new Collection(data.guides).map(data => new GuideChannel(data)) + const guideChannelsGroupedByStreamId = guideChannels.groupBy((channel: GuideChannel) => + channel.getStreamId() + ) + + const streams = new Collection(data.streams).map(data => new Stream(data)) + const streamsGroupedById = streams.groupBy((stream: Stream) => stream.getId()) + + let feeds = new Collection(data.feeds).map(data => + new Feed(data) + .withGuideChannels(guideChannelsGroupedByStreamId) + .withStreams(streamsGroupedById) + .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) + + channels = channels.map((channel: Channel) => + channel.withFeeds(feedsGroupedByChannelId).withLogos(logosGroupedByChannelId) + ) + + return { + guideChannelsGroupedByStreamId, + feedsGroupedByChannelId, + logosGroupedByChannelId, + logosGroupedByStreamId, + streamsGroupedById, + feedsKeyByStreamId, + channelsKeyById, + guideChannels, + channels, + streams, + feeds, + logos + } + } +} diff --git a/scripts/core/grabber.ts b/scripts/core/grabber.ts index 57bd322d0..d1881af7f 100644 --- a/scripts/core/grabber.ts +++ b/scripts/core/grabber.ts @@ -1,105 +1,105 @@ -import { EPGGrabber, GrabCallbackData, EPGGrabberMock, SiteConfig, Channel } from 'epg-grabber' -import { Logger, Collection } from '@freearhey/core' -import { Queue, ProxyParser } from './' -import { GrabOptions } from '../commands/epg/grab' -import { TaskQueue, PromisyClass } from 'cwait' -import { SocksProxyAgent } from 'socks-proxy-agent' - -type GrabberProps = { - logger: Logger - queue: Queue - options: GrabOptions -} - -export class Grabber { - logger: Logger - queue: Queue - options: GrabOptions - grabber: EPGGrabber | EPGGrabberMock - - constructor({ logger, queue, options }: GrabberProps) { - this.logger = logger - this.queue = queue - this.options = options - this.grabber = process.env.NODE_ENV === 'test' ? new EPGGrabberMock() : new EPGGrabber() - } - - async grab(): Promise<{ channels: Collection; programs: Collection }> { - const proxyParser = new ProxyParser() - 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 } } - } - - if (this.options.delay !== undefined) { - const delay = parseInt(this.options.delay) - config.delay = delay - } - - if (this.options.proxy !== undefined) { - const proxy = proxyParser.parse(this.options.proxy) - - if ( - proxy.protocol && - ['socks', 'socks5', 'socks5h', 'socks4', 'socks4a'].includes(String(proxy.protocol)) - ) { - const socksProxyAgent = new SocksProxyAgent(this.options.proxy) - - config.request = { - ...config.request, - ...{ httpAgent: socksProxyAgent, httpsAgent: socksProxyAgent } - } - } else { - config.request = { ...config.request, ...{ proxy } } - } - } - - if (this.options.curl === true) { - config.curl = true - } - - const _programs = await this.grabber.grab( - channel, - date, - config, - (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, ProxyParser } from './' +import { GrabOptions } from '../commands/epg/grab' +import { TaskQueue, PromisyClass } from 'cwait' +import { SocksProxyAgent } from 'socks-proxy-agent' + +interface GrabberProps { + logger: Logger + queue: Queue + options: GrabOptions +} + +export class Grabber { + logger: Logger + queue: Queue + options: GrabOptions + grabber: EPGGrabber | EPGGrabberMock + + constructor({ logger, queue, options }: GrabberProps) { + this.logger = logger + this.queue = queue + this.options = options + this.grabber = process.env.NODE_ENV === 'test' ? new EPGGrabberMock() : new EPGGrabber() + } + + async grab(): Promise<{ channels: Collection; programs: Collection }> { + const proxyParser = new ProxyParser() + 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 } } + } + + if (this.options.delay !== undefined) { + const delay = parseInt(this.options.delay) + config.delay = delay + } + + if (this.options.proxy !== undefined) { + const proxy = proxyParser.parse(this.options.proxy) + + if ( + proxy.protocol && + ['socks', 'socks5', 'socks5h', 'socks4', 'socks4a'].includes(String(proxy.protocol)) + ) { + const socksProxyAgent = new SocksProxyAgent(this.options.proxy) + + config.request = { + ...config.request, + ...{ httpAgent: socksProxyAgent, httpsAgent: socksProxyAgent } + } + } else { + config.request = { ...config.request, ...{ proxy } } + } + } + + if (this.options.curl === true) { + config.curl = true + } + + const _programs = await this.grabber.grab( + channel, + date, + config, + (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 } + } +} diff --git a/scripts/core/guideManager.ts b/scripts/core/guideManager.ts index aee2f666d..ea97d6314 100644 --- a/scripts/core/guideManager.ts +++ b/scripts/core/guideManager.ts @@ -1,111 +1,111 @@ -import { Collection, Logger, Zip, Storage, StringTemplate } from '@freearhey/core' -import epgGrabber from 'epg-grabber' -import { OptionValues } from 'commander' -import { Channel, Feed, Guide } from '../models' -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 = { - options: OptionValues - logger: Logger - channels: Collection - programs: Collection -} - -export class GuideManager { - options: OptionValues - logger: Logger - channels: Collection - programs: Collection - - constructor({ channels, programs, logger, options }: GuideManagerProps) { - this.options = options - this.logger = logger - this.channels = channels - this.programs = programs - } - - async createGuides() { - 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 - .map((channel: epgGrabber.Channel) => { - if (channel.xmltv_id && !channel.icon) { - 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 || '' }) - }) - - const groupedPrograms = this.programs - .orderBy([ - (program: epgGrabber.Program) => program.channel, - (program: epgGrabber.Program) => program.start - ]) - .groupBy((program: epgGrabber.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)) - }) - - 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) - } - } -} +import { Collection, Logger, Zip, Storage, StringTemplate } from '@freearhey/core' +import epgGrabber from 'epg-grabber' +import { OptionValues } from 'commander' +import { Channel, Feed, Guide } from '../models' +import path from 'path' +import { DataLoader, DataProcessor } from '.' +import { DataLoaderData } from '../types/dataLoader' +import { DataProcessorData } from '../types/dataProcessor' +import { DATA_DIR } from '../constants' + +interface GuideManagerProps { + options: OptionValues + logger: Logger + channels: Collection + programs: Collection +} + +export class GuideManager { + options: OptionValues + logger: Logger + channels: Collection + programs: Collection + + constructor({ channels, programs, logger, options }: GuideManagerProps) { + this.options = options + this.logger = logger + this.channels = channels + this.programs = programs + } + + async createGuides() { + 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 + .map((channel: epgGrabber.Channel) => { + if (channel.xmltv_id && !channel.icon) { + 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 || '' }) + }) + + const groupedPrograms = this.programs + .orderBy([ + (program: epgGrabber.Program) => program.channel, + (program: epgGrabber.Program) => program.start + ]) + .groupBy((program: epgGrabber.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)) + }) + + 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) + } + } +} diff --git a/scripts/core/htmlTable.ts b/scripts/core/htmlTable.ts index 144ba01be..6b917d6bb 100644 --- a/scripts/core/htmlTable.ts +++ b/scripts/core/htmlTable.ts @@ -1,55 +1,55 @@ -type Column = { - name: string - nowrap?: boolean - align?: string - colspan?: number -} - -type DataItem = { - value: string - nowrap?: boolean - align?: string - colspan?: number -}[] - -export class HTMLTable { - data: DataItem[] - columns: Column[] - - constructor(data: DataItem[], columns: Column[]) { - this.data = data - this.columns = columns - } - - toString() { - let output = '\r\n' - - output += ' \r\n ' - for (const column of this.columns) { - const nowrap = column.nowrap ? ' nowrap' : '' - const align = column.align ? ` align="${column.align}"` : '' - const colspan = column.colspan ? ` colspan="${column.colspan}"` : '' - - output += `${column.name}` - } - output += '\r\n \r\n' - - output += ' \r\n' - for (const row of this.data) { - output += ' ' - for (const item of row) { - const nowrap = item.nowrap ? ' nowrap' : '' - const align = item.align ? ` align="${item.align}"` : '' - const colspan = item.colspan ? ` colspan="${item.colspan}"` : '' - - output += `${item.value}` - } - output += '\r\n' - } - output += ' \r\n' - - output += '
    ' - - return output - } -} +interface Column { + name: string + nowrap?: boolean + align?: string + colspan?: number +} + +type DataItem = { + value: string + nowrap?: boolean + align?: string + colspan?: number +}[] + +export class HTMLTable { + data: DataItem[] + columns: Column[] + + constructor(data: DataItem[], columns: Column[]) { + this.data = data + this.columns = columns + } + + toString() { + let output = '\r\n' + + output += ' \r\n ' + for (const column of this.columns) { + const nowrap = column.nowrap ? ' nowrap' : '' + const align = column.align ? ` align="${column.align}"` : '' + const colspan = column.colspan ? ` colspan="${column.colspan}"` : '' + + output += `${column.name}` + } + output += '\r\n \r\n' + + output += ' \r\n' + for (const row of this.data) { + output += ' ' + for (const item of row) { + const nowrap = item.nowrap ? ' nowrap' : '' + const align = item.align ? ` align="${item.align}"` : '' + const colspan = item.colspan ? ` colspan="${item.colspan}"` : '' + + output += `${item.value}` + } + output += '\r\n' + } + output += ' \r\n' + + output += '
    ' + + return output + } +} diff --git a/scripts/core/job.ts b/scripts/core/job.ts index 9dbf7308a..db37ba159 100644 --- a/scripts/core/job.ts +++ b/scripts/core/job.ts @@ -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' + +interface 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() + } +} diff --git a/scripts/core/proxyParser.ts b/scripts/core/proxyParser.ts index 3e316ab2c..9cede1afc 100644 --- a/scripts/core/proxyParser.ts +++ b/scripts/core/proxyParser.ts @@ -1,31 +1,31 @@ -import { URL } from 'node:url' - -type ProxyParserResult = { - protocol: string | null - auth?: { - username?: string - password?: string - } - host: string - port: number | null -} - -export class ProxyParser { - parse(_url: string): ProxyParserResult { - const parsed = new URL(_url) - - const result: ProxyParserResult = { - protocol: parsed.protocol.replace(':', '') || null, - host: parsed.hostname, - port: parsed.port ? parseInt(parsed.port) : null - } - - if (parsed.username || parsed.password) { - result.auth = {} - if (parsed.username) result.auth.username = parsed.username - if (parsed.password) result.auth.password = parsed.password - } - - return result - } -} +import { URL } from 'node:url' + +interface ProxyParserResult { + protocol: string | null + auth?: { + username?: string + password?: string + } + host: string + port: number | null +} + +export class ProxyParser { + parse(_url: string): ProxyParserResult { + const parsed = new URL(_url) + + const result: ProxyParserResult = { + protocol: parsed.protocol.replace(':', '') || null, + host: parsed.hostname, + port: parsed.port ? parseInt(parsed.port) : null + } + + if (parsed.username || parsed.password) { + result.auth = {} + if (parsed.username) result.auth.username = parsed.username + if (parsed.password) result.auth.password = parsed.password + } + + return result + } +} diff --git a/scripts/core/queue.ts b/scripts/core/queue.ts index 06ef39616..106c111e4 100644 --- a/scripts/core/queue.ts +++ b/scripts/core/queue.ts @@ -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 interface 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 + } +} diff --git a/scripts/core/queueCreator.ts b/scripts/core/queueCreator.ts index 71213630d..56333dcb4 100644 --- a/scripts/core/queueCreator.ts +++ b/scripts/core/queueCreator.ts @@ -1,63 +1,63 @@ -import { Storage, Collection, DateTime, Logger } from '@freearhey/core' -import { SITES_DIR, DATA_DIR } from '../constants' -import { GrabOptions } from '../commands/epg/grab' -import { ConfigLoader, Queue } from './' -import { SiteConfig } from 'epg-grabber' -import path from 'path' - -type QueueCreatorProps = { - logger: Logger - options: GrabOptions - channels: Collection -} - -export class QueueCreator { - configLoader: ConfigLoader - logger: Logger - sitesStorage: Storage - dataStorage: Storage - channels: Collection - options: GrabOptions - - constructor({ channels, logger, options }: QueueCreatorProps) { - this.channels = channels - this.logger = logger - this.sitesStorage = new Storage() - this.dataStorage = new Storage(DATA_DIR) - this.options = options - this.configLoader = new ConfigLoader() - } - - async create(): Promise { - let index = 0 - const queue = new Queue() - for (const channel of this.channels.all()) { - channel.index = index++ - if (!channel.site || !channel.site_id || !channel.name) continue - - const configPath = path.resolve(SITES_DIR, `${channel.site}/${channel.site}.config.js`) - const config: SiteConfig = await this.configLoader.load(configPath) - - if (!channel.xmltv_id) { - channel.xmltv_id = channel.site_id - } - - const days = this.options.days || config.days || 1 - const currDate = new DateTime(process.env.CURR_DATE || new Date().toISOString()) - const dates = Array.from({ length: days }, (_, day) => currDate.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 { SITES_DIR, DATA_DIR } from '../constants' +import { GrabOptions } from '../commands/epg/grab' +import { ConfigLoader, Queue } from './' +import { SiteConfig } from 'epg-grabber' +import path from 'path' + +interface QueueCreatorProps { + logger: Logger + options: GrabOptions + channels: Collection +} + +export class QueueCreator { + configLoader: ConfigLoader + logger: Logger + sitesStorage: Storage + dataStorage: Storage + channels: Collection + options: GrabOptions + + constructor({ channels, logger, options }: QueueCreatorProps) { + this.channels = channels + this.logger = logger + this.sitesStorage = new Storage() + this.dataStorage = new Storage(DATA_DIR) + this.options = options + this.configLoader = new ConfigLoader() + } + + async create(): Promise { + let index = 0 + const queue = new Queue() + for (const channel of this.channels.all()) { + channel.index = index++ + if (!channel.site || !channel.site_id || !channel.name) continue + + const configPath = path.resolve(SITES_DIR, `${channel.site}/${channel.site}.config.js`) + const config: SiteConfig = await this.configLoader.load(configPath) + + if (!channel.xmltv_id) { + channel.xmltv_id = channel.site_id + } + + const days = this.options.days || config.days || 1 + const currDate = new DateTime(process.env.CURR_DATE || new Date().toISOString()) + const dates = Array.from({ length: days }, (_, day) => currDate.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 + } +} diff --git a/scripts/functions/functions.ts b/scripts/functions/functions.ts index f97b84f8b..f6e0bb630 100644 --- a/scripts/functions/functions.ts +++ b/scripts/functions/functions.ts @@ -1,77 +1,77 @@ -/** - * Sorts an array by the result of running each element through an iteratee function. - * Creates a shallow copy of the array before sorting to avoid mutating the original. - * - * @param {Array} arr - The array to sort - * @param {Function} fn - The iteratee function to compute sort values - * @returns {Array} A new sorted array - * - * @example - * const users = [{name: 'john', age: 30}, {name: 'jane', age: 25}]; - * sortBy(users, x => x.age); // [{name: 'jane', age: 25}, {name: 'john', age: 30}] - */ -export const sortBy = (arr: T[], fn: (item: T) => number | string): T[] => - [...arr].sort((a, b) => (fn(a) > fn(b) ? 1 : -1)) - -/** - * Sorts an array by multiple criteria with customizable sort orders. - * Supports ascending (default) and descending order for each criterion. - * - * @param {Array} arr - The array to sort - * @param {Array} fns - Array of iteratee functions to compute sort values - * @param {Array} orders - Array of sort orders ('asc' or 'desc'), defaults to all 'asc' - * @returns {Array} A new sorted array - * - * @example - * const users = [{name: 'john', age: 30}, {name: 'jane', age: 25}, {name: 'bob', age: 30}]; - * orderBy(users, [x => x.age, x => x.name], ['desc', 'asc']); - * // [{name: 'bob', age: 30}, {name: 'john', age: 30}, {name: 'jane', age: 25}] - */ -export const orderBy = ( - arr: Array, - fns: Array<(item: unknown) => string | number>, - orders: Array = [] -): Array => - [...arr].sort((a, b) => - fns.reduce( - (acc, fn, i) => - acc || - ((orders[i] === 'desc' ? fn(b) > fn(a) : fn(a) > fn(b)) ? 1 : fn(a) === fn(b) ? 0 : -1), - 0 - ) - ) - -/** - * Creates a duplicate-free version of an array using an iteratee function to generate - * the criterion by which uniqueness is computed. Only the first occurrence of each - * element is kept. - * - * @param {Array} arr - The array to inspect - * @param {Function} fn - The iteratee function to compute uniqueness criterion - * @returns {Array} A new duplicate-free array - * - * @example - * const users = [{id: 1, name: 'john'}, {id: 2, name: 'jane'}, {id: 1, name: 'john'}]; - * uniqBy(users, x => x.id); // [{id: 1, name: 'john'}, {id: 2, name: 'jane'}] - */ -export const uniqBy = (arr: T[], fn: (item: T) => unknown): T[] => - arr.filter((item, index) => arr.findIndex(x => fn(x) === fn(item)) === index) - -/** - * Converts a string to start case (capitalizes the first letter of each word). - * Handles camelCase, snake_case, kebab-case, and regular spaces. - * - * @param {string} str - The string to convert - * @returns {string} The start case string - * - * @example - * startCase('hello_world'); // "Hello World" - * startCase('helloWorld'); // "Hello World" - * startCase('hello-world'); // "Hello World" - * startCase('hello world'); // "Hello World" - */ -export const startCase = (str: string): string => - str - .replace(/([a-z])([A-Z])/g, '$1 $2') // Split camelCase - .replace(/[_-]/g, ' ') // Replace underscores and hyphens with spaces - .replace(/\b\w/g, c => c.toUpperCase()) // Capitalize first letter of each word +/** + * Sorts an array by the result of running each element through an iteratee function. + * Creates a shallow copy of the array before sorting to avoid mutating the original. + * + * @param {Array} arr - The array to sort + * @param {Function} fn - The iteratee function to compute sort values + * @returns {Array} A new sorted array + * + * @example + * const users = [{name: 'john', age: 30}, {name: 'jane', age: 25}]; + * sortBy(users, x => x.age); // [{name: 'jane', age: 25}, {name: 'john', age: 30}] + */ +export const sortBy = (arr: T[], fn: (item: T) => number | string): T[] => + [...arr].sort((a, b) => (fn(a) > fn(b) ? 1 : -1)) + +/** + * Sorts an array by multiple criteria with customizable sort orders. + * Supports ascending (default) and descending order for each criterion. + * + * @param {Array} arr - The array to sort + * @param {Array} fns - Array of iteratee functions to compute sort values + * @param {Array} orders - Array of sort orders ('asc' or 'desc'), defaults to all 'asc' + * @returns {Array} A new sorted array + * + * @example + * const users = [{name: 'john', age: 30}, {name: 'jane', age: 25}, {name: 'bob', age: 30}]; + * orderBy(users, [x => x.age, x => x.name], ['desc', 'asc']); + * // [{name: 'bob', age: 30}, {name: 'john', age: 30}, {name: 'jane', age: 25}] + */ +export const orderBy = ( + arr: unknown[], + fns: ((item: unknown) => string | number)[], + orders: string[] = [] +): unknown[] => + [...arr].sort((a, b) => + fns.reduce( + (acc, fn, i) => + acc || + ((orders[i] === 'desc' ? fn(b) > fn(a) : fn(a) > fn(b)) ? 1 : fn(a) === fn(b) ? 0 : -1), + 0 + ) + ) + +/** + * Creates a duplicate-free version of an array using an iteratee function to generate + * the criterion by which uniqueness is computed. Only the first occurrence of each + * element is kept. + * + * @param {Array} arr - The array to inspect + * @param {Function} fn - The iteratee function to compute uniqueness criterion + * @returns {Array} A new duplicate-free array + * + * @example + * const users = [{id: 1, name: 'john'}, {id: 2, name: 'jane'}, {id: 1, name: 'john'}]; + * uniqBy(users, x => x.id); // [{id: 1, name: 'john'}, {id: 2, name: 'jane'}] + */ +export const uniqBy = (arr: T[], fn: (item: T) => unknown): T[] => + arr.filter((item, index) => arr.findIndex(x => fn(x) === fn(item)) === index) + +/** + * Converts a string to start case (capitalizes the first letter of each word). + * Handles camelCase, snake_case, kebab-case, and regular spaces. + * + * @param {string} str - The string to convert + * @returns {string} The start case string + * + * @example + * startCase('hello_world'); // "Hello World" + * startCase('helloWorld'); // "Hello World" + * startCase('hello-world'); // "Hello World" + * startCase('hello world'); // "Hello World" + */ +export const startCase = (str: string): string => + str + .replace(/([a-z])([A-Z])/g, '$1 $2') // Split camelCase + .replace(/[_-]/g, ' ') // Replace underscores and hyphens with spaces + .replace(/\b\w/g, c => c.toUpperCase()) // Capitalize first letter of each word diff --git a/scripts/models/channel.ts b/scripts/models/channel.ts index fb62a3fc1..e5bbde5ef 100644 --- a/scripts/models/channel.ts +++ b/scripts/models/channel.ts @@ -1,164 +1,164 @@ -import { ChannelData, ChannelSearchableData } from '../types/channel' -import { Collection, Dictionary } from '@freearhey/core' -import { Stream, Feed, Logo, GuideChannel } from './' - -export class Channel { - id?: string - name?: string - altNames?: Collection - network?: string - owners?: Collection - countryCode?: string - subdivisionCode?: string - cityName?: string - categoryIds?: Collection - isNSFW: boolean = false - launched?: string - closed?: string - replacedBy?: string - website?: string - feeds?: Collection - logos: Collection = new Collection() - - constructor(data?: ChannelData) { - if (!data) return - - this.id = data.id - this.name = data.name - this.altNames = new Collection(data.alt_names) - this.network = data.network || undefined - this.owners = new Collection(data.owners) - this.countryCode = data.country - this.subdivisionCode = data.subdivision || undefined - this.cityName = data.city || undefined - this.categoryIds = new Collection(data.categories) - this.isNSFW = data.is_nsfw - this.launched = data.launched || undefined - this.closed = data.closed || undefined - this.replacedBy = data.replaced_by || undefined - this.website = data.website || undefined - } - - withFeeds(feedsGroupedByChannelId: Dictionary): this { - 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 - } - - getFeeds(): Collection { - if (!this.feeds) return new Collection() - - return this.feeds - } - - getGuideChannels(): Collection { - let channels = new Collection() - - this.getFeeds().forEach((feed: Feed) => { - channels = channels.concat(feed.getGuideChannels()) - }) - - return channels - } - - getGuideChannelNames(): Collection { - return this.getGuideChannels() - .map((channel: GuideChannel) => channel.siteName) - .uniq() - } - - getStreams(): Collection { - let streams = new Collection() - - this.getFeeds().forEach((feed: Feed) => { - streams = streams.concat(feed.getStreams()) - }) - - return streams - } - - getStreamNames(): Collection { - return this.getStreams() - .map((stream: Stream) => stream.getName()) - .uniq() - } - - getFeedFullNames(): Collection { - return this.getFeeds() - .map((feed: Feed) => feed.getFullName()) - .uniq() - } - - getName(): string { - return this.name || '' - } - - getId(): string { - return this.id || '' - } - - getAltNames(): 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 { - return { - id: this.getId(), - name: this.getName(), - altNames: this.getAltNames().all(), - guideNames: this.getGuideChannelNames().all(), - streamNames: this.getStreamNames().all(), - feedFullNames: this.getFeedFullNames().all() - } - } -} +import { ChannelData, ChannelSearchableData } from '../types/channel' +import { Collection, Dictionary } from '@freearhey/core' +import { Stream, Feed, Logo, GuideChannel } from './' + +export class Channel { + id?: string + name?: string + altNames?: Collection + network?: string + owners?: Collection + countryCode?: string + subdivisionCode?: string + cityName?: string + categoryIds?: Collection + isNSFW = false + launched?: string + closed?: string + replacedBy?: string + website?: string + feeds?: Collection + logos: Collection = new Collection() + + constructor(data?: ChannelData) { + if (!data) return + + this.id = data.id + this.name = data.name + this.altNames = new Collection(data.alt_names) + this.network = data.network || undefined + this.owners = new Collection(data.owners) + this.countryCode = data.country + this.subdivisionCode = data.subdivision || undefined + this.cityName = data.city || undefined + this.categoryIds = new Collection(data.categories) + this.isNSFW = data.is_nsfw + this.launched = data.launched || undefined + this.closed = data.closed || undefined + this.replacedBy = data.replaced_by || undefined + this.website = data.website || undefined + } + + withFeeds(feedsGroupedByChannelId: Dictionary): this { + 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 + } + + getFeeds(): Collection { + if (!this.feeds) return new Collection() + + return this.feeds + } + + getGuideChannels(): Collection { + let channels = new Collection() + + this.getFeeds().forEach((feed: Feed) => { + channels = channels.concat(feed.getGuideChannels()) + }) + + return channels + } + + getGuideChannelNames(): Collection { + return this.getGuideChannels() + .map((channel: GuideChannel) => channel.siteName) + .uniq() + } + + getStreams(): Collection { + let streams = new Collection() + + this.getFeeds().forEach((feed: Feed) => { + streams = streams.concat(feed.getStreams()) + }) + + return streams + } + + getStreamNames(): Collection { + return this.getStreams() + .map((stream: Stream) => stream.getName()) + .uniq() + } + + getFeedFullNames(): Collection { + return this.getFeeds() + .map((feed: Feed) => feed.getFullName()) + .uniq() + } + + getName(): string { + return this.name || '' + } + + getId(): string { + return this.id || '' + } + + getAltNames(): 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: Record = { + 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 { + return { + id: this.getId(), + name: this.getName(), + altNames: this.getAltNames().all(), + guideNames: this.getGuideChannelNames().all(), + streamNames: this.getStreamNames().all(), + feedFullNames: this.getFeedFullNames().all() + } + } +} diff --git a/scripts/models/channelList.ts b/scripts/models/channelList.ts index d312e71cd..951e0a90f 100644 --- a/scripts/models/channelList.ts +++ b/scripts/models/channelList.ts @@ -1,77 +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(/\n|\r/g, ' ') - .replace(/ +/g, ' ') - .trim() - } - - let output = '\r\n\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 += ` ${displayName}\r\n` - }) - - output += '\r\n' - - return output - } -} +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 = '') { + 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(/\n|\r/g, ' ') + .replace(/ +/g, ' ') + .trim() + } + + let output = '\r\n\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 += ` ${displayName}\r\n` + }) + + output += '\r\n' + + return output + } +} diff --git a/scripts/models/feed.ts b/scripts/models/feed.ts index bb2c7020a..7e91e305d 100644 --- a/scripts/models/feed.ts +++ b/scripts/models/feed.ts @@ -1,124 +1,124 @@ -import { Collection, Dictionary } from '@freearhey/core' -import { FeedData } from '../types/feed' -import { Logo, Channel } from '.' - -export class Feed { - channelId: string - channel?: Channel - id: string - name: string - isMain: boolean - broadcastAreaCodes: Collection - languageCodes: Collection - timezoneIds: Collection - videoFormat: string - guideChannels?: Collection - streams?: Collection - logos: Collection = new Collection() - - constructor(data: FeedData) { - this.channelId = data.channel - this.id = data.id - this.name = data.name - this.isMain = data.is_main - this.broadcastAreaCodes = new Collection(data.broadcast_area) - this.languageCodes = new Collection(data.languages) - this.timezoneIds = new Collection(data.timezones) - this.videoFormat = data.video_format - } - - withChannel(channelsKeyById: Dictionary): this { - this.channel = channelsKeyById.get(this.channelId) - - return this - } - - withStreams(streamsGroupedById: Dictionary): this { - this.streams = new Collection(streamsGroupedById.get(`${this.channelId}@${this.id}`)) - - if (this.isMain) { - this.streams = this.streams.concat(new Collection(streamsGroupedById.get(this.channelId))) - } - - return this - } - - withGuideChannels(guideChannelsGroupedByStreamId: Dictionary): this { - this.guideChannels = new Collection( - guideChannelsGroupedByStreamId.get(`${this.channelId}@${this.id}`) - ) - - if (this.isMain) { - this.guideChannels = this.guideChannels.concat( - new Collection(guideChannelsGroupedByStreamId.get(this.channelId)) - ) - } - - return this - } - - withLogos(logosGroupedByStreamId: Dictionary): this { - this.logos = new Collection(logosGroupedByStreamId.get(this.getStreamId())) - - return this - } - - getGuideChannels(): Collection { - if (!this.guideChannels) return new Collection() - - return this.guideChannels - } - - getStreams(): Collection { - if (!this.streams) return new Collection() - - return this.streams - } - - getFullName(): string { - if (!this.channel) return '' - - return `${this.channel.name} ${this.name}` - } - - getStreamId(): string { - 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 || '' - } -} +import { Collection, Dictionary } from '@freearhey/core' +import { FeedData } from '../types/feed' +import { Logo, Channel } from '.' + +export class Feed { + channelId: string + channel?: Channel + id: string + name: string + isMain: boolean + broadcastAreaCodes: Collection + languageCodes: Collection + timezoneIds: Collection + videoFormat: string + guideChannels?: Collection + streams?: Collection + logos: Collection = new Collection() + + constructor(data: FeedData) { + this.channelId = data.channel + this.id = data.id + this.name = data.name + this.isMain = data.is_main + this.broadcastAreaCodes = new Collection(data.broadcast_area) + this.languageCodes = new Collection(data.languages) + this.timezoneIds = new Collection(data.timezones) + this.videoFormat = data.video_format + } + + withChannel(channelsKeyById: Dictionary): this { + this.channel = channelsKeyById.get(this.channelId) + + return this + } + + withStreams(streamsGroupedById: Dictionary): this { + this.streams = new Collection(streamsGroupedById.get(`${this.channelId}@${this.id}`)) + + if (this.isMain) { + this.streams = this.streams.concat(new Collection(streamsGroupedById.get(this.channelId))) + } + + return this + } + + withGuideChannels(guideChannelsGroupedByStreamId: Dictionary): this { + this.guideChannels = new Collection( + guideChannelsGroupedByStreamId.get(`${this.channelId}@${this.id}`) + ) + + if (this.isMain) { + this.guideChannels = this.guideChannels.concat( + new Collection(guideChannelsGroupedByStreamId.get(this.channelId)) + ) + } + + return this + } + + withLogos(logosGroupedByStreamId: Dictionary): this { + this.logos = new Collection(logosGroupedByStreamId.get(this.getStreamId())) + + return this + } + + getGuideChannels(): Collection { + if (!this.guideChannels) return new Collection() + + return this.guideChannels + } + + getStreams(): Collection { + if (!this.streams) return new Collection() + + return this.streams + } + + getFullName(): string { + if (!this.channel) return '' + + return `${this.channel.name} ${this.name}` + } + + getStreamId(): string { + return `${this.channelId}@${this.id}` + } + + getLogos(): Collection { + function format(logo: Logo): number { + const levelByFormat: Record = { + 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 || '' + } +} diff --git a/scripts/models/guide.ts b/scripts/models/guide.ts index b60267436..4072c8bc4 100644 --- a/scripts/models/guide.ts +++ b/scripts/models/guide.ts @@ -1,35 +1,35 @@ -import { Collection, DateTime } from '@freearhey/core' -import { generateXMLTV } from 'epg-grabber' - -type GuideData = { - channels: Collection - programs: Collection - filepath: string - gzip: boolean -} - -export class Guide { - channels: Collection - programs: Collection - filepath: string - gzip: boolean - - constructor({ channels, programs, filepath, gzip }: GuideData) { - this.channels = channels - this.programs = programs - this.filepath = filepath - this.gzip = gzip || false - } - - toString() { - const currDate = new DateTime(process.env.CURR_DATE || new Date().toISOString(), { - timezone: 'UTC' - }) - - return generateXMLTV({ - channels: this.channels.all(), - programs: this.programs.all(), - date: currDate.toJSON() - }) - } -} +import { Collection, DateTime } from '@freearhey/core' +import { generateXMLTV } from 'epg-grabber' + +interface GuideData { + channels: Collection + programs: Collection + filepath: string + gzip: boolean +} + +export class Guide { + channels: Collection + programs: Collection + filepath: string + gzip: boolean + + constructor({ channels, programs, filepath, gzip }: GuideData) { + this.channels = channels + this.programs = programs + this.filepath = filepath + this.gzip = gzip || false + } + + toString() { + const currDate = new DateTime(process.env.CURR_DATE || new Date().toISOString(), { + timezone: 'UTC' + }) + + return generateXMLTV({ + channels: this.channels.all(), + programs: this.programs.all(), + date: currDate.toJSON() + }) + } +} diff --git a/scripts/models/issue.ts b/scripts/models/issue.ts index d9653a4f5..a26e71ff7 100644 --- a/scripts/models/issue.ts +++ b/scripts/models/issue.ts @@ -1,24 +1,24 @@ -import { Dictionary } from '@freearhey/core' -import { OWNER, REPO } from '../constants' - -type IssueProps = { - number: number - labels: string[] - data: Dictionary -} - -export class Issue { - number: number - labels: string[] - data: Dictionary - - constructor({ number, labels, data }: IssueProps) { - this.number = number - this.labels = labels - this.data = data - } - - getURL() { - return `https://github.com/${OWNER}/${REPO}/issues/${this.number}` - } -} +import { Dictionary } from '@freearhey/core' +import { OWNER, REPO } from '../constants' + +interface IssueProps { + number: number + labels: string[] + data: Dictionary +} + +export class Issue { + number: number + labels: string[] + data: Dictionary + + constructor({ number, labels, data }: IssueProps) { + this.number = number + this.labels = labels + this.data = data + } + + getURL() { + return `https://github.com/${OWNER}/${REPO}/issues/${this.number}` + } +} diff --git a/scripts/models/logo.ts b/scripts/models/logo.ts index d864a3fb1..e08f443ed 100644 --- a/scripts/models/logo.ts +++ b/scripts/models/logo.ts @@ -1,41 +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}` - } -} +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 = 0 + height = 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}` + } +} diff --git a/scripts/models/site.ts b/scripts/models/site.ts index fa95165fc..d4ddfdaa9 100644 --- a/scripts/models/site.ts +++ b/scripts/models/site.ts @@ -1,63 +1,63 @@ -import { Collection } from '@freearhey/core' -import { Issue } from './' - -enum StatusCode { - DOWN = 'down', - WARNING = 'warning', - OK = 'ok' -} - -type Status = { - code: StatusCode - emoji: string -} - -type SiteProps = { - domain: string - totalChannels?: number - markedChannels?: number - issues: Collection -} - -export class Site { - domain: string - totalChannels: number - markedChannels: number - issues: Collection - - constructor({ domain, totalChannels = 0, markedChannels = 0, issues }: SiteProps) { - this.domain = domain - this.totalChannels = totalChannels - this.markedChannels = markedChannels - this.issues = issues - } - - getStatus(): Status { - const issuesWithStatusDown = this.issues.filter((issue: Issue) => - issue.labels.find(label => label === 'status:down') - ) - if (issuesWithStatusDown.notEmpty()) - return { - code: StatusCode.DOWN, - emoji: '🔴' - } - - const issuesWithStatusWarning = this.issues.filter((issue: Issue) => - issue.labels.find(label => label === 'status:warning') - ) - if (issuesWithStatusWarning.notEmpty()) - return { - code: StatusCode.WARNING, - emoji: '🟡' - } - - return { - code: StatusCode.OK, - emoji: '🟢' - } - } - - getIssues(): Collection { - return this.issues.map((issue: Issue) => issue.getURL()) - } -} +import { Collection } from '@freearhey/core' +import { Issue } from './' + +enum StatusCode { + DOWN = 'down', + WARNING = 'warning', + OK = 'ok' +} + +interface Status { + code: StatusCode + emoji: string +} + +interface SiteProps { + domain: string + totalChannels?: number + markedChannels?: number + issues: Collection +} + +export class Site { + domain: string + totalChannels: number + markedChannels: number + issues: Collection + + constructor({ domain, totalChannels = 0, markedChannels = 0, issues }: SiteProps) { + this.domain = domain + this.totalChannels = totalChannels + this.markedChannels = markedChannels + this.issues = issues + } + + getStatus(): Status { + const issuesWithStatusDown = this.issues.filter((issue: Issue) => + issue.labels.find(label => label === 'status:down') + ) + if (issuesWithStatusDown.notEmpty()) + return { + code: StatusCode.DOWN, + emoji: '🔴' + } + + const issuesWithStatusWarning = this.issues.filter((issue: Issue) => + issue.labels.find(label => label === 'status:warning') + ) + if (issuesWithStatusWarning.notEmpty()) + return { + code: StatusCode.WARNING, + emoji: '🟡' + } + + return { + code: StatusCode.OK, + emoji: '🟢' + } + } + + getIssues(): Collection { + return this.issues.map((issue: Issue) => issue.getURL()) + } +} diff --git a/scripts/models/stream.ts b/scripts/models/stream.ts index 6ac1636b0..c519bdfbc 100644 --- a/scripts/models/stream.ts +++ b/scripts/models/stream.ts @@ -1,58 +1,58 @@ -import type { StreamData } from '../types/stream' -import { Feed, Channel } from './index' - -export class Stream { - name?: string - url: string - id?: string - channelId?: string - channel?: Channel - feedId?: string - feed?: Feed - filepath?: string - line?: number - label?: string - verticalResolution?: number - isInterlaced?: boolean - referrer?: string - userAgent?: string - groupTitle: string = 'Undefined' - removed: boolean = false - - constructor(data: StreamData) { - const id = data.channel && data.feed ? [data.channel, data.feed].join('@') : data.channel - const { verticalResolution, isInterlaced } = parseQuality(data.quality) - - this.id = id || undefined - this.channelId = data.channel || undefined - this.feedId = data.feed || undefined - this.name = data.name || undefined - this.url = data.url - this.referrer = data.referrer || undefined - this.userAgent = data.user_agent || undefined - this.verticalResolution = verticalResolution || undefined - this.isInterlaced = isInterlaced || undefined - this.label = data.label || undefined - } - - getId(): string { - return this.id || '' - } - - getName(): string { - return this.name || '' - } -} - -function parseQuality(quality: string | null): { - verticalResolution: number | null - isInterlaced: boolean | null -} { - if (!quality) return { verticalResolution: null, isInterlaced: null } - const [, verticalResolutionString] = quality.match(/^(\d+)/) || [null, undefined] - const isInterlaced = /i$/i.test(quality) - let verticalResolution = 0 - if (verticalResolutionString) verticalResolution = parseInt(verticalResolutionString) - - return { verticalResolution, isInterlaced } -} +import type { StreamData } from '../types/stream' +import { Feed, Channel } from './index' + +export class Stream { + name?: string + url: string + id?: string + channelId?: string + channel?: Channel + feedId?: string + feed?: Feed + filepath?: string + line?: number + label?: string + verticalResolution?: number + isInterlaced?: boolean + referrer?: string + userAgent?: string + groupTitle = 'Undefined' + removed = false + + constructor(data: StreamData) { + const id = data.channel && data.feed ? [data.channel, data.feed].join('@') : data.channel + const { verticalResolution, isInterlaced } = parseQuality(data.quality) + + this.id = id || undefined + this.channelId = data.channel || undefined + this.feedId = data.feed || undefined + this.name = data.name || undefined + this.url = data.url + this.referrer = data.referrer || undefined + this.userAgent = data.user_agent || undefined + this.verticalResolution = verticalResolution || undefined + this.isInterlaced = isInterlaced || undefined + this.label = data.label || undefined + } + + getId(): string { + return this.id || '' + } + + getName(): string { + return this.name || '' + } +} + +function parseQuality(quality: string | null): { + verticalResolution: number | null + isInterlaced: boolean | null +} { + if (!quality) return { verticalResolution: null, isInterlaced: null } + const [, verticalResolutionString] = quality.match(/^(\d+)/) || [null, undefined] + const isInterlaced = /i$/i.test(quality) + let verticalResolution = 0 + if (verticalResolutionString) verticalResolution = parseInt(verticalResolutionString) + + return { verticalResolution, isInterlaced } +} diff --git a/scripts/types/channel.d.ts b/scripts/types/channel.d.ts index b1d2237c9..b2c709e1a 100644 --- a/scripts/types/channel.d.ts +++ b/scripts/types/channel.d.ts @@ -1,27 +1,27 @@ -import { Collection } from '@freearhey/core' - -export type ChannelData = { - id: string - name: string - alt_names: string[] - network: string - owners: Collection - country: string - subdivision: string - city: string - categories: Collection - is_nsfw: boolean - launched: string - closed: string - replaced_by: string - website: string -} - -export type ChannelSearchableData = { - id: string - name: string - altNames: string[] - guideNames: string[] - streamNames: string[] - feedFullNames: string[] -} +import { Collection } from '@freearhey/core' + +export interface ChannelData { + id: string + name: string + alt_names: string[] + network: string + owners: Collection + country: string + subdivision: string + city: string + categories: Collection + is_nsfw: boolean + launched: string + closed: string + replaced_by: string + website: string +} + +export interface ChannelSearchableData { + id: string + name: string + altNames: string[] + guideNames: string[] + streamNames: string[] + feedFullNames: string[] +} diff --git a/scripts/types/dataLoader.d.ts b/scripts/types/dataLoader.d.ts index 135340e93..98d3d911e 100644 --- a/scripts/types/dataLoader.d.ts +++ b/scripts/types/dataLoader.d.ts @@ -1,20 +1,20 @@ -import { Storage } from '@freearhey/core' - -export type DataLoaderProps = { - storage: Storage -} - -export type DataLoaderData = { - countries: object | object[] - regions: object | object[] - subdivisions: object | object[] - languages: object | object[] - categories: object | object[] - blocklist: object | object[] - channels: object | object[] - feeds: object | object[] - timezones: object | object[] - guides: object | object[] - streams: object | object[] - logos: object | object[] -} +import { Storage } from '@freearhey/core' + +export interface DataLoaderProps { + storage: Storage +} + +export interface DataLoaderData { + countries: object | object[] + regions: object | object[] + subdivisions: object | object[] + languages: object | object[] + categories: object | object[] + blocklist: object | object[] + channels: object | object[] + feeds: object | object[] + timezones: object | object[] + guides: object | object[] + streams: object | object[] + logos: object | object[] +} diff --git a/scripts/types/dataProcessor.d.ts b/scripts/types/dataProcessor.d.ts index f158f16e4..e50915d1e 100644 --- a/scripts/types/dataProcessor.d.ts +++ b/scripts/types/dataProcessor.d.ts @@ -1,16 +1,16 @@ -import { Collection, Dictionary } from '@freearhey/core' - -export type DataProcessorData = { - guideChannelsGroupedByStreamId: Dictionary - feedsGroupedByChannelId: Dictionary - logosGroupedByChannelId: Dictionary - logosGroupedByStreamId: Dictionary - feedsKeyByStreamId: Dictionary - streamsGroupedById: Dictionary - channelsKeyById: Dictionary - guideChannels: Collection - channels: Collection - streams: Collection - feeds: Collection - logos: Collection -} +import { Collection, Dictionary } from '@freearhey/core' + +export interface DataProcessorData { + guideChannelsGroupedByStreamId: Dictionary + feedsGroupedByChannelId: Dictionary + logosGroupedByChannelId: Dictionary + logosGroupedByStreamId: Dictionary + feedsKeyByStreamId: Dictionary + streamsGroupedById: Dictionary + channelsKeyById: Dictionary + guideChannels: Collection + channels: Collection + streams: Collection + feeds: Collection + logos: Collection +} diff --git a/scripts/types/feed.d.ts b/scripts/types/feed.d.ts index 00663a1b4..00c492606 100644 --- a/scripts/types/feed.d.ts +++ b/scripts/types/feed.d.ts @@ -1,12 +1,12 @@ -import { Collection } from '@freearhey/core' - -export type FeedData = { - channel: string - id: string - name: string - is_main: boolean - broadcast_area: Collection - languages: Collection - timezones: Collection - video_format: string -} +import { Collection } from '@freearhey/core' + +export interface FeedData { + channel: string + id: string + name: string + is_main: boolean + broadcast_area: Collection + languages: Collection + timezones: Collection + video_format: string +} diff --git a/scripts/types/guide.d.ts b/scripts/types/guide.d.ts index 61ff62339..9fbe4da37 100644 --- a/scripts/types/guide.d.ts +++ b/scripts/types/guide.d.ts @@ -1,8 +1,8 @@ -export type GuideData = { - channel: string - feed: string - site: string - site_id: string - site_name: string - lang: string -} +export interface GuideData { + channel: string + feed: string + site: string + site_id: string + site_name: string + lang: string +} diff --git a/scripts/types/logo.d.ts b/scripts/types/logo.d.ts index c77f4799b..d8c54b04a 100644 --- a/scripts/types/logo.d.ts +++ b/scripts/types/logo.d.ts @@ -1,9 +1,9 @@ -export type LogoData = { - channel: string - feed: string | null - tags: string[] - width: number - height: number - format: string | null - url: string -} +export interface LogoData { + channel: string + feed: string | null + tags: string[] + width: number + height: number + format: string | null + url: string +} diff --git a/scripts/types/stream.d.ts b/scripts/types/stream.d.ts index adae13cf8..c3365889c 100644 --- a/scripts/types/stream.d.ts +++ b/scripts/types/stream.d.ts @@ -1,10 +1,10 @@ -export type StreamData = { - channel: string | null - feed: string | null - name?: string - url: string - referrer: string | null - user_agent: string | null - quality: string | null - label: string | null -} +export interface StreamData { + channel: string | null + feed: string | null + name?: string + url: string + referrer: string | null + user_agent: string | null + quality: string | null + label: string | null +} diff --git a/sites/tvim.tv/tvim.tv.test.js b/sites/tvim.tv/tvim.tv.test.js index b7c221cdc..b819b777a 100644 --- a/sites/tvim.tv/tvim.tv.test.js +++ b/sites/tvim.tv/tvim.tv.test.js @@ -1,41 +1,41 @@ -const { parser, url } = require('./tvim.tv.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2021-10-24', 'YYYY-MM-DD').startOf('d') -const channel = { site_id: 'T7', xmltv_id: 'T7.rs' } -const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - -it('can generate valid url', () => { - const result = url({ date, channel }) - expect(result).toBe( - 'https://www.tvim.tv/script/program_epg?date=24.10.2021&prog=T7&server_time=true' - ) -}) - -it('can parse response', () => { - const result = parser({ date, channel, content }) - expect(result).toMatchObject([ - { - start: 'Sat, 23 Oct 2021 22:00:00 GMT', - stop: 'Sun, 24 Oct 2021 02:00:00 GMT', - title: 'Programi i T7', - description: 'Programi i T7', - category: 'test' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./tvim.tv.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2021-10-24', 'YYYY-MM-DD').startOf('d') +const channel = { site_id: 'T7', xmltv_id: 'T7.rs' } +const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + +it('can generate valid url', () => { + const result = url({ date, channel }) + expect(result).toBe( + 'https://www.tvim.tv/script/program_epg?date=24.10.2021&prog=T7&server_time=true' + ) +}) + +it('can parse response', () => { + const result = parser({ date, channel, content }) + expect(result).toMatchObject([ + { + start: 'Sat, 23 Oct 2021 22:00:00 GMT', + stop: 'Sun, 24 Oct 2021 02:00:00 GMT', + title: 'Programi i T7', + description: 'Programi i T7', + category: 'test' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/tvinsider.com/tvinsider.com.config.js b/sites/tvinsider.com/tvinsider.com.config.js index 2ac33b164..9e43cb049 100644 --- a/sites/tvinsider.com/tvinsider.com.config.js +++ b/sites/tvinsider.com/tvinsider.com.config.js @@ -1,127 +1,127 @@ -const cheerio = require('cheerio') -const axios = require('axios') -const { DateTime } = require('luxon') - -module.exports = { - site: 'tvinsider.com', - days: 2, - url({ channel }) { - return `https://www.tvinsider.com/network/${channel.site_id}/schedule/` - }, - parser({ content, date }) { - const programs = [] - const items = parseItems(content, date) - items.forEach(item => { - const prev = programs[programs.length - 1] - const $item = cheerio.load(item) - const episodeInfo = parseEP($item) - let start = parseStart($item, date) - if (!start) return - if (prev) { - prev.stop = start - } - const stop = start.plus({ minute: 30 }) - - programs.push({ - title: parseTitle($item), - description: parseDescription($item), - category: parseCategory($item), - date: parseDate($item), - ...episodeInfo, - subTitles: parseSubtitle($item), - previouslyShown: parsePreviously($item), - start, - stop - }) - }) - - return programs - }, - async channels() { - const html = await axios - .get('https://www.tvinsider.com/network/5-star-max/') - .then(r => r.data) - .catch(console.log) - const $ = cheerio.load(html) - const items = $('body > main > section > select > option').toArray() - - const channels = [] - items.forEach(item => { - const name = $(item).text().trim() - const path = $(item).attr('value') - if (!path) return - const [, , site_id] = path.split('/') || [null, null, null] - if (!site_id) return - - channels.push({ - lang: 'en', - site_id, - name - }) - }) - - return channels - } -} - -function parseTitle($item) { - return $item('h3').text().trim() -} -function parseEP($item){ - const text = $item('h6').text().trim() - const match = text.match(/Season\s+(\d+)\s*•\s*Episode\s+(\d+)/i) - - if (!match) return {} // Return an empty object if no match, so properties are undefined later - - const season = parseInt(match[1], 10) - const episode = parseInt(match[2], 10) - - return { season, episode } // Return an object with season and episode -} - -function parseSubtitle($item) { - return $item('h5').text().trim() -} - -function parsePreviously($item){ - const h3Text = $item('h3').text().trim() - const isNewShow = /New$/.test(h3Text) - - if (isNewShow) { - return null - } else { - return {} - } -} - -function parseDescription($item) { - return $item('p').text().trim() -} - -function parseCategory($item) { - const [category] = $item('h4').text().trim().split(' • ') - - return category -} - -function parseDate($item) { - const [, date] = $item('h4').text().trim().split(' • ') - - return date -} - -function parseStart($item, date) { - let time = $item('time').text().trim() - time = `${date.format('YYYY-MM-DD')} ${time}` - - return DateTime.fromFormat(time, 'yyyy-MM-dd t', { zone: 'America/New_York' }).toUTC() -} - -function parseItems(content, date) { - const $ = cheerio.load(content) - - return $(`#${date.format('MM-DD-YYYY')}`) - .next() - .find('a') - .toArray() -} +const cheerio = require('cheerio') +const axios = require('axios') +const { DateTime } = require('luxon') + +module.exports = { + site: 'tvinsider.com', + days: 2, + url({ channel }) { + return `https://www.tvinsider.com/network/${channel.site_id}/schedule/` + }, + parser({ content, date }) { + const programs = [] + const items = parseItems(content, date) + items.forEach(item => { + const prev = programs[programs.length - 1] + const $item = cheerio.load(item) + const episodeInfo = parseEP($item) + let start = parseStart($item, date) + if (!start) return + if (prev) { + prev.stop = start + } + const stop = start.plus({ minute: 30 }) + + programs.push({ + title: parseTitle($item), + description: parseDescription($item), + category: parseCategory($item), + date: parseDate($item), + ...episodeInfo, + subTitles: parseSubtitle($item), + previouslyShown: parsePreviously($item), + start, + stop + }) + }) + + return programs + }, + async channels() { + const html = await axios + .get('https://www.tvinsider.com/network/5-star-max/') + .then(r => r.data) + .catch(console.log) + const $ = cheerio.load(html) + const items = $('body > main > section > select > option').toArray() + + const channels = [] + items.forEach(item => { + const name = $(item).text().trim() + const path = $(item).attr('value') + if (!path) return + const [, , site_id] = path.split('/') || [null, null, null] + if (!site_id) return + + channels.push({ + lang: 'en', + site_id, + name + }) + }) + + return channels + } +} + +function parseTitle($item) { + return $item('h3').text().trim() +} +function parseEP($item){ + const text = $item('h6').text().trim() + const match = text.match(/Season\s+(\d+)\s*•\s*Episode\s+(\d+)/i) + + if (!match) return {} // Return an empty object if no match, so properties are undefined later + + const season = parseInt(match[1], 10) + const episode = parseInt(match[2], 10) + + return { season, episode } // Return an object with season and episode +} + +function parseSubtitle($item) { + return $item('h5').text().trim() +} + +function parsePreviously($item){ + const h3Text = $item('h3').text().trim() + const isNewShow = /New$/.test(h3Text) + + if (isNewShow) { + return null + } else { + return {} + } +} + +function parseDescription($item) { + return $item('p').text().trim() +} + +function parseCategory($item) { + const [category] = $item('h4').text().trim().split(' • ') + + return category +} + +function parseDate($item) { + const [, date] = $item('h4').text().trim().split(' • ') + + return date +} + +function parseStart($item, date) { + let time = $item('time').text().trim() + time = `${date.format('YYYY-MM-DD')} ${time}` + + return DateTime.fromFormat(time, 'yyyy-MM-dd t', { zone: 'America/New_York' }).toUTC() +} + +function parseItems(content, date) { + const $ = cheerio.load(content) + + return $(`#${date.format('MM-DD-YYYY')}`) + .next() + .find('a') + .toArray() +} diff --git a/sites/tvireland.ie/tvireland.ie.config.js b/sites/tvireland.ie/tvireland.ie.config.js index be43fa9f9..0ea0e3493 100644 --- a/sites/tvireland.ie/tvireland.ie.config.js +++ b/sites/tvireland.ie/tvireland.ie.config.js @@ -1,99 +1,99 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') -const { uniqBy } = require('../../scripts/functions') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'tvireland.ie', - days: 2, - url: function ({ date, channel }) { - return `https://www.tvireland.ie/tv/listings/channel/${channel.site_id}?dt=${date.format( - 'YYYY-MM-DD' - )}` - }, - parser: function ({ content, date, channel }) { - const programs = [] - const items = parseItems(content) - items.forEach(item => { - const prev = programs[programs.length - 1] - const $item = cheerio.load(item) - let start = parseStart($item, date, channel) - if (prev) { - if (start.isBefore(prev.start)) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.add(30, 'm') - programs.push({ - title: parseTitle($item), - start, - stop - }) - }) - - return programs - }, - async channels() { - const axios = require('axios') - - const providers = ['-9000019', '-8000019', '-1000019', '-2000019', '-7000019'] - - const channels = [] - for (let provider of providers) { - const data = await axios - .post('https://www.tvireland.ie/tv/schedule', null, { - params: { - provider, - region: 'Ireland', - TVperiod: 'Night', - date: dayjs().format('YYYY-MM-DD'), - st: 0, - u_time: 2027, - is_mobile: 1 - } - }) - .then(r => r.data) - .catch(console.log) - - const $ = cheerio.load(data) - $('.channelname').each((i, el) => { - const name = $(el).find('center > a:eq(1)').text() - const url = $(el).find('center > a:eq(1)').attr('href') - const [, number, slug] = url.match(/\/(\d+)\/(.*)\.html$/) - - channels.push({ - lang: 'en', - name, - site_id: `${number}/${slug}` - }) - }) - } - - return uniqBy(channels, x => x.site_id) - } -} - -function parseStart($item, date) { - const timeString = $item('td:eq(0)').text().trim() - const dateString = `${date.format('YYYY-MM-DD')} ${timeString}` - - return dayjs.tz(dateString, 'YYYY-MM-DD H:mm a', 'Europe/Dublin') -} - -function parseTitle($item) { - return $item('td:eq(1)').text().trim() -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('table.table > tbody > tr').toArray() -} +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const { uniqBy } = require('../../scripts/functions') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'tvireland.ie', + days: 2, + url: function ({ date, channel }) { + return `https://www.tvireland.ie/tv/listings/channel/${channel.site_id}?dt=${date.format( + 'YYYY-MM-DD' + )}` + }, + parser: function ({ content, date, channel }) { + const programs = [] + const items = parseItems(content) + items.forEach(item => { + const prev = programs[programs.length - 1] + const $item = cheerio.load(item) + let start = parseStart($item, date, channel) + if (prev) { + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.add(30, 'm') + programs.push({ + title: parseTitle($item), + start, + stop + }) + }) + + return programs + }, + async channels() { + const axios = require('axios') + + const providers = ['-9000019', '-8000019', '-1000019', '-2000019', '-7000019'] + + const channels = [] + for (let provider of providers) { + const data = await axios + .post('https://www.tvireland.ie/tv/schedule', null, { + params: { + provider, + region: 'Ireland', + TVperiod: 'Night', + date: dayjs().format('YYYY-MM-DD'), + st: 0, + u_time: 2027, + is_mobile: 1 + } + }) + .then(r => r.data) + .catch(console.log) + + const $ = cheerio.load(data) + $('.channelname').each((i, el) => { + const name = $(el).find('center > a:eq(1)').text() + const url = $(el).find('center > a:eq(1)').attr('href') + const [, number, slug] = url.match(/\/(\d+)\/(.*)\.html$/) + + channels.push({ + lang: 'en', + name, + site_id: `${number}/${slug}` + }) + }) + } + + return uniqBy(channels, x => x.site_id) + } +} + +function parseStart($item, date) { + const timeString = $item('td:eq(0)').text().trim() + const dateString = `${date.format('YYYY-MM-DD')} ${timeString}` + + return dayjs.tz(dateString, 'YYYY-MM-DD H:mm a', 'Europe/Dublin') +} + +function parseTitle($item) { + return $item('td:eq(1)').text().trim() +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('table.table > tbody > tr').toArray() +} diff --git a/sites/tvmusor.hu/tvmusor.hu.config.js b/sites/tvmusor.hu/tvmusor.hu.config.js index ea420299e..52ecb2861 100644 --- a/sites/tvmusor.hu/tvmusor.hu.config.js +++ b/sites/tvmusor.hu/tvmusor.hu.config.js @@ -1,81 +1,81 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const { uniqBy } = require('../../scripts/functions') - -module.exports = { - site: 'tvmusor.hu', - days: 2, - url: 'https://tvmusor.borsonline.hu/a/get-events/', - request: { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' - }, - data({ channel, date }) { - const params = new URLSearchParams() - params.append( - 'data', - JSON.stringify({ - blocks: [`${channel.site_id}|${date.format('YYYY-MM-DD')}`] - }) - ) - - return params - } - }, - parser({ content, channel, date }) { - let programs = [] - const items = parseItems(content, channel, date) - items.forEach(item => { - const prev = programs[programs.length - 1] - let start = dayjs(item.e) - let stop = dayjs(item.f) - if (prev) { - start = prev.stop - } - - programs.push({ - title: item.j, - category: item.h, - description: item.c, - image: parseImage(item), - start, - stop - }) - }) - - return programs - }, - async channels() { - const data = await axios - .get('https://tvmusor.borsonline.hu/most/') - .then(r => r.data) - .catch(console.log) - - const [, channelData] = data.match(/const CHANNEL_DATA = (.*);/) - const json = channelData.replace('},}', '}}').replace(/(\d+):/g, '"$1":') - const channels = JSON.parse(json) - - return Object.values(channels).map(item => { - return { - lang: 'hu', - site_id: item.id, - name: item.name - } - }) - } -} - -function parseImage(item) { - return item.z ? `https://tvmusor.borsonline.hu/images/events/408/${item.z}` : null -} - -function parseItems(content, channel, date) { - const data = JSON.parse(content) - if (!data || !data.data || !data.data.loadedBlocks) return [] - const blocks = data.data.loadedBlocks - const blockId = `${channel.site_id}_${date.format('YYYY-MM-DD')}` - if (!Array.isArray(blocks[blockId])) return [] - - return uniqBy(uniqBy(blocks[blockId], a => a.e), b => b.b) -} +const axios = require('axios') +const dayjs = require('dayjs') +const { uniqBy } = require('../../scripts/functions') + +module.exports = { + site: 'tvmusor.hu', + days: 2, + url: 'https://tvmusor.borsonline.hu/a/get-events/', + request: { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' + }, + data({ channel, date }) { + const params = new URLSearchParams() + params.append( + 'data', + JSON.stringify({ + blocks: [`${channel.site_id}|${date.format('YYYY-MM-DD')}`] + }) + ) + + return params + } + }, + parser({ content, channel, date }) { + let programs = [] + const items = parseItems(content, channel, date) + items.forEach(item => { + const prev = programs[programs.length - 1] + let start = dayjs(item.e) + let stop = dayjs(item.f) + if (prev) { + start = prev.stop + } + + programs.push({ + title: item.j, + category: item.h, + description: item.c, + image: parseImage(item), + start, + stop + }) + }) + + return programs + }, + async channels() { + const data = await axios + .get('https://tvmusor.borsonline.hu/most/') + .then(r => r.data) + .catch(console.log) + + const [, channelData] = data.match(/const CHANNEL_DATA = (.*);/) + const json = channelData.replace('},}', '}}').replace(/(\d+):/g, '"$1":') + const channels = JSON.parse(json) + + return Object.values(channels).map(item => { + return { + lang: 'hu', + site_id: item.id, + name: item.name + } + }) + } +} + +function parseImage(item) { + return item.z ? `https://tvmusor.borsonline.hu/images/events/408/${item.z}` : null +} + +function parseItems(content, channel, date) { + const data = JSON.parse(content) + if (!data || !data.data || !data.data.loadedBlocks) return [] + const blocks = data.data.loadedBlocks + const blockId = `${channel.site_id}_${date.format('YYYY-MM-DD')}` + if (!Array.isArray(blocks[blockId])) return [] + + return uniqBy(uniqBy(blocks[blockId], a => a.e), b => b.b) +} diff --git a/sites/tvprofil.com/tvprofil.com.channels.xml b/sites/tvprofil.com/tvprofil.com.channels.xml index bd09d7a76..0c1549716 100644 --- a/sites/tvprofil.com/tvprofil.com.channels.xml +++ b/sites/tvprofil.com/tvprofil.com.channels.xml @@ -1,5839 +1,5839 @@ - - - Tring 3 Plus - 21 Mix - 360 TuneBox - ABC News Albania - Agro TV - Al Jazeera - Al Jazeera Balkans - Алсат М - AMC - Apollon TV - Arena Sport 5 RS - Arena Sport 6 RS - ARTE DE - ArtKino 1 - ArtKino 2 - ArtKino 3 - ATV Avrupa - ATV KS - ATV TR - AXN - AXN Spin - B92 - BabyTV - Balkanika TV - Bang Bang - BBC Earth - BBC World News - Beyaz TV - Radio Televizija BN - BN music - Boomerang - Bubble TV - Cartoon Network - Cartoonito CEE - CBS Reality - Cinemax - Cinemax 2 - CineStar Action&Thriller RS - CineStar TV Comedy Family - CineStar TV Fantasy - CineStar TV RS - Click TV - English Club TV - CNN Europe - Comedy Central DE - Crime & Investigation Channel - Cufo TV - Da Vinci Learning - Deutsche Welle English - Discovery Animal Planet - Discovery Channel - TLC - Disney Channel - DM SAT - DocuBox HD - Dorcel TV - Duck TV CZ - DuckTV - Elrodi - E! Entertainment - Epic Drama (CEE) - Eroxxx HD - Eurochannel - EuroNews - Euronews Albania - Explorer Histori - Explorer Natyra - Explorer Shkence - Extreme Sports Channel - FashionBox HD - Fast&FunBox HD - FAX News - FightBox HD - Film Aksion Digitalb - Film Drame Digitralb - Film Klub - Film Klub Extra - Film Thriller Digitalb - FilmBox Arthouse - FilmBox Extra RS - FilmBox Stars RS - FilmBox Premium RS - STAR Channel - STAR Crime - STAR Life - FOX Turkey - France24 - France 2 - Radio Televizija Federacije BIH - Gametoon HD - Habertürk - HappyTV - Hayat TV - HBO - HBO3 - HBO2 - The History Channel - History Channel 2 - HRT 1 - HRT 3 - HUSTLER TV - Investigation Discovery - JimJam - Tring Jolly HD - Junior TV - Kujtesa Sport 1 - Kujtesa Sport 2 - Kujtesa Sport 3 - Kujtesa Sport 4 - Kanal 6 - Kanal 7 - Kanal 10 - Kanal D - Kanali 7 - KitchenTV - Klan Kosova - Klan Plus - Kohavision - Kopliku TV - Living HD - Minimax - Motorvision HD RO - MTV Europe - MTV 00s - MTV Hits International - MTV Live HD - MTV 90s International - MUSE - My Music - NTV DE - National Geographic - Nat Geo Wild - NBA TV - News 24 - Nick Junior - Nickelodeon - Ora News - Pikaboo - RTV PINK - RED tv - Vesti - Pink Action - Pink Thriller - Pink Crime & Mystery - Pink Comedy - Pink Erotic 1 - Pink Erotic 2 - PINK Family - PINK Film - Pink Hits - Pink Kids - Pink LOL - Pink Romance - Pink SCI FI & Fantasy - Pink Music 1 - Pink and Roll - Pink Premium - Pink Reality - Pink Serije - Pink Super Kids - Pink Western - Pink World Cinema - Power TV - Power Türk TV - Brazzers TV (ex. Private Spice) - Private TV - ProSieben - ProSieben MAXX - Prva FILES - Prva KICK - Prva LIFE - Prva MAX - Prva Srpska TV - Prva World - RAI DUE - RAI TRE - RAI News 24 - Report TV - RT Documentary - RTK 1 - RTK 2 - RTK 4 - RTL - RTL 2 - RTL Zwei - RTL DE - RTRS - RTRS PLUS - RTS 1 - RTS 2 - RTS 3 - RTS Drama - RTS Kolo - RTS Muzika - RTS Poletarac - RTS SVET - RTS Trezor - RTS Život - RTV21 - RTV 21 Popullore - RTV Besa - Russia Today - Sat.1 - Sat.1 Gold - SciFi - Shenja TV - Show TV - Sky News - Tring Smile - SOS Plus - Tring Sport News - Star TV TR - Studio B - Super RTL - Superstar TV - Syri TV - Syri Vizion - Timeless Drama Channel - Tip TV - Top Channel - Travel Channel - Tring Bizarre - Tring Bunga Bunga - Tring Collection - Tring Comedy - Tring Desire - Tring History - Tring Kids - Tring Life - Tring Action - Tring Novelas - Tring Planet - Tring Shqip - Tring Super - Tring Tring - Tring World - TRT 1 - TRT 2 - TRT Spor - TRT Belgesel - TRT Çocuk - TRT Haber - TRT Müzik - TRT Turk - TRT World - TV5 Monde - TV1000 Balkan - TV 7 - TV 8 TR - Arena Sport 1 RS - Arena Sport 2 RS - Arena Sport 3 RS - Arena Sport 4 RS - TV Dukagjini - Телевизија Храм - Klan TV HD - RTV Most - DIVA (ex. Universal) - Vavoom - MTV 80s - Viasat Explore CEE - Viasat History - Viasat Nature CEE - Vizion Plus - RT Vojvodina 1 - RT Vojvodina 2 - VOX - Zico TV - Zjarr TV - 24 Kitchen - 360 TuneBox - Al Jazeera - Al Jazeera Balkans - AMC - Das Erste - ARTE FR - Auto Motor und Sport - BabyTV - Balkanika TV - BBC World News - BG-DNES - BHTV - Bloomberg TV - BN music - Body in Balance - Boomerang - BSTV BG - Cartoon Network - Cartoonito CEE - CBS Reality - CGTN - Первый - Cinemania BG - Cinemax 2 BG - Cinemax BG - Cinethronix RO - CNBC Europe - CNN Europe - Comedy Central BG - Nicktoons - Comedy Central Extra BG - Crime & Investigation Channel - Da Vinci Learning - Deluxe Music - Deutsche Welle English - Diema Sport 3 BG - Discovery Animal Planet - Discovery Channel - Discovery Science - TLC - Disney Junior BG - Stingray Djazz - DM SAT - DocuBox HD - Dorcel TV - Dorcel XXX - DSTV - DuckTV - Epic Drama (CEE) - Erox HD - Eroxxx HD - Eurochannel - EuroNews - Euronews FR - Eurosport 2 - Eurosport 1 BG - Extreme Sports Channel - F+ - Fashion TV - FashionBox HD - Fast&FunBox HD - FightBox HD - FilmBox Arthouse - FilmBox BG - FilmBox Extra BG - The Fishing and Hunting - The Fishing and Hunting RO - Fix & Foxi - France24 - France 2 - France 24 French - FunBox UHD - Gametoon HD - HBO 2 BG - HBO 3 BG - Home & Garden Television - The History Channel - History Channel 2 - HUSTLER TV - Investigation Discovery - Investigation Discovery BG - JimJam BG - K::CN 2 Music - КХЛ - KiKA - Love Nature - LUXE TV - MCM Top - Mezzo - Mezzo Live HD - Motorvision TV - MTV Europe - MTV 00s - Club MTV International - MTV Hits International - MTV Live HD - MTV 90s International - MyZen TV - Nasa TV US - Nick Junior - Nick Junior BG - Nickelodeon - Nickelodeon Commercial - Nickelodeon UK - Pickbox TV BG - Planeta TV BG - Playboy TV - Plovdivska Pravoslavna TV - Brazzers TV (ex. Private Spice) - ProSieben MAXX - RAI UNO - RAI News 24 - Reality Kings - RedLight HD - Ring TV - Россиᴙ 24 - RT Documentary - RTL Zwei - RTL DE - Russia Today - Sat.1 Gold - Sky News - Stingray iConcerts - Super RTL - SuperToons - Travel Channel - TRT Belgesel - TV5 Monde - TV5Monde Europe - TV1000 Balkan - TVE Internacional - Телевизия Стара Загора BG - MTV 80s - Viasat Explore CEE - Viasat History - Viasat Nature CEE - VOX - ZDF - 24Kitchen BG - AGRO TV BG - Alfa BG - AXN Black BG - AXN BG - AXN White BG - B1B Action TV BG - Barely Legal TV - Bloomberg TV BG - БНТ 1 - БНТ 2 - БНТ 3 - БНТ 4 - BOX TV BG - bTV BG - bTV Action - bTV Cinema - bTV Comedy - bTV Lady - Bulgaria on Air - City TV BG - Diema - Diema Family - Diema Sport 2 - Diema Sport - Disney Channel BG - EKids - Евроком - Фен Фолк ТВ - ФЕН ТВ - FilmBox Stars BG - HBO BG - Kino Nova - Magic TV BG - MAX Sport 1 BG - MAX Sport 2 BG - MAX Sport 3 BG - Max Sport 4 BG - Movie Star - National Geographic BG - Nat Geo Wild BG - Nova TV BG - Nova News - Nova Sport BG - Planeta Folk BG - Skat TV - Sport+ HD - STAR BG - STAR Crime BG - STAR Life BG - Travel TV BG - Travelxp - TV1 BG - TV Evropa BG - TV+ BG - Родина - Vivacom Arena - VTK BG - Wness TV - 24 Kitchen - 24 ВЕСТИ - 360 TuneBox - Adria TV - Adult Channel - Al Jazeera - Al Jazeera Arabic English - Al Jazeera Balkans - ALFA TV - AMC - Anixe HD Serie - ANIXE plus - Das Erste - Arena Esport - Arena Fight - Arena Sport 1 Premium - Arena Sport 1 Premium BiH - Arena Sport 1x2 - Arena Sport 2 BiH - Arena Sport 2 Premium - Arena Sport 2 Premium BiH - Arena Sport 3 BiH - Arena Sport 3 Premium - Arena Sport 3 Premium BiH - Arena Sport 4 BiH - Arena Sport 5 BiH - Arena Sport 6 RS - Arena Sport 6 BiH - Alternativna televizija Banja Luka - B92 - BabyTV - Balkan TV - Balkanika TV - BBC World News - BDC Televizija - Behar TV Sarajevo - Radio Televizija BIH - Bir TV - BlicTV - Bloomberg TV - Bloomberg Adria - Radio Televizija BN - BN music - Boomerang - Bravo Music - Cartoon Network - Cartoonito CEE - CBS Reality - CCTV 4 Europe - CGTN - Cinema TV - Cinemax - Cinemax 2 - City TV - English Club TV - Croatian Music Channel - CNBC Europe - CNN Europe - Nicktoons - Crime & Investigation Channel - Deutsche Welle English - Dexy TV - Digi 24 - Discovery Animal Planet - Discovery Channel - Discovery Science - TLC - Discovery Turbo Extra - Disney Channel - Disney Channel DE - DMAX DE - DM SAT - DocuBox HD - Dorcel TV - Dorcel XXX - DOX TV - Elta 1 HD - E! Entertainment - Erox HD - Eroxxx HD - Eska TV Extra - Etno TV RO - Eurochannel - EuroNews - Euronews FR - EuroNews Srbija - Eurosport - Eurosport 1 DE - Extreme Sports Channel - Fashion TV - FashionBox HD - Fast&FunBox HD - Favorit TV RO - FightBox HD - SK Fight - FilmBox Arthouse - FilmBox Extra RS - FilmBox Stars RS - FilmBox Premium RS - Food Network - STAR Channel - STAR Crime - STAR Life - STAR Movies - FOX NEWS - France24 - France 24 French - Radio Televizija Federacije BIH - GameHub HR - Gametoon HD - SK Golf - GP1 - Grand nostalgija - HappyTV - Hayat Folk - Hayat Plus - Hayat TV - HBO - HBO3 - HBO2 - Herceg TV - Historija TV - The History Channel - HIT TV - HSE - HRT 1 - HRT 3 - HUSTLER TV - Imperia TV - Insajder TV - Insta TV - Investigation Discovery - Izvorna TV - Otvorena televizija - JimJam - K1 TV - Kabel 1 - Kanal1 - Kanal 6 - Kazbuka - KiKA - Klape i Tambure TV - M1 Family - Maria Vision - Motorvision TV - MTV Europe - MTV 00s - Club MTV International - MTV Hits International - MTV Igman - MTV Live HD - MTV 90s International - MY TV - N1 RS - N24 Doku - Nasa TV US - National Geographic - National Geographic Channel HD - National Geographic RS - Nat Geo Wild - NBA TV - Neon TV - Nat Geo Wild HD - Nick Junior - Nick Music - Nickelodeon Commercial - NOVA TV - Nova Max - Nova Series - NTV 101 Sanski most - NTV IC Kakanj - O Kanal Music - O Kanal Plus - OBN - One - OSM TV - OTV Valentino - Pickbox TV RS - Pikaboo - RTV PINK - RED tv - Vesti - Pink Erotic 1 - Pink Erotic 2 - Pink Erotic 3 - Pink Erotic 4 - Pink Erotic 5 - Pink Erotic 6 - Pink Erotic 7 - Pink Erotic 8 - TV PINK EXTRA - PINK Film - Pink Folk 1 - Pink Hits 2 - Pink Kids - Pink Music 1 - Pink Parada - TV PINK PLUS - Pink Reality - Pink Timeout - Playboy TV - Poljoprivredna TV - Posavska Televizija - Premier League TV - Private TV - ProSieben - ProSieben MAXX - ProTV Tomislavgrad - Prva Srpska TV - QVC Deutschland - RAI UNO - RAI DUE - RAI TRE - RAI Education - RAI News 24 - Reality Kings - RT Documentary - RTL 2 - RTL Zwei - RTL DE - RTL Kockica - RTL Nitro - RTRS - RTS 1 - RTS Klasika - RTS Nauka - RTS Poletarac - RTS Život - RTSH 3 - RTSH 24 - RTSH Plus - RTSH Shkollë - RTV7 Tuzla - RTV BPK Goražde - RTV Herceg-Bosne - RTV HIT Brčko - RTV Lukavac - RTV Slon Tuzla - RTV Unsko-sanskog kantona - RTV Vogošća - RTV Zenica - RTV Tuzlanskog Kantona - Russia Today - Sat.1 - Sat.1 Gold - SciFi - Sevdah TV - SK1 BiH - SK esports - Sky News - Slobomir - Smart TV Tešanj - SOS Plus - Sport Klub 1 Srbija - Sport Klub 4 - Sport Klub 5 - Sport Klub 6 - Sport Klub 7 - Sport Klub 8 - Sport Klub 9 - Sport Klub 10 - Sport Klub HD - Sport Klub 1 Hrvatska - Sport Klub 3 - Sport Klub 2 Srbija - Superstar 2 - Super RTL - Supermedia Televizija - Tanjug Tačno - Tatabrada - TBN Polska - Timeless Drama Channel - Tele 5 DE - Televizija 5 - TLC DE - TNT Kids - Toxic Folk - Toxic Rap - Toxic TV - Travel Channel - Tropik TV - TRT 1 - TRT World - TV1 Mreža - TV Arena Bijeljina - Arena Sport 1 RS - Arena Sport 1 HR - Arena Sport 4 RS - Televizija Dalmacija - TV Duga + SAT - TV Istočno Sarajevo - TV Ras - TV Sarajevo - TV Vijesti - RTV Visoko - Televizija Crne Gore MNE - Televizija Doktor - TVR Cluj RO - TVR Craiova RO - TVR Iasi RO - TVR Tg-Mures RO - TVR Timisoara RO - UNA TV BiH - DIVA (ex. Universal) - Valentino Etno - Valentino Music HD - Vavoom - MTV 80s - Vikom - VOX - VOX up - Welt - ZDF - ZDFinfo - ZDFneo - 1-2-3.tv - 3SAT - 13th Street DE - A Spor - Al Jazeera - Animal Planet DE - Anixe HD Serie - ANIXE plus - Das Erste - ARD-alpha - ARTE DE - AstroTV - Alternativna televizija Banja Luka - ATV1 - ATV2 - Auto Motor und Sport - B92 - Bayerischen Fernsehen Nord - BBC World News - beIN Movies Premiere TR - Bergblick - BFM TV - Radio Televizija BIH - Bibel TV - Das Bild TV - Bloomberg TV - Blue Hustler - Radio Televizija BN - BN music - BonGusto - Boomerang DE - BR Fernsehen Süd - Cartoon Network DE - CCTV 4 Europe - CGTN - CGTN Documentary - Croatian Music Channel - CNBC Europe - CNews - CNN Europe - Comedy Central DE - Nicktoons - Crime & Investigation Channel - Crime & Investigation DE - Deluxe Music - Deutsche Welle English - Deutsches Musik Fernsehen - Deutsche Welle Deutsch - Discovery Channel DE - Disney Channel DE - DMAX DE - DM SAT - DocuBox HD - Dorcel TV - Dorcel XXX - DuckTV - Elta 1 HD - E! Entertainment - Erox HD - Euro Star - EuroNews - Eurosport - Eurosport 2 DE - Eurosport 1 DE - Extreme Sports Channel - Fashion TV - Fast&FunBox HD - Fight 24 - Fix & Foxi - Folx TV - France24 - France 24 French - FS1 AT - Radio Televizija Federacije BIH - Geo Television - Ginx Esports TV - Gute Laune TV - Habertürk - HappyTV - Hayat TV - Heimatkanal - HGTV DE - History Channel DE - HR Fernsehen - HSE - HSE24 Extra - HSE24 Trend - HRT 1 - HUSTLER TV - K-TV Katholisches Fernsehen - Kabel 1 - Kabel eins Österreich - Kabel Eins Classics - Kabel eins Doku - Kanal 7 - Kanal D TR - Kerrang! - KiKA - Kinowelt - KIT-TV - krone.tv - Love Nature - Marco Polo TV - MDR Fernsehen - MDR Sachsen-Anhalt - MDR Sachsen - MDR Thüringen - Melodie TV - Mezzo - Mezzo Live HD - Motorvision TV - MTV 00s - Club MTV International - MTV DE - MTV Hits International - MTV Live HD - MTV 90s International - N24 Doku - NTV DE - NatGeo Wild DE - National Geographic DE - The Nautical Channel - NDR Hamburg - NDR Mecklenburg-Vorpommern - NDR Niedersachsen - NDR Schleswig-Holstein - Nick Junior - Nick Music - Nick DE - Niederbayern TV Deggendorf-Straubing - NOVA TV - oe24.TV - One - ORF1 - ORF2 - ORF3 - ORF Sport Plus - OTV Valentino - Phoenix - TV PINK EXTRA - PINK Film - Pink Folk 1 - Pink Kids - Pink Music 1 - TV PINK PLUS - Pink Reality - Playboy TV - ProSieben - PRO 7 Österreich - ProSieben Fun - ProSieben MAXX - Prva Srpska TV - Puls 4 - QVC 2 DE - QVC Beauty - QVC Deutschland - QVC STYLE DE - RAI UNO - RAI DUE - RAI TRE - RAI Education - RAI News 24 - RAI Storia - RBB Fernsehen Brandenburg - RBB Fernsehen Berlin - RiC DE - Romance TV - RTL 2 - RTL Zwei - RTL Crime DE - RTL Croatia World - RTL DE - RTL Kockica - RTL Living DE - RTL Nitro - RTL Passion DE - RTLup - RTRS - RTS 1 - Russia Today - Sat.1 Emotions - Sat.1 - Sat.1 Österreich - Sat.1 Gold - Kurier TV - Schlager Deluxe - ServusTV - Show Türk - Sixx - Sixx AT - Sky 1 DE - Sky Atlantic DE - Sky Cinema Action DE - Sky Cinema Classics DE - Sky Cinema Family DE - Sky Krimi DE - Sky Cinema Thriller - Sky Sport 1 DE - Sky Sport 3 DE - Sky Sport 4 DE - Sky Sport 5 DE - Sky Sport 6 DE - Sky Sport 7 DE - Sky Sport 8 DE - Sky Sport 9 DE - Sky Sport 10 DE - Sky Bundesliga 1 - Sky Bundesliga 2 - Sky Bundesliga 3 - Sky Bundesliga 4 - Sky Bundesliga 5 - Sky Bundesliga 6 - Sky Bundesliga 7 - Sky Sport News DE - Sky Sports News - Sky News - sonnenklar.TV - Sony AXN - Sony Channel DE - Spiegel Geschichte - Curiosity Channel - Sport 1 plus DE - Šport TV 1 - Sportdigital Fußball - SR Fernsehen - SRF Info - SRF Zwei - Stingray Classica - Super RTL - SWR - SWR Baden-Württemberg - Syfy HD DE - tagesschau24 - Tele 5 DE - TLC DE - Warner TV Comedy DE - Warner TV Film DE - Warner TV Serie DE - TOGGO plus - TRT 1 - TRT Spor - TRT Belgesel - TRT Çocuk - TRT Müzik - TV 8 TR - Universal TV DE - MTV 80s - Vivid TV - VOX - W24 - WDR Fernsehen - WDR Fernsehen Köln - Welt - Welt der Wunder - ZDF - ZDFinfo - ZDFneo - 2M Monde - 3 Plus CH - 3SAT - 4 Plus - 4 Seven - 5 Plus - 5 Star - 6 plus - 6ter - 13th Street DE - 20 Mediaset - Action - Al Jazeera - Al Jazeera Arabic Arabic - Anixe HD Serie - ANIXE plus - Antena 3 - Das Erste - ARD-alpha - ARTE FR - ARTE DE - auftanken.TV - B1 TV - BBC1 - BBC2 - BBC3 - BBC4 - BBC News Channel - BBC Parliament - BBC World News - BFM Business - BFM TV - Bibel TV - Das Bild TV - Bloomberg TV - Bloomberg Quicktake - BN 2 HD - Boing - Boing Plus - Boomerang - Boomerang DE - BR Fernsehen Süd - C8 FR (ex. D8) - Canal 24H - Canal+ Kids FR - Canal J - Canal+ Cinéma(s) FR - Canal+ France - Canal Plus Sport FR - Canal+ Series FR - Canale 5 - CARAC4 - Cartoon Network DE - Cartoonito Italia - CBBC - Cbeebies - CCTV 4 Europe - CGTN - Challenge TV - Channel 4 - Channel 5 - Chérie 25 - cielo - Cine34 - Polar+ - Cine+ Premier FR - Children's ITV - Class TV Moda - English Club TV - Clubland TV - Croatian Music Channel - CNBC Europe - CNews - CNN Europe - CNN Türk TV - Comedy Central DE - Crime District - Crime & Investigation Channel - CStar FR - Dave - Deluxe Music - Deutsche Welle English - Deutsches Musik Fernsehen - Deutsche Welle Deutsch - Deutsche Welle Espanol - Discovery Channel DE - Discovery Channel IT - Disney Channel DE - DMAX IT - DMAX DE - DM SAT - Drama UK - Dream Türk - Sport 1 DE - Duna TV - Duna World - E4 - E! Entertainment - Euro D - Euro Star - EuroNews - Euronews FR - Eurosport - Eurosport 2 DE - Eurosport 2 FR - Eurosport 2 IT - Eurosport 1 DE - Eurosport FR - Eurosport IT - Fashion TV - Film4 - Fix & Foxi - Focus TV - Folx TV - Food Network Italia - Food Network UK - FOX NEWS - France24 - France 2 - France 3 - France 4 - France 5 - France 24 French - France Info - Frisbee - FunBox UHD - Giallo TV - Ginx Esports TV - Golf Channel Češka - Golf plus - Gulli - Habertürk - Halk TV - Hayat Folk - Hayat Music - Hayat Plus - Das Health TV - HGTV DE - HGTV IT - The History Channel - HR Fernsehen - HSE - HSE24 Extra - HSE24 Trend - HRT 1 - i24News FR - Iris - Italia 1 - Italia 2 - ITV1 - ITV2 - ITV3 - ITV4 - ITV Be - K2 - K-TV Katholisches Fernsehen - Kabel 1 - Kabel eins Doku - Kanal 9 TV - Kerrang! - KiKA - Kiss UK - Klan Kosova - Kohavision - L'Equipe - La Cinque - La7 - La7d - La Télé - La Chaîne Info - Magyar Televízió 1 - M2 Petőfi - M4 sport - m5 - M6 - Mangas - MCM FR - MDR Fernsehen - Mediaset Extra - Mediaset Italia - Mezzo - Mezzo Live HD - More4 - More Than Sports TV - Motorvision TV - Motorvision TV France - MTV DE - 5Select - N24 Doku - NTV DE - Nasa TV US - National Geographic DE - The Nautical Channel - NDR Hamburg - Nick DE - NOVE - Now 90s - NRJ12 - RMC Story - OBN - One - ORF1 - ORF2 - ORF3 - PBS America - Phoenix - Pick TV - TV PINK EXTRA - PINK Film - Pink Folk 1 - Pink Kids - Pink Koncert - Pink Music 1 - TV PINK PLUS - Pink Reality - Power Türk TV - ProSieben - ProSieben MAXX - Puls 8 - Radio Ticino Channel HD - RadioBremen - RAI UNO - RAI DUE - RAI TRE - RAI 4 - RAI 5 - RAI Education - RAI Gulp - RAI Movie - RAI News 24 - RAI Premium - RAI Sport 1 - RAI Storia - Rai Yoyo - RBB Fernsehen Berlin - real time - Rete 4 - RiC DE - RMC Découverte - Rocket Beans TV - Romance TV - CARAC1 - RSI La 1 - RSI La 2 - RTK 1 - RTL Zwei - RTL Crime DE - RTL DE - RTL Living DE - RTL Nitro - RTL Passion DE - RTLup - RTP3 - RTP Internacional - RTRS - RTS 2 Suisse - RTS SVET - RTS Un - Russia Today - S1 CH - S4C - Sat.1 - Sat.1 Gold - ServusTV - Show Türk - SIC - Sixx - Sky Atlantic - Sky Krimi DE - Sky Sport 1 DE - Sky Sports News - Sky TG24 HD - Sky News - SonLife - Spiegel Geschichte - Sport 1 plus DE - Sportitalia - SR Fernsehen - SRF 1 - SRF Info - SRF Zwei - Stingray Classica - Stingray iConcerts - Super! - Super RTL - Supertennis HD - K::CN 3 Svet Plus - Swiss1 TV - SWR - Syfy HD DE - tagesschau24 - TELE 1 - Tele 5 DE - TeleBärn - Tele Zürich - Télévision française 1 - TFX - TGCOM24 - TGRT HABER - TiJi - TLC DE - TMC - Warner TV Comedy DE - Warner TV Film DE - Warner TV Serie DE - TOGGO plus - Top Crime - Trace Urban - TRT Spor - TRT Kurdî - TRT Belgesel - TRT Çocuk - TRT Müzik - TRT Turk - TV5 Monde - TV5Monde Europe - TV8 IT - TV24 - TV25 - TV 8 TR - TV 2000 - TV Dukagjini - TVE Internacional - Televisión de Galicia - TV Internacional - TVM3 - Twenty Seven - DIVA (ex. Universal) - VH1 IT - Viaplay Xtra - VOX - VOX up - W9 - WDR Fernsehen - Welt - Welt der Wunder - Wetter - Yesterday - Zagrebačka Televizija - ZDF - ZDFinfo - ZDFneo - 1-2-3.tv - 3SAT - 13th Street DE - Al Jazeera - Al Jazeera Arabic English - Al Jazeera Balkans - Animal Planet DE - Anixe HD Serie - ANIXE plus - Das Erste - ARD-alpha - ARTE DE - AstroTV - Alternativna televizija Banja Luka - auftanken.TV - Baden TV - Balkanika TV - Bayerischen Fernsehen Nord - BBC World News - beIN iZ - beIN Movies Premiere TR - Bergblick - Bibel TV - Das Bild TV - Bloomberg TV - BonGusto - Boomerang DE - BR Fernsehen Süd - Canal 24H - Cartoon Network DE - CGTN - CGTN Documentary - Croatian Music Channel - CNBC Europe - CNN Europe - Comedy Central DE - Nicktoons - Crime & Investigation DE - DAZN 1 DE - DAZN 2 DE - Deluxe Music - Deutsche Welle English - Deutsches Musik Fernsehen - Deutsche Welle Deutsch - Discovery Channel DE - Disney Channel DE - DMAX DE - DM SAT - Sport 1 DE - Edge Sport - eSports 1 - Euro Star - EuroNews - Eurosport 2 DE - Eurosport 1 DE - EWTN - Extreme Sports Channel - Fashion TV - Fast&FunBox HD - Fight 24 - Fix & Foxi - Folx TV - France24 - France 2 - France 3 - France 4 - France 5 - France 24 French - Geo Television - Ginx Esports TV - Goldstar - Gute Laune TV - Habertürk - Halk TV - Hamburg 1 - Das Health TV - Heimatkanal - Home & Garden Television - HGTV DE - History Channel DE - HR Fernsehen - HSE - HSE24 Extra - HSE24 Trend - HRT 1 - Insight TV - Jukebox - K-TV Katholisches Fernsehen - Kabel 1 - Kabel Eins Classics - Kabel eins Doku - Kanal 7 - KiKA - Kinowelt - Klan Kosova - Kohavision - Marco Polo TV - MDR Fernsehen - MDR Sachsen-Anhalt - MDR Sachsen - MDR Thüringen - Mediaset Italia - Melodie TV - More Than Sports TV - Motorvision TV - MTV DE - MTV Live HD - münchen.tv - N24 Doku - NTV DE - NatGeo Wild DE - National Geographic DE - NDR Hamburg - NDR Mecklenburg-Vorpommern - NDR Niedersachsen - NDR Schleswig-Holstein - Nick Junior - Nick DE - Niederbayern TV Deggendorf-Straubing - One - ORF3 - ORF Sport Plus - Phoenix - TV PINK EXTRA - PINK Film - Pink Folk 1 - Pink Music 1 - TV PINK PLUS - Power Türk TV - ProSieben - ProSieben MAXX - QVC 2 DE - QVC Deutschland - RAI UNO - RAI DUE - RAI TRE - RAI News 24 - RAI Storia - RBB Fernsehen Brandenburg - RBB Fernsehen Berlin - RiC DE - Rhein Neckar Fernsehen - Rocket Beans TV - Romance TV - RTK 1 - RTL Zwei - RTL Crime DE - RTL DE - RTL Living DE - RTL Nitro - RTL Passion DE - RTLup - RTS SVET - Russia Today - Sat.1 Emotions - Sat.1 - Sat.1 Gold - Schlager Deluxe - ServusTV Deutschland - Show Türk - Silverline Movie Channel - Sixx - Sky 1 DE - Sky Atlantic DE - Sky Cinema Action DE - Sky Cinema Best Of DE - Sky Cinema Classics DE - Sky Cinema Comedy DE - Sky Cinema Family DE - Sky Krimi DE - Sky Cinema Premieren - Sky Sport 1 DE - Sky Sport 3 DE - Sky Sport 4 DE - Sky Sport 5 DE - Sky Sport 6 DE - Sky Sport 7 DE - Sky Sport 8 DE - Sky Sport 9 DE - Sky Sport 10 DE - Sky Bundesliga 1 - Sky Bundesliga 2 - Sky Bundesliga 3 - Sky Bundesliga 4 - Sky Bundesliga 5 - Sky Bundesliga 6 - Sky Bundesliga 7 - Sky Sport Mix DE - Sky Sport News DE - Sky Sport Premier League DE - Sky News - sonnenklar.TV - Sony AXN - Sony Channel DE - Spiegel Geschichte - Curiosity Channel - Sport 1 plus DE - Sportdigital Fußball - SR Fernsehen - Stingray Classica - Super RTL - SWR - SWR1 Baden-Württemberg - SWR Baden-Württemberg - Syfy HD DE - tagesschau24 - Tele 5 DE - TGRT EU - TLC DE - Warner TV Comedy DE - Warner TV Film DE - Warner TV Serie DE - TOGGO plus - TRT Turk - TV5 Monde - TV5Monde Europe - TV8 IT - TV Now DE - TVE Internacional - TVS - MTV 80s - VOX - VOX up - WDR Fernsehen - WDR Fernsehen Köln - Welt - Welt der Wunder - Wetter - Wir 24 - ZDF - ZDFinfo - ZDFneo - 4Music - 4 Seven - 5ACTION - 5 Star - 5 USA - Al Jazeera Arabic English - Alibi - AMC UK - Animal Planet UK - At The Races - BabyTV - BBC1 - BBC1 Northern Ireland - BBC One Scotland - BBC2 - BBC3 - BBC4 - BBC Alba - BBC Earth - BBC News Channel - BBC 1 Wales - BBC Parliament - BBC 2 Northern Ireland - BBC Two Wales - BBC World News - Boomerang - Boomerang UK - TNT Sports 1 - TNT Sports 2 - TNT Sports 4 - TNT Sports Europe - TNT Sports Ultimate - Cartoon Network - Cartoon Network UK - Cartoonito UK - CBBC - Cbeebies - CBS Drama UK - CBS Reality UK - Challenge TV - Channel 4 - Channel 4 +1 - Channel 5 - Channel S - Capital XTRA - Children's ITV - English Club TV - Clubland TV - Comedy Central Extra UK - Comedy Central UK - Create and Craft - Crime and Investigation UK - Dave - Deutsche Welle English - Discovery Channel UK - Discovery History UK - Discovery Science UK - Discovery Turbo UK - DMAX UK - Drama UK - E4 - E4 Extra - Eden UK - Edge Sport - BT Sport ESPN - EuroNews - Eurosport 2 UK - Eurosport 1 EMEA - Eurosport.com - Eurosport UK - Extreme Sports Channel - Film4 - Food Network UK - Gems TV - Ginx Esports TV - Great! romance - History 2 UK - History Channel UK - Home & Garden Television UK - Ideal Home Shopping - Inspiration TV - Investigation Discovery UK - ITV1 - ITV2 - ITV2+1 - ITV3 - ITV4 - ITV Be - Jewellery Channel - JML Direct - Kerrang! - Ketchup TV - Kiss UK - Pop Max - London Live - Love Nature - Magic - Manchester TV - More4 - MotorTrend - Movies 24 - MTV 80s UK - MTV 90s UK - MTV Hits UK - MTV Live HD - MTV Music UK - MTV UK - 5Select - Nat Geo Wild UK - National Geographic Channel UK - Nick Jr. Too UK - Nick Junior UK - Nick Music - Nickelodeon UK - Nicktoons UK - Now 70s - Now 80s - Now 90s - PBS America - Pick TV - Pop UK - Premier Sports 1 - Premier Sports 2 - Quest TV - Quest Red - QVC Beauty - QVC Style - Really UKTV - Revelation TV - S4C - Sky Arts - Sky Atlantic - Sky Cinema Drama - Sky Cinema Greats - Sky Cinema Hits - Sky Cinema Select - Sky Cinema SF Horror - Sky Crime - Sky Documentaries - Sky Kids - Sky Witness - Sky Max - Sky Cinema Action - Sky Cinema Comedy - Sky Cinema Thriller - Sky Family - Sky Cinema Premiere - Sky Nature UK - Sky Replay - Sky Showcase - Sky Sports Main Event - Sky Sports Cricket - Sky Sports Action - Sky Sports Golf - Sky Sports Premier League - Sky Sports Arena - Sky Sports F1 - Sky Sports Football - Sky Sports Mix - Sky Sports News - Sky Sports Racing - Sky News - Smithsonian Channel - SonLife - Sony Channel - Great! Movies - Great! Movies Action - Great! Movies Classic - Great! Movies +1 - STV UK - Sky Sci-Fi - Talking Pictures TV - TCM UK - That's TV - The Box - Trace Vault UK - Tiny Pop TV - TLC UK - Together - Trans World Radio - UK Gold - Ulster TV - Viaplay Xtra - W UK - Warner TV IT - Yesterday - 4Music - 4 Seven - 5 Star - Al Jazeera - Al Jazeera Arabic English - Alibi - Animal Planet UK - Arise News - At The Races - BBC1 - BBC1 Northern Ireland - BBC One Scotland - BBC2 - BBC3 - BBC4 - BBC Alba - BBC News Channel - BBC Parliament - Bloomberg TV - Boomerang - Boomerang UK - TNT Sports 1 - TNT Sports 2 - TNT Sports 4 - TNT Sports Europe - TNT Sports Ultimate - Cartoon Network - Cartoon Network UK - Cartoonito UK - CBBC - Cbeebies - CBS Drama UK - CBS Reality UK - CGTN - Challenge TV - Channel 4 - Channel 4 +1 - Channel 5 - Children's ITV - CNBC Europe - CNN Europe - Comedy Central Extra UK - Comedy Central UK - Crime and Investigation UK - Cúla4 - Dave - Daystar - Discovery Channel UK - Discovery History UK - Discovery Science UK - Discovery Turbo UK - DMAX UK - DocuBox HD - Drama UK - E4 - E4 Extra - Eden UK - EuroNews - Eurosport 2 UK - Eurosport UK - Extreme Sports Channel - Fashion TV - FashionBox HD - Fast&FunBox HD - Film4 - FilmBox Arthouse - Food Network UK - France24 - Gametoon HD - Gems TV - Ginx Esports TV - The History Channel - History Channel 2 - Home & Garden Television UK - Ideal Home Shopping - Inspiration TV - Investigation Discovery UK - ITV1 - ITV2 - ITV2+1 - ITV3 - ITV4 - ITV Be - JML Direct - Kerrang! - Kiss UK - Pop Max - Legend - Magic - More4 - Movies 24 - MTV 80s UK - MTV 90s UK - MTV Hits UK - MTV Music UK - MTV UK - 5Select - Nat Geo Wild UK - National Geographic Channel UK - Nick Junior UK - Nickelodeon UK - Nicktoons UK - Oireachtas TV - Paramount Network UK - PBS America - Pick TV - Pop UK - Premier Sports 1 - Premier Sports 2 - Quest TV - Quest Red - QVC Beauty - QVC Style - Really UKTV - Revelation TV - RTÉ One - RTÉ2 - RTÉ News - RTÉjr - Russia Today - S4C - Sky Arts - Sky Atlantic - Sky Crime - Sky Kids - Sky Witness - Sky Max - Sky Cinema Action - Sky Cinema Comedy - Sky Cinema Thriller - Sky Family - Sky Nature UK - Sky Showcase - Sky Sports Racing - Sky News - Smithsonian Channel - SonLife - Sony Channel - Great! TV - Great! Movies - Great! Movies Action - Great! Movies Classic - Great! Movies +1 - Sky Sci-Fi - Talking Pictures TV - TCM UK - TG4 Ireland - The Box - Tiny Pop TV - TLC UK - Together - Virgin Media One - UK Gold - Ulster TV - Viaplay Xtra - Virgin Media Four - Virgin Media More - Virgin Media Three - Virgin Media Two - W UK - WION - Yesterday - Tring 3 Plus - 3SAT - 4Fun Dance - 4Fun Kids - 24 Kitchen - 24 ВЕСТИ - 360 TuneBox - A2 CNN - ABC News Albania - Adria TV - Agro TV - Al Jazeera Balkans - AMC - Anixe HD Serie - Das Erste - Arena Esport - Arena Fight - Arena Sport 1 Premium - Arena Sport 1x2 - Arena Sport 2 Premium - Arena Sport 3 Premium - Arena Sport 5 RS - Arena Sport 6 RS - Arena Sport 7 RS - Arena Sport 8 RS - Arena Sport 9 RS - Arena Sport 10 RS - ARTE DE - AXN - AXN Spin - B92 - BabyTV - Balkan trip - Balkan TV - Bang Bang - BBC Earth - BBC World News - BlicTV - Bloomberg Adria - Radio Televizija BN - BN music - BN 2 HD - Boomerang - Brainz TV - Bravo Music - Cartoon Network - Cartoonito CEE - CBS Reality - CCTV 4 Europe - CGTN - CGTN Documentary - Cinemax - Cinemax 2 - CineStar Premiere 1 - CineStar Premiere 2 - CineStar TV2 - CineStar TV Comedy Family - CineStar TV Fantasy - Click TV - Croatian Music Channel - CNN Europe - Nicktoons - Crime & Investigation Channel - Cufo TV - Deutsche Welle English - DigitAlb T HD - Discovery Animal Planet - Discovery Channel - Discovery Science - TLC - Discovery Turbo Extra - Disney Channel - Disney Junior - DM SAT - DocuBox HD - Dorcel TV - Dorcel XXX - DOX TV - Dr. Fit HD - DuckTV - Elrodi - Elta 1 HD - E! Entertainment - Epic Drama (CEE) - Erox HD - Eroxxx HD - Eurochannel - EuroNews - Euronews Albania - EuroNews Srbija - Eurosport - Eurosport 2 - Explorer Histori - Explorer Natyra - Explorer Shkence - Extreme Sports Channel - FACE TV - Fashion TV - FashionBox HD - Fast&FunBox HD - FAX News - FightBox HD - SK Fight - Film Aksion Digitalb - Film Autor - Film Drame Digitralb - Film Klub - Film Thriller Digitalb - FilmBox Arthouse - FilmBox Extra RS - FilmBox Stars RS - FilmBox Premium RS - Food Network - STAR Channel - STAR Crime - STAR Life - STAR Movies - FOX NEWS - France24 - France 3 - France 24 French - Radio Televizija Federacije BIH - Gametoon HD - SK Golf - Golica TV - Grand Televizija - Grand nostalgija - HappyTV - Hayat Folk - Hayat Music - Hayat Plus - Hayat TV - Hayatovci - HBO - HBO3 - HBO2 - Home & Garden Television - The History Channel - History Channel 2 - HRT 1 - HRT 2 - HRT 3 - HRT 4 - HUSTLER TV - IDJ World - Insajder TV - Insta TV - Investigation Discovery - Tring Jolly HD - Jugoton TV - Junior TV - K1 TV - K::CN 2 Music - Kanal 3 Prnjavor - Kanal D - Kanali 7 - Kazbuka - KitchenTV - Klan Kosova - Kohavision - K::CN 1 Kopernikus - Kurir TV - Living HD - Lov i ribolov - MCN TV - Mediaset Italia - DigitAlb Melody TV - Minimax - MTV Europe - MTV 00s - Club MTV International - MTV Hits International - MTV Live HD - MTV 90s International - MUSE - My Music - MyZen TV - N1 RS - Наша ТВ - National Geographic - National Geographic Channel HD - Nat Geo Wild - NBA TV - News 24 - Nat Geo Wild HD - Nick Junior - Nickelodeon Commercial - Nova Max - Nova S - Nova Series - Nova Sport Srbija - RTV Novi Pazar - NTV IC Kakanj - O Kanal - OBN - Ora News - OTV Valentino - Pickbox TV RS - Pikaboo - RTV PINK - RED tv - Vesti - Pink Action - Pink Thriller - Pink Crime & Mystery - Pink BH - Pink Classic - Pink Comedy - Pink Erotic 1 - Pink Erotic 2 - Pink Erotic 3 - Pink Erotic 4 - Pink Erotic 5 - Pink Erotic 6 - Pink Erotic 7 - Pink Erotic 8 - TV PINK EXTRA - PINK Family - Pink Fashion - PINK Film - Pink Folk 1 - Pink Folk 2 - Pink Ha Ha - Pink Hits - Pink Hits 2 - Pink Horror - Pink Kids - Pink Koncert - Pink Kuvar - Pink LOL - Pink M - Pink Movies - Pink Romance - Pink SCI FI & Fantasy - Pink Music 1 - Pink and Roll - Pink Pedia - TV PINK PLUS - Pink Premium - Pink Reality - Pink Serije - Pink Show - Pink Soap - Pink Style - Pink Super Kids - Pink Western - PINK World - Pink World Cinema - PINK Zabava - Premier League TV - Brazzers TV (ex. Private Spice) - Private TV - ProSieben - Prva FILES - Prva KICK - Prva LIFE - Prva MAX - Prva Plus - Prva Srpska TV - Prva TV Crna Gora - Prva World - RAI UNO - RAI DUE - Reality Kings - Report TV - Россиᴙ 24 - RT Documentary - RTK 1 - RTL - RTL 2 - RTL Croatia World - RTL Kockica - RTL Living - RTRS - RTRS PLUS - RTS 1 - RTS 2 - RTS 3 - RTS Drama - RTS Klasika - RTS Kolo - RTS Muzika - RTS Nauka - RTS SVET - RTS Trezor - RTS Život - Radio Televizija Budva - Russia Today - Scan TV - SciFi - Sport Klub 1 Crna Gora - SK esports - Sky News - RTV Slovenija 1 - Tring Smile - SOS Plus - Sport Klub 1 Srbija - Sport Klub 4 - Sport Klub 5 - Sport Klub 6 - Sport Klub HD - Sport Klub 1 Slovenija - Sport Klub 3 - Sport Klub 2 Srbija - Superstar 2 - Stinet - Studio B - STV Folk - SuperSport 1 AL - Superstar TV - K::CN 3 Svet Plus - Syri TV - Televizioni 7 - Tanjug Tačno - Timeless Drama Channel - TGCOM24 - Tip TV - TOGGO plus - Top Channel - Top News - Toxic Folk - Toxic TV - Travel Channel - Tring Collection - Tring Comedy - Tring History - Tring Kids - Tring Life - Tring Action - Tring Novelas - Tring Planet - Tring Shqip - Tring Super - Tring Tring - Tring World - TV5Monde Europe - TV1000 Balkan - Televizija TV7 - Arena Sport 1 RS - Arena Sport 2 RS - Arena Sport 3 RS - Arena Sport 4 RS - TV Duga + SAT - TV Dukagjini - Телевизија Храм - RTV Most - Televizija Doktor - UNA TV - DIVA (ex. Universal) - Vavoom - MTV 80s - Viasat Explore CEE - Viasat History - Viasat Nature CEE - Vikom - Vizion Plus - RT Vojvodina 1 - RT Vojvodina 2 - Woman HD - ZDFinfo - ZDFneo - Zdrava televizija - Zico TV - BG Music Channel - Fuel TV - Passion XXX - The Voice BG - 7RM - 8 Andalucía - 8tv Catalunya - 3/24 - À Punt ES - AMC Break ES - Al Jazeera - AMC Crime ES - Antena 3 - Antena.nova - Aragón TV - Atreseries ES - AXN ES - AXN White ES - BabyTV ES - BBC World News - BE MAD ES - Betis TV ES - Bloomberg TV - Boing ES - BOM - Calle 13 - Canal 24H - Canal Cocina - Canal Decasa - Canal Extremadura - Canal Fútbol Replay - Odisea - Canal Panda - Canal Parlamento - Canal Sur - Castilla la Mancha TV - Caza y Pesca ES - Clan TVE - CNBC Europe - CNN Europe - Comedy Central ES - COSMO - Cuatro - DAZN 1 ES - DAZN 2 ES - DAZN F1 - DBike Channel - Deutsche Welle English - Deutsche Welle Espanol - Discovery Channel ES - Discovery MAX.es - Disney Channel ES - Disney Junior ES - Divinity - DKISS ES - El Toro TV - LEVANTE TV - Energy - Esport3 - EuroNews - Euronews FR - Eurosport 1 ES - Eurosport 2 ES - EWTN ES - Extreme Sports Channel - Fashion TV - Factoria de Ficción - Feel Good - Fight Time - FOX ES - France24 - HispanTV - Historia ES - HIT TV ES - Canal Hollywood - Iberalia HD CAZA - Iberalia HD PESCA - Iberalia TV - La 1 Cataluña - La dos - LA 8 MEDITERRANEO - La Otra ES - La primera - La Rioja - La sexta - Mega ES - Mezzo - Mezzo Live HD - MOTO ADV - Motorvision TV - Movistar Acción - Movistar Cine Español - Movistar Comedia - Movistar Deportes 1 - Movistar Deportes 2 - Movistar Drama - Movistar Estrenos - Movistar Estrenos 2 - Movistar Fórmula 1 - Movistar Golf - Movistar LaLiga - Movistar Liga de Campeones - Movistar Series - Movistar Seriesmania - MTV 00s - Club MTV International - MTV ES - MTV Hits International - MTV Live HD - MTV Music UK - MTV 90s International - MyZen TV - Nasa TV US - Nat Geo Wild ES - National Geographic ES - Nautica TV - Antena.neox - Nick Junior ES - Nickelodeon ES - Paramount Network ES - Paramount Comedy ES - Real Madrid TV - Russia Today ES - SYFY ES - Sevilla FC TV - Sky News - Stingray Classica - Sundance ES - Canal 33 - SX3 - TCM ES - Telecinco - TELE ELX - TeleDeporte - TeleMadrid - Ten ES - Warner TV ES - Toros TV - TV3 Catalunya - TV3 ES - TV5Monde Europe - TV Canaria - TVE Internacional - Televisión de Galicia - #Vamos - MTV 80s - 6ter - 360 TV - AB3 - Action - Al Arabiya - Al Jazeera - Antena 3 - Das Erste - ART Aflam 1 - ART Aflam 2 - ART Cinema - ARTE FR - ARTE DE - Atreseries ES - BabyTV - BBC Earth - BBC World News - beIN Drama Channel - beIN Movies Action - beIN Movies Drama - beIN Movies Family - beIN Movies Premiere - beIN Movies Premiere TR - beIN Series 1 - beIN Series 2 - beIN Sports 1 FR - beIN Sports 2 FR - beIN Sports 3 FR - beIN Sports MAX 5 FR - beIN Sports MAX 6 FR - beIN Sports MAX 7 FR - beIN Sports MAX 8 FR - beIN Sports MAX 9 FR - beIN Sports MAX 10 FR - beIN Sports MAX 4 FR - Benfica TV - BET FR - BFM Business - BFM TV - Bloomberg TV - Boing - Boomerang - Boomerang FR - Boomerang UK - C8 FR (ex. D8) - Canal 24H - Canal Cocina - Canal Decasa - CANAL+ DOCS - Canal+ Kids FR - CANAL+ GRAND ECRAN - Canal J - Canal+ Cinéma(s) FR - Canal+ France - Canal Plus Sport FR - Canal+ Series FR - Cartoon Network - Cartoon Network FR - CCTV 4 Europe - CGTN - CGTN Documentary - Первый - Chérie 25 - Ciné+ Classic - Ciné+ Club - Ciné+ Emotion - Ciné+ Famiz - Ciné+ Frisson - Polar+ - Cine+ Premier FR - English Club TV - CNBC Europe - CNews - CNN Europe - Comedie+ - Comedy Central FR - Comedy Central UK - Crime District - CStar FR - CStar Hits France - Daystar - Deutsche Welle English - Discovery Channel FR - Discovery Investigation FR - Disney Channel FR - Disney Junior FR - Stingray Djazz - Dorcel TV - Dorcel XXX - E! Entertainment - Euro Star - Eurochannel - Eurochannel FR - EuroNews - Euronews FR - Eurosport 2 FR - Eurosport FR - Fashion TV - FightBox HD - FOX NEWS - France24 - France 2 - France 3 - France 4 - France 5 - France24 Arabic - France 24 French - France Info - Game One - Ginx Esports TV - Golf Channel Češka - Golf Channel FR - Golf plus - Gulli - Habertürk - HUSTLER TV - i24News FR - Infosport+ - iTVN - iTVN Extra - J-One - Kabel 1 - Kanal 7 - KBS World HD - KiKA - L'Equipe - La Chaîne Info - LCP - M6 - Mangas - MBC Bollywood - MCM FR - MCM Top - Mediaset Italia - Mezzo - Mezzo Live HD - MGG TV - Motorvision TV - Motorvision TV France - MTV Europe - MTV 00s - MTV FR - MTV Hits FR - MTV Hits International - MTV Live HD - Museum TV - NTV DE - Nat Geo Wild FR - National Geographic FR - The Nautical Channel - Nickelodeon FR - Nickelodeon Junior FR - Nickelodeon Teen FR - Novelas TV FR - NRJ12 - RMC Story - OCS Geants - OCS Max - OCS Pulp - Olympia TV - OSN TV Movies Hollywood - OSN TV Movies Premiere - OSN TV Kids - OSN TV Movies Action - OSN TV Ya Hala Aflam - Paramount Channel FR - Paris premiere - TV PINK EXTRA - PINK Film - Pink Music 1 - TV PINK PLUS - Piwi+ - Planète+ Aventure - Planete+ Crime - Planète+ - Playboy TV - Power Türk TV - Brazzers TV (ex. Private Spice) - Private TV - ProSieben - RAI UNO - RAI DUE - RAI TRE - RAI Education - RAI News 24 - RAI Storia - Reality Kings - RMC Découverte - RMC Sport 1 - RMC Sport 2 - RMC Sport Live 4 - RMC Sport Live 5 - RMC Sport Live 6 - RMC Sport Live 7 - RMC Sport Live 8 - RMC Sport Live 9 - RMC Sport Live 10 - RMC Sport Live 11 - RMC Sport Live 12 - RMC Sport Live 13 - RMC Sport Live 14 - RMC Sport Live 15 - RMC Sport Live 16 - RMC Sport Live 17 - RMC Sport Live 3 - RT Documentary - RTL Zwei - RTL DE - RTL Nitro - RTP3 - RTS SVET - Russia Today - Sat.1 - Seasons - Show Türk - SIC Internacional - Sky News - Star Movies MENA - Star World MENA - Stingray CMusic - Stingray Classica - Stingray iConcerts - Super RTL - SWR - TCM UK - TCM Cinéma - Télétoon+ FR - Télévision française 1 - TFX - TGCOM24 - TiJi - TMC - Trace Urban - Travel Channel - TRT 1 - TRT Çocuk - TRT Turk - TRT World - TV3 Catalunya - TV5 Monde - TV5Monde Asia - TV5Monde Europe - TV5Monde Style - TV1000 Global Kino - TV Breizh - TVE Internacional - Televisión de Galicia - MTV 80s - Vivid TV - Vosges Télévision - VOX - W9 - Warner TV FR - Welt - World Fashion Channel - ZDF - ZDFinfo - XXL - 3SAT - 4Fun Dance - 24 Kitchen - 24 ВЕСТИ - 360 TuneBox - Adult Channel - Al Jazeera - Al Jazeera Balkans - ALFA TV - AMC - Das Erste - Arena Esport - Arena Fight - Arena Sport 4 HR - Arena Sport 6 HR - Arena Sport 8 HR - Arena Sport 9 HR - Arena Sport 10 HR - ARTE DE - Alternativna televizija Banja Luka - Aurora TV - AXN - AXN Spin - B1 TV - B92 - BabyTV - Balkan trip - Balkan TV - Balkanika TV - BBC Earth - BBC First - BBC World News - Radio Televizija BIH - Bloomberg TV - Bloomberg Adria - Blue Hustler - Radio Televizija BN - BN music - BN 2 HD - Body in Balance - Boomerang - Brainz TV - Cartoon Network - Cartoonito CEE - Cartoonito UK - CBS Reality - CCTV 4 Europe - CGTN - CGTN Documentary - Cinemax - Cinemax 2 - CineStar Action&Thriller - CineStar Premiere 1 - CineStar Premiere 2 - CineStar TV1 - CineStar TV2 - CineStar TV Comedy Family - CineStar TV Fantasy - English Club TV - Croatian Music Channel - CNBC Europe - CNN Europe - Comedy Central DE - Nicktoons - Crime & Investigation Channel - Da Vinci Learning - Deluxe Music - Deutsche Welle English - Diadora TV - Discovery Animal Planet - Discovery Channel - Discovery Science - TLC - Disney Channel - Stingray Djazz - DMAX DE - DMC televizija - DM SAT - DocuBox HD - DOKU TV - Doma TV - Dorcel TV - Dorcel XXX - DOX TV - Dr. Fit HD - Sport 1 DE - Dubrovačka Televizija - DuckTV - Duna World - E! Entertainment - Epic Drama (CEE) - Erox HD - Eroxxx HD - Eurochannel - EuroNews - Eurosport - Eurosport 2 - Eurosport 2 DE - Eurosport 1 EMEA - Eurosport.com - Eurosport 1 DE - ExtraTV - Extreme Sports Channel - FACE TV - Fashion TV - FashionBox HD - Fast&FunBox HD - FightBox HD - SK Fight - Film4 - FilmBox Arthouse - FilmBox Extra RS - FilmBox Stars RS - FilmBox Premium RS - Fireplace - Food Network - STAR Channel - STAR Crime - STAR Life - STAR Movies - FOX NEWS - France24 - France 24 French - Radio Televizija Federacije BIH - Fuel TV - FullTV - FunBox UHD - GameHub HR - Gametoon HD - Ginx Esports TV - SK Golf - GP1 - Grand Televizija - Grand nostalgija - HappyTV - Hayat Folk - Hayat TV - Hayatovci - HBO - HBO3 - HBO2 - Home & Garden Television - The History Channel - History Channel 2 - HIT TV - HSE - HRT 1 - HRT 2 - HRT 3 - HRT 4 - HRT Int. - HUSTLER TV - ICT Business - Insta TV - Otvorena televizija - JimJam - Jugoton TV - K::CN 2 Music - Kabel 1 - Kabel eins Österreich - Kabel eins Doku - Kanal Rijeka - KBS World HD - KiKA - KinoTV - KitchenTV - Pop Max - Klape i Tambure TV - KLASIK HR - KLASIK - K::CN 1 Kopernikus - Laudato TV - Legend - Libertas TV - Lov i ribolov - LUXE TV - M1 Family - M1 FILM - M1 Gold - M6 - Maria Vision - MAXSport 1 - MAXSport 2 - Mediaset Italia - Mezzo - Mezzo Live HD - Motorvision TV - MG Movie Generation - MTV Europe - MTV 00s - Club MTV International - MTV Hits International - MTV Live HD - MTV 90s International - MyZen TV - N1 HR - N24 Doku - NTV DE - National Geographic - National Geographic Channel HD - Nat Geo Wild - The Nautical Channel - MrežaZG - Nat Geo Wild HD - Nick Junior - Nick Music - Nickelodeon - NOVA TV - Nova BH - Nova Plus Family - Nova Sport Srbija - O Kanal - OBN - One - ORF1 - ORF2 - ORF Sport Plus - Osječka televizija - OTV Valentino - Phoenix - Pikaboo - Pikaboo 2 - RTV PINK - RED tv - Vesti - Pink BH - Pink Classic - TV PINK EXTRA - Pink Fashion - PINK Film - Pink Folk 1 - Pink Folk 2 - Pink Ha Ha - Pink Hits 2 - Pink Kids - Pink Koncert - Pink Kuvar - Pink LOL - Pink M - Pink Music 1 - Pink Pedia - TV PINK PLUS - Pink Reality - Pink Serije - Pink Show - Pink Style - Pink Super Kids - PINK World - Pink World Cinema - PINK Zabava - Planet Earth - Plava televizija - Playboy TV - Poljoprivredna TV - Posavska Televizija - TV Zelina - Brazzers TV (ex. Private Spice) - Private TV - ProSieben - ProSieben MAXX - Prva KICK - Prva Plus - Prva Srpska TV - Prva World - QVC Deutschland - RAI UNO - RAI DUE - RAI TRE - RAI Education - RAI News 24 - RAI Sport 1 - RAI Storia - Reality Kings - RedLight HD - Россиᴙ 24 - Radiotelevizija Banovina - RTL - RTL 2 - RTL Zwei - RTL Adria - RTL DE - RTL Kockica - RTL Living - RTL Nitro - RTLup - RTRS - RTRS PLUS - RTS 1 - RTS 2 - RTS SVET - RTV Herceg-Bosne - Russia Today - Saborska TV - Samobor TV - Sat.1 - Sat.1 Österreich - Sat.1 Gold - Slavonskobrodska Televizija - SciFi - ServusTV - SK 4K - SK esports - Sky News - TV Slavonije i Baranje - RTV Slovenija 1 - RTV Slovenija 2 - RTV Slovenija 3 - sonnenklar.TV - Sony Channel - Great! Movies Action - Great! Movies Classic - Great! Movies +1 - SOS Plus - Sport Klub 4 - Sport Klub 5 - Sport Klub 6 - Sport Klub 7 - Sport Klub 8 - Sport Klub 9 - Sport Klub 10 - Sport Klub HD - Sport Klub 1 Hrvatska - Sport Klub 2 Hrvatska - Sport Klub 3 - Sport Klub 2 Srbija - Sportska Televizija - Stars TV PL - Stingray CMusic - Stingray Classica - Stingray iConcerts - Studio B - Super RTL - Supertennis HD - K::CN 3 Svet Plus - Timeless Drama Channel - Telecinco - Televizija Alfa - Telma - TiJi - Tiny Pop TV - TOGGO plus - Toon kids - Toxic Folk - Toxic Rap - Toxic TV - Trace Urban - Travel Channel - Trend TV - TV5 Monde - TV5Monde Europe - TV1000 Balkan - Arena Sport 1 HR - Arena Sport 2 HR - Arena Sport 3 HR - Televizija Dalmacija - TV Duga + SAT - TV Jadran - TV Šibenik - TV Vijesti - Televizija Zapad Zaprešić - Televizija Crne Gore MNE - TVE Internacional - UNA TV - DIVA (ex. Universal) - Varaždinska Televizija - Vavoom - MTV 80s - Viasat Explore CEE - Viasat History - Viasat Nature CEE - Plava vinkovačka TV - Vivid Red - Vivid TV - Vizion Plus - RT Vojvodina 1 - VOX - Welt - Woman HD - Zagrebačka Televizija - ZDF - ZDFinfo - ZDFneo - Zdrava televizija - Tring 3 Plus - 4Fun Dance - 4Fun Kids - 24 Kitchen - 360 TuneBox - Agro TV - Televizija Alfa - Al Jazeera Balkans - Al Jazeera - Alpha TV - AMC HU - Antena Europe - Apostol TV - Arena4 HU - Arena Esport - Arena Fight - Arena Sport 1 BiH - Arena Sport 1 RS - Arena Sport 1 SI - Arena Sport 1 Premium - Arena Sport 1 Premium SI - Arena Sport 1x2 - Arena Sport 2 RS - Arena Sport 2 SI - Arena Sport 2 Premium - Arena Sport 3 RS - Arena Sport 3 SI - Arena Sport 3 Premium - Arena Sport 4 RS - Arena Sport 4 SI - Arena Sport 5 HR - Arena Sport 5 RS - Arena Sport 6 RS - Arena Sport 7 HR - Arena Sport 7 RS - Arena Sport 8 RS - ARTE DE - ATV Avrupa - AXN HU - AXN - AXN Spin - B1 TV - Balkanika TV - Balkan trip - BBC Earth - BBC News Channel - BBC World News - TV Belle Amie - BN 2 HD - BN music - Boomerang - Brainz TV - Brazzers TV (ex. Private Spice) - BRIO - Canale 5 - CGTN Documentary - Cinemania TV - CineStar TV1 - CineStar TV RS - CineStar TV2 - CineStar Action&Thriller - CineStar Action&Thriller RS - CineStar TV Comedy Family - CineStar TV Fantasy - CineStar Premiere 1 - CineStar Premiere 2 - CNN Europe - CNN Türk TV - Comedy Central HU - Comedy Central Family HU - CoolTV - Cufo TV - D1 TV HU - Das Erste - Da Vinci Learning - Dexy TV - Discovery Channel HU - Discovery Science - Disney Channel HU - DM SAT - DocuBox HD - DOKU TV - Doma TV - Dr. Fit HD - Deutsche Welle English - English Club TV - Epic Drama (CEE) - Erox HD - Eroxxx HD - ETV HD - Eurochannel - Euro D - EuroNews - Eurosport - Eurosport 2 - Explorer Histori - Explorer Natyra - Explorer Shkence - FACE TV - FashionBox HD - Fashion TV - Fast&FunBox HD - FEM3 - FightBox HD - Pink Fight Network - Film4 HU - Film Aksion Digitalb - FilmBox Arthouse - Filmbox Stars HU - Film Cafe HU - Film Drame Digitralb - Film Klub - Film Mania HU - Film+ HU - Film Thriller Digitalb - Food Network - STAR Crime - France24 Arabic - France24 - France 24 French - Galaxy4 - Gametoon HD - Ginx Esports TV - Golica TV - Grand Televizija - Pink Ha Ha - Hayat Folk - Hayat Music - Hayatovci - Hayat Plus - HBO HU - Home & Garden Television - Hír TV - History Channel 2 - HRT 2 - HRT 4 - HRT Int. - IDJ World - INTV AL - Investigation Discovery - Italia 1 - Izaura TV - JockyTV - Jugoton TV - Kanal 3 Prnjavor - Kanal A, SLO - Kanal D TR - Kazbuka - KINO - KinoTV - KitchenTV - Klan Kosova - Klan Macedonia - KLASIK - Kohavision - Kurir TV - La7 - Laudato TV - Life TV HU - Living HD - Pink LOL - Lov i ribolov - M1 FILM - M1 Gold - Mediaset Italia - Mezzo - Mezzo Live HD - Minimax - MiniTV - Mozi plusz TV - Moziverzum - МРТ 1 - МРТ 2 - MTV Europe HU - MTV Live HD - Muzsika TV - N1 BA - N1 HR - National Geographic HU - Nat Geo Wild HU - RTL Nitro - Nova 24 TV - Nova S - Nova Plus Cinema - Nova Sport Srbija - Nova BH - TV NOVA Pula - Nova M - Nova World - Novosadska TV - OBN - O Kanal - One - OTO - PΛX - Pickbox TV - Pink Action - Pink BH - Pink Classic - Pink Comedy - Pink Crime & Mystery - PINK Family - Pink Fashion - Pink Folk 2 - Pink Hits - Pink Horror - Pink Koncert - Pink Kuvar - Pink M - Pink Movies - Pink and Roll - Pink Pedia - Pink Premium - Pink Romance - Pink SCI FI & Fantasy - Pink Serije - Pink Show - Pink Soap - Pink Style - Pink Super Kids - Pink Thriller - Pink Western - PINK World - Pink World Cinema - PINK Zabava - Planeta TV BG - Planet Earth - Planet TV SI - Playboy TV - POP TV - Power Türk TV - Prime - ProSieben Fun - Prva FILES - Prva KICK - Prva LIFE - Prva MAX - Prva TV Crna Gora - Prva Plus - Prva World - QVC STYLE DE - QVC Style - RadioBremen - RAI UNO - RAI DUE - RAI TRE - RAI Gulp - RAI News 24 - RAI Storia - real time - Rete 4 - Russia Today - RTK 1 - RTK 2 - RTL - RTL HU - RTL Crime - RTL Gold - RTL Ketto - RTL Living - RTL Passion - Super RTL - RTRS PLUS - RTS 2 - RTS 3 - RTS Drama - RTS Kolo - RTS Muzika - RTS Poletarac - RTS SVET - RTS Trezor - RT Vojvodina 1 - RT Vojvodina 2 - RTV21 - Россиᴙ 24 - SonLife - Show Türk - Сител - Magyar Sláger TV - Sorozat+ - Spektrum TV - Spektrum Home HU - Spíler1 TV - Spíler2 TV - Sport 1 DE - Sport 1 HU - Sport 2 HU - Sport Klub 1 Srbija - Sport Klub 1 Slovenija - Sport Klub 2 Srbija - Sport Klub 2 Slovenija - Sport Klub 3 - Sport Klub 4 - Sport Klub 5 - Sport Klub 6 - SK esports - SK Golf - Sport Klub HD - Sportska Televizija - Sport TV1.pt - Šport TV 1 - Sport TV2 - Šport TV 2 - Stars TV PL - Stingray iConcerts - Story4 - Studio B - Superstar TV - Super TV2 - TGCOM24 - The Fishing and Hunting - TLC - TOGGO plus - Trace Urban - Travel Channel - TRT Arapça - TRT Avaz - TV2 HU - TV2 Comedy - TV2 Kids - TV2 Séf - TV 3 Medias - TV4 HU - TV Arena - Televizija Crne Gore 1 - Televizija Crne Gore 2 - Televizija Crne Gore MNE - TV Galaksija - Телевизија Храм - K::CN 1 Kopernikus - K::CN 2 Music - K::CN 3 Svet Plus - TV Koper - TVN24 - TVN - TV Paprika HU - TVP Polonia - RTV Slovenija 1 - RTV Slovenija 2 - RTV Slovenija 3 - ТВ Сонце - TV Vijesti - Ülke TV - Viasat3 - Viasat 6 - Viasat Explore CEE - Viasat History - TV1000 Global Kino - Viasat Nature CEE - TV1000 Balkan - VOX - Woman HD - World Fashion Channel - Zagrebačka Televizija - ZDF - Zenebutik - 360 TuneBox - Das Erste - ARD-alpha - ATV Spirit HU - BabyTV - BBC News Channel - BBC World News - Bloomberg TV - Boomerang - Cartoonito CEE - CBS Reality - CCTV 4 Europe - Cinemax 2 HU - Cinemax HU - English Club TV - CNN Europe - Da Vinci Learning - Deutsche Welle English - Discovery Turbo Extra - DocuBox HD - DuckTV - Duna TV - Duna World - E! Entertainment - Epic Drama (CEE) - Eroxxx HD - Extreme Sports Channel - FashionBox HD - Fast&FunBox HD - Fehérvár TV - FightBox HD - FilmBox Arthouse - Filmbox Extra HU - Filmbox Family HU - FilmBox HU - Filmbox Premium HU - Food Network - France24 - Gametoon HD - HBO - HBO 2 HU - HBO 3 HU - Home & Garden Television - The History Channel - History Channel 2 - History Channel HU - Home & Garden Television UK - HUSTLER TV - Investigation Discovery - Investigation Discovery UK - Jazz TV HU - JimJam - KiKA - Magyar Televízió 1 - M2 Petőfi - Magyar Televízió 3 - M4 sport - M4 Sport Plus - m5 - ATV HU - Magyar Mozi TV - MATCH4 - MAX4 - Mediaset Italia - Mezzo - Mezzo Live HD - Minimax - Minimax HU - MTV 00s - Club MTV International - MTV Hits International - MTV Live HD - MTV 90s International - MyZen TV - Nick Junior PL - Nickelodeon Commercial - Nicktoons PL - Ozone Network - Paramount Network HU - Polonia1 - Private TV - ProSieben - RAI UNO - RAI TRE - RedLight HD - RTL Zwei - RTL DE - RTL Három - Sat.1 - Sky News - Viasat Film HU - Stingray Classica - Stingray iConcerts - Super RTL - TeenNick - Travel Channel - TVE Internacional - MTV 80s - Viasat 2 - Viasat History - Viasat Nature CEE - VOX - ZDF - 7 Gold - 20 Mediaset - Alma TV - Boing - Boing Plus - Boomerang IT - Canale 5 - Canale 7 - Cartoon Network IT - Cartoonito Italia - cielo - Cine34 - Class TV Moda - Comedy Central IT - CI Crime+ Investigation - DAZN IT - DeA Junior - DeA Kids - Discovery Channel IT - Discovery MAX.es - DMAX IT - Eurosport 2 IT - Eurosport IT - Focus TV - Food Network Italia - Frisbee - Gambero Rosso Channel - Giallo TV - History Channel IT - Inter Channel - Iris - Italia 1 - Italia 2 - K2 - La Cinque - La7 - La7 AU - La7d - Lazio Style Channel - Marco Polo TV - Mediaset Extra - Mediaset Italia - Mediaset Italia AU - Milan TV - MotorTrend - MTV Hits International - MTV Italia - MTV Music UK - MTV Music IT - MTV 90s International - National Geographic - Nick Junior IT - Nickelodeon IT - NOVE - Parole di Vita - Radio Italia TV - Radiofreccia - RAI UNO - RAI DUE - RAI TRE - RAI 3 Bis - RAI 4 - RAI 5 - RAI Education - RAI Gulp - RAI Italia Australia - RAI Movie - RAI News 24 - RAI Premium - RAI Sport 1 - RAI Storia - RAI World Premium - Rai Yoyo - real time - Rete 4 - Sky Arte - Sky Atlantic IT - Sky Caccia e Pesca - Sky Cinema Action IT - Sky Cinema Collection IT - Sky Cinema Comedy IT - Sky Cinema Drama IT - Sky Cinema Due - Sky Cinema Family IT - Sky Cinema Romance IT - Sky Cinema Suspense IT - Sky Cinema Uno - Sky Documentaries IT - Sky Investigation - Sky Nature IT - Sky Pesca e Caccia - Sky Serie - Sky Sport24 HD - Sky Sport 1 IT - Sky Sport 2 IT - Sky Sport 3 IT - Sky Sport F1 IT - Sky Sport MotoGP IT - Sky Sport Plus IT - Sky TG24 HD - Sky Uno - Sportitalia - Super! - Supertennis HD - Telequattro - Teletutto - TGCOM24 - Top Calcio 24 - Top Crime - TV8 IT - TV 2000 - Twenty Seven - VH1 IT - Warner TV IT - Tring 3 Plus - 24 Kitchen - 24 ВЕСТИ - 360 TV - ABC News Albania - Agro TV - Al Jazeera - Al Jazeera Balkans - ALFA TV - Алсат М - AMC - Anixe HD Serie - Das Erste - Arena Esport - Arena Fight - Arena Sport 1 MK - Arena Sport 1 Premium - Arena Sport 2 Premium - Arena Sport 3 Premium - Arena Sport 5 RS - ARTE DE - Alternativna televizija Banja Luka - AXN - AXN Spin - Balkan trip - Balkanika TV - Bang Bang - BBC First - BBC World News - Bloomberg TV - Bloomberg Adria - Radio Televizija BN - BN music - BN 2 HD - Boomerang - Brainz TV - Cartoon Network - Cartoonito CEE - CBS Reality - Cinemania TV - Cinemax - Cinemax 2 - CineStar Premiere 1 - CineStar Premiere 2 - CineStar TV2 - Croatian Music Channel - CNBC Europe - CNN Europe - Nicktoons - Crime & Investigation Channel - Cufo TV - Da Vinci Learning - Deluxe Music - Deutsche Welle English - DigitAlb T HD - Discovery Animal Planet - Discovery Channel - Discovery Science - TLC - Disney Channel - Disney Junior - DM SAT - DocuBox HD - Sport 1 DE - Elta 1 HD - E! Entertainment - Epic Drama (CEE) - Erox HD - Eroxxx HD - Euro Star - EuroNews - Euronews Albania - Eurosport - Eurosport 2 - Explorer Histori - Explorer Natyra - Explorer Shkence - Extreme Sports Channel - FACE TV - Fashion TV - FashionBox HD - Fast&FunBox HD - FightBox HD - SK Fight - Film Autor - Film Drame Digitralb - Film Klub Extra - Film Thriller Digitalb - FilmBox Arthouse - FilmBox Extra RS - FilmBox Stars RS - The Fishing and Hunting - Food Network - STAR Channel - STAR Crime - STAR Life - STAR Movies - France24 - France 24 French - Radio Televizija Federacije BIH - Ginx Esports TV - SK Golf - Grand Televizija - HappyTV - Hayat Folk - Hayat Music - Hayat Plus - Hayat TV - Hayatovci - HBO - HBO3 - HBO2 - The History Channel - HRT 1 - HRT 3 - HRT 4 - HUSTLER TV - IDJ World - JimJam - Tring Jolly HD - Jugoton TV - Junior TV - K::CN 2 Music - Kabel 1 - Kanal1 - Канал 5 - Канал 8 - Kanal 10 - Kanal D - Kerrang! - KiKA - TV Kiss Menada - KitchenTV - Klan Macedonia - KLASIK - K::CN 1 Kopernikus - Living HD - Lov i ribolov - M1 Family - M1 Film MK - M1 Gold MK - DigitAlb Melody TV - Mezzo - Minimax - МРТ 1 - МРТ 3 - МРТ 4 - МРТ 5 - MTM Televizija - MTV Europe - MTV 00s - Club MTV International - MTV Hits International - MTV Live HD - MTV 90s International - MUSE - My Music - N1 RS - Наша ТВ - National Geographic - National Geographic Channel HD - Nat Geo Wild - News 24 - Nick Junior - Nickelodeon Commercial - Nova S - Nova Sport Srbija - RTV Novi Pazar - O Kanal - OBN - Ora News - Pickbox TV MK - Pikaboo - Pink Music 1 - Pink Serije - Pink Show - Playboy TV - Brazzers TV (ex. Private Spice) - Private TV - ProSieben - Prva FILES - Prva KICK - Prva LIFE - Prva MAX - Prva Plus - Prva Srpska TV - Prva World - RAI UNO - RAI DUE - RAI TRE - RAI Sport 1 - RAI Storia - RedLight HD - Report TV - Россиᴙ 24 - RT Documentary - RTK 1 - RTK 2 - RTK 4 - RTL - RTL 2 - RTL Zwei - RTL DE - RTL Kockica - RTL Living - RTRS - RTS 1 - RTS 2 - RTS 3 - RTS SVET - RTSH 1 - RTV 21 Popullore - RTV Besa - RTV Slon Tuzla - Russia Today - Sat.1 - Scan TV - SciFi - Shenja TV - Сител - SK esports - Sky News - RTV Slovenija 1 - RTV Slovenija 2 - RTV Slovenija 3 - Tring Smile - SOS Plus - Sport Klub 1 Srbija - Sport Klub 4 - Sport Klub 5 - Sport Klub 6 - Sport Klub 7 - Sport Klub 8 - Sport Klub 9 - Sport Klub 10 - Sport Klub HD - Sport Klub 1 Hrvatska - Sport Klub 2 Hrvatska - Sport Klub 3 - Sport Klub 2 Srbija - Superstar 2 - Stinet - Stingray CMusic - Studio B - STV Folk - Super RTL - Superstar TV - K::CN 3 Svet Plus - Syri TV - Televizioni 7 - Timeless Drama Channel - Telma - Tera TV - Tip TV - Top Channel - Top News - Toxic Folk - Toxic Rap - Toxic TV - Trace Urban - Travel Channel - Travelxp - Tring Bunga Bunga - Tring Comedy - Tring Desire - Tring History - Tring Kids - Tring Life - Tring Action - Tring Novelas - Tring Planet - Tring Shqip - Tring Super - Tring Tring - Tring World - TRT 1 - TRT Turk - TV5 Monde - TV1000 Balkan - Arena Sport 2 RS - Arena Sport 3 RS - Arena Sport 4 RS - TV Duga + SAT - TV Dukagjini - TV Edo - Klan TV HD - TVM Ohrid - TV Sarajevo - Televizija Crne Gore MNE - DIVA (ex. Universal) - Valentino Music HD - Vavoom - MTV 80s - Viasat Explore CEE - Viasat History - Viasat Nature CEE - Vikom - Vizion Plus - VOX - Welt - Wness TV - World Fashion Channel - ZDF - Zdrava televizija - 1-2-3.tv - 2X2 - 3SAT - 4Fun Dance - 4Fun Kids - 4FunTV - Telewizja 13 - 13 Ulica - 360 TuneBox - Active Family - Adult Channel - Adventure HD - Al Jazeera - Ale Kino+ - ALFA TVP - AMC PL - Animal Planet PL - Anixe HD Serie - Antena HD - Das Erste - ARD-alpha - ARTE FR - ARTE DE - AstroTV - AXN Black PL - AXN PL - AXN Spin PL - AXN White PL - BabyTV PL - BBC Brit PL - BBC Earth PL - BBC First PL - BBC Lifestyle PL - BBC News Channel - BBC World News - Belsat TV - BFM TV - Bibel TV - Biznes24 - Bloomberg TV - Blue Hustler - Bollywood PL - Boomerang - CANAL+ 4K Ultra HD - CANAL+ Dokument - CANAL+ Family PL - CANAL+ Film PL - CANAL+ Premium +1 PL - Canal+ Domo - CANAL+ Kuchnia - CANAL+ Premium PL - CANAL+ Seriale PL - CANAL+ Sport 2 - CANAL+ Sport 3 - CANAL+ Sport 4 - CANAL+ Sport 5 - CANAL+ Sport PL - Cartoon Network PL - Cartoonito CEE - Cartoonito UK - CBeebies PL - CBS Europa PL - CBS Reality PL - CGTN Documentary - Cinemax 2 PL - Cinemax PL - CNBC Europe - CNews - CNN Europe - Comedy Central DE - Comedy Central PL - COSMO - Crime+Investigation PL - ČT 1 - ČT 2 - ČT 24 - ČT :D - ČT Sport - Da Vinci Learning PL - Deluxe Music - Deutsche Welle English - Deutsche Welle Deutsch - Disco Polo Music PL - Discovery Channel PL - Discovery Historia - Discovery Life PL - Discovery Science PL - Disney Channel PL - Disney Junior Polska - Disney XD PL - DIZI PL - Stingray Djazz - DMAX DE - DocuBox HD - Dorcel TV - Dorcel XXX - Sport 1 DE - DTX PL - Duck TV Plus - DuckTV - E-Sport HD - Eleven Sports 1 PL - Eleven Sports 2 PL - Eleven Sports 3 PL - Eleven Sports 4 PL - E! Entertainment - Epic Drama (Poland) - Eska Rock TV - Eska TV - Eska TV Extra - Еспресо TV - Eurochannel - EuroNews - Euronews FR - Eurosport 1 PL - Eurosport 2 PL - EWTN - EWTN PL - EXTASY TV - Extreme Sports PL - Fashion TV - FashionBox HD - Fast&FunBox HD - Fight Klub HD - FightBox HD - Filmax - FilmBox Action PL - FilmBox Arthouse - FilmBox Extra PL - FilmBox Family PL - FilmBox Premium PL - Fokus TV - Food Network PL - FX Comedy PL - FX PL - France24 - France 2 - France 3 - France 4 - France 5 - France Info - FunBox UHD - Gametoon HD - Ginx Esports TV - Golf Channel PL - HBO 2 PL - HBO 3 PL - HBO PL - Das Health TV - HGTV PL - History 2 Polska - History Channel Polska - HR Fernsehen - HSE - HSE24 Extra - Hustler HD - HUSTLER TV - Insight TV - Investigation Discovery PL - Italia 2 - iTVN - iTVN Extra - JimJam PL - K-TV Katholisches Fernsehen - Kabel 1 - Kabel eins Österreich - Kabel eins Doku - KiKA - Kino Polska - Kino Polska Muzyka - KinoTV PL - La Chaîne Info - LCP - Leo TV - Love Nature - MDR Sachsen-Anhalt - MDR Sachsen - MDR Thüringen - Metro TV - Mezzo - Mezzo Live HD - Minimini+ - Motowizja - MTV Europe - MTV 00s - Club MTV International - MTV DE - MTV Hits International - MTV Live HD - MTV PL - MTV 90s International - münchen.tv - Music Box Polska - MyZen TV - N24 Doku - NTV DE - National Geographic Wild PL - National Geographic PL - National Geographic People PL - The Nautical Channel - NDR Hamburg - NDR Mecklenburg-Vorpommern - NDR Niedersachsen - NDR Schleswig-Holstein - News 24 - Nick Junior PL - Nick Music - Nick DE - Nickelodeon PL - Nickelodeon Ukraine Pluto - Nicktoons PL - Niederbayern TV Deggendorf-Straubing - Novela TV - Novelas+ - Nowa TV - NRJ12 - NUTA GOLD - NUTA.TV HD - oe24.TV - One - ORF2 - Paramount Network PL - Parole di Vita - Phoenix - Planete+ PL - Playboy TV - POLO TV - Polonia1 - TVP Polonia - Polsat - Polsat 1 - Polsat 2 - Polsat Cafe - Polsat Comedy Central Extra - Polsat Doku - Polsat Film - Polsat Games - Polsat Music - Polsat News - Polsat News 2 - Polsat Play - Polsat Rodzina - Polsat Seriale - Polsat Sport - Polsat Sport Extra - Polsat Sport Fight - Polsat Sport News - Polsat Sport Premium 1 - Polsat Sport Premium 2 - Polsat Sport Premium 3 PPV - Polsat Sport Premium 4 PPV - Polsat Sport Premium 5 PPV - Polsat Sport Premium 6 PPV - Polsat Viasat Explore - Polsat Viasat History - Polsat Viasat Nature - Power TV PL - Brazzers TV (ex. Private Spice) - Private TV - ProSieben - PRO 7 Österreich - Proart - ProSieben MAXX - QVC Deutschland - Radio Italia TV - Radiofreccia - RBB Fernsehen Brandenburg - RBB Fernsehen Berlin - Reality Kings - Red Carpet TV PL - RedLight HD - RiC DE - Romance TV PL - RT Documentary - RTL Zwei - RTL DE - RTL Nitro - Russia Today - Sat.1 - Sat.1 Österreich - Sat.1 Gold - Kurier TV - SciFi - ServusTV - Sky Sport News DE - Sky News - sonnenklar.TV - Sportklub PL - SR Fernsehen - Stars TV PL - Stingray CMusic - Stingray Classica - Stingray iConcerts - Stopklatka - Sundance TV PL - Super Polsat - Super RTL - SWR Baden-Württemberg - tagesschau24 - TBN Polska - TeenNick - Tele 5 PL - Tele 5 DE - teleTOON+ - TLC PL - TOGGO plus - Top Kids Jr. PL - Top Kids PL - Trace Urban - Travel Channel PL - Travelxp - TRT Arapça - TTV - TV5Monde Europe - TV6 PL - TV 4 - TV Puls 2 - TV Puls PL - TV Regionalna.pl - TV Republika - TV Silesia - Toya TV - TV Trwam - TVC PL - TVE Internacional - TVN - TVN24 - TVN24 BiS - TVN 7 - TVN Fabuła - TVN Style - TVN Turbo - TVP3 Białystok - TVP3 Bydgoszcz - TVP3 Gdańsk - TVP3 Katowice - TVP3 Kielce - TVP3 Kraków - TVP3 Łódź - TVP3 Lublin - TVP3 Olsztyn - TVP3 Opole - TVP3 Poznań - TVP3 Rzeszów - TVP3 Szczecin - TVP3 Warszawa - TVP3 Wrocław - TVP 1 - TVP 2 - TVP 3 - TVP ABC - TVP ABC 2 - TVP Dokument - TVP HD - TVP Historia - TVP Historia 2 - TVP Info - TVP Kobieta - TVP Kultura - TVP Kultura 2 - TVP Nauka - TVP Regionalna - TVP Rozrywka - TVP Seriale - TVP Sport - TVP Wilno - TVP World - TVR PL - TVS - TVT PL - MTV 80s - Viasat Explore CEE - Vivid Red - VOX - VOX Music TV PL - Warner TV FR - Warner TV PL - Water Planet - WDR Fernsehen Köln - Welt - Welt der Wunder - WP TV - wPolsce PL - Wydarzenia 24 - Xtreme TV - ZDFinfo - ZDFneo - Zoom TV PL - 3SAT - 24 Kitchen - 24Kitchen PT - AMC Break ES - Al Jazeera - AMC Break PT - AMC Crime PT - AMC PT - Antena 3 - ARD-alpha - ARTE FR - ARTV - AXN Movies PT - AXN PT - AXN White PT - BabyTV ES - BBC News Channel - BBC World News - Benfica TV - Biggs - Bloomberg TV - Canal 11 PT - Canal Hollywood PT - Canal Panda - Canal Panda PT - Cartoon Network PT - Cartoonito PT - Casa e Cozinha - Caza y Pesca ES - CBS Reality - CCTV 4 Europe - CGTN - Cinemundo - CMTV - CNBC Europe - CNN Europe - CNN PT - Comedy Central DE - Deutsche Welle English - Deutsche Welle Deutsch - Discovery Channel PT - Discovery Science - TLC PT - Disney Channel PT - Disney Junior PT - Stingray Djazz - DocuBox HD - Sport 1 DE - Eleven Sports 1 PT - Eleven Sports 2 PT - Eleven Sports 3 PT - Eleven Sports 4 PT - Eleven Sports 5 PT - Eleven Sports 6 PT - E! Entertainment - Eurochannel - EuroNews - Eurosport 1 PT - Eurosport 2 PT - Fashion TV - FashionBox HD - Fast&FunBox HD - FightBox HD - FilmBox Arthouse - Food Network - Food Network UK - FOX Comedy PT - FOX Crime PT - FOX Life PT - FOX Movies PT - FOX NEWS - FOX PT - France24 - France 24 French - Fuel TV - FunBox UHD - Gametoon HD - Ginx Esports TV - Canal HISTÓRIA PT - Canal Hollywood - Insight TV - Investigation Discovery - JimJam - KiKA - LUXE TV - M6 - MCM Top - Mezzo - Mezzo Live HD - Motorvision TV - MTV 00s - MTV ES - MTV Live HD - MTV PT - Museum TV - MyZen TV - Nat Geo Wild ES - National Geographic Portugal - The Nautical Channel - Nick Jr. PT - Nickelodeon PT - Nickelodeon Ukraine Pluto - Odisseia - Phoenix - Playboy TV - ProSieben - RAI UNO - RAI DUE - RT Documentary - RTL DE - RTP1 - RTP2 - RTP3 - RTP Açores - RTP Africa - RTP Madeira - RTP Memória - RTP Internacional - Russia Today - Russia Today ES - Sat.1 - SIC - SIC Caras - SIC Internacional - SIC K - SIC Mulher - SIC Noticias - SIC Radical - Sky News - Sport TV+ - Sport TV1.pt - Sport TV2 - Sport TV3 - Sport TV4 - Sport TV5 - Sport TV6 - Stingray CMusic - Stingray iConcerts - Super RTL - Syfy PT - Toros TV - Trace Urban - Travel Channel - TV5 Monde - TV5Monde Europe - TV Cine Action - TV Cine Edition - TV Cine Emotion - TV Cine Top - TVE Internacional - TV Internacional - TVI Ficcao - TVI Reality - VOX - ZDF - ZDFneo - AgroTV RO - Al Jazeera - Al Jazeera Arabic English - AMC RO - Antena1 RO - Antena 3 CNN - Antena Stars RO - ARTE FR - ATV Spirit HU - Auto Motor und Sport - AXN Black RO - AXN RO - AXN Spin RO - AXN White RO - B1 TV RO - BabyTV - Balkanika TV - BBC Earth - BBC First - BBC World News - Bloomberg TV - Bollywood-Classic RO - Bollywood HD RO - Bollywood Film RO - Boomerang - Bucuresti TV RO - Canal 33 RO - Cartoon Network RO - Cartoonito CEE - CBS Reality - CCTV 4 Europe - CGTN - CGTN Documentary - Cinemaraton RO - Cinemax 2 RO - Cinemax RO - Cinethronix RO - CNBC Europe - CNN Europe - Comedy Central Extra BG - Comedy Central Family HU - Comedy Central HU - Comedy Central RO - CoolTV - Credo TV - Crime & Investigation Channel - Da Vinci Learning - Deutsche Welle English - Digi 24 - Digi Animal World RO - Digi Life RO - TV Digi Sport 2 - TV Digi Sport 3 - TV Digi Sport 4 - Digi World RO - Discovery Animal Planet - Discovery Channel - Discovery Science - TLC - Discovery Turbo Extra - Disney Channel RO - Disney Junior - Diva Universal RO - DocuBox HD - Dorcel TV - Dorcel XXX - DuckTV - Duna TV - Duna World - E! Entertainment - Epic Drama (CEE) - Eroxxx HD - Etno TV RO - EuroNews - Eurosport - Eurosport 1 INT - Eurosport 2 INT - Extreme Sports Channel - Fashion TV - Fast&FunBox HD - Favorit TV RO - FEM3 - FightBox HD - Film Cafe RO - Film Now RO - Film+ HU - FilmBox Extra RO - FilmBox Family RO - FilmBox Plus RO - FilmBox Premium RO - FilmBox RO - FilmBox Stars BG - Filmbox Stars HU - The Fishing and Hunting RO - Focus TV - Food Network - Galaxy4 - H!T Music Channel RO - Happy Channel RO - HBO 2 RO - HBO 3 RO - HBO RO - Home & Garden Television - Hír TV - The History Channel - HUSTLER TV - IDA RO - Inedit TV RO - Investigation Discovery - Izaura TV - JimJam - Kanal D RO - Kiss TV RO - Linkpress TV RO - Love Nature - Magyar Televízió 1 - M2 Petőfi - M4 sport - Magic TV BG - ATV HU - Magyar Sláger TV - Mediaset Italia - Medika TV RO - Mezzo - Mezzo Live HD - Minimax HU - Minimax RO - Moldova TV - Mooz Dance - Mooz HD RO - Mooz Hits RO - Mooz Ro RO - Motorvision HD RO - Motorvision TV - MTV 00s - Club MTV International - MTV Europe HU - MTV Hits International - MTV Live HD - MTV Music UK - MTV 90s International - Museum HD RO - Music Channel 1 RO - Muzsika TV - MyZen TV - National 24 Plus RO - Nasul TV RO - National Geographic People RO - Nat Geo Wild RO - National Geographic RO - National TV RO - The Nautical Channel - Nick Junior PL - Nickelodeon Commercial - Nicktoons PL - TV Paprika RO - Polsat Sport Extra - Prima Sport 1 RO - Prima Sport 2 RO - Prima Sport 3 RO - Prima Sport 4 RO - Prima Sport 5 RO - Prima TV RO - Prime - Brazzers TV (ex. Private Spice) - Private TV - Mozi plusz TV - Acasă - ProSieben - Pro Cinema RO - Acasă Gold - PRO TV Internațional - PRO ARENA RO - Profit RO - ProTV - RAI UNO - RAI TRE - Realitatea Plus RO - Reality Kings - România TV - RTL DE - RTL Gold - RTL Ketto - RTL HU - RTLup - Sat.1 - Sky News - Sorozat+ - Speranta TV - Spíler1 TV - Stingray CMusic - Stingray Classica - Stingray iConcerts - Story4 - Super RTL - Super TV2 - Taraf Tv RO - Timeless Drama Channel - TeenNick - TV1000 Russian Kino RO - Orange Sport 1 - Orange Sport 2 - Orange Sport 3 - Orange Sport 4 - Trace Urban - Travel Channel - Travel Mix RO - Travelxp - Trinitas HD RO - TV2 Comedy - TV2 HU - TV2 Kids - TV2 Séf - TV4 HU - TV5Monde Europe - TV1000 Balkan - TV1000 Global Kino - TV Digi Sport 1 - TV Paprika HU - TV SudEst - TVE Internacional - Televiziunea Româna 1 - Televiziunea Româna 2 - TVR3 RO - TVR Cluj RO - TVR Craiova RO - TVR Iasi RO - TVR Tg-Mures RO - TVR Timisoara RO - Televiziunea Româna International - UTV RO - MTV 80s - Viasat Explore CEE - Viasat History - Viasat Nature CEE - VTV RO - Warner TV RO - Zenebutik - Zu TV - 3SAT - 24 Kitchen - Agro TV - Al Jazeera - Al Jazeera Balkans - ALFA TV - Алсат М - AMC SI - Anixe HD Serie - Das Erste - Arena Esport - Arena Fight - ARTE FR - ARTE DE - Alternativna televizija Banja Luka - AXN - AXN Spin - B92 - BabyTV - Balkan trip - Balkanika TV - BBC Earth - BBC First - BBC World News - Radio Televizija BIH - BK TV - Bloomberg TV - Bloomberg Adria - Radio Televizija BN - BN music - BN 2 HD - БНТ 2 - Boomerang - Canale 5 - Cartoon Network - Cartoon Network DE - Cartoonito CEE - CBS Reality - CCTV 4 Europe - CGTN - Cinemax - Cinemax 2 - CineStar Action&Thriller - CineStar Premiere 1 - CineStar Premiere 2 - CineStar TV1 - CineStar TV2 - CineStar TV Comedy Family - CineStar TV Fantasy - English Club TV - Croatian Music Channel - CNBC Europe - CNN Europe - Comedy Central DE - Nicktoons - Crime & Investigation Channel - Da Vinci Learning - Discovery Animal Planet - Discovery Channel - Discovery Science - TLC - Discovery Turbo Extra - Disney Channel - Disney Junior - DM SAT - DocuBox HD - Dorcel TV - Dorcel XXX - Dr. Fit HD - Sport 1 DE - DuckTV - Duna TV - Duna World - Elta 1 HD - E! Entertainment - Epic Drama (CEE) - Erox HD - Eroxxx HD - Eurochannel - EuroNews - Eurosport - Eurosport 2 - EWTN - Exodus TV - Extreme Sports Channel - Fashion TV - FashionBox HD - Fast&FunBox HD - FightBox HD - SK Fight - FilmBox Arthouse - FilmBox Extra RS - FilmBox Stars RS - The Fishing and Hunting - Focus TV - STAR Crime SI - STAR Life SI - STAR Movies SI - STAR SI - France24 - France 2 - France 24 French - Radio Televizija Federacije BIH - GEA TV - Ginx Esports TV - SK Golf - Golica TV - Gorenjska televizija - Grand Televizija - HappyTV - Hayat Folk - Hayat Music - Hayat Plus - Hayat TV - Hayatovci - HBO - HBO3 - HBO2 - Home & Garden Television - The History Channel - History Channel 2 - HIT TV - HRT 1 - HRT 2 - HRT 3 - HRT 4 - HUSTLER TV - IDJ World - Investigation Discovery - Italia 1 - Otvorena televizija - JimJam - Jugoton TV - K::CN 2 Music - Kabel 1 - Канал 5 - Kanal A, SLO - Kanal D - Kanal Rijeka - KiKA - Klape i Tambure TV - KLASIK - K::CN 1 Kopernikus - KTV Ormož - Living HD - Lov i ribolov - LUXE TV - M2 Petőfi - TV Maribor - Mediaset Italia - Mezzo - Mezzo Live HD - Minimax - Minimax SI - Motorvision TV - МРТ 1 - МРТ 2 - МРТ 3 - MTV Europe - MTV 00s - Club MTV International - MTV DE - MTV Hits International - MTV Live HD - MTV 90s International - MyZen TV - N1 BA - N1 HR - N1 RS - NTV DE - Nat Geo Wild SI - National Geographic - National Geographic Channel HD - Nat Geo Wild - The Nautical Channel - NBA TV - MrežaZG - Net TV - Nat Geo Wild HD - Nick Junior - Nickelodeon - Nickelodeon Commercial - NOVA TV - NTV IC Kakanj - O Kanal - OBN - ORF1 - ORF2 - OTV Valentino - Pickbox TV SI - Pikaboo 2 - RTV PINK - RED tv - TV PINK EXTRA - PINK Family - Pink Fashion - PINK Film - Pink Folk 1 - Pink Folk 2 - Pink Kids - Pink Movies - Pink Music 1 - TV PINK PLUS - Pink Reality - Pink Serije - Pink Show - PINK World - PINK Zabava - Planet 2 - Planet Earth - Planet TV SI - Playboy TV - POP TV - Brazzers TV (ex. Private Spice) - Private TV - ProSieben - Prva Srpska TV - Prva World - Ptujska televizija - RAI UNO - RAI DUE - RAI TRE - RAI 3 Bis - RAI Education - RAI Gulp - RAI News 24 - RAI Sport 1 - RAI Storia - Rai Yoyo - Reality Kings - RedLight HD - Rete 4 - Россиᴙ 24 - RTK 1 - RTL - RTL 2 - RTL Zwei - RTL DE - RTL Kockica - RTL Living - RTRS - RTS 1 - RTS 2 - RTS SVET - RTV International - RTV Unsko-sanskog kantona - RTV Tuzlanskog Kantona - Russia Today - Sat.1 - SciFi - ServusTV - Sexation TV - Televizija skupnih internih programov - Сител - Sixx - SK 4K - SK esports - Sky News - RTV Slovenija 1 - RTV Slovenija 2 - RTV Slovenija 3 - SOS Plus - Sport Klub 4 - Sport Klub 5 - Sport Klub 6 - Sport Klub HD - Sportitalia - Sport Klub 3 - Sport Klub 2 Srbija - Stingray iConcerts - ŠTV3 - Super RTL - K::CN 3 Svet Plus - Televizija Alfa - Televizija AS - Telma - Top TV - Toxic TV - Trace Urban - Travel Channel - Travelxp - TV3 - TV 3 Medias - TV5 Monde - TV1000 Balkan - TV ATM - TV Celje - TV Duga + SAT - TV Galeja - TV Idea - TV Jadran - TV Sarajevo - TV Veseljak - TV Vijesti - Televizija Crne Gore 1 - Televizija Crne Gore 2 - Televizija Crne Gore MNE - DIVA (ex. Universal) - Varaždinska Televizija - Vaš kanal - Vavoom - MTV 80s - Viasat Explore CEE - Viasat History - Viasat Nature CEE - Vikom - VOX - VTV Studio - Welt - Woman HD - XXL - Zagrebačka Televizija - ZDF - Zdrava televizija - 1-2-3.tv - 21 Mix - A2 CNN - A Spor - ABC News Albania - Apollon TV - ART Sport 1 - ART Sport 2 - ART Sport 3 - ART Sport 4 - ART Sport 5 - ART Sport 6 - ArtDoku 1 - ArtDoku 2 - ArtKino 1 - ArtKino 2 - ArtKino 3 - ATV TR - BabyTV - Bang Bang - Beyaz TV - Bubble TV - Cartoon Network - Cartoonito CEE - City TV - Click TV - CNBC Europe - CNN Türk TV - Comedy Central Extra UK - Deluxe Music - DigitAlb T HD - Discovery Animal Planet - Discovery Channel - Discovery Turbo UK - Disney Channel - DMAX DE - Elrodi - Euronews Albania - FAX News - Film Autor - FilmBox Extra RS - FilmBox Stars RS - STAR Channel - STAR Life - HSE24 Extra - HSE24 Trend - Info24 Albania - INTV AL - Junior TV - K-Sport 5 - Kabel 1 - Kabel eins Doku - Kanal 7 - Kanal 10 - Kanal D - Kanali 7 - KiKA - Klan Music - Klan Plus - Kopliku TV - MCN TV - DigitAlb Melody TV - My Music - N24 Doku - NTV DE - National Geographic - National Geographic Channel HD - Nat Geo Wild - Ndihma e Klientit - Nickelodeon - Premium Channel - QVC 2 DE - QVC Deutschland - RAI 4 - RAI 5 - RAI Movie - RAI Premium - Rai Yoyo - RTLup - RTS 2 - RTSH Agro - RTSH Femijë - RTSH Film - RTSH Gjirokastra - RTSH Korça - RTSH Kuvend - RTV 21 Popullore - RTV Besa - RTV Ora - Scan TV - Shenja TV - Show TV - Sixx - SuperSport 7 AL - Syri TV - Syri Vizion - Timeless Drama Channel - Tele 5 DE - TGRT Belgesel - Tring Bizarre - Tring Bunga Bunga - Tring Collection - Tring Desire - Tring Kanal 7 - Tring Novelas - Tring Sport 4 HD - Tring Sport 5 HD - TV 7 - TV Dukagjini - VOX up - Welt - ZDFneo - Zico TV - Zjarr TV - MUSE - News 24 - Ora News - Report TV - RTSH 1 - RTSH 2 - RTSH 3 - RTSH 24 - RTSH Plus - RTSH Shkollë - RTSH Shqip - RTSH Sport - Tring Smile - Stinet - STV Folk - SuperSport 1 AL - SuperSport 2 AL - SuperSport 3 AL - SuperSport 4 AL - SuperSport 5 AL - SuperSport 6 AL - Tip TV - Top Channel - Top News - Tring Action - Tring Comedy - Tring History - Tring Jolly HD - Tring Kids - Tring Life - Tring Planet - Tring Shqip - Tring Sport 3 HD - Tring Sport News - Tring Super - Tring Tring - Tring World - Klan TV HD - Vizion Plus - 3SAT - 4Fun Dance - 4Fun Kids - 4FunTV - 24 Kitchen - 24 ВЕСТИ - 360 TuneBox - Agro TV - Al Jazeera - Al Jazeera Balkans - ALFA TV - AMC - AMC HU - ANIXE plus - Das Erste - Arena Sport 9 RS - Arena Sport 10 RS - Aurora TV - AXN - AXN Spin - B1 TV - B92 - BabyTV - Balkan trip - Balkan TV - Balkanika TV - BBC Earth - BBC News Channel - BBC World News - Bit TV - BK TV - BlicTV - Bloomberg TV - Bloomberg Adria - Radio Televizija BN - BN music - BN 2 HD - Boomerang - Brainz TV - Bravo Music - Cartoon Network - Cartoonito CEE - Cartoonito UK - CBS Reality - CCTV 4 Europe - CGTN - CGTN Documentary - Первый - Cinemania TV - Cinemax - Cinemax 2 - Cinemax 2 HU - Cinemax HU - CineStar Action&Thriller RS - CineStar Premiere 1 - CineStar Premiere 2 - CineStar TV2 - CineStar TV Comedy Family - CineStar TV Fantasy - CineStar TV RS - City TV - Class TV Moda - Croatian Music Channel - CNBC Europe - CNN Europe - Nicktoons - Comedy Central HU - Comedy Central UK - CoolTV - Crime & Investigation Channel - Da Vinci Learning - Dajto - Deluxe Music - Deutsche Welle English - Dexy TV - Digi 24 - Discovery Animal Planet - Discovery Channel HU - Discovery Channel - Discovery Science - TLC - Discovery Turbo Extra - Disney Channel - Disney Channel DE - Disney Channel HU - Disney Junior - DMAX DE - DM SAT - DocuBox HD - Dorcel TV - Dorcel XXX - DOX TV - Dr. Fit HD - Sport 1 DE - DuckTV - Duna TV - Duna World - E! Entertainment - Epic Drama (CEE) - Erox HD - Eroxxx HD - Eska TV Extra - Etno TV RO - Eurochannel - EuroNews - Euronews FR - EuroNews Srbija - Eurosport - Eurosport 2 - Eurosport 1 DE - Extreme Sports Channel - FACE TV - Fashion TV - FashionBox HD - Fast&FunBox HD - Favorit TV RO - FEM3 - FightBox HD - SK Fight - Film4 HU - Film Klub - Film Klub Extra - FilmBox Arthouse - FilmBox Extra RS - Filmbox Extra HU - FilmBox Stars RS - FilmBox Premium RS - Filmbox Premium HU - Filmbox Stars HU - Folklorika SK - Food Network - STAR Channel - STAR Crime - STAR Life - STAR Movies - FOX NEWS - France24 - France 24 French - Radio Televizija Federacije BIH - Gametoon HD - Golica TV - Grand Televizija - Grand nostalgija - HappyTV - Hayat Folk - Hayat Music - Hayat Plus - Hayat TV - HBO - HBO 2 HU - HBO3 - HBO 3 HU - HBO2 - HBO HU - Home & Garden Television - Hír TV - The History Channel - History Channel 2 - HSE - HRT 1 - HRT 2 - HRT 3 - HRT 4 - HUSTLER TV - Hype TV - IDJ World - Info24 Albania - Insajder TV - Insta TV - Investigation Discovery - Izaura TV - JimJam - JOJ Plus - JOJ TV - K1 TV - K::CN 2 Music - K-Sport 5 - Kabel 1 - Kabel eins Doku - Kanal 9 TV - Kanal 25 - KiKA - KitchenTV - KLASIK - K::CN 1 Kopernikus - Kurir TV - Lala TV - LifeTV SK - Lov i ribolov - Magyar Televízió 1 - M2 Petőfi - M4 sport - M4 Sport Plus - m5 - Mediaset Italia - Mezzo - Minimax - Minimax HU - Moj Happy Život - Moja Happy Muzika - Moja Happy Zemlja - Moje Happy Društvo - MTV Europe - MTV 00s - Club MTV International - MTV Hits International - MTV Live HD - MTV 90s International - Museum TV - Muzsika TV - N1 HR - N1 RS - N24 Doku - Nasa TV US - National Geographic - National Geographic Channel HD - National Geographic HU - National Geographic RS - Nat Geo Wild - NBA TV - Nat Geo Wild HD - Nick Junior - Nick Junior PL - Nick Music - Nickelodeon Commercial - Nicktoons PL - NOVA TV - Nova Max - Nova S - Nova Series - Nova Sport Srbija - RTV Novi Pazar - Now 90s - NTV 101 Sanski most - O Kanal - OBN - Ozone Network - Pickbox TV RS - Pikaboo - RTV PINK - RED tv - Vesti - Pink Action - Pink Thriller - Pink Crime & Mystery - Pink BH - Pink Classic - Pink Comedy - Pink Erotic 1 - Pink Erotic 2 - Pink Erotic 3 - Pink Erotic 4 - Pink Erotic 5 - Pink Erotic 6 - Pink Erotic 7 - Pink Erotic 8 - TV PINK EXTRA - PINK Family - Pink Fashion - Pink Fight Network - PINK Film - Pink Folk 1 - Pink Folk 2 - Pink Ha Ha - Pink Hits - Pink Hits 2 - Pink Horror - Pink Kids - Pink Koncert - Pink Kuvar - Pink LOL - Pink M - Pink Movies - Pink Romance - Pink SCI FI & Fantasy - Pink Music 1 - Pink and Roll - Pink Parada - Pink Pedia - TV PINK PLUS - Pink Premium - Pink Reality - Pink Serije - Pink Show - Pink Soap - Pink Style - Pink Super Kids - Pink Timeout - Pink Western - PINK World - Pink World Cinema - PINK Zabava - Planet Earth - Planet TV SI - Playboy TV - Brazzers TV (ex. Private Spice) - Private TV - ProSieben - ProSieben MAXX - Prva FILES - Prva KICK - Prva LIFE - Prva MAX - Prva Plus - Prva Srpska TV - Prva World - QVC Deutschland - RAI UNO - RAI DUE - RAI TRE - RAI Education - RAI News 24 - RAI Storia - Reality Kings - Россиᴙ 24 - RT Documentary - RTL - RTL Croatia World - RTL Gold - RTL Három - RTL Nitro - RTLup - RTRS - RTRS PLUS - RTS 1 - RTS 2 - RTS 3 - RTS Drama - RTS Klasika - RTS Kolo - RTS Muzika - RTS Nauka - RTS Poletarac - RTS SVET - RTS Trezor - RTS Život - RTSH 3 - RTSH 24 - RTSH Plus - RTSH Shkollë - RTV HIT Brčko - RTV International - RTV Pančevo - RTVS Dvojka - RTVS Jednotka - Russia Today - Sat.1 - Sat.1 Gold - SAT TV - SciFi - Sixx - SK1 BiH - SK 4K - Sky News - RTV Slovenija 1 - RTV Slovenija 2 - RTV Slovenija 3 - Sorozat+ - SOS Plus - Spektrum Home HU - Sport Klub 7 - Sport Klub 8 - Sport Klub 9 - Sport Klub 10 - Sremska TV - Superstar 2 - Stars TV PL - Stingray Classica - Stingray iConcerts - Studio B - Super RTL - Super TV2 - SuperSat TV - Superstar TV - K::CN 3 Svet Plus - TA3 SK - Tanjug Tačno - TBN Polska - Timeless Drama Channel - Tele 5 DE - Televizija 5 - Televizija Alfa - TLC DE - TOGGO plus - Top TV - Toxic Folk - Toxic Rap - Toxic TV - Trace Urban - Travel Channel - Travelxp - TV2 Séf - TV4 HU - TV5 Monde - TV5Monde Europe - TV1000 Balkan - TV Doma - TV Duga + SAT - Телевизија Храм - TV K23 - TV Markíza SK - RTV Most - TV Paprika HU - TV Ras - TV Vijesti - Televizija Crne Gore MNE - Televizija Doktor - TVR Cluj RO - TVR Craiova RO - TVR Iasi RO - TVR Tg-Mures RO - TVR Timisoara RO - Televiziunea Româna International - UNA TV - DIVA (ex. Universal) - Vavoom - MTV 80s - Viasat Explore CEE - Viasat History - Viasat Nature CEE - RT Vojvodina 1 - RT Vojvodina 2 - VOX - VOX up - Woman HD - Zagrebačka Televizija - ZDF - ZDFinfo - ZDFneo - 24 Kitchen - 24Kitchen TR - 360 TV - A2TV TR - a Haber - a News - A Para - A Spor - Al Jazeera - Al Jazeera Arabic English - ATV TR - BabyTV TR - BBC First - BBC World News - BBN Türk - beIN Box Office 2 TR - beIN Box Office 3 TR - beIN Box Office 1 TR - beIN GURME - beIN HOME & ENTERTAINMENT - beIN iZ - beIN Movies Premiere 2 - beIN Movies Premiere TR - beIN Movies Stars - beIN Movies Turk - beIN Series 1 TR - beIN Series 2 TR - beIN Series 3 TR - beIN Series 4 TR - beIN Sports 1 TR - beIN Sports 2 TR - beIN Sports 3 TR - beIN Sports 4 TR - beIN Sports Haber - beIN Sports MAX 1 TR - beIN Sports MAX 2 TR - beIN TR - Beyaz TV - Bloomberg TV - Bloomberg HT - Boomerang - Boomerang TR - Cartoon Network TR - Cbeebies - CGTN - CGTN Documentary - CNBC Europe - CNN Europe - CNN Türk TV - Nicktoons - Da Vinci Learning - Da Vinci Learning TR - Deutsche Welle English - Discovery Channel TR - Discovery Channel - Discovery Science - TLC - Disney Junior - Dizi Smart Max - Dizi Smart Premium - DMAX TR - DocuBox HD - Dream Türk - Ekotürk - Epic Drama (CEE) - Euro D - Euro Star - EuroNews - Eurosport 2 - Fashion TV - Fast&FunBox HD - FENERBAHÇE TV - FilmBox TR - FOX Turkey - France24 - France 24 French - FX Turkey - Haber Global - Habertürk - Habitat TV - The History Channel - Insight TV - Kanal 7 - KANAL 24 TR - Kanal D - Kanal D TR - Kral Pop TV - KRT TV - Love Nature - MCM FR - MCM Top - Mezzo - MinikaGO TR - MovieSmart Classic - MovieSmart Türk - MTV 00s - MTV Hits International - MTV Live HD - National Geographic Wild TR - National Geographic Turkey - NBA TV - Nick Junior - Number One Türk - NTV TR - Power TV - Power Türk TV - S Sport - S Sport 2 - Show Türk - Show TV - SinemaTV 2 - SinemaTV 1001 - SinemaTV 1002 - SinemaTV Aile - SinemaTV Aile 2 - SinemaTV Aksiyon - SinemaTV Aksiyon 2 - SinemaTV Komedi - SinemaTV Komedi 2 - SinemaTV Yerli - SinemaTV Yerli 2 - SinemaTV - Spor Smart - Spor Smart 2 - Sports TV - Star TV TR - TAY TV - TELE 1 - teve2 - TGRT Belgesel - TGRT EU - TGRT HABER - Tivibu Spor - Tivibu Spor 2 - Tivibu Spor 3 - Tivibu Spor 4 - Tivibu Spor 5 - TLC TR - Trace Urban - TRT 1 - TRT 2 - TRT Spor - TRT 4K - TRT Kurdî - TRT Arapça - TRT Avaz - TRT Belgesel - TRT Çocuk - TRT Diyanet - TRT EBA TV İlkokul - TRT EBA TV Lise - TRT EBA TV Ortaokul - TRT Haber - TRT Müzik - TRT Spor Yıldız - TRT Turk - TRT World - TV5 - TV100 TR - TV 2 TR - TV 8 TR - TV 8.5 - TV Net - Uçankuş TV - Ülke TV - Viasat Explore CEE - Viasat History - Viasat Nature CEE - Yaban TV - + + + Tring 3 Plus + 21 Mix + 360 TuneBox + ABC News Albania + Agro TV + Al Jazeera + Al Jazeera Balkans + Алсат М + AMC + Apollon TV + Arena Sport 5 RS + Arena Sport 6 RS + ARTE DE + ArtKino 1 + ArtKino 2 + ArtKino 3 + ATV Avrupa + ATV KS + ATV TR + AXN + AXN Spin + B92 + BabyTV + Balkanika TV + Bang Bang + BBC Earth + BBC World News + Beyaz TV + Radio Televizija BN + BN music + Boomerang + Bubble TV + Cartoon Network + Cartoonito CEE + CBS Reality + Cinemax + Cinemax 2 + CineStar Action&Thriller RS + CineStar TV Comedy Family + CineStar TV Fantasy + CineStar TV RS + Click TV + English Club TV + CNN Europe + Comedy Central DE + Crime & Investigation Channel + Cufo TV + Da Vinci Learning + Deutsche Welle English + Discovery Animal Planet + Discovery Channel + TLC + Disney Channel + DM SAT + DocuBox HD + Dorcel TV + Duck TV CZ + DuckTV + Elrodi + E! Entertainment + Epic Drama (CEE) + Eroxxx HD + Eurochannel + EuroNews + Euronews Albania + Explorer Histori + Explorer Natyra + Explorer Shkence + Extreme Sports Channel + FashionBox HD + Fast&FunBox HD + FAX News + FightBox HD + Film Aksion Digitalb + Film Drame Digitralb + Film Klub + Film Klub Extra + Film Thriller Digitalb + FilmBox Arthouse + FilmBox Extra RS + FilmBox Stars RS + FilmBox Premium RS + STAR Channel + STAR Crime + STAR Life + FOX Turkey + France24 + France 2 + Radio Televizija Federacije BIH + Gametoon HD + Habertürk + HappyTV + Hayat TV + HBO + HBO3 + HBO2 + The History Channel + History Channel 2 + HRT 1 + HRT 3 + HUSTLER TV + Investigation Discovery + JimJam + Tring Jolly HD + Junior TV + Kujtesa Sport 1 + Kujtesa Sport 2 + Kujtesa Sport 3 + Kujtesa Sport 4 + Kanal 6 + Kanal 7 + Kanal 10 + Kanal D + Kanali 7 + KitchenTV + Klan Kosova + Klan Plus + Kohavision + Kopliku TV + Living HD + Minimax + Motorvision HD RO + MTV Europe + MTV 00s + MTV Hits International + MTV Live HD + MTV 90s International + MUSE + My Music + NTV DE + National Geographic + Nat Geo Wild + NBA TV + News 24 + Nick Junior + Nickelodeon + Ora News + Pikaboo + RTV PINK + RED tv + Vesti + Pink Action + Pink Thriller + Pink Crime & Mystery + Pink Comedy + Pink Erotic 1 + Pink Erotic 2 + PINK Family + PINK Film + Pink Hits + Pink Kids + Pink LOL + Pink Romance + Pink SCI FI & Fantasy + Pink Music 1 + Pink and Roll + Pink Premium + Pink Reality + Pink Serije + Pink Super Kids + Pink Western + Pink World Cinema + Power TV + Power Türk TV + Brazzers TV (ex. Private Spice) + Private TV + ProSieben + ProSieben MAXX + Prva FILES + Prva KICK + Prva LIFE + Prva MAX + Prva Srpska TV + Prva World + RAI DUE + RAI TRE + RAI News 24 + Report TV + RT Documentary + RTK 1 + RTK 2 + RTK 4 + RTL + RTL 2 + RTL Zwei + RTL DE + RTRS + RTRS PLUS + RTS 1 + RTS 2 + RTS 3 + RTS Drama + RTS Kolo + RTS Muzika + RTS Poletarac + RTS SVET + RTS Trezor + RTS Život + RTV21 + RTV 21 Popullore + RTV Besa + Russia Today + Sat.1 + Sat.1 Gold + SciFi + Shenja TV + Show TV + Sky News + Tring Smile + SOS Plus + Tring Sport News + Star TV TR + Studio B + Super RTL + Superstar TV + Syri TV + Syri Vizion + Timeless Drama Channel + Tip TV + Top Channel + Travel Channel + Tring Bizarre + Tring Bunga Bunga + Tring Collection + Tring Comedy + Tring Desire + Tring History + Tring Kids + Tring Life + Tring Action + Tring Novelas + Tring Planet + Tring Shqip + Tring Super + Tring Tring + Tring World + TRT 1 + TRT 2 + TRT Spor + TRT Belgesel + TRT Çocuk + TRT Haber + TRT Müzik + TRT Turk + TRT World + TV5 Monde + TV1000 Balkan + TV 7 + TV 8 TR + Arena Sport 1 RS + Arena Sport 2 RS + Arena Sport 3 RS + Arena Sport 4 RS + TV Dukagjini + Телевизија Храм + Klan TV HD + RTV Most + DIVA (ex. Universal) + Vavoom + MTV 80s + Viasat Explore CEE + Viasat History + Viasat Nature CEE + Vizion Plus + RT Vojvodina 1 + RT Vojvodina 2 + VOX + Zico TV + Zjarr TV + 24 Kitchen + 360 TuneBox + Al Jazeera + Al Jazeera Balkans + AMC + Das Erste + ARTE FR + Auto Motor und Sport + BabyTV + Balkanika TV + BBC World News + BG-DNES + BHTV + Bloomberg TV + BN music + Body in Balance + Boomerang + BSTV BG + Cartoon Network + Cartoonito CEE + CBS Reality + CGTN + Первый + Cinemania BG + Cinemax 2 BG + Cinemax BG + Cinethronix RO + CNBC Europe + CNN Europe + Comedy Central BG + Nicktoons + Comedy Central Extra BG + Crime & Investigation Channel + Da Vinci Learning + Deluxe Music + Deutsche Welle English + Diema Sport 3 BG + Discovery Animal Planet + Discovery Channel + Discovery Science + TLC + Disney Junior BG + Stingray Djazz + DM SAT + DocuBox HD + Dorcel TV + Dorcel XXX + DSTV + DuckTV + Epic Drama (CEE) + Erox HD + Eroxxx HD + Eurochannel + EuroNews + Euronews FR + Eurosport 2 + Eurosport 1 BG + Extreme Sports Channel + F+ + Fashion TV + FashionBox HD + Fast&FunBox HD + FightBox HD + FilmBox Arthouse + FilmBox BG + FilmBox Extra BG + The Fishing and Hunting + The Fishing and Hunting RO + Fix & Foxi + France24 + France 2 + France 24 French + FunBox UHD + Gametoon HD + HBO 2 BG + HBO 3 BG + Home & Garden Television + The History Channel + History Channel 2 + HUSTLER TV + Investigation Discovery + Investigation Discovery BG + JimJam BG + K::CN 2 Music + КХЛ + KiKA + Love Nature + LUXE TV + MCM Top + Mezzo + Mezzo Live HD + Motorvision TV + MTV Europe + MTV 00s + Club MTV International + MTV Hits International + MTV Live HD + MTV 90s International + MyZen TV + Nasa TV US + Nick Junior + Nick Junior BG + Nickelodeon + Nickelodeon Commercial + Nickelodeon UK + Pickbox TV BG + Planeta TV BG + Playboy TV + Plovdivska Pravoslavna TV + Brazzers TV (ex. Private Spice) + ProSieben MAXX + RAI UNO + RAI News 24 + Reality Kings + RedLight HD + Ring TV + Россиᴙ 24 + RT Documentary + RTL Zwei + RTL DE + Russia Today + Sat.1 Gold + Sky News + Stingray iConcerts + Super RTL + SuperToons + Travel Channel + TRT Belgesel + TV5 Monde + TV5Monde Europe + TV1000 Balkan + TVE Internacional + Телевизия Стара Загора BG + MTV 80s + Viasat Explore CEE + Viasat History + Viasat Nature CEE + VOX + ZDF + 24Kitchen BG + AGRO TV BG + Alfa BG + AXN Black BG + AXN BG + AXN White BG + B1B Action TV BG + Barely Legal TV + Bloomberg TV BG + БНТ 1 + БНТ 2 + БНТ 3 + БНТ 4 + BOX TV BG + bTV BG + bTV Action + bTV Cinema + bTV Comedy + bTV Lady + Bulgaria on Air + City TV BG + Diema + Diema Family + Diema Sport 2 + Diema Sport + Disney Channel BG + EKids + Евроком + Фен Фолк ТВ + ФЕН ТВ + FilmBox Stars BG + HBO BG + Kino Nova + Magic TV BG + MAX Sport 1 BG + MAX Sport 2 BG + MAX Sport 3 BG + Max Sport 4 BG + Movie Star + National Geographic BG + Nat Geo Wild BG + Nova TV BG + Nova News + Nova Sport BG + Planeta Folk BG + Skat TV + Sport+ HD + STAR BG + STAR Crime BG + STAR Life BG + Travel TV BG + Travelxp + TV1 BG + TV Evropa BG + TV+ BG + Родина + Vivacom Arena + VTK BG + Wness TV + 24 Kitchen + 24 ВЕСТИ + 360 TuneBox + Adria TV + Adult Channel + Al Jazeera + Al Jazeera Arabic English + Al Jazeera Balkans + ALFA TV + AMC + Anixe HD Serie + ANIXE plus + Das Erste + Arena Esport + Arena Fight + Arena Sport 1 Premium + Arena Sport 1 Premium BiH + Arena Sport 1x2 + Arena Sport 2 BiH + Arena Sport 2 Premium + Arena Sport 2 Premium BiH + Arena Sport 3 BiH + Arena Sport 3 Premium + Arena Sport 3 Premium BiH + Arena Sport 4 BiH + Arena Sport 5 BiH + Arena Sport 6 RS + Arena Sport 6 BiH + Alternativna televizija Banja Luka + B92 + BabyTV + Balkan TV + Balkanika TV + BBC World News + BDC Televizija + Behar TV Sarajevo + Radio Televizija BIH + Bir TV + BlicTV + Bloomberg TV + Bloomberg Adria + Radio Televizija BN + BN music + Boomerang + Bravo Music + Cartoon Network + Cartoonito CEE + CBS Reality + CCTV 4 Europe + CGTN + Cinema TV + Cinemax + Cinemax 2 + City TV + English Club TV + Croatian Music Channel + CNBC Europe + CNN Europe + Nicktoons + Crime & Investigation Channel + Deutsche Welle English + Dexy TV + Digi 24 + Discovery Animal Planet + Discovery Channel + Discovery Science + TLC + Discovery Turbo Extra + Disney Channel + Disney Channel DE + DMAX DE + DM SAT + DocuBox HD + Dorcel TV + Dorcel XXX + DOX TV + Elta 1 HD + E! Entertainment + Erox HD + Eroxxx HD + Eska TV Extra + Etno TV RO + Eurochannel + EuroNews + Euronews FR + EuroNews Srbija + Eurosport + Eurosport 1 DE + Extreme Sports Channel + Fashion TV + FashionBox HD + Fast&FunBox HD + Favorit TV RO + FightBox HD + SK Fight + FilmBox Arthouse + FilmBox Extra RS + FilmBox Stars RS + FilmBox Premium RS + Food Network + STAR Channel + STAR Crime + STAR Life + STAR Movies + FOX NEWS + France24 + France 24 French + Radio Televizija Federacije BIH + GameHub HR + Gametoon HD + SK Golf + GP1 + Grand nostalgija + HappyTV + Hayat Folk + Hayat Plus + Hayat TV + HBO + HBO3 + HBO2 + Herceg TV + Historija TV + The History Channel + HIT TV + HSE + HRT 1 + HRT 3 + HUSTLER TV + Imperia TV + Insajder TV + Insta TV + Investigation Discovery + Izvorna TV + Otvorena televizija + JimJam + K1 TV + Kabel 1 + Kanal1 + Kanal 6 + Kazbuka + KiKA + Klape i Tambure TV + M1 Family + Maria Vision + Motorvision TV + MTV Europe + MTV 00s + Club MTV International + MTV Hits International + MTV Igman + MTV Live HD + MTV 90s International + MY TV + N1 RS + N24 Doku + Nasa TV US + National Geographic + National Geographic Channel HD + National Geographic RS + Nat Geo Wild + NBA TV + Neon TV + Nat Geo Wild HD + Nick Junior + Nick Music + Nickelodeon Commercial + NOVA TV + Nova Max + Nova Series + NTV 101 Sanski most + NTV IC Kakanj + O Kanal Music + O Kanal Plus + OBN + One + OSM TV + OTV Valentino + Pickbox TV RS + Pikaboo + RTV PINK + RED tv + Vesti + Pink Erotic 1 + Pink Erotic 2 + Pink Erotic 3 + Pink Erotic 4 + Pink Erotic 5 + Pink Erotic 6 + Pink Erotic 7 + Pink Erotic 8 + TV PINK EXTRA + PINK Film + Pink Folk 1 + Pink Hits 2 + Pink Kids + Pink Music 1 + Pink Parada + TV PINK PLUS + Pink Reality + Pink Timeout + Playboy TV + Poljoprivredna TV + Posavska Televizija + Premier League TV + Private TV + ProSieben + ProSieben MAXX + ProTV Tomislavgrad + Prva Srpska TV + QVC Deutschland + RAI UNO + RAI DUE + RAI TRE + RAI Education + RAI News 24 + Reality Kings + RT Documentary + RTL 2 + RTL Zwei + RTL DE + RTL Kockica + RTL Nitro + RTRS + RTS 1 + RTS Klasika + RTS Nauka + RTS Poletarac + RTS Život + RTSH 3 + RTSH 24 + RTSH Plus + RTSH Shkollë + RTV7 Tuzla + RTV BPK Goražde + RTV Herceg-Bosne + RTV HIT Brčko + RTV Lukavac + RTV Slon Tuzla + RTV Unsko-sanskog kantona + RTV Vogošća + RTV Zenica + RTV Tuzlanskog Kantona + Russia Today + Sat.1 + Sat.1 Gold + SciFi + Sevdah TV + SK1 BiH + SK esports + Sky News + Slobomir + Smart TV Tešanj + SOS Plus + Sport Klub 1 Srbija + Sport Klub 4 + Sport Klub 5 + Sport Klub 6 + Sport Klub 7 + Sport Klub 8 + Sport Klub 9 + Sport Klub 10 + Sport Klub HD + Sport Klub 1 Hrvatska + Sport Klub 3 + Sport Klub 2 Srbija + Superstar 2 + Super RTL + Supermedia Televizija + Tanjug Tačno + Tatabrada + TBN Polska + Timeless Drama Channel + Tele 5 DE + Televizija 5 + TLC DE + TNT Kids + Toxic Folk + Toxic Rap + Toxic TV + Travel Channel + Tropik TV + TRT 1 + TRT World + TV1 Mreža + TV Arena Bijeljina + Arena Sport 1 RS + Arena Sport 1 HR + Arena Sport 4 RS + Televizija Dalmacija + TV Duga + SAT + TV Istočno Sarajevo + TV Ras + TV Sarajevo + TV Vijesti + RTV Visoko + Televizija Crne Gore MNE + Televizija Doktor + TVR Cluj RO + TVR Craiova RO + TVR Iasi RO + TVR Tg-Mures RO + TVR Timisoara RO + UNA TV BiH + DIVA (ex. Universal) + Valentino Etno + Valentino Music HD + Vavoom + MTV 80s + Vikom + VOX + VOX up + Welt + ZDF + ZDFinfo + ZDFneo + 1-2-3.tv + 3SAT + 13th Street DE + A Spor + Al Jazeera + Animal Planet DE + Anixe HD Serie + ANIXE plus + Das Erste + ARD-alpha + ARTE DE + AstroTV + Alternativna televizija Banja Luka + ATV1 + ATV2 + Auto Motor und Sport + B92 + Bayerischen Fernsehen Nord + BBC World News + beIN Movies Premiere TR + Bergblick + BFM TV + Radio Televizija BIH + Bibel TV + Das Bild TV + Bloomberg TV + Blue Hustler + Radio Televizija BN + BN music + BonGusto + Boomerang DE + BR Fernsehen Süd + Cartoon Network DE + CCTV 4 Europe + CGTN + CGTN Documentary + Croatian Music Channel + CNBC Europe + CNews + CNN Europe + Comedy Central DE + Nicktoons + Crime & Investigation Channel + Crime & Investigation DE + Deluxe Music + Deutsche Welle English + Deutsches Musik Fernsehen + Deutsche Welle Deutsch + Discovery Channel DE + Disney Channel DE + DMAX DE + DM SAT + DocuBox HD + Dorcel TV + Dorcel XXX + DuckTV + Elta 1 HD + E! Entertainment + Erox HD + Euro Star + EuroNews + Eurosport + Eurosport 2 DE + Eurosport 1 DE + Extreme Sports Channel + Fashion TV + Fast&FunBox HD + Fight 24 + Fix & Foxi + Folx TV + France24 + France 24 French + FS1 AT + Radio Televizija Federacije BIH + Geo Television + Ginx Esports TV + Gute Laune TV + Habertürk + HappyTV + Hayat TV + Heimatkanal + HGTV DE + History Channel DE + HR Fernsehen + HSE + HSE24 Extra + HSE24 Trend + HRT 1 + HUSTLER TV + K-TV Katholisches Fernsehen + Kabel 1 + Kabel eins Österreich + Kabel Eins Classics + Kabel eins Doku + Kanal 7 + Kanal D TR + Kerrang! + KiKA + Kinowelt + KIT-TV + krone.tv + Love Nature + Marco Polo TV + MDR Fernsehen + MDR Sachsen-Anhalt + MDR Sachsen + MDR Thüringen + Melodie TV + Mezzo + Mezzo Live HD + Motorvision TV + MTV 00s + Club MTV International + MTV DE + MTV Hits International + MTV Live HD + MTV 90s International + N24 Doku + NTV DE + NatGeo Wild DE + National Geographic DE + The Nautical Channel + NDR Hamburg + NDR Mecklenburg-Vorpommern + NDR Niedersachsen + NDR Schleswig-Holstein + Nick Junior + Nick Music + Nick DE + Niederbayern TV Deggendorf-Straubing + NOVA TV + oe24.TV + One + ORF1 + ORF2 + ORF3 + ORF Sport Plus + OTV Valentino + Phoenix + TV PINK EXTRA + PINK Film + Pink Folk 1 + Pink Kids + Pink Music 1 + TV PINK PLUS + Pink Reality + Playboy TV + ProSieben + PRO 7 Österreich + ProSieben Fun + ProSieben MAXX + Prva Srpska TV + Puls 4 + QVC 2 DE + QVC Beauty + QVC Deutschland + QVC STYLE DE + RAI UNO + RAI DUE + RAI TRE + RAI Education + RAI News 24 + RAI Storia + RBB Fernsehen Brandenburg + RBB Fernsehen Berlin + RiC DE + Romance TV + RTL 2 + RTL Zwei + RTL Crime DE + RTL Croatia World + RTL DE + RTL Kockica + RTL Living DE + RTL Nitro + RTL Passion DE + RTLup + RTRS + RTS 1 + Russia Today + Sat.1 Emotions + Sat.1 + Sat.1 Österreich + Sat.1 Gold + Kurier TV + Schlager Deluxe + ServusTV + Show Türk + Sixx + Sixx AT + Sky 1 DE + Sky Atlantic DE + Sky Cinema Action DE + Sky Cinema Classics DE + Sky Cinema Family DE + Sky Krimi DE + Sky Cinema Thriller + Sky Sport 1 DE + Sky Sport 3 DE + Sky Sport 4 DE + Sky Sport 5 DE + Sky Sport 6 DE + Sky Sport 7 DE + Sky Sport 8 DE + Sky Sport 9 DE + Sky Sport 10 DE + Sky Bundesliga 1 + Sky Bundesliga 2 + Sky Bundesliga 3 + Sky Bundesliga 4 + Sky Bundesliga 5 + Sky Bundesliga 6 + Sky Bundesliga 7 + Sky Sport News DE + Sky Sports News + Sky News + sonnenklar.TV + Sony AXN + Sony Channel DE + Spiegel Geschichte + Curiosity Channel + Sport 1 plus DE + Šport TV 1 + Sportdigital Fußball + SR Fernsehen + SRF Info + SRF Zwei + Stingray Classica + Super RTL + SWR + SWR Baden-Württemberg + Syfy HD DE + tagesschau24 + Tele 5 DE + TLC DE + Warner TV Comedy DE + Warner TV Film DE + Warner TV Serie DE + TOGGO plus + TRT 1 + TRT Spor + TRT Belgesel + TRT Çocuk + TRT Müzik + TV 8 TR + Universal TV DE + MTV 80s + Vivid TV + VOX + W24 + WDR Fernsehen + WDR Fernsehen Köln + Welt + Welt der Wunder + ZDF + ZDFinfo + ZDFneo + 2M Monde + 3 Plus CH + 3SAT + 4 Plus + 4 Seven + 5 Plus + 5 Star + 6 plus + 6ter + 13th Street DE + 20 Mediaset + Action + Al Jazeera + Al Jazeera Arabic Arabic + Anixe HD Serie + ANIXE plus + Antena 3 + Das Erste + ARD-alpha + ARTE FR + ARTE DE + auftanken.TV + B1 TV + BBC1 + BBC2 + BBC3 + BBC4 + BBC News Channel + BBC Parliament + BBC World News + BFM Business + BFM TV + Bibel TV + Das Bild TV + Bloomberg TV + Bloomberg Quicktake + BN 2 HD + Boing + Boing Plus + Boomerang + Boomerang DE + BR Fernsehen Süd + C8 FR (ex. D8) + Canal 24H + Canal+ Kids FR + Canal J + Canal+ Cinéma(s) FR + Canal+ France + Canal Plus Sport FR + Canal+ Series FR + Canale 5 + CARAC4 + Cartoon Network DE + Cartoonito Italia + CBBC + Cbeebies + CCTV 4 Europe + CGTN + Challenge TV + Channel 4 + Channel 5 + Chérie 25 + cielo + Cine34 + Polar+ + Cine+ Premier FR + Children's ITV + Class TV Moda + English Club TV + Clubland TV + Croatian Music Channel + CNBC Europe + CNews + CNN Europe + CNN Türk TV + Comedy Central DE + Crime District + Crime & Investigation Channel + CStar FR + Dave + Deluxe Music + Deutsche Welle English + Deutsches Musik Fernsehen + Deutsche Welle Deutsch + Deutsche Welle Espanol + Discovery Channel DE + Discovery Channel IT + Disney Channel DE + DMAX IT + DMAX DE + DM SAT + Drama UK + Dream Türk + Sport 1 DE + Duna TV + Duna World + E4 + E! Entertainment + Euro D + Euro Star + EuroNews + Euronews FR + Eurosport + Eurosport 2 DE + Eurosport 2 FR + Eurosport 2 IT + Eurosport 1 DE + Eurosport FR + Eurosport IT + Fashion TV + Film4 + Fix & Foxi + Focus TV + Folx TV + Food Network Italia + Food Network UK + FOX NEWS + France24 + France 2 + France 3 + France 4 + France 5 + France 24 French + France Info + Frisbee + FunBox UHD + Giallo TV + Ginx Esports TV + Golf Channel Češka + Golf plus + Gulli + Habertürk + Halk TV + Hayat Folk + Hayat Music + Hayat Plus + Das Health TV + HGTV DE + HGTV IT + The History Channel + HR Fernsehen + HSE + HSE24 Extra + HSE24 Trend + HRT 1 + i24News FR + Iris + Italia 1 + Italia 2 + ITV1 + ITV2 + ITV3 + ITV4 + ITV Be + K2 + K-TV Katholisches Fernsehen + Kabel 1 + Kabel eins Doku + Kanal 9 TV + Kerrang! + KiKA + Kiss UK + Klan Kosova + Kohavision + L'Equipe + La Cinque + La7 + La7d + La Télé + La Chaîne Info + Magyar Televízió 1 + M2 Petőfi + M4 sport + m5 + M6 + Mangas + MCM FR + MDR Fernsehen + Mediaset Extra + Mediaset Italia + Mezzo + Mezzo Live HD + More4 + More Than Sports TV + Motorvision TV + Motorvision TV France + MTV DE + 5Select + N24 Doku + NTV DE + Nasa TV US + National Geographic DE + The Nautical Channel + NDR Hamburg + Nick DE + NOVE + Now 90s + NRJ12 + RMC Story + OBN + One + ORF1 + ORF2 + ORF3 + PBS America + Phoenix + Pick TV + TV PINK EXTRA + PINK Film + Pink Folk 1 + Pink Kids + Pink Koncert + Pink Music 1 + TV PINK PLUS + Pink Reality + Power Türk TV + ProSieben + ProSieben MAXX + Puls 8 + Radio Ticino Channel HD + RadioBremen + RAI UNO + RAI DUE + RAI TRE + RAI 4 + RAI 5 + RAI Education + RAI Gulp + RAI Movie + RAI News 24 + RAI Premium + RAI Sport 1 + RAI Storia + Rai Yoyo + RBB Fernsehen Berlin + real time + Rete 4 + RiC DE + RMC Découverte + Rocket Beans TV + Romance TV + CARAC1 + RSI La 1 + RSI La 2 + RTK 1 + RTL Zwei + RTL Crime DE + RTL DE + RTL Living DE + RTL Nitro + RTL Passion DE + RTLup + RTP3 + RTP Internacional + RTRS + RTS 2 Suisse + RTS SVET + RTS Un + Russia Today + S1 CH + S4C + Sat.1 + Sat.1 Gold + ServusTV + Show Türk + SIC + Sixx + Sky Atlantic + Sky Krimi DE + Sky Sport 1 DE + Sky Sports News + Sky TG24 HD + Sky News + SonLife + Spiegel Geschichte + Sport 1 plus DE + Sportitalia + SR Fernsehen + SRF 1 + SRF Info + SRF Zwei + Stingray Classica + Stingray iConcerts + Super! + Super RTL + Supertennis HD + K::CN 3 Svet Plus + Swiss1 TV + SWR + Syfy HD DE + tagesschau24 + TELE 1 + Tele 5 DE + TeleBärn + Tele Zürich + Télévision française 1 + TFX + TGCOM24 + TGRT HABER + TiJi + TLC DE + TMC + Warner TV Comedy DE + Warner TV Film DE + Warner TV Serie DE + TOGGO plus + Top Crime + Trace Urban + TRT Spor + TRT Kurdî + TRT Belgesel + TRT Çocuk + TRT Müzik + TRT Turk + TV5 Monde + TV5Monde Europe + TV8 IT + TV24 + TV25 + TV 8 TR + TV 2000 + TV Dukagjini + TVE Internacional + Televisión de Galicia + TV Internacional + TVM3 + Twenty Seven + DIVA (ex. Universal) + VH1 IT + Viaplay Xtra + VOX + VOX up + W9 + WDR Fernsehen + Welt + Welt der Wunder + Wetter + Yesterday + Zagrebačka Televizija + ZDF + ZDFinfo + ZDFneo + 1-2-3.tv + 3SAT + 13th Street DE + Al Jazeera + Al Jazeera Arabic English + Al Jazeera Balkans + Animal Planet DE + Anixe HD Serie + ANIXE plus + Das Erste + ARD-alpha + ARTE DE + AstroTV + Alternativna televizija Banja Luka + auftanken.TV + Baden TV + Balkanika TV + Bayerischen Fernsehen Nord + BBC World News + beIN iZ + beIN Movies Premiere TR + Bergblick + Bibel TV + Das Bild TV + Bloomberg TV + BonGusto + Boomerang DE + BR Fernsehen Süd + Canal 24H + Cartoon Network DE + CGTN + CGTN Documentary + Croatian Music Channel + CNBC Europe + CNN Europe + Comedy Central DE + Nicktoons + Crime & Investigation DE + DAZN 1 DE + DAZN 2 DE + Deluxe Music + Deutsche Welle English + Deutsches Musik Fernsehen + Deutsche Welle Deutsch + Discovery Channel DE + Disney Channel DE + DMAX DE + DM SAT + Sport 1 DE + Edge Sport + eSports 1 + Euro Star + EuroNews + Eurosport 2 DE + Eurosport 1 DE + EWTN + Extreme Sports Channel + Fashion TV + Fast&FunBox HD + Fight 24 + Fix & Foxi + Folx TV + France24 + France 2 + France 3 + France 4 + France 5 + France 24 French + Geo Television + Ginx Esports TV + Goldstar + Gute Laune TV + Habertürk + Halk TV + Hamburg 1 + Das Health TV + Heimatkanal + Home & Garden Television + HGTV DE + History Channel DE + HR Fernsehen + HSE + HSE24 Extra + HSE24 Trend + HRT 1 + Insight TV + Jukebox + K-TV Katholisches Fernsehen + Kabel 1 + Kabel Eins Classics + Kabel eins Doku + Kanal 7 + KiKA + Kinowelt + Klan Kosova + Kohavision + Marco Polo TV + MDR Fernsehen + MDR Sachsen-Anhalt + MDR Sachsen + MDR Thüringen + Mediaset Italia + Melodie TV + More Than Sports TV + Motorvision TV + MTV DE + MTV Live HD + münchen.tv + N24 Doku + NTV DE + NatGeo Wild DE + National Geographic DE + NDR Hamburg + NDR Mecklenburg-Vorpommern + NDR Niedersachsen + NDR Schleswig-Holstein + Nick Junior + Nick DE + Niederbayern TV Deggendorf-Straubing + One + ORF3 + ORF Sport Plus + Phoenix + TV PINK EXTRA + PINK Film + Pink Folk 1 + Pink Music 1 + TV PINK PLUS + Power Türk TV + ProSieben + ProSieben MAXX + QVC 2 DE + QVC Deutschland + RAI UNO + RAI DUE + RAI TRE + RAI News 24 + RAI Storia + RBB Fernsehen Brandenburg + RBB Fernsehen Berlin + RiC DE + Rhein Neckar Fernsehen + Rocket Beans TV + Romance TV + RTK 1 + RTL Zwei + RTL Crime DE + RTL DE + RTL Living DE + RTL Nitro + RTL Passion DE + RTLup + RTS SVET + Russia Today + Sat.1 Emotions + Sat.1 + Sat.1 Gold + Schlager Deluxe + ServusTV Deutschland + Show Türk + Silverline Movie Channel + Sixx + Sky 1 DE + Sky Atlantic DE + Sky Cinema Action DE + Sky Cinema Best Of DE + Sky Cinema Classics DE + Sky Cinema Comedy DE + Sky Cinema Family DE + Sky Krimi DE + Sky Cinema Premieren + Sky Sport 1 DE + Sky Sport 3 DE + Sky Sport 4 DE + Sky Sport 5 DE + Sky Sport 6 DE + Sky Sport 7 DE + Sky Sport 8 DE + Sky Sport 9 DE + Sky Sport 10 DE + Sky Bundesliga 1 + Sky Bundesliga 2 + Sky Bundesliga 3 + Sky Bundesliga 4 + Sky Bundesliga 5 + Sky Bundesliga 6 + Sky Bundesliga 7 + Sky Sport Mix DE + Sky Sport News DE + Sky Sport Premier League DE + Sky News + sonnenklar.TV + Sony AXN + Sony Channel DE + Spiegel Geschichte + Curiosity Channel + Sport 1 plus DE + Sportdigital Fußball + SR Fernsehen + Stingray Classica + Super RTL + SWR + SWR1 Baden-Württemberg + SWR Baden-Württemberg + Syfy HD DE + tagesschau24 + Tele 5 DE + TGRT EU + TLC DE + Warner TV Comedy DE + Warner TV Film DE + Warner TV Serie DE + TOGGO plus + TRT Turk + TV5 Monde + TV5Monde Europe + TV8 IT + TV Now DE + TVE Internacional + TVS + MTV 80s + VOX + VOX up + WDR Fernsehen + WDR Fernsehen Köln + Welt + Welt der Wunder + Wetter + Wir 24 + ZDF + ZDFinfo + ZDFneo + 4Music + 4 Seven + 5ACTION + 5 Star + 5 USA + Al Jazeera Arabic English + Alibi + AMC UK + Animal Planet UK + At The Races + BabyTV + BBC1 + BBC1 Northern Ireland + BBC One Scotland + BBC2 + BBC3 + BBC4 + BBC Alba + BBC Earth + BBC News Channel + BBC 1 Wales + BBC Parliament + BBC 2 Northern Ireland + BBC Two Wales + BBC World News + Boomerang + Boomerang UK + TNT Sports 1 + TNT Sports 2 + TNT Sports 4 + TNT Sports Europe + TNT Sports Ultimate + Cartoon Network + Cartoon Network UK + Cartoonito UK + CBBC + Cbeebies + CBS Drama UK + CBS Reality UK + Challenge TV + Channel 4 + Channel 4 +1 + Channel 5 + Channel S + Capital XTRA + Children's ITV + English Club TV + Clubland TV + Comedy Central Extra UK + Comedy Central UK + Create and Craft + Crime and Investigation UK + Dave + Deutsche Welle English + Discovery Channel UK + Discovery History UK + Discovery Science UK + Discovery Turbo UK + DMAX UK + Drama UK + E4 + E4 Extra + Eden UK + Edge Sport + BT Sport ESPN + EuroNews + Eurosport 2 UK + Eurosport 1 EMEA + Eurosport.com + Eurosport UK + Extreme Sports Channel + Film4 + Food Network UK + Gems TV + Ginx Esports TV + Great! romance + History 2 UK + History Channel UK + Home & Garden Television UK + Ideal Home Shopping + Inspiration TV + Investigation Discovery UK + ITV1 + ITV2 + ITV2+1 + ITV3 + ITV4 + ITV Be + Jewellery Channel + JML Direct + Kerrang! + Ketchup TV + Kiss UK + Pop Max + London Live + Love Nature + Magic + Manchester TV + More4 + MotorTrend + Movies 24 + MTV 80s UK + MTV 90s UK + MTV Hits UK + MTV Live HD + MTV Music UK + MTV UK + 5Select + Nat Geo Wild UK + National Geographic Channel UK + Nick Jr. Too UK + Nick Junior UK + Nick Music + Nickelodeon UK + Nicktoons UK + Now 70s + Now 80s + Now 90s + PBS America + Pick TV + Pop UK + Premier Sports 1 + Premier Sports 2 + Quest TV + Quest Red + QVC Beauty + QVC Style + Really UKTV + Revelation TV + S4C + Sky Arts + Sky Atlantic + Sky Cinema Drama + Sky Cinema Greats + Sky Cinema Hits + Sky Cinema Select + Sky Cinema SF Horror + Sky Crime + Sky Documentaries + Sky Kids + Sky Witness + Sky Max + Sky Cinema Action + Sky Cinema Comedy + Sky Cinema Thriller + Sky Family + Sky Cinema Premiere + Sky Nature UK + Sky Replay + Sky Showcase + Sky Sports Main Event + Sky Sports Cricket + Sky Sports Action + Sky Sports Golf + Sky Sports Premier League + Sky Sports Arena + Sky Sports F1 + Sky Sports Football + Sky Sports Mix + Sky Sports News + Sky Sports Racing + Sky News + Smithsonian Channel + SonLife + Sony Channel + Great! Movies + Great! Movies Action + Great! Movies Classic + Great! Movies +1 + STV UK + Sky Sci-Fi + Talking Pictures TV + TCM UK + That's TV + The Box + Trace Vault UK + Tiny Pop TV + TLC UK + Together + Trans World Radio + UK Gold + Ulster TV + Viaplay Xtra + W UK + Warner TV IT + Yesterday + 4Music + 4 Seven + 5 Star + Al Jazeera + Al Jazeera Arabic English + Alibi + Animal Planet UK + Arise News + At The Races + BBC1 + BBC1 Northern Ireland + BBC One Scotland + BBC2 + BBC3 + BBC4 + BBC Alba + BBC News Channel + BBC Parliament + Bloomberg TV + Boomerang + Boomerang UK + TNT Sports 1 + TNT Sports 2 + TNT Sports 4 + TNT Sports Europe + TNT Sports Ultimate + Cartoon Network + Cartoon Network UK + Cartoonito UK + CBBC + Cbeebies + CBS Drama UK + CBS Reality UK + CGTN + Challenge TV + Channel 4 + Channel 4 +1 + Channel 5 + Children's ITV + CNBC Europe + CNN Europe + Comedy Central Extra UK + Comedy Central UK + Crime and Investigation UK + Cúla4 + Dave + Daystar + Discovery Channel UK + Discovery History UK + Discovery Science UK + Discovery Turbo UK + DMAX UK + DocuBox HD + Drama UK + E4 + E4 Extra + Eden UK + EuroNews + Eurosport 2 UK + Eurosport UK + Extreme Sports Channel + Fashion TV + FashionBox HD + Fast&FunBox HD + Film4 + FilmBox Arthouse + Food Network UK + France24 + Gametoon HD + Gems TV + Ginx Esports TV + The History Channel + History Channel 2 + Home & Garden Television UK + Ideal Home Shopping + Inspiration TV + Investigation Discovery UK + ITV1 + ITV2 + ITV2+1 + ITV3 + ITV4 + ITV Be + JML Direct + Kerrang! + Kiss UK + Pop Max + Legend + Magic + More4 + Movies 24 + MTV 80s UK + MTV 90s UK + MTV Hits UK + MTV Music UK + MTV UK + 5Select + Nat Geo Wild UK + National Geographic Channel UK + Nick Junior UK + Nickelodeon UK + Nicktoons UK + Oireachtas TV + Paramount Network UK + PBS America + Pick TV + Pop UK + Premier Sports 1 + Premier Sports 2 + Quest TV + Quest Red + QVC Beauty + QVC Style + Really UKTV + Revelation TV + RTÉ One + RTÉ2 + RTÉ News + RTÉjr + Russia Today + S4C + Sky Arts + Sky Atlantic + Sky Crime + Sky Kids + Sky Witness + Sky Max + Sky Cinema Action + Sky Cinema Comedy + Sky Cinema Thriller + Sky Family + Sky Nature UK + Sky Showcase + Sky Sports Racing + Sky News + Smithsonian Channel + SonLife + Sony Channel + Great! TV + Great! Movies + Great! Movies Action + Great! Movies Classic + Great! Movies +1 + Sky Sci-Fi + Talking Pictures TV + TCM UK + TG4 Ireland + The Box + Tiny Pop TV + TLC UK + Together + Virgin Media One + UK Gold + Ulster TV + Viaplay Xtra + Virgin Media Four + Virgin Media More + Virgin Media Three + Virgin Media Two + W UK + WION + Yesterday + Tring 3 Plus + 3SAT + 4Fun Dance + 4Fun Kids + 24 Kitchen + 24 ВЕСТИ + 360 TuneBox + A2 CNN + ABC News Albania + Adria TV + Agro TV + Al Jazeera Balkans + AMC + Anixe HD Serie + Das Erste + Arena Esport + Arena Fight + Arena Sport 1 Premium + Arena Sport 1x2 + Arena Sport 2 Premium + Arena Sport 3 Premium + Arena Sport 5 RS + Arena Sport 6 RS + Arena Sport 7 RS + Arena Sport 8 RS + Arena Sport 9 RS + Arena Sport 10 RS + ARTE DE + AXN + AXN Spin + B92 + BabyTV + Balkan trip + Balkan TV + Bang Bang + BBC Earth + BBC World News + BlicTV + Bloomberg Adria + Radio Televizija BN + BN music + BN 2 HD + Boomerang + Brainz TV + Bravo Music + Cartoon Network + Cartoonito CEE + CBS Reality + CCTV 4 Europe + CGTN + CGTN Documentary + Cinemax + Cinemax 2 + CineStar Premiere 1 + CineStar Premiere 2 + CineStar TV2 + CineStar TV Comedy Family + CineStar TV Fantasy + Click TV + Croatian Music Channel + CNN Europe + Nicktoons + Crime & Investigation Channel + Cufo TV + Deutsche Welle English + DigitAlb T HD + Discovery Animal Planet + Discovery Channel + Discovery Science + TLC + Discovery Turbo Extra + Disney Channel + Disney Junior + DM SAT + DocuBox HD + Dorcel TV + Dorcel XXX + DOX TV + Dr. Fit HD + DuckTV + Elrodi + Elta 1 HD + E! Entertainment + Epic Drama (CEE) + Erox HD + Eroxxx HD + Eurochannel + EuroNews + Euronews Albania + EuroNews Srbija + Eurosport + Eurosport 2 + Explorer Histori + Explorer Natyra + Explorer Shkence + Extreme Sports Channel + FACE TV + Fashion TV + FashionBox HD + Fast&FunBox HD + FAX News + FightBox HD + SK Fight + Film Aksion Digitalb + Film Autor + Film Drame Digitralb + Film Klub + Film Thriller Digitalb + FilmBox Arthouse + FilmBox Extra RS + FilmBox Stars RS + FilmBox Premium RS + Food Network + STAR Channel + STAR Crime + STAR Life + STAR Movies + FOX NEWS + France24 + France 3 + France 24 French + Radio Televizija Federacije BIH + Gametoon HD + SK Golf + Golica TV + Grand Televizija + Grand nostalgija + HappyTV + Hayat Folk + Hayat Music + Hayat Plus + Hayat TV + Hayatovci + HBO + HBO3 + HBO2 + Home & Garden Television + The History Channel + History Channel 2 + HRT 1 + HRT 2 + HRT 3 + HRT 4 + HUSTLER TV + IDJ World + Insajder TV + Insta TV + Investigation Discovery + Tring Jolly HD + Jugoton TV + Junior TV + K1 TV + K::CN 2 Music + Kanal 3 Prnjavor + Kanal D + Kanali 7 + Kazbuka + KitchenTV + Klan Kosova + Kohavision + K::CN 1 Kopernikus + Kurir TV + Living HD + Lov i ribolov + MCN TV + Mediaset Italia + DigitAlb Melody TV + Minimax + MTV Europe + MTV 00s + Club MTV International + MTV Hits International + MTV Live HD + MTV 90s International + MUSE + My Music + MyZen TV + N1 RS + Наша ТВ + National Geographic + National Geographic Channel HD + Nat Geo Wild + NBA TV + News 24 + Nat Geo Wild HD + Nick Junior + Nickelodeon Commercial + Nova Max + Nova S + Nova Series + Nova Sport Srbija + RTV Novi Pazar + NTV IC Kakanj + O Kanal + OBN + Ora News + OTV Valentino + Pickbox TV RS + Pikaboo + RTV PINK + RED tv + Vesti + Pink Action + Pink Thriller + Pink Crime & Mystery + Pink BH + Pink Classic + Pink Comedy + Pink Erotic 1 + Pink Erotic 2 + Pink Erotic 3 + Pink Erotic 4 + Pink Erotic 5 + Pink Erotic 6 + Pink Erotic 7 + Pink Erotic 8 + TV PINK EXTRA + PINK Family + Pink Fashion + PINK Film + Pink Folk 1 + Pink Folk 2 + Pink Ha Ha + Pink Hits + Pink Hits 2 + Pink Horror + Pink Kids + Pink Koncert + Pink Kuvar + Pink LOL + Pink M + Pink Movies + Pink Romance + Pink SCI FI & Fantasy + Pink Music 1 + Pink and Roll + Pink Pedia + TV PINK PLUS + Pink Premium + Pink Reality + Pink Serije + Pink Show + Pink Soap + Pink Style + Pink Super Kids + Pink Western + PINK World + Pink World Cinema + PINK Zabava + Premier League TV + Brazzers TV (ex. Private Spice) + Private TV + ProSieben + Prva FILES + Prva KICK + Prva LIFE + Prva MAX + Prva Plus + Prva Srpska TV + Prva TV Crna Gora + Prva World + RAI UNO + RAI DUE + Reality Kings + Report TV + Россиᴙ 24 + RT Documentary + RTK 1 + RTL + RTL 2 + RTL Croatia World + RTL Kockica + RTL Living + RTRS + RTRS PLUS + RTS 1 + RTS 2 + RTS 3 + RTS Drama + RTS Klasika + RTS Kolo + RTS Muzika + RTS Nauka + RTS SVET + RTS Trezor + RTS Život + Radio Televizija Budva + Russia Today + Scan TV + SciFi + Sport Klub 1 Crna Gora + SK esports + Sky News + RTV Slovenija 1 + Tring Smile + SOS Plus + Sport Klub 1 Srbija + Sport Klub 4 + Sport Klub 5 + Sport Klub 6 + Sport Klub HD + Sport Klub 1 Slovenija + Sport Klub 3 + Sport Klub 2 Srbija + Superstar 2 + Stinet + Studio B + STV Folk + SuperSport 1 AL + Superstar TV + K::CN 3 Svet Plus + Syri TV + Televizioni 7 + Tanjug Tačno + Timeless Drama Channel + TGCOM24 + Tip TV + TOGGO plus + Top Channel + Top News + Toxic Folk + Toxic TV + Travel Channel + Tring Collection + Tring Comedy + Tring History + Tring Kids + Tring Life + Tring Action + Tring Novelas + Tring Planet + Tring Shqip + Tring Super + Tring Tring + Tring World + TV5Monde Europe + TV1000 Balkan + Televizija TV7 + Arena Sport 1 RS + Arena Sport 2 RS + Arena Sport 3 RS + Arena Sport 4 RS + TV Duga + SAT + TV Dukagjini + Телевизија Храм + RTV Most + Televizija Doktor + UNA TV + DIVA (ex. Universal) + Vavoom + MTV 80s + Viasat Explore CEE + Viasat History + Viasat Nature CEE + Vikom + Vizion Plus + RT Vojvodina 1 + RT Vojvodina 2 + Woman HD + ZDFinfo + ZDFneo + Zdrava televizija + Zico TV + BG Music Channel + Fuel TV + Passion XXX + The Voice BG + 7RM + 8 Andalucía + 8tv Catalunya + 3/24 + À Punt ES + AMC Break ES + Al Jazeera + AMC Crime ES + Antena 3 + Antena.nova + Aragón TV + Atreseries ES + AXN ES + AXN White ES + BabyTV ES + BBC World News + BE MAD ES + Betis TV ES + Bloomberg TV + Boing ES + BOM + Calle 13 + Canal 24H + Canal Cocina + Canal Decasa + Canal Extremadura + Canal Fútbol Replay + Odisea + Canal Panda + Canal Parlamento + Canal Sur + Castilla la Mancha TV + Caza y Pesca ES + Clan TVE + CNBC Europe + CNN Europe + Comedy Central ES + COSMO + Cuatro + DAZN 1 ES + DAZN 2 ES + DAZN F1 + DBike Channel + Deutsche Welle English + Deutsche Welle Espanol + Discovery Channel ES + Discovery MAX.es + Disney Channel ES + Disney Junior ES + Divinity + DKISS ES + El Toro TV + LEVANTE TV + Energy + Esport3 + EuroNews + Euronews FR + Eurosport 1 ES + Eurosport 2 ES + EWTN ES + Extreme Sports Channel + Fashion TV + Factoria de Ficción + Feel Good + Fight Time + FOX ES + France24 + HispanTV + Historia ES + HIT TV ES + Canal Hollywood + Iberalia HD CAZA + Iberalia HD PESCA + Iberalia TV + La 1 Cataluña + La dos + LA 8 MEDITERRANEO + La Otra ES + La primera + La Rioja + La sexta + Mega ES + Mezzo + Mezzo Live HD + MOTO ADV + Motorvision TV + Movistar Acción + Movistar Cine Español + Movistar Comedia + Movistar Deportes 1 + Movistar Deportes 2 + Movistar Drama + Movistar Estrenos + Movistar Estrenos 2 + Movistar Fórmula 1 + Movistar Golf + Movistar LaLiga + Movistar Liga de Campeones + Movistar Series + Movistar Seriesmania + MTV 00s + Club MTV International + MTV ES + MTV Hits International + MTV Live HD + MTV Music UK + MTV 90s International + MyZen TV + Nasa TV US + Nat Geo Wild ES + National Geographic ES + Nautica TV + Antena.neox + Nick Junior ES + Nickelodeon ES + Paramount Network ES + Paramount Comedy ES + Real Madrid TV + Russia Today ES + SYFY ES + Sevilla FC TV + Sky News + Stingray Classica + Sundance ES + Canal 33 + SX3 + TCM ES + Telecinco + TELE ELX + TeleDeporte + TeleMadrid + Ten ES + Warner TV ES + Toros TV + TV3 Catalunya + TV3 ES + TV5Monde Europe + TV Canaria + TVE Internacional + Televisión de Galicia + #Vamos + MTV 80s + 6ter + 360 TV + AB3 + Action + Al Arabiya + Al Jazeera + Antena 3 + Das Erste + ART Aflam 1 + ART Aflam 2 + ART Cinema + ARTE FR + ARTE DE + Atreseries ES + BabyTV + BBC Earth + BBC World News + beIN Drama Channel + beIN Movies Action + beIN Movies Drama + beIN Movies Family + beIN Movies Premiere + beIN Movies Premiere TR + beIN Series 1 + beIN Series 2 + beIN Sports 1 FR + beIN Sports 2 FR + beIN Sports 3 FR + beIN Sports MAX 5 FR + beIN Sports MAX 6 FR + beIN Sports MAX 7 FR + beIN Sports MAX 8 FR + beIN Sports MAX 9 FR + beIN Sports MAX 10 FR + beIN Sports MAX 4 FR + Benfica TV + BET FR + BFM Business + BFM TV + Bloomberg TV + Boing + Boomerang + Boomerang FR + Boomerang UK + C8 FR (ex. D8) + Canal 24H + Canal Cocina + Canal Decasa + CANAL+ DOCS + Canal+ Kids FR + CANAL+ GRAND ECRAN + Canal J + Canal+ Cinéma(s) FR + Canal+ France + Canal Plus Sport FR + Canal+ Series FR + Cartoon Network + Cartoon Network FR + CCTV 4 Europe + CGTN + CGTN Documentary + Первый + Chérie 25 + Ciné+ Classic + Ciné+ Club + Ciné+ Emotion + Ciné+ Famiz + Ciné+ Frisson + Polar+ + Cine+ Premier FR + English Club TV + CNBC Europe + CNews + CNN Europe + Comedie+ + Comedy Central FR + Comedy Central UK + Crime District + CStar FR + CStar Hits France + Daystar + Deutsche Welle English + Discovery Channel FR + Discovery Investigation FR + Disney Channel FR + Disney Junior FR + Stingray Djazz + Dorcel TV + Dorcel XXX + E! Entertainment + Euro Star + Eurochannel + Eurochannel FR + EuroNews + Euronews FR + Eurosport 2 FR + Eurosport FR + Fashion TV + FightBox HD + FOX NEWS + France24 + France 2 + France 3 + France 4 + France 5 + France24 Arabic + France 24 French + France Info + Game One + Ginx Esports TV + Golf Channel Češka + Golf Channel FR + Golf plus + Gulli + Habertürk + HUSTLER TV + i24News FR + Infosport+ + iTVN + iTVN Extra + J-One + Kabel 1 + Kanal 7 + KBS World HD + KiKA + L'Equipe + La Chaîne Info + LCP + M6 + Mangas + MBC Bollywood + MCM FR + MCM Top + Mediaset Italia + Mezzo + Mezzo Live HD + MGG TV + Motorvision TV + Motorvision TV France + MTV Europe + MTV 00s + MTV FR + MTV Hits FR + MTV Hits International + MTV Live HD + Museum TV + NTV DE + Nat Geo Wild FR + National Geographic FR + The Nautical Channel + Nickelodeon FR + Nickelodeon Junior FR + Nickelodeon Teen FR + Novelas TV FR + NRJ12 + RMC Story + OCS Geants + OCS Max + OCS Pulp + Olympia TV + OSN TV Movies Hollywood + OSN TV Movies Premiere + OSN TV Kids + OSN TV Movies Action + OSN TV Ya Hala Aflam + Paramount Channel FR + Paris premiere + TV PINK EXTRA + PINK Film + Pink Music 1 + TV PINK PLUS + Piwi+ + Planète+ Aventure + Planete+ Crime + Planète+ + Playboy TV + Power Türk TV + Brazzers TV (ex. Private Spice) + Private TV + ProSieben + RAI UNO + RAI DUE + RAI TRE + RAI Education + RAI News 24 + RAI Storia + Reality Kings + RMC Découverte + RMC Sport 1 + RMC Sport 2 + RMC Sport Live 4 + RMC Sport Live 5 + RMC Sport Live 6 + RMC Sport Live 7 + RMC Sport Live 8 + RMC Sport Live 9 + RMC Sport Live 10 + RMC Sport Live 11 + RMC Sport Live 12 + RMC Sport Live 13 + RMC Sport Live 14 + RMC Sport Live 15 + RMC Sport Live 16 + RMC Sport Live 17 + RMC Sport Live 3 + RT Documentary + RTL Zwei + RTL DE + RTL Nitro + RTP3 + RTS SVET + Russia Today + Sat.1 + Seasons + Show Türk + SIC Internacional + Sky News + Star Movies MENA + Star World MENA + Stingray CMusic + Stingray Classica + Stingray iConcerts + Super RTL + SWR + TCM UK + TCM Cinéma + Télétoon+ FR + Télévision française 1 + TFX + TGCOM24 + TiJi + TMC + Trace Urban + Travel Channel + TRT 1 + TRT Çocuk + TRT Turk + TRT World + TV3 Catalunya + TV5 Monde + TV5Monde Asia + TV5Monde Europe + TV5Monde Style + TV1000 Global Kino + TV Breizh + TVE Internacional + Televisión de Galicia + MTV 80s + Vivid TV + Vosges Télévision + VOX + W9 + Warner TV FR + Welt + World Fashion Channel + ZDF + ZDFinfo + XXL + 3SAT + 4Fun Dance + 24 Kitchen + 24 ВЕСТИ + 360 TuneBox + Adult Channel + Al Jazeera + Al Jazeera Balkans + ALFA TV + AMC + Das Erste + Arena Esport + Arena Fight + Arena Sport 4 HR + Arena Sport 6 HR + Arena Sport 8 HR + Arena Sport 9 HR + Arena Sport 10 HR + ARTE DE + Alternativna televizija Banja Luka + Aurora TV + AXN + AXN Spin + B1 TV + B92 + BabyTV + Balkan trip + Balkan TV + Balkanika TV + BBC Earth + BBC First + BBC World News + Radio Televizija BIH + Bloomberg TV + Bloomberg Adria + Blue Hustler + Radio Televizija BN + BN music + BN 2 HD + Body in Balance + Boomerang + Brainz TV + Cartoon Network + Cartoonito CEE + Cartoonito UK + CBS Reality + CCTV 4 Europe + CGTN + CGTN Documentary + Cinemax + Cinemax 2 + CineStar Action&Thriller + CineStar Premiere 1 + CineStar Premiere 2 + CineStar TV1 + CineStar TV2 + CineStar TV Comedy Family + CineStar TV Fantasy + English Club TV + Croatian Music Channel + CNBC Europe + CNN Europe + Comedy Central DE + Nicktoons + Crime & Investigation Channel + Da Vinci Learning + Deluxe Music + Deutsche Welle English + Diadora TV + Discovery Animal Planet + Discovery Channel + Discovery Science + TLC + Disney Channel + Stingray Djazz + DMAX DE + DMC televizija + DM SAT + DocuBox HD + DOKU TV + Doma TV + Dorcel TV + Dorcel XXX + DOX TV + Dr. Fit HD + Sport 1 DE + Dubrovačka Televizija + DuckTV + Duna World + E! Entertainment + Epic Drama (CEE) + Erox HD + Eroxxx HD + Eurochannel + EuroNews + Eurosport + Eurosport 2 + Eurosport 2 DE + Eurosport 1 EMEA + Eurosport.com + Eurosport 1 DE + ExtraTV + Extreme Sports Channel + FACE TV + Fashion TV + FashionBox HD + Fast&FunBox HD + FightBox HD + SK Fight + Film4 + FilmBox Arthouse + FilmBox Extra RS + FilmBox Stars RS + FilmBox Premium RS + Fireplace + Food Network + STAR Channel + STAR Crime + STAR Life + STAR Movies + FOX NEWS + France24 + France 24 French + Radio Televizija Federacije BIH + Fuel TV + FullTV + FunBox UHD + GameHub HR + Gametoon HD + Ginx Esports TV + SK Golf + GP1 + Grand Televizija + Grand nostalgija + HappyTV + Hayat Folk + Hayat TV + Hayatovci + HBO + HBO3 + HBO2 + Home & Garden Television + The History Channel + History Channel 2 + HIT TV + HSE + HRT 1 + HRT 2 + HRT 3 + HRT 4 + HRT Int. + HUSTLER TV + ICT Business + Insta TV + Otvorena televizija + JimJam + Jugoton TV + K::CN 2 Music + Kabel 1 + Kabel eins Österreich + Kabel eins Doku + Kanal Rijeka + KBS World HD + KiKA + KinoTV + KitchenTV + Pop Max + Klape i Tambure TV + KLASIK HR + KLASIK + K::CN 1 Kopernikus + Laudato TV + Legend + Libertas TV + Lov i ribolov + LUXE TV + M1 Family + M1 FILM + M1 Gold + M6 + Maria Vision + MAXSport 1 + MAXSport 2 + Mediaset Italia + Mezzo + Mezzo Live HD + Motorvision TV + MG Movie Generation + MTV Europe + MTV 00s + Club MTV International + MTV Hits International + MTV Live HD + MTV 90s International + MyZen TV + N1 HR + N24 Doku + NTV DE + National Geographic + National Geographic Channel HD + Nat Geo Wild + The Nautical Channel + MrežaZG + Nat Geo Wild HD + Nick Junior + Nick Music + Nickelodeon + NOVA TV + Nova BH + Nova Plus Family + Nova Sport Srbija + O Kanal + OBN + One + ORF1 + ORF2 + ORF Sport Plus + Osječka televizija + OTV Valentino + Phoenix + Pikaboo + Pikaboo 2 + RTV PINK + RED tv + Vesti + Pink BH + Pink Classic + TV PINK EXTRA + Pink Fashion + PINK Film + Pink Folk 1 + Pink Folk 2 + Pink Ha Ha + Pink Hits 2 + Pink Kids + Pink Koncert + Pink Kuvar + Pink LOL + Pink M + Pink Music 1 + Pink Pedia + TV PINK PLUS + Pink Reality + Pink Serije + Pink Show + Pink Style + Pink Super Kids + PINK World + Pink World Cinema + PINK Zabava + Planet Earth + Plava televizija + Playboy TV + Poljoprivredna TV + Posavska Televizija + TV Zelina + Brazzers TV (ex. Private Spice) + Private TV + ProSieben + ProSieben MAXX + Prva KICK + Prva Plus + Prva Srpska TV + Prva World + QVC Deutschland + RAI UNO + RAI DUE + RAI TRE + RAI Education + RAI News 24 + RAI Sport 1 + RAI Storia + Reality Kings + RedLight HD + Россиᴙ 24 + Radiotelevizija Banovina + RTL + RTL 2 + RTL Zwei + RTL Adria + RTL DE + RTL Kockica + RTL Living + RTL Nitro + RTLup + RTRS + RTRS PLUS + RTS 1 + RTS 2 + RTS SVET + RTV Herceg-Bosne + Russia Today + Saborska TV + Samobor TV + Sat.1 + Sat.1 Österreich + Sat.1 Gold + Slavonskobrodska Televizija + SciFi + ServusTV + SK 4K + SK esports + Sky News + TV Slavonije i Baranje + RTV Slovenija 1 + RTV Slovenija 2 + RTV Slovenija 3 + sonnenklar.TV + Sony Channel + Great! Movies Action + Great! Movies Classic + Great! Movies +1 + SOS Plus + Sport Klub 4 + Sport Klub 5 + Sport Klub 6 + Sport Klub 7 + Sport Klub 8 + Sport Klub 9 + Sport Klub 10 + Sport Klub HD + Sport Klub 1 Hrvatska + Sport Klub 2 Hrvatska + Sport Klub 3 + Sport Klub 2 Srbija + Sportska Televizija + Stars TV PL + Stingray CMusic + Stingray Classica + Stingray iConcerts + Studio B + Super RTL + Supertennis HD + K::CN 3 Svet Plus + Timeless Drama Channel + Telecinco + Televizija Alfa + Telma + TiJi + Tiny Pop TV + TOGGO plus + Toon kids + Toxic Folk + Toxic Rap + Toxic TV + Trace Urban + Travel Channel + Trend TV + TV5 Monde + TV5Monde Europe + TV1000 Balkan + Arena Sport 1 HR + Arena Sport 2 HR + Arena Sport 3 HR + Televizija Dalmacija + TV Duga + SAT + TV Jadran + TV Šibenik + TV Vijesti + Televizija Zapad Zaprešić + Televizija Crne Gore MNE + TVE Internacional + UNA TV + DIVA (ex. Universal) + Varaždinska Televizija + Vavoom + MTV 80s + Viasat Explore CEE + Viasat History + Viasat Nature CEE + Plava vinkovačka TV + Vivid Red + Vivid TV + Vizion Plus + RT Vojvodina 1 + VOX + Welt + Woman HD + Zagrebačka Televizija + ZDF + ZDFinfo + ZDFneo + Zdrava televizija + Tring 3 Plus + 4Fun Dance + 4Fun Kids + 24 Kitchen + 360 TuneBox + Agro TV + Televizija Alfa + Al Jazeera Balkans + Al Jazeera + Alpha TV + AMC HU + Antena Europe + Apostol TV + Arena4 HU + Arena Esport + Arena Fight + Arena Sport 1 BiH + Arena Sport 1 RS + Arena Sport 1 SI + Arena Sport 1 Premium + Arena Sport 1 Premium SI + Arena Sport 1x2 + Arena Sport 2 RS + Arena Sport 2 SI + Arena Sport 2 Premium + Arena Sport 3 RS + Arena Sport 3 SI + Arena Sport 3 Premium + Arena Sport 4 RS + Arena Sport 4 SI + Arena Sport 5 HR + Arena Sport 5 RS + Arena Sport 6 RS + Arena Sport 7 HR + Arena Sport 7 RS + Arena Sport 8 RS + ARTE DE + ATV Avrupa + AXN HU + AXN + AXN Spin + B1 TV + Balkanika TV + Balkan trip + BBC Earth + BBC News Channel + BBC World News + TV Belle Amie + BN 2 HD + BN music + Boomerang + Brainz TV + Brazzers TV (ex. Private Spice) + BRIO + Canale 5 + CGTN Documentary + Cinemania TV + CineStar TV1 + CineStar TV RS + CineStar TV2 + CineStar Action&Thriller + CineStar Action&Thriller RS + CineStar TV Comedy Family + CineStar TV Fantasy + CineStar Premiere 1 + CineStar Premiere 2 + CNN Europe + CNN Türk TV + Comedy Central HU + Comedy Central Family HU + CoolTV + Cufo TV + D1 TV HU + Das Erste + Da Vinci Learning + Dexy TV + Discovery Channel HU + Discovery Science + Disney Channel HU + DM SAT + DocuBox HD + DOKU TV + Doma TV + Dr. Fit HD + Deutsche Welle English + English Club TV + Epic Drama (CEE) + Erox HD + Eroxxx HD + ETV HD + Eurochannel + Euro D + EuroNews + Eurosport + Eurosport 2 + Explorer Histori + Explorer Natyra + Explorer Shkence + FACE TV + FashionBox HD + Fashion TV + Fast&FunBox HD + FEM3 + FightBox HD + Pink Fight Network + Film4 HU + Film Aksion Digitalb + FilmBox Arthouse + Filmbox Stars HU + Film Cafe HU + Film Drame Digitralb + Film Klub + Film Mania HU + Film+ HU + Film Thriller Digitalb + Food Network + STAR Crime + France24 Arabic + France24 + France 24 French + Galaxy4 + Gametoon HD + Ginx Esports TV + Golica TV + Grand Televizija + Pink Ha Ha + Hayat Folk + Hayat Music + Hayatovci + Hayat Plus + HBO HU + Home & Garden Television + Hír TV + History Channel 2 + HRT 2 + HRT 4 + HRT Int. + IDJ World + INTV AL + Investigation Discovery + Italia 1 + Izaura TV + JockyTV + Jugoton TV + Kanal 3 Prnjavor + Kanal A, SLO + Kanal D TR + Kazbuka + KINO + KinoTV + KitchenTV + Klan Kosova + Klan Macedonia + KLASIK + Kohavision + Kurir TV + La7 + Laudato TV + Life TV HU + Living HD + Pink LOL + Lov i ribolov + M1 FILM + M1 Gold + Mediaset Italia + Mezzo + Mezzo Live HD + Minimax + MiniTV + Mozi plusz TV + Moziverzum + МРТ 1 + МРТ 2 + MTV Europe HU + MTV Live HD + Muzsika TV + N1 BA + N1 HR + National Geographic HU + Nat Geo Wild HU + RTL Nitro + Nova 24 TV + Nova S + Nova Plus Cinema + Nova Sport Srbija + Nova BH + TV NOVA Pula + Nova M + Nova World + Novosadska TV + OBN + O Kanal + One + OTO + PΛX + Pickbox TV + Pink Action + Pink BH + Pink Classic + Pink Comedy + Pink Crime & Mystery + PINK Family + Pink Fashion + Pink Folk 2 + Pink Hits + Pink Horror + Pink Koncert + Pink Kuvar + Pink M + Pink Movies + Pink and Roll + Pink Pedia + Pink Premium + Pink Romance + Pink SCI FI & Fantasy + Pink Serije + Pink Show + Pink Soap + Pink Style + Pink Super Kids + Pink Thriller + Pink Western + PINK World + Pink World Cinema + PINK Zabava + Planeta TV BG + Planet Earth + Planet TV SI + Playboy TV + POP TV + Power Türk TV + Prime + ProSieben Fun + Prva FILES + Prva KICK + Prva LIFE + Prva MAX + Prva TV Crna Gora + Prva Plus + Prva World + QVC STYLE DE + QVC Style + RadioBremen + RAI UNO + RAI DUE + RAI TRE + RAI Gulp + RAI News 24 + RAI Storia + real time + Rete 4 + Russia Today + RTK 1 + RTK 2 + RTL + RTL HU + RTL Crime + RTL Gold + RTL Ketto + RTL Living + RTL Passion + Super RTL + RTRS PLUS + RTS 2 + RTS 3 + RTS Drama + RTS Kolo + RTS Muzika + RTS Poletarac + RTS SVET + RTS Trezor + RT Vojvodina 1 + RT Vojvodina 2 + RTV21 + Россиᴙ 24 + SonLife + Show Türk + Сител + Magyar Sláger TV + Sorozat+ + Spektrum TV + Spektrum Home HU + Spíler1 TV + Spíler2 TV + Sport 1 DE + Sport 1 HU + Sport 2 HU + Sport Klub 1 Srbija + Sport Klub 1 Slovenija + Sport Klub 2 Srbija + Sport Klub 2 Slovenija + Sport Klub 3 + Sport Klub 4 + Sport Klub 5 + Sport Klub 6 + SK esports + SK Golf + Sport Klub HD + Sportska Televizija + Sport TV1.pt + Šport TV 1 + Sport TV2 + Šport TV 2 + Stars TV PL + Stingray iConcerts + Story4 + Studio B + Superstar TV + Super TV2 + TGCOM24 + The Fishing and Hunting + TLC + TOGGO plus + Trace Urban + Travel Channel + TRT Arapça + TRT Avaz + TV2 HU + TV2 Comedy + TV2 Kids + TV2 Séf + TV 3 Medias + TV4 HU + TV Arena + Televizija Crne Gore 1 + Televizija Crne Gore 2 + Televizija Crne Gore MNE + TV Galaksija + Телевизија Храм + K::CN 1 Kopernikus + K::CN 2 Music + K::CN 3 Svet Plus + TV Koper + TVN24 + TVN + TV Paprika HU + TVP Polonia + RTV Slovenija 1 + RTV Slovenija 2 + RTV Slovenija 3 + ТВ Сонце + TV Vijesti + Ülke TV + Viasat3 + Viasat 6 + Viasat Explore CEE + Viasat History + TV1000 Global Kino + Viasat Nature CEE + TV1000 Balkan + VOX + Woman HD + World Fashion Channel + Zagrebačka Televizija + ZDF + Zenebutik + 360 TuneBox + Das Erste + ARD-alpha + ATV Spirit HU + BabyTV + BBC News Channel + BBC World News + Bloomberg TV + Boomerang + Cartoonito CEE + CBS Reality + CCTV 4 Europe + Cinemax 2 HU + Cinemax HU + English Club TV + CNN Europe + Da Vinci Learning + Deutsche Welle English + Discovery Turbo Extra + DocuBox HD + DuckTV + Duna TV + Duna World + E! Entertainment + Epic Drama (CEE) + Eroxxx HD + Extreme Sports Channel + FashionBox HD + Fast&FunBox HD + Fehérvár TV + FightBox HD + FilmBox Arthouse + Filmbox Extra HU + Filmbox Family HU + FilmBox HU + Filmbox Premium HU + Food Network + France24 + Gametoon HD + HBO + HBO 2 HU + HBO 3 HU + Home & Garden Television + The History Channel + History Channel 2 + History Channel HU + Home & Garden Television UK + HUSTLER TV + Investigation Discovery + Investigation Discovery UK + Jazz TV HU + JimJam + KiKA + Magyar Televízió 1 + M2 Petőfi + Magyar Televízió 3 + M4 sport + M4 Sport Plus + m5 + ATV HU + Magyar Mozi TV + MATCH4 + MAX4 + Mediaset Italia + Mezzo + Mezzo Live HD + Minimax + Minimax HU + MTV 00s + Club MTV International + MTV Hits International + MTV Live HD + MTV 90s International + MyZen TV + Nick Junior PL + Nickelodeon Commercial + Nicktoons PL + Ozone Network + Paramount Network HU + Polonia1 + Private TV + ProSieben + RAI UNO + RAI TRE + RedLight HD + RTL Zwei + RTL DE + RTL Három + Sat.1 + Sky News + Viasat Film HU + Stingray Classica + Stingray iConcerts + Super RTL + TeenNick + Travel Channel + TVE Internacional + MTV 80s + Viasat 2 + Viasat History + Viasat Nature CEE + VOX + ZDF + 7 Gold + 20 Mediaset + Alma TV + Boing + Boing Plus + Boomerang IT + Canale 5 + Canale 7 + Cartoon Network IT + Cartoonito Italia + cielo + Cine34 + Class TV Moda + Comedy Central IT + CI Crime+ Investigation + DAZN IT + DeA Junior + DeA Kids + Discovery Channel IT + Discovery MAX.es + DMAX IT + Eurosport 2 IT + Eurosport IT + Focus TV + Food Network Italia + Frisbee + Gambero Rosso Channel + Giallo TV + History Channel IT + Inter Channel + Iris + Italia 1 + Italia 2 + K2 + La Cinque + La7 + La7 AU + La7d + Lazio Style Channel + Marco Polo TV + Mediaset Extra + Mediaset Italia + Mediaset Italia AU + Milan TV + MotorTrend + MTV Hits International + MTV Italia + MTV Music UK + MTV Music IT + MTV 90s International + National Geographic + Nick Junior IT + Nickelodeon IT + NOVE + Parole di Vita + Radio Italia TV + Radiofreccia + RAI UNO + RAI DUE + RAI TRE + RAI 3 Bis + RAI 4 + RAI 5 + RAI Education + RAI Gulp + RAI Italia Australia + RAI Movie + RAI News 24 + RAI Premium + RAI Sport 1 + RAI Storia + RAI World Premium + Rai Yoyo + real time + Rete 4 + Sky Arte + Sky Atlantic IT + Sky Caccia e Pesca + Sky Cinema Action IT + Sky Cinema Collection IT + Sky Cinema Comedy IT + Sky Cinema Drama IT + Sky Cinema Due + Sky Cinema Family IT + Sky Cinema Romance IT + Sky Cinema Suspense IT + Sky Cinema Uno + Sky Documentaries IT + Sky Investigation + Sky Nature IT + Sky Pesca e Caccia + Sky Serie + Sky Sport24 HD + Sky Sport 1 IT + Sky Sport 2 IT + Sky Sport 3 IT + Sky Sport F1 IT + Sky Sport MotoGP IT + Sky Sport Plus IT + Sky TG24 HD + Sky Uno + Sportitalia + Super! + Supertennis HD + Telequattro + Teletutto + TGCOM24 + Top Calcio 24 + Top Crime + TV8 IT + TV 2000 + Twenty Seven + VH1 IT + Warner TV IT + Tring 3 Plus + 24 Kitchen + 24 ВЕСТИ + 360 TV + ABC News Albania + Agro TV + Al Jazeera + Al Jazeera Balkans + ALFA TV + Алсат М + AMC + Anixe HD Serie + Das Erste + Arena Esport + Arena Fight + Arena Sport 1 MK + Arena Sport 1 Premium + Arena Sport 2 Premium + Arena Sport 3 Premium + Arena Sport 5 RS + ARTE DE + Alternativna televizija Banja Luka + AXN + AXN Spin + Balkan trip + Balkanika TV + Bang Bang + BBC First + BBC World News + Bloomberg TV + Bloomberg Adria + Radio Televizija BN + BN music + BN 2 HD + Boomerang + Brainz TV + Cartoon Network + Cartoonito CEE + CBS Reality + Cinemania TV + Cinemax + Cinemax 2 + CineStar Premiere 1 + CineStar Premiere 2 + CineStar TV2 + Croatian Music Channel + CNBC Europe + CNN Europe + Nicktoons + Crime & Investigation Channel + Cufo TV + Da Vinci Learning + Deluxe Music + Deutsche Welle English + DigitAlb T HD + Discovery Animal Planet + Discovery Channel + Discovery Science + TLC + Disney Channel + Disney Junior + DM SAT + DocuBox HD + Sport 1 DE + Elta 1 HD + E! Entertainment + Epic Drama (CEE) + Erox HD + Eroxxx HD + Euro Star + EuroNews + Euronews Albania + Eurosport + Eurosport 2 + Explorer Histori + Explorer Natyra + Explorer Shkence + Extreme Sports Channel + FACE TV + Fashion TV + FashionBox HD + Fast&FunBox HD + FightBox HD + SK Fight + Film Autor + Film Drame Digitralb + Film Klub Extra + Film Thriller Digitalb + FilmBox Arthouse + FilmBox Extra RS + FilmBox Stars RS + The Fishing and Hunting + Food Network + STAR Channel + STAR Crime + STAR Life + STAR Movies + France24 + France 24 French + Radio Televizija Federacije BIH + Ginx Esports TV + SK Golf + Grand Televizija + HappyTV + Hayat Folk + Hayat Music + Hayat Plus + Hayat TV + Hayatovci + HBO + HBO3 + HBO2 + The History Channel + HRT 1 + HRT 3 + HRT 4 + HUSTLER TV + IDJ World + JimJam + Tring Jolly HD + Jugoton TV + Junior TV + K::CN 2 Music + Kabel 1 + Kanal1 + Канал 5 + Канал 8 + Kanal 10 + Kanal D + Kerrang! + KiKA + TV Kiss Menada + KitchenTV + Klan Macedonia + KLASIK + K::CN 1 Kopernikus + Living HD + Lov i ribolov + M1 Family + M1 Film MK + M1 Gold MK + DigitAlb Melody TV + Mezzo + Minimax + МРТ 1 + МРТ 3 + МРТ 4 + МРТ 5 + MTM Televizija + MTV Europe + MTV 00s + Club MTV International + MTV Hits International + MTV Live HD + MTV 90s International + MUSE + My Music + N1 RS + Наша ТВ + National Geographic + National Geographic Channel HD + Nat Geo Wild + News 24 + Nick Junior + Nickelodeon Commercial + Nova S + Nova Sport Srbija + RTV Novi Pazar + O Kanal + OBN + Ora News + Pickbox TV MK + Pikaboo + Pink Music 1 + Pink Serije + Pink Show + Playboy TV + Brazzers TV (ex. Private Spice) + Private TV + ProSieben + Prva FILES + Prva KICK + Prva LIFE + Prva MAX + Prva Plus + Prva Srpska TV + Prva World + RAI UNO + RAI DUE + RAI TRE + RAI Sport 1 + RAI Storia + RedLight HD + Report TV + Россиᴙ 24 + RT Documentary + RTK 1 + RTK 2 + RTK 4 + RTL + RTL 2 + RTL Zwei + RTL DE + RTL Kockica + RTL Living + RTRS + RTS 1 + RTS 2 + RTS 3 + RTS SVET + RTSH 1 + RTV 21 Popullore + RTV Besa + RTV Slon Tuzla + Russia Today + Sat.1 + Scan TV + SciFi + Shenja TV + Сител + SK esports + Sky News + RTV Slovenija 1 + RTV Slovenija 2 + RTV Slovenija 3 + Tring Smile + SOS Plus + Sport Klub 1 Srbija + Sport Klub 4 + Sport Klub 5 + Sport Klub 6 + Sport Klub 7 + Sport Klub 8 + Sport Klub 9 + Sport Klub 10 + Sport Klub HD + Sport Klub 1 Hrvatska + Sport Klub 2 Hrvatska + Sport Klub 3 + Sport Klub 2 Srbija + Superstar 2 + Stinet + Stingray CMusic + Studio B + STV Folk + Super RTL + Superstar TV + K::CN 3 Svet Plus + Syri TV + Televizioni 7 + Timeless Drama Channel + Telma + Tera TV + Tip TV + Top Channel + Top News + Toxic Folk + Toxic Rap + Toxic TV + Trace Urban + Travel Channel + Travelxp + Tring Bunga Bunga + Tring Comedy + Tring Desire + Tring History + Tring Kids + Tring Life + Tring Action + Tring Novelas + Tring Planet + Tring Shqip + Tring Super + Tring Tring + Tring World + TRT 1 + TRT Turk + TV5 Monde + TV1000 Balkan + Arena Sport 2 RS + Arena Sport 3 RS + Arena Sport 4 RS + TV Duga + SAT + TV Dukagjini + TV Edo + Klan TV HD + TVM Ohrid + TV Sarajevo + Televizija Crne Gore MNE + DIVA (ex. Universal) + Valentino Music HD + Vavoom + MTV 80s + Viasat Explore CEE + Viasat History + Viasat Nature CEE + Vikom + Vizion Plus + VOX + Welt + Wness TV + World Fashion Channel + ZDF + Zdrava televizija + 1-2-3.tv + 2X2 + 3SAT + 4Fun Dance + 4Fun Kids + 4FunTV + Telewizja 13 + 13 Ulica + 360 TuneBox + Active Family + Adult Channel + Adventure HD + Al Jazeera + Ale Kino+ + ALFA TVP + AMC PL + Animal Planet PL + Anixe HD Serie + Antena HD + Das Erste + ARD-alpha + ARTE FR + ARTE DE + AstroTV + AXN Black PL + AXN PL + AXN Spin PL + AXN White PL + BabyTV PL + BBC Brit PL + BBC Earth PL + BBC First PL + BBC Lifestyle PL + BBC News Channel + BBC World News + Belsat TV + BFM TV + Bibel TV + Biznes24 + Bloomberg TV + Blue Hustler + Bollywood PL + Boomerang + CANAL+ 4K Ultra HD + CANAL+ Dokument + CANAL+ Family PL + CANAL+ Film PL + CANAL+ Premium +1 PL + Canal+ Domo + CANAL+ Kuchnia + CANAL+ Premium PL + CANAL+ Seriale PL + CANAL+ Sport 2 + CANAL+ Sport 3 + CANAL+ Sport 4 + CANAL+ Sport 5 + CANAL+ Sport PL + Cartoon Network PL + Cartoonito CEE + Cartoonito UK + CBeebies PL + CBS Europa PL + CBS Reality PL + CGTN Documentary + Cinemax 2 PL + Cinemax PL + CNBC Europe + CNews + CNN Europe + Comedy Central DE + Comedy Central PL + COSMO + Crime+Investigation PL + ČT 1 + ČT 2 + ČT 24 + ČT :D + ČT Sport + Da Vinci Learning PL + Deluxe Music + Deutsche Welle English + Deutsche Welle Deutsch + Disco Polo Music PL + Discovery Channel PL + Discovery Historia + Discovery Life PL + Discovery Science PL + Disney Channel PL + Disney Junior Polska + Disney XD PL + DIZI PL + Stingray Djazz + DMAX DE + DocuBox HD + Dorcel TV + Dorcel XXX + Sport 1 DE + DTX PL + Duck TV Plus + DuckTV + E-Sport HD + Eleven Sports 1 PL + Eleven Sports 2 PL + Eleven Sports 3 PL + Eleven Sports 4 PL + E! Entertainment + Epic Drama (Poland) + Eska Rock TV + Eska TV + Eska TV Extra + Еспресо TV + Eurochannel + EuroNews + Euronews FR + Eurosport 1 PL + Eurosport 2 PL + EWTN + EWTN PL + EXTASY TV + Extreme Sports PL + Fashion TV + FashionBox HD + Fast&FunBox HD + Fight Klub HD + FightBox HD + Filmax + FilmBox Action PL + FilmBox Arthouse + FilmBox Extra PL + FilmBox Family PL + FilmBox Premium PL + Fokus TV + Food Network PL + FX Comedy PL + FX PL + France24 + France 2 + France 3 + France 4 + France 5 + France Info + FunBox UHD + Gametoon HD + Ginx Esports TV + Golf Channel PL + HBO 2 PL + HBO 3 PL + HBO PL + Das Health TV + HGTV PL + History 2 Polska + History Channel Polska + HR Fernsehen + HSE + HSE24 Extra + Hustler HD + HUSTLER TV + Insight TV + Investigation Discovery PL + Italia 2 + iTVN + iTVN Extra + JimJam PL + K-TV Katholisches Fernsehen + Kabel 1 + Kabel eins Österreich + Kabel eins Doku + KiKA + Kino Polska + Kino Polska Muzyka + KinoTV PL + La Chaîne Info + LCP + Leo TV + Love Nature + MDR Sachsen-Anhalt + MDR Sachsen + MDR Thüringen + Metro TV + Mezzo + Mezzo Live HD + Minimini+ + Motowizja + MTV Europe + MTV 00s + Club MTV International + MTV DE + MTV Hits International + MTV Live HD + MTV PL + MTV 90s International + münchen.tv + Music Box Polska + MyZen TV + N24 Doku + NTV DE + National Geographic Wild PL + National Geographic PL + National Geographic People PL + The Nautical Channel + NDR Hamburg + NDR Mecklenburg-Vorpommern + NDR Niedersachsen + NDR Schleswig-Holstein + News 24 + Nick Junior PL + Nick Music + Nick DE + Nickelodeon PL + Nickelodeon Ukraine Pluto + Nicktoons PL + Niederbayern TV Deggendorf-Straubing + Novela TV + Novelas+ + Nowa TV + NRJ12 + NUTA GOLD + NUTA.TV HD + oe24.TV + One + ORF2 + Paramount Network PL + Parole di Vita + Phoenix + Planete+ PL + Playboy TV + POLO TV + Polonia1 + TVP Polonia + Polsat + Polsat 1 + Polsat 2 + Polsat Cafe + Polsat Comedy Central Extra + Polsat Doku + Polsat Film + Polsat Games + Polsat Music + Polsat News + Polsat News 2 + Polsat Play + Polsat Rodzina + Polsat Seriale + Polsat Sport + Polsat Sport Extra + Polsat Sport Fight + Polsat Sport News + Polsat Sport Premium 1 + Polsat Sport Premium 2 + Polsat Sport Premium 3 PPV + Polsat Sport Premium 4 PPV + Polsat Sport Premium 5 PPV + Polsat Sport Premium 6 PPV + Polsat Viasat Explore + Polsat Viasat History + Polsat Viasat Nature + Power TV PL + Brazzers TV (ex. Private Spice) + Private TV + ProSieben + PRO 7 Österreich + Proart + ProSieben MAXX + QVC Deutschland + Radio Italia TV + Radiofreccia + RBB Fernsehen Brandenburg + RBB Fernsehen Berlin + Reality Kings + Red Carpet TV PL + RedLight HD + RiC DE + Romance TV PL + RT Documentary + RTL Zwei + RTL DE + RTL Nitro + Russia Today + Sat.1 + Sat.1 Österreich + Sat.1 Gold + Kurier TV + SciFi + ServusTV + Sky Sport News DE + Sky News + sonnenklar.TV + Sportklub PL + SR Fernsehen + Stars TV PL + Stingray CMusic + Stingray Classica + Stingray iConcerts + Stopklatka + Sundance TV PL + Super Polsat + Super RTL + SWR Baden-Württemberg + tagesschau24 + TBN Polska + TeenNick + Tele 5 PL + Tele 5 DE + teleTOON+ + TLC PL + TOGGO plus + Top Kids Jr. PL + Top Kids PL + Trace Urban + Travel Channel PL + Travelxp + TRT Arapça + TTV + TV5Monde Europe + TV6 PL + TV 4 + TV Puls 2 + TV Puls PL + TV Regionalna.pl + TV Republika + TV Silesia + Toya TV + TV Trwam + TVC PL + TVE Internacional + TVN + TVN24 + TVN24 BiS + TVN 7 + TVN Fabuła + TVN Style + TVN Turbo + TVP3 Białystok + TVP3 Bydgoszcz + TVP3 Gdańsk + TVP3 Katowice + TVP3 Kielce + TVP3 Kraków + TVP3 Łódź + TVP3 Lublin + TVP3 Olsztyn + TVP3 Opole + TVP3 Poznań + TVP3 Rzeszów + TVP3 Szczecin + TVP3 Warszawa + TVP3 Wrocław + TVP 1 + TVP 2 + TVP 3 + TVP ABC + TVP ABC 2 + TVP Dokument + TVP HD + TVP Historia + TVP Historia 2 + TVP Info + TVP Kobieta + TVP Kultura + TVP Kultura 2 + TVP Nauka + TVP Regionalna + TVP Rozrywka + TVP Seriale + TVP Sport + TVP Wilno + TVP World + TVR PL + TVS + TVT PL + MTV 80s + Viasat Explore CEE + Vivid Red + VOX + VOX Music TV PL + Warner TV FR + Warner TV PL + Water Planet + WDR Fernsehen Köln + Welt + Welt der Wunder + WP TV + wPolsce PL + Wydarzenia 24 + Xtreme TV + ZDFinfo + ZDFneo + Zoom TV PL + 3SAT + 24 Kitchen + 24Kitchen PT + AMC Break ES + Al Jazeera + AMC Break PT + AMC Crime PT + AMC PT + Antena 3 + ARD-alpha + ARTE FR + ARTV + AXN Movies PT + AXN PT + AXN White PT + BabyTV ES + BBC News Channel + BBC World News + Benfica TV + Biggs + Bloomberg TV + Canal 11 PT + Canal Hollywood PT + Canal Panda + Canal Panda PT + Cartoon Network PT + Cartoonito PT + Casa e Cozinha + Caza y Pesca ES + CBS Reality + CCTV 4 Europe + CGTN + Cinemundo + CMTV + CNBC Europe + CNN Europe + CNN PT + Comedy Central DE + Deutsche Welle English + Deutsche Welle Deutsch + Discovery Channel PT + Discovery Science + TLC PT + Disney Channel PT + Disney Junior PT + Stingray Djazz + DocuBox HD + Sport 1 DE + Eleven Sports 1 PT + Eleven Sports 2 PT + Eleven Sports 3 PT + Eleven Sports 4 PT + Eleven Sports 5 PT + Eleven Sports 6 PT + E! Entertainment + Eurochannel + EuroNews + Eurosport 1 PT + Eurosport 2 PT + Fashion TV + FashionBox HD + Fast&FunBox HD + FightBox HD + FilmBox Arthouse + Food Network + Food Network UK + FOX Comedy PT + FOX Crime PT + FOX Life PT + FOX Movies PT + FOX NEWS + FOX PT + France24 + France 24 French + Fuel TV + FunBox UHD + Gametoon HD + Ginx Esports TV + Canal HISTÓRIA PT + Canal Hollywood + Insight TV + Investigation Discovery + JimJam + KiKA + LUXE TV + M6 + MCM Top + Mezzo + Mezzo Live HD + Motorvision TV + MTV 00s + MTV ES + MTV Live HD + MTV PT + Museum TV + MyZen TV + Nat Geo Wild ES + National Geographic Portugal + The Nautical Channel + Nick Jr. PT + Nickelodeon PT + Nickelodeon Ukraine Pluto + Odisseia + Phoenix + Playboy TV + ProSieben + RAI UNO + RAI DUE + RT Documentary + RTL DE + RTP1 + RTP2 + RTP3 + RTP Açores + RTP Africa + RTP Madeira + RTP Memória + RTP Internacional + Russia Today + Russia Today ES + Sat.1 + SIC + SIC Caras + SIC Internacional + SIC K + SIC Mulher + SIC Noticias + SIC Radical + Sky News + Sport TV+ + Sport TV1.pt + Sport TV2 + Sport TV3 + Sport TV4 + Sport TV5 + Sport TV6 + Stingray CMusic + Stingray iConcerts + Super RTL + Syfy PT + Toros TV + Trace Urban + Travel Channel + TV5 Monde + TV5Monde Europe + TV Cine Action + TV Cine Edition + TV Cine Emotion + TV Cine Top + TVE Internacional + TV Internacional + TVI Ficcao + TVI Reality + VOX + ZDF + ZDFneo + AgroTV RO + Al Jazeera + Al Jazeera Arabic English + AMC RO + Antena1 RO + Antena 3 CNN + Antena Stars RO + ARTE FR + ATV Spirit HU + Auto Motor und Sport + AXN Black RO + AXN RO + AXN Spin RO + AXN White RO + B1 TV RO + BabyTV + Balkanika TV + BBC Earth + BBC First + BBC World News + Bloomberg TV + Bollywood-Classic RO + Bollywood HD RO + Bollywood Film RO + Boomerang + Bucuresti TV RO + Canal 33 RO + Cartoon Network RO + Cartoonito CEE + CBS Reality + CCTV 4 Europe + CGTN + CGTN Documentary + Cinemaraton RO + Cinemax 2 RO + Cinemax RO + Cinethronix RO + CNBC Europe + CNN Europe + Comedy Central Extra BG + Comedy Central Family HU + Comedy Central HU + Comedy Central RO + CoolTV + Credo TV + Crime & Investigation Channel + Da Vinci Learning + Deutsche Welle English + Digi 24 + Digi Animal World RO + Digi Life RO + TV Digi Sport 2 + TV Digi Sport 3 + TV Digi Sport 4 + Digi World RO + Discovery Animal Planet + Discovery Channel + Discovery Science + TLC + Discovery Turbo Extra + Disney Channel RO + Disney Junior + Diva Universal RO + DocuBox HD + Dorcel TV + Dorcel XXX + DuckTV + Duna TV + Duna World + E! Entertainment + Epic Drama (CEE) + Eroxxx HD + Etno TV RO + EuroNews + Eurosport + Eurosport 1 INT + Eurosport 2 INT + Extreme Sports Channel + Fashion TV + Fast&FunBox HD + Favorit TV RO + FEM3 + FightBox HD + Film Cafe RO + Film Now RO + Film+ HU + FilmBox Extra RO + FilmBox Family RO + FilmBox Plus RO + FilmBox Premium RO + FilmBox RO + FilmBox Stars BG + Filmbox Stars HU + The Fishing and Hunting RO + Focus TV + Food Network + Galaxy4 + H!T Music Channel RO + Happy Channel RO + HBO 2 RO + HBO 3 RO + HBO RO + Home & Garden Television + Hír TV + The History Channel + HUSTLER TV + IDA RO + Inedit TV RO + Investigation Discovery + Izaura TV + JimJam + Kanal D RO + Kiss TV RO + Linkpress TV RO + Love Nature + Magyar Televízió 1 + M2 Petőfi + M4 sport + Magic TV BG + ATV HU + Magyar Sláger TV + Mediaset Italia + Medika TV RO + Mezzo + Mezzo Live HD + Minimax HU + Minimax RO + Moldova TV + Mooz Dance + Mooz HD RO + Mooz Hits RO + Mooz Ro RO + Motorvision HD RO + Motorvision TV + MTV 00s + Club MTV International + MTV Europe HU + MTV Hits International + MTV Live HD + MTV Music UK + MTV 90s International + Museum HD RO + Music Channel 1 RO + Muzsika TV + MyZen TV + National 24 Plus RO + Nasul TV RO + National Geographic People RO + Nat Geo Wild RO + National Geographic RO + National TV RO + The Nautical Channel + Nick Junior PL + Nickelodeon Commercial + Nicktoons PL + TV Paprika RO + Polsat Sport Extra + Prima Sport 1 RO + Prima Sport 2 RO + Prima Sport 3 RO + Prima Sport 4 RO + Prima Sport 5 RO + Prima TV RO + Prime + Brazzers TV (ex. Private Spice) + Private TV + Mozi plusz TV + Acasă + ProSieben + Pro Cinema RO + Acasă Gold + PRO TV Internațional + PRO ARENA RO + Profit RO + ProTV + RAI UNO + RAI TRE + Realitatea Plus RO + Reality Kings + România TV + RTL DE + RTL Gold + RTL Ketto + RTL HU + RTLup + Sat.1 + Sky News + Sorozat+ + Speranta TV + Spíler1 TV + Stingray CMusic + Stingray Classica + Stingray iConcerts + Story4 + Super RTL + Super TV2 + Taraf Tv RO + Timeless Drama Channel + TeenNick + TV1000 Russian Kino RO + Orange Sport 1 + Orange Sport 2 + Orange Sport 3 + Orange Sport 4 + Trace Urban + Travel Channel + Travel Mix RO + Travelxp + Trinitas HD RO + TV2 Comedy + TV2 HU + TV2 Kids + TV2 Séf + TV4 HU + TV5Monde Europe + TV1000 Balkan + TV1000 Global Kino + TV Digi Sport 1 + TV Paprika HU + TV SudEst + TVE Internacional + Televiziunea Româna 1 + Televiziunea Româna 2 + TVR3 RO + TVR Cluj RO + TVR Craiova RO + TVR Iasi RO + TVR Tg-Mures RO + TVR Timisoara RO + Televiziunea Româna International + UTV RO + MTV 80s + Viasat Explore CEE + Viasat History + Viasat Nature CEE + VTV RO + Warner TV RO + Zenebutik + Zu TV + 3SAT + 24 Kitchen + Agro TV + Al Jazeera + Al Jazeera Balkans + ALFA TV + Алсат М + AMC SI + Anixe HD Serie + Das Erste + Arena Esport + Arena Fight + ARTE FR + ARTE DE + Alternativna televizija Banja Luka + AXN + AXN Spin + B92 + BabyTV + Balkan trip + Balkanika TV + BBC Earth + BBC First + BBC World News + Radio Televizija BIH + BK TV + Bloomberg TV + Bloomberg Adria + Radio Televizija BN + BN music + BN 2 HD + БНТ 2 + Boomerang + Canale 5 + Cartoon Network + Cartoon Network DE + Cartoonito CEE + CBS Reality + CCTV 4 Europe + CGTN + Cinemax + Cinemax 2 + CineStar Action&Thriller + CineStar Premiere 1 + CineStar Premiere 2 + CineStar TV1 + CineStar TV2 + CineStar TV Comedy Family + CineStar TV Fantasy + English Club TV + Croatian Music Channel + CNBC Europe + CNN Europe + Comedy Central DE + Nicktoons + Crime & Investigation Channel + Da Vinci Learning + Discovery Animal Planet + Discovery Channel + Discovery Science + TLC + Discovery Turbo Extra + Disney Channel + Disney Junior + DM SAT + DocuBox HD + Dorcel TV + Dorcel XXX + Dr. Fit HD + Sport 1 DE + DuckTV + Duna TV + Duna World + Elta 1 HD + E! Entertainment + Epic Drama (CEE) + Erox HD + Eroxxx HD + Eurochannel + EuroNews + Eurosport + Eurosport 2 + EWTN + Exodus TV + Extreme Sports Channel + Fashion TV + FashionBox HD + Fast&FunBox HD + FightBox HD + SK Fight + FilmBox Arthouse + FilmBox Extra RS + FilmBox Stars RS + The Fishing and Hunting + Focus TV + STAR Crime SI + STAR Life SI + STAR Movies SI + STAR SI + France24 + France 2 + France 24 French + Radio Televizija Federacije BIH + GEA TV + Ginx Esports TV + SK Golf + Golica TV + Gorenjska televizija + Grand Televizija + HappyTV + Hayat Folk + Hayat Music + Hayat Plus + Hayat TV + Hayatovci + HBO + HBO3 + HBO2 + Home & Garden Television + The History Channel + History Channel 2 + HIT TV + HRT 1 + HRT 2 + HRT 3 + HRT 4 + HUSTLER TV + IDJ World + Investigation Discovery + Italia 1 + Otvorena televizija + JimJam + Jugoton TV + K::CN 2 Music + Kabel 1 + Канал 5 + Kanal A, SLO + Kanal D + Kanal Rijeka + KiKA + Klape i Tambure TV + KLASIK + K::CN 1 Kopernikus + KTV Ormož + Living HD + Lov i ribolov + LUXE TV + M2 Petőfi + TV Maribor + Mediaset Italia + Mezzo + Mezzo Live HD + Minimax + Minimax SI + Motorvision TV + МРТ 1 + МРТ 2 + МРТ 3 + MTV Europe + MTV 00s + Club MTV International + MTV DE + MTV Hits International + MTV Live HD + MTV 90s International + MyZen TV + N1 BA + N1 HR + N1 RS + NTV DE + Nat Geo Wild SI + National Geographic + National Geographic Channel HD + Nat Geo Wild + The Nautical Channel + NBA TV + MrežaZG + Net TV + Nat Geo Wild HD + Nick Junior + Nickelodeon + Nickelodeon Commercial + NOVA TV + NTV IC Kakanj + O Kanal + OBN + ORF1 + ORF2 + OTV Valentino + Pickbox TV SI + Pikaboo 2 + RTV PINK + RED tv + TV PINK EXTRA + PINK Family + Pink Fashion + PINK Film + Pink Folk 1 + Pink Folk 2 + Pink Kids + Pink Movies + Pink Music 1 + TV PINK PLUS + Pink Reality + Pink Serije + Pink Show + PINK World + PINK Zabava + Planet 2 + Planet Earth + Planet TV SI + Playboy TV + POP TV + Brazzers TV (ex. Private Spice) + Private TV + ProSieben + Prva Srpska TV + Prva World + Ptujska televizija + RAI UNO + RAI DUE + RAI TRE + RAI 3 Bis + RAI Education + RAI Gulp + RAI News 24 + RAI Sport 1 + RAI Storia + Rai Yoyo + Reality Kings + RedLight HD + Rete 4 + Россиᴙ 24 + RTK 1 + RTL + RTL 2 + RTL Zwei + RTL DE + RTL Kockica + RTL Living + RTRS + RTS 1 + RTS 2 + RTS SVET + RTV International + RTV Unsko-sanskog kantona + RTV Tuzlanskog Kantona + Russia Today + Sat.1 + SciFi + ServusTV + Sexation TV + Televizija skupnih internih programov + Сител + Sixx + SK 4K + SK esports + Sky News + RTV Slovenija 1 + RTV Slovenija 2 + RTV Slovenija 3 + SOS Plus + Sport Klub 4 + Sport Klub 5 + Sport Klub 6 + Sport Klub HD + Sportitalia + Sport Klub 3 + Sport Klub 2 Srbija + Stingray iConcerts + ŠTV3 + Super RTL + K::CN 3 Svet Plus + Televizija Alfa + Televizija AS + Telma + Top TV + Toxic TV + Trace Urban + Travel Channel + Travelxp + TV3 + TV 3 Medias + TV5 Monde + TV1000 Balkan + TV ATM + TV Celje + TV Duga + SAT + TV Galeja + TV Idea + TV Jadran + TV Sarajevo + TV Veseljak + TV Vijesti + Televizija Crne Gore 1 + Televizija Crne Gore 2 + Televizija Crne Gore MNE + DIVA (ex. Universal) + Varaždinska Televizija + Vaš kanal + Vavoom + MTV 80s + Viasat Explore CEE + Viasat History + Viasat Nature CEE + Vikom + VOX + VTV Studio + Welt + Woman HD + XXL + Zagrebačka Televizija + ZDF + Zdrava televizija + 1-2-3.tv + 21 Mix + A2 CNN + A Spor + ABC News Albania + Apollon TV + ART Sport 1 + ART Sport 2 + ART Sport 3 + ART Sport 4 + ART Sport 5 + ART Sport 6 + ArtDoku 1 + ArtDoku 2 + ArtKino 1 + ArtKino 2 + ArtKino 3 + ATV TR + BabyTV + Bang Bang + Beyaz TV + Bubble TV + Cartoon Network + Cartoonito CEE + City TV + Click TV + CNBC Europe + CNN Türk TV + Comedy Central Extra UK + Deluxe Music + DigitAlb T HD + Discovery Animal Planet + Discovery Channel + Discovery Turbo UK + Disney Channel + DMAX DE + Elrodi + Euronews Albania + FAX News + Film Autor + FilmBox Extra RS + FilmBox Stars RS + STAR Channel + STAR Life + HSE24 Extra + HSE24 Trend + Info24 Albania + INTV AL + Junior TV + K-Sport 5 + Kabel 1 + Kabel eins Doku + Kanal 7 + Kanal 10 + Kanal D + Kanali 7 + KiKA + Klan Music + Klan Plus + Kopliku TV + MCN TV + DigitAlb Melody TV + My Music + N24 Doku + NTV DE + National Geographic + National Geographic Channel HD + Nat Geo Wild + Ndihma e Klientit + Nickelodeon + Premium Channel + QVC 2 DE + QVC Deutschland + RAI 4 + RAI 5 + RAI Movie + RAI Premium + Rai Yoyo + RTLup + RTS 2 + RTSH Agro + RTSH Femijë + RTSH Film + RTSH Gjirokastra + RTSH Korça + RTSH Kuvend + RTV 21 Popullore + RTV Besa + RTV Ora + Scan TV + Shenja TV + Show TV + Sixx + SuperSport 7 AL + Syri TV + Syri Vizion + Timeless Drama Channel + Tele 5 DE + TGRT Belgesel + Tring Bizarre + Tring Bunga Bunga + Tring Collection + Tring Desire + Tring Kanal 7 + Tring Novelas + Tring Sport 4 HD + Tring Sport 5 HD + TV 7 + TV Dukagjini + VOX up + Welt + ZDFneo + Zico TV + Zjarr TV + MUSE + News 24 + Ora News + Report TV + RTSH 1 + RTSH 2 + RTSH 3 + RTSH 24 + RTSH Plus + RTSH Shkollë + RTSH Shqip + RTSH Sport + Tring Smile + Stinet + STV Folk + SuperSport 1 AL + SuperSport 2 AL + SuperSport 3 AL + SuperSport 4 AL + SuperSport 5 AL + SuperSport 6 AL + Tip TV + Top Channel + Top News + Tring Action + Tring Comedy + Tring History + Tring Jolly HD + Tring Kids + Tring Life + Tring Planet + Tring Shqip + Tring Sport 3 HD + Tring Sport News + Tring Super + Tring Tring + Tring World + Klan TV HD + Vizion Plus + 3SAT + 4Fun Dance + 4Fun Kids + 4FunTV + 24 Kitchen + 24 ВЕСТИ + 360 TuneBox + Agro TV + Al Jazeera + Al Jazeera Balkans + ALFA TV + AMC + AMC HU + ANIXE plus + Das Erste + Arena Sport 9 RS + Arena Sport 10 RS + Aurora TV + AXN + AXN Spin + B1 TV + B92 + BabyTV + Balkan trip + Balkan TV + Balkanika TV + BBC Earth + BBC News Channel + BBC World News + Bit TV + BK TV + BlicTV + Bloomberg TV + Bloomberg Adria + Radio Televizija BN + BN music + BN 2 HD + Boomerang + Brainz TV + Bravo Music + Cartoon Network + Cartoonito CEE + Cartoonito UK + CBS Reality + CCTV 4 Europe + CGTN + CGTN Documentary + Первый + Cinemania TV + Cinemax + Cinemax 2 + Cinemax 2 HU + Cinemax HU + CineStar Action&Thriller RS + CineStar Premiere 1 + CineStar Premiere 2 + CineStar TV2 + CineStar TV Comedy Family + CineStar TV Fantasy + CineStar TV RS + City TV + Class TV Moda + Croatian Music Channel + CNBC Europe + CNN Europe + Nicktoons + Comedy Central HU + Comedy Central UK + CoolTV + Crime & Investigation Channel + Da Vinci Learning + Dajto + Deluxe Music + Deutsche Welle English + Dexy TV + Digi 24 + Discovery Animal Planet + Discovery Channel HU + Discovery Channel + Discovery Science + TLC + Discovery Turbo Extra + Disney Channel + Disney Channel DE + Disney Channel HU + Disney Junior + DMAX DE + DM SAT + DocuBox HD + Dorcel TV + Dorcel XXX + DOX TV + Dr. Fit HD + Sport 1 DE + DuckTV + Duna TV + Duna World + E! Entertainment + Epic Drama (CEE) + Erox HD + Eroxxx HD + Eska TV Extra + Etno TV RO + Eurochannel + EuroNews + Euronews FR + EuroNews Srbija + Eurosport + Eurosport 2 + Eurosport 1 DE + Extreme Sports Channel + FACE TV + Fashion TV + FashionBox HD + Fast&FunBox HD + Favorit TV RO + FEM3 + FightBox HD + SK Fight + Film4 HU + Film Klub + Film Klub Extra + FilmBox Arthouse + FilmBox Extra RS + Filmbox Extra HU + FilmBox Stars RS + FilmBox Premium RS + Filmbox Premium HU + Filmbox Stars HU + Folklorika SK + Food Network + STAR Channel + STAR Crime + STAR Life + STAR Movies + FOX NEWS + France24 + France 24 French + Radio Televizija Federacije BIH + Gametoon HD + Golica TV + Grand Televizija + Grand nostalgija + HappyTV + Hayat Folk + Hayat Music + Hayat Plus + Hayat TV + HBO + HBO 2 HU + HBO3 + HBO 3 HU + HBO2 + HBO HU + Home & Garden Television + Hír TV + The History Channel + History Channel 2 + HSE + HRT 1 + HRT 2 + HRT 3 + HRT 4 + HUSTLER TV + Hype TV + IDJ World + Info24 Albania + Insajder TV + Insta TV + Investigation Discovery + Izaura TV + JimJam + JOJ Plus + JOJ TV + K1 TV + K::CN 2 Music + K-Sport 5 + Kabel 1 + Kabel eins Doku + Kanal 9 TV + Kanal 25 + KiKA + KitchenTV + KLASIK + K::CN 1 Kopernikus + Kurir TV + Lala TV + LifeTV SK + Lov i ribolov + Magyar Televízió 1 + M2 Petőfi + M4 sport + M4 Sport Plus + m5 + Mediaset Italia + Mezzo + Minimax + Minimax HU + Moj Happy Život + Moja Happy Muzika + Moja Happy Zemlja + Moje Happy Društvo + MTV Europe + MTV 00s + Club MTV International + MTV Hits International + MTV Live HD + MTV 90s International + Museum TV + Muzsika TV + N1 HR + N1 RS + N24 Doku + Nasa TV US + National Geographic + National Geographic Channel HD + National Geographic HU + National Geographic RS + Nat Geo Wild + NBA TV + Nat Geo Wild HD + Nick Junior + Nick Junior PL + Nick Music + Nickelodeon Commercial + Nicktoons PL + NOVA TV + Nova Max + Nova S + Nova Series + Nova Sport Srbija + RTV Novi Pazar + Now 90s + NTV 101 Sanski most + O Kanal + OBN + Ozone Network + Pickbox TV RS + Pikaboo + RTV PINK + RED tv + Vesti + Pink Action + Pink Thriller + Pink Crime & Mystery + Pink BH + Pink Classic + Pink Comedy + Pink Erotic 1 + Pink Erotic 2 + Pink Erotic 3 + Pink Erotic 4 + Pink Erotic 5 + Pink Erotic 6 + Pink Erotic 7 + Pink Erotic 8 + TV PINK EXTRA + PINK Family + Pink Fashion + Pink Fight Network + PINK Film + Pink Folk 1 + Pink Folk 2 + Pink Ha Ha + Pink Hits + Pink Hits 2 + Pink Horror + Pink Kids + Pink Koncert + Pink Kuvar + Pink LOL + Pink M + Pink Movies + Pink Romance + Pink SCI FI & Fantasy + Pink Music 1 + Pink and Roll + Pink Parada + Pink Pedia + TV PINK PLUS + Pink Premium + Pink Reality + Pink Serije + Pink Show + Pink Soap + Pink Style + Pink Super Kids + Pink Timeout + Pink Western + PINK World + Pink World Cinema + PINK Zabava + Planet Earth + Planet TV SI + Playboy TV + Brazzers TV (ex. Private Spice) + Private TV + ProSieben + ProSieben MAXX + Prva FILES + Prva KICK + Prva LIFE + Prva MAX + Prva Plus + Prva Srpska TV + Prva World + QVC Deutschland + RAI UNO + RAI DUE + RAI TRE + RAI Education + RAI News 24 + RAI Storia + Reality Kings + Россиᴙ 24 + RT Documentary + RTL + RTL Croatia World + RTL Gold + RTL Három + RTL Nitro + RTLup + RTRS + RTRS PLUS + RTS 1 + RTS 2 + RTS 3 + RTS Drama + RTS Klasika + RTS Kolo + RTS Muzika + RTS Nauka + RTS Poletarac + RTS SVET + RTS Trezor + RTS Život + RTSH 3 + RTSH 24 + RTSH Plus + RTSH Shkollë + RTV HIT Brčko + RTV International + RTV Pančevo + RTVS Dvojka + RTVS Jednotka + Russia Today + Sat.1 + Sat.1 Gold + SAT TV + SciFi + Sixx + SK1 BiH + SK 4K + Sky News + RTV Slovenija 1 + RTV Slovenija 2 + RTV Slovenija 3 + Sorozat+ + SOS Plus + Spektrum Home HU + Sport Klub 7 + Sport Klub 8 + Sport Klub 9 + Sport Klub 10 + Sremska TV + Superstar 2 + Stars TV PL + Stingray Classica + Stingray iConcerts + Studio B + Super RTL + Super TV2 + SuperSat TV + Superstar TV + K::CN 3 Svet Plus + TA3 SK + Tanjug Tačno + TBN Polska + Timeless Drama Channel + Tele 5 DE + Televizija 5 + Televizija Alfa + TLC DE + TOGGO plus + Top TV + Toxic Folk + Toxic Rap + Toxic TV + Trace Urban + Travel Channel + Travelxp + TV2 Séf + TV4 HU + TV5 Monde + TV5Monde Europe + TV1000 Balkan + TV Doma + TV Duga + SAT + Телевизија Храм + TV K23 + TV Markíza SK + RTV Most + TV Paprika HU + TV Ras + TV Vijesti + Televizija Crne Gore MNE + Televizija Doktor + TVR Cluj RO + TVR Craiova RO + TVR Iasi RO + TVR Tg-Mures RO + TVR Timisoara RO + Televiziunea Româna International + UNA TV + DIVA (ex. Universal) + Vavoom + MTV 80s + Viasat Explore CEE + Viasat History + Viasat Nature CEE + RT Vojvodina 1 + RT Vojvodina 2 + VOX + VOX up + Woman HD + Zagrebačka Televizija + ZDF + ZDFinfo + ZDFneo + 24 Kitchen + 24Kitchen TR + 360 TV + A2TV TR + a Haber + a News + A Para + A Spor + Al Jazeera + Al Jazeera Arabic English + ATV TR + BabyTV TR + BBC First + BBC World News + BBN Türk + beIN Box Office 2 TR + beIN Box Office 3 TR + beIN Box Office 1 TR + beIN GURME + beIN HOME & ENTERTAINMENT + beIN iZ + beIN Movies Premiere 2 + beIN Movies Premiere TR + beIN Movies Stars + beIN Movies Turk + beIN Series 1 TR + beIN Series 2 TR + beIN Series 3 TR + beIN Series 4 TR + beIN Sports 1 TR + beIN Sports 2 TR + beIN Sports 3 TR + beIN Sports 4 TR + beIN Sports Haber + beIN Sports MAX 1 TR + beIN Sports MAX 2 TR + beIN TR + Beyaz TV + Bloomberg TV + Bloomberg HT + Boomerang + Boomerang TR + Cartoon Network TR + Cbeebies + CGTN + CGTN Documentary + CNBC Europe + CNN Europe + CNN Türk TV + Nicktoons + Da Vinci Learning + Da Vinci Learning TR + Deutsche Welle English + Discovery Channel TR + Discovery Channel + Discovery Science + TLC + Disney Junior + Dizi Smart Max + Dizi Smart Premium + DMAX TR + DocuBox HD + Dream Türk + Ekotürk + Epic Drama (CEE) + Euro D + Euro Star + EuroNews + Eurosport 2 + Fashion TV + Fast&FunBox HD + FENERBAHÇE TV + FilmBox TR + FOX Turkey + France24 + France 24 French + FX Turkey + Haber Global + Habertürk + Habitat TV + The History Channel + Insight TV + Kanal 7 + KANAL 24 TR + Kanal D + Kanal D TR + Kral Pop TV + KRT TV + Love Nature + MCM FR + MCM Top + Mezzo + MinikaGO TR + MovieSmart Classic + MovieSmart Türk + MTV 00s + MTV Hits International + MTV Live HD + National Geographic Wild TR + National Geographic Turkey + NBA TV + Nick Junior + Number One Türk + NTV TR + Power TV + Power Türk TV + S Sport + S Sport 2 + Show Türk + Show TV + SinemaTV 2 + SinemaTV 1001 + SinemaTV 1002 + SinemaTV Aile + SinemaTV Aile 2 + SinemaTV Aksiyon + SinemaTV Aksiyon 2 + SinemaTV Komedi + SinemaTV Komedi 2 + SinemaTV Yerli + SinemaTV Yerli 2 + SinemaTV + Spor Smart + Spor Smart 2 + Sports TV + Star TV TR + TAY TV + TELE 1 + teve2 + TGRT Belgesel + TGRT EU + TGRT HABER + Tivibu Spor + Tivibu Spor 2 + Tivibu Spor 3 + Tivibu Spor 4 + Tivibu Spor 5 + TLC TR + Trace Urban + TRT 1 + TRT 2 + TRT Spor + TRT 4K + TRT Kurdî + TRT Arapça + TRT Avaz + TRT Belgesel + TRT Çocuk + TRT Diyanet + TRT EBA TV İlkokul + TRT EBA TV Lise + TRT EBA TV Ortaokul + TRT Haber + TRT Müzik + TRT Spor Yıldız + TRT Turk + TRT World + TV5 + TV100 TR + TV 2 TR + TV 8 TR + TV 8.5 + TV Net + Uçankuş TV + Ülke TV + Viasat Explore CEE + Viasat History + Viasat Nature CEE + Yaban TV + diff --git a/sites/web.magentatv.de/web.magentatv.de.config.js b/sites/web.magentatv.de/web.magentatv.de.config.js index 3a38685ab..688643298 100644 --- a/sites/web.magentatv.de/web.magentatv.de.config.js +++ b/sites/web.magentatv.de/web.magentatv.de.config.js @@ -1,213 +1,213 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -let X_CSRFTOKEN -let Cookie -const cookiesToExtract = ['JSESSIONID', 'CSESSIONID', 'CSRFSESSION'] - -dayjs.extend(utc) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'web.magentatv.de', - days: 2, - url: 'https://api.prod.sngtv.magentatv.de/EPG/JSON/PlayBillList', - request: { - method: 'POST', - async headers() { - return await setHeaders() - }, - data({ channel, date }) { - return { - count: -1, - isFillProgram: 1, - offset: 0, - properties: [ - { - include: - 'endtime,genres,id,name,starttime,channelid,pictures,introduce,subName,seasonNum,subNum,cast,country,producedate,externalIds', - name: 'playbill' - } - ], - type: 2, - begintime: date.format('YYYYMMDD000000'), - channelid: channel.site_id, - endtime: date.add(1, 'd').format('YYYYMMDD000000') - } - } - }, - parser({ content }) { - const programs = [] - const items = parseItems(content) - items.forEach(item => { - programs.push({ - title: item.name, - description: item.introduce, - image: parseImage(item), - category: parseCategory(item), - start: parseStart(item), - stop: parseStop(item), - sub_title: item.subName, - season: item.seasonNum, - episode: item.subNum, - directors: parseDirectors(item), - producers: parseProducers(item), - adapters: parseAdapters(item), - country: item.country?.toUpperCase(), - date: item.producedate, - urls: parseUrls(item) - }) - }) - return programs - }, - async channels() { - const url = 'https://api.prod.sngtv.magentatv.de/EPG/JSON/AllChannel' - const body = { - channelNamespace: 2, - filterlist: [ - { - key: 'IsHide', - value: '-1' - } - ], - metaDataVer: 'Channel/1.1', - properties: [ - { - include: '/channellist/logicalChannel/contentId,/channellist/logicalChannel/name', - name: 'logicalChannel' - } - ], - returnSatChannel: 0 - } - const params = { - headers: await setHeaders() - } - - const data = await axios - .post(url, body, params) - .then(r => r.data) - .catch(console.log) - - return data.channellist.map(item => { - return { - lang: 'de', - site_id: item.contentId, - name: item.name - } - }) - } -} - -function parseCategory(item) { - return item.genres - ? item.genres - .replace('und', ',') - .split(',') - .map(i => i.trim()) - : [] -} - -function parseDirectors(item) { - if (!item.cast || !item.cast.director) return [] - return item.cast.director - .replace('und', ',') - .split(',') - .map(i => i.trim()) -} - -function parseProducers(item) { - if (!item.cast || !item.cast.producer) return [] - return item.cast.producer - .replace('und', ',') - .split(',') - .map(i => i.trim()) -} - -function parseAdapters(item) { - if (!item.cast || !item.cast.adaptor) return [] - return item.cast.adaptor - .replace('und', ',') - .split(',') - .map(i => i.trim()) -} - -function parseUrls(item) { - // currently only a imdb id is returned by the api, thus we can construct the url here - if (!item.externalIds) return [] - return JSON.parse(item.externalIds) - .filter(externalId => externalId.type === 'imdb' && externalId.id) - .map(externalId => ({ system: 'imdb', value: `https://www.imdb.com/title/${externalId.id}` })) -} - -function parseImage(item) { - if (!Array.isArray(item.pictures) || !item.pictures.length) return null - - return item.pictures[0].href -} - -function parseStart(item) { - return dayjs.utc(item.starttime, 'YYYY-MM-DD HH:mm:ss') -} - -function parseStop(item) { - return dayjs.utc(item.endtime, 'YYYY-MM-DD HH:mm:ss') -} - -function parseItems(content) { - const data = JSON.parse(content) - if (!data || !Array.isArray(data.playbilllist)) return [] - - return data.playbilllist -} - -async function fetchCookieAndToken() { - // Only fetch the cookies and csrfToken if they are not already set - if (X_CSRFTOKEN && Cookie) { - return - } - - try { - const response = await axios.request({ - url: 'https://api.prod.sngtv.magentatv.de/EPG/JSON/Authenticate', - params: { - SID: 'firstup', - T: 'Windows_chrome_118' - }, - method: 'POST', - data: '{"terminalid":"00:00:00:00:00:00","mac":"00:00:00:00:00:00","terminaltype":"WEBTV","utcEnable":1,"timezone":"Etc/GMT0","userType":3,"terminalvendor":"Unknown"}', - }) - - // Extract the cookies specified in cookiesToExtract - const setCookieHeader = response.headers['set-cookie'] || [] - const extractedCookies = [] - cookiesToExtract.forEach(cookieName => { - const regex = new RegExp(`${cookieName}=(.+?)(;|$)`) - const match = setCookieHeader.find(header => regex.test(header)) - - if (match) { - const cookieString = regex.exec(match)[0] - extractedCookies.push(cookieString) - } - }) - - // check if we recieved a csrfToken only then store the values - if (!response.data.csrfToken) { - console.log('csrfToken not found in the response.') - return - } - - X_CSRFTOKEN = response.data.csrfToken - Cookie = extractedCookies.join(' ') - - } catch(error) { - console.error(error) - } -} - -async function setHeaders() { - await fetchCookieAndToken() - - return { X_CSRFTOKEN, Cookie } -} +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +let X_CSRFTOKEN +let Cookie +const cookiesToExtract = ['JSESSIONID', 'CSESSIONID', 'CSRFSESSION'] + +dayjs.extend(utc) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'web.magentatv.de', + days: 2, + url: 'https://api.prod.sngtv.magentatv.de/EPG/JSON/PlayBillList', + request: { + method: 'POST', + async headers() { + return await setHeaders() + }, + data({ channel, date }) { + return { + count: -1, + isFillProgram: 1, + offset: 0, + properties: [ + { + include: + 'endtime,genres,id,name,starttime,channelid,pictures,introduce,subName,seasonNum,subNum,cast,country,producedate,externalIds', + name: 'playbill' + } + ], + type: 2, + begintime: date.format('YYYYMMDD000000'), + channelid: channel.site_id, + endtime: date.add(1, 'd').format('YYYYMMDD000000') + } + } + }, + parser({ content }) { + const programs = [] + const items = parseItems(content) + items.forEach(item => { + programs.push({ + title: item.name, + description: item.introduce, + image: parseImage(item), + category: parseCategory(item), + start: parseStart(item), + stop: parseStop(item), + sub_title: item.subName, + season: item.seasonNum, + episode: item.subNum, + directors: parseDirectors(item), + producers: parseProducers(item), + adapters: parseAdapters(item), + country: item.country?.toUpperCase(), + date: item.producedate, + urls: parseUrls(item) + }) + }) + return programs + }, + async channels() { + const url = 'https://api.prod.sngtv.magentatv.de/EPG/JSON/AllChannel' + const body = { + channelNamespace: 2, + filterlist: [ + { + key: 'IsHide', + value: '-1' + } + ], + metaDataVer: 'Channel/1.1', + properties: [ + { + include: '/channellist/logicalChannel/contentId,/channellist/logicalChannel/name', + name: 'logicalChannel' + } + ], + returnSatChannel: 0 + } + const params = { + headers: await setHeaders() + } + + const data = await axios + .post(url, body, params) + .then(r => r.data) + .catch(console.log) + + return data.channellist.map(item => { + return { + lang: 'de', + site_id: item.contentId, + name: item.name + } + }) + } +} + +function parseCategory(item) { + return item.genres + ? item.genres + .replace('und', ',') + .split(',') + .map(i => i.trim()) + : [] +} + +function parseDirectors(item) { + if (!item.cast || !item.cast.director) return [] + return item.cast.director + .replace('und', ',') + .split(',') + .map(i => i.trim()) +} + +function parseProducers(item) { + if (!item.cast || !item.cast.producer) return [] + return item.cast.producer + .replace('und', ',') + .split(',') + .map(i => i.trim()) +} + +function parseAdapters(item) { + if (!item.cast || !item.cast.adaptor) return [] + return item.cast.adaptor + .replace('und', ',') + .split(',') + .map(i => i.trim()) +} + +function parseUrls(item) { + // currently only a imdb id is returned by the api, thus we can construct the url here + if (!item.externalIds) return [] + return JSON.parse(item.externalIds) + .filter(externalId => externalId.type === 'imdb' && externalId.id) + .map(externalId => ({ system: 'imdb', value: `https://www.imdb.com/title/${externalId.id}` })) +} + +function parseImage(item) { + if (!Array.isArray(item.pictures) || !item.pictures.length) return null + + return item.pictures[0].href +} + +function parseStart(item) { + return dayjs.utc(item.starttime, 'YYYY-MM-DD HH:mm:ss') +} + +function parseStop(item) { + return dayjs.utc(item.endtime, 'YYYY-MM-DD HH:mm:ss') +} + +function parseItems(content) { + const data = JSON.parse(content) + if (!data || !Array.isArray(data.playbilllist)) return [] + + return data.playbilllist +} + +async function fetchCookieAndToken() { + // Only fetch the cookies and csrfToken if they are not already set + if (X_CSRFTOKEN && Cookie) { + return + } + + try { + const response = await axios.request({ + url: 'https://api.prod.sngtv.magentatv.de/EPG/JSON/Authenticate', + params: { + SID: 'firstup', + T: 'Windows_chrome_118' + }, + method: 'POST', + data: '{"terminalid":"00:00:00:00:00:00","mac":"00:00:00:00:00:00","terminaltype":"WEBTV","utcEnable":1,"timezone":"Etc/GMT0","userType":3,"terminalvendor":"Unknown"}', + }) + + // Extract the cookies specified in cookiesToExtract + const setCookieHeader = response.headers['set-cookie'] || [] + const extractedCookies = [] + cookiesToExtract.forEach(cookieName => { + const regex = new RegExp(`${cookieName}=(.+?)(;|$)`) + const match = setCookieHeader.find(header => regex.test(header)) + + if (match) { + const cookieString = regex.exec(match)[0] + extractedCookies.push(cookieString) + } + }) + + // check if we recieved a csrfToken only then store the values + if (!response.data.csrfToken) { + console.log('csrfToken not found in the response.') + return + } + + X_CSRFTOKEN = response.data.csrfToken + Cookie = extractedCookies.join(' ') + + } catch(error) { + console.error(error) + } +} + +async function setHeaders() { + await fetchCookieAndToken() + + return { X_CSRFTOKEN, Cookie } +} diff --git a/tests/commands/channels/lint.test.ts b/tests/commands/channels/lint.test.ts index 61b9f53b0..38b2ba6b4 100644 --- a/tests/commands/channels/lint.test.ts +++ b/tests/commands/channels/lint.test.ts @@ -1,83 +1,83 @@ -import { execSync } from 'child_process' - -type ExecError = { - status: number - stdout: string -} - -describe('channels:lint', () => { - it('will show a message if the file contains a syntax error', () => { - try { - const cmd = 'npm run channels:lint --- tests/__data__/input/channels_lint/error.channels.xml' - const stdout = execSync(cmd, { encoding: 'utf8' }) - if (process.env.DEBUG === 'true') console.log(cmd, stdout) - process.exit(1) - } catch (error) { - expect((error as ExecError).status).toBe(1) - expect((error as ExecError).stdout).toContain( - "error.channels.xml\n 3:0 Element 'channel': The attribute 'lang' is required but missing.\n\n1 error(s)\n" - ) - } - }) - - it('will show a message if an error occurred while parsing an xml file', () => { - try { - const cmd = - 'npm run channels:lint --- tests/__data__/input/channels_lint/invalid.channels.xml' - const stdout = execSync(cmd, { encoding: 'utf8' }) - if (process.env.DEBUG === 'true') console.log(cmd, stdout) - process.exit(1) - } catch (error) { - expect((error as ExecError).status).toBe(1) - expect((error as ExecError).stdout).toContain( - 'invalid.channels.xml\n 2:6 XML declaration allowed only at the start of the document\n' - ) - } - }) - - it('can test multiple files at ones', () => { - try { - const cmd = - 'npm run channels:lint --- tests/__data__/input/channels_lint/error.channels.xml tests/__data__/input/channels_lint/invalid.channels.xml' - const stdout = execSync(cmd, { encoding: 'utf8' }) - if (process.env.DEBUG === 'true') console.log(cmd, stdout) - process.exit(1) - } catch (error) { - expect((error as ExecError).status).toBe(1) - expect((error as ExecError).stdout).toContain( - "error.channels.xml\n 3:0 Element 'channel': The attribute 'lang' is required but missing.\n" - ) - expect((error as ExecError).stdout).toContain( - 'invalid.channels.xml\n 2:6 XML declaration allowed only at the start of the document\n' - ) - expect((error as ExecError).stdout).toContain('2 error(s)') - } - }) - - it('will show a message if the file contains single quotes', () => { - try { - const cmd = - 'npm run channels:lint --- tests/__data__/input/channels_lint/single_quotes.channels.xml' - const stdout = execSync(cmd, { encoding: 'utf8' }) - if (process.env.DEBUG === 'true') console.log(cmd, stdout) - process.exit(1) - } catch (error) { - expect((error as ExecError).status).toBe(1) - expect((error as ExecError).stdout).toContain('single_quotes.channels.xml') - expect((error as ExecError).stdout).toContain( - '1:14 Single quotes cannot be used in attributes' - ) - } - }) - - it('does not display errors if there are none', () => { - try { - const cmd = 'npm run channels:lint --- tests/__data__/input/channels_lint/valid.channels.xml' - const stdout = execSync(cmd, { encoding: 'utf8' }) - if (process.env.DEBUG === 'true') console.log(cmd, stdout) - } catch (error) { - if (process.env.DEBUG === 'true') console.log((error as ExecError).stdout) - process.exit(1) - } - }) -}) +import { execSync } from 'child_process' + +interface ExecError { + status: number + stdout: string +} + +describe('channels:lint', () => { + it('will show a message if the file contains a syntax error', () => { + try { + const cmd = 'npm run channels:lint --- tests/__data__/input/channels_lint/error.channels.xml' + const stdout = execSync(cmd, { encoding: 'utf8' }) + if (process.env.DEBUG === 'true') console.log(cmd, stdout) + process.exit(1) + } catch (error) { + expect((error as ExecError).status).toBe(1) + expect((error as ExecError).stdout).toContain( + "error.channels.xml\n 3:0 Element 'channel': The attribute 'lang' is required but missing.\n\n1 error(s)\n" + ) + } + }) + + it('will show a message if an error occurred while parsing an xml file', () => { + try { + const cmd = + 'npm run channels:lint --- tests/__data__/input/channels_lint/invalid.channels.xml' + const stdout = execSync(cmd, { encoding: 'utf8' }) + if (process.env.DEBUG === 'true') console.log(cmd, stdout) + process.exit(1) + } catch (error) { + expect((error as ExecError).status).toBe(1) + expect((error as ExecError).stdout).toContain( + 'invalid.channels.xml\n 2:6 XML declaration allowed only at the start of the document\n' + ) + } + }) + + it('can test multiple files at ones', () => { + try { + const cmd = + 'npm run channels:lint --- tests/__data__/input/channels_lint/error.channels.xml tests/__data__/input/channels_lint/invalid.channels.xml' + const stdout = execSync(cmd, { encoding: 'utf8' }) + if (process.env.DEBUG === 'true') console.log(cmd, stdout) + process.exit(1) + } catch (error) { + expect((error as ExecError).status).toBe(1) + expect((error as ExecError).stdout).toContain( + "error.channels.xml\n 3:0 Element 'channel': The attribute 'lang' is required but missing.\n" + ) + expect((error as ExecError).stdout).toContain( + 'invalid.channels.xml\n 2:6 XML declaration allowed only at the start of the document\n' + ) + expect((error as ExecError).stdout).toContain('2 error(s)') + } + }) + + it('will show a message if the file contains single quotes', () => { + try { + const cmd = + 'npm run channels:lint --- tests/__data__/input/channels_lint/single_quotes.channels.xml' + const stdout = execSync(cmd, { encoding: 'utf8' }) + if (process.env.DEBUG === 'true') console.log(cmd, stdout) + process.exit(1) + } catch (error) { + expect((error as ExecError).status).toBe(1) + expect((error as ExecError).stdout).toContain('single_quotes.channels.xml') + expect((error as ExecError).stdout).toContain( + '1:14 Single quotes cannot be used in attributes' + ) + } + }) + + it('does not display errors if there are none', () => { + try { + const cmd = 'npm run channels:lint --- tests/__data__/input/channels_lint/valid.channels.xml' + const stdout = execSync(cmd, { encoding: 'utf8' }) + if (process.env.DEBUG === 'true') console.log(cmd, stdout) + } catch (error) { + if (process.env.DEBUG === 'true') console.log((error as ExecError).stdout) + process.exit(1) + } + }) +}) diff --git a/tests/commands/channels/validate.test.ts b/tests/commands/channels/validate.test.ts index 4dd18b37d..3a1527c3b 100644 --- a/tests/commands/channels/validate.test.ts +++ b/tests/commands/channels/validate.test.ts @@ -1,70 +1,70 @@ -import { execSync } from 'child_process' - -type ExecError = { - status: number - stdout: string -} - -const ENV_VAR = 'cross-env DATA_DIR=tests/__data__/input/__data__' - -describe('channels:validate', () => { - it('will show a message if the file contains a duplicate', () => { - try { - const cmd = `${ENV_VAR} npm run channels:validate --- tests/__data__/input/channels_validate/duplicate.channels.xml` - const stdout = execSync(cmd, { encoding: 'utf8' }) - if (process.env.DEBUG === 'true') console.log(cmd, stdout) - process.exit(1) - } catch (error) { - expect((error as ExecError).status).toBe(1) - expect((error as ExecError).stdout).toContain(` -┌─────────┬─────────────┬──────┬─────────────────┬─────────┬─────────┐ -│ (index) │ type │ lang │ xmltv_id │ site_id │ name │ -├─────────┼─────────────┼──────┼─────────────────┼─────────┼─────────┤ -│ 0 │ 'duplicate' │ 'en' │ 'Bravo.us@East' │ '140' │ 'Bravo' │ -└─────────┴─────────────┴──────┴─────────────────┴─────────┴─────────┘ - -1 error(s) in 1 file(s) -`) - } - }) - - it('will show a message if the file contains a channel with wrong channel id', () => { - try { - const cmd = `${ENV_VAR} npm run channels:validate --- tests/__data__/input/channels_validate/wrong_channel_id.channels.xml` - const stdout = execSync(cmd, { encoding: 'utf8' }) - if (process.env.DEBUG === 'true') console.log(cmd, stdout) - process.exit(1) - } catch (error) { - expect((error as ExecError).status).toBe(1) - expect((error as ExecError).stdout).toContain(` -┌─────────┬────────────────────┬──────┬────────────────────┬─────────┬─────────────────────┐ -│ (index) │ type │ lang │ xmltv_id │ site_id │ name │ -├─────────┼────────────────────┼──────┼────────────────────┼─────────┼─────────────────────┤ -│ 0 │ 'wrong_channel_id' │ 'en' │ 'CNNInternational' │ '140' │ 'CNN International' │ -└─────────┴────────────────────┴──────┴────────────────────┴─────────┴─────────────────────┘ - -1 error(s) in 1 file(s) -`) - } - }) - - it('will show a message if the file contains a channel with wrong feed id', () => { - try { - const cmd = `${ENV_VAR} npm run channels:validate --- tests/__data__/input/channels_validate/wrong_feed_id.channels.xml` - const stdout = execSync(cmd, { encoding: 'utf8' }) - if (process.env.DEBUG === 'true') console.log(cmd, stdout) - process.exit(1) - } catch (error) { - expect((error as ExecError).status).toBe(1) - expect((error as ExecError).stdout).toContain(` -┌─────────┬─────────────────┬──────┬─────────────────┬─────────┬─────────┐ -│ (index) │ type │ lang │ xmltv_id │ site_id │ name │ -├─────────┼─────────────────┼──────┼─────────────────┼─────────┼─────────┤ -│ 0 │ 'wrong_feed_id' │ 'en' │ 'Bravo.us@West' │ '150' │ 'Bravo' │ -└─────────┴─────────────────┴──────┴─────────────────┴─────────┴─────────┘ - -1 error(s) in 1 file(s) -`) - } - }) -}) +import { execSync } from 'child_process' + +interface ExecError { + status: number + stdout: string +} + +const ENV_VAR = 'cross-env DATA_DIR=tests/__data__/input/__data__' + +describe('channels:validate', () => { + it('will show a message if the file contains a duplicate', () => { + try { + const cmd = `${ENV_VAR} npm run channels:validate --- tests/__data__/input/channels_validate/duplicate.channels.xml` + const stdout = execSync(cmd, { encoding: 'utf8' }) + if (process.env.DEBUG === 'true') console.log(cmd, stdout) + process.exit(1) + } catch (error) { + expect((error as ExecError).status).toBe(1) + expect((error as ExecError).stdout).toContain(` +┌─────────┬─────────────┬──────┬─────────────────┬─────────┬─────────┐ +│ (index) │ type │ lang │ xmltv_id │ site_id │ name │ +├─────────┼─────────────┼──────┼─────────────────┼─────────┼─────────┤ +│ 0 │ 'duplicate' │ 'en' │ 'Bravo.us@East' │ '140' │ 'Bravo' │ +└─────────┴─────────────┴──────┴─────────────────┴─────────┴─────────┘ + +1 error(s) in 1 file(s) +`) + } + }) + + it('will show a message if the file contains a channel with wrong channel id', () => { + try { + const cmd = `${ENV_VAR} npm run channels:validate --- tests/__data__/input/channels_validate/wrong_channel_id.channels.xml` + const stdout = execSync(cmd, { encoding: 'utf8' }) + if (process.env.DEBUG === 'true') console.log(cmd, stdout) + process.exit(1) + } catch (error) { + expect((error as ExecError).status).toBe(1) + expect((error as ExecError).stdout).toContain(` +┌─────────┬────────────────────┬──────┬────────────────────┬─────────┬─────────────────────┐ +│ (index) │ type │ lang │ xmltv_id │ site_id │ name │ +├─────────┼────────────────────┼──────┼────────────────────┼─────────┼─────────────────────┤ +│ 0 │ 'wrong_channel_id' │ 'en' │ 'CNNInternational' │ '140' │ 'CNN International' │ +└─────────┴────────────────────┴──────┴────────────────────┴─────────┴─────────────────────┘ + +1 error(s) in 1 file(s) +`) + } + }) + + it('will show a message if the file contains a channel with wrong feed id', () => { + try { + const cmd = `${ENV_VAR} npm run channels:validate --- tests/__data__/input/channels_validate/wrong_feed_id.channels.xml` + const stdout = execSync(cmd, { encoding: 'utf8' }) + if (process.env.DEBUG === 'true') console.log(cmd, stdout) + process.exit(1) + } catch (error) { + expect((error as ExecError).status).toBe(1) + expect((error as ExecError).stdout).toContain(` +┌─────────┬─────────────────┬──────┬─────────────────┬─────────┬─────────┐ +│ (index) │ type │ lang │ xmltv_id │ site_id │ name │ +├─────────┼─────────────────┼──────┼─────────────────┼─────────┼─────────┤ +│ 0 │ 'wrong_feed_id' │ 'en' │ 'Bravo.us@West' │ '150' │ 'Bravo' │ +└─────────┴─────────────────┴──────┴─────────────────┴─────────┴─────────┘ + +1 error(s) in 1 file(s) +`) + } + }) +}) From 26e19d6a3808d424dc326f62991fe770cb0509cb Mon Sep 17 00:00:00 2001 From: theofficialomega <30985701+BellezaEmporium@users.noreply.github.com> Date: Mon, 28 Jul 2025 22:29:02 +0200 Subject: [PATCH 24/32] ... --- sites/tvprofil.com/tvprofil.com.channels.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sites/tvprofil.com/tvprofil.com.channels.xml b/sites/tvprofil.com/tvprofil.com.channels.xml index 0c1549716..043e16f76 100644 --- a/sites/tvprofil.com/tvprofil.com.channels.xml +++ b/sites/tvprofil.com/tvprofil.com.channels.xml @@ -408,7 +408,7 @@ Viasat Nature CEE VOX ZDF - 24Kitchen BG + 24Kitchen BG AGRO TV BG Alfa BG AXN Black BG @@ -438,7 +438,7 @@ Евроком Фен Фолк ТВ ФЕН ТВ - FilmBox Stars BG + FilmBox Stars BG HBO BG Kino Nova Magic TV BG @@ -3121,7 +3121,7 @@ Al Jazeera Balkans Al Jazeera Alpha TV - AMC HU + AMC HU Antena Europe Apostol TV Arena4 HU @@ -3151,7 +3151,7 @@ ATV Avrupa AXN HU AXN - AXN Spin + AXN Spin B1 TV Balkanika TV Balkan trip From 935fe02723b57096686113af83c8267b3cdceb75 Mon Sep 17 00:00:00 2001 From: freearhey <7253922+freearhey@users.noreply.github.com> Date: Tue, 29 Jul 2025 05:27:49 +0300 Subject: [PATCH 25/32] Update package-lock.json --- package-lock.json | 3855 ++++++++++++++++++++++++++++++--------------- 1 file changed, 2569 insertions(+), 1286 deletions(-) diff --git a/package-lock.json b/package-lock.json index fa3de2ec1..2eaa53650 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "@alex_neo/jest-expect-message": "^1.0.5", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "^9.30.0", + "@eslint/js": "^9.32.0", "@freearhey/chronos": "^0.0.1", "@freearhey/core": "^0.10.2", "@freearhey/search-js": "^0.1.2", @@ -18,33 +18,33 @@ "@octokit/core": "^7.0.3", "@octokit/plugin-paginate-rest": "^13.1.1", "@octokit/plugin-rest-endpoint-methods": "^16.0.0", - "@swc/core": "^1.13.0", + "@stylistic/eslint-plugin": "^5.2.2", + "@swc/core": "^1.13.2", "@swc/jest": "^0.2.39", "@types/cli-progress": "^3.11.6", "@types/fs-extra": "^11.0.4", "@types/inquirer": "^9.0.8", "@types/jest": "^30.0.0", "@types/langs": "^2.0.5", - "@types/lodash": "^4.17.19", - "@types/node": "^24.0.14", + "@types/node": "^24.1.0", "@types/node-cleanup": "^2.1.5", "@types/numeral": "^2.0.5", - "@typescript-eslint/eslint-plugin": "^8.35.0", - "@typescript-eslint/parser": "^8.35.0", - "axios": "^1.10.0", + "@typescript-eslint/eslint-plugin": "^8.38.0", + "@typescript-eslint/parser": "^8.38.0", + "axios": "^1.11.0", "axios-cookiejar-support": "^6.0.4", "chalk": "^5.4.1", - "cheerio": "^1.1.0", + "cheerio": "^1.1.2", "cli-progress": "^3.12.0", "commander": "^14.0.0", "consola": "^3.4.2", - "cross-env": "^7.0.3", + "cross-env": "^10.0.0", "csv-parser": "^3.2.0", "cwait": "^1.1.2", "dayjs": "^1.11.13", "epg-grabber": "^0.41.0", "epg-parser": "^0.3.1", - "eslint": "^9.30.0", + "eslint": "^9.32.0", "eslint-config-prettier": "^10.1.8", "form-data": "^4.0.4", "fs-extra": "^11.3.0", @@ -52,12 +52,11 @@ "globals": "^16.3.0", "husky": "^9.1.7", "iconv-lite": "^0.6.3", - "inquirer": "^12.7.0", - "jest": "^30.0.3", + "inquirer": "^12.8.2", + "jest": "^30.0.5", "jest-offline": "^1.0.1", "langs": "^2.0.0", "libxml2-wasm": "^0.5.0", - "lodash": "^4.17.21", "luxon": "^3.7.1", "mockdate": "^3.0.5", "nedb-promises": "^6.2.3", @@ -82,6 +81,7 @@ "tsx": "^4.20.3", "typescript": "^5.8.3", "unzipit": "^1.4.3", + "uuid": "^11.1.0", "wildcard-match": "^5.1.4" } }, @@ -277,12 +277,12 @@ } }, "node_modules/@babel/helpers": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", - "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.2.tgz", + "integrity": "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==", "dependencies": { "@babel/template": "^7.27.2", - "@babel/types": "^7.27.6" + "@babel/types": "^7.28.2" }, "engines": { "node": ">=6.9.0" @@ -538,9 +538,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.1.tgz", - "integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" @@ -573,33 +573,38 @@ } }, "node_modules/@emnapi/core": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.4.tgz", - "integrity": "sha512-A9CnAbC6ARNMKcIcrQwq6HeHCjpcBZ5wSx4U01WXCqEKlrzB9F9315WDNHkrs2xbx7YjjSxbUYxuN6EQzpcY2g==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.5.tgz", + "integrity": "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==", "optional": true, "dependencies": { - "@emnapi/wasi-threads": "1.0.3", + "@emnapi/wasi-threads": "1.0.4", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.4.tgz", - "integrity": "sha512-hHyapA4A3gPaDCNfiqyZUStTMqIkKRshqPIuDOXv1hcBnD4U3l8cP0T1HMCfGRxQ6V64TGCcoswChANyOAwbQg==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz", + "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==", "optional": true, "dependencies": { "tslib": "^2.4.0" } }, "node_modules/@emnapi/wasi-threads": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.3.tgz", - "integrity": "sha512-8K5IFFsQqF9wQNJptGbS6FNKgUTsSRYnTqNCG1vPP8jFdjSv18n2mQfJpkt2Oibo9iBEzcDnDxNwKTzC7svlJw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.4.tgz", + "integrity": "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==", "optional": true, "dependencies": { "tslib": "^2.4.0" } }, + "node_modules/@epic-web/invariant": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz", + "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", @@ -1084,9 +1089,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.31.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz", - "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==", + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.32.0.tgz", + "integrity": "sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -1214,13 +1219,13 @@ } }, "node_modules/@inquirer/checkbox": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.1.9.tgz", - "integrity": "sha512-DBJBkzI5Wx4jFaYm221LHvAhpKYkhVS0k9plqHwaHhofGNxvYB7J3Bz8w+bFJ05zaMb0sZNHo4KdmENQFlNTuQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.2.0.tgz", + "integrity": "sha512-fdSw07FLJEU5vbpOPzXo5c6xmMGDzbZE2+niuDHX5N6mc6V0Ebso/q3xiHra4D73+PMsC8MJmcaZKuAAoaQsSA==", "dependencies": { - "@inquirer/core": "^10.1.14", - "@inquirer/figures": "^1.0.12", - "@inquirer/type": "^3.0.7", + "@inquirer/core": "^10.1.15", + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", "ansi-escapes": "^4.3.2", "yoctocolors-cjs": "^2.1.2" }, @@ -1237,12 +1242,12 @@ } }, "node_modules/@inquirer/confirm": { - "version": "5.1.13", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.13.tgz", - "integrity": "sha512-EkCtvp67ICIVVzjsquUiVSd+V5HRGOGQfsqA4E4vMWhYnB7InUL0pa0TIWt1i+OfP16Gkds8CdIu6yGZwOM1Yw==", + "version": "5.1.14", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.14.tgz", + "integrity": "sha512-5yR4IBfe0kXe59r1YCTG8WXkUbl7Z35HK87Sw+WUyGD8wNUx7JvY7laahzeytyE1oLn74bQnL7hstctQxisQ8Q==", "dependencies": { - "@inquirer/core": "^10.1.14", - "@inquirer/type": "^3.0.7" + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8" }, "engines": { "node": ">=18" @@ -1257,12 +1262,12 @@ } }, "node_modules/@inquirer/core": { - "version": "10.1.14", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.14.tgz", - "integrity": "sha512-Ma+ZpOJPewtIYl6HZHZckeX1STvDnHTCB2GVINNUlSEn2Am6LddWwfPkIGY0IUFVjUUrr/93XlBwTK6mfLjf0A==", + "version": "10.1.15", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.15.tgz", + "integrity": "sha512-8xrp836RZvKkpNbVvgWUlxjT4CraKk2q+I3Ksy+seI2zkcE+y6wNs1BVhgcv8VyImFecUhdQrYLdW32pAjwBdA==", "dependencies": { - "@inquirer/figures": "^1.0.12", - "@inquirer/type": "^3.0.7", + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", "ansi-escapes": "^4.3.2", "cli-width": "^4.1.0", "mute-stream": "^2.0.0", @@ -1302,12 +1307,12 @@ } }, "node_modules/@inquirer/editor": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.14.tgz", - "integrity": "sha512-yd2qtLl4QIIax9DTMZ1ZN2pFrrj+yL3kgIWxm34SS6uwCr0sIhsNyudUjAo5q3TqI03xx4SEBkUJqZuAInp9uA==", + "version": "4.2.15", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.15.tgz", + "integrity": "sha512-wst31XT8DnGOSS4nNJDIklGKnf+8shuauVrWzgKegWUe28zfCftcWZ2vktGdzJgcylWSS2SrDnYUb6alZcwnCQ==", "dependencies": { - "@inquirer/core": "^10.1.14", - "@inquirer/type": "^3.0.7", + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8", "external-editor": "^3.1.0" }, "engines": { @@ -1323,12 +1328,12 @@ } }, "node_modules/@inquirer/expand": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.16.tgz", - "integrity": "sha512-oiDqafWzMtofeJyyGkb1CTPaxUkjIcSxePHHQCfif8t3HV9pHcw1Kgdw3/uGpDvaFfeTluwQtWiqzPVjAqS3zA==", + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.17.tgz", + "integrity": "sha512-PSqy9VmJx/VbE3CT453yOfNa+PykpKg/0SYP7odez1/NWBGuDXgPhp4AeGYYKjhLn5lUUavVS/JbeYMPdH50Mw==", "dependencies": { - "@inquirer/core": "^10.1.14", - "@inquirer/type": "^3.0.7", + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8", "yoctocolors-cjs": "^2.1.2" }, "engines": { @@ -1344,20 +1349,20 @@ } }, "node_modules/@inquirer/figures": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.12.tgz", - "integrity": "sha512-MJttijd8rMFcKJC8NYmprWr6hD3r9Gd9qUC0XwPNwoEPWSMVJwA2MlXxF+nhZZNMY+HXsWa+o7KY2emWYIn0jQ==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.13.tgz", + "integrity": "sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==", "engines": { "node": ">=18" } }, "node_modules/@inquirer/input": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.2.0.tgz", - "integrity": "sha512-opqpHPB1NjAmDISi3uvZOTrjEEU5CWVu/HBkDby8t93+6UxYX0Z7Ps0Ltjm5sZiEbWenjubwUkivAEYQmy9xHw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.2.1.tgz", + "integrity": "sha512-tVC+O1rBl0lJpoUZv4xY+WGWY8V5b0zxU1XDsMsIHYregdh7bN5X5QnIONNBAl0K765FYlAfNHS2Bhn7SSOVow==", "dependencies": { - "@inquirer/core": "^10.1.14", - "@inquirer/type": "^3.0.7" + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8" }, "engines": { "node": ">=18" @@ -1372,12 +1377,12 @@ } }, "node_modules/@inquirer/number": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.16.tgz", - "integrity": "sha512-kMrXAaKGavBEoBYUCgualbwA9jWUx2TjMA46ek+pEKy38+LFpL9QHlTd8PO2kWPUgI/KB+qi02o4y2rwXbzr3Q==", + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.17.tgz", + "integrity": "sha512-GcvGHkyIgfZgVnnimURdOueMk0CztycfC8NZTiIY9arIAkeOgt6zG57G+7vC59Jns3UX27LMkPKnKWAOF5xEYg==", "dependencies": { - "@inquirer/core": "^10.1.14", - "@inquirer/type": "^3.0.7" + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8" }, "engines": { "node": ">=18" @@ -1392,12 +1397,12 @@ } }, "node_modules/@inquirer/password": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.16.tgz", - "integrity": "sha512-g8BVNBj5Zeb5/Y3cSN+hDUL7CsIFDIuVxb9EPty3lkxBaYpjL5BNRKSYOF9yOLe+JOcKFd+TSVeADQ4iSY7rbg==", + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.17.tgz", + "integrity": "sha512-DJolTnNeZ00E1+1TW+8614F7rOJJCM4y4BAGQ3Gq6kQIG+OJ4zr3GLjIjVVJCbKsk2jmkmv6v2kQuN/vriHdZA==", "dependencies": { - "@inquirer/core": "^10.1.14", - "@inquirer/type": "^3.0.7", + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8", "ansi-escapes": "^4.3.2" }, "engines": { @@ -1413,20 +1418,20 @@ } }, "node_modules/@inquirer/prompts": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.6.0.tgz", - "integrity": "sha512-jAhL7tyMxB3Gfwn4HIJ0yuJ5pvcB5maYUcouGcgd/ub79f9MqZ+aVnBtuFf+VC2GTkCBF+R+eo7Vi63w5VZlzw==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.8.0.tgz", + "integrity": "sha512-JHwGbQ6wjf1dxxnalDYpZwZxUEosT+6CPGD9Zh4sm9WXdtUp9XODCQD3NjSTmu+0OAyxWXNOqf0spjIymJa2Tw==", "dependencies": { - "@inquirer/checkbox": "^4.1.9", - "@inquirer/confirm": "^5.1.13", - "@inquirer/editor": "^4.2.14", - "@inquirer/expand": "^4.0.16", - "@inquirer/input": "^4.2.0", - "@inquirer/number": "^3.0.16", - "@inquirer/password": "^4.0.16", - "@inquirer/rawlist": "^4.1.4", - "@inquirer/search": "^3.0.16", - "@inquirer/select": "^4.2.4" + "@inquirer/checkbox": "^4.2.0", + "@inquirer/confirm": "^5.1.14", + "@inquirer/editor": "^4.2.15", + "@inquirer/expand": "^4.0.17", + "@inquirer/input": "^4.2.1", + "@inquirer/number": "^3.0.17", + "@inquirer/password": "^4.0.17", + "@inquirer/rawlist": "^4.1.5", + "@inquirer/search": "^3.1.0", + "@inquirer/select": "^4.3.1" }, "engines": { "node": ">=18" @@ -1441,12 +1446,12 @@ } }, "node_modules/@inquirer/rawlist": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.4.tgz", - "integrity": "sha512-5GGvxVpXXMmfZNtvWw4IsHpR7RzqAR624xtkPd1NxxlV5M+pShMqzL4oRddRkg8rVEOK9fKdJp1jjVML2Lr7TQ==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.5.tgz", + "integrity": "sha512-R5qMyGJqtDdi4Ht521iAkNqyB6p2UPuZUbMifakg1sWtu24gc2Z8CJuw8rP081OckNDMgtDCuLe42Q2Kr3BolA==", "dependencies": { - "@inquirer/core": "^10.1.14", - "@inquirer/type": "^3.0.7", + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8", "yoctocolors-cjs": "^2.1.2" }, "engines": { @@ -1462,13 +1467,13 @@ } }, "node_modules/@inquirer/search": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.0.16.tgz", - "integrity": "sha512-POCmXo+j97kTGU6aeRjsPyuCpQQfKcMXdeTMw708ZMtWrj5aykZvlUxH4Qgz3+Y1L/cAVZsSpA+UgZCu2GMOMg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.1.0.tgz", + "integrity": "sha512-PMk1+O/WBcYJDq2H7foV0aAZSmDdkzZB9Mw2v/DmONRJopwA/128cS9M/TXWLKKdEQKZnKwBzqu2G4x/2Nqx8Q==", "dependencies": { - "@inquirer/core": "^10.1.14", - "@inquirer/figures": "^1.0.12", - "@inquirer/type": "^3.0.7", + "@inquirer/core": "^10.1.15", + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", "yoctocolors-cjs": "^2.1.2" }, "engines": { @@ -1484,13 +1489,13 @@ } }, "node_modules/@inquirer/select": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.2.4.tgz", - "integrity": "sha512-unTppUcTjmnbl/q+h8XeQDhAqIOmwWYWNyiiP2e3orXrg6tOaa5DHXja9PChCSbChOsktyKgOieRZFnajzxoBg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.3.1.tgz", + "integrity": "sha512-Gfl/5sqOF5vS/LIrSndFgOh7jgoe0UXEizDqahFRkq5aJBLegZ6WjuMh/hVEJwlFQjyLq1z9fRtvUMkb7jM1LA==", "dependencies": { - "@inquirer/core": "^10.1.14", - "@inquirer/figures": "^1.0.12", - "@inquirer/type": "^3.0.7", + "@inquirer/core": "^10.1.15", + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", "ansi-escapes": "^4.3.2", "yoctocolors-cjs": "^2.1.2" }, @@ -1507,9 +1512,9 @@ } }, "node_modules/@inquirer/type": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.7.tgz", - "integrity": "sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.8.tgz", + "integrity": "sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==", "engines": { "node": ">=18" }, @@ -1663,21 +1668,49 @@ } }, "node_modules/@jest/console": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.0.4.tgz", - "integrity": "sha512-tMLCDvBJBwPqMm4OAiuKm2uF5y5Qe26KgcMn+nrDSWpEW+eeFmqA0iO4zJfL16GP7gE3bUUQ3hIuUJ22AqVRnw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.0.5.tgz", + "integrity": "sha512-xY6b0XiL0Nav3ReresUarwl2oIz1gTnxGbGpho9/rbUWsLH0f1OD/VT84xs8c7VmH7MChnLb0pag6PhZhAdDiA==", "dependencies": { - "@jest/types": "30.0.1", + "@jest/types": "30.0.5", "@types/node": "*", "chalk": "^4.1.2", - "jest-message-util": "30.0.2", - "jest-util": "30.0.2", + "jest-message-util": "30.0.5", + "jest-util": "30.0.5", "slash": "^3.0.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/@jest/console/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/console/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/console/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1694,37 +1727,37 @@ } }, "node_modules/@jest/core": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.0.4.tgz", - "integrity": "sha512-MWScSO9GuU5/HoWjpXAOBs6F/iobvK1XlioelgOM9St7S0Z5WTI9kjCQLPeo4eQRRYusyLW25/J7J5lbFkrYXw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.0.5.tgz", + "integrity": "sha512-fKD0OulvRsXF1hmaFgHhVJzczWzA1RXMMo9LTPuFXo9q/alDbME3JIyWYqovWsUBWSoBcsHaGPSLF9rz4l9Qeg==", "dependencies": { - "@jest/console": "30.0.4", + "@jest/console": "30.0.5", "@jest/pattern": "30.0.1", - "@jest/reporters": "30.0.4", - "@jest/test-result": "30.0.4", - "@jest/transform": "30.0.4", - "@jest/types": "30.0.1", + "@jest/reporters": "30.0.5", + "@jest/test-result": "30.0.5", + "@jest/transform": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "ci-info": "^4.2.0", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", - "jest-changed-files": "30.0.2", - "jest-config": "30.0.4", - "jest-haste-map": "30.0.2", - "jest-message-util": "30.0.2", + "jest-changed-files": "30.0.5", + "jest-config": "30.0.5", + "jest-haste-map": "30.0.5", + "jest-message-util": "30.0.5", "jest-regex-util": "30.0.1", - "jest-resolve": "30.0.2", - "jest-resolve-dependencies": "30.0.4", - "jest-runner": "30.0.4", - "jest-runtime": "30.0.4", - "jest-snapshot": "30.0.4", - "jest-util": "30.0.2", - "jest-validate": "30.0.2", - "jest-watcher": "30.0.4", + "jest-resolve": "30.0.5", + "jest-resolve-dependencies": "30.0.5", + "jest-runner": "30.0.5", + "jest-runtime": "30.0.5", + "jest-snapshot": "30.0.5", + "jest-util": "30.0.5", + "jest-validate": "30.0.5", + "jest-watcher": "30.0.5", "micromatch": "^4.0.8", - "pretty-format": "30.0.2", + "pretty-format": "30.0.5", "slash": "^3.0.0" }, "engines": { @@ -1739,6 +1772,34 @@ } } }, + "node_modules/@jest/core/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/core/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/core/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1774,35 +1835,78 @@ } }, "node_modules/@jest/environment": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.0.4.tgz", - "integrity": "sha512-5NT+sr7ZOb8wW7C4r7wOKnRQ8zmRWQT2gW4j73IXAKp5/PX1Z8MCStBLQDYfIG3n1Sw0NRfYGdp0iIPVooBAFQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.0.5.tgz", + "integrity": "sha512-aRX7WoaWx1oaOkDQvCWImVQ8XNtdv5sEWgk4gxR6NXb7WBUnL5sRak4WRzIQRZ1VTWPvV4VI4mgGjNL9TeKMYA==", "dependencies": { - "@jest/fake-timers": "30.0.4", - "@jest/types": "30.0.1", + "@jest/fake-timers": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", - "jest-mock": "30.0.2" + "jest-mock": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/expect": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.0.4.tgz", - "integrity": "sha512-Z/DL7t67LBHSX4UzDyeYKqOxE/n7lbrrgEwWM3dGiH5Dgn35nk+YtgzKudmfIrBI8DRRrKYY5BCo3317HZV1Fw==", + "node_modules/@jest/environment/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", "dependencies": { - "expect": "30.0.4", - "jest-snapshot": "30.0.4" + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/expect": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.0.5.tgz", + "integrity": "sha512-6udac8KKrtTtC+AXZ2iUN/R7dp7Ydry+Fo6FPFnDG54wjVMnb6vW/XNlf7Xj8UDjAE3aAVAsR4KFyKk3TCXmTA==", + "dependencies": { + "expect": "30.0.5", + "jest-snapshot": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.0.4.tgz", - "integrity": "sha512-EgXecHDNfANeqOkcak0DxsoVI4qkDUsR7n/Lr2vtmTBjwLPBnnPOF71S11Q8IObWzxm2QgQoY6f9hzrRD3gHRA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.0.5.tgz", + "integrity": "sha512-F3lmTT7CXWYywoVUGTCmom0vXq3HTTkaZyTAzIy+bXSBizB7o5qzlC9VCtq0arOa8GqmNsbg/cE9C6HLn7Szew==", "dependencies": { "@jest/get-type": "30.0.1" }, @@ -1811,21 +1915,64 @@ } }, "node_modules/@jest/fake-timers": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.0.4.tgz", - "integrity": "sha512-qZ7nxOcL5+gwBO6LErvwVy5k06VsX/deqo2XnVUSTV0TNC9lrg8FC3dARbi+5lmrr5VyX5drragK+xLcOjvjYw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.0.5.tgz", + "integrity": "sha512-ZO5DHfNV+kgEAeP3gK3XlpJLL4U3Sz6ebl/n68Uwt64qFFs5bv4bfEEjyRGK5uM0C90ewooNgFuKMdkbEoMEXw==", "dependencies": { - "@jest/types": "30.0.1", + "@jest/types": "30.0.5", "@sinonjs/fake-timers": "^13.0.0", "@types/node": "*", - "jest-message-util": "30.0.2", - "jest-mock": "30.0.2", - "jest-util": "30.0.2" + "jest-message-util": "30.0.5", + "jest-mock": "30.0.5", + "jest-util": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/@jest/fake-timers/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/fake-timers/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/fake-timers/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@jest/get-type": { "version": "30.0.1", "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.0.1.tgz", @@ -1836,19 +1983,62 @@ } }, "node_modules/@jest/globals": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.0.4.tgz", - "integrity": "sha512-avyZuxEHF2EUhFF6NEWVdxkRRV6iXXcIES66DLhuLlU7lXhtFG/ySq/a8SRZmEJSsLkNAFX6z6mm8KWyXe9OEA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.0.5.tgz", + "integrity": "sha512-7oEJT19WW4oe6HR7oLRvHxwlJk2gev0U9px3ufs8sX9PoD1Eza68KF0/tlN7X0dq/WVsBScXQGgCldA1V9Y/jA==", "dependencies": { - "@jest/environment": "30.0.4", - "@jest/expect": "30.0.4", - "@jest/types": "30.0.1", - "jest-mock": "30.0.2" + "@jest/environment": "30.0.5", + "@jest/expect": "30.0.5", + "@jest/types": "30.0.5", + "jest-mock": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/@jest/globals/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/globals/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/globals/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@jest/pattern": { "version": "30.0.1", "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", @@ -1863,15 +2053,15 @@ } }, "node_modules/@jest/reporters": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.0.4.tgz", - "integrity": "sha512-6ycNmP0JSJEEys1FbIzHtjl9BP0tOZ/KN6iMeAKrdvGmUsa1qfRdlQRUDKJ4P84hJ3xHw1yTqJt4fvPNHhyE+g==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.0.5.tgz", + "integrity": "sha512-mafft7VBX4jzED1FwGC1o/9QUM2xebzavImZMeqnsklgcyxBto8mV4HzNSzUrryJ+8R9MFOM3HgYuDradWR+4g==", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.0.4", - "@jest/test-result": "30.0.4", - "@jest/transform": "30.0.4", - "@jest/types": "30.0.1", + "@jest/console": "30.0.5", + "@jest/test-result": "30.0.5", + "@jest/transform": "30.0.5", + "@jest/types": "30.0.5", "@jridgewell/trace-mapping": "^0.3.25", "@types/node": "*", "chalk": "^4.1.2", @@ -1884,9 +2074,9 @@ "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^5.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "30.0.2", - "jest-util": "30.0.2", - "jest-worker": "30.0.2", + "jest-message-util": "30.0.5", + "jest-util": "30.0.5", + "jest-worker": "30.0.5", "slash": "^3.0.0", "string-length": "^4.0.2", "v8-to-istanbul": "^9.0.1" @@ -1903,6 +2093,34 @@ } } }, + "node_modules/@jest/reporters/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/reporters/node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -2005,11 +2223,11 @@ } }, "node_modules/@jest/snapshot-utils": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.0.4.tgz", - "integrity": "sha512-BEpX8M/Y5lG7MI3fmiO+xCnacOrVsnbqVrcDZIT8aSGkKV1w2WwvRQxSWw5SIS8ozg7+h8tSj5EO1Riqqxcdag==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.0.5.tgz", + "integrity": "sha512-XcCQ5qWHLvi29UUrowgDFvV4t7ETxX91CbDczMnoqXPOIcZOxyNdSjm6kV5XMc8+HkxfRegU/MUmnTbJRzGrUQ==", "dependencies": { - "@jest/types": "30.0.1", + "@jest/types": "30.0.5", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "natural-compare": "^1.4.0" @@ -2018,6 +2236,34 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/@jest/snapshot-utils/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/snapshot-utils/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/snapshot-utils/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2047,12 +2293,12 @@ } }, "node_modules/@jest/test-result": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.0.4.tgz", - "integrity": "sha512-Mfpv8kjyKTHqsuu9YugB6z1gcdB3TSSOaKlehtVaiNlClMkEHY+5ZqCY2CrEE3ntpBMlstX/ShDAf84HKWsyIw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.0.5.tgz", + "integrity": "sha512-wPyztnK0gbDMQAJZ43tdMro+qblDHH1Ru/ylzUo21TBKqt88ZqnKKK2m30LKmLLoKtR2lxdpCC/P3g1vfKcawQ==", "dependencies": { - "@jest/console": "30.0.4", - "@jest/types": "30.0.1", + "@jest/console": "30.0.5", + "@jest/types": "30.0.5", "@types/istanbul-lib-coverage": "^2.0.6", "collect-v8-coverage": "^1.0.2" }, @@ -2060,14 +2306,57 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/test-sequencer": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.0.4.tgz", - "integrity": "sha512-bj6ePmqi4uxAE8EHE0Slmk5uBYd9Vd/PcVt06CsBxzH4bbA8nGsI1YbXl/NH+eii4XRtyrRx+Cikub0x8H4vDg==", + "node_modules/@jest/test-result/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", "dependencies": { - "@jest/test-result": "30.0.4", + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-result/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-result/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.0.5.tgz", + "integrity": "sha512-Aea/G1egWoIIozmDD7PBXUOxkekXl7ueGzrsGGi1SbeKgQqCYCIf+wfbflEbf2LiPxL8j2JZGLyrzZagjvW4YQ==", + "dependencies": { + "@jest/test-result": "30.0.5", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.0.2", + "jest-haste-map": "30.0.5", "slash": "^3.0.0" }, "engines": { @@ -2075,21 +2364,21 @@ } }, "node_modules/@jest/transform": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.0.4.tgz", - "integrity": "sha512-atvy4hRph/UxdCIBp+UB2jhEA/jJiUeGZ7QPgBi9jUUKNgi3WEoMXGNG7zbbELG2+88PMabUNCDchmqgJy3ELg==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.0.5.tgz", + "integrity": "sha512-Vk8amLQCmuZyy6GbBht1Jfo9RSdBtg7Lks+B0PecnjI8J+PCLQPGh7uI8Q/2wwpW2gLdiAfiHNsmekKlywULqg==", "dependencies": { "@babel/core": "^7.27.4", - "@jest/types": "30.0.1", + "@jest/types": "30.0.5", "@jridgewell/trace-mapping": "^0.3.25", "babel-plugin-istanbul": "^7.0.0", "chalk": "^4.1.2", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.0.2", + "jest-haste-map": "30.0.5", "jest-regex-util": "30.0.1", - "jest-util": "30.0.2", + "jest-util": "30.0.5", "micromatch": "^4.0.8", "pirates": "^4.0.7", "slash": "^3.0.0", @@ -2099,6 +2388,34 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/@jest/transform/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/transform/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/transform/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2353,102 +2670,6 @@ "@octokit/openapi-types": "^25.1.0" } }, - "node_modules/@oxlint/darwin-arm64": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@oxlint/darwin-arm64/-/darwin-arm64-1.7.0.tgz", - "integrity": "sha512-51vhCSQO4NSkedwEwOyqThiYqV0DAUkwNdqMQK0d29j5zmtNJJJRRBLeQuLGdstNmn3F7WMQ75Ci0/3Nq4ff8A==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@oxlint/darwin-x64": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@oxlint/darwin-x64/-/darwin-x64-1.7.0.tgz", - "integrity": "sha512-c0GN52yehYZ4TYuh4lBH9wYbBOI/RDOxZhJdBsttG0GwfvKYg/tiPNrNEsPzu0/rd1j6x3yT0zt6vezDMeC1sQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@oxlint/linux-arm64-gnu": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@oxlint/linux-arm64-gnu/-/linux-arm64-gnu-1.7.0.tgz", - "integrity": "sha512-pam/lbzbzVMDzc3f1hoRPtnUMEIqkn0dynlB5nUll/MVBSIvIPLS9kJLrRA48lrlqbkS9LGiF37JvpwXA58A9A==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxlint/linux-arm64-musl": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@oxlint/linux-arm64-musl/-/linux-arm64-musl-1.7.0.tgz", - "integrity": "sha512-LTyPy9FYS3SZ2XxJx+ITvlAq/ek5PtZK9Z2m3W72TA8hchGhJy5eQ+aotYjd/YVXOpGRpB12RdOpOTsZRu50bA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxlint/linux-x64-gnu": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@oxlint/linux-x64-gnu/-/linux-x64-gnu-1.7.0.tgz", - "integrity": "sha512-YtZ4DiAgjaEiqUiwnvtJ/znZMAAVPKR7pnsi6lqbA3BfXJ/IwMaNpdoGlCGVdDGeN4BuGCwnFtBVqKVvVg3DDg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxlint/linux-x64-musl": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@oxlint/linux-x64-musl/-/linux-x64-musl-1.7.0.tgz", - "integrity": "sha512-5aIpemNUBvwMMk4MCx1V3M6R9eMB1/SS6/24Orax9FqaI1lDX08tySdv696sr4Lms9ocA+rotxIPW9NP9439vA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxlint/win32-arm64": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@oxlint/win32-arm64/-/win32-arm64-1.7.0.tgz", - "integrity": "sha512-fpFpkHwbAu0NcR5bc1WapCPcM9qSYi5lCRVOp1WwDoFLKI2b9/UWB8OEg8UHWV5dnBu7HZAWH/SEslYGkZNsbQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@oxlint/win32-x64": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@oxlint/win32-x64/-/win32-x64-1.7.0.tgz", - "integrity": "sha512-0EPWBWOiD3wZHgeWDlTUaiFzhzIonXykxYUC+NRerPQFkO/G+bd9uLMJddHDKqfP/7g8s3E5V6KvBvvFpb7U6g==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -2459,9 +2680,9 @@ } }, "node_modules/@pkgr/core": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz", - "integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", "engines": { "node": "^12.20.0 || ^14.18.0 || >=16.0.0" }, @@ -2687,10 +2908,51 @@ "@sinonjs/commons": "^3.0.1" } }, + "node_modules/@stylistic/eslint-plugin": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.2.2.tgz", + "integrity": "sha512-bE2DUjruqXlHYP3Q2Gpqiuj2bHq7/88FnuaS0FjeGGLCy+X6a07bGVuwtiOYnPSLHR6jmx5Bwdv+j7l8H+G97A==", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/types": "^8.37.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "estraverse": "^5.3.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": ">=9.0.0" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@swc/core": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.0.tgz", - "integrity": "sha512-7Fh16ZH/Rj3Di720if+sw9BictD4N5kbTpsyDC+URXhvsZ7qRt1lH7PaeIQYyJJQHwFhoKpwwGxfGU9SHgPLdw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.2.tgz", + "integrity": "sha512-YWqn+0IKXDhqVLKoac4v2tV6hJqB/wOh8/Br8zjqeqBkKa77Qb0Kw2i7LOFzjFNZbZaPH6AlMGlBwNrxaauaAg==", "hasInstallScript": true, "dependencies": { "@swc/counter": "^0.1.3", @@ -2704,16 +2966,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.13.0", - "@swc/core-darwin-x64": "1.13.0", - "@swc/core-linux-arm-gnueabihf": "1.13.0", - "@swc/core-linux-arm64-gnu": "1.13.0", - "@swc/core-linux-arm64-musl": "1.13.0", - "@swc/core-linux-x64-gnu": "1.13.0", - "@swc/core-linux-x64-musl": "1.13.0", - "@swc/core-win32-arm64-msvc": "1.13.0", - "@swc/core-win32-ia32-msvc": "1.13.0", - "@swc/core-win32-x64-msvc": "1.13.0" + "@swc/core-darwin-arm64": "1.13.2", + "@swc/core-darwin-x64": "1.13.2", + "@swc/core-linux-arm-gnueabihf": "1.13.2", + "@swc/core-linux-arm64-gnu": "1.13.2", + "@swc/core-linux-arm64-musl": "1.13.2", + "@swc/core-linux-x64-gnu": "1.13.2", + "@swc/core-linux-x64-musl": "1.13.2", + "@swc/core-win32-arm64-msvc": "1.13.2", + "@swc/core-win32-ia32-msvc": "1.13.2", + "@swc/core-win32-x64-msvc": "1.13.2" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" @@ -2725,9 +2987,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.13.0.tgz", - "integrity": "sha512-SkmR9u7MHDu2X8hf7SjZTmsAfQTmel0mi+TJ7AGtufLwGySv6pwQfJ/CIJpcPxYENVqDJAFnDrHaKV8mgA6kxQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.13.2.tgz", + "integrity": "sha512-44p7ivuLSGFJ15Vly4ivLJjg3ARo4879LtEBAabcHhSZygpmkP8eyjyWxrH3OxkY1eRZSIJe8yRZPFw4kPXFPw==", "cpu": [ "arm64" ], @@ -2740,9 +3002,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.13.0.tgz", - "integrity": "sha512-15/SyDjXRtFJ09fYHBXUXrj4tpiSpCkjgsF1z3/sSpHH1POWpQUQzxmFyomPQVZ/SsDqP18WGH09Vph4Qriuiw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.13.2.tgz", + "integrity": "sha512-Lb9EZi7X2XDAVmuUlBm2UvVAgSCbD3qKqDCxSI4jEOddzVOpNCnyZ/xEampdngUIyDDhhJLYU9duC+Mcsv5Y+A==", "cpu": [ "x64" ], @@ -2755,9 +3017,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.13.0.tgz", - "integrity": "sha512-AHauVHZQEJI/dCZQg6VYNNQ6HROz8dSOnCSheXzzBw1DGWo77BlcxRP0fF0jaAXM9WNqtCUOY1HiJ9ohkAE61Q==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.13.2.tgz", + "integrity": "sha512-9TDe/92ee1x57x+0OqL1huG4BeljVx0nWW4QOOxp8CCK67Rpc/HHl2wciJ0Kl9Dxf2NvpNtkPvqj9+BUmM9WVA==", "cpu": [ "arm" ], @@ -2770,9 +3032,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.13.0.tgz", - "integrity": "sha512-qyZmBZF7asF6954/x7yn6R7Bzd45KRG05rK2atIF9J3MTa8az7vubP1Q3BWmmss1j8699DELpbuoJucGuhsNXw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.13.2.tgz", + "integrity": "sha512-KJUSl56DBk7AWMAIEcU83zl5mg3vlQYhLELhjwRFkGFMvghQvdqQ3zFOYa4TexKA7noBZa3C8fb24rI5sw9Exg==", "cpu": [ "arm64" ], @@ -2785,9 +3047,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.13.0.tgz", - "integrity": "sha512-whskQCOUlLQT7MjnronpHmyHegBka5ig9JkQvecbqhWzRfdwN+c2xTJs3kQsWy2Vc2f1hcL3D8hGIwY5TwPxMQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.13.2.tgz", + "integrity": "sha512-teU27iG1oyWpNh9CzcGQ48ClDRt/RCem7mYO7ehd2FY102UeTws2+OzLESS1TS1tEZipq/5xwx3FzbVgiolCiQ==", "cpu": [ "arm64" ], @@ -2800,9 +3062,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.13.0.tgz", - "integrity": "sha512-51n4P4nv6rblXyH3zCEktvmR9uSAZ7+zbfeby0sxbj8LS/IKuVd7iCwD5dwMj4CxG9Fs+HgjN73dLQF/OerHhg==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.13.2.tgz", + "integrity": "sha512-dRPsyPyqpLD0HMRCRpYALIh4kdOir8pPg4AhNQZLehKowigRd30RcLXGNVZcc31Ua8CiPI4QSgjOIxK+EQe4LQ==", "cpu": [ "x64" ], @@ -2815,9 +3077,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.13.0.tgz", - "integrity": "sha512-VMqelgvnXs27eQyhDf1S2O2MxSdchIH7c1tkxODRtu9eotcAeniNNgqqLjZ5ML0MGeRk/WpbsAY/GWi7eSpiHw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.13.2.tgz", + "integrity": "sha512-CCxETW+KkYEQDqz1SYC15YIWYheqFC+PJVOW76Maa/8yu8Biw+HTAcblKf2isrlUtK8RvrQN94v3UXkC2NzCEw==", "cpu": [ "x64" ], @@ -2830,9 +3092,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.13.0.tgz", - "integrity": "sha512-NLJmseWJngWeENgat+O/WB4ptNxtx2X4OfPnSG5a/A4sxcn2E4jq91OPvbeUQwDkH+ZQWKXmbXFzt7Nn661QYA==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.13.2.tgz", + "integrity": "sha512-Wv/QTA6PjyRLlmKcN6AmSI4jwSMRl0VTLGs57PHTqYRwwfwd7y4s2fIPJVBNbAlXd795dOEP6d/bGSQSyhOX3A==", "cpu": [ "arm64" ], @@ -2845,9 +3107,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.13.0.tgz", - "integrity": "sha512-UBfwrp0xW37KQGTA08mwrCLIm1ZKy6pXK8IVwou7BvhMgrItRNweTGyUrCnvDLUfyYFuJCmzcEaJ3NudtctD6g==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.13.2.tgz", + "integrity": "sha512-PuCdtNynEkUNbUXX/wsyUC+t4mamIU5y00lT5vJcAvco3/r16Iaxl5UCzhXYaWZSNVZMzPp9qN8NlSL8M5pPxw==", "cpu": [ "ia32" ], @@ -2860,9 +3122,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.13.0.tgz", - "integrity": "sha512-BAB1P7Z/y2EENsfsPytPnjIyBVRZN2WULY+s3ozW4QkGmYHde6XXG28n0ABTHhcIOmmR2VzM+uaW1x48laSimw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.13.2.tgz", + "integrity": "sha512-qlmMkFZJus8cYuBURx1a3YAG2G7IW44i+FEYV5/32ylKkzGNAr9tDJSA53XNnNXkAB5EXSPsOz7bn5C3JlEtdQ==", "cpu": [ "x64" ], @@ -3044,15 +3306,10 @@ "integrity": "sha512-DIUKT4mkbTBxSrX6lmnQR888ObeFVVo1uNEqBH5/ddQHpnG4CA24DibpK7aO8QAcJEZUTcIx0F96TWuzVT9Z4g==", "license": "MIT" }, - "node_modules/@types/lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==" - }, "node_modules/@types/node": { - "version": "24.0.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.14.tgz", - "integrity": "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw==", + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz", + "integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==", "dependencies": { "undici-types": "~7.8.0" } @@ -3103,15 +3360,15 @@ "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.37.0.tgz", - "integrity": "sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.38.0.tgz", + "integrity": "sha512-CPoznzpuAnIOl4nhj4tRr4gIPj5AfKgkiJmGQDaq+fQnRJTYlcBjbX3wbciGmpoPf8DREufuPRe1tNMZnGdanA==", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.37.0", - "@typescript-eslint/type-utils": "8.37.0", - "@typescript-eslint/utils": "8.37.0", - "@typescript-eslint/visitor-keys": "8.37.0", + "@typescript-eslint/scope-manager": "8.38.0", + "@typescript-eslint/type-utils": "8.38.0", + "@typescript-eslint/utils": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -3125,7 +3382,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.37.0", + "@typescript-eslint/parser": "^8.38.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } @@ -3140,14 +3397,14 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.37.0.tgz", - "integrity": "sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.38.0.tgz", + "integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==", "dependencies": { - "@typescript-eslint/scope-manager": "8.37.0", - "@typescript-eslint/types": "8.37.0", - "@typescript-eslint/typescript-estree": "8.37.0", - "@typescript-eslint/visitor-keys": "8.37.0", + "@typescript-eslint/scope-manager": "8.38.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/typescript-estree": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0", "debug": "^4.3.4" }, "engines": { @@ -3163,12 +3420,12 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.37.0.tgz", - "integrity": "sha512-BIUXYsbkl5A1aJDdYJCBAo8rCEbAvdquQ8AnLb6z5Lp1u3x5PNgSSx9A/zqYc++Xnr/0DVpls8iQ2cJs/izTXA==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.38.0.tgz", + "integrity": "sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.37.0", - "@typescript-eslint/types": "^8.37.0", + "@typescript-eslint/tsconfig-utils": "^8.38.0", + "@typescript-eslint/types": "^8.38.0", "debug": "^4.3.4" }, "engines": { @@ -3183,12 +3440,12 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.37.0.tgz", - "integrity": "sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.38.0.tgz", + "integrity": "sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==", "dependencies": { - "@typescript-eslint/types": "8.37.0", - "@typescript-eslint/visitor-keys": "8.37.0" + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3199,9 +3456,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.37.0.tgz", - "integrity": "sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz", + "integrity": "sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -3214,13 +3471,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.37.0.tgz", - "integrity": "sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.38.0.tgz", + "integrity": "sha512-c7jAvGEZVf0ao2z+nnz8BUaHZD09Agbh+DY7qvBQqLiz8uJzRgVPj5YvOh8I8uEiH8oIUGIfHzMwUcGVco/SJg==", "dependencies": { - "@typescript-eslint/types": "8.37.0", - "@typescript-eslint/typescript-estree": "8.37.0", - "@typescript-eslint/utils": "8.37.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/typescript-estree": "8.38.0", + "@typescript-eslint/utils": "8.38.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -3237,9 +3494,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.37.0.tgz", - "integrity": "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.38.0.tgz", + "integrity": "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -3249,14 +3506,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.37.0.tgz", - "integrity": "sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz", + "integrity": "sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==", "dependencies": { - "@typescript-eslint/project-service": "8.37.0", - "@typescript-eslint/tsconfig-utils": "8.37.0", - "@typescript-eslint/types": "8.37.0", - "@typescript-eslint/visitor-keys": "8.37.0", + "@typescript-eslint/project-service": "8.38.0", + "@typescript-eslint/tsconfig-utils": "8.38.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -3298,14 +3555,14 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.37.0.tgz", - "integrity": "sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.38.0.tgz", + "integrity": "sha512-hHcMA86Hgt+ijJlrD8fX0j1j8w4C92zue/8LOPAFioIno+W0+L7KqE8QZKCcPGc/92Vs9x36w/4MPTJhqXdyvg==", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.37.0", - "@typescript-eslint/types": "8.37.0", - "@typescript-eslint/typescript-estree": "8.37.0" + "@typescript-eslint/scope-manager": "8.38.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/typescript-estree": "8.38.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3320,11 +3577,11 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.37.0.tgz", - "integrity": "sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.38.0.tgz", + "integrity": "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==", "dependencies": { - "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/types": "8.38.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -3801,13 +4058,12 @@ } }, "node_modules/axios": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", - "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", - "license": "MIT", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", + "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, @@ -3849,11 +4105,11 @@ } }, "node_modules/babel-jest": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.0.4.tgz", - "integrity": "sha512-UjG2j7sAOqsp2Xua1mS/e+ekddkSu3wpf4nZUSvXNHuVWdaOUXQ77+uyjJLDE9i0atm5x4kds8K9yb5lRsRtcA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.0.5.tgz", + "integrity": "sha512-mRijnKimhGDMsizTvBTWotwNpzrkHr+VvZUQBof2AufXKB8NXrL1W69TG20EvOz7aevx6FTJIaBuBkYxS8zolg==", "dependencies": { - "@jest/transform": "30.0.4", + "@jest/transform": "30.0.5", "@types/babel__core": "^7.20.5", "babel-plugin-istanbul": "^7.0.0", "babel-preset-jest": "30.0.1", @@ -3912,9 +4168,9 @@ } }, "node_modules/babel-preset-current-node-syntax": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", - "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.1.tgz", + "integrity": "sha512-23fWKohMTvS5s0wwJKycOe0dBdCwQ6+iiLaNR9zy8P13mtFRFM9qLLX6HJX5DL2pi/FNDf3fCQHM4FIMoHH/7w==", "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", @@ -3933,7 +4189,7 @@ "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0 || ^8.0.0-0" } }, "node_modules/babel-preset-jest": { @@ -4345,25 +4601,24 @@ "integrity": "sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==" }, "node_modules/cheerio": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.0.tgz", - "integrity": "sha512-+0hMx9eYhJvWbgpKV9hN7jg0JcwydpopZE4hgi+KvQtByZXPp04NiCWU0LzcAbP63abZckIHkTQaXVF52mX3xQ==", - "license": "MIT", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.2.tgz", + "integrity": "sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==", "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", - "encoding-sniffer": "^0.2.0", + "encoding-sniffer": "^0.2.1", "htmlparser2": "^10.0.0", "parse5": "^7.3.0", "parse5-htmlparser2-tree-adapter": "^7.1.0", "parse5-parser-stream": "^7.1.2", - "undici": "^7.10.0", + "undici": "^7.12.0", "whatwg-mimetype": "^4.0.0" }, "engines": { - "node": ">=18.17" + "node": ">=20.18.1" }, "funding": { "url": "https://github.com/cheeriojs/cheerio?sponsor=1" @@ -4703,21 +4958,19 @@ "integrity": "sha512-/f6gpQuxDaqXu+1kwQYSckUglPaOrHdbIlBAu0YuW8/Cdb45XwXYNUBXg3r/9Mo6n540Kn/smKcZWko5x99KrQ==" }, "node_modules/cross-env": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", - "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", - "license": "MIT", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.0.0.tgz", + "integrity": "sha512-aU8qlEK/nHYtVuN4p7UQgAwVljzMg8hB4YK5ThRqD2l/ziSnryncPNn7bMLt5cFYsKVKBh8HqLqyCoTupEUu7Q==", "dependencies": { - "cross-spawn": "^7.0.1" + "@epic-web/invariant": "^1.0.0", + "cross-spawn": "^7.0.6" }, "bin": { - "cross-env": "src/bin/cross-env.js", - "cross-env-shell": "src/bin/cross-env-shell.js" + "cross-env": "dist/bin/cross-env.js", + "cross-env-shell": "dist/bin/cross-env-shell.js" }, "engines": { - "node": ">=10.14", - "npm": ">=6", - "yarn": ">=1" + "node": ">=20" } }, "node_modules/cross-spawn": { @@ -4981,9 +5234,9 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, "node_modules/electron-to-chromium": { - "version": "1.5.185", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.185.tgz", - "integrity": "sha512-dYOZfUk57hSMPePoIQ1fZWl1Fkj+OshhEVuPacNKWzC1efe56OsHY3l/jCfiAgIICOU3VgOIdoq7ahg7r7n6MQ==" + "version": "1.5.192", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.192.tgz", + "integrity": "sha512-rP8Ez0w7UNw/9j5eSXCe10o1g/8B1P5SM90PCCMVkIRQn2R0LEHWz4Eh9RnxkniuDe1W0cTSOB3MLlkTGDcuCg==" }, "node_modules/emittery": { "version": "0.13.1", @@ -5209,9 +5462,9 @@ } }, "node_modules/eslint": { - "version": "9.31.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz", - "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==", + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.32.0.tgz", + "integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -5219,8 +5472,8 @@ "@eslint/config-helpers": "^0.3.0", "@eslint/core": "^0.15.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.31.0", - "@eslint/plugin-kit": "^0.3.1", + "@eslint/js": "9.32.0", + "@eslint/plugin-kit": "^0.3.4", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -5508,16 +5761,16 @@ } }, "node_modules/expect": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.0.4.tgz", - "integrity": "sha512-dDLGjnP2cKbEppxVICxI/Uf4YemmGMPNy0QytCbfafbpYk9AFQsxb8Uyrxii0RPK7FWgLGlSem+07WirwS3cFQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.0.5.tgz", + "integrity": "sha512-P0te2pt+hHI5qLJkIR+iMvS+lYUZml8rKKsohVHAGY+uClp9XVbdyYNJOIjSRpHVp8s8YqxJCiHUkSYZGr8rtQ==", "dependencies": { - "@jest/expect-utils": "30.0.4", + "@jest/expect-utils": "30.0.5", "@jest/get-type": "30.0.1", - "jest-matcher-utils": "30.0.4", - "jest-message-util": "30.0.2", - "jest-mock": "30.0.2", - "jest-util": "30.0.2" + "jest-matcher-utils": "30.0.5", + "jest-message-util": "30.0.5", + "jest-mock": "30.0.5", + "jest-util": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -5800,8 +6053,7 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "license": "ISC" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/fsevents": { "version": "2.3.3", @@ -6262,7 +6514,6 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "license": "ISC", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -6279,16 +6530,16 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "node_modules/inquirer": { - "version": "12.7.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-12.7.0.tgz", - "integrity": "sha512-KKFRc++IONSyE2UYw9CJ1V0IWx5yQKomwB+pp3cWomWs+v2+ZsG11G2OVfAjFS6WWCppKw+RfKmpqGfSzD5QBQ==", + "version": "12.9.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-12.9.0.tgz", + "integrity": "sha512-LlFVmvWVCun7uEgPB3vups9NzBrjJn48kRNtFGw3xU1H5UXExTEz/oF1JGLaB0fvlkUB+W6JfgLcSEaSdH7RPA==", "dependencies": { - "@inquirer/core": "^10.1.14", - "@inquirer/prompts": "^7.6.0", - "@inquirer/type": "^3.0.7", + "@inquirer/core": "^10.1.15", + "@inquirer/prompts": "^7.8.0", + "@inquirer/type": "^3.0.8", "ansi-escapes": "^4.3.2", "mute-stream": "^2.0.0", - "run-async": "^4.0.4", + "run-async": "^4.0.5", "rxjs": "^7.8.2" }, "engines": { @@ -6610,14 +6861,14 @@ } }, "node_modules/jest": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.0.4.tgz", - "integrity": "sha512-9QE0RS4WwTj/TtTC4h/eFVmFAhGNVerSB9XpJh8sqaXlP73ILcPcZ7JWjjEtJJe2m8QyBLKKfPQuK+3F+Xij/g==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.0.5.tgz", + "integrity": "sha512-y2mfcJywuTUkvLm2Lp1/pFX8kTgMO5yyQGq/Sk/n2mN7XWYp4JsCZ/QXW34M8YScgk8bPZlREH04f6blPnoHnQ==", "dependencies": { - "@jest/core": "30.0.4", - "@jest/types": "30.0.1", + "@jest/core": "30.0.5", + "@jest/types": "30.0.5", "import-local": "^3.2.0", - "jest-cli": "30.0.4" + "jest-cli": "30.0.5" }, "bin": { "jest": "bin/jest.js" @@ -6635,12 +6886,12 @@ } }, "node_modules/jest-changed-files": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.0.2.tgz", - "integrity": "sha512-Ius/iRST9FKfJI+I+kpiDh8JuUlAISnRszF9ixZDIqJF17FckH5sOzKC8a0wd0+D+8em5ADRHA5V5MnfeDk2WA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.0.5.tgz", + "integrity": "sha512-bGl2Ntdx0eAwXuGpdLdVYVr5YQHnSZlQ0y9HVDu565lCUAe9sj6JOtBbMmBBikGIegne9piDDIOeiLVoqTkz4A==", "dependencies": { "execa": "^5.1.1", - "jest-util": "30.0.2", + "jest-util": "30.0.5", "p-limit": "^3.1.0" }, "engines": { @@ -6648,27 +6899,27 @@ } }, "node_modules/jest-circus": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.0.4.tgz", - "integrity": "sha512-o6UNVfbXbmzjYgmVPtSQrr5xFZCtkDZGdTlptYvGFSN80RuOOlTe73djvMrs+QAuSERZWcHBNIOMH+OEqvjWuw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.0.5.tgz", + "integrity": "sha512-h/sjXEs4GS+NFFfqBDYT7y5Msfxh04EwWLhQi0F8kuWpe+J/7tICSlswU8qvBqumR3kFgHbfu7vU6qruWWBPug==", "dependencies": { - "@jest/environment": "30.0.4", - "@jest/expect": "30.0.4", - "@jest/test-result": "30.0.4", - "@jest/types": "30.0.1", + "@jest/environment": "30.0.5", + "@jest/expect": "30.0.5", + "@jest/test-result": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", "chalk": "^4.1.2", "co": "^4.6.0", "dedent": "^1.6.0", "is-generator-fn": "^2.1.0", - "jest-each": "30.0.2", - "jest-matcher-utils": "30.0.4", - "jest-message-util": "30.0.2", - "jest-runtime": "30.0.4", - "jest-snapshot": "30.0.4", - "jest-util": "30.0.2", + "jest-each": "30.0.5", + "jest-matcher-utils": "30.0.5", + "jest-message-util": "30.0.5", + "jest-runtime": "30.0.5", + "jest-snapshot": "30.0.5", + "jest-util": "30.0.5", "p-limit": "^3.1.0", - "pretty-format": "30.0.2", + "pretty-format": "30.0.5", "pure-rand": "^7.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" @@ -6677,6 +6928,34 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-circus/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-circus/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/jest-circus/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -6693,19 +6972,19 @@ } }, "node_modules/jest-cli": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.0.4.tgz", - "integrity": "sha512-3dOrP3zqCWBkjoVG1zjYJpD9143N9GUCbwaF2pFF5brnIgRLHmKcCIw+83BvF1LxggfMWBA0gxkn6RuQVuRhIQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.0.5.tgz", + "integrity": "sha512-Sa45PGMkBZzF94HMrlX4kUyPOwUpdZasaliKN3mifvDmkhLYqLLg8HQTzn6gq7vJGahFYMQjXgyJWfYImKZzOw==", "dependencies": { - "@jest/core": "30.0.4", - "@jest/test-result": "30.0.4", - "@jest/types": "30.0.1", + "@jest/core": "30.0.5", + "@jest/test-result": "30.0.5", + "@jest/types": "30.0.5", "chalk": "^4.1.2", "exit-x": "^0.2.2", "import-local": "^3.2.0", - "jest-config": "30.0.4", - "jest-util": "30.0.2", - "jest-validate": "30.0.2", + "jest-config": "30.0.5", + "jest-util": "30.0.5", + "jest-validate": "30.0.5", "yargs": "^17.7.2" }, "bin": { @@ -6723,6 +7002,34 @@ } } }, + "node_modules/jest-cli/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-cli/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/jest-cli/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -6739,32 +7046,32 @@ } }, "node_modules/jest-config": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.0.4.tgz", - "integrity": "sha512-3dzbO6sh34thAGEjJIW0fgT0GA0EVlkski6ZzMcbW6dzhenylXAE/Mj2MI4HonroWbkKc6wU6bLVQ8dvBSZ9lA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.0.5.tgz", + "integrity": "sha512-aIVh+JNOOpzUgzUnPn5FLtyVnqc3TQHVMupYtyeURSb//iLColiMIR8TxCIDKyx9ZgjKnXGucuW68hCxgbrwmA==", "dependencies": { "@babel/core": "^7.27.4", "@jest/get-type": "30.0.1", "@jest/pattern": "30.0.1", - "@jest/test-sequencer": "30.0.4", - "@jest/types": "30.0.1", - "babel-jest": "30.0.4", + "@jest/test-sequencer": "30.0.5", + "@jest/types": "30.0.5", + "babel-jest": "30.0.5", "chalk": "^4.1.2", "ci-info": "^4.2.0", "deepmerge": "^4.3.1", "glob": "^10.3.10", "graceful-fs": "^4.2.11", - "jest-circus": "30.0.4", + "jest-circus": "30.0.5", "jest-docblock": "30.0.1", - "jest-environment-node": "30.0.4", + "jest-environment-node": "30.0.5", "jest-regex-util": "30.0.1", - "jest-resolve": "30.0.2", - "jest-runner": "30.0.4", - "jest-util": "30.0.2", - "jest-validate": "30.0.2", + "jest-resolve": "30.0.5", + "jest-runner": "30.0.5", + "jest-util": "30.0.5", + "jest-validate": "30.0.5", "micromatch": "^4.0.8", "parse-json": "^5.2.0", - "pretty-format": "30.0.2", + "pretty-format": "30.0.5", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -6788,6 +7095,34 @@ } } }, + "node_modules/jest-config/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-config/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/jest-config/node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -6879,14 +7214,14 @@ } }, "node_modules/jest-diff": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.0.4.tgz", - "integrity": "sha512-TSjceIf6797jyd+R64NXqicttROD+Qf98fex7CowmlSn7f8+En0da1Dglwr1AXxDtVizoxXYZBlUQwNhoOXkNw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.0.5.tgz", + "integrity": "sha512-1UIqE9PoEKaHcIKvq2vbibrCog4Y8G0zmOxgQUVEiTqwR5hJVMCoDsN1vFvI5JvwD37hjueZ1C4l2FyGnfpE0A==", "dependencies": { "@jest/diff-sequences": "30.0.1", "@jest/get-type": "30.0.1", "chalk": "^4.1.2", - "pretty-format": "30.0.2" + "pretty-format": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -6919,15 +7254,43 @@ } }, "node_modules/jest-each": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.0.2.tgz", - "integrity": "sha512-ZFRsTpe5FUWFQ9cWTMguCaiA6kkW5whccPy9JjD1ezxh+mJeqmz8naL8Fl/oSbNJv3rgB0x87WBIkA5CObIUZQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.0.5.tgz", + "integrity": "sha512-dKjRsx1uZ96TVyejD3/aAWcNKy6ajMaN531CwWIsrazIqIoXI9TnnpPlkrEYku/8rkS3dh2rbH+kMOyiEIv0xQ==", "dependencies": { "@jest/get-type": "30.0.1", - "@jest/types": "30.0.1", + "@jest/types": "30.0.5", "chalk": "^4.1.2", - "jest-util": "30.0.2", - "pretty-format": "30.0.2" + "jest-util": "30.0.5", + "pretty-format": "30.0.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-each/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-each/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -6949,35 +7312,78 @@ } }, "node_modules/jest-environment-node": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.0.4.tgz", - "integrity": "sha512-p+rLEzC2eThXqiNh9GHHTC0OW5Ca4ZfcURp7scPjYBcmgpR9HG6750716GuUipYf2AcThU3k20B31USuiaaIEg==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.0.5.tgz", + "integrity": "sha512-ppYizXdLMSvciGsRsMEnv/5EFpvOdXBaXRBzFUDPWrsfmog4kYrOGWXarLllz6AXan6ZAA/kYokgDWuos1IKDA==", "dependencies": { - "@jest/environment": "30.0.4", - "@jest/fake-timers": "30.0.4", - "@jest/types": "30.0.1", + "@jest/environment": "30.0.5", + "@jest/fake-timers": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", - "jest-mock": "30.0.2", - "jest-util": "30.0.2", - "jest-validate": "30.0.2" + "jest-mock": "30.0.5", + "jest-util": "30.0.5", + "jest-validate": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-haste-map": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.0.2.tgz", - "integrity": "sha512-telJBKpNLeCb4MaX+I5k496556Y2FiKR/QLZc0+MGBYl4k3OO0472drlV2LUe7c1Glng5HuAu+5GLYp//GpdOQ==", + "node_modules/jest-environment-node/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", "dependencies": { - "@jest/types": "30.0.1", + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-node/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-node/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-haste-map": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.0.5.tgz", + "integrity": "sha512-dkmlWNlsTSR0nH3nRfW5BKbqHefLZv0/6LCccG0xFCTWcJu8TuEwG+5Cm75iBfjVoockmO6J35o5gxtFSn5xeg==", + "dependencies": { + "@jest/types": "30.0.5", "@types/node": "*", "anymatch": "^3.1.3", "fb-watchman": "^2.0.2", "graceful-fs": "^4.2.11", "jest-regex-util": "30.0.1", - "jest-util": "30.0.2", - "jest-worker": "30.0.2", + "jest-util": "30.0.5", + "jest-worker": "30.0.5", "micromatch": "^4.0.8", "walker": "^1.0.8" }, @@ -6988,27 +7394,70 @@ "fsevents": "^2.3.3" } }, + "node_modules/jest-haste-map/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-haste-map/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-haste-map/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-leak-detector": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.0.2.tgz", - "integrity": "sha512-U66sRrAYdALq+2qtKffBLDWsQ/XoNNs2Lcr83sc9lvE/hEpNafJlq2lXCPUBMNqamMECNxSIekLfe69qg4KMIQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.0.5.tgz", + "integrity": "sha512-3Uxr5uP8jmHMcsOtYMRB/zf1gXN3yUIc+iPorhNETG54gErFIiUhLvyY/OggYpSMOEYqsmRxmuU4ZOoX5jpRFg==", "dependencies": { "@jest/get-type": "30.0.1", - "pretty-format": "30.0.2" + "pretty-format": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.0.4.tgz", - "integrity": "sha512-ubCewJ54YzeAZ2JeHHGVoU+eDIpQFsfPQs0xURPWoNiO42LGJ+QGgfSf+hFIRplkZDkhH5MOvuxHKXRTUU3dUQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.0.5.tgz", + "integrity": "sha512-uQgGWt7GOrRLP1P7IwNWwK1WAQbq+m//ZY0yXygyfWp0rJlksMSLQAA4wYQC3b6wl3zfnchyTx+k3HZ5aPtCbQ==", "dependencies": { "@jest/get-type": "30.0.1", "chalk": "^4.1.2", - "jest-diff": "30.0.4", - "pretty-format": "30.0.2" + "jest-diff": "30.0.5", + "pretty-format": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -7030,18 +7479,17 @@ } }, "node_modules/jest-message-util": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.0.2.tgz", - "integrity": "sha512-vXywcxmr0SsKXF/bAD7t7nMamRvPuJkras00gqYeB1V0WllxZrbZ0paRr3XqpFU2sYYjD0qAaG2fRyn/CGZ0aw==", - "license": "MIT", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.0.5.tgz", + "integrity": "sha512-NAiDOhsK3V7RU0Aa/HnrQo+E4JlbarbmI3q6Pi4KcxicdtjV82gcIUrejOtczChtVQR4kddu1E1EJlW6EN9IyA==", "dependencies": { "@babel/code-frame": "^7.27.1", - "@jest/types": "30.0.1", + "@jest/types": "30.0.5", "@types/stack-utils": "^2.0.3", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "micromatch": "^4.0.8", - "pretty-format": "30.0.2", + "pretty-format": "30.0.5", "slash": "^3.0.0", "stack-utils": "^2.0.6" }, @@ -7049,11 +7497,38 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-message-util/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/jest-message-util/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -7066,19 +7541,61 @@ } }, "node_modules/jest-mock": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.2.tgz", - "integrity": "sha512-PnZOHmqup/9cT/y+pXIVbbi8ID6U1XHRmbvR7MvUy4SLqhCbwpkmXhLbsWbGewHrV5x/1bF7YDjs+x24/QSvFA==", - "license": "MIT", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.5.tgz", + "integrity": "sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ==", "dependencies": { - "@jest/types": "30.0.1", + "@jest/types": "30.0.5", "@types/node": "*", - "jest-util": "30.0.2" + "jest-util": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-mock/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-mock/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-mock/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-offline": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/jest-offline/-/jest-offline-1.0.1.tgz", @@ -7113,16 +7630,16 @@ } }, "node_modules/jest-resolve": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.0.2.tgz", - "integrity": "sha512-q/XT0XQvRemykZsvRopbG6FQUT6/ra+XV6rPijyjT6D0msOyCvR2A5PlWZLd+fH0U8XWKZfDiAgrUNDNX2BkCw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.0.5.tgz", + "integrity": "sha512-d+DjBQ1tIhdz91B79mywH5yYu76bZuE96sSbxj8MkjWVx5WNdt1deEFRONVL4UkKLSrAbMkdhb24XN691yDRHg==", "dependencies": { "chalk": "^4.1.2", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.0.2", + "jest-haste-map": "30.0.5", "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.0.2", - "jest-validate": "30.0.2", + "jest-util": "30.0.5", + "jest-validate": "30.0.5", "slash": "^3.0.0", "unrs-resolver": "^1.7.11" }, @@ -7131,12 +7648,12 @@ } }, "node_modules/jest-resolve-dependencies": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.0.4.tgz", - "integrity": "sha512-EQBYow19B/hKr4gUTn+l8Z+YLlP2X0IoPyp0UydOtrcPbIOYzJ8LKdFd+yrbwztPQvmlBFUwGPPEzHH1bAvFAw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.0.5.tgz", + "integrity": "sha512-/xMvBR4MpwkrHW4ikZIWRttBBRZgWK4d6xt3xW1iRDSKt4tXzYkMkyPfBnSCgv96cpkrctfXs6gexeqMYqdEpw==", "dependencies": { "jest-regex-util": "30.0.1", - "jest-snapshot": "30.0.4" + "jest-snapshot": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -7158,30 +7675,30 @@ } }, "node_modules/jest-runner": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.0.4.tgz", - "integrity": "sha512-mxY0vTAEsowJwvFJo5pVivbCpuu6dgdXRmt3v3MXjBxFly7/lTk3Td0PaMyGOeNQUFmSuGEsGYqhbn7PA9OekQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.0.5.tgz", + "integrity": "sha512-JcCOucZmgp+YuGgLAXHNy7ualBx4wYSgJVWrYMRBnb79j9PD0Jxh0EHvR5Cx/r0Ce+ZBC4hCdz2AzFFLl9hCiw==", "dependencies": { - "@jest/console": "30.0.4", - "@jest/environment": "30.0.4", - "@jest/test-result": "30.0.4", - "@jest/transform": "30.0.4", - "@jest/types": "30.0.1", + "@jest/console": "30.0.5", + "@jest/environment": "30.0.5", + "@jest/test-result": "30.0.5", + "@jest/transform": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", "chalk": "^4.1.2", "emittery": "^0.13.1", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", "jest-docblock": "30.0.1", - "jest-environment-node": "30.0.4", - "jest-haste-map": "30.0.2", - "jest-leak-detector": "30.0.2", - "jest-message-util": "30.0.2", - "jest-resolve": "30.0.2", - "jest-runtime": "30.0.4", - "jest-util": "30.0.2", - "jest-watcher": "30.0.4", - "jest-worker": "30.0.2", + "jest-environment-node": "30.0.5", + "jest-haste-map": "30.0.5", + "jest-leak-detector": "30.0.5", + "jest-message-util": "30.0.5", + "jest-resolve": "30.0.5", + "jest-runtime": "30.0.5", + "jest-util": "30.0.5", + "jest-watcher": "30.0.5", + "jest-worker": "30.0.5", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, @@ -7189,6 +7706,34 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-runner/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runner/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/jest-runner/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -7205,30 +7750,30 @@ } }, "node_modules/jest-runtime": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.0.4.tgz", - "integrity": "sha512-tUQrZ8+IzoZYIHoPDQEB4jZoPyzBjLjq7sk0KVyd5UPRjRDOsN7o6UlvaGF8ddpGsjznl9PW+KRgWqCNO+Hn7w==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.0.5.tgz", + "integrity": "sha512-7oySNDkqpe4xpX5PPiJTe5vEa+Ak/NnNz2bGYZrA1ftG3RL3EFlHaUkA1Cjx+R8IhK0Vg43RML5mJedGTPNz3A==", "dependencies": { - "@jest/environment": "30.0.4", - "@jest/fake-timers": "30.0.4", - "@jest/globals": "30.0.4", + "@jest/environment": "30.0.5", + "@jest/fake-timers": "30.0.5", + "@jest/globals": "30.0.5", "@jest/source-map": "30.0.1", - "@jest/test-result": "30.0.4", - "@jest/transform": "30.0.4", - "@jest/types": "30.0.1", + "@jest/test-result": "30.0.5", + "@jest/transform": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", "chalk": "^4.1.2", "cjs-module-lexer": "^2.1.0", "collect-v8-coverage": "^1.0.2", "glob": "^10.3.10", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.0.2", - "jest-message-util": "30.0.2", - "jest-mock": "30.0.2", + "jest-haste-map": "30.0.5", + "jest-message-util": "30.0.5", + "jest-mock": "30.0.5", "jest-regex-util": "30.0.1", - "jest-resolve": "30.0.2", - "jest-snapshot": "30.0.4", - "jest-util": "30.0.2", + "jest-resolve": "30.0.5", + "jest-snapshot": "30.0.5", + "jest-util": "30.0.5", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -7236,6 +7781,34 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-runtime/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runtime/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/jest-runtime/node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -7327,29 +7900,29 @@ } }, "node_modules/jest-snapshot": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.0.4.tgz", - "integrity": "sha512-S/8hmSkeUib8WRUq9pWEb5zMfsOjiYWDWzFzKnjX7eDyKKgimsu9hcmsUEg8a7dPAw8s/FacxsXquq71pDgPjQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.0.5.tgz", + "integrity": "sha512-T00dWU/Ek3LqTp4+DcW6PraVxjk28WY5Ua/s+3zUKSERZSNyxTqhDXCWKG5p2HAJ+crVQ3WJ2P9YVHpj1tkW+g==", "dependencies": { "@babel/core": "^7.27.4", "@babel/generator": "^7.27.5", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.0.4", + "@jest/expect-utils": "30.0.5", "@jest/get-type": "30.0.1", - "@jest/snapshot-utils": "30.0.4", - "@jest/transform": "30.0.4", - "@jest/types": "30.0.1", + "@jest/snapshot-utils": "30.0.5", + "@jest/transform": "30.0.5", + "@jest/types": "30.0.5", "babel-preset-current-node-syntax": "^1.1.0", "chalk": "^4.1.2", - "expect": "30.0.4", + "expect": "30.0.5", "graceful-fs": "^4.2.11", - "jest-diff": "30.0.4", - "jest-matcher-utils": "30.0.4", - "jest-message-util": "30.0.2", - "jest-util": "30.0.2", - "pretty-format": "30.0.2", + "jest-diff": "30.0.5", + "jest-matcher-utils": "30.0.5", + "jest-message-util": "30.0.5", + "jest-util": "30.0.5", + "pretty-format": "30.0.5", "semver": "^7.7.2", "synckit": "^0.11.8" }, @@ -7357,6 +7930,34 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-snapshot/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/jest-snapshot/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -7373,12 +7974,11 @@ } }, "node_modules/jest-util": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.2.tgz", - "integrity": "sha512-8IyqfKS4MqprBuUpZNlFB5l+WFehc8bfCe1HSZFHzft2mOuND8Cvi9r1musli+u6F3TqanCZ/Ik4H4pXUolZIg==", - "license": "MIT", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.5.tgz", + "integrity": "sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==", "dependencies": { - "@jest/types": "30.0.1", + "@jest/types": "30.0.5", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", @@ -7389,11 +7989,38 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-util/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/jest-util/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -7406,10 +8033,9 @@ } }, "node_modules/jest-util/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "license": "MIT", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "engines": { "node": ">=12" }, @@ -7418,16 +8044,44 @@ } }, "node_modules/jest-validate": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.0.2.tgz", - "integrity": "sha512-noOvul+SFER4RIvNAwGn6nmV2fXqBq67j+hKGHKGFCmK4ks/Iy1FSrqQNBLGKlu4ZZIRL6Kg1U72N1nxuRCrGQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.0.5.tgz", + "integrity": "sha512-ouTm6VFHaS2boyl+k4u+Qip4TSH7Uld5tyD8psQ8abGgt2uYYB8VwVfAHWHjHc0NWmGGbwO5h0sCPOGHHevefw==", "dependencies": { "@jest/get-type": "30.0.1", - "@jest/types": "30.0.1", + "@jest/types": "30.0.5", "camelcase": "^6.3.0", "chalk": "^4.1.2", "leven": "^3.1.0", - "pretty-format": "30.0.2" + "pretty-format": "30.0.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-validate/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-validate/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -7460,23 +8114,51 @@ } }, "node_modules/jest-watcher": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.0.4.tgz", - "integrity": "sha512-YESbdHDs7aQOCSSKffG8jXqOKFqw4q4YqR+wHYpR5GWEQioGvL0BfbcjvKIvPEM0XGfsfJrka7jJz3Cc3gI4VQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.0.5.tgz", + "integrity": "sha512-z9slj/0vOwBDBjN3L4z4ZYaA+pG56d6p3kTUhFRYGvXbXMWhXmb/FIxREZCD06DYUwDKKnj2T80+Pb71CQ0KEg==", "dependencies": { - "@jest/test-result": "30.0.4", - "@jest/types": "30.0.1", + "@jest/test-result": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "emittery": "^0.13.1", - "jest-util": "30.0.2", + "jest-util": "30.0.5", "string-length": "^4.0.2" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-watcher/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-watcher/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/jest-watcher/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -7493,13 +8175,13 @@ } }, "node_modules/jest-worker": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.0.2.tgz", - "integrity": "sha512-RN1eQmx7qSLFA+o9pfJKlqViwL5wt+OL3Vff/A+/cPsmuw7NPwfgl33AP+/agRmHzPOFgXviRycR9kYwlcRQXg==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.0.5.tgz", + "integrity": "sha512-ojRXsWzEP16NdUuBw/4H/zkZdHOa7MMYCk4E430l+8fELeLg/mqmMlRhjL7UNZvQrDmnovWZV4DxX03fZF48fQ==", "dependencies": { "@types/node": "*", "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.0.2", + "jest-util": "30.0.5", "merge-stream": "^2.0.0", "supports-color": "^8.1.1" }, @@ -7521,6 +8203,49 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jest/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest/node_modules/@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/js-git": { "version": "0.7.8", "resolved": "https://registry.npmjs.org/js-git/-/js-git-0.7.8.tgz", @@ -7954,9 +8679,9 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, "node_modules/napi-postinstall": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.0.tgz", - "integrity": "sha512-M7NqKyhODKV1gRLdkwE7pDsZP2/SC2a2vHkOYh9MCpKMbWVfyVfUw5MaH83Fv6XMjxr5jryUp3IDDL9rlxsTeA==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.2.tgz", + "integrity": "sha512-tWVJxJHmBWLy69PvO96TZMZDrzmw5KeiZBz3RHmiM2XZ9grBJ2WgMAFVVg25nqp3ZjTFUs2Ftw1JhscL3Teliw==", "bin": { "napi-postinstall": "lib/cli.js" }, @@ -8150,7 +8875,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", "dependencies": { "wrappy": "1" } @@ -8201,31 +8925,6 @@ "node": ">=0.10.0" } }, - "node_modules/oxlint": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.7.0.tgz", - "integrity": "sha512-krJN1fIRhs3xK1FyVyPtYIV9tkT4WDoIwI7eiMEKBuCjxqjQt5ZemQm1htPvHqNDOaWFRFt4btcwFdU8bbwgvA==", - "bin": { - "oxc_language_server": "bin/oxc_language_server", - "oxlint": "bin/oxlint" - }, - "engines": { - "node": ">=8.*" - }, - "funding": { - "url": "https://github.com/sponsors/Boshen" - }, - "optionalDependencies": { - "@oxlint/darwin-arm64": "1.7.0", - "@oxlint/darwin-x64": "1.7.0", - "@oxlint/linux-arm64-gnu": "1.7.0", - "@oxlint/linux-arm64-musl": "1.7.0", - "@oxlint/linux-x64-gnu": "1.7.0", - "@oxlint/linux-x64-musl": "1.7.0", - "@oxlint/win32-arm64": "1.7.0", - "@oxlint/win32-x64": "1.7.0" - } - }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -8411,7 +9110,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -8845,27 +9543,12 @@ "node": ">= 0.8.0" } }, - "node_modules/prettier": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", - "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, "node_modules/pretty-format": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.2.tgz", - "integrity": "sha512-yC5/EBSOrTtqhCKfLHqoUIAXVRZnukHPwWBJWR7h84Q3Be1DRQZLncwcfLoPA5RPQ65qfiCMqgYwdUuQ//eVpg==", - "license": "MIT", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz", + "integrity": "sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==", "dependencies": { - "@jest/schemas": "30.0.1", + "@jest/schemas": "30.0.5", "ansi-styles": "^5.2.0", "react-is": "^18.3.1" }, @@ -8873,6 +9556,17 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/pretty-format/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/pretty-format/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", @@ -9152,13 +9846,9 @@ } }, "node_modules/run-async": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-4.0.4.tgz", - "integrity": "sha512-2cgeRHnV11lSXBEhq7sN7a5UVjTKm9JTb9x8ApIT//16D7QL96AgnNeWSGoB4gIHc0iYw/Ha0Z+waBaCYZVNhg==", - "dependencies": { - "oxlint": "^1.2.0", - "prettier": "^3.5.3" - }, + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-4.0.5.tgz", + "integrity": "sha512-oN9GTgxUNDBumHTTDmQ8dep6VIJbgj9S3dPP+9XylVLIK4xB9XTXtKWROd5pnhdXR9k0EgO1JRcNh0T+Ny2FsA==", "engines": { "node": ">=0.12.0" } @@ -9767,11 +10457,11 @@ } }, "node_modules/synckit": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.8.tgz", - "integrity": "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==", + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", + "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", "dependencies": { - "@pkgr/core": "^0.2.4" + "@pkgr/core": "^0.2.9" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -10045,10 +10735,9 @@ } }, "node_modules/undici": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.10.0.tgz", - "integrity": "sha512-u5otvFBOBZvmdjWLVW+5DAc9Nkq8f24g0O9oY7qw2JVIF1VocIFoyz9JFkuVOS2j41AufeO0xnlweJ2RLT8nGw==", - "license": "MIT", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.12.0.tgz", + "integrity": "sha512-GrKEsc3ughskmGA9jevVlIOPMiiAHJ4OFUtaAH+NhfTUSiZ1wMPIQqQvAJUrJspFXJt3EBWgpAeoHEDVT1IBug==", "engines": { "node": ">=20.18.1" } @@ -10182,6 +10871,18 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, "node_modules/uzip-module": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/uzip-module/-/uzip-module-1.0.3.tgz", @@ -10428,8 +11129,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/write-file-atomic": { "version": "5.0.1", @@ -10689,12 +11389,12 @@ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==" }, "@babel/helpers": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", - "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.2.tgz", + "integrity": "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==", "requires": { "@babel/template": "^7.27.2", - "@babel/types": "^7.27.6" + "@babel/types": "^7.28.2" } }, "@babel/parser": { @@ -10866,9 +11566,9 @@ } }, "@babel/types": { - "version": "7.28.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.1.tgz", - "integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", "requires": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" @@ -10895,33 +11595,38 @@ } }, "@emnapi/core": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.4.tgz", - "integrity": "sha512-A9CnAbC6ARNMKcIcrQwq6HeHCjpcBZ5wSx4U01WXCqEKlrzB9F9315WDNHkrs2xbx7YjjSxbUYxuN6EQzpcY2g==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.5.tgz", + "integrity": "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==", "optional": true, "requires": { - "@emnapi/wasi-threads": "1.0.3", + "@emnapi/wasi-threads": "1.0.4", "tslib": "^2.4.0" } }, "@emnapi/runtime": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.4.tgz", - "integrity": "sha512-hHyapA4A3gPaDCNfiqyZUStTMqIkKRshqPIuDOXv1hcBnD4U3l8cP0T1HMCfGRxQ6V64TGCcoswChANyOAwbQg==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz", + "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==", "optional": true, "requires": { "tslib": "^2.4.0" } }, "@emnapi/wasi-threads": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.3.tgz", - "integrity": "sha512-8K5IFFsQqF9wQNJptGbS6FNKgUTsSRYnTqNCG1vPP8jFdjSv18n2mQfJpkt2Oibo9iBEzcDnDxNwKTzC7svlJw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.4.tgz", + "integrity": "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==", "optional": true, "requires": { "tslib": "^2.4.0" } }, + "@epic-web/invariant": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz", + "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==" + }, "@esbuild/aix-ppc64": { "version": "0.25.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", @@ -11145,9 +11850,9 @@ } }, "@eslint/js": { - "version": "9.31.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz", - "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==" + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.32.0.tgz", + "integrity": "sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==" }, "@eslint/object-schema": { "version": "2.1.6", @@ -11230,33 +11935,33 @@ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==" }, "@inquirer/checkbox": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.1.9.tgz", - "integrity": "sha512-DBJBkzI5Wx4jFaYm221LHvAhpKYkhVS0k9plqHwaHhofGNxvYB7J3Bz8w+bFJ05zaMb0sZNHo4KdmENQFlNTuQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.2.0.tgz", + "integrity": "sha512-fdSw07FLJEU5vbpOPzXo5c6xmMGDzbZE2+niuDHX5N6mc6V0Ebso/q3xiHra4D73+PMsC8MJmcaZKuAAoaQsSA==", "requires": { - "@inquirer/core": "^10.1.14", - "@inquirer/figures": "^1.0.12", - "@inquirer/type": "^3.0.7", + "@inquirer/core": "^10.1.15", + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", "ansi-escapes": "^4.3.2", "yoctocolors-cjs": "^2.1.2" } }, "@inquirer/confirm": { - "version": "5.1.13", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.13.tgz", - "integrity": "sha512-EkCtvp67ICIVVzjsquUiVSd+V5HRGOGQfsqA4E4vMWhYnB7InUL0pa0TIWt1i+OfP16Gkds8CdIu6yGZwOM1Yw==", + "version": "5.1.14", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.14.tgz", + "integrity": "sha512-5yR4IBfe0kXe59r1YCTG8WXkUbl7Z35HK87Sw+WUyGD8wNUx7JvY7laahzeytyE1oLn74bQnL7hstctQxisQ8Q==", "requires": { - "@inquirer/core": "^10.1.14", - "@inquirer/type": "^3.0.7" + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8" } }, "@inquirer/core": { - "version": "10.1.14", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.14.tgz", - "integrity": "sha512-Ma+ZpOJPewtIYl6HZHZckeX1STvDnHTCB2GVINNUlSEn2Am6LddWwfPkIGY0IUFVjUUrr/93XlBwTK6mfLjf0A==", + "version": "10.1.15", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.15.tgz", + "integrity": "sha512-8xrp836RZvKkpNbVvgWUlxjT4CraKk2q+I3Ksy+seI2zkcE+y6wNs1BVhgcv8VyImFecUhdQrYLdW32pAjwBdA==", "requires": { - "@inquirer/figures": "^1.0.12", - "@inquirer/type": "^3.0.7", + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", "ansi-escapes": "^4.3.2", "cli-width": "^4.1.0", "mute-stream": "^2.0.0", @@ -11278,112 +11983,112 @@ } }, "@inquirer/editor": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.14.tgz", - "integrity": "sha512-yd2qtLl4QIIax9DTMZ1ZN2pFrrj+yL3kgIWxm34SS6uwCr0sIhsNyudUjAo5q3TqI03xx4SEBkUJqZuAInp9uA==", + "version": "4.2.15", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.15.tgz", + "integrity": "sha512-wst31XT8DnGOSS4nNJDIklGKnf+8shuauVrWzgKegWUe28zfCftcWZ2vktGdzJgcylWSS2SrDnYUb6alZcwnCQ==", "requires": { - "@inquirer/core": "^10.1.14", - "@inquirer/type": "^3.0.7", + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8", "external-editor": "^3.1.0" } }, "@inquirer/expand": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.16.tgz", - "integrity": "sha512-oiDqafWzMtofeJyyGkb1CTPaxUkjIcSxePHHQCfif8t3HV9pHcw1Kgdw3/uGpDvaFfeTluwQtWiqzPVjAqS3zA==", + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.17.tgz", + "integrity": "sha512-PSqy9VmJx/VbE3CT453yOfNa+PykpKg/0SYP7odez1/NWBGuDXgPhp4AeGYYKjhLn5lUUavVS/JbeYMPdH50Mw==", "requires": { - "@inquirer/core": "^10.1.14", - "@inquirer/type": "^3.0.7", + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8", "yoctocolors-cjs": "^2.1.2" } }, "@inquirer/figures": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.12.tgz", - "integrity": "sha512-MJttijd8rMFcKJC8NYmprWr6hD3r9Gd9qUC0XwPNwoEPWSMVJwA2MlXxF+nhZZNMY+HXsWa+o7KY2emWYIn0jQ==" + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.13.tgz", + "integrity": "sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==" }, "@inquirer/input": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.2.0.tgz", - "integrity": "sha512-opqpHPB1NjAmDISi3uvZOTrjEEU5CWVu/HBkDby8t93+6UxYX0Z7Ps0Ltjm5sZiEbWenjubwUkivAEYQmy9xHw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.2.1.tgz", + "integrity": "sha512-tVC+O1rBl0lJpoUZv4xY+WGWY8V5b0zxU1XDsMsIHYregdh7bN5X5QnIONNBAl0K765FYlAfNHS2Bhn7SSOVow==", "requires": { - "@inquirer/core": "^10.1.14", - "@inquirer/type": "^3.0.7" + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8" } }, "@inquirer/number": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.16.tgz", - "integrity": "sha512-kMrXAaKGavBEoBYUCgualbwA9jWUx2TjMA46ek+pEKy38+LFpL9QHlTd8PO2kWPUgI/KB+qi02o4y2rwXbzr3Q==", + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.17.tgz", + "integrity": "sha512-GcvGHkyIgfZgVnnimURdOueMk0CztycfC8NZTiIY9arIAkeOgt6zG57G+7vC59Jns3UX27LMkPKnKWAOF5xEYg==", "requires": { - "@inquirer/core": "^10.1.14", - "@inquirer/type": "^3.0.7" + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8" } }, "@inquirer/password": { - "version": "4.0.16", - "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.16.tgz", - "integrity": "sha512-g8BVNBj5Zeb5/Y3cSN+hDUL7CsIFDIuVxb9EPty3lkxBaYpjL5BNRKSYOF9yOLe+JOcKFd+TSVeADQ4iSY7rbg==", + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.17.tgz", + "integrity": "sha512-DJolTnNeZ00E1+1TW+8614F7rOJJCM4y4BAGQ3Gq6kQIG+OJ4zr3GLjIjVVJCbKsk2jmkmv6v2kQuN/vriHdZA==", "requires": { - "@inquirer/core": "^10.1.14", - "@inquirer/type": "^3.0.7", + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8", "ansi-escapes": "^4.3.2" } }, "@inquirer/prompts": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.6.0.tgz", - "integrity": "sha512-jAhL7tyMxB3Gfwn4HIJ0yuJ5pvcB5maYUcouGcgd/ub79f9MqZ+aVnBtuFf+VC2GTkCBF+R+eo7Vi63w5VZlzw==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.8.0.tgz", + "integrity": "sha512-JHwGbQ6wjf1dxxnalDYpZwZxUEosT+6CPGD9Zh4sm9WXdtUp9XODCQD3NjSTmu+0OAyxWXNOqf0spjIymJa2Tw==", "requires": { - "@inquirer/checkbox": "^4.1.9", - "@inquirer/confirm": "^5.1.13", - "@inquirer/editor": "^4.2.14", - "@inquirer/expand": "^4.0.16", - "@inquirer/input": "^4.2.0", - "@inquirer/number": "^3.0.16", - "@inquirer/password": "^4.0.16", - "@inquirer/rawlist": "^4.1.4", - "@inquirer/search": "^3.0.16", - "@inquirer/select": "^4.2.4" + "@inquirer/checkbox": "^4.2.0", + "@inquirer/confirm": "^5.1.14", + "@inquirer/editor": "^4.2.15", + "@inquirer/expand": "^4.0.17", + "@inquirer/input": "^4.2.1", + "@inquirer/number": "^3.0.17", + "@inquirer/password": "^4.0.17", + "@inquirer/rawlist": "^4.1.5", + "@inquirer/search": "^3.1.0", + "@inquirer/select": "^4.3.1" } }, "@inquirer/rawlist": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.4.tgz", - "integrity": "sha512-5GGvxVpXXMmfZNtvWw4IsHpR7RzqAR624xtkPd1NxxlV5M+pShMqzL4oRddRkg8rVEOK9fKdJp1jjVML2Lr7TQ==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.5.tgz", + "integrity": "sha512-R5qMyGJqtDdi4Ht521iAkNqyB6p2UPuZUbMifakg1sWtu24gc2Z8CJuw8rP081OckNDMgtDCuLe42Q2Kr3BolA==", "requires": { - "@inquirer/core": "^10.1.14", - "@inquirer/type": "^3.0.7", + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8", "yoctocolors-cjs": "^2.1.2" } }, "@inquirer/search": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.0.16.tgz", - "integrity": "sha512-POCmXo+j97kTGU6aeRjsPyuCpQQfKcMXdeTMw708ZMtWrj5aykZvlUxH4Qgz3+Y1L/cAVZsSpA+UgZCu2GMOMg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.1.0.tgz", + "integrity": "sha512-PMk1+O/WBcYJDq2H7foV0aAZSmDdkzZB9Mw2v/DmONRJopwA/128cS9M/TXWLKKdEQKZnKwBzqu2G4x/2Nqx8Q==", "requires": { - "@inquirer/core": "^10.1.14", - "@inquirer/figures": "^1.0.12", - "@inquirer/type": "^3.0.7", + "@inquirer/core": "^10.1.15", + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", "yoctocolors-cjs": "^2.1.2" } }, "@inquirer/select": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.2.4.tgz", - "integrity": "sha512-unTppUcTjmnbl/q+h8XeQDhAqIOmwWYWNyiiP2e3orXrg6tOaa5DHXja9PChCSbChOsktyKgOieRZFnajzxoBg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.3.1.tgz", + "integrity": "sha512-Gfl/5sqOF5vS/LIrSndFgOh7jgoe0UXEizDqahFRkq5aJBLegZ6WjuMh/hVEJwlFQjyLq1z9fRtvUMkb7jM1LA==", "requires": { - "@inquirer/core": "^10.1.14", - "@inquirer/figures": "^1.0.12", - "@inquirer/type": "^3.0.7", + "@inquirer/core": "^10.1.15", + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", "ansi-escapes": "^4.3.2", "yoctocolors-cjs": "^2.1.2" } }, "@inquirer/type": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.7.tgz", - "integrity": "sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.8.tgz", + "integrity": "sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==", "requires": {} }, "@isaacs/balanced-match": { @@ -11475,18 +12180,40 @@ "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==" }, "@jest/console": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.0.4.tgz", - "integrity": "sha512-tMLCDvBJBwPqMm4OAiuKm2uF5y5Qe26KgcMn+nrDSWpEW+eeFmqA0iO4zJfL16GP7gE3bUUQ3hIuUJ22AqVRnw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.0.5.tgz", + "integrity": "sha512-xY6b0XiL0Nav3ReresUarwl2oIz1gTnxGbGpho9/rbUWsLH0f1OD/VT84xs8c7VmH7MChnLb0pag6PhZhAdDiA==", "requires": { - "@jest/types": "30.0.1", + "@jest/types": "30.0.5", "@types/node": "*", "chalk": "^4.1.2", - "jest-message-util": "30.0.2", - "jest-util": "30.0.2", + "jest-message-util": "30.0.5", + "jest-util": "30.0.5", "slash": "^3.0.0" }, "dependencies": { + "@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "requires": { + "@sinclair/typebox": "^0.34.0" + } + }, + "@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "requires": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -11499,40 +12226,62 @@ } }, "@jest/core": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.0.4.tgz", - "integrity": "sha512-MWScSO9GuU5/HoWjpXAOBs6F/iobvK1XlioelgOM9St7S0Z5WTI9kjCQLPeo4eQRRYusyLW25/J7J5lbFkrYXw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.0.5.tgz", + "integrity": "sha512-fKD0OulvRsXF1hmaFgHhVJzczWzA1RXMMo9LTPuFXo9q/alDbME3JIyWYqovWsUBWSoBcsHaGPSLF9rz4l9Qeg==", "requires": { - "@jest/console": "30.0.4", + "@jest/console": "30.0.5", "@jest/pattern": "30.0.1", - "@jest/reporters": "30.0.4", - "@jest/test-result": "30.0.4", - "@jest/transform": "30.0.4", - "@jest/types": "30.0.1", + "@jest/reporters": "30.0.5", + "@jest/test-result": "30.0.5", + "@jest/transform": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "ci-info": "^4.2.0", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", - "jest-changed-files": "30.0.2", - "jest-config": "30.0.4", - "jest-haste-map": "30.0.2", - "jest-message-util": "30.0.2", + "jest-changed-files": "30.0.5", + "jest-config": "30.0.5", + "jest-haste-map": "30.0.5", + "jest-message-util": "30.0.5", "jest-regex-util": "30.0.1", - "jest-resolve": "30.0.2", - "jest-resolve-dependencies": "30.0.4", - "jest-runner": "30.0.4", - "jest-runtime": "30.0.4", - "jest-snapshot": "30.0.4", - "jest-util": "30.0.2", - "jest-validate": "30.0.2", - "jest-watcher": "30.0.4", + "jest-resolve": "30.0.5", + "jest-resolve-dependencies": "30.0.5", + "jest-runner": "30.0.5", + "jest-runtime": "30.0.5", + "jest-snapshot": "30.0.5", + "jest-util": "30.0.5", + "jest-validate": "30.0.5", + "jest-watcher": "30.0.5", "micromatch": "^4.0.8", - "pretty-format": "30.0.2", + "pretty-format": "30.0.5", "slash": "^3.0.0" }, "dependencies": { + "@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "requires": { + "@sinclair/typebox": "^0.34.0" + } + }, + "@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "requires": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -11558,44 +12307,110 @@ "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==" }, "@jest/environment": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.0.4.tgz", - "integrity": "sha512-5NT+sr7ZOb8wW7C4r7wOKnRQ8zmRWQT2gW4j73IXAKp5/PX1Z8MCStBLQDYfIG3n1Sw0NRfYGdp0iIPVooBAFQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.0.5.tgz", + "integrity": "sha512-aRX7WoaWx1oaOkDQvCWImVQ8XNtdv5sEWgk4gxR6NXb7WBUnL5sRak4WRzIQRZ1VTWPvV4VI4mgGjNL9TeKMYA==", "requires": { - "@jest/fake-timers": "30.0.4", - "@jest/types": "30.0.1", + "@jest/fake-timers": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", - "jest-mock": "30.0.2" + "jest-mock": "30.0.5" + }, + "dependencies": { + "@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "requires": { + "@sinclair/typebox": "^0.34.0" + } + }, + "@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "requires": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "@jest/expect": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.0.4.tgz", - "integrity": "sha512-Z/DL7t67LBHSX4UzDyeYKqOxE/n7lbrrgEwWM3dGiH5Dgn35nk+YtgzKudmfIrBI8DRRrKYY5BCo3317HZV1Fw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.0.5.tgz", + "integrity": "sha512-6udac8KKrtTtC+AXZ2iUN/R7dp7Ydry+Fo6FPFnDG54wjVMnb6vW/XNlf7Xj8UDjAE3aAVAsR4KFyKk3TCXmTA==", "requires": { - "expect": "30.0.4", - "jest-snapshot": "30.0.4" + "expect": "30.0.5", + "jest-snapshot": "30.0.5" } }, "@jest/expect-utils": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.0.4.tgz", - "integrity": "sha512-EgXecHDNfANeqOkcak0DxsoVI4qkDUsR7n/Lr2vtmTBjwLPBnnPOF71S11Q8IObWzxm2QgQoY6f9hzrRD3gHRA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.0.5.tgz", + "integrity": "sha512-F3lmTT7CXWYywoVUGTCmom0vXq3HTTkaZyTAzIy+bXSBizB7o5qzlC9VCtq0arOa8GqmNsbg/cE9C6HLn7Szew==", "requires": { "@jest/get-type": "30.0.1" } }, "@jest/fake-timers": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.0.4.tgz", - "integrity": "sha512-qZ7nxOcL5+gwBO6LErvwVy5k06VsX/deqo2XnVUSTV0TNC9lrg8FC3dARbi+5lmrr5VyX5drragK+xLcOjvjYw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.0.5.tgz", + "integrity": "sha512-ZO5DHfNV+kgEAeP3gK3XlpJLL4U3Sz6ebl/n68Uwt64qFFs5bv4bfEEjyRGK5uM0C90ewooNgFuKMdkbEoMEXw==", "requires": { - "@jest/types": "30.0.1", + "@jest/types": "30.0.5", "@sinonjs/fake-timers": "^13.0.0", "@types/node": "*", - "jest-message-util": "30.0.2", - "jest-mock": "30.0.2", - "jest-util": "30.0.2" + "jest-message-util": "30.0.5", + "jest-mock": "30.0.5", + "jest-util": "30.0.5" + }, + "dependencies": { + "@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "requires": { + "@sinclair/typebox": "^0.34.0" + } + }, + "@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "requires": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "@jest/get-type": { @@ -11604,14 +12419,47 @@ "integrity": "sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw==" }, "@jest/globals": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.0.4.tgz", - "integrity": "sha512-avyZuxEHF2EUhFF6NEWVdxkRRV6iXXcIES66DLhuLlU7lXhtFG/ySq/a8SRZmEJSsLkNAFX6z6mm8KWyXe9OEA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.0.5.tgz", + "integrity": "sha512-7oEJT19WW4oe6HR7oLRvHxwlJk2gev0U9px3ufs8sX9PoD1Eza68KF0/tlN7X0dq/WVsBScXQGgCldA1V9Y/jA==", "requires": { - "@jest/environment": "30.0.4", - "@jest/expect": "30.0.4", - "@jest/types": "30.0.1", - "jest-mock": "30.0.2" + "@jest/environment": "30.0.5", + "@jest/expect": "30.0.5", + "@jest/types": "30.0.5", + "jest-mock": "30.0.5" + }, + "dependencies": { + "@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "requires": { + "@sinclair/typebox": "^0.34.0" + } + }, + "@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "requires": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "@jest/pattern": { @@ -11624,15 +12472,15 @@ } }, "@jest/reporters": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.0.4.tgz", - "integrity": "sha512-6ycNmP0JSJEEys1FbIzHtjl9BP0tOZ/KN6iMeAKrdvGmUsa1qfRdlQRUDKJ4P84hJ3xHw1yTqJt4fvPNHhyE+g==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.0.5.tgz", + "integrity": "sha512-mafft7VBX4jzED1FwGC1o/9QUM2xebzavImZMeqnsklgcyxBto8mV4HzNSzUrryJ+8R9MFOM3HgYuDradWR+4g==", "requires": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.0.4", - "@jest/test-result": "30.0.4", - "@jest/transform": "30.0.4", - "@jest/types": "30.0.1", + "@jest/console": "30.0.5", + "@jest/test-result": "30.0.5", + "@jest/transform": "30.0.5", + "@jest/types": "30.0.5", "@jridgewell/trace-mapping": "^0.3.25", "@types/node": "*", "chalk": "^4.1.2", @@ -11645,14 +12493,36 @@ "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^5.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "30.0.2", - "jest-util": "30.0.2", - "jest-worker": "30.0.2", + "jest-message-util": "30.0.5", + "jest-util": "30.0.5", + "jest-worker": "30.0.5", "slash": "^3.0.0", "string-length": "^4.0.2", "v8-to-istanbul": "^9.0.1" }, "dependencies": { + "@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "requires": { + "@sinclair/typebox": "^0.34.0" + } + }, + "@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "requires": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + } + }, "brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -11725,16 +12595,38 @@ } }, "@jest/snapshot-utils": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.0.4.tgz", - "integrity": "sha512-BEpX8M/Y5lG7MI3fmiO+xCnacOrVsnbqVrcDZIT8aSGkKV1w2WwvRQxSWw5SIS8ozg7+h8tSj5EO1Riqqxcdag==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.0.5.tgz", + "integrity": "sha512-XcCQ5qWHLvi29UUrowgDFvV4t7ETxX91CbDczMnoqXPOIcZOxyNdSjm6kV5XMc8+HkxfRegU/MUmnTbJRzGrUQ==", "requires": { - "@jest/types": "30.0.1", + "@jest/types": "30.0.5", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "natural-compare": "^1.4.0" }, "dependencies": { + "@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "requires": { + "@sinclair/typebox": "^0.34.0" + } + }, + "@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "requires": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -11757,49 +12649,104 @@ } }, "@jest/test-result": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.0.4.tgz", - "integrity": "sha512-Mfpv8kjyKTHqsuu9YugB6z1gcdB3TSSOaKlehtVaiNlClMkEHY+5ZqCY2CrEE3ntpBMlstX/ShDAf84HKWsyIw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.0.5.tgz", + "integrity": "sha512-wPyztnK0gbDMQAJZ43tdMro+qblDHH1Ru/ylzUo21TBKqt88ZqnKKK2m30LKmLLoKtR2lxdpCC/P3g1vfKcawQ==", "requires": { - "@jest/console": "30.0.4", - "@jest/types": "30.0.1", + "@jest/console": "30.0.5", + "@jest/types": "30.0.5", "@types/istanbul-lib-coverage": "^2.0.6", "collect-v8-coverage": "^1.0.2" + }, + "dependencies": { + "@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "requires": { + "@sinclair/typebox": "^0.34.0" + } + }, + "@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "requires": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "@jest/test-sequencer": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.0.4.tgz", - "integrity": "sha512-bj6ePmqi4uxAE8EHE0Slmk5uBYd9Vd/PcVt06CsBxzH4bbA8nGsI1YbXl/NH+eii4XRtyrRx+Cikub0x8H4vDg==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.0.5.tgz", + "integrity": "sha512-Aea/G1egWoIIozmDD7PBXUOxkekXl7ueGzrsGGi1SbeKgQqCYCIf+wfbflEbf2LiPxL8j2JZGLyrzZagjvW4YQ==", "requires": { - "@jest/test-result": "30.0.4", + "@jest/test-result": "30.0.5", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.0.2", + "jest-haste-map": "30.0.5", "slash": "^3.0.0" } }, "@jest/transform": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.0.4.tgz", - "integrity": "sha512-atvy4hRph/UxdCIBp+UB2jhEA/jJiUeGZ7QPgBi9jUUKNgi3WEoMXGNG7zbbELG2+88PMabUNCDchmqgJy3ELg==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.0.5.tgz", + "integrity": "sha512-Vk8amLQCmuZyy6GbBht1Jfo9RSdBtg7Lks+B0PecnjI8J+PCLQPGh7uI8Q/2wwpW2gLdiAfiHNsmekKlywULqg==", "requires": { "@babel/core": "^7.27.4", - "@jest/types": "30.0.1", + "@jest/types": "30.0.5", "@jridgewell/trace-mapping": "^0.3.25", "babel-plugin-istanbul": "^7.0.0", "chalk": "^4.1.2", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.0.2", + "jest-haste-map": "30.0.5", "jest-regex-util": "30.0.1", - "jest-util": "30.0.2", + "jest-util": "30.0.5", "micromatch": "^4.0.8", "pirates": "^4.0.7", "slash": "^3.0.0", "write-file-atomic": "^5.0.1" }, "dependencies": { + "@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "requires": { + "@sinclair/typebox": "^0.34.0" + } + }, + "@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "requires": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -11993,54 +12940,6 @@ "@octokit/openapi-types": "^25.1.0" } }, - "@oxlint/darwin-arm64": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@oxlint/darwin-arm64/-/darwin-arm64-1.7.0.tgz", - "integrity": "sha512-51vhCSQO4NSkedwEwOyqThiYqV0DAUkwNdqMQK0d29j5zmtNJJJRRBLeQuLGdstNmn3F7WMQ75Ci0/3Nq4ff8A==", - "optional": true - }, - "@oxlint/darwin-x64": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@oxlint/darwin-x64/-/darwin-x64-1.7.0.tgz", - "integrity": "sha512-c0GN52yehYZ4TYuh4lBH9wYbBOI/RDOxZhJdBsttG0GwfvKYg/tiPNrNEsPzu0/rd1j6x3yT0zt6vezDMeC1sQ==", - "optional": true - }, - "@oxlint/linux-arm64-gnu": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@oxlint/linux-arm64-gnu/-/linux-arm64-gnu-1.7.0.tgz", - "integrity": "sha512-pam/lbzbzVMDzc3f1hoRPtnUMEIqkn0dynlB5nUll/MVBSIvIPLS9kJLrRA48lrlqbkS9LGiF37JvpwXA58A9A==", - "optional": true - }, - "@oxlint/linux-arm64-musl": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@oxlint/linux-arm64-musl/-/linux-arm64-musl-1.7.0.tgz", - "integrity": "sha512-LTyPy9FYS3SZ2XxJx+ITvlAq/ek5PtZK9Z2m3W72TA8hchGhJy5eQ+aotYjd/YVXOpGRpB12RdOpOTsZRu50bA==", - "optional": true - }, - "@oxlint/linux-x64-gnu": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@oxlint/linux-x64-gnu/-/linux-x64-gnu-1.7.0.tgz", - "integrity": "sha512-YtZ4DiAgjaEiqUiwnvtJ/znZMAAVPKR7pnsi6lqbA3BfXJ/IwMaNpdoGlCGVdDGeN4BuGCwnFtBVqKVvVg3DDg==", - "optional": true - }, - "@oxlint/linux-x64-musl": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@oxlint/linux-x64-musl/-/linux-x64-musl-1.7.0.tgz", - "integrity": "sha512-5aIpemNUBvwMMk4MCx1V3M6R9eMB1/SS6/24Orax9FqaI1lDX08tySdv696sr4Lms9ocA+rotxIPW9NP9439vA==", - "optional": true - }, - "@oxlint/win32-arm64": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@oxlint/win32-arm64/-/win32-arm64-1.7.0.tgz", - "integrity": "sha512-fpFpkHwbAu0NcR5bc1WapCPcM9qSYi5lCRVOp1WwDoFLKI2b9/UWB8OEg8UHWV5dnBu7HZAWH/SEslYGkZNsbQ==", - "optional": true - }, - "@oxlint/win32-x64": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@oxlint/win32-x64/-/win32-x64-1.7.0.tgz", - "integrity": "sha512-0EPWBWOiD3wZHgeWDlTUaiFzhzIonXykxYUC+NRerPQFkO/G+bd9uLMJddHDKqfP/7g8s3E5V6KvBvvFpb7U6g==", - "optional": true - }, "@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -12048,9 +12947,9 @@ "optional": true }, "@pkgr/core": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz", - "integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==" + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==" }, "@pm2/agent": { "version": "2.1.1", @@ -12235,83 +13134,108 @@ "@sinonjs/commons": "^3.0.1" } }, - "@swc/core": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.0.tgz", - "integrity": "sha512-7Fh16ZH/Rj3Di720if+sw9BictD4N5kbTpsyDC+URXhvsZ7qRt1lH7PaeIQYyJJQHwFhoKpwwGxfGU9SHgPLdw==", + "@stylistic/eslint-plugin": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.2.2.tgz", + "integrity": "sha512-bE2DUjruqXlHYP3Q2Gpqiuj2bHq7/88FnuaS0FjeGGLCy+X6a07bGVuwtiOYnPSLHR6jmx5Bwdv+j7l8H+G97A==", "requires": { - "@swc/core-darwin-arm64": "1.13.0", - "@swc/core-darwin-x64": "1.13.0", - "@swc/core-linux-arm-gnueabihf": "1.13.0", - "@swc/core-linux-arm64-gnu": "1.13.0", - "@swc/core-linux-arm64-musl": "1.13.0", - "@swc/core-linux-x64-gnu": "1.13.0", - "@swc/core-linux-x64-musl": "1.13.0", - "@swc/core-win32-arm64-msvc": "1.13.0", - "@swc/core-win32-ia32-msvc": "1.13.0", - "@swc/core-win32-x64-msvc": "1.13.0", + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/types": "^8.37.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "estraverse": "^5.3.0", + "picomatch": "^4.0.3" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==" + }, + "picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==" + } + } + }, + "@swc/core": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.2.tgz", + "integrity": "sha512-YWqn+0IKXDhqVLKoac4v2tV6hJqB/wOh8/Br8zjqeqBkKa77Qb0Kw2i7LOFzjFNZbZaPH6AlMGlBwNrxaauaAg==", + "requires": { + "@swc/core-darwin-arm64": "1.13.2", + "@swc/core-darwin-x64": "1.13.2", + "@swc/core-linux-arm-gnueabihf": "1.13.2", + "@swc/core-linux-arm64-gnu": "1.13.2", + "@swc/core-linux-arm64-musl": "1.13.2", + "@swc/core-linux-x64-gnu": "1.13.2", + "@swc/core-linux-x64-musl": "1.13.2", + "@swc/core-win32-arm64-msvc": "1.13.2", + "@swc/core-win32-ia32-msvc": "1.13.2", + "@swc/core-win32-x64-msvc": "1.13.2", "@swc/counter": "^0.1.3", "@swc/types": "^0.1.23" } }, "@swc/core-darwin-arm64": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.13.0.tgz", - "integrity": "sha512-SkmR9u7MHDu2X8hf7SjZTmsAfQTmel0mi+TJ7AGtufLwGySv6pwQfJ/CIJpcPxYENVqDJAFnDrHaKV8mgA6kxQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.13.2.tgz", + "integrity": "sha512-44p7ivuLSGFJ15Vly4ivLJjg3ARo4879LtEBAabcHhSZygpmkP8eyjyWxrH3OxkY1eRZSIJe8yRZPFw4kPXFPw==", "optional": true }, "@swc/core-darwin-x64": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.13.0.tgz", - "integrity": "sha512-15/SyDjXRtFJ09fYHBXUXrj4tpiSpCkjgsF1z3/sSpHH1POWpQUQzxmFyomPQVZ/SsDqP18WGH09Vph4Qriuiw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.13.2.tgz", + "integrity": "sha512-Lb9EZi7X2XDAVmuUlBm2UvVAgSCbD3qKqDCxSI4jEOddzVOpNCnyZ/xEampdngUIyDDhhJLYU9duC+Mcsv5Y+A==", "optional": true }, "@swc/core-linux-arm-gnueabihf": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.13.0.tgz", - "integrity": "sha512-AHauVHZQEJI/dCZQg6VYNNQ6HROz8dSOnCSheXzzBw1DGWo77BlcxRP0fF0jaAXM9WNqtCUOY1HiJ9ohkAE61Q==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.13.2.tgz", + "integrity": "sha512-9TDe/92ee1x57x+0OqL1huG4BeljVx0nWW4QOOxp8CCK67Rpc/HHl2wciJ0Kl9Dxf2NvpNtkPvqj9+BUmM9WVA==", "optional": true }, "@swc/core-linux-arm64-gnu": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.13.0.tgz", - "integrity": "sha512-qyZmBZF7asF6954/x7yn6R7Bzd45KRG05rK2atIF9J3MTa8az7vubP1Q3BWmmss1j8699DELpbuoJucGuhsNXw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.13.2.tgz", + "integrity": "sha512-KJUSl56DBk7AWMAIEcU83zl5mg3vlQYhLELhjwRFkGFMvghQvdqQ3zFOYa4TexKA7noBZa3C8fb24rI5sw9Exg==", "optional": true }, "@swc/core-linux-arm64-musl": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.13.0.tgz", - "integrity": "sha512-whskQCOUlLQT7MjnronpHmyHegBka5ig9JkQvecbqhWzRfdwN+c2xTJs3kQsWy2Vc2f1hcL3D8hGIwY5TwPxMQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.13.2.tgz", + "integrity": "sha512-teU27iG1oyWpNh9CzcGQ48ClDRt/RCem7mYO7ehd2FY102UeTws2+OzLESS1TS1tEZipq/5xwx3FzbVgiolCiQ==", "optional": true }, "@swc/core-linux-x64-gnu": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.13.0.tgz", - "integrity": "sha512-51n4P4nv6rblXyH3zCEktvmR9uSAZ7+zbfeby0sxbj8LS/IKuVd7iCwD5dwMj4CxG9Fs+HgjN73dLQF/OerHhg==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.13.2.tgz", + "integrity": "sha512-dRPsyPyqpLD0HMRCRpYALIh4kdOir8pPg4AhNQZLehKowigRd30RcLXGNVZcc31Ua8CiPI4QSgjOIxK+EQe4LQ==", "optional": true }, "@swc/core-linux-x64-musl": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.13.0.tgz", - "integrity": "sha512-VMqelgvnXs27eQyhDf1S2O2MxSdchIH7c1tkxODRtu9eotcAeniNNgqqLjZ5ML0MGeRk/WpbsAY/GWi7eSpiHw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.13.2.tgz", + "integrity": "sha512-CCxETW+KkYEQDqz1SYC15YIWYheqFC+PJVOW76Maa/8yu8Biw+HTAcblKf2isrlUtK8RvrQN94v3UXkC2NzCEw==", "optional": true }, "@swc/core-win32-arm64-msvc": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.13.0.tgz", - "integrity": "sha512-NLJmseWJngWeENgat+O/WB4ptNxtx2X4OfPnSG5a/A4sxcn2E4jq91OPvbeUQwDkH+ZQWKXmbXFzt7Nn661QYA==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.13.2.tgz", + "integrity": "sha512-Wv/QTA6PjyRLlmKcN6AmSI4jwSMRl0VTLGs57PHTqYRwwfwd7y4s2fIPJVBNbAlXd795dOEP6d/bGSQSyhOX3A==", "optional": true }, "@swc/core-win32-ia32-msvc": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.13.0.tgz", - "integrity": "sha512-UBfwrp0xW37KQGTA08mwrCLIm1ZKy6pXK8IVwou7BvhMgrItRNweTGyUrCnvDLUfyYFuJCmzcEaJ3NudtctD6g==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.13.2.tgz", + "integrity": "sha512-PuCdtNynEkUNbUXX/wsyUC+t4mamIU5y00lT5vJcAvco3/r16Iaxl5UCzhXYaWZSNVZMzPp9qN8NlSL8M5pPxw==", "optional": true }, "@swc/core-win32-x64-msvc": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.13.0.tgz", - "integrity": "sha512-BAB1P7Z/y2EENsfsPytPnjIyBVRZN2WULY+s3ozW4QkGmYHde6XXG28n0ABTHhcIOmmR2VzM+uaW1x48laSimw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.13.2.tgz", + "integrity": "sha512-qlmMkFZJus8cYuBURx1a3YAG2G7IW44i+FEYV5/32ylKkzGNAr9tDJSA53XNnNXkAB5EXSPsOz7bn5C3JlEtdQ==", "optional": true }, "@swc/counter": { @@ -12467,15 +13391,10 @@ "resolved": "https://registry.npmjs.org/@types/langs/-/langs-2.0.5.tgz", "integrity": "sha512-DIUKT4mkbTBxSrX6lmnQR888ObeFVVo1uNEqBH5/ddQHpnG4CA24DibpK7aO8QAcJEZUTcIx0F96TWuzVT9Z4g==" }, - "@types/lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==" - }, "@types/node": { - "version": "24.0.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.14.tgz", - "integrity": "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw==", + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz", + "integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==", "requires": { "undici-types": "~7.8.0" } @@ -12522,15 +13441,15 @@ "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" }, "@typescript-eslint/eslint-plugin": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.37.0.tgz", - "integrity": "sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.38.0.tgz", + "integrity": "sha512-CPoznzpuAnIOl4nhj4tRr4gIPj5AfKgkiJmGQDaq+fQnRJTYlcBjbX3wbciGmpoPf8DREufuPRe1tNMZnGdanA==", "requires": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.37.0", - "@typescript-eslint/type-utils": "8.37.0", - "@typescript-eslint/utils": "8.37.0", - "@typescript-eslint/visitor-keys": "8.37.0", + "@typescript-eslint/scope-manager": "8.38.0", + "@typescript-eslint/type-utils": "8.38.0", + "@typescript-eslint/utils": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -12545,68 +13464,68 @@ } }, "@typescript-eslint/parser": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.37.0.tgz", - "integrity": "sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.38.0.tgz", + "integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==", "requires": { - "@typescript-eslint/scope-manager": "8.37.0", - "@typescript-eslint/types": "8.37.0", - "@typescript-eslint/typescript-estree": "8.37.0", - "@typescript-eslint/visitor-keys": "8.37.0", + "@typescript-eslint/scope-manager": "8.38.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/typescript-estree": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0", "debug": "^4.3.4" } }, "@typescript-eslint/project-service": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.37.0.tgz", - "integrity": "sha512-BIUXYsbkl5A1aJDdYJCBAo8rCEbAvdquQ8AnLb6z5Lp1u3x5PNgSSx9A/zqYc++Xnr/0DVpls8iQ2cJs/izTXA==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.38.0.tgz", + "integrity": "sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==", "requires": { - "@typescript-eslint/tsconfig-utils": "^8.37.0", - "@typescript-eslint/types": "^8.37.0", + "@typescript-eslint/tsconfig-utils": "^8.38.0", + "@typescript-eslint/types": "^8.38.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.37.0.tgz", - "integrity": "sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.38.0.tgz", + "integrity": "sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==", "requires": { - "@typescript-eslint/types": "8.37.0", - "@typescript-eslint/visitor-keys": "8.37.0" + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0" } }, "@typescript-eslint/tsconfig-utils": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.37.0.tgz", - "integrity": "sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz", + "integrity": "sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==", "requires": {} }, "@typescript-eslint/type-utils": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.37.0.tgz", - "integrity": "sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.38.0.tgz", + "integrity": "sha512-c7jAvGEZVf0ao2z+nnz8BUaHZD09Agbh+DY7qvBQqLiz8uJzRgVPj5YvOh8I8uEiH8oIUGIfHzMwUcGVco/SJg==", "requires": { - "@typescript-eslint/types": "8.37.0", - "@typescript-eslint/typescript-estree": "8.37.0", - "@typescript-eslint/utils": "8.37.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/typescript-estree": "8.38.0", + "@typescript-eslint/utils": "8.38.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" } }, "@typescript-eslint/types": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.37.0.tgz", - "integrity": "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ==" + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.38.0.tgz", + "integrity": "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==" }, "@typescript-eslint/typescript-estree": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.37.0.tgz", - "integrity": "sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz", + "integrity": "sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==", "requires": { - "@typescript-eslint/project-service": "8.37.0", - "@typescript-eslint/tsconfig-utils": "8.37.0", - "@typescript-eslint/types": "8.37.0", - "@typescript-eslint/visitor-keys": "8.37.0", + "@typescript-eslint/project-service": "8.38.0", + "@typescript-eslint/tsconfig-utils": "8.38.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -12634,22 +13553,22 @@ } }, "@typescript-eslint/utils": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.37.0.tgz", - "integrity": "sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.38.0.tgz", + "integrity": "sha512-hHcMA86Hgt+ijJlrD8fX0j1j8w4C92zue/8LOPAFioIno+W0+L7KqE8QZKCcPGc/92Vs9x36w/4MPTJhqXdyvg==", "requires": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.37.0", - "@typescript-eslint/types": "8.37.0", - "@typescript-eslint/typescript-estree": "8.37.0" + "@typescript-eslint/scope-manager": "8.38.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/typescript-estree": "8.38.0" } }, "@typescript-eslint/visitor-keys": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.37.0.tgz", - "integrity": "sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.38.0.tgz", + "integrity": "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==", "requires": { - "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/types": "8.38.0", "eslint-visitor-keys": "^4.2.1" }, "dependencies": { @@ -12929,12 +13848,12 @@ } }, "axios": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", - "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", + "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", "requires": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, @@ -12957,11 +13876,11 @@ } }, "babel-jest": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.0.4.tgz", - "integrity": "sha512-UjG2j7sAOqsp2Xua1mS/e+ekddkSu3wpf4nZUSvXNHuVWdaOUXQ77+uyjJLDE9i0atm5x4kds8K9yb5lRsRtcA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.0.5.tgz", + "integrity": "sha512-mRijnKimhGDMsizTvBTWotwNpzrkHr+VvZUQBof2AufXKB8NXrL1W69TG20EvOz7aevx6FTJIaBuBkYxS8zolg==", "requires": { - "@jest/transform": "30.0.4", + "@jest/transform": "30.0.5", "@types/babel__core": "^7.20.5", "babel-plugin-istanbul": "^7.0.0", "babel-preset-jest": "30.0.1", @@ -13004,9 +13923,9 @@ } }, "babel-preset-current-node-syntax": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", - "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.1.tgz", + "integrity": "sha512-23fWKohMTvS5s0wwJKycOe0dBdCwQ6+iiLaNR9zy8P13mtFRFM9qLLX6HJX5DL2pi/FNDf3fCQHM4FIMoHH/7w==", "requires": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", @@ -13279,20 +14198,20 @@ "integrity": "sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==" }, "cheerio": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.0.tgz", - "integrity": "sha512-+0hMx9eYhJvWbgpKV9hN7jg0JcwydpopZE4hgi+KvQtByZXPp04NiCWU0LzcAbP63abZckIHkTQaXVF52mX3xQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.2.tgz", + "integrity": "sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==", "requires": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", - "encoding-sniffer": "^0.2.0", + "encoding-sniffer": "^0.2.1", "htmlparser2": "^10.0.0", "parse5": "^7.3.0", "parse5-htmlparser2-tree-adapter": "^7.1.0", "parse5-parser-stream": "^7.1.2", - "undici": "^7.10.0", + "undici": "^7.12.0", "whatwg-mimetype": "^4.0.0" } }, @@ -13554,11 +14473,12 @@ "integrity": "sha512-/f6gpQuxDaqXu+1kwQYSckUglPaOrHdbIlBAu0YuW8/Cdb45XwXYNUBXg3r/9Mo6n540Kn/smKcZWko5x99KrQ==" }, "cross-env": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", - "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.0.0.tgz", + "integrity": "sha512-aU8qlEK/nHYtVuN4p7UQgAwVljzMg8hB4YK5ThRqD2l/ziSnryncPNn7bMLt5cFYsKVKBh8HqLqyCoTupEUu7Q==", "requires": { - "cross-spawn": "^7.0.1" + "@epic-web/invariant": "^1.0.0", + "cross-spawn": "^7.0.6" } }, "cross-spawn": { @@ -13739,9 +14659,9 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, "electron-to-chromium": { - "version": "1.5.185", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.185.tgz", - "integrity": "sha512-dYOZfUk57hSMPePoIQ1fZWl1Fkj+OshhEVuPacNKWzC1efe56OsHY3l/jCfiAgIICOU3VgOIdoq7ahg7r7n6MQ==" + "version": "1.5.192", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.192.tgz", + "integrity": "sha512-rP8Ez0w7UNw/9j5eSXCe10o1g/8B1P5SM90PCCMVkIRQn2R0LEHWz4Eh9RnxkniuDe1W0cTSOB3MLlkTGDcuCg==" }, "emittery": { "version": "0.13.1", @@ -13902,9 +14822,9 @@ } }, "eslint": { - "version": "9.31.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz", - "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==", + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.32.0.tgz", + "integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==", "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -13912,8 +14832,8 @@ "@eslint/config-helpers": "^0.3.0", "@eslint/core": "^0.15.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.31.0", - "@eslint/plugin-kit": "^0.3.1", + "@eslint/js": "9.32.0", + "@eslint/plugin-kit": "^0.3.4", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -14084,16 +15004,16 @@ "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==" }, "expect": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.0.4.tgz", - "integrity": "sha512-dDLGjnP2cKbEppxVICxI/Uf4YemmGMPNy0QytCbfafbpYk9AFQsxb8Uyrxii0RPK7FWgLGlSem+07WirwS3cFQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.0.5.tgz", + "integrity": "sha512-P0te2pt+hHI5qLJkIR+iMvS+lYUZml8rKKsohVHAGY+uClp9XVbdyYNJOIjSRpHVp8s8YqxJCiHUkSYZGr8rtQ==", "requires": { - "@jest/expect-utils": "30.0.4", + "@jest/expect-utils": "30.0.5", "@jest/get-type": "30.0.1", - "jest-matcher-utils": "30.0.4", - "jest-message-util": "30.0.2", - "jest-mock": "30.0.2", - "jest-util": "30.0.2" + "jest-matcher-utils": "30.0.5", + "jest-message-util": "30.0.5", + "jest-mock": "30.0.5", + "jest-util": "30.0.5" } }, "external-editor": { @@ -14599,16 +15519,16 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "inquirer": { - "version": "12.7.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-12.7.0.tgz", - "integrity": "sha512-KKFRc++IONSyE2UYw9CJ1V0IWx5yQKomwB+pp3cWomWs+v2+ZsG11G2OVfAjFS6WWCppKw+RfKmpqGfSzD5QBQ==", + "version": "12.9.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-12.9.0.tgz", + "integrity": "sha512-LlFVmvWVCun7uEgPB3vups9NzBrjJn48kRNtFGw3xU1H5UXExTEz/oF1JGLaB0fvlkUB+W6JfgLcSEaSdH7RPA==", "requires": { - "@inquirer/core": "^10.1.14", - "@inquirer/prompts": "^7.6.0", - "@inquirer/type": "^3.0.7", + "@inquirer/core": "^10.1.15", + "@inquirer/prompts": "^7.8.0", + "@inquirer/type": "^3.0.8", "ansi-escapes": "^4.3.2", "mute-stream": "^2.0.0", - "run-async": "^4.0.4", + "run-async": "^4.0.5", "rxjs": "^7.8.2" }, "dependencies": { @@ -14811,53 +15731,108 @@ } }, "jest": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.0.4.tgz", - "integrity": "sha512-9QE0RS4WwTj/TtTC4h/eFVmFAhGNVerSB9XpJh8sqaXlP73ILcPcZ7JWjjEtJJe2m8QyBLKKfPQuK+3F+Xij/g==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.0.5.tgz", + "integrity": "sha512-y2mfcJywuTUkvLm2Lp1/pFX8kTgMO5yyQGq/Sk/n2mN7XWYp4JsCZ/QXW34M8YScgk8bPZlREH04f6blPnoHnQ==", "requires": { - "@jest/core": "30.0.4", - "@jest/types": "30.0.1", + "@jest/core": "30.0.5", + "@jest/types": "30.0.5", "import-local": "^3.2.0", - "jest-cli": "30.0.4" + "jest-cli": "30.0.5" + }, + "dependencies": { + "@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "requires": { + "@sinclair/typebox": "^0.34.0" + } + }, + "@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "requires": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "jest-changed-files": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.0.2.tgz", - "integrity": "sha512-Ius/iRST9FKfJI+I+kpiDh8JuUlAISnRszF9ixZDIqJF17FckH5sOzKC8a0wd0+D+8em5ADRHA5V5MnfeDk2WA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.0.5.tgz", + "integrity": "sha512-bGl2Ntdx0eAwXuGpdLdVYVr5YQHnSZlQ0y9HVDu565lCUAe9sj6JOtBbMmBBikGIegne9piDDIOeiLVoqTkz4A==", "requires": { "execa": "^5.1.1", - "jest-util": "30.0.2", + "jest-util": "30.0.5", "p-limit": "^3.1.0" } }, "jest-circus": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.0.4.tgz", - "integrity": "sha512-o6UNVfbXbmzjYgmVPtSQrr5xFZCtkDZGdTlptYvGFSN80RuOOlTe73djvMrs+QAuSERZWcHBNIOMH+OEqvjWuw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.0.5.tgz", + "integrity": "sha512-h/sjXEs4GS+NFFfqBDYT7y5Msfxh04EwWLhQi0F8kuWpe+J/7tICSlswU8qvBqumR3kFgHbfu7vU6qruWWBPug==", "requires": { - "@jest/environment": "30.0.4", - "@jest/expect": "30.0.4", - "@jest/test-result": "30.0.4", - "@jest/types": "30.0.1", + "@jest/environment": "30.0.5", + "@jest/expect": "30.0.5", + "@jest/test-result": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", "chalk": "^4.1.2", "co": "^4.6.0", "dedent": "^1.6.0", "is-generator-fn": "^2.1.0", - "jest-each": "30.0.2", - "jest-matcher-utils": "30.0.4", - "jest-message-util": "30.0.2", - "jest-runtime": "30.0.4", - "jest-snapshot": "30.0.4", - "jest-util": "30.0.2", + "jest-each": "30.0.5", + "jest-matcher-utils": "30.0.5", + "jest-message-util": "30.0.5", + "jest-runtime": "30.0.5", + "jest-snapshot": "30.0.5", + "jest-util": "30.0.5", "p-limit": "^3.1.0", - "pretty-format": "30.0.2", + "pretty-format": "30.0.5", "pure-rand": "^7.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" }, "dependencies": { + "@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "requires": { + "@sinclair/typebox": "^0.34.0" + } + }, + "@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "requires": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -14870,22 +15845,44 @@ } }, "jest-cli": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.0.4.tgz", - "integrity": "sha512-3dOrP3zqCWBkjoVG1zjYJpD9143N9GUCbwaF2pFF5brnIgRLHmKcCIw+83BvF1LxggfMWBA0gxkn6RuQVuRhIQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.0.5.tgz", + "integrity": "sha512-Sa45PGMkBZzF94HMrlX4kUyPOwUpdZasaliKN3mifvDmkhLYqLLg8HQTzn6gq7vJGahFYMQjXgyJWfYImKZzOw==", "requires": { - "@jest/core": "30.0.4", - "@jest/test-result": "30.0.4", - "@jest/types": "30.0.1", + "@jest/core": "30.0.5", + "@jest/test-result": "30.0.5", + "@jest/types": "30.0.5", "chalk": "^4.1.2", "exit-x": "^0.2.2", "import-local": "^3.2.0", - "jest-config": "30.0.4", - "jest-util": "30.0.2", - "jest-validate": "30.0.2", + "jest-config": "30.0.5", + "jest-util": "30.0.5", + "jest-validate": "30.0.5", "yargs": "^17.7.2" }, "dependencies": { + "@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "requires": { + "@sinclair/typebox": "^0.34.0" + } + }, + "@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "requires": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -14898,36 +15895,58 @@ } }, "jest-config": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.0.4.tgz", - "integrity": "sha512-3dzbO6sh34thAGEjJIW0fgT0GA0EVlkski6ZzMcbW6dzhenylXAE/Mj2MI4HonroWbkKc6wU6bLVQ8dvBSZ9lA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.0.5.tgz", + "integrity": "sha512-aIVh+JNOOpzUgzUnPn5FLtyVnqc3TQHVMupYtyeURSb//iLColiMIR8TxCIDKyx9ZgjKnXGucuW68hCxgbrwmA==", "requires": { "@babel/core": "^7.27.4", "@jest/get-type": "30.0.1", "@jest/pattern": "30.0.1", - "@jest/test-sequencer": "30.0.4", - "@jest/types": "30.0.1", - "babel-jest": "30.0.4", + "@jest/test-sequencer": "30.0.5", + "@jest/types": "30.0.5", + "babel-jest": "30.0.5", "chalk": "^4.1.2", "ci-info": "^4.2.0", "deepmerge": "^4.3.1", "glob": "^10.3.10", "graceful-fs": "^4.2.11", - "jest-circus": "30.0.4", + "jest-circus": "30.0.5", "jest-docblock": "30.0.1", - "jest-environment-node": "30.0.4", + "jest-environment-node": "30.0.5", "jest-regex-util": "30.0.1", - "jest-resolve": "30.0.2", - "jest-runner": "30.0.4", - "jest-util": "30.0.2", - "jest-validate": "30.0.2", + "jest-resolve": "30.0.5", + "jest-runner": "30.0.5", + "jest-util": "30.0.5", + "jest-validate": "30.0.5", "micromatch": "^4.0.8", "parse-json": "^5.2.0", - "pretty-format": "30.0.2", + "pretty-format": "30.0.5", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, "dependencies": { + "@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "requires": { + "@sinclair/typebox": "^0.34.0" + } + }, + "@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "requires": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + } + }, "brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -14992,14 +16011,14 @@ } }, "jest-diff": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.0.4.tgz", - "integrity": "sha512-TSjceIf6797jyd+R64NXqicttROD+Qf98fex7CowmlSn7f8+En0da1Dglwr1AXxDtVizoxXYZBlUQwNhoOXkNw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.0.5.tgz", + "integrity": "sha512-1UIqE9PoEKaHcIKvq2vbibrCog4Y8G0zmOxgQUVEiTqwR5hJVMCoDsN1vFvI5JvwD37hjueZ1C4l2FyGnfpE0A==", "requires": { "@jest/diff-sequences": "30.0.1", "@jest/get-type": "30.0.1", "chalk": "^4.1.2", - "pretty-format": "30.0.2" + "pretty-format": "30.0.5" }, "dependencies": { "chalk": { @@ -15022,17 +16041,39 @@ } }, "jest-each": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.0.2.tgz", - "integrity": "sha512-ZFRsTpe5FUWFQ9cWTMguCaiA6kkW5whccPy9JjD1ezxh+mJeqmz8naL8Fl/oSbNJv3rgB0x87WBIkA5CObIUZQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.0.5.tgz", + "integrity": "sha512-dKjRsx1uZ96TVyejD3/aAWcNKy6ajMaN531CwWIsrazIqIoXI9TnnpPlkrEYku/8rkS3dh2rbH+kMOyiEIv0xQ==", "requires": { "@jest/get-type": "30.0.1", - "@jest/types": "30.0.1", + "@jest/types": "30.0.5", "chalk": "^4.1.2", - "jest-util": "30.0.2", - "pretty-format": "30.0.2" + "jest-util": "30.0.5", + "pretty-format": "30.0.5" }, "dependencies": { + "@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "requires": { + "@sinclair/typebox": "^0.34.0" + } + }, + "@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "requires": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -15045,55 +16086,121 @@ } }, "jest-environment-node": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.0.4.tgz", - "integrity": "sha512-p+rLEzC2eThXqiNh9GHHTC0OW5Ca4ZfcURp7scPjYBcmgpR9HG6750716GuUipYf2AcThU3k20B31USuiaaIEg==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.0.5.tgz", + "integrity": "sha512-ppYizXdLMSvciGsRsMEnv/5EFpvOdXBaXRBzFUDPWrsfmog4kYrOGWXarLllz6AXan6ZAA/kYokgDWuos1IKDA==", "requires": { - "@jest/environment": "30.0.4", - "@jest/fake-timers": "30.0.4", - "@jest/types": "30.0.1", + "@jest/environment": "30.0.5", + "@jest/fake-timers": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", - "jest-mock": "30.0.2", - "jest-util": "30.0.2", - "jest-validate": "30.0.2" + "jest-mock": "30.0.5", + "jest-util": "30.0.5", + "jest-validate": "30.0.5" + }, + "dependencies": { + "@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "requires": { + "@sinclair/typebox": "^0.34.0" + } + }, + "@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "requires": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "jest-haste-map": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.0.2.tgz", - "integrity": "sha512-telJBKpNLeCb4MaX+I5k496556Y2FiKR/QLZc0+MGBYl4k3OO0472drlV2LUe7c1Glng5HuAu+5GLYp//GpdOQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.0.5.tgz", + "integrity": "sha512-dkmlWNlsTSR0nH3nRfW5BKbqHefLZv0/6LCccG0xFCTWcJu8TuEwG+5Cm75iBfjVoockmO6J35o5gxtFSn5xeg==", "requires": { - "@jest/types": "30.0.1", + "@jest/types": "30.0.5", "@types/node": "*", "anymatch": "^3.1.3", "fb-watchman": "^2.0.2", "fsevents": "^2.3.3", "graceful-fs": "^4.2.11", "jest-regex-util": "30.0.1", - "jest-util": "30.0.2", - "jest-worker": "30.0.2", + "jest-util": "30.0.5", + "jest-worker": "30.0.5", "micromatch": "^4.0.8", "walker": "^1.0.8" + }, + "dependencies": { + "@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "requires": { + "@sinclair/typebox": "^0.34.0" + } + }, + "@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "requires": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "jest-leak-detector": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.0.2.tgz", - "integrity": "sha512-U66sRrAYdALq+2qtKffBLDWsQ/XoNNs2Lcr83sc9lvE/hEpNafJlq2lXCPUBMNqamMECNxSIekLfe69qg4KMIQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.0.5.tgz", + "integrity": "sha512-3Uxr5uP8jmHMcsOtYMRB/zf1gXN3yUIc+iPorhNETG54gErFIiUhLvyY/OggYpSMOEYqsmRxmuU4ZOoX5jpRFg==", "requires": { "@jest/get-type": "30.0.1", - "pretty-format": "30.0.2" + "pretty-format": "30.0.5" } }, "jest-matcher-utils": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.0.4.tgz", - "integrity": "sha512-ubCewJ54YzeAZ2JeHHGVoU+eDIpQFsfPQs0xURPWoNiO42LGJ+QGgfSf+hFIRplkZDkhH5MOvuxHKXRTUU3dUQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.0.5.tgz", + "integrity": "sha512-uQgGWt7GOrRLP1P7IwNWwK1WAQbq+m//ZY0yXygyfWp0rJlksMSLQAA4wYQC3b6wl3zfnchyTx+k3HZ5aPtCbQ==", "requires": { "@jest/get-type": "30.0.1", "chalk": "^4.1.2", - "jest-diff": "30.0.4", - "pretty-format": "30.0.2" + "jest-diff": "30.0.5", + "pretty-format": "30.0.5" }, "dependencies": { "chalk": { @@ -15108,21 +16215,43 @@ } }, "jest-message-util": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.0.2.tgz", - "integrity": "sha512-vXywcxmr0SsKXF/bAD7t7nMamRvPuJkras00gqYeB1V0WllxZrbZ0paRr3XqpFU2sYYjD0qAaG2fRyn/CGZ0aw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.0.5.tgz", + "integrity": "sha512-NAiDOhsK3V7RU0Aa/HnrQo+E4JlbarbmI3q6Pi4KcxicdtjV82gcIUrejOtczChtVQR4kddu1E1EJlW6EN9IyA==", "requires": { "@babel/code-frame": "^7.27.1", - "@jest/types": "30.0.1", + "@jest/types": "30.0.5", "@types/stack-utils": "^2.0.3", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "micromatch": "^4.0.8", - "pretty-format": "30.0.2", + "pretty-format": "30.0.5", "slash": "^3.0.0", "stack-utils": "^2.0.6" }, "dependencies": { + "@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "requires": { + "@sinclair/typebox": "^0.34.0" + } + }, + "@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "requires": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -15135,13 +16264,46 @@ } }, "jest-mock": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.2.tgz", - "integrity": "sha512-PnZOHmqup/9cT/y+pXIVbbi8ID6U1XHRmbvR7MvUy4SLqhCbwpkmXhLbsWbGewHrV5x/1bF7YDjs+x24/QSvFA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.5.tgz", + "integrity": "sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ==", "requires": { - "@jest/types": "30.0.1", + "@jest/types": "30.0.5", "@types/node": "*", - "jest-util": "30.0.2" + "jest-util": "30.0.5" + }, + "dependencies": { + "@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "requires": { + "@sinclair/typebox": "^0.34.0" + } + }, + "@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "requires": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "jest-offline": { @@ -15164,16 +16326,16 @@ "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==" }, "jest-resolve": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.0.2.tgz", - "integrity": "sha512-q/XT0XQvRemykZsvRopbG6FQUT6/ra+XV6rPijyjT6D0msOyCvR2A5PlWZLd+fH0U8XWKZfDiAgrUNDNX2BkCw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.0.5.tgz", + "integrity": "sha512-d+DjBQ1tIhdz91B79mywH5yYu76bZuE96sSbxj8MkjWVx5WNdt1deEFRONVL4UkKLSrAbMkdhb24XN691yDRHg==", "requires": { "chalk": "^4.1.2", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.0.2", + "jest-haste-map": "30.0.5", "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.0.2", - "jest-validate": "30.0.2", + "jest-util": "30.0.5", + "jest-validate": "30.0.5", "slash": "^3.0.0", "unrs-resolver": "^1.7.11" }, @@ -15190,43 +16352,65 @@ } }, "jest-resolve-dependencies": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.0.4.tgz", - "integrity": "sha512-EQBYow19B/hKr4gUTn+l8Z+YLlP2X0IoPyp0UydOtrcPbIOYzJ8LKdFd+yrbwztPQvmlBFUwGPPEzHH1bAvFAw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.0.5.tgz", + "integrity": "sha512-/xMvBR4MpwkrHW4ikZIWRttBBRZgWK4d6xt3xW1iRDSKt4tXzYkMkyPfBnSCgv96cpkrctfXs6gexeqMYqdEpw==", "requires": { "jest-regex-util": "30.0.1", - "jest-snapshot": "30.0.4" + "jest-snapshot": "30.0.5" } }, "jest-runner": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.0.4.tgz", - "integrity": "sha512-mxY0vTAEsowJwvFJo5pVivbCpuu6dgdXRmt3v3MXjBxFly7/lTk3Td0PaMyGOeNQUFmSuGEsGYqhbn7PA9OekQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.0.5.tgz", + "integrity": "sha512-JcCOucZmgp+YuGgLAXHNy7ualBx4wYSgJVWrYMRBnb79j9PD0Jxh0EHvR5Cx/r0Ce+ZBC4hCdz2AzFFLl9hCiw==", "requires": { - "@jest/console": "30.0.4", - "@jest/environment": "30.0.4", - "@jest/test-result": "30.0.4", - "@jest/transform": "30.0.4", - "@jest/types": "30.0.1", + "@jest/console": "30.0.5", + "@jest/environment": "30.0.5", + "@jest/test-result": "30.0.5", + "@jest/transform": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", "chalk": "^4.1.2", "emittery": "^0.13.1", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", "jest-docblock": "30.0.1", - "jest-environment-node": "30.0.4", - "jest-haste-map": "30.0.2", - "jest-leak-detector": "30.0.2", - "jest-message-util": "30.0.2", - "jest-resolve": "30.0.2", - "jest-runtime": "30.0.4", - "jest-util": "30.0.2", - "jest-watcher": "30.0.4", - "jest-worker": "30.0.2", + "jest-environment-node": "30.0.5", + "jest-haste-map": "30.0.5", + "jest-leak-detector": "30.0.5", + "jest-message-util": "30.0.5", + "jest-resolve": "30.0.5", + "jest-runtime": "30.0.5", + "jest-util": "30.0.5", + "jest-watcher": "30.0.5", + "jest-worker": "30.0.5", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, "dependencies": { + "@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "requires": { + "@sinclair/typebox": "^0.34.0" + } + }, + "@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "requires": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -15239,34 +16423,56 @@ } }, "jest-runtime": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.0.4.tgz", - "integrity": "sha512-tUQrZ8+IzoZYIHoPDQEB4jZoPyzBjLjq7sk0KVyd5UPRjRDOsN7o6UlvaGF8ddpGsjznl9PW+KRgWqCNO+Hn7w==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.0.5.tgz", + "integrity": "sha512-7oySNDkqpe4xpX5PPiJTe5vEa+Ak/NnNz2bGYZrA1ftG3RL3EFlHaUkA1Cjx+R8IhK0Vg43RML5mJedGTPNz3A==", "requires": { - "@jest/environment": "30.0.4", - "@jest/fake-timers": "30.0.4", - "@jest/globals": "30.0.4", + "@jest/environment": "30.0.5", + "@jest/fake-timers": "30.0.5", + "@jest/globals": "30.0.5", "@jest/source-map": "30.0.1", - "@jest/test-result": "30.0.4", - "@jest/transform": "30.0.4", - "@jest/types": "30.0.1", + "@jest/test-result": "30.0.5", + "@jest/transform": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", "chalk": "^4.1.2", "cjs-module-lexer": "^2.1.0", "collect-v8-coverage": "^1.0.2", "glob": "^10.3.10", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.0.2", - "jest-message-util": "30.0.2", - "jest-mock": "30.0.2", + "jest-haste-map": "30.0.5", + "jest-message-util": "30.0.5", + "jest-mock": "30.0.5", "jest-regex-util": "30.0.1", - "jest-resolve": "30.0.2", - "jest-snapshot": "30.0.4", - "jest-util": "30.0.2", + "jest-resolve": "30.0.5", + "jest-snapshot": "30.0.5", + "jest-util": "30.0.5", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, "dependencies": { + "@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "requires": { + "@sinclair/typebox": "^0.34.0" + } + }, + "@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "requires": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + } + }, "brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -15331,33 +16537,55 @@ } }, "jest-snapshot": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.0.4.tgz", - "integrity": "sha512-S/8hmSkeUib8WRUq9pWEb5zMfsOjiYWDWzFzKnjX7eDyKKgimsu9hcmsUEg8a7dPAw8s/FacxsXquq71pDgPjQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.0.5.tgz", + "integrity": "sha512-T00dWU/Ek3LqTp4+DcW6PraVxjk28WY5Ua/s+3zUKSERZSNyxTqhDXCWKG5p2HAJ+crVQ3WJ2P9YVHpj1tkW+g==", "requires": { "@babel/core": "^7.27.4", "@babel/generator": "^7.27.5", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.0.4", + "@jest/expect-utils": "30.0.5", "@jest/get-type": "30.0.1", - "@jest/snapshot-utils": "30.0.4", - "@jest/transform": "30.0.4", - "@jest/types": "30.0.1", + "@jest/snapshot-utils": "30.0.5", + "@jest/transform": "30.0.5", + "@jest/types": "30.0.5", "babel-preset-current-node-syntax": "^1.1.0", "chalk": "^4.1.2", - "expect": "30.0.4", + "expect": "30.0.5", "graceful-fs": "^4.2.11", - "jest-diff": "30.0.4", - "jest-matcher-utils": "30.0.4", - "jest-message-util": "30.0.2", - "jest-util": "30.0.2", - "pretty-format": "30.0.2", + "jest-diff": "30.0.5", + "jest-matcher-utils": "30.0.5", + "jest-message-util": "30.0.5", + "jest-util": "30.0.5", + "pretty-format": "30.0.5", "semver": "^7.7.2", "synckit": "^0.11.8" }, "dependencies": { + "@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "requires": { + "@sinclair/typebox": "^0.34.0" + } + }, + "@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "requires": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -15370,11 +16598,11 @@ } }, "jest-util": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.2.tgz", - "integrity": "sha512-8IyqfKS4MqprBuUpZNlFB5l+WFehc8bfCe1HSZFHzft2mOuND8Cvi9r1musli+u6F3TqanCZ/Ik4H4pXUolZIg==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.5.tgz", + "integrity": "sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==", "requires": { - "@jest/types": "30.0.1", + "@jest/types": "30.0.5", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", @@ -15382,6 +16610,28 @@ "picomatch": "^4.0.2" }, "dependencies": { + "@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "requires": { + "@sinclair/typebox": "^0.34.0" + } + }, + "@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "requires": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -15392,25 +16642,47 @@ } }, "picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==" + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==" } } }, "jest-validate": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.0.2.tgz", - "integrity": "sha512-noOvul+SFER4RIvNAwGn6nmV2fXqBq67j+hKGHKGFCmK4ks/Iy1FSrqQNBLGKlu4ZZIRL6Kg1U72N1nxuRCrGQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.0.5.tgz", + "integrity": "sha512-ouTm6VFHaS2boyl+k4u+Qip4TSH7Uld5tyD8psQ8abGgt2uYYB8VwVfAHWHjHc0NWmGGbwO5h0sCPOGHHevefw==", "requires": { "@jest/get-type": "30.0.1", - "@jest/types": "30.0.1", + "@jest/types": "30.0.5", "camelcase": "^6.3.0", "chalk": "^4.1.2", "leven": "^3.1.0", - "pretty-format": "30.0.2" + "pretty-format": "30.0.5" }, "dependencies": { + "@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "requires": { + "@sinclair/typebox": "^0.34.0" + } + }, + "@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "requires": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + } + }, "camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -15428,20 +16700,42 @@ } }, "jest-watcher": { - "version": "30.0.4", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.0.4.tgz", - "integrity": "sha512-YESbdHDs7aQOCSSKffG8jXqOKFqw4q4YqR+wHYpR5GWEQioGvL0BfbcjvKIvPEM0XGfsfJrka7jJz3Cc3gI4VQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.0.5.tgz", + "integrity": "sha512-z9slj/0vOwBDBjN3L4z4ZYaA+pG56d6p3kTUhFRYGvXbXMWhXmb/FIxREZCD06DYUwDKKnj2T80+Pb71CQ0KEg==", "requires": { - "@jest/test-result": "30.0.4", - "@jest/types": "30.0.1", + "@jest/test-result": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "emittery": "^0.13.1", - "jest-util": "30.0.2", + "jest-util": "30.0.5", "string-length": "^4.0.2" }, "dependencies": { + "@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "requires": { + "@sinclair/typebox": "^0.34.0" + } + }, + "@jest/types": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "requires": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + } + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -15454,13 +16748,13 @@ } }, "jest-worker": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.0.2.tgz", - "integrity": "sha512-RN1eQmx7qSLFA+o9pfJKlqViwL5wt+OL3Vff/A+/cPsmuw7NPwfgl33AP+/agRmHzPOFgXviRycR9kYwlcRQXg==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.0.5.tgz", + "integrity": "sha512-ojRXsWzEP16NdUuBw/4H/zkZdHOa7MMYCk4E430l+8fELeLg/mqmMlRhjL7UNZvQrDmnovWZV4DxX03fZF48fQ==", "requires": { "@types/node": "*", "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.0.2", + "jest-util": "30.0.5", "merge-stream": "^2.0.0", "supports-color": "^8.1.1" }, @@ -15818,9 +17112,9 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, "napi-postinstall": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.0.tgz", - "integrity": "sha512-M7NqKyhODKV1gRLdkwE7pDsZP2/SC2a2vHkOYh9MCpKMbWVfyVfUw5MaH83Fv6XMjxr5jryUp3IDDL9rlxsTeA==" + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.2.tgz", + "integrity": "sha512-tWVJxJHmBWLy69PvO96TZMZDrzmw5KeiZBz3RHmiM2XZ9grBJ2WgMAFVVg25nqp3ZjTFUs2Ftw1JhscL3Teliw==" }, "natural-compare": { "version": "1.4.0", @@ -15996,21 +17290,6 @@ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==" }, - "oxlint": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.7.0.tgz", - "integrity": "sha512-krJN1fIRhs3xK1FyVyPtYIV9tkT4WDoIwI7eiMEKBuCjxqjQt5ZemQm1htPvHqNDOaWFRFt4btcwFdU8bbwgvA==", - "requires": { - "@oxlint/darwin-arm64": "1.7.0", - "@oxlint/darwin-x64": "1.7.0", - "@oxlint/linux-arm64-gnu": "1.7.0", - "@oxlint/linux-arm64-musl": "1.7.0", - "@oxlint/linux-x64-gnu": "1.7.0", - "@oxlint/linux-x64-musl": "1.7.0", - "@oxlint/win32-arm64": "1.7.0", - "@oxlint/win32-x64": "1.7.0" - } - }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -16451,21 +17730,24 @@ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" }, - "prettier": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", - "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==" - }, "pretty-format": { - "version": "30.0.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.2.tgz", - "integrity": "sha512-yC5/EBSOrTtqhCKfLHqoUIAXVRZnukHPwWBJWR7h84Q3Be1DRQZLncwcfLoPA5RPQ65qfiCMqgYwdUuQ//eVpg==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz", + "integrity": "sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==", "requires": { - "@jest/schemas": "30.0.1", + "@jest/schemas": "30.0.5", "ansi-styles": "^5.2.0", "react-is": "^18.3.1" }, "dependencies": { + "@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "requires": { + "@sinclair/typebox": "^0.34.0" + } + }, "ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", @@ -16653,13 +17935,9 @@ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==" }, "run-async": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-4.0.4.tgz", - "integrity": "sha512-2cgeRHnV11lSXBEhq7sN7a5UVjTKm9JTb9x8ApIT//16D7QL96AgnNeWSGoB4gIHc0iYw/Ha0Z+waBaCYZVNhg==", - "requires": { - "oxlint": "^1.2.0", - "prettier": "^3.5.3" - } + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-4.0.5.tgz", + "integrity": "sha512-oN9GTgxUNDBumHTTDmQ8dep6VIJbgj9S3dPP+9XylVLIK4xB9XTXtKWROd5pnhdXR9k0EgO1JRcNh0T+Ny2FsA==" }, "run-parallel": { "version": "1.2.0", @@ -17079,11 +18357,11 @@ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" }, "synckit": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.8.tgz", - "integrity": "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==", + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", + "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", "requires": { - "@pkgr/core": "^0.2.4" + "@pkgr/core": "^0.2.9" } }, "systeminformation": { @@ -17257,9 +18535,9 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==" }, "undici": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.10.0.tgz", - "integrity": "sha512-u5otvFBOBZvmdjWLVW+5DAc9Nkq8f24g0O9oY7qw2JVIF1VocIFoyz9JFkuVOS2j41AufeO0xnlweJ2RLT8nGw==" + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.12.0.tgz", + "integrity": "sha512-GrKEsc3ughskmGA9jevVlIOPMiiAHJ4OFUtaAH+NhfTUSiZ1wMPIQqQvAJUrJspFXJt3EBWgpAeoHEDVT1IBug==" }, "undici-types": { "version": "7.8.0", @@ -17354,6 +18632,11 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==" + }, "uzip-module": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/uzip-module/-/uzip-module-1.0.3.tgz", From 3699f1372dc8638aa3797aab317261bb5313e41e Mon Sep 17 00:00:00 2001 From: freearhey <7253922+freearhey@users.noreply.github.com> Date: Tue, 29 Jul 2025 05:27:54 +0300 Subject: [PATCH 26/32] Update validate.test.ts --- tests/commands/channels/validate.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/commands/channels/validate.test.ts b/tests/commands/channels/validate.test.ts index 9c4cc2a46..e3dc123ad 100644 --- a/tests/commands/channels/validate.test.ts +++ b/tests/commands/channels/validate.test.ts @@ -1,6 +1,6 @@ import { execSync } from 'child_process' -type ExecError = { +interface ExecError { status: number stdout: string } From 651850370a0d7e866ffb470f239c2185b6113e03 Mon Sep 17 00:00:00 2001 From: freearhey <7253922+freearhey@users.noreply.github.com> Date: Tue, 29 Jul 2025 05:28:04 +0300 Subject: [PATCH 27/32] Update validate.ts --- scripts/commands/channels/validate.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/commands/channels/validate.ts b/scripts/commands/channels/validate.ts index a75722700..93e6945e1 100644 --- a/scripts/commands/channels/validate.ts +++ b/scripts/commands/channels/validate.ts @@ -9,9 +9,9 @@ import { program } from 'commander' import chalk from 'chalk' import langs from 'langs' -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) -type ValidationError = { +interface ValidationError { type: 'duplicate' | 'wrong_channel_id' | 'wrong_feed_id' | 'wrong_lang' name: string lang?: string From ca219de82dae2f7ca6de7c4b485712d510c8f7f3 Mon Sep 17 00:00:00 2001 From: freearhey <7253922+freearhey@users.noreply.github.com> Date: Tue, 29 Jul 2025 05:28:59 +0300 Subject: [PATCH 28/32] Replaced LF endings with CRLF --- scripts/core/configLoader.ts | 64 +- sites/9tv.co.il/9tv.co.il.test.js | 112 +-- sites/allente.dk/allente.dk.test.js | 100 +-- sites/allente.fi/allente.fi.test.js | 102 +-- sites/allente.no/allente.no.test.js | 102 +-- sites/allente.se/allente.se.test.js | 102 +-- .../arianatelevision.com.test.js | 118 +-- sites/artonline.tv/artonline.tv.test.js | 132 ++-- sites/chada.ma/chada.ma.test.js | 84 +-- .../chaines-tv.orange.fr.test.js | 98 +-- sites/cosmotetv.gr/cosmotetv.gr.test.js | 110 +-- sites/cubmu.com/cubmu.com.test.js | 94 +-- sites/derana.lk/derana.lk.config.js | 96 +-- sites/directv.com.ar/directv.com.ar.test.js | 156 ++-- sites/dstv.com/dstv.com.config.js | 412 +++++------ sites/firstmedia.com/firstmedia.com.test.js | 76 +- .../foxsports.com.au/foxsports.com.au.test.js | 86 +-- sites/freetv.tv/freetv.tv.test.js | 102 +-- sites/frikanalen.no/frikanalen.no.test.js | 96 +-- sites/galamtv.kz/galamtv.kz.test.js | 104 +-- sites/guida.tv/guida.tv.config.js | 196 ++--- sites/guidatv.sky.it/guidatv.sky.it.test.js | 104 +-- sites/guidetnt.com/guidetnt.com.config.js | 676 +++++++++--------- sites/guidetnt.com/guidetnt.com.test.js | 170 ++--- sites/horizon.tv/horizon.tv.test.js | 492 ++++++------- sites/hoy.tv/hoy.tv.test.js | 92 +-- sites/i24news.tv/i24news.tv.test.js | 92 +-- sites/indihometv.com/indihometv.com.test.js | 114 +-- sites/ipko.tv/ipko.tv.test.js | 108 +-- sites/kan.org.il/kan.org.il.test.js | 94 +-- sites/magticom.ge/magticom.ge.test.js | 128 ++-- sites/mako.co.il/mako.co.il.test.js | 82 +-- sites/maxtvgo.mk/maxtvgo.mk.test.js | 144 ++-- sites/melita.com/melita.com.test.js | 102 +-- sites/mewatch.sg/mewatch.sg.test.js | 112 +-- sites/mi.tv/mi.tv.config.js | 286 ++++---- sites/mi.tv/mi.tv.test.js | 134 ++-- sites/moji.id/moji.id.test.js | 58 +- .../mojmaxtv.hrvatskitelekom.hr.config.js | 384 +++++----- sites/mtel.ba/mtel.ba.config.js | 220 +++--- sites/mtel.ba/mtel.ba.test.js | 116 +-- sites/mysky.com.ph/mysky.com.ph.test.js | 90 +-- sites/neo.io/neo.io.test.js | 122 ++-- sites/novacyprus.com/novacyprus.com.test.js | 98 +-- .../nowplayer.now.com.test.js | 116 +-- .../ontvtonight.com/ontvtonight.com.config.js | 356 ++++----- sites/ontvtonight.com/ontvtonight.com.test.js | 114 +-- sites/pbsguam.org/pbsguam.org.test.js | 88 +-- sites/programetv.ro/programetv.ro.config.js | 190 ++--- sites/programetv.ro/programetv.ro.test.js | 84 +-- .../programme-tv.vini.pf.test.js | 214 +++--- .../programtv.onet.pl.test.js | 152 ++-- sites/raiplay.it/raiplay.it.test.js | 100 +-- sites/reportv.com.ar/reportv.com.ar.config.js | 340 ++++----- sites/rikstv.no/rikstv.no.test.js | 116 +-- .../rtmklik.rtm.gov.my.test.js | 96 +-- sites/rtp.pt/rtp.pt.config.js | 130 ++-- sites/s.mxtv.jp/s.mxtv.jp.test.js | 98 +-- sites/shahid.mbc.net/shahid.mbc.net.test.js | 82 +-- sites/siba.com.co/siba.com.co.test.js | 108 +-- sites/sky.com/sky.com.config.js | 170 ++--- sites/sky.de/sky.de.test.js | 132 ++-- sites/stod2.is/stod2.is.test.js | 92 +-- .../streamingtvguides.com.config.js | 194 ++--- sites/taiwanplus.com/taiwanplus.com.test.js | 86 +-- sites/tapdmv.com/tapdmv.com.test.js | 98 +-- sites/tataplay.com/tataplay.com.config.js | 164 ++--- sites/tataplay.com/tataplay.com.test.js | 178 ++--- sites/teliatv.ee/teliatv.ee.test.js | 120 ++-- sites/tv.blue.ch/tv.blue.ch.test.js | 144 ++-- sites/tv.dir.bg/tv.dir.bg.channels.xml | 206 +++--- sites/tv.dir.bg/tv.dir.bg.config.js | 430 +++++------ sites/tv.dir.bg/tv.dir.bg.test.js | 100 +-- sites/tv.lv/tv.lv.test.js | 108 +-- sites/tv.magenta.at/tv.magenta.at.config.js | 294 ++++---- sites/tv.mail.ru/tv.mail.ru.config.js | 244 +++---- sites/tv.mail.ru/tv.mail.ru.test.js | 154 ++-- sites/tv.yandex.ru/tv.yandex.ru.test.js | 184 ++--- .../tv2go.t-2.net/tv2go.t-2.net.channels.xml | 676 +++++++++--------- sites/tv2go.t-2.net/tv2go.t-2.net.test.js | 136 ++-- sites/tvcesoir.fr/tvcesoir.fr.config.js | 198 ++--- sites/tvcesoir.fr/tvcesoir.fr.test.js | 100 +-- .../tvcubana.icrt.cu/tvcubana.icrt.cu.test.js | 104 +-- .../tvguide.myjcom.jp.test.js | 102 +-- sites/tvhebdo.com/tvhebdo.com.config.js | 194 ++--- sites/tvheute.at/tvheute.at.test.js | 90 +-- 86 files changed, 6821 insertions(+), 6821 deletions(-) diff --git a/scripts/core/configLoader.ts b/scripts/core/configLoader.ts index c5f05ad22..a49aee5a8 100644 --- a/scripts/core/configLoader.ts +++ b/scripts/core/configLoader.ts @@ -1,32 +1,32 @@ -import { SiteConfig } from 'epg-grabber' -import { pathToFileURL } from 'url' - -export class ConfigLoader { - async load(filepath: string): Promise { - const fileUrl = pathToFileURL(filepath).toString() - const config = (await import(fileUrl)).default - const defaultConfig = { - days: 1, - delay: 0, - output: 'guide.xml', - request: { - method: 'GET', - maxContentLength: 5242880, - timeout: 30000, - withCredentials: true, - jar: null, - responseType: 'arraybuffer', - cache: false, - headers: null, - data: null - }, - maxConnections: 1, - site: undefined, - url: undefined, - parser: undefined, - channels: undefined - } - - return { ...defaultConfig, ...config } as SiteConfig - } -} +import { SiteConfig } from 'epg-grabber' +import { pathToFileURL } from 'url' + +export class ConfigLoader { + async load(filepath: string): Promise { + const fileUrl = pathToFileURL(filepath).toString() + const config = (await import(fileUrl)).default + const defaultConfig = { + days: 1, + delay: 0, + output: 'guide.xml', + request: { + method: 'GET', + maxContentLength: 5242880, + timeout: 30000, + withCredentials: true, + jar: null, + responseType: 'arraybuffer', + cache: false, + headers: null, + data: null + }, + maxConnections: 1, + site: undefined, + url: undefined, + parser: undefined, + channels: undefined + } + + return { ...defaultConfig, ...config } as SiteConfig + } +} diff --git a/sites/9tv.co.il/9tv.co.il.test.js b/sites/9tv.co.il/9tv.co.il.test.js index 18ffe7fe4..7a1e9658c 100644 --- a/sites/9tv.co.il/9tv.co.il.test.js +++ b/sites/9tv.co.il/9tv.co.il.test.js @@ -1,56 +1,56 @@ -const { parser, url } = require('./9tv.co.il.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-03-06', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '#', - xmltv_id: 'Channel9.il' -} - -it('can generate valid url', () => { - expect(url({ date })).toBe( - 'https://www.9tv.co.il/BroadcastSchedule/getBrodcastSchedule?date=06/03/2022 00:00:00' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') - const result = parser({ content, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2022-03-06T04:30:00.000Z', - stop: '2022-03-06T07:10:00.000Z', - title: 'Слепая', - image: 'https://www.9tv.co.il/download/pictures/img_id=8484.jpg', - description: - 'Она не очень любит говорить о себе или о том, кто и зачем к ней обращается. Живет уединенно, в глуши. Но тех, кто приходит -принимает. Она видит судьбы.' - }, - { - start: '2022-03-06T07:10:00.000Z', - stop: '2022-03-06T08:10:00.000Z', - image: 'https://www.9tv.co.il/download/pictures/img_id=23694.jpg', - title: 'Орел и решка. Морской сезон', - description: 'Орел и решка. Морской сезон. Ведущие -Алина Астровская и Коля Серга.' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./9tv.co.il.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-03-06', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '#', + xmltv_id: 'Channel9.il' +} + +it('can generate valid url', () => { + expect(url({ date })).toBe( + 'https://www.9tv.co.il/BroadcastSchedule/getBrodcastSchedule?date=06/03/2022 00:00:00' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') + const result = parser({ content, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2022-03-06T04:30:00.000Z', + stop: '2022-03-06T07:10:00.000Z', + title: 'Слепая', + image: 'https://www.9tv.co.il/download/pictures/img_id=8484.jpg', + description: + 'Она не очень любит говорить о себе или о том, кто и зачем к ней обращается. Живет уединенно, в глуши. Но тех, кто приходит -принимает. Она видит судьбы.' + }, + { + start: '2022-03-06T07:10:00.000Z', + stop: '2022-03-06T08:10:00.000Z', + image: 'https://www.9tv.co.il/download/pictures/img_id=23694.jpg', + title: 'Орел и решка. Морской сезон', + description: 'Орел и решка. Морской сезон. Ведущие -Алина Астровская и Коля Серга.' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/allente.dk/allente.dk.test.js b/sites/allente.dk/allente.dk.test.js index 99192ef92..d79521f17 100644 --- a/sites/allente.dk/allente.dk.test.js +++ b/sites/allente.dk/allente.dk.test.js @@ -1,50 +1,50 @@ -const { parser, url } = require('./allente.dk.config.js') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2021-11-17', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '0148', - xmltv_id: 'SVT1.se' -} - -it('can generate valid url', () => { - expect(url({ date, channel })).toBe('https://cs-vcb.allente.dk/epg/events?date=2021-11-17') -}) - -it('can parse response', () => { - const content = - '' - const result = parser({ content, channel }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2022-08-22T07:10:00.000Z', - stop: '2022-08-22T07:30:00.000Z', - title: 'Hemmagympa med Sofia', - category: ['other'], - description: - 'Svenskt träningsprogram från 2021. Styrka. Sofia Åhman leder SVT:s hemmagympapass. Denna gång fokuserar vi på styrka.', - image: - 'https://viasatps.api.comspace.se/PS/channeldate/image/viasat.ps/21/2022-08-22/se.cs.svt1.event.A_41214031600.jpg?size=2560x1440', - season: 4, - episode: 1 - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: '{"date":"2001-11-17","categories":[],"channels":[]}' - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./allente.dk.config.js') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2021-11-17', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '0148', + xmltv_id: 'SVT1.se' +} + +it('can generate valid url', () => { + expect(url({ date, channel })).toBe('https://cs-vcb.allente.dk/epg/events?date=2021-11-17') +}) + +it('can parse response', () => { + const content = + '' + const result = parser({ content, channel }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2022-08-22T07:10:00.000Z', + stop: '2022-08-22T07:30:00.000Z', + title: 'Hemmagympa med Sofia', + category: ['other'], + description: + 'Svenskt träningsprogram från 2021. Styrka. Sofia Åhman leder SVT:s hemmagympapass. Denna gång fokuserar vi på styrka.', + image: + 'https://viasatps.api.comspace.se/PS/channeldate/image/viasat.ps/21/2022-08-22/se.cs.svt1.event.A_41214031600.jpg?size=2560x1440', + season: 4, + episode: 1 + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: '{"date":"2001-11-17","categories":[],"channels":[]}' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/allente.fi/allente.fi.test.js b/sites/allente.fi/allente.fi.test.js index 48d32022d..2601571d0 100644 --- a/sites/allente.fi/allente.fi.test.js +++ b/sites/allente.fi/allente.fi.test.js @@ -1,51 +1,51 @@ -const { parser, url } = require('./allente.fi.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2021-11-17', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '0148', - xmltv_id: 'SVT1.se' -} - -it('can generate valid url', () => { - expect(url({ date, channel })).toBe('https://cs-vcb.allente.fi/epg/events?date=2021-11-17') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ content, channel }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2022-08-22T07:10:00.000Z', - stop: '2022-08-22T07:30:00.000Z', - title: 'Hemmagympa med Sofia', - category: ['other'], - description: - 'Svenskt träningsprogram från 2021. Styrka. Sofia Åhman leder SVT:s hemmagympapass. Denna gång fokuserar vi på styrka.', - image: - 'https://viasatps.api.comspace.se/PS/channeldate/image/viasat.ps/21/2022-08-22/se.cs.svt1.event.A_41214031600.jpg?size=2560x1440', - season: 4, - episode: 1 - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./allente.fi.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2021-11-17', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '0148', + xmltv_id: 'SVT1.se' +} + +it('can generate valid url', () => { + expect(url({ date, channel })).toBe('https://cs-vcb.allente.fi/epg/events?date=2021-11-17') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ content, channel }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2022-08-22T07:10:00.000Z', + stop: '2022-08-22T07:30:00.000Z', + title: 'Hemmagympa med Sofia', + category: ['other'], + description: + 'Svenskt träningsprogram från 2021. Styrka. Sofia Åhman leder SVT:s hemmagympapass. Denna gång fokuserar vi på styrka.', + image: + 'https://viasatps.api.comspace.se/PS/channeldate/image/viasat.ps/21/2022-08-22/se.cs.svt1.event.A_41214031600.jpg?size=2560x1440', + season: 4, + episode: 1 + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/allente.no/allente.no.test.js b/sites/allente.no/allente.no.test.js index a58715e9a..f12e89789 100644 --- a/sites/allente.no/allente.no.test.js +++ b/sites/allente.no/allente.no.test.js @@ -1,51 +1,51 @@ -const { parser, url } = require('./allente.no.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2021-11-17', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '0148', - xmltv_id: 'SVT1.se' -} - -it('can generate valid url', () => { - expect(url({ date, channel })).toBe('https://cs-vcb.allente.no/epg/events?date=2021-11-17') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ content, channel }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2022-08-22T07:10:00.000Z', - stop: '2022-08-22T07:30:00.000Z', - title: 'Hemmagympa med Sofia', - category: ['other'], - description: - 'Svenskt träningsprogram från 2021. Styrka. Sofia Åhman leder SVT:s hemmagympapass. Denna gång fokuserar vi på styrka.', - image: - 'https://viasatps.api.comspace.se/PS/channeldate/image/viasat.ps/21/2022-08-22/se.cs.svt1.event.A_41214031600.jpg?size=2560x1440', - season: 4, - episode: 1 - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./allente.no.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2021-11-17', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '0148', + xmltv_id: 'SVT1.se' +} + +it('can generate valid url', () => { + expect(url({ date, channel })).toBe('https://cs-vcb.allente.no/epg/events?date=2021-11-17') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ content, channel }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2022-08-22T07:10:00.000Z', + stop: '2022-08-22T07:30:00.000Z', + title: 'Hemmagympa med Sofia', + category: ['other'], + description: + 'Svenskt träningsprogram från 2021. Styrka. Sofia Åhman leder SVT:s hemmagympapass. Denna gång fokuserar vi på styrka.', + image: + 'https://viasatps.api.comspace.se/PS/channeldate/image/viasat.ps/21/2022-08-22/se.cs.svt1.event.A_41214031600.jpg?size=2560x1440', + season: 4, + episode: 1 + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/allente.se/allente.se.test.js b/sites/allente.se/allente.se.test.js index de8a88512..2944c58af 100644 --- a/sites/allente.se/allente.se.test.js +++ b/sites/allente.se/allente.se.test.js @@ -1,51 +1,51 @@ -const { parser, url } = require('./allente.se.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2021-11-17', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '0148', - xmltv_id: 'SVT1.se' -} - -it('can generate valid url', () => { - expect(url({ date, channel })).toBe('https://cs-vcb.allente.se/epg/events?date=2021-11-17') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ content, channel }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2022-08-22T07:10:00.000Z', - stop: '2022-08-22T07:30:00.000Z', - title: 'Hemmagympa med Sofia', - category: ['other'], - description: - 'Svenskt träningsprogram från 2021. Styrka. Sofia Åhman leder SVT:s hemmagympapass. Denna gång fokuserar vi på styrka.', - image: - 'https://viasatps.api.comspace.se/PS/channeldate/image/viasat.ps/21/2022-08-22/se.cs.svt1.event.A_41214031600.jpg?size=2560x1440', - season: 4, - episode: 1 - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./allente.se.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2021-11-17', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '0148', + xmltv_id: 'SVT1.se' +} + +it('can generate valid url', () => { + expect(url({ date, channel })).toBe('https://cs-vcb.allente.se/epg/events?date=2021-11-17') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ content, channel }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2022-08-22T07:10:00.000Z', + stop: '2022-08-22T07:30:00.000Z', + title: 'Hemmagympa med Sofia', + category: ['other'], + description: + 'Svenskt träningsprogram från 2021. Styrka. Sofia Åhman leder SVT:s hemmagympapass. Denna gång fokuserar vi på styrka.', + image: + 'https://viasatps.api.comspace.se/PS/channeldate/image/viasat.ps/21/2022-08-22/se.cs.svt1.event.A_41214031600.jpg?size=2560x1440', + season: 4, + episode: 1 + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/arianatelevision.com/arianatelevision.com.test.js b/sites/arianatelevision.com/arianatelevision.com.test.js index 71e011035..dad80a9ac 100644 --- a/sites/arianatelevision.com/arianatelevision.com.test.js +++ b/sites/arianatelevision.com/arianatelevision.com.test.js @@ -1,59 +1,59 @@ -const { parser, url } = require('./arianatelevision.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2021-11-27', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '#', - xmltv_id: 'ArianaTVNational.af' -} - -it('can generate valid url', () => { - expect(url).toBe('https://www.arianatelevision.com/program-schedule/') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') - const result = parser({ content, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2021-11-27T02:30:00.000Z', - stop: '2021-11-27T03:00:00.000Z', - title: 'City Report' - }, - { - start: '2021-11-27T03:00:00.000Z', - stop: '2021-11-27T10:30:00.000Z', - title: 'ICC T20 Highlights' - }, - { - start: '2021-11-27T10:30:00.000Z', - stop: '2021-11-28T02:00:00.000Z', - title: 'ICC T20 World Cup' - }, - { - start: '2021-11-28T02:00:00.000Z', - stop: '2021-11-28T02:30:00.000Z', - title: 'Quran and Hadis' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./arianatelevision.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2021-11-27', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '#', + xmltv_id: 'ArianaTVNational.af' +} + +it('can generate valid url', () => { + expect(url).toBe('https://www.arianatelevision.com/program-schedule/') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') + const result = parser({ content, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2021-11-27T02:30:00.000Z', + stop: '2021-11-27T03:00:00.000Z', + title: 'City Report' + }, + { + start: '2021-11-27T03:00:00.000Z', + stop: '2021-11-27T10:30:00.000Z', + title: 'ICC T20 Highlights' + }, + { + start: '2021-11-27T10:30:00.000Z', + stop: '2021-11-28T02:00:00.000Z', + title: 'ICC T20 World Cup' + }, + { + start: '2021-11-28T02:00:00.000Z', + stop: '2021-11-28T02:30:00.000Z', + title: 'Quran and Hadis' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/artonline.tv/artonline.tv.test.js b/sites/artonline.tv/artonline.tv.test.js index ff3e093b0..cf6e020d1 100644 --- a/sites/artonline.tv/artonline.tv.test.js +++ b/sites/artonline.tv/artonline.tv.test.js @@ -1,66 +1,66 @@ -const { parser, url, request } = require('./artonline.tv.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const channel = { - site_id: '#Aflam2', - xmltv_id: 'ARTAflam2.sa' -} - -it('can generate valid url', () => { - expect(url({ channel })).toBe('https://www.artonline.tv/Home/TvlistAflam2') -}) - -it('can generate valid request method', () => { - expect(request.method).toBe('POST') -}) - -it('can generate valid request headers', () => { - expect(request.headers).toMatchObject({ - 'content-type': 'application/x-www-form-urlencoded' - }) -}) - -it('can generate valid request data for today', () => { - const date = dayjs.utc().startOf('d') - const data = request.data({ date }) - expect(data.get('objId')).toBe('0') -}) - -it('can generate valid request data for tomorrow', () => { - const date = dayjs.utc().startOf('d').add(1, 'd') - const data = request.data({ date }) - expect(data.get('objId')).toBe('1') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2022-03-03T21:30:00.000Z', - stop: '2022-03-03T23:04:00.000Z', - title: 'الراقصه و السياسي', - description: - 'تقرر الراقصه سونيا انشاء دار حضانه للأطفال اليتامى و عندما تتقدم بمشورعها للمسئول يرفض فتتحداه ، تلجأ للوزير عبد الحميد رأفت تربطه بها علاقة قديمة ، يخشى على مركزه و يرفض مساعدتها فتقرر كتابة مذكراتها بمساعدة أحد الصحفيين ، يتخوف عبد الحميد و المسئولين ثم يفاجأ عبد الحميد بحصول سونيا على الموافقه للمشورع و البدء في تنفيذه و ذلك لعلاقتها بأحد كبار المسئولين .', - image: 'https://www.artonline.tv/UploadImages/Channel/ARTAFLAM1/03/AlRaqesaWaAlSeyasi.jpg' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: '' - }) - expect(result).toMatchObject([]) -}) +const { parser, url, request } = require('./artonline.tv.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const channel = { + site_id: '#Aflam2', + xmltv_id: 'ARTAflam2.sa' +} + +it('can generate valid url', () => { + expect(url({ channel })).toBe('https://www.artonline.tv/Home/TvlistAflam2') +}) + +it('can generate valid request method', () => { + expect(request.method).toBe('POST') +}) + +it('can generate valid request headers', () => { + expect(request.headers).toMatchObject({ + 'content-type': 'application/x-www-form-urlencoded' + }) +}) + +it('can generate valid request data for today', () => { + const date = dayjs.utc().startOf('d') + const data = request.data({ date }) + expect(data.get('objId')).toBe('0') +}) + +it('can generate valid request data for tomorrow', () => { + const date = dayjs.utc().startOf('d').add(1, 'd') + const data = request.data({ date }) + expect(data.get('objId')).toBe('1') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2022-03-03T21:30:00.000Z', + stop: '2022-03-03T23:04:00.000Z', + title: 'الراقصه و السياسي', + description: + 'تقرر الراقصه سونيا انشاء دار حضانه للأطفال اليتامى و عندما تتقدم بمشورعها للمسئول يرفض فتتحداه ، تلجأ للوزير عبد الحميد رأفت تربطه بها علاقة قديمة ، يخشى على مركزه و يرفض مساعدتها فتقرر كتابة مذكراتها بمساعدة أحد الصحفيين ، يتخوف عبد الحميد و المسئولين ثم يفاجأ عبد الحميد بحصول سونيا على الموافقه للمشورع و البدء في تنفيذه و ذلك لعلاقتها بأحد كبار المسئولين .', + image: 'https://www.artonline.tv/UploadImages/Channel/ARTAFLAM1/03/AlRaqesaWaAlSeyasi.jpg' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: '' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/chada.ma/chada.ma.test.js b/sites/chada.ma/chada.ma.test.js index 56ac192cf..9d079113d 100644 --- a/sites/chada.ma/chada.ma.test.js +++ b/sites/chada.ma/chada.ma.test.js @@ -1,42 +1,42 @@ -const { parser, url } = require('./chada.ma.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -jest.mock('axios') - -it('can generate valid url', () => { - expect(url()).toBe('https://chada.ma/fr/chada-tv/grille-tv/') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') - const result = parser({ content }).map(p => { - p.start = dayjs(p.start).tz('Africa/Casablanca').format('YYYY-MM-DDTHH:mm:ssZ') - p.stop = dayjs(p.stop).tz('Africa/Casablanca').format('YYYY-MM-DDTHH:mm:ssZ') - return p - }) - - expect(result).toMatchObject([ - { - title: 'Bloc Prime + Clips', - description: 'No description available', - start: dayjs.tz('00:00', 'HH:mm', 'Africa/Casablanca').format('YYYY-MM-DDTHH:mm:ssZ'), - stop: dayjs.tz('09:00', 'HH:mm', 'Africa/Casablanca').format('YYYY-MM-DDTHH:mm:ssZ') - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./chada.ma.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +jest.mock('axios') + +it('can generate valid url', () => { + expect(url()).toBe('https://chada.ma/fr/chada-tv/grille-tv/') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') + const result = parser({ content }).map(p => { + p.start = dayjs(p.start).tz('Africa/Casablanca').format('YYYY-MM-DDTHH:mm:ssZ') + p.stop = dayjs(p.stop).tz('Africa/Casablanca').format('YYYY-MM-DDTHH:mm:ssZ') + return p + }) + + expect(result).toMatchObject([ + { + title: 'Bloc Prime + Clips', + description: 'No description available', + start: dayjs.tz('00:00', 'HH:mm', 'Africa/Casablanca').format('YYYY-MM-DDTHH:mm:ssZ'), + stop: dayjs.tz('09:00', 'HH:mm', 'Africa/Casablanca').format('YYYY-MM-DDTHH:mm:ssZ') + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/chaines-tv.orange.fr/chaines-tv.orange.fr.test.js b/sites/chaines-tv.orange.fr/chaines-tv.orange.fr.test.js index ae77a27fa..3a832c350 100644 --- a/sites/chaines-tv.orange.fr/chaines-tv.orange.fr.test.js +++ b/sites/chaines-tv.orange.fr/chaines-tv.orange.fr.test.js @@ -1,49 +1,49 @@ -const { parser, url } = require('./chaines-tv.orange.fr.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2021-11-08', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '192', - xmltv_id: 'TF1.fr' -} - -it('can generate valid url', () => { - const result = url({ channel, date }) - expect(result).toBe( - 'https://rp-ott-mediation-tv.woopic.com/api-gw/live/v3/applications/STB4PC/programs?groupBy=channel&includeEmptyChannels=false&period=1636329600000,1636416000000&after=192&limit=1' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ date, channel, content }) - expect(result).toMatchObject([ - { - start: '2021-11-07T23:35:00.000Z', - stop: '2021-11-08T00:20:00.000Z', - title: 'Tête de liste', - subTitle: 'Esprits criminels', - season: 10, - episode: 12, - description: - "Un tueur en série prend un plaisir pervers à prévenir les autorités de Tallahassee avant chaque nouveau meurtre. Rossi apprend le décès d'un de ses vieux amis.", - category: 'Série Suspense', - image: 'https://proxymedia.woopic.com/340/p/169_EMI_9697669.jpg' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./chaines-tv.orange.fr.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2021-11-08', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '192', + xmltv_id: 'TF1.fr' +} + +it('can generate valid url', () => { + const result = url({ channel, date }) + expect(result).toBe( + 'https://rp-ott-mediation-tv.woopic.com/api-gw/live/v3/applications/STB4PC/programs?groupBy=channel&includeEmptyChannels=false&period=1636329600000,1636416000000&after=192&limit=1' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ date, channel, content }) + expect(result).toMatchObject([ + { + start: '2021-11-07T23:35:00.000Z', + stop: '2021-11-08T00:20:00.000Z', + title: 'Tête de liste', + subTitle: 'Esprits criminels', + season: 10, + episode: 12, + description: + "Un tueur en série prend un plaisir pervers à prévenir les autorités de Tallahassee avant chaque nouveau meurtre. Rossi apprend le décès d'un de ses vieux amis.", + category: 'Série Suspense', + image: 'https://proxymedia.woopic.com/340/p/169_EMI_9697669.jpg' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/cosmotetv.gr/cosmotetv.gr.test.js b/sites/cosmotetv.gr/cosmotetv.gr.test.js index 07beb9f94..405a932d4 100644 --- a/sites/cosmotetv.gr/cosmotetv.gr.test.js +++ b/sites/cosmotetv.gr/cosmotetv.gr.test.js @@ -1,55 +1,55 @@ -const { parser, url } = require('./cosmotetv.gr.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -const timezone = require('dayjs/plugin/timezone') - -dayjs.extend(utc) -dayjs.extend(customParseFormat) -dayjs.extend(timezone) - -jest.mock('axios') - -const date = dayjs.utc('2024-12-26', 'YYYY-MM-DD').startOf('d') -const channel = { site_id: 'vouli', xmltv_id: 'HellenicParliamentTV.gr' } - -it('can generate valid url', () => { - const startOfDay = dayjs(date).startOf('day').utc().unix() - const endOfDay = dayjs(date).endOf('day').utc().unix() - expect(url({ date, channel })).toBe( - `https://mwapi-prod.cosmotetvott.gr/api/v3.4/epg/listings/el?from=${startOfDay}&to=${endOfDay}&callSigns=${channel.site_id}&endingIncludedInRange=false` - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') - const result = parser({ date, content }).map(p => { - p.start = dayjs(p.start).toISOString() - p.stop = dayjs(p.stop).toISOString() - return p - }) - - expect(result).toMatchObject([ - { - title: 'Τι Λέει ο Νόμος', - description: - 'νημερωτική εκπομπή. Συζήτηση με τους εισηγητές των κομμάτων για το νομοθετικό έργο.', - category: 'Special', - image: - 'https://gr-ermou-prod-cache05.static.cdn.cosmotetvott.gr/ote-prod/70/280/040029714812000800_1734415727199.jpg', - start: '2024-12-26T23:00:00.000Z', - stop: '2024-12-27T00:00:00.000Z' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json'), 'utf8') - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./cosmotetv.gr.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const timezone = require('dayjs/plugin/timezone') + +dayjs.extend(utc) +dayjs.extend(customParseFormat) +dayjs.extend(timezone) + +jest.mock('axios') + +const date = dayjs.utc('2024-12-26', 'YYYY-MM-DD').startOf('d') +const channel = { site_id: 'vouli', xmltv_id: 'HellenicParliamentTV.gr' } + +it('can generate valid url', () => { + const startOfDay = dayjs(date).startOf('day').utc().unix() + const endOfDay = dayjs(date).endOf('day').utc().unix() + expect(url({ date, channel })).toBe( + `https://mwapi-prod.cosmotetvott.gr/api/v3.4/epg/listings/el?from=${startOfDay}&to=${endOfDay}&callSigns=${channel.site_id}&endingIncludedInRange=false` + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') + const result = parser({ date, content }).map(p => { + p.start = dayjs(p.start).toISOString() + p.stop = dayjs(p.stop).toISOString() + return p + }) + + expect(result).toMatchObject([ + { + title: 'Τι Λέει ο Νόμος', + description: + 'νημερωτική εκπομπή. Συζήτηση με τους εισηγητές των κομμάτων για το νομοθετικό έργο.', + category: 'Special', + image: + 'https://gr-ermou-prod-cache05.static.cdn.cosmotetvott.gr/ote-prod/70/280/040029714812000800_1734415727199.jpg', + start: '2024-12-26T23:00:00.000Z', + stop: '2024-12-27T00:00:00.000Z' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json'), 'utf8') + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/cubmu.com/cubmu.com.test.js b/sites/cubmu.com/cubmu.com.test.js index a12f9e75c..810454dfa 100644 --- a/sites/cubmu.com/cubmu.com.test.js +++ b/sites/cubmu.com/cubmu.com.test.js @@ -1,47 +1,47 @@ -const { url, parser } = require('./cubmu.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -dayjs.extend(utc) - -const date = dayjs.utc('2023-11-05', 'DD/MM/YYYY').startOf('d') -const channel = { site_id: '4028c68574537fcd0174be43042758d8', xmltv_id: 'TransTV.id', lang: 'id' } -const channelEn = Object.assign({}, channel, { lang: 'en' }) - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://servicebuss.transvision.co.id/v2/cms/getEPGData?app_id=cubmu&tvs_platform_id=standalone&schedule_date=2023-11-05&channel_id=4028c68574537fcd0174be43042758d8' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') - const idResults = parser({ content, channel }) - expect(idResults).toMatchObject([ - { - start: '2023-11-04T18:30:00.000Z', - stop: '2023-11-04T19:00:00.000Z', - title: 'CNN Tech News', - description: - 'CNN Indonesia Tech News adalah berita teknologi yang membawa pemirsa ke dunia teknologi yang penuh dengan informasi, pendidikan, hiburan sampai informasi kesehatan terkini.' - } - ]) - - const enResults = parser({ content, channel: channelEn }) - expect(enResults).toMatchObject([ - { - start: '2023-11-04T18:30:00.000Z', - stop: '2023-11-04T19:00:00.000Z', - title: 'CNN Tech News', - description: - 'CNN Indonesia Tech News is tech news brings viewers into the world of technology that provides information, education, entertainment to the latest health information.' - } - ]) -}) - -it('can handle empty guide', () => { - const results = parser({ content: '' }) - - expect(results).toMatchObject([]) -}) +const { url, parser } = require('./cubmu.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +dayjs.extend(utc) + +const date = dayjs.utc('2023-11-05', 'DD/MM/YYYY').startOf('d') +const channel = { site_id: '4028c68574537fcd0174be43042758d8', xmltv_id: 'TransTV.id', lang: 'id' } +const channelEn = Object.assign({}, channel, { lang: 'en' }) + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://servicebuss.transvision.co.id/v2/cms/getEPGData?app_id=cubmu&tvs_platform_id=standalone&schedule_date=2023-11-05&channel_id=4028c68574537fcd0174be43042758d8' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') + const idResults = parser({ content, channel }) + expect(idResults).toMatchObject([ + { + start: '2023-11-04T18:30:00.000Z', + stop: '2023-11-04T19:00:00.000Z', + title: 'CNN Tech News', + description: + 'CNN Indonesia Tech News adalah berita teknologi yang membawa pemirsa ke dunia teknologi yang penuh dengan informasi, pendidikan, hiburan sampai informasi kesehatan terkini.' + } + ]) + + const enResults = parser({ content, channel: channelEn }) + expect(enResults).toMatchObject([ + { + start: '2023-11-04T18:30:00.000Z', + stop: '2023-11-04T19:00:00.000Z', + title: 'CNN Tech News', + description: + 'CNN Indonesia Tech News is tech news brings viewers into the world of technology that provides information, education, entertainment to the latest health information.' + } + ]) +}) + +it('can handle empty guide', () => { + const results = parser({ content: '' }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/derana.lk/derana.lk.config.js b/sites/derana.lk/derana.lk.config.js index 24ec4ae09..790de7ade 100644 --- a/sites/derana.lk/derana.lk.config.js +++ b/sites/derana.lk/derana.lk.config.js @@ -1,48 +1,48 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -const parseDuration = require('parse-duration').default -const timezone = require('dayjs/plugin/timezone') -const { sortBy } = require('../../scripts/functions') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) -dayjs.extend(timezone) - -module.exports = { - site: 'derana.lk', - url({ date }) { - return `https://derana.lk/api/schedules/${date.format('DD-MM-YYYY')}` - }, - parser({ content }) { - const programs = parseItems(content).map(item => { - const start = parseStart(item) - const duration = parseDuration(item.duration) - const stop = start.add(duration, 'ms') - - return { - title: item.dramaName, - image: item.imageUrl, - start, - stop - } - }) - - return sortBy(programs, p => p.start.valueOf()) - } -} - -function parseStart(item) { - return dayjs.tz(`${item.date} ${item.time}`, 'DD-MM-YYYY H:mm A', 'Asia/Colombo') -} - -function parseItems(content) { - try { - const data = JSON.parse(content) - if (!data || !Array.isArray(data.all_schedules)) return [] - - return data.all_schedules - } catch { - return [] - } -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const parseDuration = require('parse-duration').default +const timezone = require('dayjs/plugin/timezone') +const { sortBy } = require('../../scripts/functions') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) +dayjs.extend(timezone) + +module.exports = { + site: 'derana.lk', + url({ date }) { + return `https://derana.lk/api/schedules/${date.format('DD-MM-YYYY')}` + }, + parser({ content }) { + const programs = parseItems(content).map(item => { + const start = parseStart(item) + const duration = parseDuration(item.duration) + const stop = start.add(duration, 'ms') + + return { + title: item.dramaName, + image: item.imageUrl, + start, + stop + } + }) + + return sortBy(programs, p => p.start.valueOf()) + } +} + +function parseStart(item) { + return dayjs.tz(`${item.date} ${item.time}`, 'DD-MM-YYYY H:mm A', 'Asia/Colombo') +} + +function parseItems(content) { + try { + const data = JSON.parse(content) + if (!data || !Array.isArray(data.all_schedules)) return [] + + return data.all_schedules + } catch { + return [] + } +} diff --git a/sites/directv.com.ar/directv.com.ar.test.js b/sites/directv.com.ar/directv.com.ar.test.js index 786950a16..8786781a1 100644 --- a/sites/directv.com.ar/directv.com.ar.test.js +++ b/sites/directv.com.ar/directv.com.ar.test.js @@ -1,78 +1,78 @@ -const { parser, url, request } = require('./directv.com.ar.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-06-19', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '207#A&EHD', - xmltv_id: 'AEHDSouth.us' -} - -it('can generate valid url', () => { - expect(url).toBe('https://www.directv.com.ar/guia/ChannelDetail.aspx/GetProgramming') -}) - -it('can generate valid request method', () => { - expect(request.method).toBe('POST') -}) - -it('can generate valid request headers', () => { - expect(request.headers).toMatchObject({ - 'Content-Type': 'application/json; charset=UTF-8', - Cookie: 'PGCSS=16; PGLang=S; PGCulture=es-AR;' - }) -}) - -it('can generate valid request data', () => { - expect(request.data({ channel, date })).toMatchObject({ - filterParameters: { - day: 19, - time: 0, - minute: 0, - month: 6, - year: 2022, - offSetValue: 0, - filtersScreenFilters: [''], - isHd: '', - isChannelDetails: 'Y', - channelNum: '207', - channelName: 'A&EHD' - } - }) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ content, channel }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2022-06-19T03:00:00.000Z', - stop: '2022-06-19T03:15:00.000Z', - title: 'Chicas guapas', - description: - 'Un espacio destinado a la belleza y los distintos estilos de vida, que muestra el trabajo inspiracional de la moda latinoamericana.', - rating: { - system: 'MPA', - value: 'NR' - } - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: '', - channel - }) - expect(result).toMatchObject([]) -}) +const { parser, url, request } = require('./directv.com.ar.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-06-19', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '207#A&EHD', + xmltv_id: 'AEHDSouth.us' +} + +it('can generate valid url', () => { + expect(url).toBe('https://www.directv.com.ar/guia/ChannelDetail.aspx/GetProgramming') +}) + +it('can generate valid request method', () => { + expect(request.method).toBe('POST') +}) + +it('can generate valid request headers', () => { + expect(request.headers).toMatchObject({ + 'Content-Type': 'application/json; charset=UTF-8', + Cookie: 'PGCSS=16; PGLang=S; PGCulture=es-AR;' + }) +}) + +it('can generate valid request data', () => { + expect(request.data({ channel, date })).toMatchObject({ + filterParameters: { + day: 19, + time: 0, + minute: 0, + month: 6, + year: 2022, + offSetValue: 0, + filtersScreenFilters: [''], + isHd: '', + isChannelDetails: 'Y', + channelNum: '207', + channelName: 'A&EHD' + } + }) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ content, channel }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2022-06-19T03:00:00.000Z', + stop: '2022-06-19T03:15:00.000Z', + title: 'Chicas guapas', + description: + 'Un espacio destinado a la belleza y los distintos estilos de vida, que muestra el trabajo inspiracional de la moda latinoamericana.', + rating: { + system: 'MPA', + value: 'NR' + } + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: '', + channel + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/dstv.com/dstv.com.config.js b/sites/dstv.com/dstv.com.config.js index 6f1ce2470..44c761111 100644 --- a/sites/dstv.com/dstv.com.config.js +++ b/sites/dstv.com/dstv.com.config.js @@ -1,206 +1,206 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') -const { uniqBy } = require('../../scripts/functions') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -const API_ENDPOINT = 'https://www.dstv.com/umbraco/api/TvGuide' - -module.exports = { - site: 'dstv.com', - days: 2, - request: { - cache: { - ttl: 3 * 60 * 60 * 1000, // 3h - interpretHeader: false - } - }, - url: function ({ channel, date }) { - const [region] = channel.site_id.split('#') - const packageName = region === 'nga' ? '&package=DStv%20Premium' : '' - - return `${API_ENDPOINT}/GetProgrammes?d=${date.format( - 'YYYY-MM-DD' - )}${packageName}&country=${region}` - }, - async parser({ content, channel }) { - let programs = [] - const items = parseItems(content, channel) - for (const item of items) { - const details = await loadProgramDetails(item) - programs.push({ - title: item.Title, - description: parseDescription(details), - image: parseImage(details), - category: parseCategory(details), - start: parseTime(item.StartTime, channel), - stop: parseTime(item.EndTime, channel) - }) - } - - return programs - }, - async channels({ country }) { - - const countries = { - ao: 'ago', - bj: 'ben', - bw: 'bwa', - bf: 'bfa', - bi: 'bdi', - cm: 'cmr', - cv: 'cpv', - td: 'tcd', - cf: 'caf', - km: 'com', - cd: 'cod', - dj: 'dji', - gq: 'gnq', - er: 'eri', - sz: 'swz', - et: 'eth', - ga: 'gab', - gm: 'gmb', - gh: 'gha', - gn: 'gin', - gw: 'gnb', - ci: 'civ', - ke: 'ken', - lr: 'lbr', - mg: 'mdg', - mw: 'mwi', - ml: 'mli', - mr: 'mrt', - mu: 'mus', - mz: 'moz', - na: 'nam', - ne: 'ner', - ng: 'nga', - cg: 'cog', - rw: 'rwa', - st: 'stp', - sn: 'sen', - sc: 'syc', - sl: 'sle', - so: 'som', - za: 'zaf', - ss: 'ssd', - sd: 'sdn', - tz: 'tza', - tg: 'tgo', - ug: 'uga', - zm: 'zmb', - zw: 'zwe' - } - - const code = countries[country] - - const data = await axios - .get(`${API_ENDPOINT}/GetProgrammes?d=${dayjs().format('YYYY-MM-DD')}&country=${code}`) - .then(r => r.data) - .catch(console.log) - - let channels = [] - data.Channels.forEach(item => { - channels.push({ - lang: 'en', - site_id: `${code}#${item.Number}`, - name: item.Name - }) - }) - - return uniqBy(channels, 'site_id') - } -} - -function parseTime(time, channel) { - const tz = { - ago: 'Africa/Luanda', - ben: 'Africa/Porto-Novo', - bwa: 'Africa/Gaborone', - bfa: 'Africa/Ouagadougou', - bdi: 'Africa/Bujumbura', - cmr: 'Africa/Douala', - cpv: 'CVT', - tcd: 'Africa/Ndjamena', - caf: 'Africa/Bangui', - com: 'Indian/Comoro', - cod: 'Africa/Kinshasa', - dji: 'Africa/Djibouti', - gnq: 'Africa/Malabo', - eri: 'Africa/Asmara', - swz: 'SAST', - eth: 'Africa/Addis_Ababa', - gap: 'Africa/Libreville', - gmb: 'Africa/Banjul', - gha: 'Africa/Accra', - gin: 'Africa/Conakry', - gnb: 'Africa/Bissau', - civ: 'Africa/Abidjan', - ken: 'Africa/Nairobi', - lbr: 'Africa/Monrovia', - mdg: 'Indian/Antananarivo', - mwi: 'Africa/Blantyre', - mli: 'Africa/Bamako', - mrt: 'Africa/Nouakchott', - mus: 'Indian/Mauritius', - moz: 'Africa/Maputo', - nam: 'Africa/Windhoek', - ner: 'Africa/Niamey', - nga: 'Africa/Lagos', - cog: 'Africa/Brazzaville', - rwa: 'Africa/Kigali', - stp: 'Africa/Sao_Tome', - sen: 'Africa/Dakar', - syc: 'Indian/Mahe', - sle: 'Africa/Freetown', - som: 'Africa/Mogadishu', - zaf: 'Africa/Johannesburg', - ssd: 'Africa/Juba', - sdn: 'Africa/Khartoum', - tza: 'Africa/Dar_es_Salaam', - tgo: 'Africa/Lome', - uga: 'Africa/Kampala', - zmb: 'Africa/Lusaka', - zwe: 'Africa/Harare' - } - const [region] = channel.site_id.split('#') - - return dayjs.tz(time, 'YYYY-MM-DDTHH:mm:ss', tz[region]) -} - -function parseDescription(details) { - return details ? details.Synopsis : null -} - -function parseImage(details) { - return details ? details.ThumbnailUri : null -} - -function parseCategory(details) { - return details ? details.SubGenres : null -} - -async function loadProgramDetails(item) { - const url = `${API_ENDPOINT}/GetProgramme?id=${item.Id}` - - return axios - .get(url) - .then(r => r.data) - .catch(console.error) -} - -function parseItems(content, channel) { - const [, channelId] = channel.site_id.split('#') - const data = JSON.parse(content) - if (!data || !Array.isArray(data.Channels)) return [] - const channelData = data.Channels.find(c => c.Number === channelId) - if (!channelData || !Array.isArray(channelData.Programmes)) return [] - - return channelData.Programmes -} +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const { uniqBy } = require('../../scripts/functions') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +const API_ENDPOINT = 'https://www.dstv.com/umbraco/api/TvGuide' + +module.exports = { + site: 'dstv.com', + days: 2, + request: { + cache: { + ttl: 3 * 60 * 60 * 1000, // 3h + interpretHeader: false + } + }, + url: function ({ channel, date }) { + const [region] = channel.site_id.split('#') + const packageName = region === 'nga' ? '&package=DStv%20Premium' : '' + + return `${API_ENDPOINT}/GetProgrammes?d=${date.format( + 'YYYY-MM-DD' + )}${packageName}&country=${region}` + }, + async parser({ content, channel }) { + let programs = [] + const items = parseItems(content, channel) + for (const item of items) { + const details = await loadProgramDetails(item) + programs.push({ + title: item.Title, + description: parseDescription(details), + image: parseImage(details), + category: parseCategory(details), + start: parseTime(item.StartTime, channel), + stop: parseTime(item.EndTime, channel) + }) + } + + return programs + }, + async channels({ country }) { + + const countries = { + ao: 'ago', + bj: 'ben', + bw: 'bwa', + bf: 'bfa', + bi: 'bdi', + cm: 'cmr', + cv: 'cpv', + td: 'tcd', + cf: 'caf', + km: 'com', + cd: 'cod', + dj: 'dji', + gq: 'gnq', + er: 'eri', + sz: 'swz', + et: 'eth', + ga: 'gab', + gm: 'gmb', + gh: 'gha', + gn: 'gin', + gw: 'gnb', + ci: 'civ', + ke: 'ken', + lr: 'lbr', + mg: 'mdg', + mw: 'mwi', + ml: 'mli', + mr: 'mrt', + mu: 'mus', + mz: 'moz', + na: 'nam', + ne: 'ner', + ng: 'nga', + cg: 'cog', + rw: 'rwa', + st: 'stp', + sn: 'sen', + sc: 'syc', + sl: 'sle', + so: 'som', + za: 'zaf', + ss: 'ssd', + sd: 'sdn', + tz: 'tza', + tg: 'tgo', + ug: 'uga', + zm: 'zmb', + zw: 'zwe' + } + + const code = countries[country] + + const data = await axios + .get(`${API_ENDPOINT}/GetProgrammes?d=${dayjs().format('YYYY-MM-DD')}&country=${code}`) + .then(r => r.data) + .catch(console.log) + + let channels = [] + data.Channels.forEach(item => { + channels.push({ + lang: 'en', + site_id: `${code}#${item.Number}`, + name: item.Name + }) + }) + + return uniqBy(channels, 'site_id') + } +} + +function parseTime(time, channel) { + const tz = { + ago: 'Africa/Luanda', + ben: 'Africa/Porto-Novo', + bwa: 'Africa/Gaborone', + bfa: 'Africa/Ouagadougou', + bdi: 'Africa/Bujumbura', + cmr: 'Africa/Douala', + cpv: 'CVT', + tcd: 'Africa/Ndjamena', + caf: 'Africa/Bangui', + com: 'Indian/Comoro', + cod: 'Africa/Kinshasa', + dji: 'Africa/Djibouti', + gnq: 'Africa/Malabo', + eri: 'Africa/Asmara', + swz: 'SAST', + eth: 'Africa/Addis_Ababa', + gap: 'Africa/Libreville', + gmb: 'Africa/Banjul', + gha: 'Africa/Accra', + gin: 'Africa/Conakry', + gnb: 'Africa/Bissau', + civ: 'Africa/Abidjan', + ken: 'Africa/Nairobi', + lbr: 'Africa/Monrovia', + mdg: 'Indian/Antananarivo', + mwi: 'Africa/Blantyre', + mli: 'Africa/Bamako', + mrt: 'Africa/Nouakchott', + mus: 'Indian/Mauritius', + moz: 'Africa/Maputo', + nam: 'Africa/Windhoek', + ner: 'Africa/Niamey', + nga: 'Africa/Lagos', + cog: 'Africa/Brazzaville', + rwa: 'Africa/Kigali', + stp: 'Africa/Sao_Tome', + sen: 'Africa/Dakar', + syc: 'Indian/Mahe', + sle: 'Africa/Freetown', + som: 'Africa/Mogadishu', + zaf: 'Africa/Johannesburg', + ssd: 'Africa/Juba', + sdn: 'Africa/Khartoum', + tza: 'Africa/Dar_es_Salaam', + tgo: 'Africa/Lome', + uga: 'Africa/Kampala', + zmb: 'Africa/Lusaka', + zwe: 'Africa/Harare' + } + const [region] = channel.site_id.split('#') + + return dayjs.tz(time, 'YYYY-MM-DDTHH:mm:ss', tz[region]) +} + +function parseDescription(details) { + return details ? details.Synopsis : null +} + +function parseImage(details) { + return details ? details.ThumbnailUri : null +} + +function parseCategory(details) { + return details ? details.SubGenres : null +} + +async function loadProgramDetails(item) { + const url = `${API_ENDPOINT}/GetProgramme?id=${item.Id}` + + return axios + .get(url) + .then(r => r.data) + .catch(console.error) +} + +function parseItems(content, channel) { + const [, channelId] = channel.site_id.split('#') + const data = JSON.parse(content) + if (!data || !Array.isArray(data.Channels)) return [] + const channelData = data.Channels.find(c => c.Number === channelId) + if (!channelData || !Array.isArray(channelData.Programmes)) return [] + + return channelData.Programmes +} diff --git a/sites/firstmedia.com/firstmedia.com.test.js b/sites/firstmedia.com/firstmedia.com.test.js index 21bdd7b87..f9420c8bf 100644 --- a/sites/firstmedia.com/firstmedia.com.test.js +++ b/sites/firstmedia.com/firstmedia.com.test.js @@ -1,38 +1,38 @@ -const { url, parser } = require('./firstmedia.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -dayjs.extend(utc) - -const date = dayjs.utc('2023-11-08').startOf('d') -const channel = { site_id: '243', xmltv_id: 'AlJazeeraEnglish.qa', lang: 'id' } - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://api.firstmedia.com/api/content/tv-guide/list?date=08/11/2023&channel=243&startTime=1&endTime=24' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') - const results = parser({ content, channel, date }) - - // All time in Asia/Jakarta - // 2023-11-08 17:00:00 -> 2023-11-08 20:00:00 = 2023-11-08 03:00:00 - // 2023-11-08 17:00:00 -> 2023-11-08 20:30:00 = 2023-11-08 03:30:00 - expect(results).toMatchObject([ - { - start: '2023-11-07T20:00:00.000Z', - stop: '2023-11-07T20:30:00.000Z', - title: 'News Live', - description: 'Up to date news and analysis from around the world.' - } - ]) -}) - -it('can handle empty guide', () => { - const results = parser({ content: '' }) - - expect(results).toMatchObject([]) -}) +const { url, parser } = require('./firstmedia.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +dayjs.extend(utc) + +const date = dayjs.utc('2023-11-08').startOf('d') +const channel = { site_id: '243', xmltv_id: 'AlJazeeraEnglish.qa', lang: 'id' } + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://api.firstmedia.com/api/content/tv-guide/list?date=08/11/2023&channel=243&startTime=1&endTime=24' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') + const results = parser({ content, channel, date }) + + // All time in Asia/Jakarta + // 2023-11-08 17:00:00 -> 2023-11-08 20:00:00 = 2023-11-08 03:00:00 + // 2023-11-08 17:00:00 -> 2023-11-08 20:30:00 = 2023-11-08 03:30:00 + expect(results).toMatchObject([ + { + start: '2023-11-07T20:00:00.000Z', + stop: '2023-11-07T20:30:00.000Z', + title: 'News Live', + description: 'Up to date news and analysis from around the world.' + } + ]) +}) + +it('can handle empty guide', () => { + const results = parser({ content: '' }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/foxsports.com.au/foxsports.com.au.test.js b/sites/foxsports.com.au/foxsports.com.au.test.js index 0cf3431d3..b6ed8a020 100644 --- a/sites/foxsports.com.au/foxsports.com.au.test.js +++ b/sites/foxsports.com.au/foxsports.com.au.test.js @@ -1,43 +1,43 @@ -const { parser, url } = require('./foxsports.com.au.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -dayjs.extend(utc) - -const date = dayjs.utc('2022-12-14', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '2', - xmltv_id: 'FoxLeague.au' -} -it('can generate valid url', () => { - expect(url({ date })).toBe( - 'https://tvguide.foxsports.com.au/granite-api/programmes.json?from=2022-12-14&to=2022-12-15' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ content, channel }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - title: 'NRL', - sub_title: 'Eels v Titans', - description: - 'The Eels and Titans have plenty of motivation this season after heartbreaking Finals losses in 2021. Parramatta has won their past five against Gold Coast.', - category: 'Rugby League', - start: '2022-12-13T13:00:00.000Z', - stop: '2022-12-13T14:00:00.000Z' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({content: ''}, channel) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./foxsports.com.au.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +dayjs.extend(utc) + +const date = dayjs.utc('2022-12-14', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '2', + xmltv_id: 'FoxLeague.au' +} +it('can generate valid url', () => { + expect(url({ date })).toBe( + 'https://tvguide.foxsports.com.au/granite-api/programmes.json?from=2022-12-14&to=2022-12-15' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ content, channel }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + title: 'NRL', + sub_title: 'Eels v Titans', + description: + 'The Eels and Titans have plenty of motivation this season after heartbreaking Finals losses in 2021. Parramatta has won their past five against Gold Coast.', + category: 'Rugby League', + start: '2022-12-13T13:00:00.000Z', + stop: '2022-12-13T14:00:00.000Z' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({content: ''}, channel) + expect(result).toMatchObject([]) +}) diff --git a/sites/freetv.tv/freetv.tv.test.js b/sites/freetv.tv/freetv.tv.test.js index cbb3a4437..bab8ef6a8 100644 --- a/sites/freetv.tv/freetv.tv.test.js +++ b/sites/freetv.tv/freetv.tv.test.js @@ -1,51 +1,51 @@ -const { parser, url } = require('./freetv.tv.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-03-28', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '3370462', - xmltv_id: 'Kan11.il' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe('https://web.freetv.tv/api/products/lives/programmes?liveId[]=3370462&since=2025-03-28T00%3A00%2B0200&till=2025-03-29T00%3A00%2B0300&lang=HEB&platform=BROWSER') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const results = parser({ content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(2) - expect(results[0]).toMatchObject({ - title: 'בוש 4 - פרק 3', - description: 'עונה 4 חדשה לדרמה הבלשית. 3. השטן בתוך הבית: הכוח המיוחד מנסה לחקור, ומגלה אליבי שקרי עם השלכות מרעישות. הבלש סנטיאגו רוברטסון צריך לשים את קשריו האישיים בצד למען החקירה. בוש זוכה לביקור פתע לילי.כ עב', - image: 'https://d1zqtf09wb8nt5.cloudfront.net/scale/oil/freetv/upload/programme/1361162/COVER/images/1361162_1736767668746.jpg?dsth=177&dstw=315&srcmode=0&srcx=0&srcy=0&quality=65&type=1&srcw=1/1&srch=1/1', - icon: 'https://d1zqtf09wb8nt5.cloudfront.net/scale/oil/freetv/upload/programme/1361162/COVER/images/1361162_1736767668746.jpg?dsth=177&dstw=315&srcmode=0&srcx=0&srcy=0&quality=65&type=1&srcw=1/1&srch=1/1', - start: '2025-03-27T21:26:00.000Z', - stop: '2025-03-27T22:17:00.000Z' - }) - expect(results[1]).toMatchObject({ - title: 'אבא משתדל - 5. חבר', - description: 'סדרה קומית. יוסי מכיר אב לילד עם צרכים מיוחדים ובין השניים מתפתח קשר בסגנון חיזור גורלי שמערער את יוסי. כ עב.', - image: 'https://d1zqtf09wb8nt5.cloudfront.net/scale/oil/freetv/upload/programme/1070668/COVER/images/1070668_1742202219830.jpg?dsth=177&dstw=315&srcmode=0&srcx=0&srcy=0&quality=65&type=1&srcw=1/1&srch=1/1', - icon: 'https://d1zqtf09wb8nt5.cloudfront.net/scale/oil/freetv/upload/programme/1070668/COVER/images/1070668_1742202219830.jpg?dsth=177&dstw=315&srcmode=0&srcx=0&srcy=0&quality=65&type=1&srcw=1/1&srch=1/1', - start: '2025-03-27T22:17:00.000Z', - stop: '2025-03-27T22:43:00.000Z' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ content: '' }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./freetv.tv.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-03-28', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '3370462', + xmltv_id: 'Kan11.il' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe('https://web.freetv.tv/api/products/lives/programmes?liveId[]=3370462&since=2025-03-28T00%3A00%2B0200&till=2025-03-29T00%3A00%2B0300&lang=HEB&platform=BROWSER') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const results = parser({ content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(2) + expect(results[0]).toMatchObject({ + title: 'בוש 4 - פרק 3', + description: 'עונה 4 חדשה לדרמה הבלשית. 3. השטן בתוך הבית: הכוח המיוחד מנסה לחקור, ומגלה אליבי שקרי עם השלכות מרעישות. הבלש סנטיאגו רוברטסון צריך לשים את קשריו האישיים בצד למען החקירה. בוש זוכה לביקור פתע לילי.כ עב', + image: 'https://d1zqtf09wb8nt5.cloudfront.net/scale/oil/freetv/upload/programme/1361162/COVER/images/1361162_1736767668746.jpg?dsth=177&dstw=315&srcmode=0&srcx=0&srcy=0&quality=65&type=1&srcw=1/1&srch=1/1', + icon: 'https://d1zqtf09wb8nt5.cloudfront.net/scale/oil/freetv/upload/programme/1361162/COVER/images/1361162_1736767668746.jpg?dsth=177&dstw=315&srcmode=0&srcx=0&srcy=0&quality=65&type=1&srcw=1/1&srch=1/1', + start: '2025-03-27T21:26:00.000Z', + stop: '2025-03-27T22:17:00.000Z' + }) + expect(results[1]).toMatchObject({ + title: 'אבא משתדל - 5. חבר', + description: 'סדרה קומית. יוסי מכיר אב לילד עם צרכים מיוחדים ובין השניים מתפתח קשר בסגנון חיזור גורלי שמערער את יוסי. כ עב.', + image: 'https://d1zqtf09wb8nt5.cloudfront.net/scale/oil/freetv/upload/programme/1070668/COVER/images/1070668_1742202219830.jpg?dsth=177&dstw=315&srcmode=0&srcx=0&srcy=0&quality=65&type=1&srcw=1/1&srch=1/1', + icon: 'https://d1zqtf09wb8nt5.cloudfront.net/scale/oil/freetv/upload/programme/1070668/COVER/images/1070668_1742202219830.jpg?dsth=177&dstw=315&srcmode=0&srcx=0&srcy=0&quality=65&type=1&srcw=1/1&srch=1/1', + start: '2025-03-27T22:17:00.000Z', + stop: '2025-03-27T22:43:00.000Z' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ content: '' }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/frikanalen.no/frikanalen.no.test.js b/sites/frikanalen.no/frikanalen.no.test.js index 6c4617925..a7aee3c20 100644 --- a/sites/frikanalen.no/frikanalen.no.test.js +++ b/sites/frikanalen.no/frikanalen.no.test.js @@ -1,48 +1,48 @@ -const { parser, url } = require('./frikanalen.no.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-01-19', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '#', - xmltv_id: 'Frikanalen.no' -} - -it('can generate valid url', () => { - expect(url({ date })).toBe( - 'https://frikanalen.no/api/scheduleitems/?date=2022-01-19&format=json&limit=100' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2022-01-18T23:47:00.000Z', - stop: '2022-01-19T00:44:55.640Z', - title: 'FSCONS 2017 - Keynote: TBA - Linda Sandvik', - category: ['Samfunn'], - description: "Linda Sandvik's keynote at FSCONS 2017\r\n\r\nRecorded by NUUG for FSCONS." - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./frikanalen.no.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-01-19', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '#', + xmltv_id: 'Frikanalen.no' +} + +it('can generate valid url', () => { + expect(url({ date })).toBe( + 'https://frikanalen.no/api/scheduleitems/?date=2022-01-19&format=json&limit=100' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2022-01-18T23:47:00.000Z', + stop: '2022-01-19T00:44:55.640Z', + title: 'FSCONS 2017 - Keynote: TBA - Linda Sandvik', + category: ['Samfunn'], + description: "Linda Sandvik's keynote at FSCONS 2017\r\n\r\nRecorded by NUUG for FSCONS." + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/galamtv.kz/galamtv.kz.test.js b/sites/galamtv.kz/galamtv.kz.test.js index 8caca3f96..9e762edbb 100644 --- a/sites/galamtv.kz/galamtv.kz.test.js +++ b/sites/galamtv.kz/galamtv.kz.test.js @@ -1,52 +1,52 @@ -const { parser, url } = require('./galamtv.kz.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-01-10', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '636e54cf8a8f73bae8244f41', - xmltv_id: 'Qazaqstan.kz' -} - -it('can generate valid url', () => { - expect(url({ date, channel })).toBe( - `https://galam.server-api.lfstrm.tv/channels/${ - channel.site_id - }/programs?period=${date.unix()}:${date.add(1, 'day').unix()}` - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ content, channel }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2025-01-10T01:00:00.000Z', - stop: '2025-01-10T01:05:00.000Z', - title: 'Гимн', - description: 'Государственный гимн Республики Казахстан', - image: - 'http://galam.server-img.lfstrm.tv:80/image/aHR0cDovL2dhbGFtLmltZy1vcmlnaW5hbHMubGZzdHJtLnR2OjgwL3R2aW1hZ2VzL3RodW1iL2YyNWFmYWY2ZDkzYjU5YjdkMjBiZDNiODhiZjg4NWI0X29yaWcuanBn' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./galamtv.kz.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-01-10', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '636e54cf8a8f73bae8244f41', + xmltv_id: 'Qazaqstan.kz' +} + +it('can generate valid url', () => { + expect(url({ date, channel })).toBe( + `https://galam.server-api.lfstrm.tv/channels/${ + channel.site_id + }/programs?period=${date.unix()}:${date.add(1, 'day').unix()}` + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ content, channel }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2025-01-10T01:00:00.000Z', + stop: '2025-01-10T01:05:00.000Z', + title: 'Гимн', + description: 'Государственный гимн Республики Казахстан', + image: + 'http://galam.server-img.lfstrm.tv:80/image/aHR0cDovL2dhbGFtLmltZy1vcmlnaW5hbHMubGZzdHJtLnR2OjgwL3R2aW1hZ2VzL3RodW1iL2YyNWFmYWY2ZDkzYjU5YjdkMjBiZDNiODhiZjg4NWI0X29yaWcuanBn' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/guida.tv/guida.tv.config.js b/sites/guida.tv/guida.tv.config.js index a5e7feb3d..9e00f73f9 100644 --- a/sites/guida.tv/guida.tv.config.js +++ b/sites/guida.tv/guida.tv.config.js @@ -1,98 +1,98 @@ -const axios = require('axios') -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') -const { uniqBy } = require('../../scripts/functions') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'guida.tv', - days: 2, - url: function ({ date, channel }) { - return `https://www.guida.tv/programmi-tv/palinsesto/canale/${ - channel.site_id - }.html?dt=${date.format('YYYY-MM-DD')}` - }, - parser: function ({ content, date, channel }) { - const programs = [] - const items = parseItems(content) - items.forEach(item => { - const prev = programs[programs.length - 1] - const $item = cheerio.load(item) - let start = parseStart($item, date, channel) - if (prev) { - if (start.isBefore(prev.start)) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.add(30, 'm') - programs.push({ - title: parseTitle($item), - start, - stop - }) - }) - - return programs - }, - async channels() { - const providers = ['-1', '-2', '-3'] - - const channels = [] - for (let provider of providers) { - const data = await axios - .post('https://www.guida.tv/guide/schedule', null, { - params: { - provider, - region: 'Italy', - TVperiod: 'Night', - date: dayjs().format('YYYY-MM-DD'), - st: 0, - u_time: 1429, - is_mobile: 1 - } - }) - .then(r => r.data) - .catch(console.log) - - const $ = cheerio.load(data) - $('.channelname').each((i, el) => { - const name = $(el).find('center > a:eq(1)').text() - const url = $(el).find('center > a:eq(1)').attr('href') - const [, number, slug] = url.match(/\/(\d+)\/(.*)\.html$/) - - channels.push({ - lang: 'it', - name, - site_id: `${number}/${slug}` - }) - }) - } - - return uniqBy(channels, 'site_id') - } -} - -function parseStart($item, date) { - const timeString = $item('td:eq(0)').text().trim() - const dateString = `${date.format('YYYY-MM-DD')} ${timeString}` - - return dayjs.tz(dateString, 'YYYY-MM-DD HH:mm', 'Europe/Rome') -} - -function parseTitle($item) { - return $item('td:eq(1)').text().trim() -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('table.table > tbody > tr').toArray() -} +const axios = require('axios') +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const { uniqBy } = require('../../scripts/functions') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'guida.tv', + days: 2, + url: function ({ date, channel }) { + return `https://www.guida.tv/programmi-tv/palinsesto/canale/${ + channel.site_id + }.html?dt=${date.format('YYYY-MM-DD')}` + }, + parser: function ({ content, date, channel }) { + const programs = [] + const items = parseItems(content) + items.forEach(item => { + const prev = programs[programs.length - 1] + const $item = cheerio.load(item) + let start = parseStart($item, date, channel) + if (prev) { + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.add(30, 'm') + programs.push({ + title: parseTitle($item), + start, + stop + }) + }) + + return programs + }, + async channels() { + const providers = ['-1', '-2', '-3'] + + const channels = [] + for (let provider of providers) { + const data = await axios + .post('https://www.guida.tv/guide/schedule', null, { + params: { + provider, + region: 'Italy', + TVperiod: 'Night', + date: dayjs().format('YYYY-MM-DD'), + st: 0, + u_time: 1429, + is_mobile: 1 + } + }) + .then(r => r.data) + .catch(console.log) + + const $ = cheerio.load(data) + $('.channelname').each((i, el) => { + const name = $(el).find('center > a:eq(1)').text() + const url = $(el).find('center > a:eq(1)').attr('href') + const [, number, slug] = url.match(/\/(\d+)\/(.*)\.html$/) + + channels.push({ + lang: 'it', + name, + site_id: `${number}/${slug}` + }) + }) + } + + return uniqBy(channels, 'site_id') + } +} + +function parseStart($item, date) { + const timeString = $item('td:eq(0)').text().trim() + const dateString = `${date.format('YYYY-MM-DD')} ${timeString}` + + return dayjs.tz(dateString, 'YYYY-MM-DD HH:mm', 'Europe/Rome') +} + +function parseTitle($item) { + return $item('td:eq(1)').text().trim() +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('table.table > tbody > tr').toArray() +} diff --git a/sites/guidatv.sky.it/guidatv.sky.it.test.js b/sites/guidatv.sky.it/guidatv.sky.it.test.js index 68a7ebbe7..6ed281553 100644 --- a/sites/guidatv.sky.it/guidatv.sky.it.test.js +++ b/sites/guidatv.sky.it/guidatv.sky.it.test.js @@ -1,52 +1,52 @@ -const { parser, url } = require('./guidatv.sky.it.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-05-06', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'DTH#10458', - xmltv_id: '20Mediaset.it' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://apid.sky.it/gtv/v1/events?from=2022-05-06T00:00:00Z&to=2022-05-07T00:00:00Z&pageSize=999&pageNum=0&env=DTH&channels=10458' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2022-05-06T00:35:40.000Z', - stop: '2022-05-06T01:15:40.000Z', - title: 'Distretto di Polizia', - description: - "S6 Ep26 La resa dei conti - Fino all'ultimo la sfida tra Ardenzi e Carrano, nemici di vecchia data, riserva clamorosi colpi di scena. E si scopre che non e' tutto come sembrava.", - season: 6, - episode: 26, - image: - 'https://guidatv.sky.it/uuid/77c630aa-4744-44cb-a88e-3e871c6b73d9/cover?md5ChecksumParam=61135b999a63e3d3f4a933b9edeb0c1b', - category: 'Intrattenimento/Fiction', - url: 'https://guidatv.sky.it/serie-tv/distretto-di-polizia/stagione-6/episodio-26/77c630aa-4744-44cb-a88e-3e871c6b73d9' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./guidatv.sky.it.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-05-06', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'DTH#10458', + xmltv_id: '20Mediaset.it' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://apid.sky.it/gtv/v1/events?from=2022-05-06T00:00:00Z&to=2022-05-07T00:00:00Z&pageSize=999&pageNum=0&env=DTH&channels=10458' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2022-05-06T00:35:40.000Z', + stop: '2022-05-06T01:15:40.000Z', + title: 'Distretto di Polizia', + description: + "S6 Ep26 La resa dei conti - Fino all'ultimo la sfida tra Ardenzi e Carrano, nemici di vecchia data, riserva clamorosi colpi di scena. E si scopre che non e' tutto come sembrava.", + season: 6, + episode: 26, + image: + 'https://guidatv.sky.it/uuid/77c630aa-4744-44cb-a88e-3e871c6b73d9/cover?md5ChecksumParam=61135b999a63e3d3f4a933b9edeb0c1b', + category: 'Intrattenimento/Fiction', + url: 'https://guidatv.sky.it/serie-tv/distretto-di-polizia/stagione-6/episodio-26/77c630aa-4744-44cb-a88e-3e871c6b73d9' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/guidetnt.com/guidetnt.com.config.js b/sites/guidetnt.com/guidetnt.com.config.js index 092871f64..bc0fcf1bd 100755 --- a/sites/guidetnt.com/guidetnt.com.config.js +++ b/sites/guidetnt.com/guidetnt.com.config.js @@ -1,338 +1,338 @@ -const cheerio = require('cheerio') -const axios = require('axios') -const dayjs = require('dayjs') -const customParseFormat = require('dayjs/plugin/customParseFormat') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -require('dayjs/locale/fr') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) -dayjs.extend(timezone) - -const PARIS_TZ = 'Europe/Paris' - -module.exports = { - site: 'guidetnt.com', - days: 2, - url({ channel, date }) { - const now = dayjs() - const demain = now.add(1, 'd') - if (date && date.isSame(demain, 'day')) { - return `https://www.guidetnt.com/tv-demain/programme-${channel.site_id}` - } else if (!date || date.isSame(now, 'day')) { - return `https://www.guidetnt.com/tv/programme-${channel.site_id}` - } else { - return null - } - }, - async parser({ content, date }) { - const programs = [] - const allItems = parseItems(content) - const items = allItems?.rows - const itemDate = allItems?.formattedDate - for (const item of items) { - const prev = programs[programs.length - 1] - const $item = cheerio.load(item) - const title = parseTitle($item) - let start = parseStart($item, itemDate) - - if (!start || !title) return - if (prev) { - if (start.isBefore(prev.start)) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } - prev.stop = start - } - let stop = start.add(30, 'm') - - let itemDetails = null - let subTitle = null - //let duration = null - let country = null - let productionDate = null - let episode = null - let season = null - let category = parseCategory($item) - let description = parseDescription($item) - const itemDetailsURL = parseDescriptionURL($item) - if (itemDetailsURL) { - const url = 'https://www.guidetnt.com' + itemDetailsURL - try { - const response = await axios.get(url) - itemDetails = parseItemDetails(response.data) - } catch (err) { - console.error(`Erreur lors du fetch des détails pour l'item: ${url}`, err) - } - - const timeRange = parseTimeRange(itemDetails?.programHour, date.format('YYYY-MM-DD')) - start = timeRange?.start - stop = timeRange?.stop - - subTitle = itemDetails?.subTitle - if (title == subTitle) subTitle = null - description = itemDetails?.description - - const categoryDetails = parseCategoryText(itemDetails?.category) - //duration = categoryDetails?.duration - country = categoryDetails?.country - productionDate = categoryDetails?.productionDate - season = categoryDetails?.season - episode = categoryDetails?.episode - } - // See https://www.npmjs.com/package/epg-parser for parameters - programs.push({ - title, - subTitle: subTitle, - description: description, - image: itemDetails?.image, - category: category, - directors: itemDetails?.directorActors?.Réalisateur, - actors: itemDetails?.directorActors?.Acteur, - country: country, - date: productionDate, - //duration: duration, // Tried with length: too, but does not work ! (stop-start is not accurate because of Ads) - season: season, - episode: episode, - start, - stop - }) - } - - return programs - }, - async channels() { - const response = await axios.get('https://www.guidetnt.com') - const channels = [] - const $ = cheerio.load(response.data) - - // Look inside each .tvlogo container - $('.tvlogo').each((i, el) => { - // Find all descendants that have an alt attribute - $(el) - .find('[alt]') - .each((j, subEl) => { - const alt = $(subEl).attr('alt') - const href = $(subEl).attr('href') - if (href && alt && alt.trim() !== '') { - const name = alt.trim() - const site_id = href.replace(/^\/tv\/programme-/, '') - channels.push({ - lang: 'fr', - name, - site_id - }) - } - }) - }) - return channels - } -} - -function parseTimeRange(timeRange, baseDate) { - // Split times - const [startStr, endStr] = timeRange.split(' - ').map(s => s.trim()) - - // Parse with base date - const start = dayjs(`${baseDate} ${startStr}`, 'YYYY-MM-DD HH:mm') - let end = dayjs(`${baseDate} ${endStr}`, 'YYYY-MM-DD HH:mm') - - // Handle possible day wrap (e.g., 23:30 - 00:15) - if (end.isBefore(start)) { - end = end.add(1, 'day') - } - - // Calculate duration in minutes - const diffMinutes = end.diff(start, 'minute') - - return { - start: start.format(), - stop: end.format(), - duration: diffMinutes - } -} - -function parseItemDetails(itemDetails) { - const $ = cheerio.load(itemDetails) - - const program = $('.program-wrapper').first() - - const programHour = program.find('.program-hour').text().trim() - const programTitle = program.find('.program-title').text().trim() - const programElementBold = program.find('.program-element-bold').text().trim() - const programArea1 = program.find('.program-element.program-area-1').text().trim() - - let description = '' - const programElements = $('.program-element').filter((i, el) => { - const classAttr = $(el).attr('class') - // Return true only if it is exactly "program-element" (no extra classes) - return classAttr.trim() === 'program-element' - }) - - programElements.each((i, el) => { - description += $(el).text().trim() - }) - - const area2Node = $('.program-area-2').first() - const area2 = $(area2Node) - const data = {} - let currentLabel = null - let texts = [] - - area2.contents().each((i, node) => { - if (node.type === 'tag' && node.name === 'strong') { - // If we had collected some text for the previous label, save it - if (currentLabel && texts.length) { - data[currentLabel] = texts.join('').trim().replace(/,\s*$/, '') // Remove trailing comma - } - // New label - get text without colon - currentLabel = $(node).text().replace(/:$/, '').trim() - texts = [] - } else if (currentLabel) { - // Append the text content (text node or others) - if (node.type === 'text') { - texts.push(node.data) - } else if (node.type === 'tag' && node.name !== 'strong' && node.name !== 'br') { - texts.push($(node).text()) - } - } - }) - - // Save last label text - if (currentLabel && texts.length) { - data[currentLabel] = texts.join('').trim().replace(/,\s*$/, '') - } - - const imgSrc = program.find('div[style*="float:left"]')?.find('img')?.attr('src') || null - - return { - programHour, - title: programTitle, - subTitle: programElementBold, - category: programArea1, - description: description, - directorActors: data, - image: imgSrc - } -} - -function parseCategoryText(text) { - if (!text) return null - - const parts = text - .split(',') - .map(s => s.trim()) - .filter(Boolean) - const len = parts.length - - const category = parts[0] || null - - if (len < 3) { - return { - category: category, - duration: null, - country: null, - productionDate: null, - season: null, - episode: null - } - } - - // Check last part: date if numeric - const dateCandidate = parts[len - 1] - const productionDate = /^\d{4}$/.test(dateCandidate) ? dateCandidate : null - - // Check for duration (first part containing "minutes") - let durationMinute = null - //let duration = null - let episode = null - let season = null - let durationIndex = -1 - for (let i = 0; i < len; i++) { - if (parts[i].toLowerCase().includes('minute')) { - durationMinute = parts[i].trim() - durationMinute = durationMinute.replace('minutes', '') - durationMinute = durationMinute.replace('minute', '') - //duration = [{ units: 'minutes', value: durationMinute }], - durationIndex = i - } else if (parts[i].toLowerCase().includes('épisode')) { - const match = text.match(/épisode\s+(\d+)(?:\/(\d+))?/i) - if (match) { - episode = parseInt(match[1], 10) - } - } else if (parts[i].toLowerCase().includes('saison')) { - season = parts[i].replace('saison', '').trim() - } - } - - // Country: second to last - const countryIndex = len - 2 - let country = durationIndex === countryIndex ? null : parts[countryIndex] - - return { - category, - durationMinute, - country, - productionDate, - season, - episode - } -} - -function parseTitle($item) { - return $item('.channel-programs-title a').text().trim() -} - -function parseDescription($item) { - return $item('#descr').text().trim() || null -} - -function parseDescriptionURL($item) { - const descrLink = $item('#descr a') - return descrLink.attr('href') || null -} - -function parseCategory($item) { - let type = null - $item('.channel-programs-title span').each((i, span) => { - const className = $item(span).attr('class') - if (className && className.startsWith('text_bg')) { - type = $item(span).text().trim() - } - }) - return type -} - -function parseStart($item, itemDate) { - const dt = $item('.channel-programs-time a').text().trim() - if (!dt) return null - - const datetimeStr = `${itemDate} ${dt}` - return dayjs.tz(datetimeStr, 'YYYY-MM-DD HH:mm', PARIS_TZ) -} - -function parseItems(content) { - const $ = cheerio.load(content) - - // Extract header information - const logoSrc = $('#logo img').attr('src') - const title = $('#title h1').text().trim() - const subtitle = $('#subtitle').text().trim() - const dateMatch = subtitle.match(/(\d{1,2} \w+ \d{4})/) - const dateStr = dateMatch ? dateMatch[1].toLowerCase() : null - - // Parse the French date string - const parsedDate = dayjs(dateStr, 'D MMMM YYYY', 'fr') - // Format it as YYYY-MM-DD - const formattedDate = parsedDate.format('YYYY-MM-DD') - - const rows = $('.channel-row').toArray() - - return { - rows, - logoSrc, - title, - formattedDate - } -} +const cheerio = require('cheerio') +const axios = require('axios') +const dayjs = require('dayjs') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +require('dayjs/locale/fr') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) +dayjs.extend(timezone) + +const PARIS_TZ = 'Europe/Paris' + +module.exports = { + site: 'guidetnt.com', + days: 2, + url({ channel, date }) { + const now = dayjs() + const demain = now.add(1, 'd') + if (date && date.isSame(demain, 'day')) { + return `https://www.guidetnt.com/tv-demain/programme-${channel.site_id}` + } else if (!date || date.isSame(now, 'day')) { + return `https://www.guidetnt.com/tv/programme-${channel.site_id}` + } else { + return null + } + }, + async parser({ content, date }) { + const programs = [] + const allItems = parseItems(content) + const items = allItems?.rows + const itemDate = allItems?.formattedDate + for (const item of items) { + const prev = programs[programs.length - 1] + const $item = cheerio.load(item) + const title = parseTitle($item) + let start = parseStart($item, itemDate) + + if (!start || !title) return + if (prev) { + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } + prev.stop = start + } + let stop = start.add(30, 'm') + + let itemDetails = null + let subTitle = null + //let duration = null + let country = null + let productionDate = null + let episode = null + let season = null + let category = parseCategory($item) + let description = parseDescription($item) + const itemDetailsURL = parseDescriptionURL($item) + if (itemDetailsURL) { + const url = 'https://www.guidetnt.com' + itemDetailsURL + try { + const response = await axios.get(url) + itemDetails = parseItemDetails(response.data) + } catch (err) { + console.error(`Erreur lors du fetch des détails pour l'item: ${url}`, err) + } + + const timeRange = parseTimeRange(itemDetails?.programHour, date.format('YYYY-MM-DD')) + start = timeRange?.start + stop = timeRange?.stop + + subTitle = itemDetails?.subTitle + if (title == subTitle) subTitle = null + description = itemDetails?.description + + const categoryDetails = parseCategoryText(itemDetails?.category) + //duration = categoryDetails?.duration + country = categoryDetails?.country + productionDate = categoryDetails?.productionDate + season = categoryDetails?.season + episode = categoryDetails?.episode + } + // See https://www.npmjs.com/package/epg-parser for parameters + programs.push({ + title, + subTitle: subTitle, + description: description, + image: itemDetails?.image, + category: category, + directors: itemDetails?.directorActors?.Réalisateur, + actors: itemDetails?.directorActors?.Acteur, + country: country, + date: productionDate, + //duration: duration, // Tried with length: too, but does not work ! (stop-start is not accurate because of Ads) + season: season, + episode: episode, + start, + stop + }) + } + + return programs + }, + async channels() { + const response = await axios.get('https://www.guidetnt.com') + const channels = [] + const $ = cheerio.load(response.data) + + // Look inside each .tvlogo container + $('.tvlogo').each((i, el) => { + // Find all descendants that have an alt attribute + $(el) + .find('[alt]') + .each((j, subEl) => { + const alt = $(subEl).attr('alt') + const href = $(subEl).attr('href') + if (href && alt && alt.trim() !== '') { + const name = alt.trim() + const site_id = href.replace(/^\/tv\/programme-/, '') + channels.push({ + lang: 'fr', + name, + site_id + }) + } + }) + }) + return channels + } +} + +function parseTimeRange(timeRange, baseDate) { + // Split times + const [startStr, endStr] = timeRange.split(' - ').map(s => s.trim()) + + // Parse with base date + const start = dayjs(`${baseDate} ${startStr}`, 'YYYY-MM-DD HH:mm') + let end = dayjs(`${baseDate} ${endStr}`, 'YYYY-MM-DD HH:mm') + + // Handle possible day wrap (e.g., 23:30 - 00:15) + if (end.isBefore(start)) { + end = end.add(1, 'day') + } + + // Calculate duration in minutes + const diffMinutes = end.diff(start, 'minute') + + return { + start: start.format(), + stop: end.format(), + duration: diffMinutes + } +} + +function parseItemDetails(itemDetails) { + const $ = cheerio.load(itemDetails) + + const program = $('.program-wrapper').first() + + const programHour = program.find('.program-hour').text().trim() + const programTitle = program.find('.program-title').text().trim() + const programElementBold = program.find('.program-element-bold').text().trim() + const programArea1 = program.find('.program-element.program-area-1').text().trim() + + let description = '' + const programElements = $('.program-element').filter((i, el) => { + const classAttr = $(el).attr('class') + // Return true only if it is exactly "program-element" (no extra classes) + return classAttr.trim() === 'program-element' + }) + + programElements.each((i, el) => { + description += $(el).text().trim() + }) + + const area2Node = $('.program-area-2').first() + const area2 = $(area2Node) + const data = {} + let currentLabel = null + let texts = [] + + area2.contents().each((i, node) => { + if (node.type === 'tag' && node.name === 'strong') { + // If we had collected some text for the previous label, save it + if (currentLabel && texts.length) { + data[currentLabel] = texts.join('').trim().replace(/,\s*$/, '') // Remove trailing comma + } + // New label - get text without colon + currentLabel = $(node).text().replace(/:$/, '').trim() + texts = [] + } else if (currentLabel) { + // Append the text content (text node or others) + if (node.type === 'text') { + texts.push(node.data) + } else if (node.type === 'tag' && node.name !== 'strong' && node.name !== 'br') { + texts.push($(node).text()) + } + } + }) + + // Save last label text + if (currentLabel && texts.length) { + data[currentLabel] = texts.join('').trim().replace(/,\s*$/, '') + } + + const imgSrc = program.find('div[style*="float:left"]')?.find('img')?.attr('src') || null + + return { + programHour, + title: programTitle, + subTitle: programElementBold, + category: programArea1, + description: description, + directorActors: data, + image: imgSrc + } +} + +function parseCategoryText(text) { + if (!text) return null + + const parts = text + .split(',') + .map(s => s.trim()) + .filter(Boolean) + const len = parts.length + + const category = parts[0] || null + + if (len < 3) { + return { + category: category, + duration: null, + country: null, + productionDate: null, + season: null, + episode: null + } + } + + // Check last part: date if numeric + const dateCandidate = parts[len - 1] + const productionDate = /^\d{4}$/.test(dateCandidate) ? dateCandidate : null + + // Check for duration (first part containing "minutes") + let durationMinute = null + //let duration = null + let episode = null + let season = null + let durationIndex = -1 + for (let i = 0; i < len; i++) { + if (parts[i].toLowerCase().includes('minute')) { + durationMinute = parts[i].trim() + durationMinute = durationMinute.replace('minutes', '') + durationMinute = durationMinute.replace('minute', '') + //duration = [{ units: 'minutes', value: durationMinute }], + durationIndex = i + } else if (parts[i].toLowerCase().includes('épisode')) { + const match = text.match(/épisode\s+(\d+)(?:\/(\d+))?/i) + if (match) { + episode = parseInt(match[1], 10) + } + } else if (parts[i].toLowerCase().includes('saison')) { + season = parts[i].replace('saison', '').trim() + } + } + + // Country: second to last + const countryIndex = len - 2 + let country = durationIndex === countryIndex ? null : parts[countryIndex] + + return { + category, + durationMinute, + country, + productionDate, + season, + episode + } +} + +function parseTitle($item) { + return $item('.channel-programs-title a').text().trim() +} + +function parseDescription($item) { + return $item('#descr').text().trim() || null +} + +function parseDescriptionURL($item) { + const descrLink = $item('#descr a') + return descrLink.attr('href') || null +} + +function parseCategory($item) { + let type = null + $item('.channel-programs-title span').each((i, span) => { + const className = $item(span).attr('class') + if (className && className.startsWith('text_bg')) { + type = $item(span).text().trim() + } + }) + return type +} + +function parseStart($item, itemDate) { + const dt = $item('.channel-programs-time a').text().trim() + if (!dt) return null + + const datetimeStr = `${itemDate} ${dt}` + return dayjs.tz(datetimeStr, 'YYYY-MM-DD HH:mm', PARIS_TZ) +} + +function parseItems(content) { + const $ = cheerio.load(content) + + // Extract header information + const logoSrc = $('#logo img').attr('src') + const title = $('#title h1').text().trim() + const subtitle = $('#subtitle').text().trim() + const dateMatch = subtitle.match(/(\d{1,2} \w+ \d{4})/) + const dateStr = dateMatch ? dateMatch[1].toLowerCase() : null + + // Parse the French date string + const parsedDate = dayjs(dateStr, 'D MMMM YYYY', 'fr') + // Format it as YYYY-MM-DD + const formattedDate = parsedDate.format('YYYY-MM-DD') + + const rows = $('.channel-row').toArray() + + return { + rows, + logoSrc, + title, + formattedDate + } +} diff --git a/sites/guidetnt.com/guidetnt.com.test.js b/sites/guidetnt.com/guidetnt.com.test.js index 299f19cf0..9b83b7d03 100644 --- a/sites/guidetnt.com/guidetnt.com.test.js +++ b/sites/guidetnt.com/guidetnt.com.test.js @@ -1,85 +1,85 @@ -const { parser, url } = require('./guidetnt.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -const timezone = require('dayjs/plugin/timezone') -require('dayjs/locale/fr') -dayjs.extend(customParseFormat) -dayjs.extend(utc) -dayjs.extend(timezone) - -const date = dayjs.utc('2025-07-01', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'tf1', - xmltv_id: 'TF1.fr' -} - -it('can generate valid url', () => { - expect(url({ channel })).toBe('https://www.guidetnt.com/tv/programme-tf1') -}) - -it('can parse response', async () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - let results = await parser({ content, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(29) - expect(results[0]).toMatchObject({ - category: 'Série', - description: - "Grande effervescence pour toute l'équipe du Camping Paradis, qui prépare les Olympiades. Côté arrivants, Hélène et sa fille Eva viennent passer quelques jours dans le but d'optimiser les révisions d'E...", - start: '2025-06-30T22:55:00.000Z', - stop: '2025-06-30T23:45:00.000Z', - title: 'Camping Paradis' - }) - expect(results[2]).toMatchObject({ - category: 'Magazine', - description: 'Retrouvez tous vos programmes de nuit.', - start: '2025-07-01T00:55:00.000Z', - stop: '2025-07-01T04:00:00.000Z', - title: 'Programmes de la nuit' - }) - expect(results[15]).toMatchObject({ - category: 'Téléfilm', - description: - "La vie quasi parfaite de Riley bascule brutalement lorsqu'un accident de voiture lui coûte la vie, laissant derrière elle sa famille. Alors que l'enquête débute, l'affaire prend une tournure étrange l...", - start: '2025-07-01T12:25:00.000Z', - stop: '2025-07-01T14:00:00.000Z', - title: "Trahie par l'amour" - }) -}) - -it('can parse response for current day', async () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - let results = await parser({ content, date: dayjs.utc('2025-07-01', 'YYYY-MM-DD').startOf('d') }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(29) - expect(results[0]).toMatchObject({ - category: 'Série', - description: - "Grande effervescence pour toute l'équipe du Camping Paradis, qui prépare les Olympiades. Côté arrivants, Hélène et sa fille Eva viennent passer quelques jours dans le but d'optimiser les révisions d'E...", - start: '2025-06-30T22:55:00.000Z', - stop: '2025-06-30T23:45:00.000Z', - title: 'Camping Paradis' - }) -}) - -it('can handle empty guide', async () => { - const results = await parser({ - date, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) - }) - - expect(results).toEqual([]) -}) +const { parser, url } = require('./guidetnt.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const timezone = require('dayjs/plugin/timezone') +require('dayjs/locale/fr') +dayjs.extend(customParseFormat) +dayjs.extend(utc) +dayjs.extend(timezone) + +const date = dayjs.utc('2025-07-01', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'tf1', + xmltv_id: 'TF1.fr' +} + +it('can generate valid url', () => { + expect(url({ channel })).toBe('https://www.guidetnt.com/tv/programme-tf1') +}) + +it('can parse response', async () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + let results = await parser({ content, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(29) + expect(results[0]).toMatchObject({ + category: 'Série', + description: + "Grande effervescence pour toute l'équipe du Camping Paradis, qui prépare les Olympiades. Côté arrivants, Hélène et sa fille Eva viennent passer quelques jours dans le but d'optimiser les révisions d'E...", + start: '2025-06-30T22:55:00.000Z', + stop: '2025-06-30T23:45:00.000Z', + title: 'Camping Paradis' + }) + expect(results[2]).toMatchObject({ + category: 'Magazine', + description: 'Retrouvez tous vos programmes de nuit.', + start: '2025-07-01T00:55:00.000Z', + stop: '2025-07-01T04:00:00.000Z', + title: 'Programmes de la nuit' + }) + expect(results[15]).toMatchObject({ + category: 'Téléfilm', + description: + "La vie quasi parfaite de Riley bascule brutalement lorsqu'un accident de voiture lui coûte la vie, laissant derrière elle sa famille. Alors que l'enquête débute, l'affaire prend une tournure étrange l...", + start: '2025-07-01T12:25:00.000Z', + stop: '2025-07-01T14:00:00.000Z', + title: "Trahie par l'amour" + }) +}) + +it('can parse response for current day', async () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + let results = await parser({ content, date: dayjs.utc('2025-07-01', 'YYYY-MM-DD').startOf('d') }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(29) + expect(results[0]).toMatchObject({ + category: 'Série', + description: + "Grande effervescence pour toute l'équipe du Camping Paradis, qui prépare les Olympiades. Côté arrivants, Hélène et sa fille Eva viennent passer quelques jours dans le but d'optimiser les révisions d'E...", + start: '2025-06-30T22:55:00.000Z', + stop: '2025-06-30T23:45:00.000Z', + title: 'Camping Paradis' + }) +}) + +it('can handle empty guide', async () => { + const results = await parser({ + date, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) + }) + + expect(results).toEqual([]) +}) diff --git a/sites/horizon.tv/horizon.tv.test.js b/sites/horizon.tv/horizon.tv.test.js index 923ea43a1..3d27a296b 100644 --- a/sites/horizon.tv/horizon.tv.test.js +++ b/sites/horizon.tv/horizon.tv.test.js @@ -1,246 +1,246 @@ -const { parser, url } = require('./horizon.tv.config.js') -const fs = require('fs') -const path = require('path') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -const date = dayjs.utc('2023-02-07', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '10024', - xmltv_id: 'AMCCzechRepublic.cz' -} - -it('can generate valid url', () => { - expect(url({ date, channel })).toBe( - 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/programschedules/20230207/1' - ) -}) - -it('can parse response', done => { - const content = JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8')) - - axios.get.mockImplementation(url => { - if ( - url === 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/programschedules/20230207/2' - ) { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/content_1.json'), 'utf8')) - }) - } else if ( - url === 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/programschedules/20230207/3' - ) { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/content_2.json'), 'utf8')) - }) - } else if ( - url === 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/programschedules/20230207/4' - ) { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/content_3.json'), 'utf8')) - }) - } else if ( - url === 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/listings/crid:~~2F~~2Fport.cs~~2F122941980,imi:7ca159c917344e0dd3fbe1cd8db5ff8043d96a78' - ) { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/content_listings_1.json'), 'utf8')) - }) - } else if ( - url === 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/listings/crid:~~2F~~2Fport.cs~~2F248281986,imi:e85129f9d1e211406a521df7a36f22237c22651b' - ) { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/content_listings_2.json'), 'utf8')) - }) - } else if ( - url === 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/listings/crid:~~2F~~2Fport.cs~~2F1379541,imi:5f806a2a0bc13e9745e14907a27116c60ea2c6ad' - ) { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/content_listings_3.json'), 'utf8')) - }) - } else if ( - url === 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/listings/crid:~~2F~~2Fport.cs~~2F71927954,imi:f1b4b0285b72cf44cba74e1c62322a4c682385c7' - ) { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/content_listings_4.json'), 'utf8')) - }) - } else { - return Promise.resolve({ data: '' }) - } - }) - - parser({ content, channel, date }) - .then(result => { - result = result.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2023-02-06T21:35:00.000Z', - stop: '2023-02-06T23:05:00.000Z', - title: 'Avengement', - description: - 'Během propustky z vězení za účelem návštěvy umírající matky v nemocnici zločinec Cain Burgess (Scott Adkins) unikne svým dozorcům a mizí v ulicích Londýna. Jde o epickou cestu krve a bolesti za dosažením vytoužené pomsty na těch, kteří z něj udělali chladnokrevného vraha.', - category: ['Drama', 'Akcia'], - directors: ['Jesse V. Johnson'], - actors: [ - 'Scott Adkins', - 'Craig Fairbrass', - 'Thomas Turgoose', - 'Nick Moran', - 'Kierston Wareing', - 'Leo Gregory', - 'Mark Strange', - 'Luke LaFontaine', - 'Beau Fowler', - 'Dan Styles', - 'Christopher Sciueref', - 'Matt Routledge', - 'Jane Thorne', - 'Louis Mandylor', - 'Terence Maynard', - 'Greg Burridge', - 'Michael Higgs', - 'Damian Gallagher', - 'Daniel Adegboyega', - 'John Ioannou', - 'Sofie Golding-Spittle', - 'Joe Egan', - 'Darren Swain', - 'Lee Charles', - 'Dominic Kinnaird', - "Ross O'Hennessy", - 'Teresa Mahoney', - 'Andrew Dunkelberger', - 'Sam Hardy', - 'Ivan Moy', - 'Mark Sears', - 'Phillip Ray Tommy' - ], - date: '2019' - }, - { - start: '2023-02-07T04:35:00.000Z', - stop: '2023-02-07T05:00:00.000Z', - title: 'Zoom In', - description: 'Film/Kino', - category: ['Hudba a umenie', 'Film'], - date: '2010' - }, - { - start: '2023-02-07T09:10:00.000Z', - stop: '2023-02-07T11:00:00.000Z', - title: 'Studentka', - description: - 'Ambiciózní vysokoškolačka Valentina (Sophie Marceau) studuje literaturu na pařížské Sorbonně a právě se připravuje k závěrečným zkouškám. Žádný odpočinek, žádné volno, žádné večírky, téměř žádný spánek a především a hlavně ... žádná láska! Věří, že jedině tak obstojí před zkušební komisí. Jednoho dne se však odehraje něco, s čím nepočítala. Potká charismatického hudebníka Neda - a bláznivě se zamiluje. V tuto chvíli stojí před osudovým rozhodnutím: zahodí roky obrovského studijního nasazení, nebo odmítne lásku? Nebo se snad dá obojí skloubit dohromady?', - category: ['Film', 'Komédia'], - actors: [ - 'Sophie Marceauová', - 'Vincent Lindon', - 'Elisabeth Vitali', - 'Elena Pompei', - 'Jean-Claude Leguay', - 'Brigitte Chamarande', - 'Christian Pereira', - 'Gérard Dacier', - 'Roberto Attias', - 'Beppe Chierici', - 'Nathalie Mann', - 'Anne Macina', - 'Janine Souchon', - 'Virginie Demians', - 'Hugues Leforestier', - 'Jacqueline Noëlle', - 'Marc-André Brunet', - 'Isabelle Caubère', - 'André Chazel', - 'Med Salah Cheurfi', - 'Guillaume Corea', - 'Eric Denize', - 'Gilles Gaston-Dreyfuss', - 'Benoît Gourley', - 'Marc Innocenti', - 'Najim Laouriga', - 'Laurent Ledermann', - 'Philippe Maygal', - 'Dominique Pifarely', - 'Ysé Tran' - ], - directors: ['Francis De Gueltz', 'Dominique Talmon', 'Claude Pinoteau'], - date: '1988' - }, - { - start: '2023-02-07T16:05:00.000Z', - stop: '2023-02-07T17:45:00.000Z', - title: 'Zilionáři', - description: - 'David (Zach Galifianakis) je nekomplikovaný muž, který uvízl v monotónním životě. Den co den usedá za volant svého obrněného automobilu, aby odvážel obrovské sumy peněz jiných lidí. Jediným vzrušujícím momentem v jeho životě je flirtování s kolegyní Kelly (Kristen Wiig), která ho však brzy zatáhne do těžko uvěřitelného dobrodružství. Skupinka nepříliš inteligentních loserů, pod vedením Steva (Owen Wilson), plánuje vyloupit banku a David jim v tom má samozřejmě pomoci. Navzdory absolutně amatérskému plánu se ale stane nemožné a oni mají najednou v kapse 17 miliónů dolarů. A protože tato partička je opravdu bláznivá, začne je hned ve velkém roztáčet. Peníze létají vzduchem za luxusní a kolikrát i zbytečné věci, ale nedochází jim, že pro policii tak zanechávají jasné stopy...', - category: ['Drama', 'Akcia'], - actors: [ - 'Zach Galifianakis', - 'Kristen Wiigová', - 'Owen Wilson', - 'Kate McKinnon', - 'Leslie Jones', - 'Jason Sudeikis', - 'Ross Kimball', - 'Devin Ratray', - 'Mary Elizabeth Ellisová', - 'Jon Daly', - 'Ken Marino', - 'Daniel Zacapa', - 'Tom Werme', - 'Njema Williams', - 'Nils Cruz', - 'Michael Fraguada', - 'Christian Gonzalez', - 'Candace Blanchard', - 'Karsten Friske', - 'Dallas Edwards', - 'Barry Ratcliffe', - 'Shelton Grant', - 'Laura Palka', - 'Reegus Flenory', - 'Wynn Reichert', - 'Jill Jane Clements', - 'Joseph S. Wilson', - 'Jee An', - 'Rhoda Griffisová', - 'Nicole Dupre Sobchack' - ], - directors: [ - 'Scott August', - 'Richard L. Fox', - 'Michelle Malley-Campos', - 'Sebastian Mazzola', - 'Steven Ritzi', - 'Pete Waterman', - 'Jared Hess' - ], - date: '2016' - } - ]) - done() - }) - .catch(done) -}) - -it('can handle empty guide', done => { - parser({ - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')), - channel, - date - }) - .then(result => { - expect(result).toMatchObject([]) - done() - }) - .catch(done) -}) +const { parser, url } = require('./horizon.tv.config.js') +const fs = require('fs') +const path = require('path') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.utc('2023-02-07', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '10024', + xmltv_id: 'AMCCzechRepublic.cz' +} + +it('can generate valid url', () => { + expect(url({ date, channel })).toBe( + 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/programschedules/20230207/1' + ) +}) + +it('can parse response', done => { + const content = JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8')) + + axios.get.mockImplementation(url => { + if ( + url === 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/programschedules/20230207/2' + ) { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/content_1.json'), 'utf8')) + }) + } else if ( + url === 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/programschedules/20230207/3' + ) { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/content_2.json'), 'utf8')) + }) + } else if ( + url === 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/programschedules/20230207/4' + ) { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/content_3.json'), 'utf8')) + }) + } else if ( + url === 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/listings/crid:~~2F~~2Fport.cs~~2F122941980,imi:7ca159c917344e0dd3fbe1cd8db5ff8043d96a78' + ) { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/content_listings_1.json'), 'utf8')) + }) + } else if ( + url === 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/listings/crid:~~2F~~2Fport.cs~~2F248281986,imi:e85129f9d1e211406a521df7a36f22237c22651b' + ) { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/content_listings_2.json'), 'utf8')) + }) + } else if ( + url === 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/listings/crid:~~2F~~2Fport.cs~~2F1379541,imi:5f806a2a0bc13e9745e14907a27116c60ea2c6ad' + ) { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/content_listings_3.json'), 'utf8')) + }) + } else if ( + url === 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web/listings/crid:~~2F~~2Fport.cs~~2F71927954,imi:f1b4b0285b72cf44cba74e1c62322a4c682385c7' + ) { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/content_listings_4.json'), 'utf8')) + }) + } else { + return Promise.resolve({ data: '' }) + } + }) + + parser({ content, channel, date }) + .then(result => { + result = result.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2023-02-06T21:35:00.000Z', + stop: '2023-02-06T23:05:00.000Z', + title: 'Avengement', + description: + 'Během propustky z vězení za účelem návštěvy umírající matky v nemocnici zločinec Cain Burgess (Scott Adkins) unikne svým dozorcům a mizí v ulicích Londýna. Jde o epickou cestu krve a bolesti za dosažením vytoužené pomsty na těch, kteří z něj udělali chladnokrevného vraha.', + category: ['Drama', 'Akcia'], + directors: ['Jesse V. Johnson'], + actors: [ + 'Scott Adkins', + 'Craig Fairbrass', + 'Thomas Turgoose', + 'Nick Moran', + 'Kierston Wareing', + 'Leo Gregory', + 'Mark Strange', + 'Luke LaFontaine', + 'Beau Fowler', + 'Dan Styles', + 'Christopher Sciueref', + 'Matt Routledge', + 'Jane Thorne', + 'Louis Mandylor', + 'Terence Maynard', + 'Greg Burridge', + 'Michael Higgs', + 'Damian Gallagher', + 'Daniel Adegboyega', + 'John Ioannou', + 'Sofie Golding-Spittle', + 'Joe Egan', + 'Darren Swain', + 'Lee Charles', + 'Dominic Kinnaird', + "Ross O'Hennessy", + 'Teresa Mahoney', + 'Andrew Dunkelberger', + 'Sam Hardy', + 'Ivan Moy', + 'Mark Sears', + 'Phillip Ray Tommy' + ], + date: '2019' + }, + { + start: '2023-02-07T04:35:00.000Z', + stop: '2023-02-07T05:00:00.000Z', + title: 'Zoom In', + description: 'Film/Kino', + category: ['Hudba a umenie', 'Film'], + date: '2010' + }, + { + start: '2023-02-07T09:10:00.000Z', + stop: '2023-02-07T11:00:00.000Z', + title: 'Studentka', + description: + 'Ambiciózní vysokoškolačka Valentina (Sophie Marceau) studuje literaturu na pařížské Sorbonně a právě se připravuje k závěrečným zkouškám. Žádný odpočinek, žádné volno, žádné večírky, téměř žádný spánek a především a hlavně ... žádná láska! Věří, že jedině tak obstojí před zkušební komisí. Jednoho dne se však odehraje něco, s čím nepočítala. Potká charismatického hudebníka Neda - a bláznivě se zamiluje. V tuto chvíli stojí před osudovým rozhodnutím: zahodí roky obrovského studijního nasazení, nebo odmítne lásku? Nebo se snad dá obojí skloubit dohromady?', + category: ['Film', 'Komédia'], + actors: [ + 'Sophie Marceauová', + 'Vincent Lindon', + 'Elisabeth Vitali', + 'Elena Pompei', + 'Jean-Claude Leguay', + 'Brigitte Chamarande', + 'Christian Pereira', + 'Gérard Dacier', + 'Roberto Attias', + 'Beppe Chierici', + 'Nathalie Mann', + 'Anne Macina', + 'Janine Souchon', + 'Virginie Demians', + 'Hugues Leforestier', + 'Jacqueline Noëlle', + 'Marc-André Brunet', + 'Isabelle Caubère', + 'André Chazel', + 'Med Salah Cheurfi', + 'Guillaume Corea', + 'Eric Denize', + 'Gilles Gaston-Dreyfuss', + 'Benoît Gourley', + 'Marc Innocenti', + 'Najim Laouriga', + 'Laurent Ledermann', + 'Philippe Maygal', + 'Dominique Pifarely', + 'Ysé Tran' + ], + directors: ['Francis De Gueltz', 'Dominique Talmon', 'Claude Pinoteau'], + date: '1988' + }, + { + start: '2023-02-07T16:05:00.000Z', + stop: '2023-02-07T17:45:00.000Z', + title: 'Zilionáři', + description: + 'David (Zach Galifianakis) je nekomplikovaný muž, který uvízl v monotónním životě. Den co den usedá za volant svého obrněného automobilu, aby odvážel obrovské sumy peněz jiných lidí. Jediným vzrušujícím momentem v jeho životě je flirtování s kolegyní Kelly (Kristen Wiig), která ho však brzy zatáhne do těžko uvěřitelného dobrodružství. Skupinka nepříliš inteligentních loserů, pod vedením Steva (Owen Wilson), plánuje vyloupit banku a David jim v tom má samozřejmě pomoci. Navzdory absolutně amatérskému plánu se ale stane nemožné a oni mají najednou v kapse 17 miliónů dolarů. A protože tato partička je opravdu bláznivá, začne je hned ve velkém roztáčet. Peníze létají vzduchem za luxusní a kolikrát i zbytečné věci, ale nedochází jim, že pro policii tak zanechávají jasné stopy...', + category: ['Drama', 'Akcia'], + actors: [ + 'Zach Galifianakis', + 'Kristen Wiigová', + 'Owen Wilson', + 'Kate McKinnon', + 'Leslie Jones', + 'Jason Sudeikis', + 'Ross Kimball', + 'Devin Ratray', + 'Mary Elizabeth Ellisová', + 'Jon Daly', + 'Ken Marino', + 'Daniel Zacapa', + 'Tom Werme', + 'Njema Williams', + 'Nils Cruz', + 'Michael Fraguada', + 'Christian Gonzalez', + 'Candace Blanchard', + 'Karsten Friske', + 'Dallas Edwards', + 'Barry Ratcliffe', + 'Shelton Grant', + 'Laura Palka', + 'Reegus Flenory', + 'Wynn Reichert', + 'Jill Jane Clements', + 'Joseph S. Wilson', + 'Jee An', + 'Rhoda Griffisová', + 'Nicole Dupre Sobchack' + ], + directors: [ + 'Scott August', + 'Richard L. Fox', + 'Michelle Malley-Campos', + 'Sebastian Mazzola', + 'Steven Ritzi', + 'Pete Waterman', + 'Jared Hess' + ], + date: '2016' + } + ]) + done() + }) + .catch(done) +}) + +it('can handle empty guide', done => { + parser({ + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')), + channel, + date + }) + .then(result => { + expect(result).toMatchObject([]) + done() + }) + .catch(done) +}) diff --git a/sites/hoy.tv/hoy.tv.test.js b/sites/hoy.tv/hoy.tv.test.js index 543162b23..ea0b2569d 100644 --- a/sites/hoy.tv/hoy.tv.test.js +++ b/sites/hoy.tv/hoy.tv.test.js @@ -1,46 +1,46 @@ -const { parser, url } = require('./hoy.tv.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') - -dayjs.extend(utc) - -jest.mock('axios') - -const date = dayjs.utc('2024-09-13', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '76', - xmltv_id: 'HOYIBC.hk', - lang: 'zh' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe('https://epg-file.hoy.tv/hoy/OTT7620240913.xml') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.xml'), 'utf8') - - const result = parser({ content, channel, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2024-09-13T03:30:00.000Z', - stop: '2024-09-13T04:30:00.000Z', - title: '點講都係一家人[PG]', - sub_title: '第46集' - }, - { - start: '2024-09-13T04:30:00.000Z', - stop: '2024-09-13T05:30:00.000Z', - title: '麝香之路', - description: - 'Ep. 2 .The Secret of disappeared kingdom.shows the mysterious disappearance of the ancient Tibetan kingdom which gained world' - } - ]) -}) +const { parser, url } = require('./hoy.tv.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') + +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.utc('2024-09-13', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '76', + xmltv_id: 'HOYIBC.hk', + lang: 'zh' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe('https://epg-file.hoy.tv/hoy/OTT7620240913.xml') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.xml'), 'utf8') + + const result = parser({ content, channel, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2024-09-13T03:30:00.000Z', + stop: '2024-09-13T04:30:00.000Z', + title: '點講都係一家人[PG]', + sub_title: '第46集' + }, + { + start: '2024-09-13T04:30:00.000Z', + stop: '2024-09-13T05:30:00.000Z', + title: '麝香之路', + description: + 'Ep. 2 .The Secret of disappeared kingdom.shows the mysterious disappearance of the ancient Tibetan kingdom which gained world' + } + ]) +}) diff --git a/sites/i24news.tv/i24news.tv.test.js b/sites/i24news.tv/i24news.tv.test.js index 6b8cd8692..2e48ebbda 100644 --- a/sites/i24news.tv/i24news.tv.test.js +++ b/sites/i24news.tv/i24news.tv.test.js @@ -1,46 +1,46 @@ -const { parser, url } = require('./i24news.tv.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-03-06', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'ar', - xmltv_id: 'I24NewsArabic.il' -} - -it('can generate valid url', () => { - expect(url({ channel })).toBe('https://api.i24news.tv/v2/ar/schedules') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ content, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2022-03-06T13:00:00.000Z', - stop: '2022-03-06T13:28:00.000Z', - title: 'تغطية خاصة', - description: 'Special Edition', - image: - 'https://cdn.i24news.tv/uploads/a1/be/85/20/69/6f/32/1c/ed/b0/f8/5c/f6/1c/40/f9/a1be8520696f321cedb0f85cf61c40f9.png' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: '[]', - date - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./i24news.tv.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-03-06', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'ar', + xmltv_id: 'I24NewsArabic.il' +} + +it('can generate valid url', () => { + expect(url({ channel })).toBe('https://api.i24news.tv/v2/ar/schedules') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ content, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2022-03-06T13:00:00.000Z', + stop: '2022-03-06T13:28:00.000Z', + title: 'تغطية خاصة', + description: 'Special Edition', + image: + 'https://cdn.i24news.tv/uploads/a1/be/85/20/69/6f/32/1c/ed/b0/f8/5c/f6/1c/40/f9/a1be8520696f321cedb0f85cf61c40f9.png' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: '[]', + date + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/indihometv.com/indihometv.com.test.js b/sites/indihometv.com/indihometv.com.test.js index d84c0c24e..7ff6a62d4 100644 --- a/sites/indihometv.com/indihometv.com.test.js +++ b/sites/indihometv.com/indihometv.com.test.js @@ -1,57 +1,57 @@ -const { parser, url } = require('./indihometv.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -dayjs.extend(utc) - -const date = dayjs.utc('2022-08-08').startOf('d') -const channel = { - site_id: 'metrotv', - xmltv_id: 'MetroTV.id' -} - -it('can generate valid url', () => { - expect(url({ channel })).toBe('https://www.indihometv.com/livetv/metrotv') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') - const result = parser({ content, channel, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - title: 'Headline News', - start: '2022-08-08T00:00:00.000Z', - stop: '2022-08-08T00:05:00.000Z' - }, - { - title: 'Editorial Media Indonesia', - start: '2022-08-08T00:05:00.000Z', - stop: '2022-08-08T00:30:00.000Z' - }, - { - title: 'Editorial Media Indonesia', - start: '2022-08-08T00:30:00.000Z', - stop: '2022-08-08T00:45:00.000Z' - }, - { - title: 'Editorial Media Indonesia', - start: '2022-08-08T00:45:00.000Z', - stop: '2022-08-08T01:00:00.000Z' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./indihometv.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +dayjs.extend(utc) + +const date = dayjs.utc('2022-08-08').startOf('d') +const channel = { + site_id: 'metrotv', + xmltv_id: 'MetroTV.id' +} + +it('can generate valid url', () => { + expect(url({ channel })).toBe('https://www.indihometv.com/livetv/metrotv') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') + const result = parser({ content, channel, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + title: 'Headline News', + start: '2022-08-08T00:00:00.000Z', + stop: '2022-08-08T00:05:00.000Z' + }, + { + title: 'Editorial Media Indonesia', + start: '2022-08-08T00:05:00.000Z', + stop: '2022-08-08T00:30:00.000Z' + }, + { + title: 'Editorial Media Indonesia', + start: '2022-08-08T00:30:00.000Z', + stop: '2022-08-08T00:45:00.000Z' + }, + { + title: 'Editorial Media Indonesia', + start: '2022-08-08T00:45:00.000Z', + stop: '2022-08-08T01:00:00.000Z' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/ipko.tv/ipko.tv.test.js b/sites/ipko.tv/ipko.tv.test.js index 238aca7f5..a7e456f06 100644 --- a/sites/ipko.tv/ipko.tv.test.js +++ b/sites/ipko.tv/ipko.tv.test.js @@ -1,54 +1,54 @@ -const { parser, url } = require('./ipko.tv.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2024-12-24', 'YYYY-MM-DD').startOf('day') -const channel = { - site_id: 'ipko-promo', - xmltv_id: 'IPKOPROMO' -} - -it('can generate valid url', () => { - expect(url({ date, channel })).toBe('https://stargate.ipko.tv/api/titan.tv.WebEpg/GetWebEpgData') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ content, channel }) - - expect(result).toMatchObject([ - { - title: 'IPKO Promo', - description: 'No description available', - start: '2024-12-24T04:00:00.000Z', - stop: '2024-12-24T06:00:00.000Z', - thumbnail: 'https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg' - }, - { - title: 'IPKO Promo', - description: 'No description available', - start: '2024-12-24T06:00:00.000Z', - stop: '2024-12-24T08:00:00.000Z', - thumbnail: 'https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg' - }, - { - title: 'IPKO Promo', - description: 'No description available', - start: '2024-12-24T08:00:00.000Z', - stop: '2024-12-24T10:00:00.000Z', - thumbnail: 'https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./ipko.tv.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2024-12-24', 'YYYY-MM-DD').startOf('day') +const channel = { + site_id: 'ipko-promo', + xmltv_id: 'IPKOPROMO' +} + +it('can generate valid url', () => { + expect(url({ date, channel })).toBe('https://stargate.ipko.tv/api/titan.tv.WebEpg/GetWebEpgData') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ content, channel }) + + expect(result).toMatchObject([ + { + title: 'IPKO Promo', + description: 'No description available', + start: '2024-12-24T04:00:00.000Z', + stop: '2024-12-24T06:00:00.000Z', + thumbnail: 'https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg' + }, + { + title: 'IPKO Promo', + description: 'No description available', + start: '2024-12-24T06:00:00.000Z', + stop: '2024-12-24T08:00:00.000Z', + thumbnail: 'https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg' + }, + { + title: 'IPKO Promo', + description: 'No description available', + start: '2024-12-24T08:00:00.000Z', + stop: '2024-12-24T10:00:00.000Z', + thumbnail: 'https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/kan.org.il/kan.org.il.test.js b/sites/kan.org.il/kan.org.il.test.js index f20c02093..8b7c270f1 100644 --- a/sites/kan.org.il/kan.org.il.test.js +++ b/sites/kan.org.il/kan.org.il.test.js @@ -1,47 +1,47 @@ -const { parser, url } = require('./kan.org.il.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-03-06', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '19', - xmltv_id: 'KANEducational.il' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://www.kan.org.il/tv-guide/tv_guidePrograms.ashx?stationID=19&day=06/03/2022' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2022-03-05T22:05:37.000Z', - stop: '2022-03-05T22:27:12.000Z', - title: 'ארץ מולדת - בין תורכיה לבריטניה', - description: - "קבוצת תלמידים מתארגנת בפרוץ מלחמת העולם הראשונה להגיש עזרה לישוב. באמצעות התלמידים לומד הצופה על בעיותיו של הישוב בתקופת המלחמה, והתלבטותו בין נאמנות לשלטון העות'מאני לבין תקוותיו מהבריטים הכובשים.", - image: 'https://kanweb.blob.core.windows.net/download/pictures/2021/1/20/imgid=45847_Z.jpeg' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: '[]' - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./kan.org.il.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-03-06', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '19', + xmltv_id: 'KANEducational.il' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://www.kan.org.il/tv-guide/tv_guidePrograms.ashx?stationID=19&day=06/03/2022' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2022-03-05T22:05:37.000Z', + stop: '2022-03-05T22:27:12.000Z', + title: 'ארץ מולדת - בין תורכיה לבריטניה', + description: + "קבוצת תלמידים מתארגנת בפרוץ מלחמת העולם הראשונה להגיש עזרה לישוב. באמצעות התלמידים לומד הצופה על בעיותיו של הישוב בתקופת המלחמה, והתלבטותו בין נאמנות לשלטון העות'מאני לבין תקוותיו מהבריטים הכובשים.", + image: 'https://kanweb.blob.core.windows.net/download/pictures/2021/1/20/imgid=45847_Z.jpeg' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: '[]' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/magticom.ge/magticom.ge.test.js b/sites/magticom.ge/magticom.ge.test.js index 5c857e3e8..ce78191ab 100644 --- a/sites/magticom.ge/magticom.ge.test.js +++ b/sites/magticom.ge/magticom.ge.test.js @@ -1,64 +1,64 @@ -const { parser, url, request } = require('./magticom.ge.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2021-11-22', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '260', - xmltv_id: 'BollywoodHDRussia.ru' -} - -it('can generate valid url', () => { - expect(url).toBe('https://www.magticom.ge/request/channel-program.php') -}) - -it('can generate valid request method', () => { - expect(request.method).toBe('POST') -}) - -it('can generate valid request headers', () => { - expect(request.headers).toMatchObject({ - 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', - Referer: 'https://www.magticom.ge/en/tv/tv-services/tv-guide' - }) -}) - -it('can generate valid request data', () => { - const result = request.data({ channel, date }) - expect(result.has('channelId')).toBe(true) - expect(result.has('start')).toBe(true) - expect(result.has('end')).toBe(true) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2021-11-22T03:00:00.000Z', - stop: '2021-11-22T05:00:00.000Z', - title: 'Х/ф "Неравный брак".', - description: - 'Гуджаратец Хасмукх Пател поссорился с новым соседом Гугги Тандоном. Но им приходится помириться, когда их дети влюбляются друг в друга. Режиссер: Санджай Чхел. Актеры: Риши Капур, Пареш Равал, Вир Дас. 2017 год.' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: '[]' - }) - expect(result).toMatchObject([]) -}) +const { parser, url, request } = require('./magticom.ge.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2021-11-22', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '260', + xmltv_id: 'BollywoodHDRussia.ru' +} + +it('can generate valid url', () => { + expect(url).toBe('https://www.magticom.ge/request/channel-program.php') +}) + +it('can generate valid request method', () => { + expect(request.method).toBe('POST') +}) + +it('can generate valid request headers', () => { + expect(request.headers).toMatchObject({ + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + Referer: 'https://www.magticom.ge/en/tv/tv-services/tv-guide' + }) +}) + +it('can generate valid request data', () => { + const result = request.data({ channel, date }) + expect(result.has('channelId')).toBe(true) + expect(result.has('start')).toBe(true) + expect(result.has('end')).toBe(true) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2021-11-22T03:00:00.000Z', + stop: '2021-11-22T05:00:00.000Z', + title: 'Х/ф "Неравный брак".', + description: + 'Гуджаратец Хасмукх Пател поссорился с новым соседом Гугги Тандоном. Но им приходится помириться, когда их дети влюбляются друг в друга. Режиссер: Санджай Чхел. Актеры: Риши Капур, Пареш Равал, Вир Дас. 2017 год.' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: '[]' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/mako.co.il/mako.co.il.test.js b/sites/mako.co.il/mako.co.il.test.js index 06be75366..bba70e6af 100644 --- a/sites/mako.co.il/mako.co.il.test.js +++ b/sites/mako.co.il/mako.co.il.test.js @@ -1,41 +1,41 @@ -const { parser, url } = require('./mako.co.il.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-03-07', 'YYYY-MM-DD').startOf('d') - -it('can generate valid url', () => { - expect(url).toBe('https://www.mako.co.il/AjaxPage?jspName=EPGResponse.jsp') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ content, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2022-03-07T00:38:00.000Z', - stop: '2022-03-07T00:39:00.000Z', - title: 'רוקדים עם כוכבים - בר זומר', - description: 'מהדורת החדשות המרכזית של הבוקר, האנשים הפרשנויות והכותרות שיעשו את היום.', - image: 'https://img.mako.co.il/2022/02/13/DancingWithStars2022_EPG.jpg' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: '[]', - date - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./mako.co.il.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-03-07', 'YYYY-MM-DD').startOf('d') + +it('can generate valid url', () => { + expect(url).toBe('https://www.mako.co.il/AjaxPage?jspName=EPGResponse.jsp') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ content, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2022-03-07T00:38:00.000Z', + stop: '2022-03-07T00:39:00.000Z', + title: 'רוקדים עם כוכבים - בר זומר', + description: 'מהדורת החדשות המרכזית של הבוקר, האנשים הפרשנויות והכותרות שיעשו את היום.', + image: 'https://img.mako.co.il/2022/02/13/DancingWithStars2022_EPG.jpg' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: '[]', + date + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/maxtvgo.mk/maxtvgo.mk.test.js b/sites/maxtvgo.mk/maxtvgo.mk.test.js index 336265fa2..bb813b28a 100644 --- a/sites/maxtvgo.mk/maxtvgo.mk.test.js +++ b/sites/maxtvgo.mk/maxtvgo.mk.test.js @@ -1,72 +1,72 @@ -const { parser, url } = require('./maxtvgo.mk.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2021-11-17', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '105', - xmltv_id: 'MRT1.mk' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://prd-static-mkt.spectar.tv/rev-1636968171/client_api.php/epg/list/instance_id/1/language/mk/channel_id/105/start/20211117000000/stop/20211118000000/include_current/true/format/json' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2021-11-16T22:10:00.000Z', - stop: '2021-11-17T00:00:00.000Z', - title: 'Палмето - игран филм', - category: 'Останато', - description: - 'Екстремниот рибар, Џереми Вејд, е во потрага по слатководни риби кои јадат човечко месо. Со форензички методи, Џереми им илустрира на гледачите како овие нови чудовишта се создадени да убиваат.', - image: - 'https://prd-static-mkt.spectar.tv/rev-1636968170/image_transform.php/transform/1/epg_program_id/21949063/instance_id/1' - } - ]) -}) - -it('can parse response with no description', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_no_description.json')) - const result = parser({ content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2021-11-16T22:10:00.000Z', - stop: '2021-11-17T00:00:00.000Z', - title: 'Палмето - игран филм', - category: 'Останато', - description: null, - image: - 'https://prd-static-mkt.spectar.tv/rev-1636968170/image_transform.php/transform/1/epg_program_id/21949063/instance_id/1' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./maxtvgo.mk.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2021-11-17', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '105', + xmltv_id: 'MRT1.mk' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://prd-static-mkt.spectar.tv/rev-1636968171/client_api.php/epg/list/instance_id/1/language/mk/channel_id/105/start/20211117000000/stop/20211118000000/include_current/true/format/json' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2021-11-16T22:10:00.000Z', + stop: '2021-11-17T00:00:00.000Z', + title: 'Палмето - игран филм', + category: 'Останато', + description: + 'Екстремниот рибар, Џереми Вејд, е во потрага по слатководни риби кои јадат човечко месо. Со форензички методи, Џереми им илустрира на гледачите како овие нови чудовишта се создадени да убиваат.', + image: + 'https://prd-static-mkt.spectar.tv/rev-1636968170/image_transform.php/transform/1/epg_program_id/21949063/instance_id/1' + } + ]) +}) + +it('can parse response with no description', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_no_description.json')) + const result = parser({ content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2021-11-16T22:10:00.000Z', + stop: '2021-11-17T00:00:00.000Z', + title: 'Палмето - игран филм', + category: 'Останато', + description: null, + image: + 'https://prd-static-mkt.spectar.tv/rev-1636968170/image_transform.php/transform/1/epg_program_id/21949063/instance_id/1' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/melita.com/melita.com.test.js b/sites/melita.com/melita.com.test.js index 55869a74a..1781cbb28 100644 --- a/sites/melita.com/melita.com.test.js +++ b/sites/melita.com/melita.com.test.js @@ -1,51 +1,51 @@ -const { parser, url } = require('./melita.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-04-20', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '4d40a9f9-12fd-4f03-8072-61c637ff6995', - xmltv_id: 'TVM.mt' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://androme.melitacable.com/api/epg/v1/schedule/channel/4d40a9f9-12fd-4f03-8072-61c637ff6995/from/2022-04-20T00:00+00:00/until/2022-04-21T00:00+00:00' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2022-04-20T06:25:00.000Z', - stop: '2022-04-20T06:45:00.000Z', - title: 'How I Met Your Mother', - description: - 'Symphony of Illumination - Robin gets some bad news and decides to keep it to herself. Marshall decorates the house.', - season: 7, - episode: 12, - image: - 'https://androme.melitacable.com/media/images/epg/bc/07/p8953134_e_h10_ad.jpg?form=epg-card-6', - category: ['comedy'] - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: '{}' - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./melita.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-04-20', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '4d40a9f9-12fd-4f03-8072-61c637ff6995', + xmltv_id: 'TVM.mt' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://androme.melitacable.com/api/epg/v1/schedule/channel/4d40a9f9-12fd-4f03-8072-61c637ff6995/from/2022-04-20T00:00+00:00/until/2022-04-21T00:00+00:00' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2022-04-20T06:25:00.000Z', + stop: '2022-04-20T06:45:00.000Z', + title: 'How I Met Your Mother', + description: + 'Symphony of Illumination - Robin gets some bad news and decides to keep it to herself. Marshall decorates the house.', + season: 7, + episode: 12, + image: + 'https://androme.melitacable.com/media/images/epg/bc/07/p8953134_e_h10_ad.jpg?form=epg-card-6', + category: ['comedy'] + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: '{}' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/mewatch.sg/mewatch.sg.test.js b/sites/mewatch.sg/mewatch.sg.test.js index f65ecb3f9..0eb0565e5 100644 --- a/sites/mewatch.sg/mewatch.sg.test.js +++ b/sites/mewatch.sg/mewatch.sg.test.js @@ -1,56 +1,56 @@ -const { parser, url } = require('./mewatch.sg.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-06-11', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '97098', - xmltv_id: 'Channel5Singapore.sg' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://cdn.mewatch.sg/api/schedules?channels=97098&date=2022-06-10&duration=24&ff=idp,ldp,rpt,cd&hour=12&intersect=true&lang=en&segments=all' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ content, channel }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2022-06-11T21:00:00.000Z', - stop: '2022-06-11T21:30:00.000Z', - title: 'Open Homes S3 - EP 2', - description: - 'Mike heads down to the Sydney beaches to visit a beachside renovation with all the bells and whistles, we see a kitchen tip and recipe anyone can do at home. We finish up in the prestigious Byron bay to visit a multi million dollar award winning home.', - image: - "https://production.togglestatic.com/shain/v1/dataservice/ResizeImage/$value?Format='jpg'&Quality=85&ImageId='4853697'&EntityType='LinearSchedule'&EntityId='788a7dd9-9b12-446f-91b4-c8ac9fec95e5'&Width=1280&Height=720&device=web_browser&subscriptions=Anonymous&segmentationTags=all", - episode: 2, - season: 3, - rating: { - system: 'IMDA', - value: 'G' - } - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: - fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')), - channel - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./mewatch.sg.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-06-11', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '97098', + xmltv_id: 'Channel5Singapore.sg' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://cdn.mewatch.sg/api/schedules?channels=97098&date=2022-06-10&duration=24&ff=idp,ldp,rpt,cd&hour=12&intersect=true&lang=en&segments=all' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ content, channel }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2022-06-11T21:00:00.000Z', + stop: '2022-06-11T21:30:00.000Z', + title: 'Open Homes S3 - EP 2', + description: + 'Mike heads down to the Sydney beaches to visit a beachside renovation with all the bells and whistles, we see a kitchen tip and recipe anyone can do at home. We finish up in the prestigious Byron bay to visit a multi million dollar award winning home.', + image: + "https://production.togglestatic.com/shain/v1/dataservice/ResizeImage/$value?Format='jpg'&Quality=85&ImageId='4853697'&EntityType='LinearSchedule'&EntityId='788a7dd9-9b12-446f-91b4-c8ac9fec95e5'&Width=1280&Height=720&device=web_browser&subscriptions=Anonymous&segmentationTags=all", + episode: 2, + season: 3, + rating: { + system: 'IMDA', + value: 'G' + } + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: + fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')), + channel + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/mi.tv/mi.tv.config.js b/sites/mi.tv/mi.tv.config.js index 569259bb1..fe7df4aa1 100644 --- a/sites/mi.tv/mi.tv.config.js +++ b/sites/mi.tv/mi.tv.config.js @@ -1,144 +1,144 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(customParseFormat) - -const headers = { - 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', - 'accept-language': 'en', - 'sec-fetch-site': 'same-origin', - 'sec-fetch-user': '?1', - 'upgrade-insecure-requests': '1', - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36' -} - -module.exports = { - site: 'mi.tv', - days: 2, - request: { headers }, - url({ date, channel }) { - const [country, id] = channel.site_id.split('#') - return `https://mi.tv/${country}/async/channel/${id}/${date.format('YYYY-MM-DD')}/0` - }, - parser({ content, date }) { - const programs = [] - const items = parseItems(content) - items.forEach(item => { - const prev = programs[programs.length - 1] - const $item = cheerio.load(item) - let start = parseStart($item, date) - if (!start) return - if (prev) { - if (start.isBefore(prev.start)) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.add(1, 'h') - programs.push({ - title: parseTitle($item), - category: parseCategory($item), - description: parseDescription($item), - image: parseImage($item), - start, - stop - }) - }) - - return programs - }, - async channels({ country }) { - let lang = 'es' - if (country === 'br') lang = 'pt' - - const axios = require('axios') - const data = await axios - .get(`https://mi.tv/${country}/sitemap`) - .then(r => r.data) - .catch(console.log) - - const $ = cheerio.load(data) - - let channels = [] - $(`#page-contents a[href*="${country}/canales"], a[href*="${country}/canais"]`).each( - (i, el) => { - const name = $(el).text() - const url = $(el).attr('href') - const [, , , channelId] = url.split('/') - - channels.push({ - lang, - name, - site_id: `${country}#${channelId}` - }) - } - ) - - return channels - } -} - -function parseStart($item, date) { - const timeString = $item('a > div.content > span.time').text() - if (!timeString) return null - const dateString = `${date.format('MM/DD/YYYY')} ${timeString}` - - return dayjs.utc(dateString, 'MM/DD/YYYY HH:mm') -} - -function parseTitle($item) { - return $item('a > div.content > h2').text().trim() -} - -function parseCategory($item) { - return $item('a > div.content > span.sub-title').text().trim() -} - -function parseDescription($item) { - return $item('a > div.content > p.synopsis').text().trim() -} - - -function parseImage($item) { - const styleAttr = $item('a > div.image-parent > div.image').attr('style') - - if (styleAttr) { - const match = styleAttr.match(/background-image:\s*url\(['"]?(.*?)['"]?\)/) - if (match) { - return cleanUrl(match[1]) - } - } - - const backgroundImage = $item('a > div.image-parent > div.image').css('background-image') - - if (backgroundImage && backgroundImage !== 'none') { - const match = backgroundImage.match(/url\(['"]?(.*?)['"]?\)/) - if (match) { - return cleanUrl(match[1]) - } - } - - return null -} - -function cleanUrl(url) { - if (!url) return null - - return url - .replace(/^['"`\\]+/, '') - .replace(/['"`\\]+$/, '') - .replace(/\\'/g, "'") - .replace(/\\"/g, '"') - .replace(/\\\\/g, '\\') -} - - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('#listings > ul > li').toArray() +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(customParseFormat) + +const headers = { + 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', + 'accept-language': 'en', + 'sec-fetch-site': 'same-origin', + 'sec-fetch-user': '?1', + 'upgrade-insecure-requests': '1', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36' +} + +module.exports = { + site: 'mi.tv', + days: 2, + request: { headers }, + url({ date, channel }) { + const [country, id] = channel.site_id.split('#') + return `https://mi.tv/${country}/async/channel/${id}/${date.format('YYYY-MM-DD')}/0` + }, + parser({ content, date }) { + const programs = [] + const items = parseItems(content) + items.forEach(item => { + const prev = programs[programs.length - 1] + const $item = cheerio.load(item) + let start = parseStart($item, date) + if (!start) return + if (prev) { + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.add(1, 'h') + programs.push({ + title: parseTitle($item), + category: parseCategory($item), + description: parseDescription($item), + image: parseImage($item), + start, + stop + }) + }) + + return programs + }, + async channels({ country }) { + let lang = 'es' + if (country === 'br') lang = 'pt' + + const axios = require('axios') + const data = await axios + .get(`https://mi.tv/${country}/sitemap`) + .then(r => r.data) + .catch(console.log) + + const $ = cheerio.load(data) + + let channels = [] + $(`#page-contents a[href*="${country}/canales"], a[href*="${country}/canais"]`).each( + (i, el) => { + const name = $(el).text() + const url = $(el).attr('href') + const [, , , channelId] = url.split('/') + + channels.push({ + lang, + name, + site_id: `${country}#${channelId}` + }) + } + ) + + return channels + } +} + +function parseStart($item, date) { + const timeString = $item('a > div.content > span.time').text() + if (!timeString) return null + const dateString = `${date.format('MM/DD/YYYY')} ${timeString}` + + return dayjs.utc(dateString, 'MM/DD/YYYY HH:mm') +} + +function parseTitle($item) { + return $item('a > div.content > h2').text().trim() +} + +function parseCategory($item) { + return $item('a > div.content > span.sub-title').text().trim() +} + +function parseDescription($item) { + return $item('a > div.content > p.synopsis').text().trim() +} + + +function parseImage($item) { + const styleAttr = $item('a > div.image-parent > div.image').attr('style') + + if (styleAttr) { + const match = styleAttr.match(/background-image:\s*url\(['"]?(.*?)['"]?\)/) + if (match) { + return cleanUrl(match[1]) + } + } + + const backgroundImage = $item('a > div.image-parent > div.image').css('background-image') + + if (backgroundImage && backgroundImage !== 'none') { + const match = backgroundImage.match(/url\(['"]?(.*?)['"]?\)/) + if (match) { + return cleanUrl(match[1]) + } + } + + return null +} + +function cleanUrl(url) { + if (!url) return null + + return url + .replace(/^['"`\\]+/, '') + .replace(/['"`\\]+$/, '') + .replace(/\\'/g, "'") + .replace(/\\"/g, '"') + .replace(/\\\\/g, '\\') +} + + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('#listings > ul > li').toArray() } \ No newline at end of file diff --git a/sites/mi.tv/mi.tv.test.js b/sites/mi.tv/mi.tv.test.js index ec5652e02..69a07e8d8 100644 --- a/sites/mi.tv/mi.tv.test.js +++ b/sites/mi.tv/mi.tv.test.js @@ -1,67 +1,67 @@ -const { parser, url } = require('./mi.tv.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2021-11-24', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'ar#24-7-canal-de-noticias', - xmltv_id: '247CanaldeNoticias.ar' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://mi.tv/ar/async/channel/24-7-canal-de-noticias/2021-11-24/0' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') - const result = parser({ content, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2021-11-24T03:00:00.000Z', - stop: '2021-11-24T23:00:00.000Z', - title: 'Trasnoche de 24/7', - category: 'Interés general', - description: 'Lo más visto de la semana en nuestra pantalla.', - image: 'https://cdn.mitvstatic.com/programs/fallback_other_l_m.jpg' - }, - { - start: '2021-11-24T23:00:00.000Z', - stop: '2021-11-25T01:00:00.000Z', - title: 'Noticiero central - Segunda edición', - category: 'Noticiero', - description: - 'Cerramos el día con un completo resumen de los temas más relevantes con columnistas y análisis especiales para terminar el día.', - image: 'https://cdn.mitvstatic.com/programs/fallback_other_l_m.jpg' - }, - { - start: '2021-11-25T01:00:00.000Z', - stop: '2021-11-25T02:00:00.000Z', - title: 'Plus energético', - category: 'Cultural', - description: - 'La energía tiene mucho para mostrar. Este programa reúne a las principales empresas y protagonistas de la actividad que esta revolucionando la región.', - image: 'https://cdn.mitvstatic.com/programs/fallback_other_l_m.jpg' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: '' - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./mi.tv.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2021-11-24', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'ar#24-7-canal-de-noticias', + xmltv_id: '247CanaldeNoticias.ar' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://mi.tv/ar/async/channel/24-7-canal-de-noticias/2021-11-24/0' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') + const result = parser({ content, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2021-11-24T03:00:00.000Z', + stop: '2021-11-24T23:00:00.000Z', + title: 'Trasnoche de 24/7', + category: 'Interés general', + description: 'Lo más visto de la semana en nuestra pantalla.', + image: 'https://cdn.mitvstatic.com/programs/fallback_other_l_m.jpg' + }, + { + start: '2021-11-24T23:00:00.000Z', + stop: '2021-11-25T01:00:00.000Z', + title: 'Noticiero central - Segunda edición', + category: 'Noticiero', + description: + 'Cerramos el día con un completo resumen de los temas más relevantes con columnistas y análisis especiales para terminar el día.', + image: 'https://cdn.mitvstatic.com/programs/fallback_other_l_m.jpg' + }, + { + start: '2021-11-25T01:00:00.000Z', + stop: '2021-11-25T02:00:00.000Z', + title: 'Plus energético', + category: 'Cultural', + description: + 'La energía tiene mucho para mostrar. Este programa reúne a las principales empresas y protagonistas de la actividad que esta revolucionando la región.', + image: 'https://cdn.mitvstatic.com/programs/fallback_other_l_m.jpg' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: '' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/moji.id/moji.id.test.js b/sites/moji.id/moji.id.test.js index 7e487374f..589931e63 100644 --- a/sites/moji.id/moji.id.test.js +++ b/sites/moji.id/moji.id.test.js @@ -1,29 +1,29 @@ -const { parser } = require('./moji.id.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -dayjs.extend(utc) - -const date = dayjs.utc('2023-08-18', 'YYYY-MM-DD').startOf('d') - -it('can handle empty guide', () => { - const results = parser({ content: '' }) - expect(results).toMatchObject([]) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') - const results = parser({ content, date }).map(p => { - p.start = p.start.year(2023).toJSON() - p.stop = p.stop.year(2023).toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - title: 'TRUST', - start: '2023-08-17T17:00:00.000Z', - stop: '2023-08-17T17:30:00.000Z', - description: 'Informasi seputar menjaga vitalitas pria' - }) -}) +const { parser } = require('./moji.id.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +dayjs.extend(utc) + +const date = dayjs.utc('2023-08-18', 'YYYY-MM-DD').startOf('d') + +it('can handle empty guide', () => { + const results = parser({ content: '' }) + expect(results).toMatchObject([]) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') + const results = parser({ content, date }).map(p => { + p.start = p.start.year(2023).toJSON() + p.stop = p.stop.year(2023).toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + title: 'TRUST', + start: '2023-08-17T17:00:00.000Z', + stop: '2023-08-17T17:30:00.000Z', + description: 'Informasi seputar menjaga vitalitas pria' + }) +}) diff --git a/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js b/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js index 8aff9e1fe..db27786b9 100644 --- a/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js +++ b/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js @@ -1,192 +1,192 @@ -const doFetch = require('@ntlab/sfetch') -const axios = require('axios') -const dayjs = require('dayjs') -const crypto = require('crypto') -const { sortBy } = require('../../scripts/functions') - -// API Configuration Constants -const NATCO_CODE = 'hr' -const APP_LANGUAGE = 'hr' -const APP_KEY = 'GWaBW4RTloLwpUgYVzOiW5zUxFLmoMj5' -const APP_VERSION = '02.0.1080' -const NATCO_KEY = 'l2lyvGVbUm2EKJE96ImQgcc8PKMZWtbE' -const SITE_URL = 'mojmaxtv.hrvatskitelekom.hr' - -// Role Types -const ROLE_TYPES = { - ACTOR: 'GLUMI', // Croatian for "ACTS" - DIRECTOR: 'REŽIJA', // Croatian for "DIRECTOR" - PRODUCER: 'PRODUKCIJA', // Croatian for "PRODUCER" - WRITER: 'AUTOR', - SCENARIO: 'SCENARIJ' -} - -// Dynamic API Endpoint based on NATCO_CODE -const API_ENDPOINT = `https://tv-${NATCO_CODE}-prod.yo-digital.com/${NATCO_CODE}-bifrost` - -// Session/Device IDs -const DEVICE_ID = crypto.randomUUID() -const SESSION_ID = crypto.randomUUID() - -const cached = {} - -const getHeaders = () => ({ - 'app_key': APP_KEY, - 'app_version': APP_VERSION, - 'device-id': DEVICE_ID, - 'tenant': 'tv', - 'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36', - 'origin': `https://${SITE_URL}`, - 'x-request-session-id': SESSION_ID, - 'x-request-tracking-id': crypto.randomUUID(), - 'x-tv-step': 'EPG_SCHEDULES', - 'x-tv-flow': 'EPG', - 'x-call-type': 'GUEST_USER', - 'x-user-agent': `web|web|Chrome-133|${APP_VERSION}|1` -}) - -module.exports = { - site: SITE_URL, - url({ date }) { - return `${API_ENDPOINT}/epg/channel/schedules?date=${date.format( - 'YYYY-MM-DD' - )}&hour_offset=0&hour_range=3&channelMap_id&filler=true&app_language=${APP_LANGUAGE}&natco_code=${NATCO_CODE}` - }, - request: { - headers: getHeaders(), - cache: { - ttl: 24 * 60 * 60 * 1000 // 1 day - } - }, - async parser({ content, channel, date }) { - const data = parseData(content) - if (!data) return [] - - let items = parseItems(data, channel) - if (!items.length) return [] - - const queue = [3, 6, 9, 12, 15, 18, 21] - .map(offset => { - const url = module.exports.url({ date }).replace('hour_offset=0', `hour_offset=${offset}`) - const params = { ...module.exports.request, headers: getHeaders() } - - if (cached[url]) { - items = items.concat(parseItems(cached[url], channel)) - return null - } - - return { url, params } - }) - .filter(Boolean) - - await doFetch(queue, (_req, _data) => { - if (_data) { - cached[_req.url] = _data - items = items.concat(parseItems(_data, channel)) - } - }) - - items = sortBy(items, i => dayjs(i.start_time).valueOf()) - - // Fetch program details for each item - const programs = [] - for (let item of items) { - const detail = await loadProgramDetails(item) - -// detectUnknownRoles(detail) - - programs.push({ - title: item.description, - sub_title: item.episode_name, - description: parseDescription(detail), - categories: Array.isArray(item.genres) ? item.genres.map(g => g.name) : [], - date: parseDate(item), - image: detail.poster_image_url, - actors: parseRoles(detail, ROLE_TYPES.ACTOR), - directors: parseRoles(detail, ROLE_TYPES.DIRECTOR), - producers: parseRoles(detail, ROLE_TYPES.PRODUCER), - season: parseSeason(item), - episode: parseEpisode(item), - rating: parseRating(item), - start: item.start_time, - stop: item.end_time - }) - } - - return programs - }, - async channels() { - const data = await axios - .get( - `${API_ENDPOINT}/epg/channel?channelMap_id=&includeVirtualChannels=false&natco_key=${NATCO_KEY}&app_language=${APP_LANGUAGE}&natco_code=${NATCO_CODE}`, - { ...module.exports.request, headers: getHeaders() } - ) - .then(r => r.data) - .catch(console.error) - - return data.channels.map(channel => ({ - lang: NATCO_CODE, - name: channel.title, - site_id: channel.station_id - })) - } -} - -async function loadProgramDetails(item) { - if (!item.program_id) return {} - const url = `${API_ENDPOINT}/details/series/${item.program_id}?natco_code=${NATCO_CODE}` - const data = await axios - .get(url, { headers: getHeaders() }) - .then(r => r.data) - .catch(console.log) - - return data || {} -} - -function parseData(content) { - try { - const data = JSON.parse(content) - return data || null - } catch { - return null - } -} - -function parseItems(data, channel) { - if (!data.channels || !Array.isArray(data.channels[channel.site_id])) return [] - return data.channels[channel.site_id] -} - -function parseDate(item) { - return item && item.release_year ? item.release_year.toString() : null -} - -function parseRating(item) { - return item.ratings - ? { - system: 'MPA', - value: item.ratings - } - : null -} - -function parseSeason(item) { - if (item.season_display_number === 'Epizode') return null // 'Epizode' is 'Episodes' in Croatian - return item.season_number -} - -function parseEpisode(item) { - if (item.episode_number) return parseInt(item.episode_number) - if (item.season_display_number === 'Epizode') return item.season_number - return null -} - -function parseDescription(item) { - if (!item.details) return null - return item.details.description -} - -function parseRoles(item, role_name) { - if (!item.roles) return null - return item.roles.filter(role => role.role_name === role_name).map(role => role.person_name) -} +const doFetch = require('@ntlab/sfetch') +const axios = require('axios') +const dayjs = require('dayjs') +const crypto = require('crypto') +const { sortBy } = require('../../scripts/functions') + +// API Configuration Constants +const NATCO_CODE = 'hr' +const APP_LANGUAGE = 'hr' +const APP_KEY = 'GWaBW4RTloLwpUgYVzOiW5zUxFLmoMj5' +const APP_VERSION = '02.0.1080' +const NATCO_KEY = 'l2lyvGVbUm2EKJE96ImQgcc8PKMZWtbE' +const SITE_URL = 'mojmaxtv.hrvatskitelekom.hr' + +// Role Types +const ROLE_TYPES = { + ACTOR: 'GLUMI', // Croatian for "ACTS" + DIRECTOR: 'REŽIJA', // Croatian for "DIRECTOR" + PRODUCER: 'PRODUKCIJA', // Croatian for "PRODUCER" + WRITER: 'AUTOR', + SCENARIO: 'SCENARIJ' +} + +// Dynamic API Endpoint based on NATCO_CODE +const API_ENDPOINT = `https://tv-${NATCO_CODE}-prod.yo-digital.com/${NATCO_CODE}-bifrost` + +// Session/Device IDs +const DEVICE_ID = crypto.randomUUID() +const SESSION_ID = crypto.randomUUID() + +const cached = {} + +const getHeaders = () => ({ + 'app_key': APP_KEY, + 'app_version': APP_VERSION, + 'device-id': DEVICE_ID, + 'tenant': 'tv', + 'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36', + 'origin': `https://${SITE_URL}`, + 'x-request-session-id': SESSION_ID, + 'x-request-tracking-id': crypto.randomUUID(), + 'x-tv-step': 'EPG_SCHEDULES', + 'x-tv-flow': 'EPG', + 'x-call-type': 'GUEST_USER', + 'x-user-agent': `web|web|Chrome-133|${APP_VERSION}|1` +}) + +module.exports = { + site: SITE_URL, + url({ date }) { + return `${API_ENDPOINT}/epg/channel/schedules?date=${date.format( + 'YYYY-MM-DD' + )}&hour_offset=0&hour_range=3&channelMap_id&filler=true&app_language=${APP_LANGUAGE}&natco_code=${NATCO_CODE}` + }, + request: { + headers: getHeaders(), + cache: { + ttl: 24 * 60 * 60 * 1000 // 1 day + } + }, + async parser({ content, channel, date }) { + const data = parseData(content) + if (!data) return [] + + let items = parseItems(data, channel) + if (!items.length) return [] + + const queue = [3, 6, 9, 12, 15, 18, 21] + .map(offset => { + const url = module.exports.url({ date }).replace('hour_offset=0', `hour_offset=${offset}`) + const params = { ...module.exports.request, headers: getHeaders() } + + if (cached[url]) { + items = items.concat(parseItems(cached[url], channel)) + return null + } + + return { url, params } + }) + .filter(Boolean) + + await doFetch(queue, (_req, _data) => { + if (_data) { + cached[_req.url] = _data + items = items.concat(parseItems(_data, channel)) + } + }) + + items = sortBy(items, i => dayjs(i.start_time).valueOf()) + + // Fetch program details for each item + const programs = [] + for (let item of items) { + const detail = await loadProgramDetails(item) + +// detectUnknownRoles(detail) + + programs.push({ + title: item.description, + sub_title: item.episode_name, + description: parseDescription(detail), + categories: Array.isArray(item.genres) ? item.genres.map(g => g.name) : [], + date: parseDate(item), + image: detail.poster_image_url, + actors: parseRoles(detail, ROLE_TYPES.ACTOR), + directors: parseRoles(detail, ROLE_TYPES.DIRECTOR), + producers: parseRoles(detail, ROLE_TYPES.PRODUCER), + season: parseSeason(item), + episode: parseEpisode(item), + rating: parseRating(item), + start: item.start_time, + stop: item.end_time + }) + } + + return programs + }, + async channels() { + const data = await axios + .get( + `${API_ENDPOINT}/epg/channel?channelMap_id=&includeVirtualChannels=false&natco_key=${NATCO_KEY}&app_language=${APP_LANGUAGE}&natco_code=${NATCO_CODE}`, + { ...module.exports.request, headers: getHeaders() } + ) + .then(r => r.data) + .catch(console.error) + + return data.channels.map(channel => ({ + lang: NATCO_CODE, + name: channel.title, + site_id: channel.station_id + })) + } +} + +async function loadProgramDetails(item) { + if (!item.program_id) return {} + const url = `${API_ENDPOINT}/details/series/${item.program_id}?natco_code=${NATCO_CODE}` + const data = await axios + .get(url, { headers: getHeaders() }) + .then(r => r.data) + .catch(console.log) + + return data || {} +} + +function parseData(content) { + try { + const data = JSON.parse(content) + return data || null + } catch { + return null + } +} + +function parseItems(data, channel) { + if (!data.channels || !Array.isArray(data.channels[channel.site_id])) return [] + return data.channels[channel.site_id] +} + +function parseDate(item) { + return item && item.release_year ? item.release_year.toString() : null +} + +function parseRating(item) { + return item.ratings + ? { + system: 'MPA', + value: item.ratings + } + : null +} + +function parseSeason(item) { + if (item.season_display_number === 'Epizode') return null // 'Epizode' is 'Episodes' in Croatian + return item.season_number +} + +function parseEpisode(item) { + if (item.episode_number) return parseInt(item.episode_number) + if (item.season_display_number === 'Epizode') return item.season_number + return null +} + +function parseDescription(item) { + if (!item.details) return null + return item.details.description +} + +function parseRoles(item, role_name) { + if (!item.roles) return null + return item.roles.filter(role => role.role_name === role_name).map(role => role.person_name) +} diff --git a/sites/mtel.ba/mtel.ba.config.js b/sites/mtel.ba/mtel.ba.config.js index 6f84f5cf9..13b4d26fb 100644 --- a/sites/mtel.ba/mtel.ba.config.js +++ b/sites/mtel.ba/mtel.ba.config.js @@ -1,110 +1,110 @@ -const doFetch = require('@ntlab/sfetch') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') -const { sortBy } = require('../../scripts/functions') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'mtel.ba', - days: 2, - url({ channel, date }) { - const [platform] = channel.site_id.split('#') - - return `https://mtel.ba/hybris/ecommerce/b2c/v1/products/channels/epg?platform=tv-${platform}&pageSize=999&date=${date.format( - 'YYYY-MM-DD' - )}` - }, - request: { - timeout: 20000, // 20 seconds - maxContentLength: 10000000, // 10 Mb - cache: { - interpretHeader: false, - ttl: 24 * 60 * 60 * 1000 // 1 day - } - }, - parser({ content, channel }) { - let programs = [] - const items = parseItems(content, channel) - items.forEach(item => { - programs.push({ - title: item.title, - description: item.description, - categories: parseCategories(item), - image: parseImage(item), - start: parseStart(item), - stop: parseStop(item) - }) - }) - - return programs - }, - async channels({ platform = 'msat' }) { - const platforms = { - msat: 'https://mtel.ba/hybris/ecommerce/b2c/v1/products/channels/search?pageSize=999&query=:relevantno:tv-kategorija:tv-msat', - iptv: 'https://mtel.ba/hybris/ecommerce/b2c/v1/products/channels/search?pageSize=999&query=:relevantno:tv-kategorija:tv-iptv' - } - - const queue = [ - { - platform, - url: platforms[platform] - } - ] - - let channels = [] - await doFetch(queue, (req, data) => { - if (data && data.pagination.currentPage < data.pagination.totalPages) { - queue.push({ - platform: req.platform, - url: platforms[req.platform] - }) - } - - data.products.forEach(channel => { - channels.push({ - lang: 'bs', - name: channel.name, - site_id: `${req.platform}#${channel.code}` - }) - }) - }) - - return channels - } -} - -function parseStart(item) { - return dayjs.tz(item.start, 'YYYY-MM-DD HH:mm', 'Europe/Sarajevo') -} - -function parseStop(item) { - return dayjs.tz(item.end, 'YYYY-MM-DD HH:mm', 'Europe/Sarajevo') -} - -function parseCategories(item) { - return item.category ? item.category.split(' / ') : [] -} - -function parseImage(item) { - return item?.picture?.url ? item.picture.url : null -} - -function parseItems(content, channel) { - try { - const data = JSON.parse(content) - if (!data || !Array.isArray(data.products)) return [] - const [, channelId] = channel.site_id.split('#') - const channelData = data.products.find(channel => channel.code === channelId) - if (!channelData || !Array.isArray(channelData.programs)) return [] - // filter out programs that have the sentence "no program information available" - channelData.programs = channelData.programs.filter(p => !p.title.includes('Nema informacija o programu')) - return sortBy(channelData.programs, p => parseStart(p).valueOf()) - } catch { - return [] - } -} +const doFetch = require('@ntlab/sfetch') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const { sortBy } = require('../../scripts/functions') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'mtel.ba', + days: 2, + url({ channel, date }) { + const [platform] = channel.site_id.split('#') + + return `https://mtel.ba/hybris/ecommerce/b2c/v1/products/channels/epg?platform=tv-${platform}&pageSize=999&date=${date.format( + 'YYYY-MM-DD' + )}` + }, + request: { + timeout: 20000, // 20 seconds + maxContentLength: 10000000, // 10 Mb + cache: { + interpretHeader: false, + ttl: 24 * 60 * 60 * 1000 // 1 day + } + }, + parser({ content, channel }) { + let programs = [] + const items = parseItems(content, channel) + items.forEach(item => { + programs.push({ + title: item.title, + description: item.description, + categories: parseCategories(item), + image: parseImage(item), + start: parseStart(item), + stop: parseStop(item) + }) + }) + + return programs + }, + async channels({ platform = 'msat' }) { + const platforms = { + msat: 'https://mtel.ba/hybris/ecommerce/b2c/v1/products/channels/search?pageSize=999&query=:relevantno:tv-kategorija:tv-msat', + iptv: 'https://mtel.ba/hybris/ecommerce/b2c/v1/products/channels/search?pageSize=999&query=:relevantno:tv-kategorija:tv-iptv' + } + + const queue = [ + { + platform, + url: platforms[platform] + } + ] + + let channels = [] + await doFetch(queue, (req, data) => { + if (data && data.pagination.currentPage < data.pagination.totalPages) { + queue.push({ + platform: req.platform, + url: platforms[req.platform] + }) + } + + data.products.forEach(channel => { + channels.push({ + lang: 'bs', + name: channel.name, + site_id: `${req.platform}#${channel.code}` + }) + }) + }) + + return channels + } +} + +function parseStart(item) { + return dayjs.tz(item.start, 'YYYY-MM-DD HH:mm', 'Europe/Sarajevo') +} + +function parseStop(item) { + return dayjs.tz(item.end, 'YYYY-MM-DD HH:mm', 'Europe/Sarajevo') +} + +function parseCategories(item) { + return item.category ? item.category.split(' / ') : [] +} + +function parseImage(item) { + return item?.picture?.url ? item.picture.url : null +} + +function parseItems(content, channel) { + try { + const data = JSON.parse(content) + if (!data || !Array.isArray(data.products)) return [] + const [, channelId] = channel.site_id.split('#') + const channelData = data.products.find(channel => channel.code === channelId) + if (!channelData || !Array.isArray(channelData.programs)) return [] + // filter out programs that have the sentence "no program information available" + channelData.programs = channelData.programs.filter(p => !p.title.includes('Nema informacija o programu')) + return sortBy(channelData.programs, p => parseStart(p).valueOf()) + } catch { + return [] + } +} diff --git a/sites/mtel.ba/mtel.ba.test.js b/sites/mtel.ba/mtel.ba.test.js index 393da5075..122ee19b2 100644 --- a/sites/mtel.ba/mtel.ba.test.js +++ b/sites/mtel.ba/mtel.ba.test.js @@ -1,58 +1,58 @@ -const { parser, url } = require('./mtel.ba.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-02-04', 'YYYY-MM-DD').startOf('d') -const channel = { site_id: 'msat#ch-11-rtrs' } - -it('can generate valid url', () => { - expect(url({ date, channel })).toBe( - 'https://mtel.ba/hybris/ecommerce/b2c/v1/products/channels/epg?platform=tv-msat&pageSize=999&date=2025-02-04' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - let results = parser({ channel, content }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - - return p - }) - - expect(results.length).toBe(38) - expect(results[0]).toMatchObject({ - start: '2025-02-03T22:38:00.000Z', - stop: '2025-02-03T23:38:00.000Z', - title: 'Neka pesma kaže', - image: - 'https://medias.services.mtel.ba/medias/407368591.jpg?context=bWFzdGVyfHJvb3R8MTM2MTZ8aW1hZ2UvanBlZ3xhR1F5TDJnell5ODBOekExTmpFMk1qRTJNRFkzTUM4ME1EY3pOamcxT1RFdWFuQm58ZWM3Zjc4MDNlZTY5OWU1ZGJiZDI5N2UzMDg4ODA3NzQ1NWM0OThlMjdhYmU4MjI4NGJhOWE2YzYwMTc5ODM3NQ', - description: - 'Zabavni-muzički program donosi nam divne zvukove prave, narodne muzike, u kojoj se izvođači oslanjaju na kvalitet i tradiciju.', - categories: ['Music', 'Ballet', 'Dance'] - }) - expect(results[37]).toMatchObject({ - start: '2025-02-04T22:27:00.000Z', - stop: '2025-02-04T23:58:00.000Z', - title: 'Bitanga s plaže', - image: - 'https://medias.services.mtel.ba/medias/117604203.jpg?context=bWFzdGVyfHJvb3R8MTY1MTZ8aW1hZ2UvanBlZ3xhRGd6TDJnek1DODBOekExTmpFMk16STNORGM0TWk4eE1UYzJNRFF5TURNdWFuQm58YmU5MjdkOTljMGE4YjIyNjg3ZmI1YWJjYWQ0ZDY5YjA0YWJiY2RlN2E0ZGVjOTdlYzM4MzI4MzYyMzFiODBlMg', - description: - 'Film prati urnebesne avanture Moondoga, buntovnika i skitnicu koji svoj život živi isključivo prema vlastitim pravilima. Uz glumačke nastupe Snoop Dogga, Zaca Efrona i Isle Fisher, Bitanga s plaže osvježavajuće je originalna i subverzivna nova komedija scenarista i redatelja Harmonyja Korinea.', - categories: ['Movie', 'Drama'] - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - channel, - content: '{}' - }) - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./mtel.ba.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-02-04', 'YYYY-MM-DD').startOf('d') +const channel = { site_id: 'msat#ch-11-rtrs' } + +it('can generate valid url', () => { + expect(url({ date, channel })).toBe( + 'https://mtel.ba/hybris/ecommerce/b2c/v1/products/channels/epg?platform=tv-msat&pageSize=999&date=2025-02-04' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + let results = parser({ channel, content }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + + return p + }) + + expect(results.length).toBe(38) + expect(results[0]).toMatchObject({ + start: '2025-02-03T22:38:00.000Z', + stop: '2025-02-03T23:38:00.000Z', + title: 'Neka pesma kaže', + image: + 'https://medias.services.mtel.ba/medias/407368591.jpg?context=bWFzdGVyfHJvb3R8MTM2MTZ8aW1hZ2UvanBlZ3xhR1F5TDJnell5ODBOekExTmpFMk1qRTJNRFkzTUM4ME1EY3pOamcxT1RFdWFuQm58ZWM3Zjc4MDNlZTY5OWU1ZGJiZDI5N2UzMDg4ODA3NzQ1NWM0OThlMjdhYmU4MjI4NGJhOWE2YzYwMTc5ODM3NQ', + description: + 'Zabavni-muzički program donosi nam divne zvukove prave, narodne muzike, u kojoj se izvođači oslanjaju na kvalitet i tradiciju.', + categories: ['Music', 'Ballet', 'Dance'] + }) + expect(results[37]).toMatchObject({ + start: '2025-02-04T22:27:00.000Z', + stop: '2025-02-04T23:58:00.000Z', + title: 'Bitanga s plaže', + image: + 'https://medias.services.mtel.ba/medias/117604203.jpg?context=bWFzdGVyfHJvb3R8MTY1MTZ8aW1hZ2UvanBlZ3xhRGd6TDJnek1DODBOekExTmpFMk16STNORGM0TWk4eE1UYzJNRFF5TURNdWFuQm58YmU5MjdkOTljMGE4YjIyNjg3ZmI1YWJjYWQ0ZDY5YjA0YWJiY2RlN2E0ZGVjOTdlYzM4MzI4MzYyMzFiODBlMg', + description: + 'Film prati urnebesne avanture Moondoga, buntovnika i skitnicu koji svoj život živi isključivo prema vlastitim pravilima. Uz glumačke nastupe Snoop Dogga, Zaca Efrona i Isle Fisher, Bitanga s plaže osvježavajuće je originalna i subverzivna nova komedija scenarista i redatelja Harmonyja Korinea.', + categories: ['Movie', 'Drama'] + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + channel, + content: '{}' + }) + expect(results).toMatchObject([]) +}) diff --git a/sites/mysky.com.ph/mysky.com.ph.test.js b/sites/mysky.com.ph/mysky.com.ph.test.js index fba104724..9a340a7e8 100644 --- a/sites/mysky.com.ph/mysky.com.ph.test.js +++ b/sites/mysky.com.ph/mysky.com.ph.test.js @@ -1,45 +1,45 @@ -const { parser, url } = require('./mysky.com.ph.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-10-04', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '8', - xmltv_id: 'KapamilyaChannel.ph' -} - -it('can generate valid url', () => { - expect(url).toBe('https://skyepg.mysky.com.ph/Main/getEventsbyType') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ content, channel, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2022-10-04T11:00:00.000Z', - stop: '2022-10-04T12:00:00.000Z', - title: 'TV PATROL', - description: 'Description example' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: '', - channel, - date - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./mysky.com.ph.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-10-04', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '8', + xmltv_id: 'KapamilyaChannel.ph' +} + +it('can generate valid url', () => { + expect(url).toBe('https://skyepg.mysky.com.ph/Main/getEventsbyType') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ content, channel, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2022-10-04T11:00:00.000Z', + stop: '2022-10-04T12:00:00.000Z', + title: 'TV PATROL', + description: 'Description example' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: '', + channel, + date + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/neo.io/neo.io.test.js b/sites/neo.io/neo.io.test.js index be6e23a41..afb05a550 100644 --- a/sites/neo.io/neo.io.test.js +++ b/sites/neo.io/neo.io.test.js @@ -1,61 +1,61 @@ -const { parser, url } = require('./neo.io.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2024-12-26', 'YYYY-MM-DD').startOf('day') -const channel = { - site_id: 'tv-slo-1', - xmltv_id: 'TVSLO1.si' -} - -it('can generate valid url', () => { - expect(url({ date, channel })).toBe( - 'https://stargate.telekom.si/api/titan.tv.WebEpg/GetWebEpgData' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ content, channel }) - - expect(result).toMatchObject([ - { - title: 'Napovedujemo', - description: 'Vabilo k ogledu naših oddaj.', - start: '2024-12-26T04:05:00.000Z', - stop: '2024-12-26T05:50:00.000Z', - thumbnail: - 'https://ngimg.siol.tv/sioltv/mtcmsprod/52/0/0/5200d01a-fe5f-487e-835a-274e77227a6b.jpg' - }, - { - title: 'S0E0 - Hrabri zajčki: Prvi sneg', - description: - 'Hrabri zajčki so prispeli v borov gozd in izkusili prvi sneg. Bob in Bu še nikoli nista videla snega. Mami kuha korenčkov kakav, Bu in Bob pa kmalu spoznata novega prijatelja, losa Danija.', - start: '2024-12-26T05:50:00.000Z', - stop: '2024-12-26T06:00:00.000Z', - thumbnail: - 'https://ngimg.siol.tv/sioltv/mtcmsprod/d6/4/5/d6456f4a-4f0a-4825-90c1-1749abd59688.jpg' - }, - { - title: 'Dobro jutro', - description: - 'Oddaja Dobro jutro poleg informativnih in zabavnih vsebin podaja koristne nasvete o najrazličnejših tematikah iz vsakdanjega življenja.', - start: '2024-12-26T06:00:00.000Z', - stop: '2024-12-26T09:05:00.000Z', - thumbnail: - 'https://ngimg.siol.tv/sioltv/mtcmsprod/e1/2/d/e12d8eb4-693a-43d3-89d4-fd96dade9f0f.jpg' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./neo.io.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2024-12-26', 'YYYY-MM-DD').startOf('day') +const channel = { + site_id: 'tv-slo-1', + xmltv_id: 'TVSLO1.si' +} + +it('can generate valid url', () => { + expect(url({ date, channel })).toBe( + 'https://stargate.telekom.si/api/titan.tv.WebEpg/GetWebEpgData' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ content, channel }) + + expect(result).toMatchObject([ + { + title: 'Napovedujemo', + description: 'Vabilo k ogledu naših oddaj.', + start: '2024-12-26T04:05:00.000Z', + stop: '2024-12-26T05:50:00.000Z', + thumbnail: + 'https://ngimg.siol.tv/sioltv/mtcmsprod/52/0/0/5200d01a-fe5f-487e-835a-274e77227a6b.jpg' + }, + { + title: 'S0E0 - Hrabri zajčki: Prvi sneg', + description: + 'Hrabri zajčki so prispeli v borov gozd in izkusili prvi sneg. Bob in Bu še nikoli nista videla snega. Mami kuha korenčkov kakav, Bu in Bob pa kmalu spoznata novega prijatelja, losa Danija.', + start: '2024-12-26T05:50:00.000Z', + stop: '2024-12-26T06:00:00.000Z', + thumbnail: + 'https://ngimg.siol.tv/sioltv/mtcmsprod/d6/4/5/d6456f4a-4f0a-4825-90c1-1749abd59688.jpg' + }, + { + title: 'Dobro jutro', + description: + 'Oddaja Dobro jutro poleg informativnih in zabavnih vsebin podaja koristne nasvete o najrazličnejših tematikah iz vsakdanjega življenja.', + start: '2024-12-26T06:00:00.000Z', + stop: '2024-12-26T09:05:00.000Z', + thumbnail: + 'https://ngimg.siol.tv/sioltv/mtcmsprod/e1/2/d/e12d8eb4-693a-43d3-89d4-fd96dade9f0f.jpg' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/novacyprus.com/novacyprus.com.test.js b/sites/novacyprus.com/novacyprus.com.test.js index 0f4de631a..c2585d924 100644 --- a/sites/novacyprus.com/novacyprus.com.test.js +++ b/sites/novacyprus.com/novacyprus.com.test.js @@ -1,49 +1,49 @@ -const { parser, url } = require('./novacyprus.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2021-11-17', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '614', - xmltv_id: 'NovaCinema1.gr' -} - -it('can generate valid url', () => { - expect(url({ date })).toBe( - 'https://www.novacyprus.com/api/v1/tvprogram/from/20211117/to/20211118' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ content, channel }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2021-11-17T04:20:00.000Z', - stop: '2021-11-17T06:10:00.000Z', - title: 'Δεσμοί Αίματος', - description: 'Θρίλερ Μυστηρίου', - image: - 'http://cache-forthnet.secure.footprint.net/linear/3/0/305608_COMMOBLOOX_GUIDE_STILL.jpg' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./novacyprus.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2021-11-17', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '614', + xmltv_id: 'NovaCinema1.gr' +} + +it('can generate valid url', () => { + expect(url({ date })).toBe( + 'https://www.novacyprus.com/api/v1/tvprogram/from/20211117/to/20211118' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ content, channel }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2021-11-17T04:20:00.000Z', + stop: '2021-11-17T06:10:00.000Z', + title: 'Δεσμοί Αίματος', + description: 'Θρίλερ Μυστηρίου', + image: + 'http://cache-forthnet.secure.footprint.net/linear/3/0/305608_COMMOBLOOX_GUIDE_STILL.jpg' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/nowplayer.now.com/nowplayer.now.com.test.js b/sites/nowplayer.now.com/nowplayer.now.com.test.js index 39e90077a..0ebc99b31 100644 --- a/sites/nowplayer.now.com/nowplayer.now.com.test.js +++ b/sites/nowplayer.now.com/nowplayer.now.com.test.js @@ -1,58 +1,58 @@ -const { parser, url, request } = require('./nowplayer.now.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const channel = { - lang: 'zh', - site_id: '096', - xmltv_id: 'ViuTVsix.hk' -} - -it('can generate valid url for today', () => { - const date = dayjs.utc().startOf('d') - expect(url({ channel, date })).toBe( - 'https://nowplayer.now.com/tvguide/epglist?channelIdList[]=096&day=1' - ) -}) - -it('can generate valid url for tomorrow', () => { - const date = dayjs.utc().startOf('d').add(1, 'd') - expect(url({ channel, date })).toBe( - 'https://nowplayer.now.com/tvguide/epglist?channelIdList[]=096&day=2' - ) -}) - -it('can generate valid request headers', () => { - expect(request.headers({ channel })).toMatchObject({ - Cookie: 'LANG=zh; Expires=null; Path=/; Domain=nowplayer.now.com' - }) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2021-11-23T18:00:00.000Z', - stop: '2021-11-24T01:00:00.000Z', - title: 'ViuTVsix Station Closing' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: '[[]]' - }) - expect(result).toMatchObject([]) -}) +const { parser, url, request } = require('./nowplayer.now.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const channel = { + lang: 'zh', + site_id: '096', + xmltv_id: 'ViuTVsix.hk' +} + +it('can generate valid url for today', () => { + const date = dayjs.utc().startOf('d') + expect(url({ channel, date })).toBe( + 'https://nowplayer.now.com/tvguide/epglist?channelIdList[]=096&day=1' + ) +}) + +it('can generate valid url for tomorrow', () => { + const date = dayjs.utc().startOf('d').add(1, 'd') + expect(url({ channel, date })).toBe( + 'https://nowplayer.now.com/tvguide/epglist?channelIdList[]=096&day=2' + ) +}) + +it('can generate valid request headers', () => { + expect(request.headers({ channel })).toMatchObject({ + Cookie: 'LANG=zh; Expires=null; Path=/; Domain=nowplayer.now.com' + }) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2021-11-23T18:00:00.000Z', + stop: '2021-11-24T01:00:00.000Z', + title: 'ViuTVsix Station Closing' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: '[[]]' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/ontvtonight.com/ontvtonight.com.config.js b/sites/ontvtonight.com/ontvtonight.com.config.js index 0595d2081..eac558a9e 100644 --- a/sites/ontvtonight.com/ontvtonight.com.config.js +++ b/sites/ontvtonight.com/ontvtonight.com.config.js @@ -1,178 +1,178 @@ -const axios = require('axios') -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') -const { uniqBy } = require('../../scripts/functions') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'ontvtonight.com', - days: 2, - url: function ({ date, channel }) { - const [region, id] = channel.site_id.split('#') - let url = 'https://www.ontvtonight.com' - if (region && region !== 'us') url += `/${region}` - url += `/guide/listings/channel/${id}.html?dt=${date.format('YYYY-MM-DD')}` - - return url - }, - parser: function ({ content, date, channel }) { - const programs = [] - const items = parseItems(content) - items.forEach(item => { - const prev = programs[programs.length - 1] - const $item = cheerio.load(item) - let start = parseStart($item, date, channel) - if (prev) { - if (start.isBefore(prev.start)) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.add(1, 'h') - programs.push({ - title: parseTitle($item), - description: parseDescription($item), - start, - stop - }) - }) - - return programs - }, - async channels({ country }) { - const providers = { - au: ['o', 'a'], - ca: [ - 'Y464014423', - '-464014503', - '-464014594', - '-464014738', - 'X3153330286', - 'X464014503', - 'X464013696', - 'X464014594', - 'X464014738', - 'X464014470', - 'X464013514', - 'X1210684931', - 'T3153330286', - 'T464014503', - 'T1810267316', - 'T1210684931' - ], - us: [ - 'Y341768590', - 'Y1693286984', - 'Y8833268284', - '-341767428', - '-341769166', - '-341769884', - '-3679985536', - '-341766967', - 'X4100694897', - 'X341767428', - 'X341768182', - 'X341767434', - 'X341768272', - 'X341769884', - 'X3679985536', - 'X3679984937', - 'X341764975', - 'X3679985052', - 'X341766967', - 'K4805071612', - 'K5039655414' - ] - } - const regions = { - au: [ - 1, 2, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 17, 18, 29, 28, 27, 26, 25, 23, 22, - 21, 20, 19, 24, 30, 31, 32, 33, 34, 35, 36, 39, 38, 37, 40, 41, 42, 43, 44, 45, 46, 47, 48, - 49, 50, 51, 52, 53 - ], - ca: [null], - us: [null] - } - const zipcodes = { - au: [null], - ca: ['M5G1P5', 'H3B1X8', 'V6Z2H7', 'T2P3E6', 'T5J2Z2', 'K1P1B1'], - us: [10199, 90052, 60607, 77201, 85026, 19104, 78284, 92199, 75260] - } - - const channels = [] - for (let provider of providers[country]) { - for (let zipcode of zipcodes[country]) { - for (let region of regions[country]) { - let url = 'https://www.ontvtonight.com' - if (country === 'us') url += '/guide/schedule' - else url += `/${country}/guide/schedule` - const data = await axios - .post(url, null, { - params: { - provider, - region, - zipcode, - TVperiod: 'Night', - date: dayjs().format('YYYY-MM-DD'), - st: 0, - is_mobile: 1 - } - }) - .then(r => r.data) - .catch(console.log) - - const $ = cheerio.load(data) - $('.channelname').each((i, el) => { - let name = $(el).find('center > a:eq(1)').text() - name = name.replace(/--/gi, '-') - const url = $(el).find('center > a:eq(1)').attr('href') - if (!url) return - const [, number, slug] = url.match(/\/(\d+)\/(.*)\.html$/) - - channels.push({ - lang: 'en', - name, - site_id: `${country}#${number}/${slug}` - }) - }) - } - } - } - - return uniqBy(channels, 'site_id') - } -} - -function parseStart($item, date, channel) { - const timezones = { - au: 'Australia/Sydney', - ca: 'America/Toronto', - us: 'America/New_York' - } - const [region] = channel.site_id.split('#') - const timeString = $item('td:nth-child(1) > h5').text().trim() - const dateString = `${date.format('YYYY-MM-DD')} ${timeString}` - - return dayjs.tz(dateString, 'YYYY-MM-DD H:mm a', timezones[region]) -} - -function parseTitle($item) { - return $item('td:nth-child(2) > h5').text().trim() -} - -function parseDescription($item) { - return $item('td:nth-child(2) > h6').text().trim() -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('#content > div > div > div > table > tbody > tr').toArray() -} +const axios = require('axios') +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const { uniqBy } = require('../../scripts/functions') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'ontvtonight.com', + days: 2, + url: function ({ date, channel }) { + const [region, id] = channel.site_id.split('#') + let url = 'https://www.ontvtonight.com' + if (region && region !== 'us') url += `/${region}` + url += `/guide/listings/channel/${id}.html?dt=${date.format('YYYY-MM-DD')}` + + return url + }, + parser: function ({ content, date, channel }) { + const programs = [] + const items = parseItems(content) + items.forEach(item => { + const prev = programs[programs.length - 1] + const $item = cheerio.load(item) + let start = parseStart($item, date, channel) + if (prev) { + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.add(1, 'h') + programs.push({ + title: parseTitle($item), + description: parseDescription($item), + start, + stop + }) + }) + + return programs + }, + async channels({ country }) { + const providers = { + au: ['o', 'a'], + ca: [ + 'Y464014423', + '-464014503', + '-464014594', + '-464014738', + 'X3153330286', + 'X464014503', + 'X464013696', + 'X464014594', + 'X464014738', + 'X464014470', + 'X464013514', + 'X1210684931', + 'T3153330286', + 'T464014503', + 'T1810267316', + 'T1210684931' + ], + us: [ + 'Y341768590', + 'Y1693286984', + 'Y8833268284', + '-341767428', + '-341769166', + '-341769884', + '-3679985536', + '-341766967', + 'X4100694897', + 'X341767428', + 'X341768182', + 'X341767434', + 'X341768272', + 'X341769884', + 'X3679985536', + 'X3679984937', + 'X341764975', + 'X3679985052', + 'X341766967', + 'K4805071612', + 'K5039655414' + ] + } + const regions = { + au: [ + 1, 2, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 17, 18, 29, 28, 27, 26, 25, 23, 22, + 21, 20, 19, 24, 30, 31, 32, 33, 34, 35, 36, 39, 38, 37, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53 + ], + ca: [null], + us: [null] + } + const zipcodes = { + au: [null], + ca: ['M5G1P5', 'H3B1X8', 'V6Z2H7', 'T2P3E6', 'T5J2Z2', 'K1P1B1'], + us: [10199, 90052, 60607, 77201, 85026, 19104, 78284, 92199, 75260] + } + + const channels = [] + for (let provider of providers[country]) { + for (let zipcode of zipcodes[country]) { + for (let region of regions[country]) { + let url = 'https://www.ontvtonight.com' + if (country === 'us') url += '/guide/schedule' + else url += `/${country}/guide/schedule` + const data = await axios + .post(url, null, { + params: { + provider, + region, + zipcode, + TVperiod: 'Night', + date: dayjs().format('YYYY-MM-DD'), + st: 0, + is_mobile: 1 + } + }) + .then(r => r.data) + .catch(console.log) + + const $ = cheerio.load(data) + $('.channelname').each((i, el) => { + let name = $(el).find('center > a:eq(1)').text() + name = name.replace(/--/gi, '-') + const url = $(el).find('center > a:eq(1)').attr('href') + if (!url) return + const [, number, slug] = url.match(/\/(\d+)\/(.*)\.html$/) + + channels.push({ + lang: 'en', + name, + site_id: `${country}#${number}/${slug}` + }) + }) + } + } + } + + return uniqBy(channels, 'site_id') + } +} + +function parseStart($item, date, channel) { + const timezones = { + au: 'Australia/Sydney', + ca: 'America/Toronto', + us: 'America/New_York' + } + const [region] = channel.site_id.split('#') + const timeString = $item('td:nth-child(1) > h5').text().trim() + const dateString = `${date.format('YYYY-MM-DD')} ${timeString}` + + return dayjs.tz(dateString, 'YYYY-MM-DD H:mm a', timezones[region]) +} + +function parseTitle($item) { + return $item('td:nth-child(2) > h5').text().trim() +} + +function parseDescription($item) { + return $item('td:nth-child(2) > h6').text().trim() +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('#content > div > div > div > table > tbody > tr').toArray() +} diff --git a/sites/ontvtonight.com/ontvtonight.com.test.js b/sites/ontvtonight.com/ontvtonight.com.test.js index 9f1eaf151..a40b980eb 100644 --- a/sites/ontvtonight.com/ontvtonight.com.test.js +++ b/sites/ontvtonight.com/ontvtonight.com.test.js @@ -1,57 +1,57 @@ -const { parser, url } = require('./ontvtonight.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2021-11-25', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'au#1692/7two', - xmltv_id: '7two.au' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://www.ontvtonight.com/au/guide/listings/channel/1692/7two.html?dt=2021-11-25' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') - const result = parser({ content, channel, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2021-11-24T13:10:00.000Z', - stop: '2021-11-24T13:50:00.000Z', - title: 'What A Carry On' - }, - { - start: '2021-11-24T13:50:00.000Z', - stop: '2021-11-25T11:50:00.000Z', - title: 'Bones', - description: 'The Devil In The Details' - }, - { - start: '2021-11-25T11:50:00.000Z', - stop: '2021-11-25T12:50:00.000Z', - title: 'Inspector Morse: The Remorseful Day' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./ontvtonight.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2021-11-25', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'au#1692/7two', + xmltv_id: '7two.au' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://www.ontvtonight.com/au/guide/listings/channel/1692/7two.html?dt=2021-11-25' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') + const result = parser({ content, channel, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2021-11-24T13:10:00.000Z', + stop: '2021-11-24T13:50:00.000Z', + title: 'What A Carry On' + }, + { + start: '2021-11-24T13:50:00.000Z', + stop: '2021-11-25T11:50:00.000Z', + title: 'Bones', + description: 'The Devil In The Details' + }, + { + start: '2021-11-25T11:50:00.000Z', + stop: '2021-11-25T12:50:00.000Z', + title: 'Inspector Morse: The Remorseful Day' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/pbsguam.org/pbsguam.org.test.js b/sites/pbsguam.org/pbsguam.org.test.js index 5921d3ccf..dd6984fa7 100644 --- a/sites/pbsguam.org/pbsguam.org.test.js +++ b/sites/pbsguam.org/pbsguam.org.test.js @@ -1,44 +1,44 @@ -const { parser, url } = require('./pbsguam.org.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2021-11-25', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '#', - xmltv_id: 'KGTF.us' -} - -it('can generate valid url', () => { - expect(url).toBe('https://pbsguam.org/calendar/') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') - const result = parser({ date, content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2021-11-25T08:30:00.000Z', - stop: '2021-11-25T09:00:00.000Z', - title: 'Xavier Riddle and the Secret Museum' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./pbsguam.org.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2021-11-25', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '#', + xmltv_id: 'KGTF.us' +} + +it('can generate valid url', () => { + expect(url).toBe('https://pbsguam.org/calendar/') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') + const result = parser({ date, content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2021-11-25T08:30:00.000Z', + stop: '2021-11-25T09:00:00.000Z', + title: 'Xavier Riddle and the Secret Museum' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/programetv.ro/programetv.ro.config.js b/sites/programetv.ro/programetv.ro.config.js index 344e65b3d..86f34f586 100644 --- a/sites/programetv.ro/programetv.ro.config.js +++ b/sites/programetv.ro/programetv.ro.config.js @@ -1,95 +1,95 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') - -dayjs.extend(utc) - -module.exports = { - site: 'programetv.ro', - days: 2, - url: function ({ date, channel }) { - const daysOfWeek = { - 0: 'duminica', - 1: 'luni', - 2: 'marti', - 3: 'miercuri', - 4: 'joi', - 5: 'vineri', - 6: 'sambata' - } - const day = date.day() - - return `https://www.programetv.ro/program-tv/${channel.site_id}/${daysOfWeek[day]}/` - }, - parser: function ({ content }) { - let programs = [] - const data = parseContent(content) - if (!data || !data.shows) return programs - const items = data.shows - items.forEach(item => { - programs.push({ - title: item.title, - sub_title: item.titleOriginal, - description: item.desc || item.obs, - category: item.categories, - season: item.season || null, - episode: item.episode || null, - start: parseStart(item), - stop: parseStop(item), - url: item.url || null, - date: item.date, - rating: parseRating(item), - directors: parseDirector(item), - actors: parseActor(item), - icon: item.icon - }) - }) - - return programs - }, - async channels() { - const axios = require('axios') - const data = await axios - .get('https://www.programetv.ro/api/station/index/') - .then(r => r.data) - .catch(console.log) - - return data.map(item => { - return { - lang: 'ro', - site_id: item.slug, - name: item.displayName - } - }) - } -} - -function parseStart(item) { - return dayjs(item.start).toJSON() -} - -function parseStop(item) { - return dayjs(item.stop).toJSON() -} - -function parseContent(content) { - const [, data] = content.match(/var pageData = ({.+?});/) || [null, null] - - return data ? JSON.parse(data) : {} -} - -function parseDirector(item) { - return item.credits && item.credits.director ? item.credits.director : null -} - -function parseActor(item) { - return item.credits && item.credits.actor ? item.credits.actor : null -} - -function parseRating(item) { - return item.rating - ? { - system: 'CNC', - value: item.rating.toUpperCase() - } - : null -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') + +dayjs.extend(utc) + +module.exports = { + site: 'programetv.ro', + days: 2, + url: function ({ date, channel }) { + const daysOfWeek = { + 0: 'duminica', + 1: 'luni', + 2: 'marti', + 3: 'miercuri', + 4: 'joi', + 5: 'vineri', + 6: 'sambata' + } + const day = date.day() + + return `https://www.programetv.ro/program-tv/${channel.site_id}/${daysOfWeek[day]}/` + }, + parser: function ({ content }) { + let programs = [] + const data = parseContent(content) + if (!data || !data.shows) return programs + const items = data.shows + items.forEach(item => { + programs.push({ + title: item.title, + sub_title: item.titleOriginal, + description: item.desc || item.obs, + category: item.categories, + season: item.season || null, + episode: item.episode || null, + start: parseStart(item), + stop: parseStop(item), + url: item.url || null, + date: item.date, + rating: parseRating(item), + directors: parseDirector(item), + actors: parseActor(item), + icon: item.icon + }) + }) + + return programs + }, + async channels() { + const axios = require('axios') + const data = await axios + .get('https://www.programetv.ro/api/station/index/') + .then(r => r.data) + .catch(console.log) + + return data.map(item => { + return { + lang: 'ro', + site_id: item.slug, + name: item.displayName + } + }) + } +} + +function parseStart(item) { + return dayjs(item.start).toJSON() +} + +function parseStop(item) { + return dayjs(item.stop).toJSON() +} + +function parseContent(content) { + const [, data] = content.match(/var pageData = ({.+?});/) || [null, null] + + return data ? JSON.parse(data) : {} +} + +function parseDirector(item) { + return item.credits && item.credits.director ? item.credits.director : null +} + +function parseActor(item) { + return item.credits && item.credits.actor ? item.credits.actor : null +} + +function parseRating(item) { + return item.rating + ? { + system: 'CNC', + value: item.rating.toUpperCase() + } + : null +} diff --git a/sites/programetv.ro/programetv.ro.test.js b/sites/programetv.ro/programetv.ro.test.js index 22731737d..a5ef779a9 100644 --- a/sites/programetv.ro/programetv.ro.test.js +++ b/sites/programetv.ro/programetv.ro.test.js @@ -1,42 +1,42 @@ -const { parser, url } = require('./programetv.ro.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2021-10-24', 'YYYY-MM-DD').startOf('d') -const channel = { site_id: 'pro-tv', xmltv_id: 'ProTV.ro' } - -it('can generate valid url', () => { - const result = url({ date, channel }) - expect(result).toBe('https://www.programetv.ro/program-tv/pro-tv/duminica/') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') - const result = parser({ date, channel, content }) - expect(result).toMatchObject([ - { - start: '2021-11-07T05:00:00.000Z', - stop: '2021-11-07T07:59:59.000Z', - title: 'Ştirile Pro Tv', - description: - 'În fiecare zi, cele mai importante evenimente, transmisiuni LIVE, analize, anchete şi reportaje sunt la Ştirile ProTV.', - category: ['Ştiri'], - icon: 'https://www.programetv.ro/img/shows/84/54/stirile-pro-tv.png?key=Z2lfZnVial90cmFyZXZwLzAwLzAwLzA1LzE4MzgxMnktMTIwazE3MC1hLW40NTk4MW9zLmNhdA==' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') - - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./programetv.ro.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2021-10-24', 'YYYY-MM-DD').startOf('d') +const channel = { site_id: 'pro-tv', xmltv_id: 'ProTV.ro' } + +it('can generate valid url', () => { + const result = url({ date, channel }) + expect(result).toBe('https://www.programetv.ro/program-tv/pro-tv/duminica/') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') + const result = parser({ date, channel, content }) + expect(result).toMatchObject([ + { + start: '2021-11-07T05:00:00.000Z', + stop: '2021-11-07T07:59:59.000Z', + title: 'Ştirile Pro Tv', + description: + 'În fiecare zi, cele mai importante evenimente, transmisiuni LIVE, analize, anchete şi reportaje sunt la Ştirile ProTV.', + category: ['Ştiri'], + icon: 'https://www.programetv.ro/img/shows/84/54/stirile-pro-tv.png?key=Z2lfZnVial90cmFyZXZwLzAwLzAwLzA1LzE4MzgxMnktMTIwazE3MC1hLW40NTk4MW9zLmNhdA==' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') + + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/programme-tv.vini.pf/programme-tv.vini.pf.test.js b/sites/programme-tv.vini.pf/programme-tv.vini.pf.test.js index ccbb8e416..1dba5bcd6 100644 --- a/sites/programme-tv.vini.pf/programme-tv.vini.pf.test.js +++ b/sites/programme-tv.vini.pf/programme-tv.vini.pf.test.js @@ -1,107 +1,107 @@ -const { parser, url, request } = require('./programme-tv.vini.pf.config.js') -const fs = require('fs') -const path = require('path') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -const date = dayjs.utc('2021-11-21', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'tf1', - xmltv_id: 'TF1.fr' -} - -it('can generate valid url', () => { - expect(url).toBe('https://programme-tv.vini.pf/programmesJSON') -}) - -it('can generate valid request method', () => { - expect(request.method).toBe('POST') -}) - -it('can generate valid request data', () => { - expect(request.data({ date })).toMatchObject({ dateDebut: '2021-11-20T14:00:00-10:00' }) -}) - -it('can parse response', done => { - axios.post.mockImplementation((url, data) => { - if (data.dateDebut === '2021-11-20T16:00:00-10:00') { - return Promise.resolve({ - data: Buffer.from(fs.readFileSync(path.resolve(__dirname, '__data__/content_1.json'))) - }) - } else { - return Promise.resolve({ - data: Buffer.from(fs.readFileSync(path.resolve(__dirname, '__data__/content_2.json'))) - }) - } -}) - - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - - parser({ content, channel, date }) - .then(result => { - result = result.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - expect(result).toMatchObject([ - { - start: '2021-11-20T23:50:00.000Z', - stop: '2021-11-21T01:10:00.000Z', - title: 'Reportages découverte', - category: 'Magazine', - description: - "Pour faire face à la crise du logement, aux loyers toujours plus élevés, à la solitude ou pour les gardes d'enfants, les colocations ont le vent en poupe, Pour mieux comprendre ce nouveau phénomène, une équipe a partagé le quotidien de quatre foyers : une retraitée qui héberge des étudiants, des mamans solos, enceintes, qui partagent un appartement associatif, trois générations de la même famille sur un domaine viticole et une étudiante qui intègre une colocation XXL.", - image: - 'https://programme-tv.vini.pf/sites/default/files/img-icones/52ada51ed86b7e7bc11eaee83ff2192785989d77.jpg' - }, - { - start: '2021-11-21T01:10:00.000Z', - stop: '2021-11-21T02:30:00.000Z', - title: 'Les docs du week-end', - category: 'Magazine', - description: - 'Un documentaire français réalisé en 2019, Cindy Sander, Myriam Abel, Mario, Michal ou encore Magali Vaé ont fait les grandes heures des premières émissions de télécrochet modernes, dans les années 2000, Des années après leur passage, que reste-t-il de leur notoriété ? Comment ces candidats ont-ils vécu leur soudaine médiatisation ? Quels rapports entretenaient-ils avec les autres participants et les membres du jury, souvent intransigeants ?', - image: - 'https://programme-tv.vini.pf/sites/default/files/img-icones/6e64cfbc55c1f4cbd11e3011401403d4dc08c6d2.jpg' - }, - { - start: '2021-11-21T02:30:00.000Z', - stop: '2021-11-21T03:45:00.000Z', - title: '50mn Inside', - category: 'Magazine', - description: - "50'INSIDE, c'est toute l'actualité des stars résumée, chaque samedi, Le rendez-vous glamour pour retrouver toujours,,", - image: - 'https://programme-tv.vini.pf/sites/default/files/img-icones/3d7e252312dacb5fb7a1a786fa0022ca1be15499.jpg' - } - ]) - done() - }) - .catch(err => { - done(err) - }) -}) - -it('can handle empty guide', done => { - parser({ - date, - channel, - content: - '' - }) - .then(result => { - expect(result).toMatchObject([]) - done() - }) - .catch(err => { - done(err) - }) -}) +const { parser, url, request } = require('./programme-tv.vini.pf.config.js') +const fs = require('fs') +const path = require('path') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.utc('2021-11-21', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'tf1', + xmltv_id: 'TF1.fr' +} + +it('can generate valid url', () => { + expect(url).toBe('https://programme-tv.vini.pf/programmesJSON') +}) + +it('can generate valid request method', () => { + expect(request.method).toBe('POST') +}) + +it('can generate valid request data', () => { + expect(request.data({ date })).toMatchObject({ dateDebut: '2021-11-20T14:00:00-10:00' }) +}) + +it('can parse response', done => { + axios.post.mockImplementation((url, data) => { + if (data.dateDebut === '2021-11-20T16:00:00-10:00') { + return Promise.resolve({ + data: Buffer.from(fs.readFileSync(path.resolve(__dirname, '__data__/content_1.json'))) + }) + } else { + return Promise.resolve({ + data: Buffer.from(fs.readFileSync(path.resolve(__dirname, '__data__/content_2.json'))) + }) + } +}) + + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + + parser({ content, channel, date }) + .then(result => { + result = result.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + expect(result).toMatchObject([ + { + start: '2021-11-20T23:50:00.000Z', + stop: '2021-11-21T01:10:00.000Z', + title: 'Reportages découverte', + category: 'Magazine', + description: + "Pour faire face à la crise du logement, aux loyers toujours plus élevés, à la solitude ou pour les gardes d'enfants, les colocations ont le vent en poupe, Pour mieux comprendre ce nouveau phénomène, une équipe a partagé le quotidien de quatre foyers : une retraitée qui héberge des étudiants, des mamans solos, enceintes, qui partagent un appartement associatif, trois générations de la même famille sur un domaine viticole et une étudiante qui intègre une colocation XXL.", + image: + 'https://programme-tv.vini.pf/sites/default/files/img-icones/52ada51ed86b7e7bc11eaee83ff2192785989d77.jpg' + }, + { + start: '2021-11-21T01:10:00.000Z', + stop: '2021-11-21T02:30:00.000Z', + title: 'Les docs du week-end', + category: 'Magazine', + description: + 'Un documentaire français réalisé en 2019, Cindy Sander, Myriam Abel, Mario, Michal ou encore Magali Vaé ont fait les grandes heures des premières émissions de télécrochet modernes, dans les années 2000, Des années après leur passage, que reste-t-il de leur notoriété ? Comment ces candidats ont-ils vécu leur soudaine médiatisation ? Quels rapports entretenaient-ils avec les autres participants et les membres du jury, souvent intransigeants ?', + image: + 'https://programme-tv.vini.pf/sites/default/files/img-icones/6e64cfbc55c1f4cbd11e3011401403d4dc08c6d2.jpg' + }, + { + start: '2021-11-21T02:30:00.000Z', + stop: '2021-11-21T03:45:00.000Z', + title: '50mn Inside', + category: 'Magazine', + description: + "50'INSIDE, c'est toute l'actualité des stars résumée, chaque samedi, Le rendez-vous glamour pour retrouver toujours,,", + image: + 'https://programme-tv.vini.pf/sites/default/files/img-icones/3d7e252312dacb5fb7a1a786fa0022ca1be15499.jpg' + } + ]) + done() + }) + .catch(err => { + done(err) + }) +}) + +it('can handle empty guide', done => { + parser({ + date, + channel, + content: + '' + }) + .then(result => { + expect(result).toMatchObject([]) + done() + }) + .catch(err => { + done(err) + }) +}) diff --git a/sites/programtv.onet.pl/programtv.onet.pl.test.js b/sites/programtv.onet.pl/programtv.onet.pl.test.js index 3cfca2bfe..03bbd4818 100644 --- a/sites/programtv.onet.pl/programtv.onet.pl.test.js +++ b/sites/programtv.onet.pl/programtv.onet.pl.test.js @@ -1,76 +1,76 @@ -const MockDate = require('mockdate') -const { parser, url } = require('./programtv.onet.pl.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2021-11-24', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '13th-street-250', - xmltv_id: '13thStreet.de' -} - -it('can generate valid url', () => { - MockDate.set(dayjs.utc('2021-11-24', 'YYYY-MM-DD').startOf('d')) - expect(url({ channel, date })).toBe( - 'https://programtv.onet.pl/program-tv/13th-street-250?dzien=0' - ) - MockDate.reset() -}) - -it('can generate valid url for next day', () => { - MockDate.set(dayjs.utc('2021-11-23', 'YYYY-MM-DD').startOf('d')) - expect(url({ channel, date })).toBe( - 'https://programtv.onet.pl/program-tv/13th-street-250?dzien=1' - ) - MockDate.reset() -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - const result = parser({ content, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2021-11-24T02:20:00.000Z', - stop: '2021-11-24T22:30:00.000Z', - title: 'Law & Order, odc. 15: Letzte Worte', - category: 'Krimiserie', - description: - 'Bei einer Reality-TV-Show stirbt einer der Teilnehmer. Zunächst tappen Briscoe (Jerry Orbach) und Green (Jesse L....' - }, - { - start: '2021-11-24T22:30:00.000Z', - stop: '2021-11-25T00:00:00.000Z', - title: 'Navy CIS, odc. 1: New Orleans', - category: 'Krimiserie', - description: - 'Der Abgeordnete Dan McLane, ein ehemaliger Vorgesetzter von Gibbs, wird in New Orleans ermordet. In den 90er Jahren...' - }, - { - start: '2021-11-25T00:00:00.000Z', - stop: '2021-11-25T01:00:00.000Z', - title: 'Navy CIS: L.A, odc. 13: High Society', - category: 'Krimiserie', - description: - 'Die Zahl der Drogentoten ist gestiegen. Das Team des NCIS glaubt, dass sich Terroristen durch den zunehmenden...' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) - }) - expect(result).toMatchObject([]) -}) +const MockDate = require('mockdate') +const { parser, url } = require('./programtv.onet.pl.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2021-11-24', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '13th-street-250', + xmltv_id: '13thStreet.de' +} + +it('can generate valid url', () => { + MockDate.set(dayjs.utc('2021-11-24', 'YYYY-MM-DD').startOf('d')) + expect(url({ channel, date })).toBe( + 'https://programtv.onet.pl/program-tv/13th-street-250?dzien=0' + ) + MockDate.reset() +}) + +it('can generate valid url for next day', () => { + MockDate.set(dayjs.utc('2021-11-23', 'YYYY-MM-DD').startOf('d')) + expect(url({ channel, date })).toBe( + 'https://programtv.onet.pl/program-tv/13th-street-250?dzien=1' + ) + MockDate.reset() +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + const result = parser({ content, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2021-11-24T02:20:00.000Z', + stop: '2021-11-24T22:30:00.000Z', + title: 'Law & Order, odc. 15: Letzte Worte', + category: 'Krimiserie', + description: + 'Bei einer Reality-TV-Show stirbt einer der Teilnehmer. Zunächst tappen Briscoe (Jerry Orbach) und Green (Jesse L....' + }, + { + start: '2021-11-24T22:30:00.000Z', + stop: '2021-11-25T00:00:00.000Z', + title: 'Navy CIS, odc. 1: New Orleans', + category: 'Krimiserie', + description: + 'Der Abgeordnete Dan McLane, ein ehemaliger Vorgesetzter von Gibbs, wird in New Orleans ermordet. In den 90er Jahren...' + }, + { + start: '2021-11-25T00:00:00.000Z', + stop: '2021-11-25T01:00:00.000Z', + title: 'Navy CIS: L.A, odc. 13: High Society', + category: 'Krimiserie', + description: + 'Die Zahl der Drogentoten ist gestiegen. Das Team des NCIS glaubt, dass sich Terroristen durch den zunehmenden...' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/raiplay.it/raiplay.it.test.js b/sites/raiplay.it/raiplay.it.test.js index 167008634..27bed4b29 100644 --- a/sites/raiplay.it/raiplay.it.test.js +++ b/sites/raiplay.it/raiplay.it.test.js @@ -1,50 +1,50 @@ -const { parser, url } = require('./raiplay.it.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-05-03', 'YYYY-MM-DD') -const channel = { - site_id: 'rai-2', - xmltv_id: 'Rai2.it' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe('https://www.raiplay.it/palinsesto/app/rai-2/03-05-2022.json') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ content, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - - return p - }) - - expect(result).toMatchObject([ - { - start: '2022-05-03T17:40:00.000Z', - stop: '2022-05-03T18:30:00.000Z', - title: 'The Good Doctor S3E5 - La prima volta', - description: - "Shaun affronta il suo primo intervento. Il caso si rivela complicato e, nonostante Shaun abbia un'idea geniale, sarà Andrews a portare a termine l'operazione.", - season: '3', - episode: '5', - sub_title: 'La prima volta', - image: 'https://www.raiplay.it/dl/img/2020/03/09/1583748471860_dddddd.jpg', - url: 'https://www.raiplay.it/dirette/rai2/The-Good-Doctor-S3E5---La-prima-volta-2f81030d-803b-456a-9ea5-40233234fd9d.html' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./raiplay.it.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-05-03', 'YYYY-MM-DD') +const channel = { + site_id: 'rai-2', + xmltv_id: 'Rai2.it' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe('https://www.raiplay.it/palinsesto/app/rai-2/03-05-2022.json') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ content, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + + return p + }) + + expect(result).toMatchObject([ + { + start: '2022-05-03T17:40:00.000Z', + stop: '2022-05-03T18:30:00.000Z', + title: 'The Good Doctor S3E5 - La prima volta', + description: + "Shaun affronta il suo primo intervento. Il caso si rivela complicato e, nonostante Shaun abbia un'idea geniale, sarà Andrews a portare a termine l'operazione.", + season: '3', + episode: '5', + sub_title: 'La prima volta', + image: 'https://www.raiplay.it/dl/img/2020/03/09/1583748471860_dddddd.jpg', + url: 'https://www.raiplay.it/dirette/rai2/The-Good-Doctor-S3E5---La-prima-volta-2f81030d-803b-456a-9ea5-40233234fd9d.html' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/reportv.com.ar/reportv.com.ar.config.js b/sites/reportv.com.ar/reportv.com.ar.config.js index 29fc8a7b8..cfe6141e5 100644 --- a/sites/reportv.com.ar/reportv.com.ar.config.js +++ b/sites/reportv.com.ar/reportv.com.ar.config.js @@ -1,170 +1,170 @@ -require('dayjs/locale/es') -const axios = require('axios') -const dayjs = require('dayjs') -const cheerio = require('cheerio') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') -const { startCase } = require('../../scripts/functions') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'reportv.com.ar', - days: 2, - request: { - cache: { - ttl: 60 * 60 * 1000 // 1 hour - }, - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - }, - data({ channel, date }) { - const formData = new URLSearchParams() - formData.append('idSenial', channel.site_id) - formData.append('Alineacion', '2694') - formData.append('DiaDesde', date.format('YYYY/MM/DD')) - formData.append('HoraDesde', '00:00:00') - - return formData - } - }, - url: 'https://www.reportv.com.ar/buscador/ProgXSenial.php', - parser: async function ({ content, date }) { - let programs = [] - const items = parseItems(content, date) - for (let item of items) { - const $item = cheerio.load(item) - const start = parseStart($item, date) - const duration = parseDuration($item) - const stop = start.add(duration, 's') - const details = await loadProgramDetails($item) - programs.push({ - title: parseTitle($item), - category: parseCategory($item), - image: details.image, - description: details.description, - directors: details.directors, - actors: details.actors, - start, - stop - }) - } - - return programs - }, - async channels() { - const content = await axios - .get('https://www.reportv.com.ar/buscador/Buscador.php?aid=2694') - .then(r => r.data) - .catch(console.log) - const $ = cheerio.load(content) - const items = $('#tr_home_2 > td:nth-child(1) > select > option').toArray() - - return items.map(item => { - return { - lang: 'es', - site_id: $(item).attr('value'), - name: $(item).text() - } - }) - } -} - -async function loadProgramDetails($item) { - const onclick = $item('*').attr('onclick') - const regexp = /detallePrograma\((\d+),(\d+),(\d+),(\d+),'([^']+)'\);/g - const match = [...onclick.matchAll(regexp)] - const [, id, idc, id_alineacion, idp, title] = match[0] - if (!id || !idc || !id_alineacion || !idp || !title) return Promise.resolve({}) - const formData = new URLSearchParams() - formData.append('id', id) - formData.append('idc', idc) - formData.append('id_alineacion', id_alineacion) - formData.append('idp', idp) - formData.append('title', title) - const content = await axios - .post('https://www.reportv.com.ar/buscador/DetallePrograma.php', formData) - .then(r => r.data.toString()) - .catch(console.error) - if (!content) return Promise.resolve({}) - - const $ = cheerio.load(content) - - return Promise.resolve({ - image: parseImage($), - actors: parseActors($), - directors: parseDirectors($), - description: parseDescription($) - }) -} - -function parseActors($) { - const section = $('#Ficha > div') - .html() - .split('
    ') - .find(str => str.includes('Actores:')) - if (!section) return null - const $section = cheerio.load(section) - - return $section('span') - .map((i, el) => $(el).text().trim()) - .get() -} - -function parseDirectors($) { - const section = $('#Ficha > div') - .html() - .split('
    ') - .find(str => str.includes('Directores:')) - if (!section) return null - const $section = cheerio.load(section) - - return $section('span') - .map((i, el) => $(el).text().trim()) - .get() -} - -function parseDescription($) { - return $('#Sinopsis > div').text().trim() -} - -function parseImage($) { - const src = $('#ImgProg').attr('src') - const url = new URL(src, 'https://www.reportv.com.ar/buscador/') - - return url.href -} - -function parseTitle($item) { - const [, title] = $item('div:nth-child(1) > span').text().split(' - ') - - return title -} - -function parseCategory($item) { - return $item('div:nth-child(3) > span').text() -} - -function parseStart($item, date) { - const [time] = $item('div:nth-child(1) > span').text().split(' - ') - - return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'America/Caracas') -} - -function parseDuration($item) { - const [hh, mm, ss] = $item('div:nth-child(4) > span').text().split(':') - - return parseInt(hh) * 3600 + parseInt(mm) * 60 + parseInt(ss) -} - -function parseItems(content, date) { - if (!content) return [] - const $ = cheerio.load(content) - const d = startCase(date.locale('es').format('DD MMMM YYYY')) - - return $(`.trProg[title*="${d}"]`).toArray() -} +require('dayjs/locale/es') +const axios = require('axios') +const dayjs = require('dayjs') +const cheerio = require('cheerio') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const { startCase } = require('../../scripts/functions') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'reportv.com.ar', + days: 2, + request: { + cache: { + ttl: 60 * 60 * 1000 // 1 hour + }, + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + data({ channel, date }) { + const formData = new URLSearchParams() + formData.append('idSenial', channel.site_id) + formData.append('Alineacion', '2694') + formData.append('DiaDesde', date.format('YYYY/MM/DD')) + formData.append('HoraDesde', '00:00:00') + + return formData + } + }, + url: 'https://www.reportv.com.ar/buscador/ProgXSenial.php', + parser: async function ({ content, date }) { + let programs = [] + const items = parseItems(content, date) + for (let item of items) { + const $item = cheerio.load(item) + const start = parseStart($item, date) + const duration = parseDuration($item) + const stop = start.add(duration, 's') + const details = await loadProgramDetails($item) + programs.push({ + title: parseTitle($item), + category: parseCategory($item), + image: details.image, + description: details.description, + directors: details.directors, + actors: details.actors, + start, + stop + }) + } + + return programs + }, + async channels() { + const content = await axios + .get('https://www.reportv.com.ar/buscador/Buscador.php?aid=2694') + .then(r => r.data) + .catch(console.log) + const $ = cheerio.load(content) + const items = $('#tr_home_2 > td:nth-child(1) > select > option').toArray() + + return items.map(item => { + return { + lang: 'es', + site_id: $(item).attr('value'), + name: $(item).text() + } + }) + } +} + +async function loadProgramDetails($item) { + const onclick = $item('*').attr('onclick') + const regexp = /detallePrograma\((\d+),(\d+),(\d+),(\d+),'([^']+)'\);/g + const match = [...onclick.matchAll(regexp)] + const [, id, idc, id_alineacion, idp, title] = match[0] + if (!id || !idc || !id_alineacion || !idp || !title) return Promise.resolve({}) + const formData = new URLSearchParams() + formData.append('id', id) + formData.append('idc', idc) + formData.append('id_alineacion', id_alineacion) + formData.append('idp', idp) + formData.append('title', title) + const content = await axios + .post('https://www.reportv.com.ar/buscador/DetallePrograma.php', formData) + .then(r => r.data.toString()) + .catch(console.error) + if (!content) return Promise.resolve({}) + + const $ = cheerio.load(content) + + return Promise.resolve({ + image: parseImage($), + actors: parseActors($), + directors: parseDirectors($), + description: parseDescription($) + }) +} + +function parseActors($) { + const section = $('#Ficha > div') + .html() + .split('
    ') + .find(str => str.includes('Actores:')) + if (!section) return null + const $section = cheerio.load(section) + + return $section('span') + .map((i, el) => $(el).text().trim()) + .get() +} + +function parseDirectors($) { + const section = $('#Ficha > div') + .html() + .split('
    ') + .find(str => str.includes('Directores:')) + if (!section) return null + const $section = cheerio.load(section) + + return $section('span') + .map((i, el) => $(el).text().trim()) + .get() +} + +function parseDescription($) { + return $('#Sinopsis > div').text().trim() +} + +function parseImage($) { + const src = $('#ImgProg').attr('src') + const url = new URL(src, 'https://www.reportv.com.ar/buscador/') + + return url.href +} + +function parseTitle($item) { + const [, title] = $item('div:nth-child(1) > span').text().split(' - ') + + return title +} + +function parseCategory($item) { + return $item('div:nth-child(3) > span').text() +} + +function parseStart($item, date) { + const [time] = $item('div:nth-child(1) > span').text().split(' - ') + + return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'America/Caracas') +} + +function parseDuration($item) { + const [hh, mm, ss] = $item('div:nth-child(4) > span').text().split(':') + + return parseInt(hh) * 3600 + parseInt(mm) * 60 + parseInt(ss) +} + +function parseItems(content, date) { + if (!content) return [] + const $ = cheerio.load(content) + const d = startCase(date.locale('es').format('DD MMMM YYYY')) + + return $(`.trProg[title*="${d}"]`).toArray() +} diff --git a/sites/rikstv.no/rikstv.no.test.js b/sites/rikstv.no/rikstv.no.test.js index 0c79c07ce..3ea233a52 100644 --- a/sites/rikstv.no/rikstv.no.test.js +++ b/sites/rikstv.no/rikstv.no.test.js @@ -1,58 +1,58 @@ -const { parser, url } = require('./rikstv.no.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-01-14', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '47', - xmltv_id: 'NRK1.no' -} - -describe('rikstv.no Module Tests', () => { - it('can generate valid url', () => { - expect(url({ date, channel })).toBe( - `https://play.rikstv.no/api/content-search/1/channel/${channel.site_id}/epg/${date.format( - 'YYYY-MM-DD' - )}` - ) - }) - - it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ content }).map(p => { - p.start = dayjs(p.start).toISOString() - p.stop = dayjs(p.stop).toISOString() - return p - }) - - expect(result).toMatchObject([ - { - title: 'Vakre og ville Oman', - sub_title: 'Vakre og ville Oman', - description: - 'Oman er eit arabisk skattkammer av unike habitat og variert dyreliv. Rev, kvalhai, reptil og skjelpadder er blant skapningane du finn her.', - season: 1, - episode: 1, - category: ['Dokumentar', 'Fakta', 'Natur'], - actors: ['Gergana Muskalla'], - directors: 'Stefania Muller', - icon: 'https://imageservice.rikstv.no/hash/EC206C374F42287C0BDF850A7D3CB4D3.jpg', - start: '2025-01-13T23:00:00.000Z', - stop: '2025-01-13T23:55:00.000Z' - } - ]) - }) - - it('can handle empty guide', () => { - const result = parser({ - content: '[]' - }) - expect(result).toMatchObject([]) - }) -}) +const { parser, url } = require('./rikstv.no.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-01-14', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '47', + xmltv_id: 'NRK1.no' +} + +describe('rikstv.no Module Tests', () => { + it('can generate valid url', () => { + expect(url({ date, channel })).toBe( + `https://play.rikstv.no/api/content-search/1/channel/${channel.site_id}/epg/${date.format( + 'YYYY-MM-DD' + )}` + ) + }) + + it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ content }).map(p => { + p.start = dayjs(p.start).toISOString() + p.stop = dayjs(p.stop).toISOString() + return p + }) + + expect(result).toMatchObject([ + { + title: 'Vakre og ville Oman', + sub_title: 'Vakre og ville Oman', + description: + 'Oman er eit arabisk skattkammer av unike habitat og variert dyreliv. Rev, kvalhai, reptil og skjelpadder er blant skapningane du finn her.', + season: 1, + episode: 1, + category: ['Dokumentar', 'Fakta', 'Natur'], + actors: ['Gergana Muskalla'], + directors: 'Stefania Muller', + icon: 'https://imageservice.rikstv.no/hash/EC206C374F42287C0BDF850A7D3CB4D3.jpg', + start: '2025-01-13T23:00:00.000Z', + stop: '2025-01-13T23:55:00.000Z' + } + ]) + }) + + it('can handle empty guide', () => { + const result = parser({ + content: '[]' + }) + expect(result).toMatchObject([]) + }) +}) diff --git a/sites/rtmklik.rtm.gov.my/rtmklik.rtm.gov.my.test.js b/sites/rtmklik.rtm.gov.my/rtmklik.rtm.gov.my.test.js index 095868359..2f5b56b17 100644 --- a/sites/rtmklik.rtm.gov.my/rtmklik.rtm.gov.my.test.js +++ b/sites/rtmklik.rtm.gov.my/rtmklik.rtm.gov.my.test.js @@ -1,48 +1,48 @@ -const { parser, url } = require('./rtmklik.rtm.gov.my.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-09-04', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '2', - xmltv_id: 'TV2.my' -} - -it('can generate valid url', () => { - expect(url({ date, channel })).toBe( - 'https://rtm.glueapi.io/v3/epg/2/ChannelSchedule?dateStart=2022-09-04&dateEnd=2022-09-04&timezone=0' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ content, channel, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2022-09-04T19:00:00.000Z', - stop: '2022-09-04T20:00:00.000Z', - title: 'Hope Of Life', - description: - 'Kisah kehidupan 3 pakar bedah yang berbeza status dan latar belakang, namun mereka komited dengan kerjaya mereka sebagai doktor. Lakonan : Johnson Low, Elvis Chin, Mayjune Tan, Kelvin Liew, Jacky Kam dan Katrina Ho.' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./rtmklik.rtm.gov.my.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-09-04', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '2', + xmltv_id: 'TV2.my' +} + +it('can generate valid url', () => { + expect(url({ date, channel })).toBe( + 'https://rtm.glueapi.io/v3/epg/2/ChannelSchedule?dateStart=2022-09-04&dateEnd=2022-09-04&timezone=0' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ content, channel, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2022-09-04T19:00:00.000Z', + stop: '2022-09-04T20:00:00.000Z', + title: 'Hope Of Life', + description: + 'Kisah kehidupan 3 pakar bedah yang berbeza status dan latar belakang, namun mereka komited dengan kerjaya mereka sebagai doktor. Lakonan : Johnson Low, Elvis Chin, Mayjune Tan, Kelvin Liew, Jacky Kam dan Katrina Ho.' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/rtp.pt/rtp.pt.config.js b/sites/rtp.pt/rtp.pt.config.js index 99f233909..7f53e268d 100644 --- a/sites/rtp.pt/rtp.pt.config.js +++ b/sites/rtp.pt/rtp.pt.config.js @@ -1,65 +1,65 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -const tz = { - lis: 'Europe/Lisbon', - per: 'Asia/Macau', - rja: 'America/Sao_Paulo' -} - -module.exports = { - site: 'rtp.pt', - days: 2, - url({ channel, date }) { - let [region, channelCode] = channel.site_id.split('#') - return `https://www.rtp.pt/EPG/json/rtp-channels-page/list-grid/tv/${channelCode}/${date.format( - 'D-M-YYYY' - )}/${region}` - }, - parser({ content, channel }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - const prev = programs[programs.length - 1] - let start = parseStart(item, channel) - if (!start) return - if (prev) { - prev.stop = start - } - const stop = start.add(30, 'm') - programs.push({ - title: item.name, - description: item.description, - image: parseImage(item), - start, - stop - }) - }) - - return programs - } -} - -function parseImage(item) { - const last = item.image.pop() - if (!last) return null - return last.src -} - -function parseStart(item, channel) { - let [region] = channel.site_id.split('#') - return dayjs.tz(item.date, 'YYYY-MM-DD HH:mm:ss', tz[region]) -} - -function parseItems(content) { - if (!content) return [] - const data = JSON.parse(content) - - return Object.values(data.result).flat() -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +const tz = { + lis: 'Europe/Lisbon', + per: 'Asia/Macau', + rja: 'America/Sao_Paulo' +} + +module.exports = { + site: 'rtp.pt', + days: 2, + url({ channel, date }) { + let [region, channelCode] = channel.site_id.split('#') + return `https://www.rtp.pt/EPG/json/rtp-channels-page/list-grid/tv/${channelCode}/${date.format( + 'D-M-YYYY' + )}/${region}` + }, + parser({ content, channel }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + const prev = programs[programs.length - 1] + let start = parseStart(item, channel) + if (!start) return + if (prev) { + prev.stop = start + } + const stop = start.add(30, 'm') + programs.push({ + title: item.name, + description: item.description, + image: parseImage(item), + start, + stop + }) + }) + + return programs + } +} + +function parseImage(item) { + const last = item.image.pop() + if (!last) return null + return last.src +} + +function parseStart(item, channel) { + let [region] = channel.site_id.split('#') + return dayjs.tz(item.date, 'YYYY-MM-DD HH:mm:ss', tz[region]) +} + +function parseItems(content) { + if (!content) return [] + const data = JSON.parse(content) + + return Object.values(data.result).flat() +} diff --git a/sites/s.mxtv.jp/s.mxtv.jp.test.js b/sites/s.mxtv.jp/s.mxtv.jp.test.js index 16fd4bb7b..fe47492b4 100644 --- a/sites/s.mxtv.jp/s.mxtv.jp.test.js +++ b/sites/s.mxtv.jp/s.mxtv.jp.test.js @@ -1,49 +1,49 @@ -const { parser, url } = require('./s.mxtv.jp.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2024-08-01', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '2', - name: 'Tokyo MX2', - xmltv_id: 'TokyoMX2.jp' -} - -it('can generate valid url', () => { - const result = url({ date, channel }) - expect(result).toBe('https://s.mxtv.jp/bangumi_file/json01/SV2EPG20240801.json') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ date, channel, content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2024-07-26T20:00:00.000Z', // UTC time - stop: '2024-07-26T21:00:00.000Z', // UTC - title: 'ヒーリングタイム&ヘッドラインニュース', - description: 'ねこの足跡', - image: null, - category: null - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: '[]' - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./s.mxtv.jp.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2024-08-01', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '2', + name: 'Tokyo MX2', + xmltv_id: 'TokyoMX2.jp' +} + +it('can generate valid url', () => { + const result = url({ date, channel }) + expect(result).toBe('https://s.mxtv.jp/bangumi_file/json01/SV2EPG20240801.json') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ date, channel, content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2024-07-26T20:00:00.000Z', // UTC time + stop: '2024-07-26T21:00:00.000Z', // UTC + title: 'ヒーリングタイム&ヘッドラインニュース', + description: 'ねこの足跡', + image: null, + category: null + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: '[]' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/shahid.mbc.net/shahid.mbc.net.test.js b/sites/shahid.mbc.net/shahid.mbc.net.test.js index 2effd5f70..c804fa448 100644 --- a/sites/shahid.mbc.net/shahid.mbc.net.test.js +++ b/sites/shahid.mbc.net/shahid.mbc.net.test.js @@ -1,41 +1,41 @@ -const { url, parser } = require('./shahid.mbc.net.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') - -dayjs.extend(utc) - -const date = dayjs.utc('2023-11-11').startOf('d') -const channel = { site_id: '996520', xmltv_id: 'AlAanTV.ae', lang: 'en' } - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - `https://api2.shahid.net/proxy/v2.1/shahid-epg-api/?csvChannelIds=${ - channel.site_id - }&from=${date.format('YYYY-MM-DD')}T00:00:00.000Z&to=${date.format( - 'YYYY-MM-DD' - )}T23:59:59.999Z&country=SA&language=${channel.lang}&Accept-Language=${channel.lang}` - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ content, channel, date }) - - expect(result).toMatchObject([ - { - start: '2023-11-10T21:00:00.000Z', - stop: '2023-11-10T21:30:00.000Z', - title: "Menassaatona Fi Osboo'", - description: - "The presenter reviews the most prominent episodes of news programs produced by the channel's team on a weekly basis, which include the most important global updates and developments at all levels." - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ content: '' }) - - expect(result).toMatchObject([]) -}) +const { url, parser } = require('./shahid.mbc.net.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') + +dayjs.extend(utc) + +const date = dayjs.utc('2023-11-11').startOf('d') +const channel = { site_id: '996520', xmltv_id: 'AlAanTV.ae', lang: 'en' } + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + `https://api2.shahid.net/proxy/v2.1/shahid-epg-api/?csvChannelIds=${ + channel.site_id + }&from=${date.format('YYYY-MM-DD')}T00:00:00.000Z&to=${date.format( + 'YYYY-MM-DD' + )}T23:59:59.999Z&country=SA&language=${channel.lang}&Accept-Language=${channel.lang}` + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ content, channel, date }) + + expect(result).toMatchObject([ + { + start: '2023-11-10T21:00:00.000Z', + stop: '2023-11-10T21:30:00.000Z', + title: "Menassaatona Fi Osboo'", + description: + "The presenter reviews the most prominent episodes of news programs produced by the channel's team on a weekly basis, which include the most important global updates and developments at all levels." + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ content: '' }) + + expect(result).toMatchObject([]) +}) diff --git a/sites/siba.com.co/siba.com.co.test.js b/sites/siba.com.co/siba.com.co.test.js index 12d2a5711..c804e185c 100644 --- a/sites/siba.com.co/siba.com.co.test.js +++ b/sites/siba.com.co/siba.com.co.test.js @@ -1,54 +1,54 @@ -const { parser, url, request } = require('./siba.com.co.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2021-11-11', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '395', - xmltv_id: 'CanalClaro.cl' -} - -it('can generate valid url', () => { - expect(url).toBe('http://devportal.siba.com.co/index.php?action=grilla') -}) - -it('can generate valid request headers', () => { - expect(request.headers).toMatchObject({ - 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' - }) -}) - -it('can generate valid request data', () => { - const result = request.data({ channel, date }) - expect(result.has('servicio')).toBe(true) - expect(result.has('ini')).toBe(true) - expect(result.has('end')).toBe(true) - expect(result.has('chn')).toBe(true) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ date, channel, content }) - expect(result).toMatchObject([ - { - start: '2021-11-11T00:00:00.000Z', - stop: '2021-11-11T01:00:00.000Z', - title: 'Worst Cooks In America' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - - }) - expect(result).toMatchObject([]) -}) +const { parser, url, request } = require('./siba.com.co.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2021-11-11', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '395', + xmltv_id: 'CanalClaro.cl' +} + +it('can generate valid url', () => { + expect(url).toBe('http://devportal.siba.com.co/index.php?action=grilla') +}) + +it('can generate valid request headers', () => { + expect(request.headers).toMatchObject({ + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' + }) +}) + +it('can generate valid request data', () => { + const result = request.data({ channel, date }) + expect(result.has('servicio')).toBe(true) + expect(result.has('ini')).toBe(true) + expect(result.has('end')).toBe(true) + expect(result.has('chn')).toBe(true) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ date, channel, content }) + expect(result).toMatchObject([ + { + start: '2021-11-11T00:00:00.000Z', + stop: '2021-11-11T01:00:00.000Z', + title: 'Worst Cooks In America' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/sky.com/sky.com.config.js b/sites/sky.com/sky.com.config.js index 3ee678374..291ecdfc2 100644 --- a/sites/sky.com/sky.com.config.js +++ b/sites/sky.com/sky.com.config.js @@ -1,85 +1,85 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const doFetch = require('@ntlab/sfetch') -const debug = require('debug')('site:sky.com') -const { sortBy } = require('../../scripts/functions') - -dayjs.extend(utc) - -doFetch.setDebugger(debug) - -module.exports = { - site: 'sky.com', - days: 2, - url({ date, channel }) { - return `https://awk.epgsky.com/hawk/linear/schedule/${date.format('YYYYMMDD')}/${ - channel.site_id - }` - }, - parser({ content, channel, date }) { - const programs = [] - if (content) { - const items = JSON.parse(content) || null - if (Array.isArray(items.schedule)) { - items.schedule - .filter(schedule => schedule.sid === channel.site_id) - .forEach(schedule => { - if (Array.isArray(schedule.events)) { - sortBy(schedule.events, p => p.st).forEach(event => { - const start = dayjs.utc(event.st * 1000) - if (start.isSame(date, 'd')) { - const image = `https://images.metadata.sky.com/pd-image/${event.programmeuuid}/16-9/640` - programs.push({ - title: event.t, - description: event.sy, - season: event.seasonnumber, - episode: event.episodenumber, - start, - stop: start.add(event.d, 's'), - icon: image, - image - }) - } - }) - } - }) - } - } - - return programs - }, - async channels() { - const channels = {} - const queues = [{ t: 'r', url: 'https://www.sky.com/tv-guide' }] - await doFetch(queues, (queue, res) => { - // process regions - if (queue.t === 'r') { - const $ = cheerio.load(res) - const initialData = JSON.parse(decodeURIComponent($('#initialData').text())) - initialData.state.epgData.regions.forEach(region => { - queues.push({ - t: 'c', - url: `https://awk.epgsky.com/hawk/linear/services/${region.bouquet}/${region.subBouquet}` - }) - }) - } - // process channels - if (queue.t === 'c') { - if (Array.isArray(res.services)) { - for (const ch of res.services) { - if (channels[ch.sid] === undefined) { - channels[ch.sid] = { - lang: 'en', - site_id: ch.sid, - name: ch.t - } - } - } - } - } - }) - - return Object.values(channels) - } -} +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const doFetch = require('@ntlab/sfetch') +const debug = require('debug')('site:sky.com') +const { sortBy } = require('../../scripts/functions') + +dayjs.extend(utc) + +doFetch.setDebugger(debug) + +module.exports = { + site: 'sky.com', + days: 2, + url({ date, channel }) { + return `https://awk.epgsky.com/hawk/linear/schedule/${date.format('YYYYMMDD')}/${ + channel.site_id + }` + }, + parser({ content, channel, date }) { + const programs = [] + if (content) { + const items = JSON.parse(content) || null + if (Array.isArray(items.schedule)) { + items.schedule + .filter(schedule => schedule.sid === channel.site_id) + .forEach(schedule => { + if (Array.isArray(schedule.events)) { + sortBy(schedule.events, p => p.st).forEach(event => { + const start = dayjs.utc(event.st * 1000) + if (start.isSame(date, 'd')) { + const image = `https://images.metadata.sky.com/pd-image/${event.programmeuuid}/16-9/640` + programs.push({ + title: event.t, + description: event.sy, + season: event.seasonnumber, + episode: event.episodenumber, + start, + stop: start.add(event.d, 's'), + icon: image, + image + }) + } + }) + } + }) + } + } + + return programs + }, + async channels() { + const channels = {} + const queues = [{ t: 'r', url: 'https://www.sky.com/tv-guide' }] + await doFetch(queues, (queue, res) => { + // process regions + if (queue.t === 'r') { + const $ = cheerio.load(res) + const initialData = JSON.parse(decodeURIComponent($('#initialData').text())) + initialData.state.epgData.regions.forEach(region => { + queues.push({ + t: 'c', + url: `https://awk.epgsky.com/hawk/linear/services/${region.bouquet}/${region.subBouquet}` + }) + }) + } + // process channels + if (queue.t === 'c') { + if (Array.isArray(res.services)) { + for (const ch of res.services) { + if (channels[ch.sid] === undefined) { + channels[ch.sid] = { + lang: 'en', + site_id: ch.sid, + name: ch.t + } + } + } + } + } + }) + + return Object.values(channels) + } +} diff --git a/sites/sky.de/sky.de.test.js b/sites/sky.de/sky.de.test.js index bdcb8a15d..2a6120a51 100644 --- a/sites/sky.de/sky.de.test.js +++ b/sites/sky.de/sky.de.test.js @@ -1,66 +1,66 @@ -const { parser, url, request } = require('./sky.de.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -dayjs.extend(utc) - -const date = dayjs.utc('2022-02-28', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '522', - xmltv_id: 'WarnerTVComedyHD.de' -} - -it('can generate valid url', () => { - expect(url).toBe('https://www.sky.de/sgtvg/service/getBroadcastsForGrid') -}) - -it('can generate valid request method', () => { - expect(request.method).toBe('POST') -}) - -it('can generate valid request data', () => { - expect(request.data({ channel, date })).toMatchObject({ - cil: [channel.site_id], - d: date.valueOf() - }) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ content, channel }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - title: 'King of Queens', - description: 'Der Experte', - category: 'Comedyserie', - start: '2022-02-26T23:05:00.000Z', - stop: '2022-02-26T23:30:00.000Z', - season: '4', - episode: '11', - image: 'http://sky.de/static/img/program_guide/1522936_s.jpg' - }, - { - title: 'King of Queens', - description: 'Speedy Gonzales', - category: 'Comedyserie', - start: '2022-02-26T23:30:00.000Z', - stop: '2022-02-26T23:55:00.000Z', - season: '4', - episode: '12', - image: 'http://sky.de/static/img/program_guide/1522937_s.jpg' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: '[]' - }) - expect(result).toMatchObject([]) -}) +const { parser, url, request } = require('./sky.de.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +dayjs.extend(utc) + +const date = dayjs.utc('2022-02-28', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '522', + xmltv_id: 'WarnerTVComedyHD.de' +} + +it('can generate valid url', () => { + expect(url).toBe('https://www.sky.de/sgtvg/service/getBroadcastsForGrid') +}) + +it('can generate valid request method', () => { + expect(request.method).toBe('POST') +}) + +it('can generate valid request data', () => { + expect(request.data({ channel, date })).toMatchObject({ + cil: [channel.site_id], + d: date.valueOf() + }) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ content, channel }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + title: 'King of Queens', + description: 'Der Experte', + category: 'Comedyserie', + start: '2022-02-26T23:05:00.000Z', + stop: '2022-02-26T23:30:00.000Z', + season: '4', + episode: '11', + image: 'http://sky.de/static/img/program_guide/1522936_s.jpg' + }, + { + title: 'King of Queens', + description: 'Speedy Gonzales', + category: 'Comedyserie', + start: '2022-02-26T23:30:00.000Z', + stop: '2022-02-26T23:55:00.000Z', + season: '4', + episode: '12', + image: 'http://sky.de/static/img/program_guide/1522937_s.jpg' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: '[]' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/stod2.is/stod2.is.test.js b/sites/stod2.is/stod2.is.test.js index c8ff849dc..2c7a21303 100644 --- a/sites/stod2.is/stod2.is.test.js +++ b/sites/stod2.is/stod2.is.test.js @@ -1,46 +1,46 @@ -const { parser, url } = require('./stod2.is.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -const timezone = require('dayjs/plugin/timezone') - -dayjs.extend(utc) -dayjs.extend(customParseFormat) -dayjs.extend(timezone) - -const date = dayjs.utc('2025-01-03', 'YYYY-MM-DD').startOf('day') -const channel = { site_id: 'stod2', xmltv_id: 'Stod2.is' } - -it('can generate valid url', () => { - const generatedUrl = url({ date, channel }) - expect(generatedUrl).toBe('https://api.stod2.is/dagskra/api/stod2/2025-01-03') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ content }).map(p => { - p.start = p.start.toISOString() - p.stop = p.stop.toISOString() - return p - }) - - expect(result).toMatchObject([ - { - title: 'Heimsókn', - sub_title: 'Telma Borgþórsdóttir', - description: - 'Frábærir þættir með Sindra Sindrasyni sem lítur inn hjá íslenskum fagurkerum. Heimilin eru jafn ólík og þau eru mörg en eiga það þó eitt sameiginlegt að vera sett saman af alúð og smekklegheitum. Sindri hefur líka einstakt lag á að ná fram því besta í viðmælendum sínum.', - actors: '', - directors: '', - start: '2025-01-03T08:00:00.000Z', - stop: '2025-01-03T08:15:00.000Z' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ content: '[]' }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./stod2.is.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const timezone = require('dayjs/plugin/timezone') + +dayjs.extend(utc) +dayjs.extend(customParseFormat) +dayjs.extend(timezone) + +const date = dayjs.utc('2025-01-03', 'YYYY-MM-DD').startOf('day') +const channel = { site_id: 'stod2', xmltv_id: 'Stod2.is' } + +it('can generate valid url', () => { + const generatedUrl = url({ date, channel }) + expect(generatedUrl).toBe('https://api.stod2.is/dagskra/api/stod2/2025-01-03') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ content }).map(p => { + p.start = p.start.toISOString() + p.stop = p.stop.toISOString() + return p + }) + + expect(result).toMatchObject([ + { + title: 'Heimsókn', + sub_title: 'Telma Borgþórsdóttir', + description: + 'Frábærir þættir með Sindra Sindrasyni sem lítur inn hjá íslenskum fagurkerum. Heimilin eru jafn ólík og þau eru mörg en eiga það þó eitt sameiginlegt að vera sett saman af alúð og smekklegheitum. Sindri hefur líka einstakt lag á að ná fram því besta í viðmælendum sínum.', + actors: '', + directors: '', + start: '2025-01-03T08:00:00.000Z', + stop: '2025-01-03T08:15:00.000Z' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ content: '[]' }) + expect(result).toMatchObject([]) +}) diff --git a/sites/streamingtvguides.com/streamingtvguides.com.config.js b/sites/streamingtvguides.com/streamingtvguides.com.config.js index e0b1d5a64..ccff2d3f3 100644 --- a/sites/streamingtvguides.com/streamingtvguides.com.config.js +++ b/sites/streamingtvguides.com/streamingtvguides.com.config.js @@ -1,97 +1,97 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const customParseFormat = require('dayjs/plugin/customParseFormat') -const timezone = require('dayjs/plugin/timezone') -const { sortBy, uniqBy } = require('../../scripts/functions') - -dayjs.extend(customParseFormat) -dayjs.extend(timezone) - -module.exports = { - site: 'streamingtvguides.com', - days: 2, - url({ channel }) { - return `https://streamingtvguides.com/Channel/${channel.site_id}` - }, - parser({ content, date }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - const $item = cheerio.load(item) - const start = parseStart($item) - if (!date.isSame(start, 'd')) return - - programs.push({ - title: parseTitle($item), - description: parseDescription($item), - start, - stop: parseStop($item) - }) - }) - - programs = sortBy(uniqBy(programs, p => p.start), p => p.start.valueOf()) - - return programs - }, - async channels() { - const axios = require('axios') - const data = await axios - .get('https://streamingtvguides.com/Preferences') - .then(r => r.data) - .catch(console.log) - - let channels = [] - - const $ = cheerio.load(data) - $('#channel-group-all > div > div').each((i, el) => { - const site_id = $(el).find('input').attr('value').replace('&', '&') - const label = $(el).text().trim() - const svgTitle = $(el).find('svg').attr('alt') - const name = (label || svgTitle || '').replace(site_id, '').trim() - - if (!name || !site_id) return - - channels.push({ - lang: 'en', - site_id, - name - }) - }) - - return channels - } -} - -function parseTitle($item) { - return $item('.card-body > .prog-contains > .card-title') - .clone() - .children() - .remove() - .end() - .text() - .trim() -} - -function parseDescription($item) { - return $item('.card-body > .card-text').clone().children().remove().end().text().trim() -} - -function parseStart($item) { - const date = $item('.card-body').clone().children().remove().end().text().trim() - const [time] = date.split(' - ') - - return dayjs.tz(time, 'YYYY-MM-DD HH:mm:ss [PST]', 'PST').utc() -} - -function parseStop($item) { - const date = $item('.card-body').clone().children().remove().end().text().trim() - const [, time] = date.split(' - ') - - return dayjs.tz(time, 'YYYY-MM-DD HH:mm:ss [PST]', 'PST').utc() -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('.container').toArray() -} +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const timezone = require('dayjs/plugin/timezone') +const { sortBy, uniqBy } = require('../../scripts/functions') + +dayjs.extend(customParseFormat) +dayjs.extend(timezone) + +module.exports = { + site: 'streamingtvguides.com', + days: 2, + url({ channel }) { + return `https://streamingtvguides.com/Channel/${channel.site_id}` + }, + parser({ content, date }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + const $item = cheerio.load(item) + const start = parseStart($item) + if (!date.isSame(start, 'd')) return + + programs.push({ + title: parseTitle($item), + description: parseDescription($item), + start, + stop: parseStop($item) + }) + }) + + programs = sortBy(uniqBy(programs, p => p.start), p => p.start.valueOf()) + + return programs + }, + async channels() { + const axios = require('axios') + const data = await axios + .get('https://streamingtvguides.com/Preferences') + .then(r => r.data) + .catch(console.log) + + let channels = [] + + const $ = cheerio.load(data) + $('#channel-group-all > div > div').each((i, el) => { + const site_id = $(el).find('input').attr('value').replace('&', '&') + const label = $(el).text().trim() + const svgTitle = $(el).find('svg').attr('alt') + const name = (label || svgTitle || '').replace(site_id, '').trim() + + if (!name || !site_id) return + + channels.push({ + lang: 'en', + site_id, + name + }) + }) + + return channels + } +} + +function parseTitle($item) { + return $item('.card-body > .prog-contains > .card-title') + .clone() + .children() + .remove() + .end() + .text() + .trim() +} + +function parseDescription($item) { + return $item('.card-body > .card-text').clone().children().remove().end().text().trim() +} + +function parseStart($item) { + const date = $item('.card-body').clone().children().remove().end().text().trim() + const [time] = date.split(' - ') + + return dayjs.tz(time, 'YYYY-MM-DD HH:mm:ss [PST]', 'PST').utc() +} + +function parseStop($item) { + const date = $item('.card-body').clone().children().remove().end().text().trim() + const [, time] = date.split(' - ') + + return dayjs.tz(time, 'YYYY-MM-DD HH:mm:ss [PST]', 'PST').utc() +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('.container').toArray() +} diff --git a/sites/taiwanplus.com/taiwanplus.com.test.js b/sites/taiwanplus.com/taiwanplus.com.test.js index 6ca257976..6d846f5bf 100644 --- a/sites/taiwanplus.com/taiwanplus.com.test.js +++ b/sites/taiwanplus.com/taiwanplus.com.test.js @@ -1,43 +1,43 @@ -const { url, parser } = require('./taiwanplus.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -dayjs.extend(utc) - -const date = dayjs.utc('2023-08-20', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '#', - xmltv_id: 'TaiwanPlusTV.tw', - lang: 'en', - logo: 'https://i.imgur.com/SfcZyqm.png' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe('https://www.taiwanplus.com/api/video/live/schedule/0') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - - const results = parser({ content, date }) - - expect(results).toMatchObject([ - { - title: 'Master Class', - start: dayjs.utc('2023/08/20 00:00', 'YYYY/MM/DD HH:mm'), - stop: dayjs.utc('2023/08/21 00:00', 'YYYY/MM/DD HH:mm'), - description: - 'From blockchain to Buddha statues, Taiwan’s culture is a kaleidoscope of old and new just waiting to be discovered.', - image: 'https://prod-img.taiwanplus.com/live-schedule/Single/S30668_20230810104937.webp', - category: 'TaiwanPlus ✕ Discovery', - rating: '0+' - } - ]) -}) - -it('can handle empty guide', () => { - const results = parser({ content: '' }) - - expect(results).toMatchObject([]) -}) +const { url, parser } = require('./taiwanplus.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +dayjs.extend(utc) + +const date = dayjs.utc('2023-08-20', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '#', + xmltv_id: 'TaiwanPlusTV.tw', + lang: 'en', + logo: 'https://i.imgur.com/SfcZyqm.png' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe('https://www.taiwanplus.com/api/video/live/schedule/0') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + + const results = parser({ content, date }) + + expect(results).toMatchObject([ + { + title: 'Master Class', + start: dayjs.utc('2023/08/20 00:00', 'YYYY/MM/DD HH:mm'), + stop: dayjs.utc('2023/08/21 00:00', 'YYYY/MM/DD HH:mm'), + description: + 'From blockchain to Buddha statues, Taiwan’s culture is a kaleidoscope of old and new just waiting to be discovered.', + image: 'https://prod-img.taiwanplus.com/live-schedule/Single/S30668_20230810104937.webp', + category: 'TaiwanPlus ✕ Discovery', + rating: '0+' + } + ]) +}) + +it('can handle empty guide', () => { + const results = parser({ content: '' }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/tapdmv.com/tapdmv.com.test.js b/sites/tapdmv.com/tapdmv.com.test.js index 55e117723..15c355dda 100644 --- a/sites/tapdmv.com/tapdmv.com.test.js +++ b/sites/tapdmv.com/tapdmv.com.test.js @@ -1,49 +1,49 @@ -const { parser, url } = require('./tapdmv.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-10-04', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '94b7db9b-5bbd-47d3-a2d3-ce792342a756', - xmltv_id: 'TAPActionFlix.ph' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://epg.tapdmv.com/calendar/94b7db9b-5bbd-47d3-a2d3-ce792342a756?%24limit=10000&%24sort%5BcreatedAt%5D=-1&start=2022-10-04T00:00:00.000Z&end=2022-10-05T00:00:00.000Z' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ content, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2022-10-04T01:00:00.000Z', - stop: '2022-10-04T02:25:00.000Z', - title: 'The Devil Inside', - description: - 'In Italy, a woman becomes involved in a series of unauthorized exorcisms during her mission to discover what happened to her mother, who allegedly murdered three people during her own exorcism.', - category: 'Horror', - image: 'https://s3.ap-southeast-1.amazonaws.com/epg.tapdmv.com/tapactionflix.png' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')), - date - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./tapdmv.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-10-04', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '94b7db9b-5bbd-47d3-a2d3-ce792342a756', + xmltv_id: 'TAPActionFlix.ph' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://epg.tapdmv.com/calendar/94b7db9b-5bbd-47d3-a2d3-ce792342a756?%24limit=10000&%24sort%5BcreatedAt%5D=-1&start=2022-10-04T00:00:00.000Z&end=2022-10-05T00:00:00.000Z' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ content, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2022-10-04T01:00:00.000Z', + stop: '2022-10-04T02:25:00.000Z', + title: 'The Devil Inside', + description: + 'In Italy, a woman becomes involved in a series of unauthorized exorcisms during her mission to discover what happened to her mother, who allegedly murdered three people during her own exorcism.', + category: 'Horror', + image: 'https://s3.ap-southeast-1.amazonaws.com/epg.tapdmv.com/tapactionflix.png' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')), + date + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/tataplay.com/tataplay.com.config.js b/sites/tataplay.com/tataplay.com.config.js index 777634d81..57142c3e6 100644 --- a/sites/tataplay.com/tataplay.com.config.js +++ b/sites/tataplay.com/tataplay.com.config.js @@ -1,82 +1,82 @@ -const axios = require('axios') - -module.exports = { - site: 'tataplay.com', - days: 1, - - url({ date }) { - return `https://tm.tapi.videoready.tv/content-detail/pub/api/v2/channels/schedule?date=${date.format( - 'DD-MM-YYYY' - )}` - }, - - request: { - method: 'POST', - headers: { - Accept: '*/*', - Origin: 'https://watch.tataplay.com', - Referer: 'https://watch.tataplay.com/', - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', - 'content-type': 'application/json', - locale: 'ENG', - platform: 'web' - }, - data({ channel }) { - return { id: channel.site_id } - } - }, - - parser(context) { - let data = [] - try { - const json = JSON.parse(context.content) - const programs = json?.data?.epg || [] - - data = programs.map(program => ({ - title: program.title, - start: program.startTime, - stop: program.endTime, - description: program.desc, - category: program.category, - icon: program.boxCoverImage - })) - } catch { - data = [] - } - return data - }, - - async channels() { - const headers = { - Accept: '*/*', - Origin: 'https://watch.tataplay.com', - Referer: 'https://watch.tataplay.com/', - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', - 'content-type': 'application/json', - locale: 'ENG', - platform: 'web' - } - - const baseUrl = 'https://tm.tapi.videoready.tv/portal-search/pub/api/v1/channels/schedule' - const initialUrl = `${baseUrl}?date=&languageFilters=&genreFilters=&limit=20&offset=0` - const initialResponse = await axios.get(initialUrl, { headers }) - const total = initialResponse.data?.data?.total || 0 - const channels = [] - - for (let offset = 0; offset < total; offset += 20) { - const url = `${baseUrl}?date=&languageFilters=&genreFilters=&limit=20&offset=${offset}` - const response = await axios.get(url, { headers }) - const page = response.data?.data?.channelList || [] - channels.push(...page) - } - - return channels.map(channel => ({ - site_id: channel.id, - name: channel.title, - lang: 'en', - icon: channel.transparentImageUrl || channel.thumbnailImage - })) - } -} +const axios = require('axios') + +module.exports = { + site: 'tataplay.com', + days: 1, + + url({ date }) { + return `https://tm.tapi.videoready.tv/content-detail/pub/api/v2/channels/schedule?date=${date.format( + 'DD-MM-YYYY' + )}` + }, + + request: { + method: 'POST', + headers: { + Accept: '*/*', + Origin: 'https://watch.tataplay.com', + Referer: 'https://watch.tataplay.com/', + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + 'content-type': 'application/json', + locale: 'ENG', + platform: 'web' + }, + data({ channel }) { + return { id: channel.site_id } + } + }, + + parser(context) { + let data = [] + try { + const json = JSON.parse(context.content) + const programs = json?.data?.epg || [] + + data = programs.map(program => ({ + title: program.title, + start: program.startTime, + stop: program.endTime, + description: program.desc, + category: program.category, + icon: program.boxCoverImage + })) + } catch { + data = [] + } + return data + }, + + async channels() { + const headers = { + Accept: '*/*', + Origin: 'https://watch.tataplay.com', + Referer: 'https://watch.tataplay.com/', + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + 'content-type': 'application/json', + locale: 'ENG', + platform: 'web' + } + + const baseUrl = 'https://tm.tapi.videoready.tv/portal-search/pub/api/v1/channels/schedule' + const initialUrl = `${baseUrl}?date=&languageFilters=&genreFilters=&limit=20&offset=0` + const initialResponse = await axios.get(initialUrl, { headers }) + const total = initialResponse.data?.data?.total || 0 + const channels = [] + + for (let offset = 0; offset < total; offset += 20) { + const url = `${baseUrl}?date=&languageFilters=&genreFilters=&limit=20&offset=${offset}` + const response = await axios.get(url, { headers }) + const page = response.data?.data?.channelList || [] + channels.push(...page) + } + + return channels.map(channel => ({ + site_id: channel.id, + name: channel.title, + lang: 'en', + icon: channel.transparentImageUrl || channel.thumbnailImage + })) + } +} diff --git a/sites/tataplay.com/tataplay.com.test.js b/sites/tataplay.com/tataplay.com.test.js index 2f5551af5..9f7214912 100644 --- a/sites/tataplay.com/tataplay.com.test.js +++ b/sites/tataplay.com/tataplay.com.test.js @@ -1,89 +1,89 @@ -const { parser, url, channels } = require('./tataplay.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-06-09', 'YYYY-MM-DD').startOf('d') -const channel = { site_id: '1001' } - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://tm.tapi.videoready.tv/content-detail/pub/api/v2/channels/schedule?date=09-06-2025' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - - const results = parser({ content, date }) - - expect(results.length).toBe(2) - expect(results[0]).toMatchObject({ - title: 'Yeh Rishta Kya Kehlata Hai', - start: '2025-06-09T18:00:00.000Z', - stop: '2025-06-09T18:30:00.000Z', - description: 'The story of the Rajshri family and their journey through life.', - category: 'Drama', - icon: 'https://img.tataplay.com/thumbnails/1001/yeh-rishta.jpg' - }) - expect(results[1]).toMatchObject({ - title: 'Anupamaa', - start: '2025-06-09T18:30:00.000Z', - stop: '2025-06-09T19:00:00.000Z', - description: 'The story of Anupamaa, a housewife who rediscovers herself.', - category: 'Drama', - icon: 'https://img.tataplay.com/thumbnails/1001/anupamaa.jpg' - }) -}) - -it('can handle empty guide', () => { - const content = JSON.stringify({ data: { epg: [] } }) - const results = parser({ content, date }) - expect(results).toMatchObject([]) -}) - -it('can parse channel list', async () => { - const mockResponse = { - data: { - data: { - total: 2, - channelList: [ - { - id: '1001', - title: 'Star Plus', - transparentImageUrl: 'https://img.tataplay.com/channels/1001/logo.png' - }, - { - id: '1002', - title: 'Sony TV', - transparentImageUrl: 'https://img.tataplay.com/channels/1002/logo.png' - } - ] - } - } - } - - // Mock axios.get to return our test data - const axios = require('axios') - axios.get = jest.fn().mockResolvedValue(mockResponse) - - const results = await channels() - - expect(results.length).toBe(2) - expect(results[0]).toMatchObject({ - site_id: '1001', - name: 'Star Plus', - lang: 'en', - icon: 'https://img.tataplay.com/channels/1001/logo.png' - }) - expect(results[1]).toMatchObject({ - site_id: '1002', - name: 'Sony TV', - lang: 'en', - icon: 'https://img.tataplay.com/channels/1002/logo.png' - }) -}) +const { parser, url, channels } = require('./tataplay.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-06-09', 'YYYY-MM-DD').startOf('d') +const channel = { site_id: '1001' } + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://tm.tapi.videoready.tv/content-detail/pub/api/v2/channels/schedule?date=09-06-2025' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + + const results = parser({ content, date }) + + expect(results.length).toBe(2) + expect(results[0]).toMatchObject({ + title: 'Yeh Rishta Kya Kehlata Hai', + start: '2025-06-09T18:00:00.000Z', + stop: '2025-06-09T18:30:00.000Z', + description: 'The story of the Rajshri family and their journey through life.', + category: 'Drama', + icon: 'https://img.tataplay.com/thumbnails/1001/yeh-rishta.jpg' + }) + expect(results[1]).toMatchObject({ + title: 'Anupamaa', + start: '2025-06-09T18:30:00.000Z', + stop: '2025-06-09T19:00:00.000Z', + description: 'The story of Anupamaa, a housewife who rediscovers herself.', + category: 'Drama', + icon: 'https://img.tataplay.com/thumbnails/1001/anupamaa.jpg' + }) +}) + +it('can handle empty guide', () => { + const content = JSON.stringify({ data: { epg: [] } }) + const results = parser({ content, date }) + expect(results).toMatchObject([]) +}) + +it('can parse channel list', async () => { + const mockResponse = { + data: { + data: { + total: 2, + channelList: [ + { + id: '1001', + title: 'Star Plus', + transparentImageUrl: 'https://img.tataplay.com/channels/1001/logo.png' + }, + { + id: '1002', + title: 'Sony TV', + transparentImageUrl: 'https://img.tataplay.com/channels/1002/logo.png' + } + ] + } + } + } + + // Mock axios.get to return our test data + const axios = require('axios') + axios.get = jest.fn().mockResolvedValue(mockResponse) + + const results = await channels() + + expect(results.length).toBe(2) + expect(results[0]).toMatchObject({ + site_id: '1001', + name: 'Star Plus', + lang: 'en', + icon: 'https://img.tataplay.com/channels/1001/logo.png' + }) + expect(results[1]).toMatchObject({ + site_id: '1002', + name: 'Sony TV', + lang: 'en', + icon: 'https://img.tataplay.com/channels/1002/logo.png' + }) +}) diff --git a/sites/teliatv.ee/teliatv.ee.test.js b/sites/teliatv.ee/teliatv.ee.test.js index 322f9591b..de2b8b639 100644 --- a/sites/teliatv.ee/teliatv.ee.test.js +++ b/sites/teliatv.ee/teliatv.ee.test.js @@ -1,60 +1,60 @@ -const { parser, url } = require('./teliatv.ee.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2021-11-20', 'YYYY-MM-DD').startOf('d') -const channel = { - lang: 'et', - site_id: 'et#1', - xmltv_id: 'ETV.ee' -} - -it('can generate valid url', () => { - expect(url({ date, channel })).toBe( - 'https://api.teliatv.ee/dtv-api/3.2/et/epg/guide?channelIds=1&relations=programmes&images=webGuideItemLarge&startAt=2021-11-21T00:00&startAtOp=lte&endAt=2021-11-20T00:00&endAtOp=gt' - ) -}) - -it('can generate valid url with different language', () => { - const ruChannel = { - lang: 'ru', - site_id: 'ru#1', - xmltv_id: 'ETV.ee' - } - expect(url({ date, channel: ruChannel })).toBe( - 'https://api.teliatv.ee/dtv-api/3.2/ru/epg/guide?channelIds=1&relations=programmes&images=webGuideItemLarge&startAt=2021-11-21T00:00&startAtOp=lte&endAt=2021-11-20T00:00&endAtOp=gt' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ content, channel }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2021-11-19T22:05:00.000Z', - stop: '2021-11-19T22:55:00.000Z', - title: 'Inimjaht', - image: - 'https://inet-static.mw.elion.ee/resized/ri93Qj4OLXXvg7QAsUOcKMnIb3g=/570x330/filters:format(jpeg)/inet-static.mw.elion.ee/epg_images/9/b/17e48b3966e65c02.jpg' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./teliatv.ee.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2021-11-20', 'YYYY-MM-DD').startOf('d') +const channel = { + lang: 'et', + site_id: 'et#1', + xmltv_id: 'ETV.ee' +} + +it('can generate valid url', () => { + expect(url({ date, channel })).toBe( + 'https://api.teliatv.ee/dtv-api/3.2/et/epg/guide?channelIds=1&relations=programmes&images=webGuideItemLarge&startAt=2021-11-21T00:00&startAtOp=lte&endAt=2021-11-20T00:00&endAtOp=gt' + ) +}) + +it('can generate valid url with different language', () => { + const ruChannel = { + lang: 'ru', + site_id: 'ru#1', + xmltv_id: 'ETV.ee' + } + expect(url({ date, channel: ruChannel })).toBe( + 'https://api.teliatv.ee/dtv-api/3.2/ru/epg/guide?channelIds=1&relations=programmes&images=webGuideItemLarge&startAt=2021-11-21T00:00&startAtOp=lte&endAt=2021-11-20T00:00&endAtOp=gt' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ content, channel }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2021-11-19T22:05:00.000Z', + stop: '2021-11-19T22:55:00.000Z', + title: 'Inimjaht', + image: + 'https://inet-static.mw.elion.ee/resized/ri93Qj4OLXXvg7QAsUOcKMnIb3g=/570x330/filters:format(jpeg)/inet-static.mw.elion.ee/epg_images/9/b/17e48b3966e65c02.jpg' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/tv.blue.ch/tv.blue.ch.test.js b/sites/tv.blue.ch/tv.blue.ch.test.js index 539b2485c..0f268e652 100644 --- a/sites/tv.blue.ch/tv.blue.ch.test.js +++ b/sites/tv.blue.ch/tv.blue.ch.test.js @@ -1,72 +1,72 @@ -const { parser, url } = require('./tv.blue.ch.config.js') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const fs = require('fs') -const path = require('path') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-01-17', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '1221', - xmltv_id: 'BlueZoomD.ch' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://services.sg101.prd.sctv.ch/catalog/tv/channels/list/(ids=1221;start=202201170000;end=202201180000;level=normal)' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2022-01-16T23:30:00.000Z', - stop: '2022-01-17T00:00:00.000Z', - title: 'Weekend on the Rocks', - description: - ' - «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.', - image: - 'https://services.sg101.prd.sctv.ch/content/images/tv/broadcast/1221/t1221ddc59247d45_landscape_w1920.webp' - } - ]) -}) - -it('can parse response without image', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_without_image.json')) - const result = parser({ content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2022-01-17T04:59:00.000Z', - stop: '2022-01-17T05:00:00.000Z', - title: 'Lorem ipsum' - } - ]) -}) - -it('can handle wrong site id', () => { - const result = parser({ - content: fs.readFileSync(path.resolve(__dirname, '__data__/content_invalid_siteid.json')) - }) - expect(result).toMatchObject([]) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./tv.blue.ch.config.js') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const fs = require('fs') +const path = require('path') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-01-17', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '1221', + xmltv_id: 'BlueZoomD.ch' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://services.sg101.prd.sctv.ch/catalog/tv/channels/list/(ids=1221;start=202201170000;end=202201180000;level=normal)' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2022-01-16T23:30:00.000Z', + stop: '2022-01-17T00:00:00.000Z', + title: 'Weekend on the Rocks', + description: + ' - «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.', + image: + 'https://services.sg101.prd.sctv.ch/content/images/tv/broadcast/1221/t1221ddc59247d45_landscape_w1920.webp' + } + ]) +}) + +it('can parse response without image', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_without_image.json')) + const result = parser({ content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2022-01-17T04:59:00.000Z', + stop: '2022-01-17T05:00:00.000Z', + title: 'Lorem ipsum' + } + ]) +}) + +it('can handle wrong site id', () => { + const result = parser({ + content: fs.readFileSync(path.resolve(__dirname, '__data__/content_invalid_siteid.json')) + }) + expect(result).toMatchObject([]) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/tv.dir.bg/tv.dir.bg.channels.xml b/sites/tv.dir.bg/tv.dir.bg.channels.xml index 130fe1a45..c3ee65847 100644 --- a/sites/tv.dir.bg/tv.dir.bg.channels.xml +++ b/sites/tv.dir.bg/tv.dir.bg.channels.xml @@ -1,103 +1,103 @@ - - - 24 Kitchen - 7/8 TV - Al Jazeera - Animal Planet - AXN - AXN Black - AXN White - Baby TV - BBC News (former BBC World News) - Bloomberg TV Bulgaria - BNT1 (БНТ1) - BNT2 (БНТ2) - BNT3 (БНТ3) - BNT4 (БНТ4) - bTV - bTV Action - bTV Cinema - bTV Comedy - bTV Story (f.k.a bTV Lady) - Bulgaria ON AIR (България Он Еър) - Cartoon Network Bulgaria - Cartoonito (f.k.a Boomerang TV) - Cinemania - Cinemax - Cinemax 2 - CineStar TV - CineStar TV Action&Thriller - CNN - Code Fashion TV - Code Health TV - Crime & Investigation - Diema - Diema Family - Diema Sport - Diema Sport 2 - Diema Sport 3 - Discovery Channel - Disney Channel - Dizi (Timeless Drama Channel) - Duck TV - DW TV - Epic Drama - Eurocom - Euronews - Euronews Bulgaria (f.k.a Evropa TV) - Eurosport - Eurosport 2 - Eurosport 4K - Fightklub HD (Bulgaria) - FilmBox Basic - FilmBox Extra - FilmBox Stars (FilmBox Plus) - Food Network HD - France 24 - HBO - HBO2 - HBO3 - HGTV (Discovery Home & Garden) - History Bulgaria - ID (Investigation Discovery) - Kanal 3 (Канал 3) - Kanal 4 (Канал 4) - Kino Nova - Love Nature - Magic TV - MAX Sport 1 - MAX Sport 2 - MAX Sport 3 - MAX Sport 4 - MovieSTAR - MTV Europe - National Geographic - National Geographic Wild - Nick Jr - Nickelodeon - Nicktoons - Nostalgia TV - Nova News HD - NOVA Sport - NOVA TV - Ring.bg (bTV Sport) - RTL - SKAT TV - Skyshowtime 1 - Skyshowtime 2 - STAR Channel (f.k.a. FOX) - STAR Crime (f.k.a FOX Crime) - STAR Life (f.k.a. FOX Life) - Super Toons - The History Channel 2 - The Voice TV - TLC - Travel Channel - TV1 Bulgaria - Viasat Explore - Viasat History - Viasat Kino (TV1000) - Viasat Nature - Viasat True Crime - Vivacom Arena (Виваком Арена) - + + + 24 Kitchen + 7/8 TV + Al Jazeera + Animal Planet + AXN + AXN Black + AXN White + Baby TV + BBC News (former BBC World News) + Bloomberg TV Bulgaria + BNT1 (БНТ1) + BNT2 (БНТ2) + BNT3 (БНТ3) + BNT4 (БНТ4) + bTV + bTV Action + bTV Cinema + bTV Comedy + bTV Story (f.k.a bTV Lady) + Bulgaria ON AIR (България Он Еър) + Cartoon Network Bulgaria + Cartoonito (f.k.a Boomerang TV) + Cinemania + Cinemax + Cinemax 2 + CineStar TV + CineStar TV Action&Thriller + CNN + Code Fashion TV + Code Health TV + Crime & Investigation + Diema + Diema Family + Diema Sport + Diema Sport 2 + Diema Sport 3 + Discovery Channel + Disney Channel + Dizi (Timeless Drama Channel) + Duck TV + DW TV + Epic Drama + Eurocom + Euronews + Euronews Bulgaria (f.k.a Evropa TV) + Eurosport + Eurosport 2 + Eurosport 4K + Fightklub HD (Bulgaria) + FilmBox Basic + FilmBox Extra + FilmBox Stars (FilmBox Plus) + Food Network HD + France 24 + HBO + HBO2 + HBO3 + HGTV (Discovery Home & Garden) + History Bulgaria + ID (Investigation Discovery) + Kanal 3 (Канал 3) + Kanal 4 (Канал 4) + Kino Nova + Love Nature + Magic TV + MAX Sport 1 + MAX Sport 2 + MAX Sport 3 + MAX Sport 4 + MovieSTAR + MTV Europe + National Geographic + National Geographic Wild + Nick Jr + Nickelodeon + Nicktoons + Nostalgia TV + Nova News HD + NOVA Sport + NOVA TV + Ring.bg (bTV Sport) + RTL + SKAT TV + Skyshowtime 1 + Skyshowtime 2 + STAR Channel (f.k.a. FOX) + STAR Crime (f.k.a FOX Crime) + STAR Life (f.k.a. FOX Life) + Super Toons + The History Channel 2 + The Voice TV + TLC + Travel Channel + TV1 Bulgaria + Viasat Explore + Viasat History + Viasat Kino (TV1000) + Viasat Nature + Viasat True Crime + Vivacom Arena (Виваком Арена) + diff --git a/sites/tv.dir.bg/tv.dir.bg.config.js b/sites/tv.dir.bg/tv.dir.bg.config.js index e62e97e13..adf780d03 100644 --- a/sites/tv.dir.bg/tv.dir.bg.config.js +++ b/sites/tv.dir.bg/tv.dir.bg.config.js @@ -1,216 +1,216 @@ -const axios = require('axios') -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -let sessionCache = null - -async function getSession(forceRefresh = false) { - if (sessionCache && !forceRefresh) { - return sessionCache - } - - try { - const initResponse = await axios.get('https://tv.dir.bg/init') - - if (!initResponse.data) { - throw new Error('No response data from init endpoint') - } - - // Extract cookies from response headers - const setCookieHeader = initResponse.headers['set-cookie'] - let xsrfToken = null - let dirSessionCookie = null - - if (setCookieHeader) { - setCookieHeader.forEach(cookie => { - // Extract XSRF token from cookie - const xsrfMatch = cookie.match(/XSRF-TOKEN=([^;]+)/) - if (xsrfMatch) { - xsrfToken = decodeURIComponent(xsrfMatch[1]) - } - - // Extract dir_session cookie - const sessionMatch = cookie.match(/dir_session=([^;]+)/) - if (sessionMatch) { - dirSessionCookie = sessionMatch[1] - } - }) - } - - const csrfToken = initResponse.data.csrfToken - - if (!csrfToken) { - throw new Error('No CSRF/XSRF token found in response') - } - - // Build cookie string - let cookieString = '' - if (xsrfToken) { - cookieString += `XSRF-TOKEN=${encodeURIComponent(xsrfToken)}` - } - if (dirSessionCookie) { - if (cookieString) cookieString += '; ' - cookieString += `dir_session=${dirSessionCookie}` - } - - sessionCache = { - csrfToken, - cookieString, - timestamp: Date.now() - } - - return sessionCache - - } catch (error) { - console.error('Error getting session:', error.message) - throw error - } -} - -module.exports = { - site: 'tv.dir.bg', - days: 2, - url: 'https://tv.dir.bg/load/programs', - request: { - maxContentLength: 125000000, // 10 MB - method: 'POST', - async headers() { - try { - const session = await getSession() - return { - 'Cookie': session.cookieString, - 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', - 'X-Requested-With': 'XMLHttpRequest' - } - - } catch (error) { - console.error('Error getting headers:', error.message) - throw error - } - }, - async data({ channel, date }) { - try { - const session = await getSession() - - const params = new URLSearchParams() - params.append('_token', session.csrfToken) - params.append('channel', channel.site_id) - params.append('day', date.format('YYYY-MM-DD')) - - return params - - } catch (error) { - console.error('Error preparing request data:', error.message) - throw error - } - }, - }, - parser({ content, date }) { - const programs = [] - const items = parseItems(content) - - items.forEach(item => { - const prev = programs[programs.length - 1] - const $item = cheerio.load(item) - let start = parseStart($item, date) - - if (prev) { - if (start.isBefore(prev.start)) { - start = start.add(1, 'd') - } - prev.stop = start - } - - const stop = start.add(30, 'm') - programs.push({ - title: parseTitle($item), - start, - stop - }) - }) - - return programs - }, - - async channels() { - try { - const response = await axios.get('https://tv.dir.bg/channels') - const $ = cheerio.load(response.data) - - const channels = [] - - $('.channel_cont').each((_index, element) => { - const $element = $(element) - - const $link = $element.find('a.channel_link') - const href = $link.attr('href') - - const $img = $element.find('img') - const name = $img.attr('alt') - const logo = $img.attr('src') - - const site_id = href ? href.match(/\/programa\/(\d+)/)?.[1] : '' - - if (site_id && name) { - channels.push({ - lang: 'bg', - site_id: site_id, - name: name.trim(), - logo: logo ? (logo.startsWith('http') ? logo : `https://tv.dir.bg${logo}`) : null - }) - } - }) - - return channels - - } catch (error) { - console.error('Error fetching channels:', error.message) - return [] - } - }, - - clearSession() { - sessionCache = null - } -} - -function parseStart($item, date) { - const time = $item('.broadcast-time').text().trim() - const dateString = `${date.format('YYYY-MM-DD')} ${time}` - - return dayjs.tz(dateString, 'YYYY-MM-DD HH:mm', 'Europe/Sofia') -} - - -function parseTitle($item) { - return $item('.broadcast-title').text() - .replace(/\s+/g, ' ') - .trim() -} - -function parseItems(content) { - try { - const json = JSON.parse(content) - - if (!json || json.status !== true) { - return [] - } - - const $ = cheerio.load(json.html) - const items = $('.broadcast-item').toArray() - - return items - - } catch (error) { - console.error('❌ Error parsing items:', error.message) - console.error('Error stack:', error.stack) - return [] - } +const axios = require('axios') +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +let sessionCache = null + +async function getSession(forceRefresh = false) { + if (sessionCache && !forceRefresh) { + return sessionCache + } + + try { + const initResponse = await axios.get('https://tv.dir.bg/init') + + if (!initResponse.data) { + throw new Error('No response data from init endpoint') + } + + // Extract cookies from response headers + const setCookieHeader = initResponse.headers['set-cookie'] + let xsrfToken = null + let dirSessionCookie = null + + if (setCookieHeader) { + setCookieHeader.forEach(cookie => { + // Extract XSRF token from cookie + const xsrfMatch = cookie.match(/XSRF-TOKEN=([^;]+)/) + if (xsrfMatch) { + xsrfToken = decodeURIComponent(xsrfMatch[1]) + } + + // Extract dir_session cookie + const sessionMatch = cookie.match(/dir_session=([^;]+)/) + if (sessionMatch) { + dirSessionCookie = sessionMatch[1] + } + }) + } + + const csrfToken = initResponse.data.csrfToken + + if (!csrfToken) { + throw new Error('No CSRF/XSRF token found in response') + } + + // Build cookie string + let cookieString = '' + if (xsrfToken) { + cookieString += `XSRF-TOKEN=${encodeURIComponent(xsrfToken)}` + } + if (dirSessionCookie) { + if (cookieString) cookieString += '; ' + cookieString += `dir_session=${dirSessionCookie}` + } + + sessionCache = { + csrfToken, + cookieString, + timestamp: Date.now() + } + + return sessionCache + + } catch (error) { + console.error('Error getting session:', error.message) + throw error + } +} + +module.exports = { + site: 'tv.dir.bg', + days: 2, + url: 'https://tv.dir.bg/load/programs', + request: { + maxContentLength: 125000000, // 10 MB + method: 'POST', + async headers() { + try { + const session = await getSession() + return { + 'Cookie': session.cookieString, + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + 'X-Requested-With': 'XMLHttpRequest' + } + + } catch (error) { + console.error('Error getting headers:', error.message) + throw error + } + }, + async data({ channel, date }) { + try { + const session = await getSession() + + const params = new URLSearchParams() + params.append('_token', session.csrfToken) + params.append('channel', channel.site_id) + params.append('day', date.format('YYYY-MM-DD')) + + return params + + } catch (error) { + console.error('Error preparing request data:', error.message) + throw error + } + }, + }, + parser({ content, date }) { + const programs = [] + const items = parseItems(content) + + items.forEach(item => { + const prev = programs[programs.length - 1] + const $item = cheerio.load(item) + let start = parseStart($item, date) + + if (prev) { + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') + } + prev.stop = start + } + + const stop = start.add(30, 'm') + programs.push({ + title: parseTitle($item), + start, + stop + }) + }) + + return programs + }, + + async channels() { + try { + const response = await axios.get('https://tv.dir.bg/channels') + const $ = cheerio.load(response.data) + + const channels = [] + + $('.channel_cont').each((_index, element) => { + const $element = $(element) + + const $link = $element.find('a.channel_link') + const href = $link.attr('href') + + const $img = $element.find('img') + const name = $img.attr('alt') + const logo = $img.attr('src') + + const site_id = href ? href.match(/\/programa\/(\d+)/)?.[1] : '' + + if (site_id && name) { + channels.push({ + lang: 'bg', + site_id: site_id, + name: name.trim(), + logo: logo ? (logo.startsWith('http') ? logo : `https://tv.dir.bg${logo}`) : null + }) + } + }) + + return channels + + } catch (error) { + console.error('Error fetching channels:', error.message) + return [] + } + }, + + clearSession() { + sessionCache = null + } +} + +function parseStart($item, date) { + const time = $item('.broadcast-time').text().trim() + const dateString = `${date.format('YYYY-MM-DD')} ${time}` + + return dayjs.tz(dateString, 'YYYY-MM-DD HH:mm', 'Europe/Sofia') +} + + +function parseTitle($item) { + return $item('.broadcast-title').text() + .replace(/\s+/g, ' ') + .trim() +} + +function parseItems(content) { + try { + const json = JSON.parse(content) + + if (!json || json.status !== true) { + return [] + } + + const $ = cheerio.load(json.html) + const items = $('.broadcast-item').toArray() + + return items + + } catch (error) { + console.error('❌ Error parsing items:', error.message) + console.error('Error stack:', error.stack) + return [] + } } \ No newline at end of file diff --git a/sites/tv.dir.bg/tv.dir.bg.test.js b/sites/tv.dir.bg/tv.dir.bg.test.js index eac5d01d1..4f35d3a07 100644 --- a/sites/tv.dir.bg/tv.dir.bg.test.js +++ b/sites/tv.dir.bg/tv.dir.bg.test.js @@ -1,50 +1,50 @@ -const { parser, url } = require('./tv.dir.bg.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-06-30', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '61', - xmltv_id: 'BTV.bg' -} - -it('can generate valid url', () => { - expect(url).toBe('https://tv.dir.bg/load/programs') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const results = parser({ content, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(63) - - expect(results[0]).toMatchObject({ - start: '2025-06-30T03:00:00.000Z', - stop: '2025-06-30T03:30:00.000Z', - title: 'Светът на здравето' - }) - - expect(results[62]).toMatchObject({ - start: '2025-07-01T02:00:00.000Z', - stop: '2025-07-01T02:30:00.000Z', - title: 'Убийства в Рая , сезон 1 , епизод 7' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./tv.dir.bg.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-06-30', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '61', + xmltv_id: 'BTV.bg' +} + +it('can generate valid url', () => { + expect(url).toBe('https://tv.dir.bg/load/programs') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const results = parser({ content, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(63) + + expect(results[0]).toMatchObject({ + start: '2025-06-30T03:00:00.000Z', + stop: '2025-06-30T03:30:00.000Z', + title: 'Светът на здравето' + }) + + expect(results[62]).toMatchObject({ + start: '2025-07-01T02:00:00.000Z', + stop: '2025-07-01T02:30:00.000Z', + title: 'Убийства в Рая , сезон 1 , епизод 7' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/tv.lv/tv.lv.test.js b/sites/tv.lv/tv.lv.test.js index 2a53efd2c..57e4cd742 100644 --- a/sites/tv.lv/tv.lv.test.js +++ b/sites/tv.lv/tv.lv.test.js @@ -1,54 +1,54 @@ -const { parser, url } = require('./tv.lv.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-11-30', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'ltv1', - xmltv_id: 'LTV1.lv' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://www.tv.lv/programme/listing/none/30-11-2023?filter=channel&subslug=ltv1' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const results = parser({ content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(40) - - expect(results[0]).toMatchObject({ - start: '2023-11-29T22:05:00.000Z', - stop: '2023-11-29T22:35:00.000Z', - title: 'Ielas garumā. Pārdaugavas koka arhitektūra', - description: '', - category: '' - }) - - expect(results[39]).toMatchObject({ - start: '2023-11-30T21:30:00.000Z', - stop: '2023-11-30T22:30:00.000Z', - title: 'Latvijas Sirdsdziesma', - description: '', - category: '' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - }) - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./tv.lv.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-11-30', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'ltv1', + xmltv_id: 'LTV1.lv' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://www.tv.lv/programme/listing/none/30-11-2023?filter=channel&subslug=ltv1' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const results = parser({ content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(40) + + expect(results[0]).toMatchObject({ + start: '2023-11-29T22:05:00.000Z', + stop: '2023-11-29T22:35:00.000Z', + title: 'Ielas garumā. Pārdaugavas koka arhitektūra', + description: '', + category: '' + }) + + expect(results[39]).toMatchObject({ + start: '2023-11-30T21:30:00.000Z', + stop: '2023-11-30T22:30:00.000Z', + title: 'Latvijas Sirdsdziesma', + description: '', + category: '' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + }) + expect(results).toMatchObject([]) +}) diff --git a/sites/tv.magenta.at/tv.magenta.at.config.js b/sites/tv.magenta.at/tv.magenta.at.config.js index aebd8c0df..a9bcbb72e 100644 --- a/sites/tv.magenta.at/tv.magenta.at.config.js +++ b/sites/tv.magenta.at/tv.magenta.at.config.js @@ -1,147 +1,147 @@ -const axios = require('axios') -const crypto = require('crypto') -const dayjs = require('dayjs') - -const API_ENDPOINT = 'https://tv-at-prod.yo-digital.com/at-bifrost' - -const headers = { - 'Device-Id': crypto.randomUUID(), - app_key: 'CTnKA63ruKM0JM1doxAXwwyQLLmQiEiy', - app_version: '02.0.1260', - 'X-User-Agent': 'web|web|Firefox-120|02.0.1260|1', - 'x-request-tracking-id': crypto.randomUUID() -} - -module.exports = { - site: 'tv.magenta.at', - days: 2, - request: { - headers, - cache: { - ttl: 60 * 60 * 1000 // 1 hour - } - }, - url: function ({ channel, date }) { - return `${API_ENDPOINT}/epg/channel/schedules/v2?station_ids=${ - channel.site_id - }&date=${date.format('YYYY-MM-DD')}&hour_offset=${date.format('H')}&hour_range=3&natco_code=at` - }, - async parser({ content, channel, date }) { - let programs = [] - if (!content) return programs - - let items = parseItems(JSON.parse(content), channel) - if (!items.length) return programs - - const promises = [3, 6, 9, 12, 15, 18, 21].map(i => - axios.get( - `${API_ENDPOINT}/epg/channel/schedules/v2?station_ids=${channel.site_id}&date=${date.format( - 'YYYY-MM-DD' - )}&hour_offset=${i}&hour_range=3&natco_code=at`, - { headers } - ) - ) - - await Promise.allSettled(promises) - .then(results => { - results.forEach(r => { - if (r.status === 'fulfilled') { - const parsed = parseItems(r.value.data, channel) - - items = items.concat(parsed) - } - }) - }) - .catch(console.error) - - for (let item of items) { - const detail = await loadProgramDetails(item) - programs.push({ - title: item.description, - description: parseDescription(detail), - date: parseDate(item), - category: parseCategory(item), - image: detail.poster_image_url, - actors: parseRoles(detail, 'Schauspieler'), - directors: parseRoles(detail, 'Regisseur'), - producers: parseRoles(detail, 'Produzent'), - season: parseSeason(item), - episode: parseEpisode(item), - start: parseStart(item), - stop: parseStop(item) - }) - } - - return programs - }, - async channels() { - const data = await axios - .get(`${API_ENDPOINT}/epg/channel?natco_code=at`, { headers }) - .then(r => r.data) - .catch(console.log) - - return data.channels.map(item => { - return { - lang: 'de', - site_id: item.station_id, - name: item.title - } - }) - } -} - -async function loadProgramDetails(item) { - if (!item.program_id) return {} - const url = `${API_ENDPOINT}/details/series/${item.program_id}?natco_code=at` - const data = await axios - .get(url, { headers }) - .then(r => r.data) - .catch(console.log) - - return data || {} -} - -function parseDate(item) { - return item && item.release_year ? item.release_year.toString() : null -} - -function parseStart(item) { - return dayjs(item.start_time) -} - -function parseStop(item) { - return dayjs(item.end_time) -} - -function parseItems(data, channel) { - if (!data || !data.channels) return [] - const channelData = data.channels[channel.site_id] - if (!channelData) return [] - return channelData -} - -function parseCategory(item) { - if (!item.genres) return null - return item.genres.map(genre => genre.id) -} - -function parseSeason(item) { - if (item.season_display_number === 'Folgen') return null - return item.season_number -} - -function parseEpisode(item) { - if (item.episode_number) return parseInt(item.episode_number) - if (item.season_display_number === 'Folgen') return item.season_number - return null -} - -function parseDescription(item) { - if (!item.details) return null - return item.details.description -} - -function parseRoles(item, role_name) { - if (!item.roles) return null - return item.roles.filter(role => role.role_name === role_name).map(role => role.person_name) -} +const axios = require('axios') +const crypto = require('crypto') +const dayjs = require('dayjs') + +const API_ENDPOINT = 'https://tv-at-prod.yo-digital.com/at-bifrost' + +const headers = { + 'Device-Id': crypto.randomUUID(), + app_key: 'CTnKA63ruKM0JM1doxAXwwyQLLmQiEiy', + app_version: '02.0.1260', + 'X-User-Agent': 'web|web|Firefox-120|02.0.1260|1', + 'x-request-tracking-id': crypto.randomUUID() +} + +module.exports = { + site: 'tv.magenta.at', + days: 2, + request: { + headers, + cache: { + ttl: 60 * 60 * 1000 // 1 hour + } + }, + url: function ({ channel, date }) { + return `${API_ENDPOINT}/epg/channel/schedules/v2?station_ids=${ + channel.site_id + }&date=${date.format('YYYY-MM-DD')}&hour_offset=${date.format('H')}&hour_range=3&natco_code=at` + }, + async parser({ content, channel, date }) { + let programs = [] + if (!content) return programs + + let items = parseItems(JSON.parse(content), channel) + if (!items.length) return programs + + const promises = [3, 6, 9, 12, 15, 18, 21].map(i => + axios.get( + `${API_ENDPOINT}/epg/channel/schedules/v2?station_ids=${channel.site_id}&date=${date.format( + 'YYYY-MM-DD' + )}&hour_offset=${i}&hour_range=3&natco_code=at`, + { headers } + ) + ) + + await Promise.allSettled(promises) + .then(results => { + results.forEach(r => { + if (r.status === 'fulfilled') { + const parsed = parseItems(r.value.data, channel) + + items = items.concat(parsed) + } + }) + }) + .catch(console.error) + + for (let item of items) { + const detail = await loadProgramDetails(item) + programs.push({ + title: item.description, + description: parseDescription(detail), + date: parseDate(item), + category: parseCategory(item), + image: detail.poster_image_url, + actors: parseRoles(detail, 'Schauspieler'), + directors: parseRoles(detail, 'Regisseur'), + producers: parseRoles(detail, 'Produzent'), + season: parseSeason(item), + episode: parseEpisode(item), + start: parseStart(item), + stop: parseStop(item) + }) + } + + return programs + }, + async channels() { + const data = await axios + .get(`${API_ENDPOINT}/epg/channel?natco_code=at`, { headers }) + .then(r => r.data) + .catch(console.log) + + return data.channels.map(item => { + return { + lang: 'de', + site_id: item.station_id, + name: item.title + } + }) + } +} + +async function loadProgramDetails(item) { + if (!item.program_id) return {} + const url = `${API_ENDPOINT}/details/series/${item.program_id}?natco_code=at` + const data = await axios + .get(url, { headers }) + .then(r => r.data) + .catch(console.log) + + return data || {} +} + +function parseDate(item) { + return item && item.release_year ? item.release_year.toString() : null +} + +function parseStart(item) { + return dayjs(item.start_time) +} + +function parseStop(item) { + return dayjs(item.end_time) +} + +function parseItems(data, channel) { + if (!data || !data.channels) return [] + const channelData = data.channels[channel.site_id] + if (!channelData) return [] + return channelData +} + +function parseCategory(item) { + if (!item.genres) return null + return item.genres.map(genre => genre.id) +} + +function parseSeason(item) { + if (item.season_display_number === 'Folgen') return null + return item.season_number +} + +function parseEpisode(item) { + if (item.episode_number) return parseInt(item.episode_number) + if (item.season_display_number === 'Folgen') return item.season_number + return null +} + +function parseDescription(item) { + if (!item.details) return null + return item.details.description +} + +function parseRoles(item, role_name) { + if (!item.roles) return null + return item.roles.filter(role => role.role_name === role_name).map(role => role.person_name) +} diff --git a/sites/tv.mail.ru/tv.mail.ru.config.js b/sites/tv.mail.ru/tv.mail.ru.config.js index 46c11d41a..5b5bb99cc 100644 --- a/sites/tv.mail.ru/tv.mail.ru.config.js +++ b/sites/tv.mail.ru/tv.mail.ru.config.js @@ -1,122 +1,122 @@ -const { DateTime } = require('luxon') -const axios = require('axios') -const { uniqBy } = require('../../scripts/functions') - -module.exports = { - site: 'tv.mail.ru', - days: 2, - delay: 1000, - url({ channel, date }) { - return `https://tv.mail.ru/ajax/channel/?region_id=70&channel_id=${ - channel.site_id - }&date=${date.format('YYYY-MM-DD')}` - }, - parser({ content, date }) { - const programs = [] - const items = parseItems(content) - items.forEach(item => { - const prev = programs[programs.length - 1] - let start = parseStart(item, date) - if (prev) { - if (start < prev.start) { - start = start.plus({ days: 1 }) - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.plus({ hours: 1 }) - programs.push({ - title: item.name, - category: parseCategory(item), - start, - stop - }) - }) - - return programs - }, - async channels() { - const regions = [5506, 1096, 1125, 285] - - let channels = [] - for (let region of regions) { - const totalPages = await getTotalPageCount(region) - const pages = Array.from(Array(totalPages).keys()) - for (let page of pages) { - const data = await axios - .get('https://tv.mail.ru/ajax/channel/list/', { - params: { page }, - headers: { - cookie: `s=fver=0|geo=${region};` - } - }) - .then(r => r.data) - .catch(console.log) - - data.channels.forEach(item => { - channels.push({ - lang: 'ru', - name: item.name, - site_id: item.id - }) - }) - } - } - - return uniqBy(channels, 'site_id') - } -} - -async function getTotalPageCount(region) { - const data = await axios - .get('https://tv.mail.ru/ajax/channel/list/', { - params: { page: 0 }, - headers: { - cookie: `s=fver=0|geo=${region};` - } - }) - .then(r => r.data) - .catch(console.log) - - return data.total -} - -function parseStart(item, date) { - const dateString = `${date.format('YYYY-MM-DD')} ${item.start}` - - return DateTime.fromFormat(dateString, 'yyyy-MM-dd HH:mm', { zone: 'Europe/Moscow' }).toUTC() -} - -function parseCategory(item) { - const categories = { - 1: 'Фильм', - 2: 'Сериал', - 6: 'Документальное', - 7: 'Телемагазин', - 8: 'Позновательное', - 10: 'Другое', - 14: 'ТВ-шоу', - 16: 'Досуг,Хобби', - 17: 'Ток-шоу', - 18: 'Юмористическое', - 23: 'Музыка', - 24: 'Развлекательное', - 25: 'Игровое', - 26: 'Новости' - } - - return categories[item.category_id] - ? { - lang: 'ru', - value: categories[item.category_id] - } - : null -} - -function parseItems(content) { - const json = JSON.parse(content) - if (!Array.isArray(json.schedule) || !json.schedule[0]) return [] - const event = json.schedule[0].event || [] - - return [...event.past, ...event.current] -} +const { DateTime } = require('luxon') +const axios = require('axios') +const { uniqBy } = require('../../scripts/functions') + +module.exports = { + site: 'tv.mail.ru', + days: 2, + delay: 1000, + url({ channel, date }) { + return `https://tv.mail.ru/ajax/channel/?region_id=70&channel_id=${ + channel.site_id + }&date=${date.format('YYYY-MM-DD')}` + }, + parser({ content, date }) { + const programs = [] + const items = parseItems(content) + items.forEach(item => { + const prev = programs[programs.length - 1] + let start = parseStart(item, date) + if (prev) { + if (start < prev.start) { + start = start.plus({ days: 1 }) + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.plus({ hours: 1 }) + programs.push({ + title: item.name, + category: parseCategory(item), + start, + stop + }) + }) + + return programs + }, + async channels() { + const regions = [5506, 1096, 1125, 285] + + let channels = [] + for (let region of regions) { + const totalPages = await getTotalPageCount(region) + const pages = Array.from(Array(totalPages).keys()) + for (let page of pages) { + const data = await axios + .get('https://tv.mail.ru/ajax/channel/list/', { + params: { page }, + headers: { + cookie: `s=fver=0|geo=${region};` + } + }) + .then(r => r.data) + .catch(console.log) + + data.channels.forEach(item => { + channels.push({ + lang: 'ru', + name: item.name, + site_id: item.id + }) + }) + } + } + + return uniqBy(channels, 'site_id') + } +} + +async function getTotalPageCount(region) { + const data = await axios + .get('https://tv.mail.ru/ajax/channel/list/', { + params: { page: 0 }, + headers: { + cookie: `s=fver=0|geo=${region};` + } + }) + .then(r => r.data) + .catch(console.log) + + return data.total +} + +function parseStart(item, date) { + const dateString = `${date.format('YYYY-MM-DD')} ${item.start}` + + return DateTime.fromFormat(dateString, 'yyyy-MM-dd HH:mm', { zone: 'Europe/Moscow' }).toUTC() +} + +function parseCategory(item) { + const categories = { + 1: 'Фильм', + 2: 'Сериал', + 6: 'Документальное', + 7: 'Телемагазин', + 8: 'Позновательное', + 10: 'Другое', + 14: 'ТВ-шоу', + 16: 'Досуг,Хобби', + 17: 'Ток-шоу', + 18: 'Юмористическое', + 23: 'Музыка', + 24: 'Развлекательное', + 25: 'Игровое', + 26: 'Новости' + } + + return categories[item.category_id] + ? { + lang: 'ru', + value: categories[item.category_id] + } + : null +} + +function parseItems(content) { + const json = JSON.parse(content) + if (!Array.isArray(json.schedule) || !json.schedule[0]) return [] + const event = json.schedule[0].event || [] + + return [...event.past, ...event.current] +} diff --git a/sites/tv.mail.ru/tv.mail.ru.test.js b/sites/tv.mail.ru/tv.mail.ru.test.js index 49f76081a..f2fa2c966 100644 --- a/sites/tv.mail.ru/tv.mail.ru.test.js +++ b/sites/tv.mail.ru/tv.mail.ru.test.js @@ -1,77 +1,77 @@ -const { parser, url } = require('./tv.mail.ru.config.js') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -const fs = require('fs') -const path = require('path') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2021-11-24', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '2785', - xmltv_id: '21TV.am' -} -const content = fs.readFileSync(path.join(__dirname, '__data__', 'content.json'), 'utf8') - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://tv.mail.ru/ajax/channel/?region_id=70&channel_id=2785&date=2021-11-24' - ) -}) - -it('can parse response', () => { - const result = parser({ content, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2021-11-24T20:35:00.000Z', - stop: '2021-11-24T22:40:00.000Z', - title: 'Նոնստոպ․ Տեսահոլովակներ', - category: { - lang: 'ru', - value: 'Музыка' - } - }, - { - start: '2021-11-24T22:40:00.000Z', - stop: '2021-11-24T23:40:00.000Z', - title: 'Վերջին թագավորությունը', - category: { - lang: 'ru', - value: 'Сериал' - } - }, - { - start: '2021-11-24T23:40:00.000Z', - stop: '2021-11-25T00:25:00.000Z', - title: 'Պրոֆեսիոնալները', - category: { - lang: 'ru', - value: 'Позновательное' - } - }, - { - start: '2021-11-25T00:25:00.000Z', - stop: '2021-11-25T01:25:00.000Z', - title: 'Նոնստոպ․ Տեսահոլովակներ', - category: { - lang: 'ru', - value: 'Музыка' - } - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: fs.readFileSync(path.join(__dirname, '__data__', 'no_content.json'), 'utf8') - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./tv.mail.ru.config.js') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const fs = require('fs') +const path = require('path') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2021-11-24', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '2785', + xmltv_id: '21TV.am' +} +const content = fs.readFileSync(path.join(__dirname, '__data__', 'content.json'), 'utf8') + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://tv.mail.ru/ajax/channel/?region_id=70&channel_id=2785&date=2021-11-24' + ) +}) + +it('can parse response', () => { + const result = parser({ content, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2021-11-24T20:35:00.000Z', + stop: '2021-11-24T22:40:00.000Z', + title: 'Նոնստոպ․ Տեսահոլովակներ', + category: { + lang: 'ru', + value: 'Музыка' + } + }, + { + start: '2021-11-24T22:40:00.000Z', + stop: '2021-11-24T23:40:00.000Z', + title: 'Վերջին թագավորությունը', + category: { + lang: 'ru', + value: 'Сериал' + } + }, + { + start: '2021-11-24T23:40:00.000Z', + stop: '2021-11-25T00:25:00.000Z', + title: 'Պրոֆեսիոնալները', + category: { + lang: 'ru', + value: 'Позновательное' + } + }, + { + start: '2021-11-25T00:25:00.000Z', + stop: '2021-11-25T01:25:00.000Z', + title: 'Նոնստոպ․ Տեսահոլովակներ', + category: { + lang: 'ru', + value: 'Музыка' + } + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: fs.readFileSync(path.join(__dirname, '__data__', 'no_content.json'), 'utf8') + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/tv.yandex.ru/tv.yandex.ru.test.js b/sites/tv.yandex.ru/tv.yandex.ru.test.js index a1b6cb09d..a9290ac4a 100644 --- a/sites/tv.yandex.ru/tv.yandex.ru.test.js +++ b/sites/tv.yandex.ru/tv.yandex.ru.test.js @@ -1,92 +1,92 @@ -const { parser, url, request } = require('./tv.yandex.ru.config.js') -const fs = require('fs') -const path = require('path') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -const date = dayjs.utc('2023-11-26').startOf('d') -const channel = { - site_id: '16', - xmltv_id: 'ChannelOne.ru' -} -axios.get.mockImplementation(url => { - if (url === 'https://tv.yandex.ru/?date=2023-11-26&grid=all&period=all-day') { - return Promise.resolve({ - headers: {}, - data: fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - }) - } - if (url === 'https://tv.yandex.ru/api/120809?date=2023-11-26&grid=all&period=all-day') { - return Promise.resolve({ - headers: {}, - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/schedule.json'))) - }) - } - if ( - url === - 'https://tv.yandex.ru/api/120809/main/chunk?page=0&date=2023-11-26&period=all-day&offset=0&limit=11' - ) { - return Promise.resolve({ - headers: {}, - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/schedule0.json'))) - }) - } - if (url === 'https://tv.yandex.ru/api/120809/event?eventId=217749657&programCoId=') { - return Promise.resolve({ - headers: {}, - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program.json'))) - }) - } -}) - -it('can generate valid url', () => { - expect(url({ date })).toBe('https://tv.yandex.ru/?date=2023-11-26&grid=all&period=all-day') -}) - -it('can generate valid request headers', () => { - expect(request.headers).toMatchObject({ - Cookie: - 'i=eIUfSP+/mzQWXcH+Cuz8o1vY+D2K8fhBd6Sj0xvbPZeO4l3cY+BvMp8fFIuM17l6UE1Z5+R2a18lP00ex9iYVJ+VT+c=; ' + - 'spravka=dD0xNzM0MjA0NjM4O2k9MTI1LjE2NC4xNDkuMjAwO0Q9QTVCQ0IyOTI5RDQxNkU5NkEyOTcwMTNDMzZGMDAzNjRDNTFFNDM4QkE2Q0IyOTJDRjhCOTZDRDIzODdBQzk2MzRFRDc5QTk2Qjc2OEI1MUY5MTM5M0QzNkY3OEQ2OUY3OTUwNkQ3RjBCOEJGOEJDMjAwMTQ0RDUwRkFCMDNEQzJFMDI2OEI5OTk5OUJBNEFERUYwOEQ1MjUwQTE0QTI3RDU1MEQwM0U0O3U9MTczNDIwNDYzODUyNDYyNzg1NDtoPTIxNTc0ZTc2MDQ1ZjcwMDBkYmY0NTVkM2Q2ZWMyM2Y1; ' + - 'yandexuid=1197179041732383499; ' + - 'yashr=4682342911732383504; ' + - 'yuidss=1197179041732383499; ' + - 'user_display=824' - }) -}) - -it('can parse response', async () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - const result = (await parser({ content, date, channel })).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2023-11-26T01:35:00.000Z', - stop: '2023-11-26T02:10:00.000Z', - title: 'ПОДКАСТ.ЛАБ. Мелодии моей жизни', - category: 'досуг', - description: - 'Впереди вся ночь и есть о чем поговорить. Фильмы, музыка, любовь, звезды, еда, мода, анекдоты, спорт, деньги, настоящее, будущее - все это в творческом эксперименте.\nЛариса Гузеева читает любовные письма. Леонид Якубович рассказывает, кого не берут в пилоты. Арина Холина - какой секс способен довести до мужа или до развода. Валерий Сюткин на ходу сочиняет песню для Карины Кросс и Вали Карнавал. Дмитрий Дибров дарит новую жизнь любимой "Антропологии". Денис Казанский - все о футболе, хоккее и не только.\n"ПОДКАСТЫ. ЛАБ" - серия подкастов разной тематики, которые невозможно проспать. Интеллектуальные дискуссии после полуночи с самыми компетентными экспертами и актуальными спикерами.' - } - ]) -}) - -it('can handle empty guide', async () => { - const result = await parser({ - date, - channel, - content: fs.readFileSync(path.resolve(__dirname, '__data__', 'no_content.html'), 'utf8') - }) - expect(result).toMatchObject([]) -}) +const { parser, url, request } = require('./tv.yandex.ru.config.js') +const fs = require('fs') +const path = require('path') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.utc('2023-11-26').startOf('d') +const channel = { + site_id: '16', + xmltv_id: 'ChannelOne.ru' +} +axios.get.mockImplementation(url => { + if (url === 'https://tv.yandex.ru/?date=2023-11-26&grid=all&period=all-day') { + return Promise.resolve({ + headers: {}, + data: fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + }) + } + if (url === 'https://tv.yandex.ru/api/120809?date=2023-11-26&grid=all&period=all-day') { + return Promise.resolve({ + headers: {}, + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/schedule.json'))) + }) + } + if ( + url === + 'https://tv.yandex.ru/api/120809/main/chunk?page=0&date=2023-11-26&period=all-day&offset=0&limit=11' + ) { + return Promise.resolve({ + headers: {}, + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/schedule0.json'))) + }) + } + if (url === 'https://tv.yandex.ru/api/120809/event?eventId=217749657&programCoId=') { + return Promise.resolve({ + headers: {}, + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program.json'))) + }) + } +}) + +it('can generate valid url', () => { + expect(url({ date })).toBe('https://tv.yandex.ru/?date=2023-11-26&grid=all&period=all-day') +}) + +it('can generate valid request headers', () => { + expect(request.headers).toMatchObject({ + Cookie: + 'i=eIUfSP+/mzQWXcH+Cuz8o1vY+D2K8fhBd6Sj0xvbPZeO4l3cY+BvMp8fFIuM17l6UE1Z5+R2a18lP00ex9iYVJ+VT+c=; ' + + 'spravka=dD0xNzM0MjA0NjM4O2k9MTI1LjE2NC4xNDkuMjAwO0Q9QTVCQ0IyOTI5RDQxNkU5NkEyOTcwMTNDMzZGMDAzNjRDNTFFNDM4QkE2Q0IyOTJDRjhCOTZDRDIzODdBQzk2MzRFRDc5QTk2Qjc2OEI1MUY5MTM5M0QzNkY3OEQ2OUY3OTUwNkQ3RjBCOEJGOEJDMjAwMTQ0RDUwRkFCMDNEQzJFMDI2OEI5OTk5OUJBNEFERUYwOEQ1MjUwQTE0QTI3RDU1MEQwM0U0O3U9MTczNDIwNDYzODUyNDYyNzg1NDtoPTIxNTc0ZTc2MDQ1ZjcwMDBkYmY0NTVkM2Q2ZWMyM2Y1; ' + + 'yandexuid=1197179041732383499; ' + + 'yashr=4682342911732383504; ' + + 'yuidss=1197179041732383499; ' + + 'user_display=824' + }) +}) + +it('can parse response', async () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + const result = (await parser({ content, date, channel })).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2023-11-26T01:35:00.000Z', + stop: '2023-11-26T02:10:00.000Z', + title: 'ПОДКАСТ.ЛАБ. Мелодии моей жизни', + category: 'досуг', + description: + 'Впереди вся ночь и есть о чем поговорить. Фильмы, музыка, любовь, звезды, еда, мода, анекдоты, спорт, деньги, настоящее, будущее - все это в творческом эксперименте.\nЛариса Гузеева читает любовные письма. Леонид Якубович рассказывает, кого не берут в пилоты. Арина Холина - какой секс способен довести до мужа или до развода. Валерий Сюткин на ходу сочиняет песню для Карины Кросс и Вали Карнавал. Дмитрий Дибров дарит новую жизнь любимой "Антропологии". Денис Казанский - все о футболе, хоккее и не только.\n"ПОДКАСТЫ. ЛАБ" - серия подкастов разной тематики, которые невозможно проспать. Интеллектуальные дискуссии после полуночи с самыми компетентными экспертами и актуальными спикерами.' + } + ]) +}) + +it('can handle empty guide', async () => { + const result = await parser({ + date, + channel, + content: fs.readFileSync(path.resolve(__dirname, '__data__', 'no_content.html'), 'utf8') + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/tv2go.t-2.net/tv2go.t-2.net.channels.xml b/sites/tv2go.t-2.net/tv2go.t-2.net.channels.xml index 172c09fab..ac60fc392 100644 --- a/sites/tv2go.t-2.net/tv2go.t-2.net.channels.xml +++ b/sites/tv2go.t-2.net/tv2go.t-2.net.channels.xml @@ -1,338 +1,338 @@ - - - RTS Maribor - TV Veseljak Golica - Discovery Channel - Hayat Plus - AMC - History Channel - History Channel - Disney Channel - Folk Plus - Disney Junior - Alfa TV MAK - MTV 3 - Naša TV - Alfa TV BiH - INFO TV - Animal Planet - Nickelodeon - TV Nakupi - HBO - HBO 2 - HBO 3 - Cinemax - Cinemax 2 - RT Doc - Discovery Science - DTX - ID - Freedom - Filmbox Premium - E! - Pink Serije - Pink Koncert - Pink’n’Roll - Sonce TV - Prva World - Prva Max - Happy Reality - Happy Reality 2 - Prva Files - Prva Kick - Prva Life - Prva Plus - Adria - One Adria - Folx Slovenija - BBC News - Dom TV - Espreso TV - Duck TV - Non Stop - Hit TV - Bloomberg Adria - Arena Sport 1 Premium - Megafon TV - CineStar TV 2 - LH TV - English Club TV - Harmonika TV - GLAM - XXXTazy - Angels - BooB - Capable Hole - Devils Home - Foxy Dolls - MIxxx - Prva TV - BIR TV - KIC TV - Kitchen TV - Mediaset Italia - TgCom24 - R Kanal+ - Cartoon Network - RTV Shqiptar - Europe by Satellite - RTV Atlas - 3SAT - 24Kitchen Adria - 360 Tunebox - Agro TV - Al Jazeera Balkans - Alsat Macedoniae - AMC - Anixe - TV Arena Esport - Arena Fight - Arena Sport 1 - Arena Sport 2 - Arena Sport 3 - Arena Sport 4 - Arte - ATM Kranjska Gora - B92 - Baby TV - Balkan Erotic - Balkanika Music TV - TV Balkan Trip - BBC Earth - BBC First - BHT 1 - BK TV - BN TV 2 - BN Music - Cartoonito - Aktual TV - BRIO - Karousel - CBS Reality - CGTN - Channel One Russia - CineStar TV 1 - Cinestar Action & Thriller - Cinestar Comedy - Cinestar Fantasy - Cinestar Premiere - Cinestar Premiere 2 - Club MTV - CMC - Croatian Music Channel - CNN International - Crime & Investigation Channel - Das Erste (ARD) - Da Vinci - Diva - DM SAT Televizija - Docubox - Dom Kino - Dorcel TV XXX - Duna - Duna World - Dusk - Elta 2 - Elta TV - Epic Drama - ePosavje TV - Erox - Eroxxx - ETV - Euronews - Eurosport (NEM) - Eurosport - Eurosport 2 - Eurosport - EWTN Europe - Exodus - Extreme - Extreme Sports - Fashionbox - Fashion TV - Fastnfunbox - FTV - FenFolk TV - FEN TV - Fightbox - Filmbox Art House - Filmbox Extra - Filmbox Stars - STAR - STAR Crime - STAR Life - STAR Movies - FR2 - France 24 English - France 24 French - Funbox - Gametoon - Gea TV Plus - GOLD TV - TV Zlati zvoki - Happy TV - Hayat - Hayat Folk - Hema - HGTV - H2 - Hot Pleasure - Hot XXL - HRT 1 - HRT 2 - Hustler TV - Hustler TV - Jim Jam - Jugoton TV - Kabel 1 - Kanal 5 - Kanal A - Tring 7 - KINO - Klasik - Koroška TV - M1 - M2 - M5 - Mezzo - Mezzo Live - Milf TV - Minimax - Mreža TV - MTV 1 - MTV 2 - MTV 00s - MTV 80s - MTV 90s - MTV - MTV Hits - MTV Live - Muzika Pervogo - Narodna TV - National Geographic - National Geographic Wild - Net TV - Net XXL - NHK World - Nickelodeon - Nick JR - Nova 24 TV 2 - Nova 24 TV - NTV IC Kakanj - OBN - O Kanal - ORF1 - ORF2 - TV Oron - OTO - OTV - OTV Valentino - PETV - Pink Extra - Pink Film - Pink Folk - Pink Hits - Pink Music - Pink Plus - Pink Reality - Pink SI - Pink World - Pink Zabava - Planet Eva - Planet TV 2 - Planet TV - Play House - POP TV - Pro 7 - Prva - RAI 1 - RAI 2 - RAI 3 - RED xxx - RT - RTK Kosova - RTL 2 HR - RTL - RTL Televizija - RTL2 - RTL Kockica - RTL Living - Super RTL - RTRS - RTS 1 - RTS 2 - RTS - RTVi - Vikom TV - SAT1 - Sci Fi - Servus TV - Sexation - SIP TV - TV Sitel - Sky News - SPORT1 - Šport TV - Šport TV 2 - Šport TV 3 - Festival - Super One - T-2 Info - Telecafe - Telma TV - TLC - TL Novelas Europe - TNT Music - TOP TV - TV Toxic - Trace Sport Stars - Trace Urban - Travel Channel - Travelxp 4K - Travelxp - Tring Action - Tring Shqip - Tring Tring - Tržič TV - TV 3 - TV 8 - 24 Vesti - Arena TV - TV AS - TV Celje - MNE - TV Duga Novi Sad - TVE - TV Galeja - TV IDEA - TV Jadran - KCN - KCN Music - KCN 3 - TV Koper - TV Maribor - TV Miklavž - TV Sarajevo - SLO 1 - SLO 2 - SLO 3 - TV Slon - Vijesti - Vaš kanal - Best TV - Viasat Explore - Viasat History - Viasat Nature - TV1000 - Vitel - Vivid Red - Vivid TV - Tring Vizion+ - VOX - Vremya - VTV Velenje - vŽivo.si - Z1 televizija - ZDF - Zdrava Televizija - Zdrava TV - + + + RTS Maribor + TV Veseljak Golica + Discovery Channel + Hayat Plus + AMC + History Channel + History Channel + Disney Channel + Folk Plus + Disney Junior + Alfa TV MAK + MTV 3 + Naša TV + Alfa TV BiH + INFO TV + Animal Planet + Nickelodeon + TV Nakupi + HBO + HBO 2 + HBO 3 + Cinemax + Cinemax 2 + RT Doc + Discovery Science + DTX + ID + Freedom + Filmbox Premium + E! + Pink Serije + Pink Koncert + Pink’n’Roll + Sonce TV + Prva World + Prva Max + Happy Reality + Happy Reality 2 + Prva Files + Prva Kick + Prva Life + Prva Plus + Adria + One Adria + Folx Slovenija + BBC News + Dom TV + Espreso TV + Duck TV + Non Stop + Hit TV + Bloomberg Adria + Arena Sport 1 Premium + Megafon TV + CineStar TV 2 + LH TV + English Club TV + Harmonika TV + GLAM + XXXTazy + Angels + BooB + Capable Hole + Devils Home + Foxy Dolls + MIxxx + Prva TV + BIR TV + KIC TV + Kitchen TV + Mediaset Italia + TgCom24 + R Kanal+ + Cartoon Network + RTV Shqiptar + Europe by Satellite + RTV Atlas + 3SAT + 24Kitchen Adria + 360 Tunebox + Agro TV + Al Jazeera Balkans + Alsat Macedoniae + AMC + Anixe + TV Arena Esport + Arena Fight + Arena Sport 1 + Arena Sport 2 + Arena Sport 3 + Arena Sport 4 + Arte + ATM Kranjska Gora + B92 + Baby TV + Balkan Erotic + Balkanika Music TV + TV Balkan Trip + BBC Earth + BBC First + BHT 1 + BK TV + BN TV 2 + BN Music + Cartoonito + Aktual TV + BRIO + Karousel + CBS Reality + CGTN + Channel One Russia + CineStar TV 1 + Cinestar Action & Thriller + Cinestar Comedy + Cinestar Fantasy + Cinestar Premiere + Cinestar Premiere 2 + Club MTV + CMC - Croatian Music Channel + CNN International + Crime & Investigation Channel + Das Erste (ARD) + Da Vinci + Diva + DM SAT Televizija + Docubox + Dom Kino + Dorcel TV XXX + Duna + Duna World + Dusk + Elta 2 + Elta TV + Epic Drama + ePosavje TV + Erox + Eroxxx + ETV + Euronews + Eurosport (NEM) + Eurosport + Eurosport 2 + Eurosport + EWTN Europe + Exodus + Extreme + Extreme Sports + Fashionbox + Fashion TV + Fastnfunbox + FTV + FenFolk TV + FEN TV + Fightbox + Filmbox Art House + Filmbox Extra + Filmbox Stars + STAR + STAR Crime + STAR Life + STAR Movies + FR2 + France 24 English + France 24 French + Funbox + Gametoon + Gea TV Plus + GOLD TV + TV Zlati zvoki + Happy TV + Hayat + Hayat Folk + Hema + HGTV + H2 + Hot Pleasure + Hot XXL + HRT 1 + HRT 2 + Hustler TV + Hustler TV + Jim Jam + Jugoton TV + Kabel 1 + Kanal 5 + Kanal A + Tring 7 + KINO + Klasik + Koroška TV + M1 + M2 + M5 + Mezzo + Mezzo Live + Milf TV + Minimax + Mreža TV + MTV 1 + MTV 2 + MTV 00s + MTV 80s + MTV 90s + MTV + MTV Hits + MTV Live + Muzika Pervogo + Narodna TV + National Geographic + National Geographic Wild + Net TV + Net XXL + NHK World + Nickelodeon + Nick JR + Nova 24 TV 2 + Nova 24 TV + NTV IC Kakanj + OBN + O Kanal + ORF1 + ORF2 + TV Oron + OTO + OTV + OTV Valentino + PETV + Pink Extra + Pink Film + Pink Folk + Pink Hits + Pink Music + Pink Plus + Pink Reality + Pink SI + Pink World + Pink Zabava + Planet Eva + Planet TV 2 + Planet TV + Play House + POP TV + Pro 7 + Prva + RAI 1 + RAI 2 + RAI 3 + RED xxx + RT + RTK Kosova + RTL 2 HR + RTL + RTL Televizija + RTL2 + RTL Kockica + RTL Living + Super RTL + RTRS + RTS 1 + RTS 2 + RTS + RTVi + Vikom TV + SAT1 + Sci Fi + Servus TV + Sexation + SIP TV + TV Sitel + Sky News + SPORT1 + Šport TV + Šport TV 2 + Šport TV 3 + Festival + Super One + T-2 Info + Telecafe + Telma TV + TLC + TL Novelas Europe + TNT Music + TOP TV + TV Toxic + Trace Sport Stars + Trace Urban + Travel Channel + Travelxp 4K + Travelxp + Tring Action + Tring Shqip + Tring Tring + Tržič TV + TV 3 + TV 8 + 24 Vesti + Arena TV + TV AS + TV Celje + MNE + TV Duga Novi Sad + TVE + TV Galeja + TV IDEA + TV Jadran + KCN + KCN Music + KCN 3 + TV Koper + TV Maribor + TV Miklavž + TV Sarajevo + SLO 1 + SLO 2 + SLO 3 + TV Slon + Vijesti + Vaš kanal + Best TV + Viasat Explore + Viasat History + Viasat Nature + TV1000 + Vitel + Vivid Red + Vivid TV + Tring Vizion+ + VOX + Vremya + VTV Velenje + vŽivo.si + Z1 televizija + ZDF + Zdrava Televizija + Zdrava TV + diff --git a/sites/tv2go.t-2.net/tv2go.t-2.net.test.js b/sites/tv2go.t-2.net/tv2go.t-2.net.test.js index b54ba8d7b..ea4638970 100644 --- a/sites/tv2go.t-2.net/tv2go.t-2.net.test.js +++ b/sites/tv2go.t-2.net/tv2go.t-2.net.test.js @@ -1,68 +1,68 @@ -const { parser, url, request } = require('./tv2go.t-2.net.config.js') -const dayjs = require('dayjs') -const fs = require('fs') -const path = require('path') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2021-11-19', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '1000259', - xmltv_id: 'TVSlovenija1.si' -} - -it('can generate valid url', () => { - expect(url({ date, channel })).toBe( - 'https://tv2go.t-2.net/Catherine/api/9.4/json/464830403846070/d79cf4dc84f2131689f426956b8d40de/client/tv/getEpg' - ) -}) - -it('can generate valid request headers', () => { - expect(request.headers).toMatchObject({ - 'Content-Type': 'application/json' - }) -}) - -it('can generate valid request data', () => { - expect(request.data({ date, channel })).toMatchObject({ - locale: 'sl-SI', - channelId: [1000259], - startTime: 1637280000000, - endTime: 1637366400000, - imageInfo: [{ height: 500, width: 1100 }], - includeBookmarks: false, - includeShow: true - }) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.join(__dirname, '__data__', 'content.json'), 'utf8') - const result = parser({ content, channel }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2021-11-19T00:50:00.000Z', - stop: '2021-11-19T01:15:00.000Z', - title: 'Dnevnik Slovencev v Italiji', - category: ['Informativni'], - description: - 'Dnevnik Slovencev v Italiji je informativna oddaja, v kateri novinarji poročajo predvsem o dnevnih dogodkih med Slovenci v Italiji.', - image: 'https://tv2go.t-2.net/static/media/img/epg/max_crop/EPG_IMG_2927405.jpg' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: 'Invalid API client identifier' - }) - expect(result).toMatchObject([]) -}) +const { parser, url, request } = require('./tv2go.t-2.net.config.js') +const dayjs = require('dayjs') +const fs = require('fs') +const path = require('path') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2021-11-19', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '1000259', + xmltv_id: 'TVSlovenija1.si' +} + +it('can generate valid url', () => { + expect(url({ date, channel })).toBe( + 'https://tv2go.t-2.net/Catherine/api/9.4/json/464830403846070/d79cf4dc84f2131689f426956b8d40de/client/tv/getEpg' + ) +}) + +it('can generate valid request headers', () => { + expect(request.headers).toMatchObject({ + 'Content-Type': 'application/json' + }) +}) + +it('can generate valid request data', () => { + expect(request.data({ date, channel })).toMatchObject({ + locale: 'sl-SI', + channelId: [1000259], + startTime: 1637280000000, + endTime: 1637366400000, + imageInfo: [{ height: 500, width: 1100 }], + includeBookmarks: false, + includeShow: true + }) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.join(__dirname, '__data__', 'content.json'), 'utf8') + const result = parser({ content, channel }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2021-11-19T00:50:00.000Z', + stop: '2021-11-19T01:15:00.000Z', + title: 'Dnevnik Slovencev v Italiji', + category: ['Informativni'], + description: + 'Dnevnik Slovencev v Italiji je informativna oddaja, v kateri novinarji poročajo predvsem o dnevnih dogodkih med Slovenci v Italiji.', + image: 'https://tv2go.t-2.net/static/media/img/epg/max_crop/EPG_IMG_2927405.jpg' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: 'Invalid API client identifier' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/tvcesoir.fr/tvcesoir.fr.config.js b/sites/tvcesoir.fr/tvcesoir.fr.config.js index 900cfc9f2..1a9855bf0 100644 --- a/sites/tvcesoir.fr/tvcesoir.fr.config.js +++ b/sites/tvcesoir.fr/tvcesoir.fr.config.js @@ -1,99 +1,99 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') -const uniqBy = require('../../scripts/functions') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'tvcesoir.fr', - days: 2, - url: function ({ date, channel }) { - return `https://www.tvcesoir.fr/programme-tv/programme/chaine/${ - channel.site_id - }.html?dt=${date.format('YYYY-MM-DD')}` - }, - parser: function ({ content, date, channel }) { - const programs = [] - const items = parseItems(content) - items.forEach(item => { - const prev = programs[programs.length - 1] - const $item = cheerio.load(item) - let start = parseStart($item, date, channel) - if (prev) { - if (start.isBefore(prev.start)) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.add(30, 'm') - programs.push({ - title: parseTitle($item), - start, - stop - }) - }) - - return programs - }, - async channels() { - const axios = require('axios') - - const providers = ['-1', '-2', '-3', '-4', '-5'] - - const channels = [] - for (let provider of providers) { - const data = await axios - .post('https://www.tvcesoir.fr/guide/schedule', null, { - params: { - provider, - region: 'France', - TVperiod: 'Night', - date: dayjs().format('YYYY-MM-DD'), - st: 0, - u_time: 2155, - is_mobile: 1 - } - }) - .then(r => r.data) - .catch(console.log) - - const $ = cheerio.load(data) - $('.channelname').each((i, el) => { - const name = $(el).find('center > a:eq(1)').text() - const url = $(el).find('center > a:eq(1)').attr('href') - const [, number, slug] = url.match(/\/(\d+)\/(.*)\.html$/) - - channels.push({ - lang: 'fr', - name, - site_id: `${number}/${slug}` - }) - }) - } - - return uniqBy(channels, x => x.site_id) - } -} - -function parseStart($item, date) { - const timeString = $item('td:eq(0)').text().trim() - const dateString = `${date.format('YYYY-MM-DD')} ${timeString}` - - return dayjs.tz(dateString, 'YYYY-MM-DD HH[h]mm', 'Europe/Rome') -} - -function parseTitle($item) { - return $item('td:eq(1)').text().trim() -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('table.table > tbody > tr').toArray() -} +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const uniqBy = require('../../scripts/functions') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'tvcesoir.fr', + days: 2, + url: function ({ date, channel }) { + return `https://www.tvcesoir.fr/programme-tv/programme/chaine/${ + channel.site_id + }.html?dt=${date.format('YYYY-MM-DD')}` + }, + parser: function ({ content, date, channel }) { + const programs = [] + const items = parseItems(content) + items.forEach(item => { + const prev = programs[programs.length - 1] + const $item = cheerio.load(item) + let start = parseStart($item, date, channel) + if (prev) { + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.add(30, 'm') + programs.push({ + title: parseTitle($item), + start, + stop + }) + }) + + return programs + }, + async channels() { + const axios = require('axios') + + const providers = ['-1', '-2', '-3', '-4', '-5'] + + const channels = [] + for (let provider of providers) { + const data = await axios + .post('https://www.tvcesoir.fr/guide/schedule', null, { + params: { + provider, + region: 'France', + TVperiod: 'Night', + date: dayjs().format('YYYY-MM-DD'), + st: 0, + u_time: 2155, + is_mobile: 1 + } + }) + .then(r => r.data) + .catch(console.log) + + const $ = cheerio.load(data) + $('.channelname').each((i, el) => { + const name = $(el).find('center > a:eq(1)').text() + const url = $(el).find('center > a:eq(1)').attr('href') + const [, number, slug] = url.match(/\/(\d+)\/(.*)\.html$/) + + channels.push({ + lang: 'fr', + name, + site_id: `${number}/${slug}` + }) + }) + } + + return uniqBy(channels, x => x.site_id) + } +} + +function parseStart($item, date) { + const timeString = $item('td:eq(0)').text().trim() + const dateString = `${date.format('YYYY-MM-DD')} ${timeString}` + + return dayjs.tz(dateString, 'YYYY-MM-DD HH[h]mm', 'Europe/Rome') +} + +function parseTitle($item) { + return $item('td:eq(1)').text().trim() +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('table.table > tbody > tr').toArray() +} diff --git a/sites/tvcesoir.fr/tvcesoir.fr.test.js b/sites/tvcesoir.fr/tvcesoir.fr.test.js index dfe0e991e..10610f107 100644 --- a/sites/tvcesoir.fr/tvcesoir.fr.test.js +++ b/sites/tvcesoir.fr/tvcesoir.fr.test.js @@ -1,50 +1,50 @@ -const { parser, url } = require('./tvcesoir.fr.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-11-24', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '847049/tf-1', - xmltv_id: 'TF1.fr' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://www.tvcesoir.fr/programme-tv/programme/chaine/847049/tf-1.html?dt=2023-11-24' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - const results = parser({ content, channel, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2023-11-24T01:00:00.000Z', - stop: '2023-11-24T01:10:00.000Z', - title: "Tirage de l'Euro Millions" - }) - - expect(results[26]).toMatchObject({ - start: '2023-11-24T22:45:00.000Z', - stop: '2023-11-24T23:15:00.000Z', - title: 'Juge Arthur' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: fs.readFileSync(path.resolve(__dirname, './__data__/no_content.html'), 'utf8') - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./tvcesoir.fr.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-11-24', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '847049/tf-1', + xmltv_id: 'TF1.fr' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://www.tvcesoir.fr/programme-tv/programme/chaine/847049/tf-1.html?dt=2023-11-24' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + const results = parser({ content, channel, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2023-11-24T01:00:00.000Z', + stop: '2023-11-24T01:10:00.000Z', + title: "Tirage de l'Euro Millions" + }) + + expect(results[26]).toMatchObject({ + start: '2023-11-24T22:45:00.000Z', + stop: '2023-11-24T23:15:00.000Z', + title: 'Juge Arthur' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: fs.readFileSync(path.resolve(__dirname, './__data__/no_content.html'), 'utf8') + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/tvcubana.icrt.cu/tvcubana.icrt.cu.test.js b/sites/tvcubana.icrt.cu/tvcubana.icrt.cu.test.js index 6741994df..9e9f453cb 100644 --- a/sites/tvcubana.icrt.cu/tvcubana.icrt.cu.test.js +++ b/sites/tvcubana.icrt.cu/tvcubana.icrt.cu.test.js @@ -1,52 +1,52 @@ -const { parser, url } = require('./tvcubana.icrt.cu.config.js') -const dayjs = require('dayjs') -const fs = require('fs') -const path = require('path') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2021-11-22', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'cv', - xmltv_id: 'CubavisionNacional.cu' -} -let content = fs.readFileSync(path.resolve(__dirname, './__data__/content.json'), {encoding: 'utf8'}) -// in the specific case of this site, the unicode escape sequences are double-escaped -content = content.replace(/\\\\u([0-9a-fA-F]{4})/g, '\\u$1') - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe('https://www.tvcubana.icrt.cu/cartv/cv/lunes.php') -}) - -it('can generate valid url for next day', () => { - expect(url({ channel, date: date.add(2, 'd') })).toBe( - 'https://www.tvcubana.icrt.cu/cartv/cv/miercoles.php' - ) -}) - -it('can parse response', () => { - const result = parser({ date, channel, content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - expect(result).toMatchObject([ - { - start: '2021-11-22T05:40:00.000Z', - stop: '2021-11-22T05:50:00.000Z', - title: 'CARIBE NOTICIAS', - description: 'EMISIÓN DE CIERRE.' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: fs.readFileSync(path.resolve(__dirname, './__data__/no_content.html'), 'utf8') - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./tvcubana.icrt.cu.config.js') +const dayjs = require('dayjs') +const fs = require('fs') +const path = require('path') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2021-11-22', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'cv', + xmltv_id: 'CubavisionNacional.cu' +} +let content = fs.readFileSync(path.resolve(__dirname, './__data__/content.json'), {encoding: 'utf8'}) +// in the specific case of this site, the unicode escape sequences are double-escaped +content = content.replace(/\\\\u([0-9a-fA-F]{4})/g, '\\u$1') + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe('https://www.tvcubana.icrt.cu/cartv/cv/lunes.php') +}) + +it('can generate valid url for next day', () => { + expect(url({ channel, date: date.add(2, 'd') })).toBe( + 'https://www.tvcubana.icrt.cu/cartv/cv/miercoles.php' + ) +}) + +it('can parse response', () => { + const result = parser({ date, channel, content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + expect(result).toMatchObject([ + { + start: '2021-11-22T05:40:00.000Z', + stop: '2021-11-22T05:50:00.000Z', + title: 'CARIBE NOTICIAS', + description: 'EMISIÓN DE CIERRE.' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: fs.readFileSync(path.resolve(__dirname, './__data__/no_content.html'), 'utf8') + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/tvguide.myjcom.jp/tvguide.myjcom.jp.test.js b/sites/tvguide.myjcom.jp/tvguide.myjcom.jp.test.js index d4edce274..51510b314 100644 --- a/sites/tvguide.myjcom.jp/tvguide.myjcom.jp.test.js +++ b/sites/tvguide.myjcom.jp/tvguide.myjcom.jp.test.js @@ -1,51 +1,51 @@ -const { parser, url } = require('./tvguide.myjcom.jp.config.js') -const dayjs = require('dayjs') -const fs = require('fs') -const path = require('path') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-01-14', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '120_200_4', - name: 'Star Channel 1', - xmltv_id: 'StarChannel1.jp' -} -const content = fs.readFileSync(path.resolve(__dirname, './__data__/content.json'), 'utf8') - -it('can generate valid url', () => { - const result = url({ date, channel }) - expect(result).toBe('https://tvguide.myjcom.jp/api/getEpgInfo/?channels=120_200_4_20220114') -}) - -it('can parse response', () => { - const result = parser({ date, channel, content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2022-01-13T20:00:00.000Z', - stop: '2022-01-13T21:00:00.000Z', - title: '[5.1]フードロア:タマリンド', - description: - 'HBO(R)アジア製作。日本の齊藤工などアジアの監督が、各国の食をテーマに描いたアンソロジーシリーズ。(全8話)(19年 シンガポール 56分)', - image: - 'https://tvguide.myjcom.jp/monomedia/si/2022/20220114/7305523/image/7743d17b655b8d2274ca58b74f2f095c.jpg', - category: 'ドラマ' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: fs.readFileSync(path.resolve(__dirname, './__data__/no_content.json'), 'utf8') - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./tvguide.myjcom.jp.config.js') +const dayjs = require('dayjs') +const fs = require('fs') +const path = require('path') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-01-14', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '120_200_4', + name: 'Star Channel 1', + xmltv_id: 'StarChannel1.jp' +} +const content = fs.readFileSync(path.resolve(__dirname, './__data__/content.json'), 'utf8') + +it('can generate valid url', () => { + const result = url({ date, channel }) + expect(result).toBe('https://tvguide.myjcom.jp/api/getEpgInfo/?channels=120_200_4_20220114') +}) + +it('can parse response', () => { + const result = parser({ date, channel, content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2022-01-13T20:00:00.000Z', + stop: '2022-01-13T21:00:00.000Z', + title: '[5.1]フードロア:タマリンド', + description: + 'HBO(R)アジア製作。日本の齊藤工などアジアの監督が、各国の食をテーマに描いたアンソロジーシリーズ。(全8話)(19年 シンガポール 56分)', + image: + 'https://tvguide.myjcom.jp/monomedia/si/2022/20220114/7305523/image/7743d17b655b8d2274ca58b74f2f095c.jpg', + category: 'ドラマ' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: fs.readFileSync(path.resolve(__dirname, './__data__/no_content.json'), 'utf8') + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/tvhebdo.com/tvhebdo.com.config.js b/sites/tvhebdo.com/tvhebdo.com.config.js index 7506ba98e..5f77af29c 100644 --- a/sites/tvhebdo.com/tvhebdo.com.config.js +++ b/sites/tvhebdo.com/tvhebdo.com.config.js @@ -1,97 +1,97 @@ -const cheerio = require('cheerio') -const axios = require('axios') -const { DateTime } = require('luxon') -const { uniqBy } = require('../../scripts/functions') - -module.exports = { - site: 'tvhebdo.com', - days: 2, - url: function ({ channel, date }) { - return `https://www.tvhebdo.com/horaire-tele/${channel.site_id}/date/${date.format( - 'YYYY-MM-DD' - )}` - }, - parser: function ({ content, date }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - const prev = programs[programs.length - 1] - const $item = cheerio.load(item) - let start = parseStart($item, date) - if (prev) { - if (start < prev.start) { - start = start.plus({ days: 1 }) - } - prev.stop = start - } - let stop = start.plus({ minutes: 30 }) - programs.push({ - title: parseTitle($item), - start, - stop - }) - }) - - return programs - }, - async channels() { - - let items = [] - const offsets = [ - 0, 20, 40, 60, 80, 100, 120, 140, 160, 180, 200, 220, 240, 260, 280, 300, 320, 340, 360 - ] - for (let offset of offsets) { - const url = `https://www.tvhebdo.com/horaire/gr/offset/${offset}/gr_id/0/date/2022-05-11/time/12:00:00` - console.log(url) - const html = await axios - .get(url, { - headers: { - Cookie: - 'distributeur=8004264; __utmz=222163677.1652094266.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); _gcl_au=1.1.656635701.1652094273; tvh=3c2kaml9u14m83v91bg4dqgaf3; __utmc=222163677; IR_gbd=tvhebdo.com; IR_MPI=cf76b363-cf87-11ec-93f5-13daf79f8f76%7C1652367602625; __utma=222163677.2064368965.1652094266.1652281202.1652281479.3; __utmt=1; IR_MPS=1652284935955%7C1652284314367; _uetsid=0d8e2e60d13b11ec850db551304ae9e7; _uetvid=80456fa0b26e11ec9bf94951ce79b5f8; __utmb=222163677.19.9.1652284953979; __atuvc=30%7C19; __atuvs=627bdb98682bc242006' - } - }) - .then(r => r.data) - .catch(console.error) - const $ = cheerio.load(html) - const rows = $('table.gr_row').toArray() - items = items.concat(rows) - } - - let channels = [] - items.forEach(item => { - const $item = cheerio.load(item) - const name = $item('.gr_row_head > div > a.gr_row_head_logo.link_to_station > img').attr( - 'alt' - ) - const url = $item('.gr_row_head > div > div.gr_row_head_poste > a').attr('href') - const [, site_id] = url.match(/horaire-tele\/(.*)/) || [null, null] - channels.push({ - lang: 'fr', - site_id, - name - }) - }) - - return uniqBy(channels, x => x.site_id) - } -} - -function parseTitle($item) { - return $item('.titre').first().text().trim() -} - -function parseStart($item, date) { - const time = $item('.heure').text() - - return DateTime.fromFormat(`${date.format('YYYY-MM-DD')} ${time}`, 'yyyy-MM-dd HH:mm', { - zone: 'America/Toronto' - }).toUTC() -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $( - '#main_container > div.liste_container > table > tbody > tr[class^=liste_row_style_]' - ).toArray() -} +const cheerio = require('cheerio') +const axios = require('axios') +const { DateTime } = require('luxon') +const { uniqBy } = require('../../scripts/functions') + +module.exports = { + site: 'tvhebdo.com', + days: 2, + url: function ({ channel, date }) { + return `https://www.tvhebdo.com/horaire-tele/${channel.site_id}/date/${date.format( + 'YYYY-MM-DD' + )}` + }, + parser: function ({ content, date }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + const prev = programs[programs.length - 1] + const $item = cheerio.load(item) + let start = parseStart($item, date) + if (prev) { + if (start < prev.start) { + start = start.plus({ days: 1 }) + } + prev.stop = start + } + let stop = start.plus({ minutes: 30 }) + programs.push({ + title: parseTitle($item), + start, + stop + }) + }) + + return programs + }, + async channels() { + + let items = [] + const offsets = [ + 0, 20, 40, 60, 80, 100, 120, 140, 160, 180, 200, 220, 240, 260, 280, 300, 320, 340, 360 + ] + for (let offset of offsets) { + const url = `https://www.tvhebdo.com/horaire/gr/offset/${offset}/gr_id/0/date/2022-05-11/time/12:00:00` + console.log(url) + const html = await axios + .get(url, { + headers: { + Cookie: + 'distributeur=8004264; __utmz=222163677.1652094266.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); _gcl_au=1.1.656635701.1652094273; tvh=3c2kaml9u14m83v91bg4dqgaf3; __utmc=222163677; IR_gbd=tvhebdo.com; IR_MPI=cf76b363-cf87-11ec-93f5-13daf79f8f76%7C1652367602625; __utma=222163677.2064368965.1652094266.1652281202.1652281479.3; __utmt=1; IR_MPS=1652284935955%7C1652284314367; _uetsid=0d8e2e60d13b11ec850db551304ae9e7; _uetvid=80456fa0b26e11ec9bf94951ce79b5f8; __utmb=222163677.19.9.1652284953979; __atuvc=30%7C19; __atuvs=627bdb98682bc242006' + } + }) + .then(r => r.data) + .catch(console.error) + const $ = cheerio.load(html) + const rows = $('table.gr_row').toArray() + items = items.concat(rows) + } + + let channels = [] + items.forEach(item => { + const $item = cheerio.load(item) + const name = $item('.gr_row_head > div > a.gr_row_head_logo.link_to_station > img').attr( + 'alt' + ) + const url = $item('.gr_row_head > div > div.gr_row_head_poste > a').attr('href') + const [, site_id] = url.match(/horaire-tele\/(.*)/) || [null, null] + channels.push({ + lang: 'fr', + site_id, + name + }) + }) + + return uniqBy(channels, x => x.site_id) + } +} + +function parseTitle($item) { + return $item('.titre').first().text().trim() +} + +function parseStart($item, date) { + const time = $item('.heure').text() + + return DateTime.fromFormat(`${date.format('YYYY-MM-DD')} ${time}`, 'yyyy-MM-dd HH:mm', { + zone: 'America/Toronto' + }).toUTC() +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $( + '#main_container > div.liste_container > table > tbody > tr[class^=liste_row_style_]' + ).toArray() +} diff --git a/sites/tvheute.at/tvheute.at.test.js b/sites/tvheute.at/tvheute.at.test.js index e13afd210..bc39c18f1 100644 --- a/sites/tvheute.at/tvheute.at.test.js +++ b/sites/tvheute.at/tvheute.at.test.js @@ -1,45 +1,45 @@ -const { parser, url } = require('./tvheute.at.config.js') -const dayjs = require('dayjs') -const path = require('path') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -const { readFileSync } = require('fs') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2021-11-08', 'YYYY-MM-DD').startOf('d') -const channel = { site_id: 'orf1', xmltv_id: 'ORF1.at' } - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://tvheute.at/part/channel-shows/partial/orf1/08-11-2021' - ) -}) - -it('can parse response', () => { - expect(parser({ date, channel, content: readFileSync(path.resolve(__dirname, './__data__/content.html'), 'utf8') })).toMatchObject([ - { - start: '2021-11-08T05:00:00.000Z', - stop: '2021-11-08T05:10:00.000Z', - title: 'Monchhichi (Wh.)', - category: 'Kids', - description: - 'Roger hat sich Ärger mit Dr. Bellows eingehandelt, der ihn für einen Monat strafversetzen möchte. Einmal mehr hadert Roger mit dem Schicksal, dass er keinen eigenen Flaschengeist besitzt, der ihm aus der Patsche helfen kann. Jeannie schlägt vor, ihm Cousine Marilla zu schicken. Doch Tony ist strikt dagegen. Als ein Zaubererpärchen im exotischen Bühnenoutfit für die Zeit von Rogers Abwesenheit sein Apartment in Untermiete bezieht, glaubt Roger, Jeannie habe ihm ihre Verwandte doch noch gesandt.', - image: 'https://tvheute.at/images/orf1/monchhichi_kids--1895216560-00.jpg' - }, - { - start: '2021-11-08T17:00:00.000Z', - stop: '2021-11-08T17:10:00.000Z', - title: 'ZIB 18' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: readFileSync(path.resolve(__dirname, './__data__/no_content.html'), 'utf8') - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./tvheute.at.config.js') +const dayjs = require('dayjs') +const path = require('path') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const { readFileSync } = require('fs') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2021-11-08', 'YYYY-MM-DD').startOf('d') +const channel = { site_id: 'orf1', xmltv_id: 'ORF1.at' } + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://tvheute.at/part/channel-shows/partial/orf1/08-11-2021' + ) +}) + +it('can parse response', () => { + expect(parser({ date, channel, content: readFileSync(path.resolve(__dirname, './__data__/content.html'), 'utf8') })).toMatchObject([ + { + start: '2021-11-08T05:00:00.000Z', + stop: '2021-11-08T05:10:00.000Z', + title: 'Monchhichi (Wh.)', + category: 'Kids', + description: + 'Roger hat sich Ärger mit Dr. Bellows eingehandelt, der ihn für einen Monat strafversetzen möchte. Einmal mehr hadert Roger mit dem Schicksal, dass er keinen eigenen Flaschengeist besitzt, der ihm aus der Patsche helfen kann. Jeannie schlägt vor, ihm Cousine Marilla zu schicken. Doch Tony ist strikt dagegen. Als ein Zaubererpärchen im exotischen Bühnenoutfit für die Zeit von Rogers Abwesenheit sein Apartment in Untermiete bezieht, glaubt Roger, Jeannie habe ihm ihre Verwandte doch noch gesandt.', + image: 'https://tvheute.at/images/orf1/monchhichi_kids--1895216560-00.jpg' + }, + { + start: '2021-11-08T17:00:00.000Z', + stop: '2021-11-08T17:10:00.000Z', + title: 'ZIB 18' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: readFileSync(path.resolve(__dirname, './__data__/no_content.html'), 'utf8') + }) + expect(result).toMatchObject([]) +}) From cf6fe095ea4c0220b073207383b9770815ac6620 Mon Sep 17 00:00:00 2001 From: theofficialomega <30985701+BellezaEmporium@users.noreply.github.com> Date: Wed, 30 Jul 2025 22:58:39 +0200 Subject: [PATCH 29/32] CRLF ? --- SITES.md | 484 +++++++++--------- .../tvplus.com.tr/tvplus.com.tr.channels.xml | 292 +++++------ sites/tvplus.com.tr/tvplus.com.tr.config.js | 198 +++---- 3 files changed, 487 insertions(+), 487 deletions(-) diff --git a/SITES.md b/SITES.md index 4fb9e1d96..88c7be24b 100644 --- a/SITES.md +++ b/SITES.md @@ -1,242 +1,242 @@ -# Sites - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    SiteChannels
    (total / with xmltv-id)
    StatusNotes
    9tv.co.il11🟢
    abc.net.au5480🟢
    allente.dk7443🟢
    allente.fi7124🟢
    allente.no8452🟢
    allente.se9291🟢
    andorradifusio.ad11🟢
    anteltv.com.uy5347🟢
    antennaeurope.gr11🟢
    antennapacific.gr11🟢
    antennasatellite.gr11🟢
    arianaafgtv.com11🟢
    arianatelevision.com11🟢
    arirang.com33🟢
    artonline.tv55🟢
    awilime.com1110🟢
    bein.com160160🟢
    beinsports.com10481🟢
    berrymedia.co.kr55🟢
    cableplus.com.uy17147🟢
    canalplus.com11720212🟢
    cgates.lt10261🟢
    chada.ma11🟢
    chaines-tv.orange.fr295146🟢
    clickthecity.com3230🟢
    content.astro.com.my157112🟢
    cosmotetv.gr1080🟢
    ctc.ru11🟢
    cubmu.com174122🟢
    cyta.com.cy1160🟢
    dens.tv6764🟢
    derana.lk11🟢
    digea.gr920🟢
    digiturk.com.tr108107🟢
    directv.com1043696🔴https://github.com/iptv-org/epg/issues/2284
    directv.com.ar412229🔴https://github.com/iptv-org/epg/issues/2339
    directv.com.uy143142🟢
    dishtv.in44889🟢
    dna.fi1220🟢
    dsmart.com.tr10490🟢
    dstv.com6983181🟢
    dtv8.net11🟢
    elcinema.com262226🟢
    ena.skylifetv.co.kr66🟢
    energeek.cl62🟢
    entertainment.ie10995🟢
    epg.112114.xyz9301🟢
    epg.iptvx.one2862747🟢
    epg.telemach.ba2590🟢
    epg.telemach.me2160🟢
    epgmaster.com11🟢
    epgshare01.online2097117🟢
    firstmedia.com116101🟢
    foxsports.com.au77🟢
    foxtel.com.au9961🟢
    freetv.tv77🟢
    freeview.co.uk171100🟢
    frikanalen.no11🟢
    galamtv.kz2722🟢
    gatotv.com475362🟢
    getafteritmedia.com55🟢
    gigatv.3bbtv.co.th7938🟢
    guiadetv.com1240🟢
    guida.tv8888🟢
    guidatv.sky.it168153🟢
    guidetnt.com6969🟢
    horizon.tv184172🟢
    hoy.tv31🟢
    i.mjh.nz64581489🟢
    i24news.tv43🟢
    iltalehti.fi14244🟢
    indihometv.com130124🟢
    ionplustv.com11🟢
    ipko.tv194152🟢
    jiotv.com10940🟢
    kan.org.il33🔴https://github.com/iptv-org/epg/issues/2273
    knr.gl11🟢
    kvf.fo11🟢
    m.tv.sms.cz1027450🟢
    m.tving.com3026🟢
    magticom.ge240110🟢
    mako.co.il11🟢
    makrodigitaltelevision.com11🟢
    maxtvgo.mk11048🟢
    mediagenie.co.kr54🟢
    mediaklikk.hu88🟢
    mediasetinfinity.mediaset.it1313🟢
    melita.com127111🟢
    meo.pt216192🟢
    meuguia.tv10297🟢
    mewatch.sg2524🟢
    mi.tv2084620🟢
    mncvision.id276223🟢
    moji.id11🟢
    mojmaxtv.hrvatskitelekom.hr2430🟢
    mon-programme-tv.be11195🟢
    movistarplus.es1780🟢
    mtel.ba5010🟢
    mts.rs4570🟢
    mujtvprogram.cz216202🟢
    musor.tv181145🟢
    mysky.com.ph11543🟢
    mytelly.co.uk488401🟢
    mytvsuper.com10899🟢
    neo.io337241🟢
    nhkworldpremium.com22🟢
    nhl.com11🟢
    nostv.pt168155🟢
    novacyprus.com2924🟢
    novasports.gr1616🟢
    nowplayer.now.com288229🟢
    nuevosiglo.com.uy17347🟢
    nzxmltv.com532118🟢
    ontvtonight.com5177532🟢
    opto.sic.pt44🟢
    orangetv.orange.es168165🟢
    osn.com11898🟢
    pbsguam.org11🟢
    pickx.be404391🟢
    player.ee.co.uk241206🟢
    playtv.unifi.com.my6661🟢
    plex.tv170119🟢
    pluto.tv33020🟢
    programacion-tv.elpais.com195104🟢
    programacion.tcc.com.uy14956🟢
    programetv.ro331224🟢
    programme-tv.net295197🟢
    programme-tv.vini.pf582🟢
    programme.tvb.com86🟢
    programtv.onet.pl590362🟢
    raiplay.it1713🟢
    reportv.com.ar16397🟢
    rikstv.no800🟢
    rotana.net3228🟢
    rtb.gov.bn33🔴https://github.com/iptv-org/epg/issues/2257
    rthk.hk88🟢
    rtmklik.rtm.gov.my86🟢
    rtp.pt1010🟢
    ruv.is22🟢
    s.mxtv.jp22🟢
    sat.tv30308249🟢
    shahid.mbc.net231165🟢
    siba.com.co9896🟢
    singtel.com155113🟢
    sjonvarp.is1313🟢
    sky.co.nz11193🟢
    sky.com559458🟡https://github.com/iptv-org/epg/issues/2763
    sky.de7575🟢
    skylife.co.kr2510🟢
    skyperfectv.co.jp137130🟢
    snrt.ma117🟢
    sporttv.pt98🟢
    starhubtvplus.com232208🟢
    startimestv.com7758🟢
    stod2.is128🟢
    streamingtvguides.com30661🟢
    superguidatv.it204163🟢
    taiwanplus.com11🟢
    tapdmv.com397🟢
    tataplay.com785401🟢
    telebilbao.es11🟢
    teleboy.ch3250🟢
    telenet.tv26091🟢
    teliatv.ee342233🟢
    telkussa.fi6632🟢
    telsu.fi1715🟢
    thesportplus.com30🟢
    tivie.id4544🟢
    tivu.tv6966🟢
    toonamiaftermath.com11🟢
    turksatkablo.com.tr177118🟢
    tv-programme.telecablesat.fr268250🟢
    tv-spored.siol.net3120🟢
    tv.blue.ch1030565🟢
    tv.cctv.com9488🟢
    tv.dir.bg11193🔴https://github.com/iptv-org/epg/issues/2779
    tv.lv13749🟢
    tv.magenta.at307228🟢
    tv.mail.ru664643🟢
    tv.movistar.com.pe28240🟢
    tv.nu199180🟢
    tv.post.lu332242🟢
    tv.sfr.fr489456🟢
    tv.trueid.net26674🟢
    tv.yandex.ru9767🔴https://github.com/iptv-org/epg/issues/2803
    tv24.co.uk107239🟢
    tv24.se326157🟢
    tv2go.t-2.net335254🟢
    tvarenasport.com1412🟢
    tvarenasport.hr1010🟢
    tvcesoir.fr135133🟢
    tvcubana.icrt.cu1010🟢
    tvgids.nl11590🟢
    tvguide.com153149🟡https://github.com/iptv-org/epg/issues/2644
    tvguide.myjcom.jp145140🟢
    tvhebdo.com317215🟢
    tvheute.at5353🟢
    tvi.iol.pt66🟢
    tvim.tv2519🟢
    tvinsider.com3740🟢
    tvireland.ie334304🟢
    tvkaista.org1490🟢
    tvmi.mt33🟢
    tvmusor.hu9967🟢
    tvmustra.hu1880🟢
    tvpassport.com192872509🟢
    tvplus.com.tr143134🟢https://github.com/iptv-org/epg/issues/2816
    tvprofil.com5836455🟢
    tvtv.us22992255🟢
    v3.myafn.dodmedia.osd.mil88🟢
    vidio.com5752🟢
    virginmediatelevision.ie55🟢
    virgintvgo.virginmedia.com238195🟢
    visionplus.id250226🟢
    vivoplay.com.br3890🟢
    vtm.be76🟢
    walesi.com.fj98🟢
    watch.sportsnet.ca88🟢
    watchyour.tv4024🟢
    wavve.com7776🟢
    web.magentatv.de348247🟢
    webtv.delta.nl247218🟢
    winplay.co22🟢
    worldfishingnetwork.com11🟢
    www3.nhk.or.jp11🟢
    xem.kplus.vn770🟢
    xumo.tv35033🟢
    yes.co.il1740🟢
    zap.co.ao11464🟢
    zap2it.com5950🟢
    ziggogo.tv152130🟢
    znbc.co.zm44🟢
    zuragt.mn3625🟢
    +# Sites + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    SiteChannels
    (total / with xmltv-id)
    StatusNotes
    9tv.co.il11🟢
    abc.net.au5480🟢
    allente.dk7443🟢
    allente.fi7124🟢
    allente.no8452🟢
    allente.se9291🟢
    andorradifusio.ad11🟢
    anteltv.com.uy5347🟢
    antennaeurope.gr11🟢
    antennapacific.gr11🟢
    antennasatellite.gr11🟢
    arianaafgtv.com11🟢
    arianatelevision.com11🟢
    arirang.com33🟢
    artonline.tv55🟢
    awilime.com1110🟢
    bein.com160160🟢
    beinsports.com10481🟢
    berrymedia.co.kr55🟢
    cableplus.com.uy17147🟢
    canalplus.com11720212🟢
    cgates.lt10261🟢
    chada.ma11🟢
    chaines-tv.orange.fr295146🟢
    clickthecity.com3230🟢
    content.astro.com.my157112🟢
    cosmotetv.gr1080🟢
    ctc.ru11🟢
    cubmu.com174122🟢
    cyta.com.cy1160🟢
    dens.tv6764🟢
    derana.lk11🟢
    digea.gr920🟢
    digiturk.com.tr108107🟢
    directv.com1043696🔴https://github.com/iptv-org/epg/issues/2284
    directv.com.ar412229🔴https://github.com/iptv-org/epg/issues/2339
    directv.com.uy143142🟢
    dishtv.in44889🟢
    dna.fi1220🟢
    dsmart.com.tr10490🟢
    dstv.com6983181🟢
    dtv8.net11🟢
    elcinema.com262226🟢
    ena.skylifetv.co.kr66🟢
    energeek.cl62🟢
    entertainment.ie10995🟢
    epg.112114.xyz9301🟢
    epg.iptvx.one2862747🟢
    epg.telemach.ba2590🟢
    epg.telemach.me2160🟢
    epgmaster.com11🟢
    epgshare01.online2097117🟢
    firstmedia.com116101🟢
    foxsports.com.au77🟢
    foxtel.com.au9961🟢
    freetv.tv77🟢
    freeview.co.uk171100🟢
    frikanalen.no11🟢
    galamtv.kz2722🟢
    gatotv.com475362🟢
    getafteritmedia.com55🟢
    gigatv.3bbtv.co.th7938🟢
    guiadetv.com1240🟢
    guida.tv8888🟢
    guidatv.sky.it168153🟢
    guidetnt.com6969🟢
    horizon.tv184172🟢
    hoy.tv31🟢
    i.mjh.nz64581489🟢
    i24news.tv43🟢
    iltalehti.fi14244🟢
    indihometv.com130124🟢
    ionplustv.com11🟢
    ipko.tv194152🟢
    jiotv.com10940🟢
    kan.org.il33🔴https://github.com/iptv-org/epg/issues/2273
    knr.gl11🟢
    kvf.fo11🟢
    m.tv.sms.cz1027450🟢
    m.tving.com3026🟢
    magticom.ge240110🟢
    mako.co.il11🟢
    makrodigitaltelevision.com11🟢
    maxtvgo.mk11048🟢
    mediagenie.co.kr54🟢
    mediaklikk.hu88🟢
    mediasetinfinity.mediaset.it1313🟢
    melita.com127111🟢
    meo.pt216192🟢
    meuguia.tv10297🟢
    mewatch.sg2524🟢
    mi.tv2084620🟢
    mncvision.id276223🟢
    moji.id11🟢
    mojmaxtv.hrvatskitelekom.hr2430🟢
    mon-programme-tv.be11195🟢
    movistarplus.es1780🟢
    mtel.ba5010🟢
    mts.rs4570🟢
    mujtvprogram.cz216202🟢
    musor.tv181145🟢
    mysky.com.ph11543🟢
    mytelly.co.uk488401🟢
    mytvsuper.com10899🟢
    neo.io337241🟢
    nhkworldpremium.com22🟢
    nhl.com11🟢
    nostv.pt168155🟢
    novacyprus.com2924🟢
    novasports.gr1616🟢
    nowplayer.now.com288229🟢
    nuevosiglo.com.uy17347🟢
    nzxmltv.com532118🟢
    ontvtonight.com5177532🟢
    opto.sic.pt44🟢
    orangetv.orange.es168165🟢
    osn.com11898🟢
    pbsguam.org11🟢
    pickx.be404391🟢
    player.ee.co.uk241206🟢
    playtv.unifi.com.my6661🟢
    plex.tv170119🟢
    pluto.tv33020🟢
    programacion-tv.elpais.com195104🟢
    programacion.tcc.com.uy14956🟢
    programetv.ro331224🟢
    programme-tv.net295197🟢
    programme-tv.vini.pf582🟢
    programme.tvb.com86🟢
    programtv.onet.pl590362🟢
    raiplay.it1713🟢
    reportv.com.ar16397🟢
    rikstv.no800🟢
    rotana.net3228🟢
    rtb.gov.bn33🔴https://github.com/iptv-org/epg/issues/2257
    rthk.hk88🟢
    rtmklik.rtm.gov.my86🟢
    rtp.pt1010🟢
    ruv.is22🟢
    s.mxtv.jp22🟢
    sat.tv30308249🟢
    shahid.mbc.net231165🟢
    siba.com.co9896🟢
    singtel.com155113🟢
    sjonvarp.is1313🟢
    sky.co.nz11193🟢
    sky.com559458🟡https://github.com/iptv-org/epg/issues/2763
    sky.de7575🟢
    skylife.co.kr2510🟢
    skyperfectv.co.jp137130🟢
    snrt.ma117🟢
    sporttv.pt98🟢
    starhubtvplus.com232208🟢
    startimestv.com7758🟢
    stod2.is128🟢
    streamingtvguides.com30661🟢
    superguidatv.it204163🟢
    taiwanplus.com11🟢
    tapdmv.com397🟢
    tataplay.com785401🟢
    telebilbao.es11🟢
    teleboy.ch3250🟢
    telenet.tv26091🟢
    teliatv.ee342233🟢
    telkussa.fi6632🟢
    telsu.fi1715🟢
    thesportplus.com30🟢
    tivie.id4544🟢
    tivu.tv6966🟢
    toonamiaftermath.com11🟢
    turksatkablo.com.tr177118🟢
    tv-programme.telecablesat.fr268250🟢
    tv-spored.siol.net3120🟢
    tv.blue.ch1030565🟢
    tv.cctv.com9488🟢
    tv.dir.bg11193🔴https://github.com/iptv-org/epg/issues/2779
    tv.lv13749🟢
    tv.magenta.at307228🟢
    tv.mail.ru664643🟢
    tv.movistar.com.pe28240🟢
    tv.nu199180🟢
    tv.post.lu332242🟢
    tv.sfr.fr489456🟢
    tv.trueid.net26674🟢
    tv.yandex.ru9767🔴https://github.com/iptv-org/epg/issues/2803
    tv24.co.uk107239🟢
    tv24.se326157🟢
    tv2go.t-2.net335254🟢
    tvarenasport.com1412🟢
    tvarenasport.hr1010🟢
    tvcesoir.fr135133🟢
    tvcubana.icrt.cu1010🟢
    tvgids.nl11590🟢
    tvguide.com153149🟡https://github.com/iptv-org/epg/issues/2644
    tvguide.myjcom.jp145140🟢
    tvhebdo.com317215🟢
    tvheute.at5353🟢
    tvi.iol.pt66🟢
    tvim.tv2519🟢
    tvinsider.com3740🟢
    tvireland.ie334304🟢
    tvkaista.org1490🟢
    tvmi.mt33🟢
    tvmusor.hu9967🟢
    tvmustra.hu1880🟢
    tvpassport.com192872509🟢
    tvplus.com.tr143134🟢https://github.com/iptv-org/epg/issues/2816
    tvprofil.com5836455🟢
    tvtv.us22992255🟢
    v3.myafn.dodmedia.osd.mil88🟢
    vidio.com5752🟢
    virginmediatelevision.ie55🟢
    virgintvgo.virginmedia.com238195🟢
    visionplus.id250226🟢
    vivoplay.com.br3890🟢
    vtm.be76🟢
    walesi.com.fj98🟢
    watch.sportsnet.ca88🟢
    watchyour.tv4024🟢
    wavve.com7776🟢
    web.magentatv.de348247🟢
    webtv.delta.nl247218🟢
    winplay.co22🟢
    worldfishingnetwork.com11🟢
    www3.nhk.or.jp11🟢
    xem.kplus.vn770🟢
    xumo.tv35033🟢
    yes.co.il1740🟢
    zap.co.ao11464🟢
    zap2it.com5950🟢
    ziggogo.tv152130🟢
    znbc.co.zm44🟢
    zuragt.mn3625🟢
    diff --git a/sites/tvplus.com.tr/tvplus.com.tr.channels.xml b/sites/tvplus.com.tr/tvplus.com.tr.channels.xml index bc078b680..e49631694 100644 --- a/sites/tvplus.com.tr/tvplus.com.tr.channels.xml +++ b/sites/tvplus.com.tr/tvplus.com.tr.channels.xml @@ -1,146 +1,146 @@ - - - BluTV Play 1 - BluTV Play 2 - EKOL TV - GZT TV - KIBRIS ADA TV - tabii spor - tabii TV - TRT EBA - TV 2020 - 24 - 360 - A2 - A HABER - AKİT TV - AL JAZEERA ARABIC - AL JAZEERA ENGLISH - A NEWS - A PARA - A SPOR - ATV - BABYTV - BBC News - BENGÜTÜRK - BEYAZ TV - BİZİM EV TV - BLOOMBERG HT - Bloomberg - BRT 1 - BRT 2 - CARTOONITO - CARTOON NETWORK - ÇİFTÇİ TV - CNBC-E - CNN International - CNN TÜRK - DA VINCI - DISCOVERY CHANNEL - DISNEY JUNIOR - DİYANET TV - DMAX - DREAM TÜRK - DUCK TV - DEUTSCHE WELLE ENGLISH - EKOTÜRK - ENGLISH CLUB TV - EPIC DRAMA - EURONEWS - EUROSPORT 1 - EUROSPORT 2 - FB TV - FLASH TV - FM TV - FRANCE 24 ARABIC - FRANCE 24 ENGLISH - FX - HABER GLOBAL - HABERTÜRK - HALK TV - HT SPOR - KADIRGA TV - KANAL 7 - KANAL 23 - KANAL 26 - KANAL 33 - KANAL D - KANAL V - KIBRIS GENC TV - KANAL T - KIBRIS TV - KONTV - KRT TV - LOVE NATURE - MELTEM TV - MİNİKA ÇOCUK - MİNİKA GO - MOONBUG KIDS TV - NATIONAL GEOGRAPHIC - NATIONAL GEOGRAPHIC WILD - NBA TV - Nick JR - NICKTOONS - NOW - NTV - NR1 DAMAR - NUMBER1 TURK - NUMBER1 TV - ON6 - POWER TURK - POWER TV - SEMERKAND - SHOW TV - SİNEMA TV 2 - SİNEMA TV 1001 - SİNEMA 1002 - SİNEMA AİLE 2 - SİNEMA AİLE - SİNEMA AKSİYON 2 - SİNEMA TV AKSİYON - SİNEMA KOMEDİ - SİNEMA TV - SİNEMA YERLİ 2 - SİNEMA YERLİ - SKY NEWS ARABIA - SÖZCÜ TV - SPORTS TV - S SPORT 2 - S SPORT - STAR TV - TARIH TV - TARIM TV - TELE1 - TEVE2 - TGRT HABER - TLC - TMB TV - TRT1 - TRT 2 - TRT 3 - TRT ARABI - TRT AVAZ - TRT BELGESEL - TRT ÇOCUK - TRT DIYANET COCUK - TRT HABER - TRT KURDİ - TRT MÜZİK - TRT SPOR - TRT SPOR YILDIZ - TRT TÜRK - TRT World - TURKHABER - TV 4 - TV5 - TV5 MONDE - TV8 - TV8,5 - TV100 - TVNET - ÜLKE TV - ULUSAL KANAL - VAV TV - VIASAT EXPLORE - VIASAT HISTORY - + + + BluTV Play 1 + BluTV Play 2 + EKOL TV + GZT TV + KIBRIS ADA TV + tabii spor + tabii TV + TRT EBA + TV 2020 + 24 + 360 + A2 + A HABER + AKİT TV + AL JAZEERA ARABIC + AL JAZEERA ENGLISH + A NEWS + A PARA + A SPOR + ATV + BABYTV + BBC News + BENGÜTÜRK + BEYAZ TV + BİZİM EV TV + BLOOMBERG HT + Bloomberg + BRT 1 + BRT 2 + CARTOONITO + CARTOON NETWORK + ÇİFTÇİ TV + CNBC-E + CNN International + CNN TÜRK + DA VINCI + DISCOVERY CHANNEL + DISNEY JUNIOR + DİYANET TV + DMAX + DREAM TÜRK + DUCK TV + DEUTSCHE WELLE ENGLISH + EKOTÜRK + ENGLISH CLUB TV + EPIC DRAMA + EURONEWS + EUROSPORT 1 + EUROSPORT 2 + FB TV + FLASH TV + FM TV + FRANCE 24 ARABIC + FRANCE 24 ENGLISH + FX + HABER GLOBAL + HABERTÜRK + HALK TV + HT SPOR + KADIRGA TV + KANAL 7 + KANAL 23 + KANAL 26 + KANAL 33 + KANAL D + KANAL V + KIBRIS GENC TV + KANAL T + KIBRIS TV + KONTV + KRT TV + LOVE NATURE + MELTEM TV + MİNİKA ÇOCUK + MİNİKA GO + MOONBUG KIDS TV + NATIONAL GEOGRAPHIC + NATIONAL GEOGRAPHIC WILD + NBA TV + Nick JR + NICKTOONS + NOW + NTV + NR1 DAMAR + NUMBER1 TURK + NUMBER1 TV + ON6 + POWER TURK + POWER TV + SEMERKAND + SHOW TV + SİNEMA TV 2 + SİNEMA TV 1001 + SİNEMA 1002 + SİNEMA AİLE 2 + SİNEMA AİLE + SİNEMA AKSİYON 2 + SİNEMA TV AKSİYON + SİNEMA KOMEDİ + SİNEMA TV + SİNEMA YERLİ 2 + SİNEMA YERLİ + SKY NEWS ARABIA + SÖZCÜ TV + SPORTS TV + S SPORT 2 + S SPORT + STAR TV + TARIH TV + TARIM TV + TELE1 + TEVE2 + TGRT HABER + TLC + TMB TV + TRT1 + TRT 2 + TRT 3 + TRT ARABI + TRT AVAZ + TRT BELGESEL + TRT ÇOCUK + TRT DIYANET COCUK + TRT HABER + TRT KURDİ + TRT MÜZİK + TRT SPOR + TRT SPOR YILDIZ + TRT TÜRK + TRT World + TURKHABER + TV 4 + TV5 + TV5 MONDE + TV8 + TV8,5 + TV100 + TVNET + ÜLKE TV + ULUSAL KANAL + VAV TV + VIASAT EXPLORE + VIASAT HISTORY + diff --git a/sites/tvplus.com.tr/tvplus.com.tr.config.js b/sites/tvplus.com.tr/tvplus.com.tr.config.js index 60ed9aa20..a75934dbb 100644 --- a/sites/tvplus.com.tr/tvplus.com.tr.config.js +++ b/sites/tvplus.com.tr/tvplus.com.tr.config.js @@ -1,99 +1,99 @@ -const cheerio = require('cheerio') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -const debug = require('debug')('site:tvplus.com.tr') - -dayjs.extend(utc) -dayjs.extend(customParseFormat) - -const channelsUrl = 'https://tvplus.com.tr/canli-tv/yayin-akisi' - -module.exports = { - site: 'tvplus.com.tr', - days: 2, - request: { - cache: { - ttl: 24 * 60 * 60 * 1000 // 1 day - } - }, - async url({ channel }) { - if (module.exports.buildId === undefined) { - module.exports.buildId = await module.exports.fetchBuildId() - debug('Got build id', module.exports.buildId) - } - const channelId = channel.site_id.replace('/', '--') - return `https://tvplus.com.tr/_next/data/${module.exports.buildId}/${channel.lang}/canli-tv/yayin-akisi/${channelId}.json?title=${channelId}` - }, - parser({ content, date }) { - const programs = [] - if (content) { - const data = JSON.parse(content) - if (Array.isArray(data?.pageProps?.allPlaybillList)) { - data.pageProps.allPlaybillList - .filter(i => i.length && i[0].starttime.startsWith(date.format('YYYY-MM-DD'))) - .forEach(i => { - for (const schedule of i) { - const [, season, episode] = schedule.seasonInfo?.match( - /(\d+)\. Sezon - (\d+)\. Bölüm/ - ) || [null, null, null] - programs.push({ - title: schedule.name, - description: schedule.introduce, - category: schedule.genres, - image: schedule.picture, - season: season ? parseInt(season) : null, - episode: episode ? parseInt(episode) : null, - start: dayjs.utc(schedule.starttime), - stop: dayjs.utc(schedule.endtime) - }) - } - }) - } - } - - return programs - }, - async channels() { - const channels = [] - const data = await axios - .get(channelsUrl) - .then(r => r.data) - .catch(console.error) - - const $ = cheerio.load(data) - $('.channel-list-item a') - .toArray() - .forEach(el => { - const a = $(el) - channels.push({ - lang: 'tr', - name: a - .attr('title') - .replace(/Yayın Akışı/, '') - .trim(), - site_id: a - .attr('href') - .replace(/\/canli-tv\/yayin-akisi\//, '') - .replace('--', '/') // change -- to / as it used in xml comment - }) - }) - - return channels - }, - async fetchBuildId() { - const data = await axios - .get(channelsUrl) - .then(r => r.data) - .catch(console.error) - - if (data) { - const $ = cheerio.load(data) - const nextData = JSON.parse($('#__NEXT_DATA__').text()) - return nextData?.buildId || null - } else { - return null - } - } -} +const cheerio = require('cheerio') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const debug = require('debug')('site:tvplus.com.tr') + +dayjs.extend(utc) +dayjs.extend(customParseFormat) + +const channelsUrl = 'https://tvplus.com.tr/canli-tv/yayin-akisi' + +module.exports = { + site: 'tvplus.com.tr', + days: 2, + request: { + cache: { + ttl: 24 * 60 * 60 * 1000 // 1 day + } + }, + async url({ channel }) { + if (module.exports.buildId === undefined) { + module.exports.buildId = await module.exports.fetchBuildId() + debug('Got build id', module.exports.buildId) + } + const channelId = channel.site_id.replace('/', '--') + return `https://tvplus.com.tr/_next/data/${module.exports.buildId}/${channel.lang}/canli-tv/yayin-akisi/${channelId}.json?title=${channelId}` + }, + parser({ content, date }) { + const programs = [] + if (content) { + const data = JSON.parse(content) + if (Array.isArray(data?.pageProps?.allPlaybillList)) { + data.pageProps.allPlaybillList + .filter(i => i.length && i[0].starttime.startsWith(date.format('YYYY-MM-DD'))) + .forEach(i => { + for (const schedule of i) { + const [, season, episode] = schedule.seasonInfo?.match( + /(\d+)\. Sezon - (\d+)\. Bölüm/ + ) || [null, null, null] + programs.push({ + title: schedule.name, + description: schedule.introduce, + category: schedule.genres, + image: schedule.picture, + season: season ? parseInt(season) : null, + episode: episode ? parseInt(episode) : null, + start: dayjs.utc(schedule.starttime), + stop: dayjs.utc(schedule.endtime) + }) + } + }) + } + } + + return programs + }, + async channels() { + const channels = [] + const data = await axios + .get(channelsUrl) + .then(r => r.data) + .catch(console.error) + + const $ = cheerio.load(data) + $('.channel-list-item a') + .toArray() + .forEach(el => { + const a = $(el) + channels.push({ + lang: 'tr', + name: a + .attr('title') + .replace(/Yayın Akışı/, '') + .trim(), + site_id: a + .attr('href') + .replace(/\/canli-tv\/yayin-akisi\//, '') + .replace('--', '/') // change -- to / as it used in xml comment + }) + }) + + return channels + }, + async fetchBuildId() { + const data = await axios + .get(channelsUrl) + .then(r => r.data) + .catch(console.error) + + if (data) { + const $ = cheerio.load(data) + const nextData = JSON.parse($('#__NEXT_DATA__').text()) + return nextData?.buildId || null + } else { + return null + } + } +} From 280f564ddf560fcc8e5d41c306d5c5dc71e1fe96 Mon Sep 17 00:00:00 2001 From: theofficialomega <30985701+BellezaEmporium@users.noreply.github.com> Date: Wed, 30 Jul 2025 23:08:29 +0200 Subject: [PATCH 30/32] fix tests --- sites/allente.dk/allente.dk.test.js | 5 +-- sites/foxsports.com.au/__data__/content.json | 35 ++++++++++++++++++- .../foxsports.com.au/foxsports.com.au.test.js | 2 +- sites/frikanalen.no/__data__/content.json | 2 +- 4 files changed, 39 insertions(+), 5 deletions(-) diff --git a/sites/allente.dk/allente.dk.test.js b/sites/allente.dk/allente.dk.test.js index d79521f17..e6f0caa96 100644 --- a/sites/allente.dk/allente.dk.test.js +++ b/sites/allente.dk/allente.dk.test.js @@ -2,6 +2,8 @@ const { parser, url } = require('./allente.dk.config.js') const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') +const { readFileSync } = require('fs') +const { resolve } = require('path') dayjs.extend(customParseFormat) dayjs.extend(utc) @@ -16,8 +18,7 @@ it('can generate valid url', () => { }) it('can parse response', () => { - const content = - '' + const content = readFileSync(resolve(__dirname, '__data__/content.json')) const result = parser({ content, channel }).map(p => { p.start = p.start.toJSON() p.stop = p.stop.toJSON() diff --git a/sites/foxsports.com.au/__data__/content.json b/sites/foxsports.com.au/__data__/content.json index a2affed50..6ec175aa9 100644 --- a/sites/foxsports.com.au/__data__/content.json +++ b/sites/foxsports.com.au/__data__/content.json @@ -1 +1,34 @@ -{"channel-programme":[{"id":"31cc8b4c-3711-49f0-bf22-2ec3993b0a07","programmeTitle":"NRL","title":"Eels v Titans","startTime":"2022-12-14T00:00:00+11:00","endTime":"2022-12-14T01:00:00+11:00","duration":60,"live":false,"genreId":"5c389cf4-8db7-4b52-9773-52355bd28559","channelId":2,"channelName":"FOX League","channelAbbreviation":"LEAGUE","programmeUID":235220,"round":"R1","statsMatchId":null,"closedCaptioned":true,"statsFixtureId":10207,"genreTitle":"Rugby League","parentGenreId":"a953f929-2d12-41a4-b0e9-97f401afff11","parentGenreTitle":"Sport","pmgId":"PMG01306944","statsSport":"league","type":"GAME","hiDef":true,"widescreen":true,"classification":"","synopsis":"The Eels and Titans have plenty of motivation this season after heartbreaking Finals losses in 2021. Parramatta has won their past five against Gold Coast.","preGameStartTime":null,"closeCaptioned":true}]} \ No newline at end of file +{ + "channel-programme":[ + { + "id":"31cc8b4c-3711-49f0-bf22-2ec3993b0a07", + "programmeTitle":"NRL", + "title":"Eels v Titans", + "startTime":"2022-12-14T00:00:00+11:00", + "endTime":"2022-12-14T01:00:00+11:00", + "duration":60, + "live":false, + "genreId":"5c389cf4-8db7-4b52-9773-52355bd28559", + "channelId":2, + "channelName":"FOX League", + "channelAbbreviation":"LEAGUE", + "programmeUID":235220, + "round":"R1", + "statsMatchId":null, + "closedCaptioned":true, + "statsFixtureId":10207, + "genreTitle":"Rugby League", + "parentGenreId":"a953f929-2d12-41a4-b0e9-97f401afff11", + "parentGenreTitle":"Sport", + "pmgId":"PMG01306944", + "statsSport":"league", + "type":"GAME", + "hiDef":true, + "widescreen":true, + "classification":"", + "synopsis":"The Eels and Titans have plenty of motivation this season after heartbreaking Finals losses in 2021. Parramatta has won their past five against Gold Coast.", + "preGameStartTime":null, + "closeCaptioned":true + } + ] +} \ No newline at end of file diff --git a/sites/foxsports.com.au/foxsports.com.au.test.js b/sites/foxsports.com.au/foxsports.com.au.test.js index b6ed8a020..1978d8f06 100644 --- a/sites/foxsports.com.au/foxsports.com.au.test.js +++ b/sites/foxsports.com.au/foxsports.com.au.test.js @@ -38,6 +38,6 @@ it('can parse response', () => { }) it('can handle empty guide', () => { - const result = parser({content: ''}, channel) + const result = parser({content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json'))}, channel) expect(result).toMatchObject([]) }) diff --git a/sites/frikanalen.no/__data__/content.json b/sites/frikanalen.no/__data__/content.json index 0f01cdcd0..221056a1b 100644 --- a/sites/frikanalen.no/__data__/content.json +++ b/sites/frikanalen.no/__data__/content.json @@ -1 +1 @@ -{"count":83,"next":null,"previous":null,"results":[{"id":135605,"video":{"id":626094,"name":"FSCONS 2017 - Keynote: TBA - Linda Sandvik","header":"Linda Sandvik's keynote at FSCONS 2017\\r\\n\\r\\nRecorded by NUUG for FSCONS.","description":null,"creator":"davidwnoble@gmail.com","organization":{"id":82,"name":"NUUG","homepage":"https://www.nuug.no/","description":"Forening NUUG er for alle som er interessert i fri programvare, åpne standarder og Unix-lignende operativsystemer.","postalAddress":"","streetAddress":"","editorId":2148,"editorName":"David Noble","editorEmail":"davidwnoble@gmail.com","editorMsisdn":"","fkmember":true},"duration":"00:57:55.640000","categories":["Samfunn"]},"schedulereason":5,"starttime":"2022-01-19T00:47:00+01:00","endtime":"2022-01-19T01:44:55.640000+01:00","duration":"00:57:55.640000"}]} \ No newline at end of file +{"count":83,"next":null,"previous":null,"results":[{"id":135605,"video":{"id":626094,"name":"FSCONS 2017 - Keynote: TBA - Linda Sandvik","header":"Linda Sandvik's keynote at FSCONS 2017\r\n\r\nRecorded by NUUG for FSCONS.","description":null,"creator":"davidwnoble@gmail.com","organization":{"id":82,"name":"NUUG","homepage":"https://www.nuug.no/","description":"Forening NUUG er for alle som er interessert i fri programvare, åpne standarder og Unix-lignende operativsystemer.","postalAddress":"","streetAddress":"","editorId":2148,"editorName":"David Noble","editorEmail":"davidwnoble@gmail.com","editorMsisdn":"","fkmember":true},"duration":"00:57:55.640000","categories":["Samfunn"]},"schedulereason":5,"starttime":"2022-01-19T00:47:00+01:00","endtime":"2022-01-19T01:44:55.640000+01:00","duration":"00:57:55.640000"}]} \ No newline at end of file From 17e3b4ddda937d23989fc9e267b9fb9ef9327edc Mon Sep 17 00:00:00 2001 From: theofficialomega <30985701+BellezaEmporium@users.noreply.github.com> Date: Thu, 31 Jul 2025 11:22:35 +0200 Subject: [PATCH 31/32] use splitted lodash modules for better efficiency --- README.md | 474 +-- package-lock.json | 131 + package.json | 8 + scripts/commands/api/generate.ts | 82 +- scripts/commands/api/load.ts | 50 +- scripts/commands/channels/lint.mts | 218 +- scripts/commands/sites/update.ts | 152 +- scripts/core/dataLoader.ts | 206 +- scripts/core/index.ts | 28 +- scripts/core/issueLoader.ts | 74 +- scripts/functions/functions.ts | 77 - scripts/functions/index.ts | 1 - scripts/models/guideChannel.ts | 118 +- scripts/models/index.ts | 18 +- .../chaines-tv.orange.fr.channels.xml | 596 ++-- .../chaines-tv.orange.fr.config.js | 158 +- sites/ctc.ru/ctc.ru.config.js | 190 +- sites/ctc.ru/ctc.ru.test.js | 182 +- sites/derana.lk/derana.lk.config.js | 2 +- sites/dstv.com/dstv.com.config.js | 2 +- sites/epgshare01.online/readme.md | 270 +- sites/guida.tv/guida.tv.config.js | 2 +- sites/i.mjh.nz/i.mjh.nz_samsung.channels.xml | 3056 ++++++++--------- .../mojmaxtv.hrvatskitelekom.hr.config.js | 2 +- sites/mtel.ba/mtel.ba.config.js | 2 +- .../ontvtonight.com/ontvtonight.com.config.js | 2 +- .../programme-tv.net.config.js | 252 +- sites/reportv.com.ar/reportv.com.ar.config.js | 2 +- sites/sky.com/sky.com.config.js | 2 +- .../streamingtvguides.com.config.js | 3 +- sites/tivie.id/tivie.id.config.js | 282 +- sites/tv.mail.ru/tv.mail.ru.config.js | 2 +- sites/tv.sfr.fr/tv.sfr.fr.channels.xml | 982 +++--- sites/tv.sfr.fr/tv.sfr.fr.config.js | 130 +- sites/tv.sfr.fr/tv.sfr.fr.test.js | 142 +- sites/tvcesoir.fr/tvcesoir.fr.config.js | 2 +- sites/tvhebdo.com/tvhebdo.com.config.js | 2 +- sites/tvireland.ie/tvireland.ie.config.js | 2 +- sites/tvmusor.hu/tvmusor.hu.config.js | 2 +- sites/vidio.com/vidio.com.channels.xml | 120 +- sites/vidio.com/vidio.com.config.js | 176 +- sites/vidio.com/vidio.com.test.js | 134 +- .../__data__/expected/epg_grab/base.guide.xml | 16 +- .../epg_grab/custom_channels.guide.xml | 28 +- .../epg_grab/guides/en/example.com.xml | 10 +- .../__data__/expected/epg_grab/lang.guide.xml | 14 +- .../expected/epg_grab/proxy.guide.xml | 16 +- .../expected/epg_grab/template.guide.xml | 28 +- tests/__data__/input/__data__/channels.json | 120 +- .../example.com/example.com.config.js | 56 +- .../sites/example.com/example.com.config.js | 56 +- .../sites/example2.com/example2.com.config.js | 46 +- tests/commands/api/generate.test.ts | 54 +- tests/commands/channels/edit.test.ts | 72 +- tests/commands/epg/grab.test.ts | 324 +- tests/commands/sites/init.test.ts | 82 +- tests/commands/sites/update.test.ts | 56 +- 57 files changed, 4688 insertions(+), 4626 deletions(-) delete mode 100644 scripts/functions/functions.ts delete mode 100644 scripts/functions/index.ts diff --git a/README.md b/README.md index c494b7049..ec67ae0c5 100644 --- a/README.md +++ b/README.md @@ -1,237 +1,237 @@ -# EPG [![update](https://github.com/iptv-org/epg/actions/workflows/update.yml/badge.svg)](https://github.com/iptv-org/epg/actions/workflows/update.yml) - -Tools for downloading the EPG (Electronic Program Guide) for thousands of TV channels from hundreds of sources. - -## Table of contents - -- ✨ [Installation](#installation) -- 🚀 [Usage](#usage) -- 💫 [Update](#update) -- 🐋 [Docker](#docker) -- 📺 [Playlists](#playlists) -- 🗄 [Database](#database) -- 👨‍💻 [API](#api) -- 📚 [Resources](#resources) -- 💬 [Discussions](#discussions) -- 🛠 [Contribution](#contribution) -- 📄 [License](#license) - -## Installation - -First, you need to install [Node.js](https://nodejs.org/en) on your computer. You will also need to install [Git](https://git-scm.com/downloads) to follow these instructions. - -After that open the [Console](https://en.wikipedia.org/wiki/Windows_Console) (or [Terminal]() if you have macOS) and type the following command: - -```sh -git clone --depth 1 -b master https://github.com/iptv-org/epg.git -``` - -Then navigate to the downloaded `epg` folder: - -```sh -cd epg -``` - -And install all the dependencies: - -```sh -npm install -``` - -## Usage - -To start the download of the guide, select one of the supported sites from [SITES.md](SITES.md) file and paste its name into the command below: - -```sh -npm run grab --- --site=example.com -``` - -Then run it and wait for the guide to finish downloading. When finished, a new `guide.xml` file will appear in the current directory. - -You can also customize the behavior of the script using this options: - -```sh -Usage: npm run grab --- [options] - -Options: - -s, --site Name of the site to parse - -c, --channels Path to *.channels.xml file (required if the "--site" attribute is - not specified) - -o, --output Path to output file (default: "guide.xml") - -l, --lang Allows you to restrict downloading to channels in specified languages only (example: "en,id") - -t, --timeout Timeout for each request in milliseconds (default: 0) - -d, --delay Delay between request in milliseconds (default: 0) - -x, --proxy Use the specified proxy (example: "socks5://username:password@127.0.0.1:1234") - --days Number of days for which the program will be loaded (defaults to the value from the site config) - --maxConnections Number of concurrent requests (default: 1) - --gzip Specifies whether or not to create a compressed version of the guide (default: false) - --curl Display each request as CURL (default: false) -``` - -### Parallel downloading - -By default, the guide for each channel is downloaded one by one, but you can change this behavior by increasing the number of simultaneous requests using the `--maxConnections` attribute: - -```sh -npm run grab --- --site=example.com --maxConnections=10 -``` - -But be aware that under heavy load, some sites may start return an error or completely block your access. - -### Use custom channel list - -Create an XML file and copy the descriptions of all the channels you need from the [/sites](sites) into it: - -```xml - - - Arirang TV - ... - -``` - -And then specify the path to that file via the `--channels` attribute: - -```sh -npm run grab --- --channels=path/to/custom.channels.xml -``` - -### Run on schedule - -If you want to download guides on a schedule, you can use [cron](https://en.wikipedia.org/wiki/Cron) or any other task scheduler. Currently, we use a tool called `chronos` for this purpose. - -To start it, you only need to specify the necessary `grab` command and [cron expression](https://crontab.guru/): - -```sh -npx chronos --execute="npm run grab --- --site=example.com" --pattern="0 0,12 * * *" --log -``` - -For more info go to [chronos](https://github.com/freearhey/chronos) documentation. - -### Access the guide by URL - -You can make the guide available via URL by running your own server. The easiest way to do this is to run this command: - -```sh -npx serve -``` - -After that, the guide will be available at the link: - -``` -http://localhost:3000/guide.xml -``` - -In addition it will be available to other devices on the same local network at the address: - -``` -http://:3000/guide.xml -``` - -For more info go to [serve](https://github.com/vercel/serve) documentation. - -## Update - -If you have downloaded the repository code according to the instructions above, then to update it will be enough to run the command: - -```sh -git pull -``` - -And then update all the dependencies: - -```sh -npm install -``` - -## Docker - -### Build an image - -```sh -docker build -t iptv-org/epg --no-cache . -``` - -### Create and run container - -```sh -docker run -p 3000:3000 -v /path/to/channels.xml:/epg/channels.xml iptv-org/epg -``` - -By default, the guide will be downloaded every day at 00:00 UTC and saved to the `/epg/public/guide.xml` file inside the container. - -From the outside, it will be available at this link: - -``` -http://localhost:3000/guide.xml -``` - -or - -``` -http://:3000/guide.xml -``` - -### Environment Variables - -To fine-tune the execution, you can pass environment variables to the container as follows: - -```sh -docker run \ --p 5000:3000 \ --v /path/to/channels.xml:/epg/channels.xml \ --e CRON_SCHEDULE="0 0,12 * * *" \ --e MAX_CONNECTIONS=10 \ --e GZIP=true \ --e CURL=true \ --e PROXY="socks5://127.0.0.1:1234" \ --e DAYS=14 \ --e TIMEOUT=5 \ --e DELAY=2 \ -iptv-org/epg -``` - -| Variable | Description | -| --------------- | ------------------------------------------------------------------------------------------------------------------ | -| CRON_SCHEDULE | A [cron expression](https://crontab.guru/) describing the schedule of the guide loadings (default: "0 0 \* \* \*") | -| MAX_CONNECTIONS | Limit on the number of concurrent requests (default: 1) | -| GZIP | Boolean value indicating whether to create a compressed version of the guide (default: false) | -| CURL | Display each request as CURL (default: false) | -| PROXY | Use the specified proxy | -| DAYS | Number of days for which the guide will be loaded (defaults to the value from the site config) | -| TIMEOUT | Timeout for each request in milliseconds (default: 0) | -| DELAY | Delay between request in milliseconds (default: 0) | - -## Database - -All channel data is taken from the [iptv-org/database](https://github.com/iptv-org/database) repository. If you find any errors please open a new [issue](https://github.com/iptv-org/database/issues) there. - -## API - -The API documentation can be found in the [iptv-org/api](https://github.com/iptv-org/api) repository. - -## Resources - -Links to other useful IPTV-related resources can be found in the [iptv-org/awesome-iptv](https://github.com/iptv-org/awesome-iptv) repository. - -## Discussions - -If you have a question or an idea, you can post it in the [Discussions](https://github.com/orgs/iptv-org/discussions) tab. - -## Contribution - -Please make sure to read the [Contributing Guide](https://github.com/iptv-org/epg/blob/master/CONTRIBUTING.md) before sending [issue](https://github.com/iptv-org/epg/issues) or a [pull request](https://github.com/iptv-org/epg/pulls). - -And thank you to everyone who has already contributed! - -### Backers - - - -### Contributors - - - -## License - -[![CC0](http://mirrors.creativecommons.org/presskit/buttons/88x31/svg/cc-zero.svg)](LICENSE) +# EPG [![update](https://github.com/iptv-org/epg/actions/workflows/update.yml/badge.svg)](https://github.com/iptv-org/epg/actions/workflows/update.yml) + +Tools for downloading the EPG (Electronic Program Guide) for thousands of TV channels from hundreds of sources. + +## Table of contents + +- ✨ [Installation](#installation) +- 🚀 [Usage](#usage) +- 💫 [Update](#update) +- 🐋 [Docker](#docker) +- 📺 [Playlists](#playlists) +- 🗄 [Database](#database) +- 👨‍💻 [API](#api) +- 📚 [Resources](#resources) +- 💬 [Discussions](#discussions) +- 🛠 [Contribution](#contribution) +- 📄 [License](#license) + +## Installation + +First, you need to install [Node.js](https://nodejs.org/en) on your computer. You will also need to install [Git](https://git-scm.com/downloads) to follow these instructions. + +After that open the [Console](https://en.wikipedia.org/wiki/Windows_Console) (or [Terminal]() if you have macOS) and type the following command: + +```sh +git clone --depth 1 -b master https://github.com/iptv-org/epg.git +``` + +Then navigate to the downloaded `epg` folder: + +```sh +cd epg +``` + +And install all the dependencies: + +```sh +npm install +``` + +## Usage + +To start the download of the guide, select one of the supported sites from [SITES.md](SITES.md) file and paste its name into the command below: + +```sh +npm run grab --- --site=example.com +``` + +Then run it and wait for the guide to finish downloading. When finished, a new `guide.xml` file will appear in the current directory. + +You can also customize the behavior of the script using this options: + +```sh +Usage: npm run grab --- [options] + +Options: + -s, --site Name of the site to parse + -c, --channels Path to *.channels.xml file (required if the "--site" attribute is + not specified) + -o, --output Path to output file (default: "guide.xml") + -l, --lang Allows you to restrict downloading to channels in specified languages only (example: "en,id") + -t, --timeout Timeout for each request in milliseconds (default: 0) + -d, --delay Delay between request in milliseconds (default: 0) + -x, --proxy Use the specified proxy (example: "socks5://username:password@127.0.0.1:1234") + --days Number of days for which the program will be loaded (defaults to the value from the site config) + --maxConnections Number of concurrent requests (default: 1) + --gzip Specifies whether or not to create a compressed version of the guide (default: false) + --curl Display each request as CURL (default: false) +``` + +### Parallel downloading + +By default, the guide for each channel is downloaded one by one, but you can change this behavior by increasing the number of simultaneous requests using the `--maxConnections` attribute: + +```sh +npm run grab --- --site=example.com --maxConnections=10 +``` + +But be aware that under heavy load, some sites may start return an error or completely block your access. + +### Use custom channel list + +Create an XML file and copy the descriptions of all the channels you need from the [/sites](sites) into it: + +```xml + + + Arirang TV + ... + +``` + +And then specify the path to that file via the `--channels` attribute: + +```sh +npm run grab --- --channels=path/to/custom.channels.xml +``` + +### Run on schedule + +If you want to download guides on a schedule, you can use [cron](https://en.wikipedia.org/wiki/Cron) or any other task scheduler. Currently, we use a tool called `chronos` for this purpose. + +To start it, you only need to specify the necessary `grab` command and [cron expression](https://crontab.guru/): + +```sh +npx chronos --execute="npm run grab --- --site=example.com" --pattern="0 0,12 * * *" --log +``` + +For more info go to [chronos](https://github.com/freearhey/chronos) documentation. + +### Access the guide by URL + +You can make the guide available via URL by running your own server. The easiest way to do this is to run this command: + +```sh +npx serve +``` + +After that, the guide will be available at the link: + +``` +http://localhost:3000/guide.xml +``` + +In addition it will be available to other devices on the same local network at the address: + +``` +http://:3000/guide.xml +``` + +For more info go to [serve](https://github.com/vercel/serve) documentation. + +## Update + +If you have downloaded the repository code according to the instructions above, then to update it will be enough to run the command: + +```sh +git pull +``` + +And then update all the dependencies: + +```sh +npm install +``` + +## Docker + +### Build an image + +```sh +docker build -t iptv-org/epg --no-cache . +``` + +### Create and run container + +```sh +docker run -p 3000:3000 -v /path/to/channels.xml:/epg/channels.xml iptv-org/epg +``` + +By default, the guide will be downloaded every day at 00:00 UTC and saved to the `/epg/public/guide.xml` file inside the container. + +From the outside, it will be available at this link: + +``` +http://localhost:3000/guide.xml +``` + +or + +``` +http://:3000/guide.xml +``` + +### Environment Variables + +To fine-tune the execution, you can pass environment variables to the container as follows: + +```sh +docker run \ +-p 5000:3000 \ +-v /path/to/channels.xml:/epg/channels.xml \ +-e CRON_SCHEDULE="0 0,12 * * *" \ +-e MAX_CONNECTIONS=10 \ +-e GZIP=true \ +-e CURL=true \ +-e PROXY="socks5://127.0.0.1:1234" \ +-e DAYS=14 \ +-e TIMEOUT=5 \ +-e DELAY=2 \ +iptv-org/epg +``` + +| Variable | Description | +| --------------- | ------------------------------------------------------------------------------------------------------------------ | +| CRON_SCHEDULE | A [cron expression](https://crontab.guru/) describing the schedule of the guide loadings (default: "0 0 \* \* \*") | +| MAX_CONNECTIONS | Limit on the number of concurrent requests (default: 1) | +| GZIP | Boolean value indicating whether to create a compressed version of the guide (default: false) | +| CURL | Display each request as CURL (default: false) | +| PROXY | Use the specified proxy | +| DAYS | Number of days for which the guide will be loaded (defaults to the value from the site config) | +| TIMEOUT | Timeout for each request in milliseconds (default: 0) | +| DELAY | Delay between request in milliseconds (default: 0) | + +## Database + +All channel data is taken from the [iptv-org/database](https://github.com/iptv-org/database) repository. If you find any errors please open a new [issue](https://github.com/iptv-org/database/issues) there. + +## API + +The API documentation can be found in the [iptv-org/api](https://github.com/iptv-org/api) repository. + +## Resources + +Links to other useful IPTV-related resources can be found in the [iptv-org/awesome-iptv](https://github.com/iptv-org/awesome-iptv) repository. + +## Discussions + +If you have a question or an idea, you can post it in the [Discussions](https://github.com/orgs/iptv-org/discussions) tab. + +## Contribution + +Please make sure to read the [Contributing Guide](https://github.com/iptv-org/epg/blob/master/CONTRIBUTING.md) before sending [issue](https://github.com/iptv-org/epg/issues) or a [pull request](https://github.com/iptv-org/epg/pulls). + +And thank you to everyone who has already contributed! + +### Backers + + + +### Contributors + + + +## License + +[![CC0](http://mirrors.creativecommons.org/presskit/buttons/88x31/svg/cc-zero.svg)](LICENSE) diff --git a/package-lock.json b/package-lock.json index 2eaa53650..94050c790 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,10 @@ "@types/inquirer": "^9.0.8", "@types/jest": "^30.0.0", "@types/langs": "^2.0.5", + "@types/lodash.orderby": "^4.6.9", + "@types/lodash.sortby": "^4.7.9", + "@types/lodash.startcase": "^4.4.9", + "@types/lodash.uniqby": "^4.7.9", "@types/node": "^24.1.0", "@types/node-cleanup": "^2.1.5", "@types/numeral": "^2.0.5", @@ -57,6 +61,10 @@ "jest-offline": "^1.0.1", "langs": "^2.0.0", "libxml2-wasm": "^0.5.0", + "lodash.orderby": "^4.6.0", + "lodash.sortby": "^4.7.0", + "lodash.startcase": "^4.4.0", + "lodash.uniqby": "^4.7.0", "luxon": "^3.7.1", "mockdate": "^3.0.5", "nedb-promises": "^6.2.3", @@ -3306,6 +3314,48 @@ "integrity": "sha512-DIUKT4mkbTBxSrX6lmnQR888ObeFVVo1uNEqBH5/ddQHpnG4CA24DibpK7aO8QAcJEZUTcIx0F96TWuzVT9Z4g==", "license": "MIT" }, + "node_modules/@types/lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==", + "license": "MIT" + }, + "node_modules/@types/lodash.orderby": { + "version": "4.6.9", + "resolved": "https://registry.npmjs.org/@types/lodash.orderby/-/lodash.orderby-4.6.9.tgz", + "integrity": "sha512-T9o2wkIJOmxXwVTPTmwJ59W6eTi2FseiLR369fxszG649Po/xe9vqFNhf/MtnvT5jrbDiyWKxPFPZbpSVK0SVQ==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/lodash.sortby": { + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/@types/lodash.sortby/-/lodash.sortby-4.7.9.tgz", + "integrity": "sha512-PDmjHnOlndLS59GofH0pnxIs+n9i4CWeXGErSB5JyNFHu2cmvW6mQOaUKjG8EDPkni14IgF8NsRW8bKvFzTm9A==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/lodash.startcase": { + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/@types/lodash.startcase/-/lodash.startcase-4.4.9.tgz", + "integrity": "sha512-C0M4DlN1pnn2vEEhLHkTHxiRZ+3GlTegpoAEHHGXnuJkSOXyJMHGiSc+SLRzBlFZWHsBkixe6FqvEAEU04g14g==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/lodash.uniqby": { + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/@types/lodash.uniqby/-/lodash.uniqby-4.7.9.tgz", + "integrity": "sha512-rjrXji/seS6BZJRgXrU2h6FqxRVufsbq/HE0Tx0SdgbtlWr2YmD/M64BlYEYYlaMcpZwy32IYVkMfUMYlPuv0w==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/node": { "version": "24.1.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz", @@ -8486,6 +8536,30 @@ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, + "node_modules/lodash.orderby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.orderby/-/lodash.orderby-4.6.0.tgz", + "integrity": "sha512-T0rZxKmghOOf5YPnn8EY5iLYeWCpZq8G41FfqoVHH5QDTAFaghJRmAdLiadEDq+ztgM2q5PjA+Z1fOwGrLgmtg==", + "license": "MIT" + }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", + "license": "MIT" + }, + "node_modules/lodash.startcase": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", + "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", + "license": "MIT" + }, + "node_modules/lodash.uniqby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", + "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==", + "license": "MIT" + }, "node_modules/logform": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", @@ -13391,6 +13465,43 @@ "resolved": "https://registry.npmjs.org/@types/langs/-/langs-2.0.5.tgz", "integrity": "sha512-DIUKT4mkbTBxSrX6lmnQR888ObeFVVo1uNEqBH5/ddQHpnG4CA24DibpK7aO8QAcJEZUTcIx0F96TWuzVT9Z4g==" }, + "@types/lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==" + }, + "@types/lodash.orderby": { + "version": "4.6.9", + "resolved": "https://registry.npmjs.org/@types/lodash.orderby/-/lodash.orderby-4.6.9.tgz", + "integrity": "sha512-T9o2wkIJOmxXwVTPTmwJ59W6eTi2FseiLR369fxszG649Po/xe9vqFNhf/MtnvT5jrbDiyWKxPFPZbpSVK0SVQ==", + "requires": { + "@types/lodash": "*" + } + }, + "@types/lodash.sortby": { + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/@types/lodash.sortby/-/lodash.sortby-4.7.9.tgz", + "integrity": "sha512-PDmjHnOlndLS59GofH0pnxIs+n9i4CWeXGErSB5JyNFHu2cmvW6mQOaUKjG8EDPkni14IgF8NsRW8bKvFzTm9A==", + "requires": { + "@types/lodash": "*" + } + }, + "@types/lodash.startcase": { + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/@types/lodash.startcase/-/lodash.startcase-4.4.9.tgz", + "integrity": "sha512-C0M4DlN1pnn2vEEhLHkTHxiRZ+3GlTegpoAEHHGXnuJkSOXyJMHGiSc+SLRzBlFZWHsBkixe6FqvEAEU04g14g==", + "requires": { + "@types/lodash": "*" + } + }, + "@types/lodash.uniqby": { + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/@types/lodash.uniqby/-/lodash.uniqby-4.7.9.tgz", + "integrity": "sha512-rjrXji/seS6BZJRgXrU2h6FqxRVufsbq/HE0Tx0SdgbtlWr2YmD/M64BlYEYYlaMcpZwy32IYVkMfUMYlPuv0w==", + "requires": { + "@types/lodash": "*" + } + }, "@types/node": { "version": "24.1.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz", @@ -16969,6 +17080,26 @@ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, + "lodash.orderby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.orderby/-/lodash.orderby-4.6.0.tgz", + "integrity": "sha512-T0rZxKmghOOf5YPnn8EY5iLYeWCpZq8G41FfqoVHH5QDTAFaghJRmAdLiadEDq+ztgM2q5PjA+Z1fOwGrLgmtg==" + }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==" + }, + "lodash.startcase": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", + "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==" + }, + "lodash.uniqby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", + "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==" + }, "logform": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", diff --git a/package.json b/package.json index d72355344..c8bed9f71 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,10 @@ "@types/inquirer": "^9.0.8", "@types/jest": "^30.0.0", "@types/langs": "^2.0.5", + "@types/lodash.orderby": "^4.6.9", + "@types/lodash.sortby": "^4.7.9", + "@types/lodash.startcase": "^4.4.9", + "@types/lodash.uniqby": "^4.7.9", "@types/node": "^24.1.0", "@types/node-cleanup": "^2.1.5", "@types/numeral": "^2.0.5", @@ -85,6 +89,10 @@ "jest-offline": "^1.0.1", "langs": "^2.0.0", "libxml2-wasm": "^0.5.0", + "lodash.orderby": "^4.6.0", + "lodash.sortby": "^4.7.0", + "lodash.startcase": "^4.4.0", + "lodash.uniqby": "^4.7.0", "luxon": "^3.7.1", "mockdate": "^3.0.5", "nedb-promises": "^6.2.3", diff --git a/scripts/commands/api/generate.ts b/scripts/commands/api/generate.ts index b0e078c4d..8fd2f068e 100644 --- a/scripts/commands/api/generate.ts +++ b/scripts/commands/api/generate.ts @@ -1,41 +1,41 @@ -import { Logger, Collection, Storage } from '@freearhey/core' -import { SITES_DIR, API_DIR } from '../../constants' -import { GuideChannel } from '../../models' -import { ChannelsParser } from '../../core' -import epgGrabber from 'epg-grabber' -import path from 'path' - -async function main() { - const logger = new Logger() - - logger.start('staring...') - - logger.info('loading channels...') - const sitesStorage = new Storage(SITES_DIR) - const parser = new ChannelsParser({ - storage: sitesStorage - }) - - const files: string[] = await sitesStorage.list('**/*.channels.xml') - - const channels = new Collection() - for (const filepath of files) { - const channelList = await parser.parse(filepath) - - channelList.channels.forEach((data: epgGrabber.Channel) => { - channels.add(new GuideChannel(data)) - }) - } - - logger.info(`found ${channels.count()} channel(s)`) - - const output = channels.map((channel: GuideChannel) => channel.toJSON()) - - const apiStorage = new Storage(API_DIR) - const outputFilename = 'guides.json' - await apiStorage.save('guides.json', output.toJSON()) - - logger.info(`saved to "${path.join(API_DIR, outputFilename)}"`) -} - -main() +import { Logger, Collection, Storage } from '@freearhey/core' +import { SITES_DIR, API_DIR } from '../../constants' +import { GuideChannel } from '../../models' +import { ChannelsParser } from '../../core' +import epgGrabber from 'epg-grabber' +import path from 'path' + +async function main() { + const logger = new Logger() + + logger.start('staring...') + + logger.info('loading channels...') + const sitesStorage = new Storage(SITES_DIR) + const parser = new ChannelsParser({ + storage: sitesStorage + }) + + const files: string[] = await sitesStorage.list('**/*.channels.xml') + + const channels = new Collection() + for (const filepath of files) { + const channelList = await parser.parse(filepath) + + channelList.channels.forEach((data: epgGrabber.Channel) => { + channels.add(new GuideChannel(data)) + }) + } + + logger.info(`found ${channels.count()} channel(s)`) + + const output = channels.map((channel: GuideChannel) => channel.toJSON()) + + const apiStorage = new Storage(API_DIR) + const outputFilename = 'guides.json' + await apiStorage.save('guides.json', output.toJSON()) + + logger.info(`saved to "${path.join(API_DIR, outputFilename)}"`) +} + +main() diff --git a/scripts/commands/api/load.ts b/scripts/commands/api/load.ts index 7a8f753da..0e5e2e07c 100644 --- a/scripts/commands/api/load.ts +++ b/scripts/commands/api/load.ts @@ -1,25 +1,25 @@ -import { DATA_DIR } from '../../constants' -import { Storage } from '@freearhey/core' -import { DataLoader } from '../../core' - -async function main() { - const storage = new Storage(DATA_DIR) - const loader = new DataLoader({ storage }) - - await Promise.all([ - loader.download('blocklist.json'), - loader.download('categories.json'), - loader.download('channels.json'), - loader.download('countries.json'), - loader.download('languages.json'), - loader.download('regions.json'), - loader.download('subdivisions.json'), - loader.download('feeds.json'), - loader.download('timezones.json'), - loader.download('guides.json'), - loader.download('streams.json'), - loader.download('logos.json') - ]) -} - -main() +import { DATA_DIR } from '../../constants' +import { Storage } from '@freearhey/core' +import { DataLoader } from '../../core' + +async function main() { + const storage = new Storage(DATA_DIR) + const loader = new DataLoader({ storage }) + + await Promise.all([ + loader.download('blocklist.json'), + loader.download('categories.json'), + loader.download('channels.json'), + loader.download('countries.json'), + loader.download('languages.json'), + loader.download('regions.json'), + loader.download('subdivisions.json'), + loader.download('feeds.json'), + loader.download('timezones.json'), + loader.download('guides.json'), + loader.download('streams.json'), + loader.download('logos.json') + ]) +} + +main() diff --git a/scripts/commands/channels/lint.mts b/scripts/commands/channels/lint.mts index 72cb003c2..e1f2a4f2b 100644 --- a/scripts/commands/channels/lint.mts +++ b/scripts/commands/channels/lint.mts @@ -1,109 +1,109 @@ -import chalk from 'chalk' -import { program } from 'commander' -import { Storage, File } from '@freearhey/core' -import { XmlDocument, XsdValidator, XmlValidateError, ErrorDetail } from 'libxml2-wasm' - -const xsd = ` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -` - -program.argument('[filepath...]', 'Path to *.channels.xml files to check').parse(process.argv) - -async function main() { - const storage = new Storage() - - let errors: ErrorDetail[] = [] - - const files = program.args.length ? program.args : await storage.list('sites/**/*.channels.xml') - for (const filepath of files) { - const file = new File(filepath) - if (file.extension() !== 'xml') continue - - const xml = await storage.load(filepath) - - let localErrors: ErrorDetail[] = [] - - try { - const schema = XmlDocument.fromString(xsd) - const validator = XsdValidator.fromDoc(schema) - const doc = XmlDocument.fromString(xml) - - validator.validate(doc) - - schema.dispose() - validator.dispose() - doc.dispose() - } catch (_error) { - const error = _error as XmlValidateError - - localErrors = localErrors.concat(error.details) - } - - xml.split('\n').forEach((line: string, lineIndex: number) => { - const found = line.match(/='/) - if (found) { - const colIndex = found.index || 0 - localErrors.push({ - line: lineIndex + 1, - col: colIndex + 1, - message: 'Single quotes cannot be used in attributes' - }) - } - }) - - if (localErrors.length) { - console.log(`\n${chalk.underline(filepath)}`) - localErrors.forEach((error: ErrorDetail) => { - const position = `${error.line}:${error.col}` - console.log(` ${chalk.gray(position.padEnd(4, ' '))} ${error.message.trim()}`) - }) - - errors = errors.concat(localErrors) - } - } - - if (errors.length) { - console.log(chalk.red(`\n${errors.length} error(s)`)) - process.exit(1) - } -} - -main() +import chalk from 'chalk' +import { program } from 'commander' +import { Storage, File } from '@freearhey/core' +import { XmlDocument, XsdValidator, XmlValidateError, ErrorDetail } from 'libxml2-wasm' + +const xsd = ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +` + +program.argument('[filepath...]', 'Path to *.channels.xml files to check').parse(process.argv) + +async function main() { + const storage = new Storage() + + let errors: ErrorDetail[] = [] + + const files = program.args.length ? program.args : await storage.list('sites/**/*.channels.xml') + for (const filepath of files) { + const file = new File(filepath) + if (file.extension() !== 'xml') continue + + const xml = await storage.load(filepath) + + let localErrors: ErrorDetail[] = [] + + try { + const schema = XmlDocument.fromString(xsd) + const validator = XsdValidator.fromDoc(schema) + const doc = XmlDocument.fromString(xml) + + validator.validate(doc) + + schema.dispose() + validator.dispose() + doc.dispose() + } catch (_error) { + const error = _error as XmlValidateError + + localErrors = localErrors.concat(error.details) + } + + xml.split('\n').forEach((line: string, lineIndex: number) => { + const found = line.match(/='/) + if (found) { + const colIndex = found.index || 0 + localErrors.push({ + line: lineIndex + 1, + col: colIndex + 1, + message: 'Single quotes cannot be used in attributes' + }) + } + }) + + if (localErrors.length) { + console.log(`\n${chalk.underline(filepath)}`) + localErrors.forEach((error: ErrorDetail) => { + const position = `${error.line}:${error.col}` + console.log(` ${chalk.gray(position.padEnd(4, ' '))} ${error.message.trim()}`) + }) + + errors = errors.concat(localErrors) + } + } + + if (errors.length) { + console.log(chalk.red(`\n${errors.length} error(s)`)) + process.exit(1) + } +} + +main() diff --git a/scripts/commands/sites/update.ts b/scripts/commands/sites/update.ts index 42db9acff..e1e1ef212 100644 --- a/scripts/commands/sites/update.ts +++ b/scripts/commands/sites/update.ts @@ -1,76 +1,76 @@ -import { IssueLoader, HTMLTable, ChannelsParser } from '../../core' -import { Logger, Storage, Collection } from '@freearhey/core' -import { ChannelList, Issue, Site } from '../../models' -import { SITES_DIR, ROOT_DIR } from '../../constants' -import { Channel } from 'epg-grabber' - -async function main() { - const logger = new Logger({ level: -999 }) - const issueLoader = new IssueLoader() - const sitesStorage = new Storage(SITES_DIR) - const sites = new Collection() - - logger.info('loading channels...') - const channelsParser = new ChannelsParser({ - storage: sitesStorage - }) - - logger.info('loading list of sites') - const folders = await sitesStorage.list('*/') - - logger.info('loading issues...') - const issues = await issueLoader.load() - - logger.info('putting the data together...') - const brokenGuideReports = issues.filter(issue => - issue.labels.find((label: string) => label === 'broken guide') - ) - for (const domain of folders) { - const filteredIssues = brokenGuideReports.filter( - (issue: Issue) => domain === issue.data.get('site') - ) - - const site = new Site({ - domain, - issues: filteredIssues - }) - - const files = await sitesStorage.list(`${domain}/*.channels.xml`) - for (const filepath of files) { - const channelList: ChannelList = await channelsParser.parse(filepath) - - site.totalChannels += channelList.channels.count() - site.markedChannels += channelList.channels - .filter((channel: Channel) => channel.xmltv_id) - .count() - } - - sites.add(site) - } - - logger.info('creating sites table...') - const tableData = new Collection() - sites.forEach((site: Site) => { - tableData.add([ - { value: `${site.domain}` }, - { value: site.totalChannels, align: 'right' }, - { value: site.markedChannels, align: 'right' }, - { value: site.getStatus().emoji, align: 'center' }, - { value: site.getIssues().all().join(', ') } - ]) - }) - - logger.info('updating sites.md...') - const table = new HTMLTable(tableData.all(), [ - { name: 'Site', align: 'left' }, - { name: 'Channels
    (total / with xmltv-id)', colspan: 2, align: 'left' }, - { name: 'Status', align: 'left' }, - { name: 'Notes', align: 'left' } - ]) - const rootStorage = new Storage(ROOT_DIR) - const sitesTemplate = await new Storage().load('scripts/templates/_sites.md') - const sitesContent = sitesTemplate.replace('_TABLE_', table.toString()) - await rootStorage.save('SITES.md', sitesContent) -} - -main() +import { IssueLoader, HTMLTable, ChannelsParser } from '../../core' +import { Logger, Storage, Collection } from '@freearhey/core' +import { ChannelList, Issue, Site } from '../../models' +import { SITES_DIR, ROOT_DIR } from '../../constants' +import { Channel } from 'epg-grabber' + +async function main() { + const logger = new Logger({ level: -999 }) + const issueLoader = new IssueLoader() + const sitesStorage = new Storage(SITES_DIR) + const sites = new Collection() + + logger.info('loading channels...') + const channelsParser = new ChannelsParser({ + storage: sitesStorage + }) + + logger.info('loading list of sites') + const folders = await sitesStorage.list('*/') + + logger.info('loading issues...') + const issues = await issueLoader.load() + + logger.info('putting the data together...') + const brokenGuideReports = issues.filter(issue => + issue.labels.find((label: string) => label === 'broken guide') + ) + for (const domain of folders) { + const filteredIssues = brokenGuideReports.filter( + (issue: Issue) => domain === issue.data.get('site') + ) + + const site = new Site({ + domain, + issues: filteredIssues + }) + + const files = await sitesStorage.list(`${domain}/*.channels.xml`) + for (const filepath of files) { + const channelList: ChannelList = await channelsParser.parse(filepath) + + site.totalChannels += channelList.channels.count() + site.markedChannels += channelList.channels + .filter((channel: Channel) => channel.xmltv_id) + .count() + } + + sites.add(site) + } + + logger.info('creating sites table...') + const tableData = new Collection() + sites.forEach((site: Site) => { + tableData.add([ + { value: `${site.domain}` }, + { value: site.totalChannels, align: 'right' }, + { value: site.markedChannels, align: 'right' }, + { value: site.getStatus().emoji, align: 'center' }, + { value: site.getIssues().all().join(', ') } + ]) + }) + + logger.info('updating sites.md...') + const table = new HTMLTable(tableData.all(), [ + { name: 'Site', align: 'left' }, + { name: 'Channels
    (total / with xmltv-id)', colspan: 2, align: 'left' }, + { name: 'Status', align: 'left' }, + { name: 'Notes', align: 'left' } + ]) + const rootStorage = new Storage(ROOT_DIR) + const sitesTemplate = await new Storage().load('scripts/templates/_sites.md') + const sitesContent = sitesTemplate.replace('_TABLE_', table.toString()) + await rootStorage.save('SITES.md', sitesContent) +} + +main() diff --git a/scripts/core/dataLoader.ts b/scripts/core/dataLoader.ts index 3d817977d..9f4c4cfb0 100644 --- a/scripts/core/dataLoader.ts +++ b/scripts/core/dataLoader.ts @@ -1,103 +1,103 @@ -import type { DataLoaderProps, DataLoaderData } from '../types/dataLoader' -import cliProgress, { MultiBar } from 'cli-progress' -import { Storage } from '@freearhey/core' -import { ApiClient } from './apiClient' -import numeral from 'numeral' - -export class DataLoader { - client: ApiClient - storage: Storage - progressBar: MultiBar - - constructor(props: DataLoaderProps) { - this.client = new ApiClient() - this.storage = props.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 load(): Promise { - const [ - countries, - regions, - subdivisions, - languages, - categories, - blocklist, - channels, - feeds, - timezones, - guides, - streams, - logos - ] = await Promise.all([ - this.storage.json('countries.json'), - this.storage.json('regions.json'), - this.storage.json('subdivisions.json'), - this.storage.json('languages.json'), - this.storage.json('categories.json'), - this.storage.json('blocklist.json'), - this.storage.json('channels.json'), - this.storage.json('feeds.json'), - this.storage.json('timezones.json'), - this.storage.json('guides.json'), - this.storage.json('streams.json'), - this.storage.json('logos.json') - ]) - - return { - countries, - regions, - subdivisions, - languages, - categories, - blocklist, - channels, - feeds, - timezones, - guides, - streams, - logos - } - } - - async download(filename: string) { - if (!this.storage || !this.progressBar) return - - const stream = await this.storage.createStream(filename) - const progressBar = this.progressBar.create(0, 0, { filename }) - - this.client - .get(filename, { - responseType: 'stream', - onDownloadProgress({ total, loaded, rate }) { - if (total) progressBar.setTotal(total) - progressBar.update(loaded, { speed: rate }) - } - }) - .then(response => { - response.data.pipe(stream) - }) - } -} +import type { DataLoaderProps, DataLoaderData } from '../types/dataLoader' +import cliProgress, { MultiBar } from 'cli-progress' +import { Storage } from '@freearhey/core' +import { ApiClient } from './apiClient' +import numeral from 'numeral' + +export class DataLoader { + client: ApiClient + storage: Storage + progressBar: MultiBar + + constructor(props: DataLoaderProps) { + this.client = new ApiClient() + this.storage = props.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 load(): Promise { + const [ + countries, + regions, + subdivisions, + languages, + categories, + blocklist, + channels, + feeds, + timezones, + guides, + streams, + logos + ] = await Promise.all([ + this.storage.json('countries.json'), + this.storage.json('regions.json'), + this.storage.json('subdivisions.json'), + this.storage.json('languages.json'), + this.storage.json('categories.json'), + this.storage.json('blocklist.json'), + this.storage.json('channels.json'), + this.storage.json('feeds.json'), + this.storage.json('timezones.json'), + this.storage.json('guides.json'), + this.storage.json('streams.json'), + this.storage.json('logos.json') + ]) + + return { + countries, + regions, + subdivisions, + languages, + categories, + blocklist, + channels, + feeds, + timezones, + guides, + streams, + logos + } + } + + async download(filename: string) { + if (!this.storage || !this.progressBar) return + + const stream = await this.storage.createStream(filename) + const progressBar = this.progressBar.create(0, 0, { filename }) + + this.client + .get(filename, { + responseType: 'stream', + onDownloadProgress({ total, loaded, rate }) { + if (total) progressBar.setTotal(total) + progressBar.update(loaded, { speed: rate }) + } + }) + .then(response => { + response.data.pipe(stream) + }) + } +} diff --git a/scripts/core/index.ts b/scripts/core/index.ts index 8d528fe7e..8694174a1 100644 --- a/scripts/core/index.ts +++ b/scripts/core/index.ts @@ -1,14 +1,14 @@ -export * from './apiClient' -export * from './channelsParser' -export * from './configLoader' -export * from './dataLoader' -export * from './dataProcessor' -export * from './grabber' -export * from './guideManager' -export * from './htmlTable' -export * from './issueLoader' -export * from './issueParser' -export * from './job' -export * from './proxyParser' -export * from './queue' -export * from './queueCreator' +export * from './apiClient' +export * from './channelsParser' +export * from './configLoader' +export * from './dataLoader' +export * from './dataProcessor' +export * from './grabber' +export * from './guideManager' +export * from './htmlTable' +export * from './issueLoader' +export * from './issueParser' +export * from './job' +export * from './proxyParser' +export * from './queue' +export * from './queueCreator' diff --git a/scripts/core/issueLoader.ts b/scripts/core/issueLoader.ts index eebd6c399..855f99e2d 100644 --- a/scripts/core/issueLoader.ts +++ b/scripts/core/issueLoader.ts @@ -1,37 +1,37 @@ -import { restEndpointMethods } from '@octokit/plugin-rest-endpoint-methods' -import { paginateRest } from '@octokit/plugin-paginate-rest' -import { TESTING, OWNER, REPO } from '../constants' -import { Collection } from '@freearhey/core' -import { Octokit } from '@octokit/core' -import { IssueParser } from './' - -const CustomOctokit = Octokit.plugin(paginateRest, restEndpointMethods) -const octokit = new CustomOctokit() - -export class IssueLoader { - async load(props?: { labels: string[] | string }) { - let labels = '' - if (props && props.labels) { - labels = Array.isArray(props.labels) ? props.labels.join(',') : props.labels - } - let issues: object[] = [] - if (TESTING) { - issues = (await import('../../tests/__data__/input/sites_update/issues.mjs')).default - } else { - issues = await octokit.paginate(octokit.rest.issues.listForRepo, { - owner: OWNER, - repo: REPO, - per_page: 100, - labels, - state: 'open', - headers: { - 'X-GitHub-Api-Version': '2022-11-28' - } - }) - } - - const parser = new IssueParser() - - return new Collection(issues).map(parser.parse) - } -} +import { restEndpointMethods } from '@octokit/plugin-rest-endpoint-methods' +import { paginateRest } from '@octokit/plugin-paginate-rest' +import { TESTING, OWNER, REPO } from '../constants' +import { Collection } from '@freearhey/core' +import { Octokit } from '@octokit/core' +import { IssueParser } from './' + +const CustomOctokit = Octokit.plugin(paginateRest, restEndpointMethods) +const octokit = new CustomOctokit() + +export class IssueLoader { + async load(props?: { labels: string[] | string }) { + let labels = '' + if (props && props.labels) { + labels = Array.isArray(props.labels) ? props.labels.join(',') : props.labels + } + let issues: object[] = [] + if (TESTING) { + issues = (await import('../../tests/__data__/input/sites_update/issues.mjs')).default + } else { + issues = await octokit.paginate(octokit.rest.issues.listForRepo, { + owner: OWNER, + repo: REPO, + per_page: 100, + labels, + state: 'open', + headers: { + 'X-GitHub-Api-Version': '2022-11-28' + } + }) + } + + const parser = new IssueParser() + + return new Collection(issues).map(parser.parse) + } +} diff --git a/scripts/functions/functions.ts b/scripts/functions/functions.ts deleted file mode 100644 index f6e0bb630..000000000 --- a/scripts/functions/functions.ts +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Sorts an array by the result of running each element through an iteratee function. - * Creates a shallow copy of the array before sorting to avoid mutating the original. - * - * @param {Array} arr - The array to sort - * @param {Function} fn - The iteratee function to compute sort values - * @returns {Array} A new sorted array - * - * @example - * const users = [{name: 'john', age: 30}, {name: 'jane', age: 25}]; - * sortBy(users, x => x.age); // [{name: 'jane', age: 25}, {name: 'john', age: 30}] - */ -export const sortBy = (arr: T[], fn: (item: T) => number | string): T[] => - [...arr].sort((a, b) => (fn(a) > fn(b) ? 1 : -1)) - -/** - * Sorts an array by multiple criteria with customizable sort orders. - * Supports ascending (default) and descending order for each criterion. - * - * @param {Array} arr - The array to sort - * @param {Array} fns - Array of iteratee functions to compute sort values - * @param {Array} orders - Array of sort orders ('asc' or 'desc'), defaults to all 'asc' - * @returns {Array} A new sorted array - * - * @example - * const users = [{name: 'john', age: 30}, {name: 'jane', age: 25}, {name: 'bob', age: 30}]; - * orderBy(users, [x => x.age, x => x.name], ['desc', 'asc']); - * // [{name: 'bob', age: 30}, {name: 'john', age: 30}, {name: 'jane', age: 25}] - */ -export const orderBy = ( - arr: unknown[], - fns: ((item: unknown) => string | number)[], - orders: string[] = [] -): unknown[] => - [...arr].sort((a, b) => - fns.reduce( - (acc, fn, i) => - acc || - ((orders[i] === 'desc' ? fn(b) > fn(a) : fn(a) > fn(b)) ? 1 : fn(a) === fn(b) ? 0 : -1), - 0 - ) - ) - -/** - * Creates a duplicate-free version of an array using an iteratee function to generate - * the criterion by which uniqueness is computed. Only the first occurrence of each - * element is kept. - * - * @param {Array} arr - The array to inspect - * @param {Function} fn - The iteratee function to compute uniqueness criterion - * @returns {Array} A new duplicate-free array - * - * @example - * const users = [{id: 1, name: 'john'}, {id: 2, name: 'jane'}, {id: 1, name: 'john'}]; - * uniqBy(users, x => x.id); // [{id: 1, name: 'john'}, {id: 2, name: 'jane'}] - */ -export const uniqBy = (arr: T[], fn: (item: T) => unknown): T[] => - arr.filter((item, index) => arr.findIndex(x => fn(x) === fn(item)) === index) - -/** - * Converts a string to start case (capitalizes the first letter of each word). - * Handles camelCase, snake_case, kebab-case, and regular spaces. - * - * @param {string} str - The string to convert - * @returns {string} The start case string - * - * @example - * startCase('hello_world'); // "Hello World" - * startCase('helloWorld'); // "Hello World" - * startCase('hello-world'); // "Hello World" - * startCase('hello world'); // "Hello World" - */ -export const startCase = (str: string): string => - str - .replace(/([a-z])([A-Z])/g, '$1 $2') // Split camelCase - .replace(/[_-]/g, ' ') // Replace underscores and hyphens with spaces - .replace(/\b\w/g, c => c.toUpperCase()) // Capitalize first letter of each word diff --git a/scripts/functions/index.ts b/scripts/functions/index.ts deleted file mode 100644 index 8c9cb76ad..000000000 --- a/scripts/functions/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './functions' \ No newline at end of file diff --git a/scripts/models/guideChannel.ts b/scripts/models/guideChannel.ts index 92aca912d..4876a89d8 100644 --- a/scripts/models/guideChannel.ts +++ b/scripts/models/guideChannel.ts @@ -1,59 +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 || '' - } - } -} +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 || '' + } + } +} diff --git a/scripts/models/index.ts b/scripts/models/index.ts index 38ab20272..b97d859cf 100644 --- a/scripts/models/index.ts +++ b/scripts/models/index.ts @@ -1,9 +1,9 @@ -export * from './channel' -export * from './feed' -export * from './guide' -export * from './guideChannel' -export * from './issue' -export * from './logo' -export * from './site' -export * from './stream' -export * from './channelList' +export * from './channel' +export * from './feed' +export * from './guide' +export * from './guideChannel' +export * from './issue' +export * from './logo' +export * from './site' +export * from './stream' +export * from './channelList' diff --git a/sites/chaines-tv.orange.fr/chaines-tv.orange.fr.channels.xml b/sites/chaines-tv.orange.fr/chaines-tv.orange.fr.channels.xml index 929c7a1cc..0c3030c19 100644 --- a/sites/chaines-tv.orange.fr/chaines-tv.orange.fr.channels.xml +++ b/sites/chaines-tv.orange.fr/chaines-tv.orange.fr.channels.xml @@ -1,298 +1,298 @@ - - - LA CHAINE DU PERE NOEL - FRANCE 3 ALPES - FRANCE 3 ALSACE - FRANCE 3 AQUITAINE - FRANCE 3 AUVERGNE - FRANCE 3 NORMANDIE CAEN - FRANCE 3 BOURGOGNE - FRANCE 3 BRETAGNE - FRANCE 3 CENTRE - FRANCE 3 CHAMPAGNE ARDENNE - FRANCE 3 COTE D'AZUR - FRANCE 3 FRANCHE COMTE - FRANCE 3 NORMANDIE ROUEN - FRANCE 3 LANGUEDOC - FRANCE 3 LIMOUSIN - FRANCE 3 LORRAINE - FRANCE 3 MIDI-PYRENEES - FRANCE 3 NORD P. CALAIS - FRANCE 3 PARIS IDF - FRANCE 3 PAYS DE LA LOIRE - FRANCE 3 PICARDIE - FRANCE 3 POITOU CHARENTES - FRANCE 3 PROVENCE ALPES - FRANCE 3 RHONE ALPES - WARNER TV NEXT - BOOMERANG (VO) - TCM CINEMA (VO) - TF1 4K - NCI - TECH&CO - DISNEY CHANNEL +1 - TOP SANTE TV - CANAL+ LIGUE1 UBER EATS - M6 4K - FRANCE 24 Arabe - CANAL+FOOT - CANAL+SPORT360 - L'ESPRIT SORCIER TV - FRANCE 24 Espagnol - CARTOONITO - SQOOL TV - CANAL+BOX OFFICE - TVMONACO - DAZN 1 - TRACE URBAN - STAR ACADEMY, LE LIVE - RFM TV - TRACE CARIBBEAN - TRACE LATINA - TRACE VANILLA - CSTAR HITS FRANCE - MEN'S UP TV - SOUVENIRS FROM EARTH - PUBLIC SENAT 24/24 - B SMART - LA CHAINE METEO - SKYNEWS - AFRICA 24 - AL JAZEERA Arabic - MEDI 1 TV - TRT WORLD - CANAL 10 Guadeloupe - TAHITI NUI TELEVISION - TELE ANTILLES - MADRAS FM TV - TRAVEL CHANNEL - FOOD NETWORK - FOXNEWS - ANTENA 3 - STAR TVE - A3 SERIES - CANAL 24 HORAS - ALL FLAMENCO - TV3 CATALUNYA - ETB BASQUE - TV DE GALICIA - REAL MADRID TV - RTP 3 - TVI INTERNACIONAL - SIC NOTICIAS - SIC INTERNACIONAL - TV RECORD - TVI FICCAO - ALMA LUSA - A BOLA TV - CORREIO DA MANHA TV - RAI STORIA - RAI SCUOLA - MEDIASET ITALIA - AL ARABIYA - ALARABY TELEVISION - AL AOULA - CANAL ALGERIE - MBC - ROTANA CLASSIC - ROTANA CLIP - ENNAHAR TV - ECHOROUK TV - NESSMA EU - EL HIWAR ETTOUNSI - AL RESALAH - IQRAA - IQRAA INTERNATIONAL - SAMIRA TV - ROTANA MUSICA - ECHOROUK NEWS - ROTANA KHALIJIA - ROTANA CINEMA - ROTANA COMEDY - ROTANA DRAMA - EL BILAD TV - PANORAMA DRAMA - MBC DRAMA - MBC MASR - AL RAWDA - NTD TV - CCTV 4 - PHOENIX CNE - PHOENIX INFONEWS - CHINA MOVIE CHANNEL - CCTV DIVERTISSEMENT - ZHEJIANG INTERNATIONAL TV - SHANGHAI DRAGON TV - BEIJING TV - HUNAN WORLD TV - JIANGSU INTERNATIONAL TV - GRT GBA Satellite TV - GREAT WALL ELITE - RTS - 2STV - ORTM - RTI1 - CRTV - RTNC - TELE CONGO - ORTB - A+ - AFRICABLE - CANAL 2 INT. - TVT - RTG - TFM - TRACE AFRICA - TRACE GOSPEL - SEN TV - TRACE TERANGA - 2M MONDE - 6TER - AB1 - ACTION - AL JAZEERA Anglais - ANIMAUX - ARTE - AUTOMOTO, la chaine - BBC ENTERTAINMENT - BBC NEWS - BEIN SPORTS 1 - BEIN SPORTS 2 - BEIN SPORTS 3 - BEIN SPORTS MAX 10 - BEIN SPORTS MAX 4 - BEIN SPORTS MAX 5 - BEIN SPORTS MAX 6 - BEIN SPORTS MAX 7 - BEIN SPORTS MAX 8 - BEIN SPORTS MAX 9 - BET - BFM BUSINESS - BFM TV - BLOOMBERG EUROPE - BOOMERANG - BOOMERANG +1 - CANAL J - CANAL+ - CANAL+CINEMA(S) - CANAL+DOCS - CANAL+GRAND ECRAN - CANAL+kids - CANAL+SERIES - CANAL+SPORT - CHASSE PECHE - CHERIE 25 - CINE+CLASSIC - CINE+CLUB - CINE+EMOTION - CINE+FAMIZ - CINE+FRISSON - CINE+PREMIER - CLUBBING TV - CNBC - CNEWS - CNN INTERNATIONAL - COMEDIE+ - COMEDY CENTRAL - CRIME DISTRICT - CSTAR - DEMAIN - DISNEY CHANNEL - DISNEY JUNIOR - DEUTSCHE WELLE - EQUIDIA - EUROCHANNEL - EURONEWS Français - FASHIONTV PARIS - FRANCE 2 - FRANCE 24 Anglais - FRANCE 24 Français - FRANCE 3 - FRANCE 3 CORSE VIA STELLA - FRANCE 4 - FRANCE 5 - FRANCEINFO: - GAME ONE - GAME ONE +1 - GOLF CHANNEL - GULLI - HISTOIRE TV - I24NEWS - J-ONE - KTO - LCI - LCP 100% - LA CHAINE L'EQUIPE - LUCKY JACK - LUXE TV - M6 - M6MUSIC - MAISON ET TRAVAUX TV - MANGAS - MCM - MELODY - MELODY D'AFRIQUE - MEZZO - MEZZO LIVE - MGG TV - MTV - MTV HITS - MUSEUM TV - MY ZEN TV - NATIONAL GEOGRAPHIC - NATIONAL GEOGRAPHIC WILD - NHK WORLD - JAPAN - NICKELODEON - NICKELODEON JUNIOR - NICKELODEON +1 - NICKELODEON TEEN - NOLLYWOOD TV - NOVELAS TV - NRJ HITS - OCS PULP - OCS GEANTS - OCS MAX - OLYMPIA TV - PARAMOUNT CHANNEL - PARAMOUNT CHANNEL DECALE - PARIS PREMIERE - PIWI+ - PLANETE+ - PLANETE+AVENTURE - PLANETE+CRIME - POLAR+ - LCP/PS - RAI UNO - RAI DUE - RAI TRE - RAI NEWS 24 - RMC DECOUVERTE - RMC STORY - RTL9 - RTPI - SCIENCE & VIE TV - SERIE CLUB - SPORT EN FRANCE - STINGRAY CLASSICA - SUNU YEUF - T18 - TCM CINEMA - TELETOON+ - TELETOON +1 - TEVA - TF1 - TF1 +1 - TF1 SERIES FILMS - TFX - TIJI - TMC - TMC +1 - TOUTE L'HISTOIRE - TV5MONDE - TV BREIZH - TVE INTERNACIONAL - TV PITCHOUN - USHUAIA TV - VOXAFRICA - W9 - + + + LA CHAINE DU PERE NOEL + FRANCE 3 ALPES + FRANCE 3 ALSACE + FRANCE 3 AQUITAINE + FRANCE 3 AUVERGNE + FRANCE 3 NORMANDIE CAEN + FRANCE 3 BOURGOGNE + FRANCE 3 BRETAGNE + FRANCE 3 CENTRE + FRANCE 3 CHAMPAGNE ARDENNE + FRANCE 3 COTE D'AZUR + FRANCE 3 FRANCHE COMTE + FRANCE 3 NORMANDIE ROUEN + FRANCE 3 LANGUEDOC + FRANCE 3 LIMOUSIN + FRANCE 3 LORRAINE + FRANCE 3 MIDI-PYRENEES + FRANCE 3 NORD P. CALAIS + FRANCE 3 PARIS IDF + FRANCE 3 PAYS DE LA LOIRE + FRANCE 3 PICARDIE + FRANCE 3 POITOU CHARENTES + FRANCE 3 PROVENCE ALPES + FRANCE 3 RHONE ALPES + WARNER TV NEXT + BOOMERANG (VO) + TCM CINEMA (VO) + TF1 4K + NCI + TECH&CO + DISNEY CHANNEL +1 + TOP SANTE TV + CANAL+ LIGUE1 UBER EATS + M6 4K + FRANCE 24 Arabe + CANAL+FOOT + CANAL+SPORT360 + L'ESPRIT SORCIER TV + FRANCE 24 Espagnol + CARTOONITO + SQOOL TV + CANAL+BOX OFFICE + TVMONACO + DAZN 1 + TRACE URBAN + STAR ACADEMY, LE LIVE + RFM TV + TRACE CARIBBEAN + TRACE LATINA + TRACE VANILLA + CSTAR HITS FRANCE + MEN'S UP TV + SOUVENIRS FROM EARTH + PUBLIC SENAT 24/24 + B SMART + LA CHAINE METEO + SKYNEWS + AFRICA 24 + AL JAZEERA Arabic + MEDI 1 TV + TRT WORLD + CANAL 10 Guadeloupe + TAHITI NUI TELEVISION + TELE ANTILLES + MADRAS FM TV + TRAVEL CHANNEL + FOOD NETWORK + FOXNEWS + ANTENA 3 + STAR TVE + A3 SERIES + CANAL 24 HORAS + ALL FLAMENCO + TV3 CATALUNYA + ETB BASQUE + TV DE GALICIA + REAL MADRID TV + RTP 3 + TVI INTERNACIONAL + SIC NOTICIAS + SIC INTERNACIONAL + TV RECORD + TVI FICCAO + ALMA LUSA + A BOLA TV + CORREIO DA MANHA TV + RAI STORIA + RAI SCUOLA + MEDIASET ITALIA + AL ARABIYA + ALARABY TELEVISION + AL AOULA + CANAL ALGERIE + MBC + ROTANA CLASSIC + ROTANA CLIP + ENNAHAR TV + ECHOROUK TV + NESSMA EU + EL HIWAR ETTOUNSI + AL RESALAH + IQRAA + IQRAA INTERNATIONAL + SAMIRA TV + ROTANA MUSICA + ECHOROUK NEWS + ROTANA KHALIJIA + ROTANA CINEMA + ROTANA COMEDY + ROTANA DRAMA + EL BILAD TV + PANORAMA DRAMA + MBC DRAMA + MBC MASR + AL RAWDA + NTD TV + CCTV 4 + PHOENIX CNE + PHOENIX INFONEWS + CHINA MOVIE CHANNEL + CCTV DIVERTISSEMENT + ZHEJIANG INTERNATIONAL TV + SHANGHAI DRAGON TV + BEIJING TV + HUNAN WORLD TV + JIANGSU INTERNATIONAL TV + GRT GBA Satellite TV + GREAT WALL ELITE + RTS + 2STV + ORTM + RTI1 + CRTV + RTNC + TELE CONGO + ORTB + A+ + AFRICABLE + CANAL 2 INT. + TVT + RTG + TFM + TRACE AFRICA + TRACE GOSPEL + SEN TV + TRACE TERANGA + 2M MONDE + 6TER + AB1 + ACTION + AL JAZEERA Anglais + ANIMAUX + ARTE + AUTOMOTO, la chaine + BBC ENTERTAINMENT + BBC NEWS + BEIN SPORTS 1 + BEIN SPORTS 2 + BEIN SPORTS 3 + BEIN SPORTS MAX 10 + BEIN SPORTS MAX 4 + BEIN SPORTS MAX 5 + BEIN SPORTS MAX 6 + BEIN SPORTS MAX 7 + BEIN SPORTS MAX 8 + BEIN SPORTS MAX 9 + BET + BFM BUSINESS + BFM TV + BLOOMBERG EUROPE + BOOMERANG + BOOMERANG +1 + CANAL J + CANAL+ + CANAL+CINEMA(S) + CANAL+DOCS + CANAL+GRAND ECRAN + CANAL+kids + CANAL+SERIES + CANAL+SPORT + CHASSE PECHE + CHERIE 25 + CINE+CLASSIC + CINE+CLUB + CINE+EMOTION + CINE+FAMIZ + CINE+FRISSON + CINE+PREMIER + CLUBBING TV + CNBC + CNEWS + CNN INTERNATIONAL + COMEDIE+ + COMEDY CENTRAL + CRIME DISTRICT + CSTAR + DEMAIN + DISNEY CHANNEL + DISNEY JUNIOR + DEUTSCHE WELLE + EQUIDIA + EUROCHANNEL + EURONEWS Français + FASHIONTV PARIS + FRANCE 2 + FRANCE 24 Anglais + FRANCE 24 Français + FRANCE 3 + FRANCE 3 CORSE VIA STELLA + FRANCE 4 + FRANCE 5 + FRANCEINFO: + GAME ONE + GAME ONE +1 + GOLF CHANNEL + GULLI + HISTOIRE TV + I24NEWS + J-ONE + KTO + LCI + LCP 100% + LA CHAINE L'EQUIPE + LUCKY JACK + LUXE TV + M6 + M6MUSIC + MAISON ET TRAVAUX TV + MANGAS + MCM + MELODY + MELODY D'AFRIQUE + MEZZO + MEZZO LIVE + MGG TV + MTV + MTV HITS + MUSEUM TV + MY ZEN TV + NATIONAL GEOGRAPHIC + NATIONAL GEOGRAPHIC WILD + NHK WORLD - JAPAN + NICKELODEON + NICKELODEON JUNIOR + NICKELODEON +1 + NICKELODEON TEEN + NOLLYWOOD TV + NOVELAS TV + NRJ HITS + OCS PULP + OCS GEANTS + OCS MAX + OLYMPIA TV + PARAMOUNT CHANNEL + PARAMOUNT CHANNEL DECALE + PARIS PREMIERE + PIWI+ + PLANETE+ + PLANETE+AVENTURE + PLANETE+CRIME + POLAR+ + LCP/PS + RAI UNO + RAI DUE + RAI TRE + RAI NEWS 24 + RMC DECOUVERTE + RMC STORY + RTL9 + RTPI + SCIENCE & VIE TV + SERIE CLUB + SPORT EN FRANCE + STINGRAY CLASSICA + SUNU YEUF + T18 + TCM CINEMA + TELETOON+ + TELETOON +1 + TEVA + TF1 + TF1 +1 + TF1 SERIES FILMS + TFX + TIJI + TMC + TMC +1 + TOUTE L'HISTOIRE + TV5MONDE + TV BREIZH + TVE INTERNACIONAL + TV PITCHOUN + USHUAIA TV + VOXAFRICA + W9 + diff --git a/sites/chaines-tv.orange.fr/chaines-tv.orange.fr.config.js b/sites/chaines-tv.orange.fr/chaines-tv.orange.fr.config.js index 3ed3ebc46..80f232d29 100644 --- a/sites/chaines-tv.orange.fr/chaines-tv.orange.fr.config.js +++ b/sites/chaines-tv.orange.fr/chaines-tv.orange.fr.config.js @@ -1,79 +1,79 @@ -const axios = require('axios') -const dayjs = require('dayjs') - -module.exports = { - site: 'chaines-tv.orange.fr', - days: 2, - url({ channel, date }) { - return `https://rp-ott-mediation-tv.woopic.com/api-gw/live/v3/applications/STB4PC/programs?groupBy=channel&includeEmptyChannels=false&period=${date.valueOf()},${date - .add(1, 'd') - .valueOf()}&after=${channel.site_id}&limit=1` - }, - parser: function ({ content, channel }) { - let programs = [] - const items = parseItems(content, channel) - items.forEach(item => { - const start = parseStart(item) - const stop = parseStop(item, start) - programs.push({ - title: item.title, - subTitle: item.season?.serie?.title, - category: item.genreDetailed, - description: item.synopsis, - season: parseSeason(item), - episode: parseEpisode(item), - image: parseImage(item), - start: start.toJSON(), - stop: stop.toJSON() - }) - }) - - return programs - }, - async channels() { - const html = await axios - .get('https://chaines-tv.orange.fr/programme-tv?filtres=all') - .then(r => r.data) - .catch(console.log) - - const [, nuxtFunc] = html.match(/window\.__NUXT__=([^<]+)/) || [null, null] - const func = new Function(`"use strict";return ${nuxtFunc}`) - - const data = func() - const items = data.state.channels.channels - - return items.map(item => { - return { - lang: 'fr', - site_id: item.idEPG, - name: item.name - } - }) - } -} - -function parseImage(item) { - return item.covers && item.covers.length ? item.covers[0].url : null -} - -function parseStart(item) { - return dayjs.unix(item.diffusionDate) -} - -function parseStop(item, start) { - return start.add(item.duration, 's') -} - -function parseSeason(item) { - return item.season?.number -} - -function parseEpisode(item) { - return item.episodeNumber -} - -function parseItems(content, channel) { - const data = JSON.parse(content) - - return data && data[channel.site_id] ? data[channel.site_id] : [] -} +const axios = require('axios') +const dayjs = require('dayjs') + +module.exports = { + site: 'chaines-tv.orange.fr', + days: 2, + url({ channel, date }) { + return `https://rp-ott-mediation-tv.woopic.com/api-gw/live/v3/applications/STB4PC/programs?groupBy=channel&includeEmptyChannels=false&period=${date.valueOf()},${date + .add(1, 'd') + .valueOf()}&after=${channel.site_id}&limit=1` + }, + parser: function ({ content, channel }) { + let programs = [] + const items = parseItems(content, channel) + items.forEach(item => { + const start = parseStart(item) + const stop = parseStop(item, start) + programs.push({ + title: item.title, + subTitle: item.season?.serie?.title, + category: item.genreDetailed, + description: item.synopsis, + season: parseSeason(item), + episode: parseEpisode(item), + image: parseImage(item), + start: start.toJSON(), + stop: stop.toJSON() + }) + }) + + return programs + }, + async channels() { + const html = await axios + .get('https://chaines-tv.orange.fr/programme-tv?filtres=all') + .then(r => r.data) + .catch(console.log) + + const [, nuxtFunc] = html.match(/window\.__NUXT__=([^<]+)/) || [null, null] + const func = new Function(`"use strict";return ${nuxtFunc}`) + + const data = func() + const items = data.state.channels.channels + + return items.map(item => { + return { + lang: 'fr', + site_id: item.idEPG, + name: item.name + } + }) + } +} + +function parseImage(item) { + return item.covers && item.covers.length ? item.covers[0].url : null +} + +function parseStart(item) { + return dayjs.unix(item.diffusionDate) +} + +function parseStop(item, start) { + return start.add(item.duration, 's') +} + +function parseSeason(item) { + return item.season?.number +} + +function parseEpisode(item) { + return item.episodeNumber +} + +function parseItems(content, channel) { + const data = JSON.parse(content) + + return data && data[channel.site_id] ? data[channel.site_id] : [] +} diff --git a/sites/ctc.ru/ctc.ru.config.js b/sites/ctc.ru/ctc.ru.config.js index 773d9366f..5dc417729 100644 --- a/sites/ctc.ru/ctc.ru.config.js +++ b/sites/ctc.ru/ctc.ru.config.js @@ -1,95 +1,95 @@ -const dayjs = require('dayjs') - -module.exports = { - site: 'ctc.ru', - days: 1, - url: ({ date }) => `https://ctc.ru/api/page/v2/programm/?date=${formatDate(date)}`, - parser({ content }) { - const programs = [] - const items = parseItems(content) - for (const item of items) { - programs.push({ - title: item.bubbleTitle, - // more like "films", "shows", "cartoons" - not a genre - category: item.bubbleSubTitle, - icons: parseIcons(item), - images: parseImages(item), - start: parseStart(item), - stop: parseStop(item), - // not sure if CTC uses this more like `premiere` but I don't have any - // additional info to use in the `premiere` field so I'm using this - // instead. - new: item.isPremiere ?? false, - url: item.bubbleUrl ? `https://ctc.ru${item.bubbleUrl}` : undefined, - rating: parseRating(item), - }) - } - - return programs - } -} - -function formatDate(date) { - return dayjs(date).format('DD-MM-YYYY') -} - -function parseIcons(item) { - const images = item.bubbleImage ?? [] - // biggest first - const sorted = images.sort((a, b) => b.height - a.height) - - return sorted.map((image) => ({ - src: image.url, - width: image.width, - height: image.height, - })) -} - -function parseImages(item) { - const images = item.trackImageUrl ?? [] - // biggest first - const sorted = images.sort((a, b) => b.height - a.height) - - // compile one image of each size since the content should be the same - const sizes = {} - for (const image of sorted) { - const maxRes = Math.max(image.width, image.height) - const item = { - type: 'backdrop', - // https://github.com/ektotv/xmltv/blob/801417b4b7aae38f13caa82d8bbfbed0a254ee5f/src/types.ts#L40-L48 - size: maxRes > 400 ? '3' : maxRes > 200 ? '2' : '1', - orient: image.height > image.width ? 'P' : 'L', - value: image.url, - } - if (sizes[item.size]) continue - sizes[item.size] = item - } - - // re-sort so the size 3 images are first - return Object - .values(sizes) - .sort((a, b) => Number(b.size) - Number(a.size)) -} - -function parseStart(item) { - return dayjs(item.startTime) -} - -function parseStop(item) { - return dayjs(item.endTime) -} - -function parseRating(item) { - if (item.ageLimit == null) return null - return { - // Not sure what the Russian system is actually called (if anything) - system: 'Russia', - value: `${item.ageLimit}+`, - } -} - -function parseItems(content) { - const node = JSON.parse(content).content.find(n => n.type === 'tv-program') - if (node) return node.widgets - return [] -} +const dayjs = require('dayjs') + +module.exports = { + site: 'ctc.ru', + days: 1, + url: ({ date }) => `https://ctc.ru/api/page/v2/programm/?date=${formatDate(date)}`, + parser({ content }) { + const programs = [] + const items = parseItems(content) + for (const item of items) { + programs.push({ + title: item.bubbleTitle, + // more like "films", "shows", "cartoons" - not a genre + category: item.bubbleSubTitle, + icons: parseIcons(item), + images: parseImages(item), + start: parseStart(item), + stop: parseStop(item), + // not sure if CTC uses this more like `premiere` but I don't have any + // additional info to use in the `premiere` field so I'm using this + // instead. + new: item.isPremiere ?? false, + url: item.bubbleUrl ? `https://ctc.ru${item.bubbleUrl}` : undefined, + rating: parseRating(item), + }) + } + + return programs + } +} + +function formatDate(date) { + return dayjs(date).format('DD-MM-YYYY') +} + +function parseIcons(item) { + const images = item.bubbleImage ?? [] + // biggest first + const sorted = images.sort((a, b) => b.height - a.height) + + return sorted.map((image) => ({ + src: image.url, + width: image.width, + height: image.height, + })) +} + +function parseImages(item) { + const images = item.trackImageUrl ?? [] + // biggest first + const sorted = images.sort((a, b) => b.height - a.height) + + // compile one image of each size since the content should be the same + const sizes = {} + for (const image of sorted) { + const maxRes = Math.max(image.width, image.height) + const item = { + type: 'backdrop', + // https://github.com/ektotv/xmltv/blob/801417b4b7aae38f13caa82d8bbfbed0a254ee5f/src/types.ts#L40-L48 + size: maxRes > 400 ? '3' : maxRes > 200 ? '2' : '1', + orient: image.height > image.width ? 'P' : 'L', + value: image.url, + } + if (sizes[item.size]) continue + sizes[item.size] = item + } + + // re-sort so the size 3 images are first + return Object + .values(sizes) + .sort((a, b) => Number(b.size) - Number(a.size)) +} + +function parseStart(item) { + return dayjs(item.startTime) +} + +function parseStop(item) { + return dayjs(item.endTime) +} + +function parseRating(item) { + if (item.ageLimit == null) return null + return { + // Not sure what the Russian system is actually called (if anything) + system: 'Russia', + value: `${item.ageLimit}+`, + } +} + +function parseItems(content) { + const node = JSON.parse(content).content.find(n => n.type === 'tv-program') + if (node) return node.widgets + return [] +} diff --git a/sites/ctc.ru/ctc.ru.test.js b/sites/ctc.ru/ctc.ru.test.js index 4e11cc649..529f39b2d 100644 --- a/sites/ctc.ru/ctc.ru.test.js +++ b/sites/ctc.ru/ctc.ru.test.js @@ -1,91 +1,91 @@ -const { parser, url } = require('./ctc.ru.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-06-22') - -it('can generate valid url', () => { - expect(url({ date })).toBe('https://ctc.ru/api/page/v2/programm/?date=22-06-2025') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - let results = parser({ content, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2025-06-22T03:00:00.000Z', - stop: '2025-06-22T03:55:00.000Z', - title: 'Три кота', - category: 'Мультфильмы', - new: false, - url: 'https://ctc.ru/collections/multiki/', - icons: [ - { - src: 'https://mgf-static-ssl.ctc.ru/images/ctc-entity-dictionary-category/5/iconurl/web/5f9027fcba9ac-125x125.png', - width: 125, - height: 125, - }, - { - src: 'https://mgf-static-ssl.ctc.ru/images/ctc-entity-dictionary-category/5/iconurl/web/5f9027fc99587-60x60.png', - width: 60, - height: 60, - }, - ], - images: [ - { - type: 'backdrop', - size: '3', - orient: 'L', - value: 'https://mgf-static-ssl.ctc.ru/images/ctc-entity-project/1129/horizontalcover/web/66c319f0a31b5-1740x978.jpeg', - }, - { - type: 'backdrop', - size: '2', - orient: 'L', - value: 'https://mgf-static-ssl.ctc.ru/images/ctc-entity-project/1129/horizontalcover/web/66c319f20bac2-400x225.jpeg', - }, - { - type: 'backdrop', - size: '1', - orient: 'L', - value: 'https://mgf-static-ssl.ctc.ru/images/ctc-entity-project/1129/horizontalcover/web/66c319f244f92-150x84.jpeg', - }, - ], - rating: { - system: 'Russia', - value: '0+', - }, - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - content: JSON.stringify({ - isActive: true, - url: '/programm/', - header: [], - sidebar: [], - footer: [], - content: [], - seoTags: {}, - ogMarkup: {}, - userGeo: null, - userData: null, - meta: {}, - activeFrom: null, - activeTo: null, - type: 'tv-program-page', - }) - }) - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./ctc.ru.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-06-22') + +it('can generate valid url', () => { + expect(url({ date })).toBe('https://ctc.ru/api/page/v2/programm/?date=22-06-2025') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + let results = parser({ content, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2025-06-22T03:00:00.000Z', + stop: '2025-06-22T03:55:00.000Z', + title: 'Три кота', + category: 'Мультфильмы', + new: false, + url: 'https://ctc.ru/collections/multiki/', + icons: [ + { + src: 'https://mgf-static-ssl.ctc.ru/images/ctc-entity-dictionary-category/5/iconurl/web/5f9027fcba9ac-125x125.png', + width: 125, + height: 125, + }, + { + src: 'https://mgf-static-ssl.ctc.ru/images/ctc-entity-dictionary-category/5/iconurl/web/5f9027fc99587-60x60.png', + width: 60, + height: 60, + }, + ], + images: [ + { + type: 'backdrop', + size: '3', + orient: 'L', + value: 'https://mgf-static-ssl.ctc.ru/images/ctc-entity-project/1129/horizontalcover/web/66c319f0a31b5-1740x978.jpeg', + }, + { + type: 'backdrop', + size: '2', + orient: 'L', + value: 'https://mgf-static-ssl.ctc.ru/images/ctc-entity-project/1129/horizontalcover/web/66c319f20bac2-400x225.jpeg', + }, + { + type: 'backdrop', + size: '1', + orient: 'L', + value: 'https://mgf-static-ssl.ctc.ru/images/ctc-entity-project/1129/horizontalcover/web/66c319f244f92-150x84.jpeg', + }, + ], + rating: { + system: 'Russia', + value: '0+', + }, + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + content: JSON.stringify({ + isActive: true, + url: '/programm/', + header: [], + sidebar: [], + footer: [], + content: [], + seoTags: {}, + ogMarkup: {}, + userGeo: null, + userData: null, + meta: {}, + activeFrom: null, + activeTo: null, + type: 'tv-program-page', + }) + }) + expect(results).toMatchObject([]) +}) diff --git a/sites/derana.lk/derana.lk.config.js b/sites/derana.lk/derana.lk.config.js index 790de7ade..266bb7257 100644 --- a/sites/derana.lk/derana.lk.config.js +++ b/sites/derana.lk/derana.lk.config.js @@ -3,7 +3,7 @@ const utc = require('dayjs/plugin/utc') const customParseFormat = require('dayjs/plugin/customParseFormat') const parseDuration = require('parse-duration').default const timezone = require('dayjs/plugin/timezone') -const { sortBy } = require('../../scripts/functions') +const sortBy = require('lodash.sortby') dayjs.extend(customParseFormat) dayjs.extend(utc) diff --git a/sites/dstv.com/dstv.com.config.js b/sites/dstv.com/dstv.com.config.js index 44c761111..3412a83d5 100644 --- a/sites/dstv.com/dstv.com.config.js +++ b/sites/dstv.com/dstv.com.config.js @@ -3,7 +3,7 @@ const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const timezone = require('dayjs/plugin/timezone') const customParseFormat = require('dayjs/plugin/customParseFormat') -const { uniqBy } = require('../../scripts/functions') +const uniqBy = require('lodash.uniqby') dayjs.extend(utc) dayjs.extend(timezone) diff --git a/sites/epgshare01.online/readme.md b/sites/epgshare01.online/readme.md index 7979cca33..8625d1598 100644 --- a/sites/epgshare01.online/readme.md +++ b/sites/epgshare01.online/readme.md @@ -1,135 +1,135 @@ -# epgshare01.online - -https://epgshare01.online/epgshare01/ - -| Tag | -| ------------------- | -| `ALJAZEERA1` | -| `AR1` | -| `ASIANTELEVISION1` | -| `AU1` | -| `BA1` | -| `BE2` | -| `BEIN1` | -| `BG1` | -| `BR1` | -| `CA1` | -| `CH1` | -| `CL1` | -| `CO1` | -| `CR1` | -| `CY1` | -| `CZ1` | -| `DE1` | -| `DELUXEMUSIC1` | -| `DIRECTVSPORTS1` | -| `DISTROTV1` | -| `DK1` | -| `DO1` | -| `DRAFTKINGS1` | -| `DUMMY_CHANNELS` | -| `EC1` | -| `EG1` | -| `ES1` | -| `EUROSPORT1` | -| `FANDUEL1` | -| `FI1` | -| `FR1` | -| `GR1` | -| `HK1` | -| `HR1` | -| `HU1` | -| `ID1` | -| `IE1` | -| `IL1` | -| `IN4` | -| `IT1` | -| `JM1` | -| `JP1` | -| `JP2` | -| `KE1` | -| `KR1` | -| `MT1` | -| `MX1` | -| `MY1` | -| `NAUTICAL_CHANNEL1` | -| `NG1` | -| `NL1` | -| `NO1` | -| `NZ1` | -| `OPTUSSPORTS1` | -| `PA1` | -| `PAC-12` | -| `PE1` | -| `PH1` | -| `PH2` | -| `PK1` | -| `PL1` | -| `PLEX1` | -| `POWERNATION1` | -| `PT1` | -| `RAKUTEN_DE1` | -| `RAKUTEN_EN1` | -| `RAKUTEN_ES1` | -| `RAKUTEN_FR1` | -| `RAKUTEN_IT1` | -| `RAKUTEN_NL1` | -| `RAKUTEN_PL1` | -| `RALLY_TV1` | -| `RO1` | -| `RO2` | -| `SA1` | -| `SA2` | -| `SAMSUNG1` | -| `SE1` | -| `SG1` | -| `SK1` | -| `SPORTKLUB1` | -| `SSPORTPLUS1` | -| `SV1` | -| `TBNPLUS1` | -| `TENNIS1` | -| `THESPORTPLUS1` | -| `TR1` | -| `TR3` | -| `UK1` | -| `US1` | -| `US_LOCALS2` | -| `US_SPORTS1` | -| `UY1` | -| `VN1` | -| `VOA1` | -| `ZA1` | -| `viva-russia.ru` | - -### Download the guide - -Windows (Command Prompt): - -```sh -SET "NODE_OPTIONS=--max-old-space-size=6000" && npm run grab --- --channels=sites/epgshare01.online/epgshare01.online_.channels.xml -``` - -Windows (PowerShell): - -```sh -$env:NODE_OPTIONS="--max-old-space-size=6000"; npm run grab --- --channels=sites/epgshare01.online/epgshare01.online_.channels.xml -``` - -Linux and macOS: - -```sh -NODE_OPTIONS=--max-old-space-size=6000 npm run grab --- --channels=sites/epgshare01.online/epgshare01.online_.channels.xml -``` - -### Update channel list - -```sh -npm run channels:parse --- --config=./sites/epgshare01.online/epgshare01.online.config.js --output=./sites/epgshare01.online/epgshare01.online_.channels.xml --set=tag: -``` - -### Test - -```sh -npm test --- epgshare01.online -``` +# epgshare01.online + +https://epgshare01.online/epgshare01/ + +| Tag | +| ------------------- | +| `ALJAZEERA1` | +| `AR1` | +| `ASIANTELEVISION1` | +| `AU1` | +| `BA1` | +| `BE2` | +| `BEIN1` | +| `BG1` | +| `BR1` | +| `CA1` | +| `CH1` | +| `CL1` | +| `CO1` | +| `CR1` | +| `CY1` | +| `CZ1` | +| `DE1` | +| `DELUXEMUSIC1` | +| `DIRECTVSPORTS1` | +| `DISTROTV1` | +| `DK1` | +| `DO1` | +| `DRAFTKINGS1` | +| `DUMMY_CHANNELS` | +| `EC1` | +| `EG1` | +| `ES1` | +| `EUROSPORT1` | +| `FANDUEL1` | +| `FI1` | +| `FR1` | +| `GR1` | +| `HK1` | +| `HR1` | +| `HU1` | +| `ID1` | +| `IE1` | +| `IL1` | +| `IN4` | +| `IT1` | +| `JM1` | +| `JP1` | +| `JP2` | +| `KE1` | +| `KR1` | +| `MT1` | +| `MX1` | +| `MY1` | +| `NAUTICAL_CHANNEL1` | +| `NG1` | +| `NL1` | +| `NO1` | +| `NZ1` | +| `OPTUSSPORTS1` | +| `PA1` | +| `PAC-12` | +| `PE1` | +| `PH1` | +| `PH2` | +| `PK1` | +| `PL1` | +| `PLEX1` | +| `POWERNATION1` | +| `PT1` | +| `RAKUTEN_DE1` | +| `RAKUTEN_EN1` | +| `RAKUTEN_ES1` | +| `RAKUTEN_FR1` | +| `RAKUTEN_IT1` | +| `RAKUTEN_NL1` | +| `RAKUTEN_PL1` | +| `RALLY_TV1` | +| `RO1` | +| `RO2` | +| `SA1` | +| `SA2` | +| `SAMSUNG1` | +| `SE1` | +| `SG1` | +| `SK1` | +| `SPORTKLUB1` | +| `SSPORTPLUS1` | +| `SV1` | +| `TBNPLUS1` | +| `TENNIS1` | +| `THESPORTPLUS1` | +| `TR1` | +| `TR3` | +| `UK1` | +| `US1` | +| `US_LOCALS2` | +| `US_SPORTS1` | +| `UY1` | +| `VN1` | +| `VOA1` | +| `ZA1` | +| `viva-russia.ru` | + +### Download the guide + +Windows (Command Prompt): + +```sh +SET "NODE_OPTIONS=--max-old-space-size=6000" && npm run grab --- --channels=sites/epgshare01.online/epgshare01.online_.channels.xml +``` + +Windows (PowerShell): + +```sh +$env:NODE_OPTIONS="--max-old-space-size=6000"; npm run grab --- --channels=sites/epgshare01.online/epgshare01.online_.channels.xml +``` + +Linux and macOS: + +```sh +NODE_OPTIONS=--max-old-space-size=6000 npm run grab --- --channels=sites/epgshare01.online/epgshare01.online_.channels.xml +``` + +### Update channel list + +```sh +npm run channels:parse --- --config=./sites/epgshare01.online/epgshare01.online.config.js --output=./sites/epgshare01.online/epgshare01.online_.channels.xml --set=tag: +``` + +### Test + +```sh +npm test --- epgshare01.online +``` diff --git a/sites/guida.tv/guida.tv.config.js b/sites/guida.tv/guida.tv.config.js index 9e00f73f9..810c53a25 100644 --- a/sites/guida.tv/guida.tv.config.js +++ b/sites/guida.tv/guida.tv.config.js @@ -4,7 +4,7 @@ const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const timezone = require('dayjs/plugin/timezone') const customParseFormat = require('dayjs/plugin/customParseFormat') -const { uniqBy } = require('../../scripts/functions') +const uniqBy = require('lodash.uniqby') dayjs.extend(utc) dayjs.extend(timezone) diff --git a/sites/i.mjh.nz/i.mjh.nz_samsung.channels.xml b/sites/i.mjh.nz/i.mjh.nz_samsung.channels.xml index fc581f590..80c7ca377 100644 --- a/sites/i.mjh.nz/i.mjh.nz_samsung.channels.xml +++ b/sites/i.mjh.nz/i.mjh.nz_samsung.channels.xml @@ -1,1528 +1,1528 @@ - - - Deluxe Lounge HD - Focus TV - Netzkino - Tierwelt - Xplore - Hip Trips - just.fishing - Sportdigital Free - Just Cooking - SPIEGEL TV - GoldStar TV - Charlie - Grjngo - Tempora - auto motor und sport - MTV Pluto TV - Teen Nick - SpongeBob Schwammkopf - Ice Pilots - The Pet Collective - Actionfilme - Rakuten TV - Drama Filme - Rakuten TV - Nick Pluto TV - iCarly - MTV Teen Mom - FailArmy - Komödien - Rakuten TV - Dokumentarfilme - Rakuten TV - Pluto TV Serie - Pluto TV Paranormal - Pluto TV Animals - Pluto TV Food - Pluto TV Science - Pluto TV Sitcoms - Comedy Central Pluto TV - Pluto TV Movies - Comedy Central Made in Germany - MTV Catfish - People Are Awesome - Euronews Live - Travelxp - Yu-Gi-Oh! - Fantasja - Fabella - One Terra - Bergblick Free - Deluxe Deutschpop - Qwest TV - INFAST - Tastemade - Tennis Channel - Zee One - FIFA+ - CURIOSITY Now - SPIEGEL TV Konflikte - Strongman Champions League - Deutsches Kino - Rakuten TV - Bloomberg TV+ - MyTime Movie Network - wedo movies - Horse &amp; Country - Wilder Planet - Fashion TV - Top Filme - Rakuten TV - Moconomy - Heimatkino - INWONDER - XITE Hits - Crime Serien - Rakuten TV - Teletubbies - Comedy &amp; Shows - Craction TV - Moviedome - 100% Weihnachten - Rakuten TV - Moviedome Family - Clubbing TV - Crime Mix - Qello Concerts by Stingray - Stars in Gefahr - The Wicked Tuna Channel - Naruto - Spannung &amp; Emotionen - Filmgold - CNN - FRANCE 24 FAST - Kaminfeuer - Cine Mix - Red Bull TV - Sport 1 Motor - Out TV - Hell's Kitchen - Sallys Welt - Baywatch - More than Sports TV - Motorvision Classic - Motorvision - Terra Mater - X-MAS Mix - Deluxe Wintertime - CNN FAST - World Poker Tour - Aniverse - Deluxe Lounge HD - Focus TV - Netzkino - Tierwelt - Xplore - Hip Trips - just.fishing - Sportdigital Free - Just Cooking - SPIEGEL TV - GoldStar TV - Charlie - Grjngo - Tempora - auto motor und sport - MTV Pluto TV - Nick Pluto TV - Teen Nick - iCarly - Pluto TV Serie - Ice Pilots - MTV Teen Mom - The Pet Collective - WP - Drama Filme - Rakuten TV - Dokumentarfilme - Rakuten TV - SpongeBob Schwammkopf - Pluto TV Paranormal - FailArmy - Actionfilme - Rakuten TV - Komödien - Rakuten TV - Pluto TV Animals - Pluto TV Food - Pluto TV Science - Pluto TV Sitcoms - Comedy Central Pluto TV - Pluto TV Movies - Comedy Central Made in Germany - MTV Catfish - People Are Awesome - Travelxp - Fashion TV - Travelxp [FR] - Top Filme - Rakuten TV - Fabella - One Terra - Heimatkino - Bergblick Free - Deluxe Deutschpop - Qwest TV - INFAST - INWONDER - Tastemade - ADN [FR] - SportOutdoor.tv [IT] - FIFA+ - Strongman Champions League - Teletubbies - Risate all'italiana - Bloomberg TV+ - BelAir TV [FR] - Wellbeing TV [FR] - wedo movies - Craction TV - Wilder Planet - Moviedome Family - Horse &amp; Country - Moconomy - Tennis Channel - Deutsches Kino - Rakuten TV - Crime Serien - Rakuten TV - Euronews Live - Fantasja - 100% Weihnachten - Rakuten TV - Moviedome - Clubbing TV - Emotion'L [FR] - Qello Concerts by Stingray - Chefclub TV [FR] - Ciné Nanar [FR] - Motus [FR] - Spannung &amp; Emotionen - Comedy &amp; Shows - Scream'IN [FR] - Y'a que la vérité qui compte [FR] - Stars in Gefahr - Crime Mix - MyTime Movie Network - Trace Latina [FR] - Gong [FR] - Fréquence Novelas [FR] - Ciné Prime [FR] - Enquêtes de Choc [FR] - Filmgold - Homicide [FR] - Naruto - Destination Nature [FR] - Émotion [FR] - Le Figaro Live [FR] - The Wicked Tuna Channel - Echappées belles &amp; co [FR] - Grandi Documentari - Wedo Big Stories [IT] - Nature Time [FR] - Génération Sitcoms [FR] - Les filles d'à côté [FR] - Film Al Femminile - Wedo Movies [IT] - CNN - Le Tigre [FR] - Vialma [FR] - FRANCE 24 FAST - CURIOSITY Now - SPIEGEL TV Konflikte - Yu-Gi-Oh! - CNN FAST - Screen Green - Alerte à Malibu [FR] - Sport 1 Motor - Out TV - Sallys Welt - Baywatch - More than Sports TV - Motorvision Classic - Terra Mater - World Poker Tour - X-MAS Mix - Plus belle la vie [FR] - Culture Pub [FR] - Hell's Kitchen - Motorvision - Zee One - Deluxe Wintertime - Aniverse - Kaminfeuer - Deluxe Lounge HD - Focus TV - Netzkino - Xplore - Tierwelt - Hip Trips - just.fishing - Sportdigital Free - Nick Pluto TV - Teen Nick - iCarly - Pluto TV Serie - Pluto TV Paranormal - SPIEGEL TV Konflikte - Yu-Gi-Oh! - Zee One - FailArmy - The Pet Collective - Actionfilme - Rakuten TV - Komödien - Rakuten TV - Drama Filme - Rakuten TV - People Are Awesome - MTV Pluto TV - Ice Pilots - MTV Teen Mom - Terra Mater - Dokumentarfilme - Rakuten TV - SpongeBob Schwammkopf - Just Cooking - SPIEGEL TV - GoldStar TV - Charlie - Grjngo - Tempora - auto motor und sport - Comedy Central Pluto TV - Comedy Central Made in Germany - MTV Catfish - Pluto TV Animals - Pluto TV Science - Pluto TV Sitcoms - Pluto TV Movies - Euronews Live - BBC History - BBC Travel - BBC Food - Moconomy - Fantasja - Heimatkino - INFAST - INWONDER - Qwest TV - Tastemade - XITE Hits - Tennis Channel Deutschland - Pluto TV Food - CURIOSITY Now - Top Filme - Rakuten TV - Strongman Champions League - Deutsches Kino - Rakuten TV - Crime Serien - Rakuten TV - FIFA+ - Reuters - Fashion TV - Teletubbies - SuperToons TV - Bloomberg TV+ - Trace Urban - MyTime Movie Network - wedo movies - Craction TV - Moviedome - Moviedome Family - Wilder Planet - Fabella - One Terra - Bergblick Free - 100% Weihnachten - Rakuten TV - Horse &amp; Country - Travelxp - Deluxe Deutschpop - Clubbing TV - Crime Mix - Qello Concerts by Stingray - Cine Mix - Vevo Pop - Spannung &amp; Emotionen - Comedy &amp; Shows - Stars in Gefahr - Red Bull TV - Terra X - Love the Planet - duckTV - Vevo Schlager Pop - Filmgold - Hell's Kitchen - The Wicked Tuna Channel - CNN - FRANCE 24 FAST - Kaminfeuer - Screen Green - Sport 1 Motor - Out TV - Sallys Welt - Motorvision Classic - Motorvision - DEFA TV - XITE Rock On - X-MAS Mix - ZDF kocht! - Deluxe Wintertime - Naruto - CNN FAST - Baywatch - More than Sports TV - World Poker Tour - Bares für Rares - Aniverse - The Asylum - FilmRise Classic TV - NBC News NOW - Forensic Files - FilmRise Action - Game Show Central - FailArmy - Outside - Law&amp;Crime - Stingray Naturescape - The Red Green Channel - WeatherSpy - CINEVAULT: Classics - The Preview Channel - Dry Bar Comedy - The Design Network - MagellanTV Now - FilmRise True Crime - Unsolved Mysteries - FilmRise Western - People Are Awesome - Kidoodle.TV - MHz Now - FilmRise Free Movies - The Pet Collective - INFAST - InWonder - Haunt TV - Always Funny Videos - Baywatch - Moonbug - Court TV - IMPACT Wrestling - beIN SPORTS XTRA - Crime Time - Nosey - TED - Gusto TV - Deal or No Deal - Operation Repo - Alien Nation by DUST - Journy - Waypoint TV - XITE Just Chill - Hell's Kitchen - Dr. G: Medical Examiner - XITE 80s Flashback - XITE 90s Throwback - Circle - This Old House - XITE Rock On - 21 Jump Street - LOL! Network - Fireplace 4K - The Jack Hanna Channel - Xplore - Top Gear - NHRA TV - Cowboy Way - MotorTrend FAST TV - CBC News Explore - Radio-Canada INFO - RetroCrush - Origin Sports - Bob Ross - HappyKids - Documentary+ - Supermarket Sweep - The Biggest Loser - Tastemade Home - Strawberry Shortcake - Bon Appétit - Divorce Court - Architectural Digest - Vevo '70s - Vevo '80s - Vevo 2K - Vevo Retro Rock - Vevo R&amp;B - Comedy Dynamics - Midnight Pulp - Homeful - Hollywire - Midsomer Murders - Cops - The Weather Network - Bob the Builder - Vevo '90s - Vevo Country - Vevo Hip-Hop - Vevo Pop - LEGO Channel - Gravitas Movies - Tastemade - Antiques Roadshow - FIFA+ - Vevo 2010s - BBC Food - BBC Home &amp; Garden - World Poker Tour - MAVTV Select - POWERNATION - XITE Country's Finest - XITE Hits - XITE Icons - Baby Einstein - Cheaters - CBC Comedy - Highway to Heaven - XITE Only Love - batteryPOP - pocket.watch - XITE Country Today - Pluto TV Motor - Sparkle Movies - Popflix - Pluto TV Animals - Pluto TV Food - The Guardian - Fashion TV - Planet Knowledge - Action Movies - Rakuten TV - Comedy Movies - Rakuten TV - Documentaries - Rakuten TV - Filmstream - People Are Awesome - Euronews Live - The Pet Collective - FailArmy - INTROUBLE - INFAST - INWONDER - Drama Movies - Rakuten TV - Kidoodle.TV - Travelxp - GoUSA TV - 5 Cops - 5 Exploring Britain - Pluto TV Action - Pluto TV Movies - Pluto TV Drama - Pluto TV Crime - Pluto TV Inside - Pluto TV Romance - Pluto TV Classic TV - MotorRacing - Qwest TV - Real Stories - History Hit - Horse &amp; Country - Romance Movies - Rakuten TV - Revry - 100% Christmas - Rakuten TV - YAAAS! - SuperToons TV - Bloomberg TV+ - Homicide Hunter - wedo movies - MyTime Movie Network - Real Crime - Deluxe Lounge HD - Wonder - Reuters - Teletubbies - Pluto TV Conspiracy - Pluto TV Paranormal - Tennis Channel International - PBS History - MovieSphere - INWILD - Gusto TV - Beano TV - Catfish - TalkTV - Real Wild - Choppertown - Qello Concerts by Stingray - NBC News NOW - PGA Tour - Are We There Yet? - GB News - Bridezillas - Clubbing TV - Ketchup TV - Moviedome - Shades of Black - XITE Hits - Survivor - McLeod's Daughters - Viaplay Xtra - Project Runway - Wild Planet - Comedy Dynamics - Deal or No Deal US - Tastemade - World Drama by ITV Studios - Shorts - Origin Sports - Hell's Kitchen - WaterBear - Trace Urban - Horizons - Now 80s - The Wicked Tuna Channel - Haunt TV - GREAT! movies - GREAT! Christmas - Radical Docs - LOL! Network - Bloomberg Originals - Homes Under the Hammer - Great British Menu - Adventure Earth - Vevo '90s &amp; '00s - American Idol - America's Got Talent - Entertainment Hub - Mythbusters - The Jamie Oliver Channel - Toon Goggles - WildEarth - Real Life - The LEGO Channel - Come Dine With Me - Vevo Pop - Strongman Champions League - Top Movies - Rakuten TV - Festive Hub - POP - Love the Planet - Inside Crime - Mech+ - Grjngo - Western Movies - Baywatch - World War TV - FRANCE 24 FAST - Love Pets - pocket.watch - Real Series - Rakuten TV - True Crime UK - FIFA+ - So…Real - Super Anime - Dennis and Gnasher - Smurf TV - Fireplace - The Chat Show Channel - CNN FAST - Vevo '70s &amp; '80s - Vevo Hip Hop &amp; R&amp;B - Red Bull TV - Pointless UK: 'Powered by Banijay' - World Poker Tour - Wipeout Xtra Powered by Banijay - Gigs - UKTV Play Full Throttle - UKTV Play Heroes - UKTV Play Laughs - UKTV Play Uncovered - Challenge - Sky Mix - Earth Touch - Eggheads - Tiny POP - CNBC - DATELINE 24/7 - True Lives - Crime Network - CNN - Trace UK - Masterchef UK: 'Powered by Banijay' - Comedy Hub - Vevo Christmas - Mystery TV - Rabbids Invasion - Crime &amp; Justice - MAVTV Motorsports Network - Qwest TV - INFAST - INWONDER - INWILD - Real Stories - History Hit - Wonder - Tennis Channel - Republic TV - Xplore - Jack Hanna - Toon Goggles - Real Wild - Real Crime - Motorvision.TV - Hollywire - Magellan TV Now - Billiard TV - Boxing TV - FIFA+ - Gusto TV - Bloomberg TV - Bloomberg Originals - MAVTV Motorsports Network - GoUSA TV - Film Stream - WeatherSpy - The Pet Collective - FailArmy - People are Awesome - INTROUBLE - Real Life - Nosey - Don't Tell The Bride - Fashion TV - Tastemade - Republic Bharat - True Crime Now - Discovery - Mastiii - WildEarth - Discovery HD - Animal Planet - Animal Planet HD - TLC HD - Investigation Discovery - ID-HD - Eurosport - Eurosport HD - Discovery Science - Discovery Turbo - Maiboli - NDTV 24X7 - NDTV India - Aaj Tak - Good News Today - India Today - NDTV Profit - NDTV GoodTimes - Balle Balle - 9X Tashan - 9X Jalwa - BEANI TV - FIGHT TV - Heritage - South Station - Cook &amp; Bake - Korean TV - Hooray Rhymes - Zee News - TLC - Discovery Kids - Discovery Tamil - 9X Music - 9X Jhakaas - HD TRAVEL - BEST ACTION TV - Hollywood Desi - Dabangg - Pitaara - BABY FIRST - Zee 24 Taas - Zee 24 Ghanta - Zee Business - ABP News - ABP Ananda - ABP Majha - ABP Asmita - Times Now Navbharat - CNBC AWAAZ - CNBC TV18 - CNN News18 - News18 India - TV9 Gujarati - Dangal TV - Bhojpuri Cinema - Pogo - CNN - TV9 Kannada - TV9 Telugu - TV9 Marathi - TV9 Bangla - News18 Gujarati - Zee 24 Kalak - WION - Divya - Cartoon Network - The Movie Club - News9 Live - TV9 Bharatvarsh - PGA Tour - Enterr10 Bangla - Tu Cine - Home.Made.Nation - NEW KPOP - Bloomberg TV+ UHD - Documentary+ - Cheddar News - Sony Canal Competencias - Yahoo Finance - Estrella TV - The LEGO Channel - Kitchen Nightmares - Mixible - Fireplace 4K - PBS Digital Studios - XITE Just Chill - XITE Rock On - K-Stories by CJ ENM - Hell's Kitchen - REELZ Famous &amp; Infamous - Dr. G: Medical Examiner - XITE 90s Throwback - CINEVAULT: Classics - Telemundo Al Día - Million Dollar Listing - The Rotten Tomatoes Channel - Project Runway - WN San Francisco - All-Out Reality - Dabl - CBS Sports HQ - Dateline 24/7 - Holiday Movie Favorites by Lifetime - Ax Men - Crimes Cults Killers - Modern Marvels Presented by History - Torque - UnXplained Zone - Moonbug - CNN Headlines - ViX Novelas de romance - Sky News - Crime ThrillHer - Deal Zone - 21 Jump Street - XITE 80s Flashback - GOLFPASS - Ice Road Truckers - Perform - T2 - Antiques Roadshow - World’s Most Evil Killers - Shades of Black - This Old House Makers - LOL! Network - Fear Zone - The Jack Hanna Channel - ViX JaJaJa - ViX Villanos de Novela - ViX Grandes Parejas - TED - Rovr Pets - Top Gear - NBC Bay Area News - NHRA TV - Cowboy Way - Tastemade Home - MotorTrend FAST TV - MyTime Movie Network - Dance Moms - Duck Dynasty - CBC News International - Just for Laughs GAGS - Localish - Haunt TV - ABC7 Bay Area - Strawberry Shortcake - Bob the Builder - Jamie Oliver - The Price is Right - Telemundo California - Operation Repo - CNN RESUMEN - Home Refresh - Estrella Games - RetroCrush - Canela.TV - ABC News Live - Military Heroes - HappyKids - Supermarket Sweep - Bon Appétit - ALTER - Cold Case Files - Matched Married Meet - The Biggest Loser - BET Pluto TV - ION - ION Plus - Divorce Court - Bounce XL - Vevo '70s - Vevo '80s - MSG SportsZone - ALLBLK Gems - FOX Weather - Degrassi - Gravitas Movies - The Bob Ross Channel - DraftKings Network - Midnight Pulp - Dove Channel - Tastemade Travel - BUZZR - Homeful - The Walking Dead Universe - All Weddings WE tv - OuterSphere - Vevo Holiday - History &amp; Warfare Now - Origin Sports - Outdoor America - Shout! Factory - The Design Network - BBC Earth - Storage Wars: LA - I Survived… - World Poker Tour - Noticias Univision 24/7 - Aquí y Ahora - Zona TUDN - Rebelde - MAVTV Select - Ebony TV by Lionsgate - POWERNATION - Cine Retro - Galanes - Pequenos Gigantes - Como Dice el Dicho - Cine de Oro - CBS News Bay Area - Swamp People - Forged In Fire - FOX 2 San Francisco - Scripps News - Vevo '90s - Vevo Retro Rock - XITE Hits - XITE Icons - XITE Country Today - XITE Nuevo Latino - XITE Only Love - XITE Siempre Latino - XITE Country's Finest - batteryPOP - Sensical Jr - Barney and Friends - Rainbow Ruby - Rev and Roll - Sensical Makers - HooplaKidzTV - Sonic The Hedgehog - Stingray Hot Country - Stingray Remember the 80s - Stingray Flashback 70s - Stingray Today's Latin Pop - Stingray Hip Hop - Stingray Easy Listening - Stingray Spa - Teletubbies - Stingray Today's K-Pop - Stingray Romance Latino - Stingray Greatest Holiday Hits - Qello Concerts by Stingray - Stingray DJAZZ - ZenLIFE by Stingray - Cheaters - Stingray Holidayscapes - AMC en Español - Comedy Dynamics - Tastemade - Portlandia - All Reality WE tv - pocket.watch - Billiard TV - Ryan and Friends - FIFA+ - Pursuit UP - Bring It! - Mountain Men - MovieSphere - Las 3 Marías - At Home with Family Handyman - ACC Digital Network - Alone By History - Slugterra - Stingray Smooth Jazz - Stingray Nothin' But 90s - Stingray Classic Rock - TMZ - 4UV - Conan O'Brien TV - Vevo 2010s - Little Women: LA - Hallmark Movies &amp; More - XITE R&amp;B Classic Jams - Baby Einstein - Stingray Classica - XITE Celebrates - Always Funny Videos - America's Test Kitchen - Anime All day - Backstage - Baywatch - BBC Food - BBC Home &amp; Garden - beIN SPORTS XTRA - Bloomberg Originals - Brat TV - Cars - CBS News - Chicken Soup for the Soul - Cine Romantico - CINEVAULT: 80s - CINEVAULT: Westerns - Circle - Clarity 4K - Court TV - Crime 360 - Dallas Cowboys Cheer - Danger TV - Deal or No Deal - Drama Life - Dry Bar Comedy - Alien Nation by DUST - ElectricNOW - Estrella News - FailArmy - Family Ties - Fear Factor - FilmRise Action - FilmRise Free Movies - FilmRise Western - Forensic Files - FOX SOUL - fubo Sports Network - Game Show Central - Gusto TV - Highway to Heaven - Heartland - Hollywire - Hungry - IGN - IMPACT Wrestling - INFAST - InWonder - Journy - Kidoodle.TV - Law &amp; Crime - LiveNOW from FOX - Loupe 4K - Love &amp; Hip Hop - Love Nature 4K - Lucky Dog - Magellan TV Now - Maverick Black Cinema - MHz Now - Midsomer Murders - Pluto TV Pixel World - MTV Pluto TV - NBC LX Home - NBC News NOW - NEW KMOVIES - Newsmax TV - Nick Pluto TV - Nosey - Outside - Pac-12 Insider - Paramount Movie Channel - People Are Awesome - Pluto TV Fantastic - Pluto TV Westerns - Real America's Voice - Revry - RiffTrax - Samsung Wild Life - Xtreme Outdoor Presented by HISTORY - Sony Canal Comedias - Sony Canal Novelas - SportsGrid - Stadium - Stingray Naturescape - SURF NOW TV - TG Junior - The Asylum - The Challenge - The New Detectives - The Pet Collective - The Preview Channel - This Old House - Tiny House Nation - TODAY All Day - Toon Goggles - TV Land Drama - TV Land Sitcoms - TYT Network - Unidentified - Unsolved Mysteries - USA Today - Vevo 2K - Vevo Country - Vevo Hip-Hop - Vevo Latino - Vevo Pop - Vevo R&amp;B - VICE - Waypoint TV - WeatherSpy - Wild 'N Out - Wipeout Xtra - Xplore - ZooMoo - Top Gear en Español - Bloomberg Originals - Estilo y Vida – Rakuten TV - Pluto TV Cine Estelar - MTV Catfish - Comedia Made in Spain - MTV Cribs - Pluto TV Cocina - Pluto TV Animakids - The Pet Collective - Fashion TV - Comedias - Rakuten TV - Dramas - Rakuten TV - Documentales - Rakuten TV - People Are Awesome - Euronews en directo - Pluto TV Kids - Acción - Rakuten TV - INFAST - Qwest TV - MTV Originals - Doctor Who - BBC Drama - FailArmy - Yu-Gi-Oh! - ¡Hola! Play - Deluxe Lounge HD - MyTime Movie Network - Tastemade - Cortos - 100% Navidad - Rakuten TV - Caillou - Bloomberg TV+ - Los Asesinatos de Midsomer - Las Reglas Del Juego - iCarly - El Comisario - Encantador de Perros - Trace Sportstars - BelAir TV - Tu Cine - Trace Latina - Trace Urban - Nature Time - SuperToons TV - Bob Esponja - Rugrats - Surf Channel - Stormcast Novelas - Flash - WildEarth - Clubbing TV - Runtime - NBC News NOW - El Confidencial - Vive Kanal D Drama - Televisión Consciente - Trailers - Vevo Pop - Vevo Latino - Grjngo - Películas Del Oeste - Bigtime - Películas Gratis - Películas Top - Rakuten TV - Just For Laughs - FIFA+ - Crimen - Rakuten TV - Cines Verdi TV - Cine Feel Good – Verdi TV - Vivaldi TV - Ideas en 5 Minutos - MyPadel TV - Vivir con Perros - Vivir con Gatos - Pocoyó - La 1 - La 2 - Clan - 24H - duckTV - Love the Planet - Teen Vee - GoUSA TV - Cine Español - Rakuten TV - Películas Románticas - Rakuten TV - Ticker News - El País - Dark Matter TV - Todo Novelas - WaterBear - Teledeporte - Cine Friki - Runtime Romance - FRANCE 24 FAST - Baywatch – Los Vigilantes de la Playa - Corazón - Azteca Internacional - Travelxp - Mi chimenea - Rabbids : La invasion - Sol Música - Tennis Channel - World Poker Tour - Motorvision - Heidi &amp; Maya - Negocios TV - La Liga - DATELINE 24/7 - Vevo Navidad - Runtime Comedia - CNBC - Runtime Acción - Pitufo TV - CNN FAST - Cine Clásico - Vevo '70s &amp; '80s - Pluto TV Ciné - MTV Classics - Bob L'eponge - Fashion TV - Films d'action - Rakuten TV - Comédies - Rakuten TV - Films dramatiques - Rakuten TV - Documentaires - Rakuten TV - Pluto TV Kids Séries - MTV Catfish - iCarly - Pluto TV Polar - Euronews en direct - BBC Drama - Brefcinema - Deluxe Lounge HD - The Boat Show - Qwest TV - ADN - UniversCiné - MyTime Movie Network - Xilam TV - Doctor Who - Wild Side TV - Televisa TeleNovelas - Reuters - 100% Noël - Rakuten TV - Caillou - SuperToons TV - Stormcast Novelas - Bloomberg TV+ - Top Films - Rakuten TV - Juste Pour Rire - FIFA+ - Yu-Gi-Oh! - Pluto TV Cuisine - Tortues Ninja - Wellbeing TV - BelAir TV - People Are Awesome - FailArmy - Films Romantiques - Rakuten TV - Crime - Rakuten TV - Travelxp - Clubbing TV - Chefclub TV - Jellytoon - Scream'IN - Nature Time - Ciné Nanar - Gong - Homicide - Tahiti TV - Runtime - Emotion'L - Motus - Fréquence Novelas - Émotion - Y'a que la vérité qui compte - Le Figaro Live - Vevo Pop - Vevo Hip-Hop &amp; R&amp;B - Ciné Prime - Secret Story - MasterChef - Echappées belles &amp; co - MGG FAST - Love the Planet - Destination Nature - L'Equipe Live 1 - L'Equipe Live 2 - Génération Sitcoms - Alerte à Malibu - A prendre ou à laisser - Les 30 histoires - Trailers - Tele.Novela - Un Village Français - Les filles d'à côté - Enquêtes de Choc - Nashville Channel - Le Tigre - Vialma - Au coin de feu - Les Z'amours - Le meilleur d'Arthur - Qui veut gagner des millions ? - Charlotte aux Fraises - Vevo '90s &amp; '00s - Family Club - Les Lapins Crétins: Invasion - Maison &amp; Travaux - World Poker Tour - Motorvision - Drive TV - Allociné - Reportages by Spica - Le Meilleur de la TV Réalité - Les Anges - Popcorn - Motor Racing - L'effet papillon - Passion Novelas - Ciné Western - Degrassi - Top Santé - Plus belle la vie - Vevo Noël - Scènes de Crime - 100% Docs - Wasabi - Culture Pub - CNN FAST - Ardivision - Les secrets de nos régions - BBC Drama - Catfish - Super! iCarly - Pluto TV Real Life - Pluto TV Serie - Serie Teen - Pluto TV Film Romantici - Super! Pop - The Pet Collective - Fashion TV - Commedia - Rakuten TV - Drama - Rakuten TV - Documentari - Rakuten TV - People Are Awesome - Euronews in diretta - Motor1TV - Pluto TV Film - Film d'azione - Rakuten TV - Super! Spongebob - FailArmy - Canale Europa - Yamato Animation - Teletubbies - Deluxe Lounge HD - HorizonSports - Qwest TV - RADIO ITALIA TREND - Sportitalia - SuperToons TV - MONDO TV KIDS - SportOutdoor.tv - Bizzarro Movies - Bloomberg TV+ - Vivaldi TV - Ticker News - BelAir TV - Trace Urban - Trace Latina - WildEarth - Trailers - Televisa Telenovelas - 100% Natale - Rakuten TV - CG - CINEMA d'Autore - Cinema Segreto - 5-Minuti Creativi - Wellbeing TV - WaterBear - Clubbing TV - Italian Fishing TV - Doctor Who - Giornale Radio TV - WP - Dark Matter TV - Brindiamo! - Cmusic - Montagna! - Alta Tensione - Smile - Grandi Nomi - Explorer HD Channel - Top Film - Rakuten TV - Risate all'italiana - Romance - Rakuten TV - Rock TV - Vevo Pop - Pluto TV Film Commedia - Pluto TV Film Azione - Consulenze Illegali - TVPlay - Teen Vee - NBC News NOW - duckTV - Solocalcio - Love the Planet - Film Al Femminile - Wedo Movies - Velvet - Full Moon - Hip Hop TV - Pluto TV Reality - Grandi Documentari - Wedo Big Stories - Vevo '90s &amp; '00s - Move - Italian Active Lives - Serially Drama - Just For Laughs - Serie Crime - Rakuten TV - FIFA+ - Yu-Gi-Oh! - Serially Crime - House of Docs - FRANCE 24 FAST - Travel &amp; Living by DOVE - Baywatch - U-Trend - Travelxp - CineMadame - La 7 - Serie TV asiatiche d’azione by Liv TV - Lifestyle by LEI - Vevo '70s &amp; '80s - RBN - Cinema Excelsior - Le Vite Degli Altri - Andromeda - Autostop per il Cielo - Mutant X - Heidi &amp; Maya - GCTV (Global Champions TV) - CNBC - DATELINE 24/7 - Vevo Natale - East is East - Dolomiti Life TV - Grjngo- Film Western - The Boat Show - Caminetto - CNN FAST - World Poker Tour - Yu yu Hakusho - Film e Sorrisi - 현대홈쇼핑 - 현대홈쇼핑+Shop - MBC every1 어서와 한국은 처음이지? - 코코비TV - MBC every1 별순검 - Bloomberg TV+ UHD - Bloomberg Originals - SBS 런닝맨 - SBS 미운 우리 새끼 - SBS 순풍산부인과 - SBS 펜트하우스 - SBS 동상이몽2 - 너는 내 운명 - SBS 나는 솔로 - SBS 백종원의 골목식당 - SBS 정글의 법칙 - SBS 순간포착 세상에 이런일이 - SBS 패밀리가 떴다 - SBS 궁금한 이야기 Y - SBS 그것이 알고싶다 - SBS 생활의 달인 - MBC 무한도전 - MBC 돈꽃 - MBC 옷소매 붉은끝동 - 초록뱀미디어 K-STAR 골프 - SBS 편의점 샛별이 - SBS 불타는 청춘 - MBC 심야괴담회 - MBC 선을 넘는 녀석들 - SBS 스토브리그 - SBS TV 동물농장 - SBS 빽드 - MBC 나혼자산다 - MBC 지붕뚫고 하이킥 - 투니버스 뱀파이어소녀 달자 - tvN 대탈출3 - MBC 거침없이 하이킥 - 꽃보다 남자 - NEW MOVIES - OGN 스타리그 - 맛있는 녀석들 - TV CHOSUN 식객 허영만의 백반기행 - KBS Joy 무엇이든 물어보살 - World Billiards TV - tvN 응답하라 전 시즌 모아보기 - tvN 갯마을 차차차 - tvN 삼시세끼 산촌편 - Mnet 스트릿 우먼 파이터 - Mnet 스트릿 우먼 파이터 2 HOT CLIP - 심리 읽어드립니다 - tvN 호텔 델루나 - tvN 스트리트 푸드 파이터 1~2 - tvN 미스터 션샤인 - tvN 사랑의 불시착 - tvN 빈센조 - tvN 스물다섯 스물하나 - tvN 여신강림 - 로보카폴리 TV - 고독한 미식가 - 도라마코리아 - MBN 나는 자연인이다 - MBN 휴먼다큐 사노라면 - MBN 속풀이쇼 동치미 - MBN 돌싱글즈 - iHQ 돈쭐내러 왔습니다 - 전우치 - KBS Joy 연애의 참견 - 채널A 나만 믿고 따라와 도시어부 - 채널A 이제 만나러 갑니다 - E채널 토요일은 밥이 좋아 - KBS 개는 훌륭하다 - KBS 쌈마이웨이 - KBS 1박2일 시즌1 - tvN 작은 아씨들 - tvN 슬기로운 의사생활 시즌2 - JTBC 괴물 - JTBC 힘쎈여자 도봉순 - JTBC 효리네 민박 시즌1 - JTBC 골프 - JTBC 최강야구 - JTBC 비긴어게인 - Mnet 스트릿댄스 걸스 파이터 + Zㅏ때는 말이야 - MBC 안싸우면 다행이야 - MBC 나는 가수다 - ch.핑크퐁 - tvN 어쩌다 사장 전 시즌 몰아보기 - 사피엔스스튜디오 - 역사 읽어드립니다 - tvN 신서유기 6 - tvN 놀라운 토요일 하이라이트 - TV CHOSUN 국가가 부른다 - Animax 반지의 비밀일기 - 채널A 요즘 육아 금쪽같은 내새끼 - JTBC 크라임씬 - JTBC 뉴스 - JTBC 톡파원25시 - JTBC 품위있는 그녀 - tvN 환혼 - 연합뉴스TV - FIFA+ - 뽀요TV - 우리의식탁 - YTN - TV조선 빨간 풍선 - TV조선 골프왕 - essential; by Bugs - PLAYY 영화 - PLAYY 어워드특집 - tvN 유 퀴즈 온 더 블럭 HOT CLIP - 글로벌 뉴스 by LeadStory - PGA Tour - 씨네21+ - TV조선 미스터트롯 영웅의 탄생 - TED - + + + Deluxe Lounge HD + Focus TV + Netzkino + Tierwelt + Xplore + Hip Trips + just.fishing + Sportdigital Free + Just Cooking + SPIEGEL TV + GoldStar TV + Charlie + Grjngo + Tempora + auto motor und sport + MTV Pluto TV + Teen Nick + SpongeBob Schwammkopf + Ice Pilots + The Pet Collective + Actionfilme - Rakuten TV + Drama Filme - Rakuten TV + Nick Pluto TV + iCarly + MTV Teen Mom + FailArmy + Komödien - Rakuten TV + Dokumentarfilme - Rakuten TV + Pluto TV Serie + Pluto TV Paranormal + Pluto TV Animals + Pluto TV Food + Pluto TV Science + Pluto TV Sitcoms + Comedy Central Pluto TV + Pluto TV Movies + Comedy Central Made in Germany + MTV Catfish + People Are Awesome + Euronews Live + Travelxp + Yu-Gi-Oh! + Fantasja + Fabella + One Terra + Bergblick Free + Deluxe Deutschpop + Qwest TV + INFAST + Tastemade + Tennis Channel + Zee One + FIFA+ + CURIOSITY Now + SPIEGEL TV Konflikte + Strongman Champions League + Deutsches Kino - Rakuten TV + Bloomberg TV+ + MyTime Movie Network + wedo movies + Horse &amp; Country + Wilder Planet + Fashion TV + Top Filme - Rakuten TV + Moconomy + Heimatkino + INWONDER + XITE Hits + Crime Serien - Rakuten TV + Teletubbies + Comedy &amp; Shows + Craction TV + Moviedome + 100% Weihnachten - Rakuten TV + Moviedome Family + Clubbing TV + Crime Mix + Qello Concerts by Stingray + Stars in Gefahr + The Wicked Tuna Channel + Naruto + Spannung &amp; Emotionen + Filmgold + CNN + FRANCE 24 FAST + Kaminfeuer + Cine Mix + Red Bull TV + Sport 1 Motor + Out TV + Hell's Kitchen + Sallys Welt + Baywatch + More than Sports TV + Motorvision Classic + Motorvision + Terra Mater + X-MAS Mix + Deluxe Wintertime + CNN FAST + World Poker Tour + Aniverse + Deluxe Lounge HD + Focus TV + Netzkino + Tierwelt + Xplore + Hip Trips + just.fishing + Sportdigital Free + Just Cooking + SPIEGEL TV + GoldStar TV + Charlie + Grjngo + Tempora + auto motor und sport + MTV Pluto TV + Nick Pluto TV + Teen Nick + iCarly + Pluto TV Serie + Ice Pilots + MTV Teen Mom + The Pet Collective + WP + Drama Filme - Rakuten TV + Dokumentarfilme - Rakuten TV + SpongeBob Schwammkopf + Pluto TV Paranormal + FailArmy + Actionfilme - Rakuten TV + Komödien - Rakuten TV + Pluto TV Animals + Pluto TV Food + Pluto TV Science + Pluto TV Sitcoms + Comedy Central Pluto TV + Pluto TV Movies + Comedy Central Made in Germany + MTV Catfish + People Are Awesome + Travelxp + Fashion TV + Travelxp [FR] + Top Filme - Rakuten TV + Fabella + One Terra + Heimatkino + Bergblick Free + Deluxe Deutschpop + Qwest TV + INFAST + INWONDER + Tastemade + ADN [FR] + SportOutdoor.tv [IT] + FIFA+ + Strongman Champions League + Teletubbies + Risate all'italiana + Bloomberg TV+ + BelAir TV [FR] + Wellbeing TV [FR] + wedo movies + Craction TV + Wilder Planet + Moviedome Family + Horse &amp; Country + Moconomy + Tennis Channel + Deutsches Kino - Rakuten TV + Crime Serien - Rakuten TV + Euronews Live + Fantasja + 100% Weihnachten - Rakuten TV + Moviedome + Clubbing TV + Emotion'L [FR] + Qello Concerts by Stingray + Chefclub TV [FR] + Ciné Nanar [FR] + Motus [FR] + Spannung &amp; Emotionen + Comedy &amp; Shows + Scream'IN [FR] + Y'a que la vérité qui compte [FR] + Stars in Gefahr + Crime Mix + MyTime Movie Network + Trace Latina [FR] + Gong [FR] + Fréquence Novelas [FR] + Ciné Prime [FR] + Enquêtes de Choc [FR] + Filmgold + Homicide [FR] + Naruto + Destination Nature [FR] + Émotion [FR] + Le Figaro Live [FR] + The Wicked Tuna Channel + Echappées belles &amp; co [FR] + Grandi Documentari - Wedo Big Stories [IT] + Nature Time [FR] + Génération Sitcoms [FR] + Les filles d'à côté [FR] + Film Al Femminile - Wedo Movies [IT] + CNN + Le Tigre [FR] + Vialma [FR] + FRANCE 24 FAST + CURIOSITY Now + SPIEGEL TV Konflikte + Yu-Gi-Oh! + CNN FAST + Screen Green + Alerte à Malibu [FR] + Sport 1 Motor + Out TV + Sallys Welt + Baywatch + More than Sports TV + Motorvision Classic + Terra Mater + World Poker Tour + X-MAS Mix + Plus belle la vie [FR] + Culture Pub [FR] + Hell's Kitchen + Motorvision + Zee One + Deluxe Wintertime + Aniverse + Kaminfeuer + Deluxe Lounge HD + Focus TV + Netzkino + Xplore + Tierwelt + Hip Trips + just.fishing + Sportdigital Free + Nick Pluto TV + Teen Nick + iCarly + Pluto TV Serie + Pluto TV Paranormal + SPIEGEL TV Konflikte + Yu-Gi-Oh! + Zee One + FailArmy + The Pet Collective + Actionfilme - Rakuten TV + Komödien - Rakuten TV + Drama Filme - Rakuten TV + People Are Awesome + MTV Pluto TV + Ice Pilots + MTV Teen Mom + Terra Mater + Dokumentarfilme - Rakuten TV + SpongeBob Schwammkopf + Just Cooking + SPIEGEL TV + GoldStar TV + Charlie + Grjngo + Tempora + auto motor und sport + Comedy Central Pluto TV + Comedy Central Made in Germany + MTV Catfish + Pluto TV Animals + Pluto TV Science + Pluto TV Sitcoms + Pluto TV Movies + Euronews Live + BBC History + BBC Travel + BBC Food + Moconomy + Fantasja + Heimatkino + INFAST + INWONDER + Qwest TV + Tastemade + XITE Hits + Tennis Channel Deutschland + Pluto TV Food + CURIOSITY Now + Top Filme - Rakuten TV + Strongman Champions League + Deutsches Kino - Rakuten TV + Crime Serien - Rakuten TV + FIFA+ + Reuters + Fashion TV + Teletubbies + SuperToons TV + Bloomberg TV+ + Trace Urban + MyTime Movie Network + wedo movies + Craction TV + Moviedome + Moviedome Family + Wilder Planet + Fabella + One Terra + Bergblick Free + 100% Weihnachten - Rakuten TV + Horse &amp; Country + Travelxp + Deluxe Deutschpop + Clubbing TV + Crime Mix + Qello Concerts by Stingray + Cine Mix + Vevo Pop + Spannung &amp; Emotionen + Comedy &amp; Shows + Stars in Gefahr + Red Bull TV + Terra X + Love the Planet + duckTV + Vevo Schlager Pop + Filmgold + Hell's Kitchen + The Wicked Tuna Channel + CNN + FRANCE 24 FAST + Kaminfeuer + Screen Green + Sport 1 Motor + Out TV + Sallys Welt + Motorvision Classic + Motorvision + DEFA TV + XITE Rock On + X-MAS Mix + ZDF kocht! + Deluxe Wintertime + Naruto + CNN FAST + Baywatch + More than Sports TV + World Poker Tour + Bares für Rares + Aniverse + The Asylum + FilmRise Classic TV + NBC News NOW + Forensic Files + FilmRise Action + Game Show Central + FailArmy + Outside + Law&amp;Crime + Stingray Naturescape + The Red Green Channel + WeatherSpy + CINEVAULT: Classics + The Preview Channel + Dry Bar Comedy + The Design Network + MagellanTV Now + FilmRise True Crime + Unsolved Mysteries + FilmRise Western + People Are Awesome + Kidoodle.TV + MHz Now + FilmRise Free Movies + The Pet Collective + INFAST + InWonder + Haunt TV + Always Funny Videos + Baywatch + Moonbug + Court TV + IMPACT Wrestling + beIN SPORTS XTRA + Crime Time + Nosey + TED + Gusto TV + Deal or No Deal + Operation Repo + Alien Nation by DUST + Journy + Waypoint TV + XITE Just Chill + Hell's Kitchen + Dr. G: Medical Examiner + XITE 80s Flashback + XITE 90s Throwback + Circle + This Old House + XITE Rock On + 21 Jump Street + LOL! Network + Fireplace 4K + The Jack Hanna Channel + Xplore + Top Gear + NHRA TV + Cowboy Way + MotorTrend FAST TV + CBC News Explore + Radio-Canada INFO + RetroCrush + Origin Sports + Bob Ross + HappyKids + Documentary+ + Supermarket Sweep + The Biggest Loser + Tastemade Home + Strawberry Shortcake + Bon Appétit + Divorce Court + Architectural Digest + Vevo '70s + Vevo '80s + Vevo 2K + Vevo Retro Rock + Vevo R&amp;B + Comedy Dynamics + Midnight Pulp + Homeful + Hollywire + Midsomer Murders + Cops + The Weather Network + Bob the Builder + Vevo '90s + Vevo Country + Vevo Hip-Hop + Vevo Pop + LEGO Channel + Gravitas Movies + Tastemade + Antiques Roadshow + FIFA+ + Vevo 2010s + BBC Food + BBC Home &amp; Garden + World Poker Tour + MAVTV Select + POWERNATION + XITE Country's Finest + XITE Hits + XITE Icons + Baby Einstein + Cheaters + CBC Comedy + Highway to Heaven + XITE Only Love + batteryPOP + pocket.watch + XITE Country Today + Pluto TV Motor + Sparkle Movies + Popflix + Pluto TV Animals + Pluto TV Food + The Guardian + Fashion TV + Planet Knowledge + Action Movies - Rakuten TV + Comedy Movies - Rakuten TV + Documentaries - Rakuten TV + Filmstream + People Are Awesome + Euronews Live + The Pet Collective + FailArmy + INTROUBLE + INFAST + INWONDER + Drama Movies - Rakuten TV + Kidoodle.TV + Travelxp + GoUSA TV + 5 Cops + 5 Exploring Britain + Pluto TV Action + Pluto TV Movies + Pluto TV Drama + Pluto TV Crime + Pluto TV Inside + Pluto TV Romance + Pluto TV Classic TV + MotorRacing + Qwest TV + Real Stories + History Hit + Horse &amp; Country + Romance Movies - Rakuten TV + Revry + 100% Christmas - Rakuten TV + YAAAS! + SuperToons TV + Bloomberg TV+ + Homicide Hunter + wedo movies + MyTime Movie Network + Real Crime + Deluxe Lounge HD + Wonder + Reuters + Teletubbies + Pluto TV Conspiracy + Pluto TV Paranormal + Tennis Channel International + PBS History + MovieSphere + INWILD + Gusto TV + Beano TV + Catfish + TalkTV + Real Wild + Choppertown + Qello Concerts by Stingray + NBC News NOW + PGA Tour + Are We There Yet? + GB News + Bridezillas + Clubbing TV + Ketchup TV + Moviedome + Shades of Black + XITE Hits + Survivor + McLeod's Daughters + Viaplay Xtra + Project Runway + Wild Planet + Comedy Dynamics + Deal or No Deal US + Tastemade + World Drama by ITV Studios + Shorts + Origin Sports + Hell's Kitchen + WaterBear + Trace Urban + Horizons + Now 80s + The Wicked Tuna Channel + Haunt TV + GREAT! movies + GREAT! Christmas + Radical Docs + LOL! Network + Bloomberg Originals + Homes Under the Hammer + Great British Menu + Adventure Earth + Vevo '90s &amp; '00s + American Idol + America's Got Talent + Entertainment Hub + Mythbusters + The Jamie Oliver Channel + Toon Goggles + WildEarth + Real Life + The LEGO Channel + Come Dine With Me + Vevo Pop + Strongman Champions League + Top Movies - Rakuten TV + Festive Hub + POP + Love the Planet + Inside Crime + Mech+ + Grjngo - Western Movies + Baywatch + World War TV + FRANCE 24 FAST + Love Pets + pocket.watch + Real Series - Rakuten TV + True Crime UK + FIFA+ + So…Real + Super Anime + Dennis and Gnasher + Smurf TV + Fireplace + The Chat Show Channel + CNN FAST + Vevo '70s &amp; '80s + Vevo Hip Hop &amp; R&amp;B + Red Bull TV + Pointless UK: 'Powered by Banijay' + World Poker Tour + Wipeout Xtra Powered by Banijay + Gigs + UKTV Play Full Throttle + UKTV Play Heroes + UKTV Play Laughs + UKTV Play Uncovered + Challenge + Sky Mix + Earth Touch + Eggheads + Tiny POP + CNBC + DATELINE 24/7 + True Lives + Crime Network + CNN + Trace UK + Masterchef UK: 'Powered by Banijay' + Comedy Hub + Vevo Christmas + Mystery TV + Rabbids Invasion + Crime &amp; Justice + MAVTV Motorsports Network + Qwest TV + INFAST + INWONDER + INWILD + Real Stories + History Hit + Wonder + Tennis Channel + Republic TV + Xplore + Jack Hanna + Toon Goggles + Real Wild + Real Crime + Motorvision.TV + Hollywire + Magellan TV Now + Billiard TV + Boxing TV + FIFA+ + Gusto TV + Bloomberg TV + Bloomberg Originals + MAVTV Motorsports Network + GoUSA TV + Film Stream + WeatherSpy + The Pet Collective + FailArmy + People are Awesome + INTROUBLE + Real Life + Nosey + Don't Tell The Bride + Fashion TV + Tastemade + Republic Bharat + True Crime Now + Discovery + Mastiii + WildEarth + Discovery HD + Animal Planet + Animal Planet HD + TLC HD + Investigation Discovery + ID-HD + Eurosport + Eurosport HD + Discovery Science + Discovery Turbo + Maiboli + NDTV 24X7 + NDTV India + Aaj Tak + Good News Today + India Today + NDTV Profit + NDTV GoodTimes + Balle Balle + 9X Tashan + 9X Jalwa + BEANI TV + FIGHT TV + Heritage + South Station + Cook &amp; Bake + Korean TV + Hooray Rhymes + Zee News + TLC + Discovery Kids + Discovery Tamil + 9X Music + 9X Jhakaas + HD TRAVEL + BEST ACTION TV + Hollywood Desi + Dabangg + Pitaara + BABY FIRST + Zee 24 Taas + Zee 24 Ghanta + Zee Business + ABP News + ABP Ananda + ABP Majha + ABP Asmita + Times Now Navbharat + CNBC AWAAZ + CNBC TV18 + CNN News18 + News18 India + TV9 Gujarati + Dangal TV + Bhojpuri Cinema + Pogo + CNN + TV9 Kannada + TV9 Telugu + TV9 Marathi + TV9 Bangla + News18 Gujarati + Zee 24 Kalak + WION + Divya + Cartoon Network + The Movie Club + News9 Live + TV9 Bharatvarsh + PGA Tour + Enterr10 Bangla + Tu Cine + Home.Made.Nation + NEW KPOP + Bloomberg TV+ UHD + Documentary+ + Cheddar News + Sony Canal Competencias + Yahoo Finance + Estrella TV + The LEGO Channel + Kitchen Nightmares + Mixible + Fireplace 4K + PBS Digital Studios + XITE Just Chill + XITE Rock On + K-Stories by CJ ENM + Hell's Kitchen + REELZ Famous &amp; Infamous + Dr. G: Medical Examiner + XITE 90s Throwback + CINEVAULT: Classics + Telemundo Al Día + Million Dollar Listing + The Rotten Tomatoes Channel + Project Runway + WN San Francisco + All-Out Reality + Dabl + CBS Sports HQ + Dateline 24/7 + Holiday Movie Favorites by Lifetime + Ax Men + Crimes Cults Killers + Modern Marvels Presented by History + Torque + UnXplained Zone + Moonbug + CNN Headlines + ViX Novelas de romance + Sky News + Crime ThrillHer + Deal Zone + 21 Jump Street + XITE 80s Flashback + GOLFPASS + Ice Road Truckers + Perform + T2 + Antiques Roadshow + World’s Most Evil Killers + Shades of Black + This Old House Makers + LOL! Network + Fear Zone + The Jack Hanna Channel + ViX JaJaJa + ViX Villanos de Novela + ViX Grandes Parejas + TED + Rovr Pets + Top Gear + NBC Bay Area News + NHRA TV + Cowboy Way + Tastemade Home + MotorTrend FAST TV + MyTime Movie Network + Dance Moms + Duck Dynasty + CBC News International + Just for Laughs GAGS + Localish + Haunt TV + ABC7 Bay Area + Strawberry Shortcake + Bob the Builder + Jamie Oliver + The Price is Right + Telemundo California + Operation Repo + CNN RESUMEN + Home Refresh + Estrella Games + RetroCrush + Canela.TV + ABC News Live + Military Heroes + HappyKids + Supermarket Sweep + Bon Appétit + ALTER + Cold Case Files + Matched Married Meet + The Biggest Loser + BET Pluto TV + ION + ION Plus + Divorce Court + Bounce XL + Vevo '70s + Vevo '80s + MSG SportsZone + ALLBLK Gems + FOX Weather + Degrassi + Gravitas Movies + The Bob Ross Channel + DraftKings Network + Midnight Pulp + Dove Channel + Tastemade Travel + BUZZR + Homeful + The Walking Dead Universe + All Weddings WE tv + OuterSphere + Vevo Holiday + History &amp; Warfare Now + Origin Sports + Outdoor America + Shout! Factory + The Design Network + BBC Earth + Storage Wars: LA + I Survived… + World Poker Tour + Noticias Univision 24/7 + Aquí y Ahora + Zona TUDN + Rebelde + MAVTV Select + Ebony TV by Lionsgate + POWERNATION + Cine Retro + Galanes + Pequenos Gigantes + Como Dice el Dicho + Cine de Oro + CBS News Bay Area + Swamp People + Forged In Fire + FOX 2 San Francisco + Scripps News + Vevo '90s + Vevo Retro Rock + XITE Hits + XITE Icons + XITE Country Today + XITE Nuevo Latino + XITE Only Love + XITE Siempre Latino + XITE Country's Finest + batteryPOP + Sensical Jr + Barney and Friends + Rainbow Ruby + Rev and Roll + Sensical Makers + HooplaKidzTV + Sonic The Hedgehog + Stingray Hot Country + Stingray Remember the 80s + Stingray Flashback 70s + Stingray Today's Latin Pop + Stingray Hip Hop + Stingray Easy Listening + Stingray Spa + Teletubbies + Stingray Today's K-Pop + Stingray Romance Latino + Stingray Greatest Holiday Hits + Qello Concerts by Stingray + Stingray DJAZZ + ZenLIFE by Stingray + Cheaters + Stingray Holidayscapes + AMC en Español + Comedy Dynamics + Tastemade + Portlandia + All Reality WE tv + pocket.watch + Billiard TV + Ryan and Friends + FIFA+ + Pursuit UP + Bring It! + Mountain Men + MovieSphere + Las 3 Marías + At Home with Family Handyman + ACC Digital Network + Alone By History + Slugterra + Stingray Smooth Jazz + Stingray Nothin' But 90s + Stingray Classic Rock + TMZ + 4UV + Conan O'Brien TV + Vevo 2010s + Little Women: LA + Hallmark Movies &amp; More + XITE R&amp;B Classic Jams + Baby Einstein + Stingray Classica + XITE Celebrates + Always Funny Videos + America's Test Kitchen + Anime All day + Backstage + Baywatch + BBC Food + BBC Home &amp; Garden + beIN SPORTS XTRA + Bloomberg Originals + Brat TV + Cars + CBS News + Chicken Soup for the Soul + Cine Romantico + CINEVAULT: 80s + CINEVAULT: Westerns + Circle + Clarity 4K + Court TV + Crime 360 + Dallas Cowboys Cheer + Danger TV + Deal or No Deal + Drama Life + Dry Bar Comedy + Alien Nation by DUST + ElectricNOW + Estrella News + FailArmy + Family Ties + Fear Factor + FilmRise Action + FilmRise Free Movies + FilmRise Western + Forensic Files + FOX SOUL + fubo Sports Network + Game Show Central + Gusto TV + Highway to Heaven + Heartland + Hollywire + Hungry + IGN + IMPACT Wrestling + INFAST + InWonder + Journy + Kidoodle.TV + Law &amp; Crime + LiveNOW from FOX + Loupe 4K + Love &amp; Hip Hop + Love Nature 4K + Lucky Dog + Magellan TV Now + Maverick Black Cinema + MHz Now + Midsomer Murders + Pluto TV Pixel World + MTV Pluto TV + NBC LX Home + NBC News NOW + NEW KMOVIES + Newsmax TV + Nick Pluto TV + Nosey + Outside + Pac-12 Insider + Paramount Movie Channel + People Are Awesome + Pluto TV Fantastic + Pluto TV Westerns + Real America's Voice + Revry + RiffTrax + Samsung Wild Life + Xtreme Outdoor Presented by HISTORY + Sony Canal Comedias + Sony Canal Novelas + SportsGrid + Stadium + Stingray Naturescape + SURF NOW TV + TG Junior + The Asylum + The Challenge + The New Detectives + The Pet Collective + The Preview Channel + This Old House + Tiny House Nation + TODAY All Day + Toon Goggles + TV Land Drama + TV Land Sitcoms + TYT Network + Unidentified + Unsolved Mysteries + USA Today + Vevo 2K + Vevo Country + Vevo Hip-Hop + Vevo Latino + Vevo Pop + Vevo R&amp;B + VICE + Waypoint TV + WeatherSpy + Wild 'N Out + Wipeout Xtra + Xplore + ZooMoo + Top Gear en Español + Bloomberg Originals + Estilo y Vida – Rakuten TV + Pluto TV Cine Estelar + MTV Catfish + Comedia Made in Spain + MTV Cribs + Pluto TV Cocina + Pluto TV Animakids + The Pet Collective + Fashion TV + Comedias - Rakuten TV + Dramas - Rakuten TV + Documentales - Rakuten TV + People Are Awesome + Euronews en directo + Pluto TV Kids + Acción - Rakuten TV + INFAST + Qwest TV + MTV Originals + Doctor Who + BBC Drama + FailArmy + Yu-Gi-Oh! + ¡Hola! Play + Deluxe Lounge HD + MyTime Movie Network + Tastemade + Cortos + 100% Navidad - Rakuten TV + Caillou + Bloomberg TV+ + Los Asesinatos de Midsomer + Las Reglas Del Juego + iCarly + El Comisario + Encantador de Perros + Trace Sportstars + BelAir TV + Tu Cine + Trace Latina + Trace Urban + Nature Time + SuperToons TV + Bob Esponja + Rugrats + Surf Channel + Stormcast Novelas + Flash + WildEarth + Clubbing TV + Runtime + NBC News NOW + El Confidencial + Vive Kanal D Drama + Televisión Consciente + Trailers + Vevo Pop + Vevo Latino + Grjngo - Películas Del Oeste + Bigtime - Películas Gratis + Películas Top - Rakuten TV + Just For Laughs + FIFA+ + Crimen - Rakuten TV + Cines Verdi TV + Cine Feel Good – Verdi TV + Vivaldi TV + Ideas en 5 Minutos + MyPadel TV + Vivir con Perros + Vivir con Gatos + Pocoyó + La 1 + La 2 + Clan + 24H + duckTV + Love the Planet + Teen Vee + GoUSA TV + Cine Español - Rakuten TV + Películas Románticas - Rakuten TV + Ticker News + El País + Dark Matter TV + Todo Novelas + WaterBear + Teledeporte + Cine Friki + Runtime Romance + FRANCE 24 FAST + Baywatch – Los Vigilantes de la Playa + Corazón + Azteca Internacional + Travelxp + Mi chimenea + Rabbids : La invasion + Sol Música + Tennis Channel + World Poker Tour + Motorvision + Heidi &amp; Maya + Negocios TV + La Liga + DATELINE 24/7 + Vevo Navidad + Runtime Comedia + CNBC + Runtime Acción + Pitufo TV + CNN FAST + Cine Clásico + Vevo '70s &amp; '80s + Pluto TV Ciné + MTV Classics + Bob L'eponge + Fashion TV + Films d'action - Rakuten TV + Comédies - Rakuten TV + Films dramatiques - Rakuten TV + Documentaires - Rakuten TV + Pluto TV Kids Séries + MTV Catfish + iCarly + Pluto TV Polar + Euronews en direct + BBC Drama + Brefcinema + Deluxe Lounge HD + The Boat Show + Qwest TV + ADN + UniversCiné + MyTime Movie Network + Xilam TV + Doctor Who + Wild Side TV + Televisa TeleNovelas + Reuters + 100% Noël - Rakuten TV + Caillou + SuperToons TV + Stormcast Novelas + Bloomberg TV+ + Top Films - Rakuten TV + Juste Pour Rire + FIFA+ + Yu-Gi-Oh! + Pluto TV Cuisine + Tortues Ninja + Wellbeing TV + BelAir TV + People Are Awesome + FailArmy + Films Romantiques - Rakuten TV + Crime - Rakuten TV + Travelxp + Clubbing TV + Chefclub TV + Jellytoon + Scream'IN + Nature Time + Ciné Nanar + Gong + Homicide + Tahiti TV + Runtime + Emotion'L + Motus + Fréquence Novelas + Émotion + Y'a que la vérité qui compte + Le Figaro Live + Vevo Pop + Vevo Hip-Hop &amp; R&amp;B + Ciné Prime + Secret Story + MasterChef + Echappées belles &amp; co + MGG FAST + Love the Planet + Destination Nature + L'Equipe Live 1 + L'Equipe Live 2 + Génération Sitcoms + Alerte à Malibu + A prendre ou à laisser + Les 30 histoires + Trailers + Tele.Novela + Un Village Français + Les filles d'à côté + Enquêtes de Choc + Nashville Channel + Le Tigre + Vialma + Au coin de feu + Les Z'amours + Le meilleur d'Arthur + Qui veut gagner des millions ? + Charlotte aux Fraises + Vevo '90s &amp; '00s + Family Club + Les Lapins Crétins: Invasion + Maison &amp; Travaux + World Poker Tour + Motorvision + Drive TV + Allociné + Reportages by Spica + Le Meilleur de la TV Réalité + Les Anges + Popcorn + Motor Racing + L'effet papillon + Passion Novelas + Ciné Western + Degrassi + Top Santé + Plus belle la vie + Vevo Noël + Scènes de Crime + 100% Docs + Wasabi + Culture Pub + CNN FAST + Ardivision + Les secrets de nos régions + BBC Drama + Catfish + Super! iCarly + Pluto TV Real Life + Pluto TV Serie + Serie Teen + Pluto TV Film Romantici + Super! Pop + The Pet Collective + Fashion TV + Commedia - Rakuten TV + Drama - Rakuten TV + Documentari - Rakuten TV + People Are Awesome + Euronews in diretta + Motor1TV + Pluto TV Film + Film d'azione - Rakuten TV + Super! Spongebob + FailArmy + Canale Europa + Yamato Animation + Teletubbies + Deluxe Lounge HD + HorizonSports + Qwest TV + RADIO ITALIA TREND + Sportitalia + SuperToons TV + MONDO TV KIDS + SportOutdoor.tv + Bizzarro Movies + Bloomberg TV+ + Vivaldi TV + Ticker News + BelAir TV + Trace Urban + Trace Latina + WildEarth + Trailers + Televisa Telenovelas + 100% Natale - Rakuten TV + CG - CINEMA d'Autore + Cinema Segreto + 5-Minuti Creativi + Wellbeing TV + WaterBear + Clubbing TV + Italian Fishing TV + Doctor Who + Giornale Radio TV + WP + Dark Matter TV + Brindiamo! + Cmusic + Montagna! + Alta Tensione + Smile + Grandi Nomi + Explorer HD Channel + Top Film - Rakuten TV + Risate all'italiana + Romance - Rakuten TV + Rock TV + Vevo Pop + Pluto TV Film Commedia + Pluto TV Film Azione + Consulenze Illegali + TVPlay + Teen Vee + NBC News NOW + duckTV + Solocalcio + Love the Planet + Film Al Femminile - Wedo Movies + Velvet + Full Moon + Hip Hop TV + Pluto TV Reality + Grandi Documentari - Wedo Big Stories + Vevo '90s &amp; '00s + Move - Italian Active Lives + Serially Drama + Just For Laughs + Serie Crime - Rakuten TV + FIFA+ + Yu-Gi-Oh! + Serially Crime + House of Docs + FRANCE 24 FAST + Travel &amp; Living by DOVE + Baywatch + U-Trend + Travelxp + CineMadame + La 7 + Serie TV asiatiche d’azione by Liv TV + Lifestyle by LEI + Vevo '70s &amp; '80s + RBN + Cinema Excelsior + Le Vite Degli Altri + Andromeda + Autostop per il Cielo + Mutant X + Heidi &amp; Maya + GCTV (Global Champions TV) + CNBC + DATELINE 24/7 + Vevo Natale + East is East + Dolomiti Life TV + Grjngo- Film Western + The Boat Show + Caminetto + CNN FAST + World Poker Tour + Yu yu Hakusho + Film e Sorrisi + 현대홈쇼핑 + 현대홈쇼핑+Shop + MBC every1 어서와 한국은 처음이지? + 코코비TV + MBC every1 별순검 + Bloomberg TV+ UHD + Bloomberg Originals + SBS 런닝맨 + SBS 미운 우리 새끼 + SBS 순풍산부인과 + SBS 펜트하우스 + SBS 동상이몽2 - 너는 내 운명 + SBS 나는 솔로 + SBS 백종원의 골목식당 + SBS 정글의 법칙 + SBS 순간포착 세상에 이런일이 + SBS 패밀리가 떴다 + SBS 궁금한 이야기 Y + SBS 그것이 알고싶다 + SBS 생활의 달인 + MBC 무한도전 + MBC 돈꽃 + MBC 옷소매 붉은끝동 + 초록뱀미디어 K-STAR 골프 + SBS 편의점 샛별이 + SBS 불타는 청춘 + MBC 심야괴담회 + MBC 선을 넘는 녀석들 + SBS 스토브리그 + SBS TV 동물농장 + SBS 빽드 + MBC 나혼자산다 + MBC 지붕뚫고 하이킥 + 투니버스 뱀파이어소녀 달자 + tvN 대탈출3 + MBC 거침없이 하이킥 + 꽃보다 남자 + NEW MOVIES + OGN 스타리그 + 맛있는 녀석들 + TV CHOSUN 식객 허영만의 백반기행 + KBS Joy 무엇이든 물어보살 + World Billiards TV + tvN 응답하라 전 시즌 모아보기 + tvN 갯마을 차차차 + tvN 삼시세끼 산촌편 + Mnet 스트릿 우먼 파이터 + Mnet 스트릿 우먼 파이터 2 HOT CLIP + 심리 읽어드립니다 + tvN 호텔 델루나 + tvN 스트리트 푸드 파이터 1~2 + tvN 미스터 션샤인 + tvN 사랑의 불시착 + tvN 빈센조 + tvN 스물다섯 스물하나 + tvN 여신강림 + 로보카폴리 TV + 고독한 미식가 + 도라마코리아 + MBN 나는 자연인이다 + MBN 휴먼다큐 사노라면 + MBN 속풀이쇼 동치미 + MBN 돌싱글즈 + iHQ 돈쭐내러 왔습니다 + 전우치 + KBS Joy 연애의 참견 + 채널A 나만 믿고 따라와 도시어부 + 채널A 이제 만나러 갑니다 + E채널 토요일은 밥이 좋아 + KBS 개는 훌륭하다 + KBS 쌈마이웨이 + KBS 1박2일 시즌1 + tvN 작은 아씨들 + tvN 슬기로운 의사생활 시즌2 + JTBC 괴물 + JTBC 힘쎈여자 도봉순 + JTBC 효리네 민박 시즌1 + JTBC 골프 + JTBC 최강야구 + JTBC 비긴어게인 + Mnet 스트릿댄스 걸스 파이터 + Zㅏ때는 말이야 + MBC 안싸우면 다행이야 + MBC 나는 가수다 + ch.핑크퐁 + tvN 어쩌다 사장 전 시즌 몰아보기 + 사피엔스스튜디오 + 역사 읽어드립니다 + tvN 신서유기 6 + tvN 놀라운 토요일 하이라이트 + TV CHOSUN 국가가 부른다 + Animax 반지의 비밀일기 + 채널A 요즘 육아 금쪽같은 내새끼 + JTBC 크라임씬 + JTBC 뉴스 + JTBC 톡파원25시 + JTBC 품위있는 그녀 + tvN 환혼 + 연합뉴스TV + FIFA+ + 뽀요TV + 우리의식탁 + YTN + TV조선 빨간 풍선 + TV조선 골프왕 + essential; by Bugs + PLAYY 영화 + PLAYY 어워드특집 + tvN 유 퀴즈 온 더 블럭 HOT CLIP + 글로벌 뉴스 by LeadStory + PGA Tour + 씨네21+ + TV조선 미스터트롯 영웅의 탄생 + TED + diff --git a/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js b/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js index db27786b9..bbad1e6ce 100644 --- a/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js +++ b/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.config.js @@ -2,7 +2,7 @@ const doFetch = require('@ntlab/sfetch') const axios = require('axios') const dayjs = require('dayjs') const crypto = require('crypto') -const { sortBy } = require('../../scripts/functions') +const sortBy = require('lodash.sortby') // API Configuration Constants const NATCO_CODE = 'hr' diff --git a/sites/mtel.ba/mtel.ba.config.js b/sites/mtel.ba/mtel.ba.config.js index 13b4d26fb..6ade976db 100644 --- a/sites/mtel.ba/mtel.ba.config.js +++ b/sites/mtel.ba/mtel.ba.config.js @@ -3,7 +3,7 @@ const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const timezone = require('dayjs/plugin/timezone') const customParseFormat = require('dayjs/plugin/customParseFormat') -const { sortBy } = require('../../scripts/functions') +const sortBy = require('lodash.sortby') dayjs.extend(utc) dayjs.extend(timezone) diff --git a/sites/ontvtonight.com/ontvtonight.com.config.js b/sites/ontvtonight.com/ontvtonight.com.config.js index eac558a9e..e4003ad15 100644 --- a/sites/ontvtonight.com/ontvtonight.com.config.js +++ b/sites/ontvtonight.com/ontvtonight.com.config.js @@ -4,7 +4,7 @@ const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const timezone = require('dayjs/plugin/timezone') const customParseFormat = require('dayjs/plugin/customParseFormat') -const { uniqBy } = require('../../scripts/functions') +const uniqBy = require('lodash.uniqby') dayjs.extend(utc) dayjs.extend(timezone) diff --git a/sites/programme-tv.net/programme-tv.net.config.js b/sites/programme-tv.net/programme-tv.net.config.js index bee7936f8..4b3919d74 100644 --- a/sites/programme-tv.net/programme-tv.net.config.js +++ b/sites/programme-tv.net/programme-tv.net.config.js @@ -1,127 +1,127 @@ -const durationParser = require('parse-duration').default -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'programme-tv.net', - days: 2, - request: { - headers: { - cookie: 'authId=b7154156fe4fb8acdb6f38e1207c6231' - } - }, - url: function ({ date, channel }) { - return `https://www.programme-tv.net/programme/chaine/${date.format('YYYY-MM-DD')}/programme-${ - channel.site_id - }.html` - }, - parser: function ({ content, date }) { - const programs = [] - const items = parseItems(content) - items.forEach(item => { - const $item = cheerio.load(item) - const title = parseTitle($item) - const subTitle = parseSubtitle($item) - const image = parseImage($item) - const category = parseCategory($item) - const start = parseStart($item, date) - const duration = parseDuration($item) - const stop = start.add(duration, 'ms') - - programs.push({ title, subTitle, image, category, start, stop }) - }) - - return programs - }, - async channels() { - const axios = require('axios') - const data = await axios - .get( - `https://www.programme-tv.net/_esi/channel-list/${dayjs().format( - 'YYYY-MM-DD' - )}/?bouquet=perso&modal=0`, - { - headers: { - cookie: 'authId=b7154156fe4fb8acdb6f38e1207c6231' - } - } - ) - .then(r => r.data) - .catch(console.error) - - let channels = [] - - const $ = cheerio.load(data) - $('.channelList-listItemsLink').each((i, el) => { - const name = $(el).attr('title') - const url = $(el).attr('href') - const [, site_id] = url.match(/\/programme-(.*)\.html$/i) - - channels.push({ - lang: 'fr', - site_id, - name - }) - }) - - return channels - } -} - -function parseStart($item, date) { - let time = $item('.mainBroadcastCard-startingHour').first().text().trim() - time = `${date.format('MM/DD/YYYY')} ${time.replace('h', ':')}` - - return dayjs.tz(time, 'MM/DD/YYYY HH:mm', 'Europe/Paris') -} - -function parseDuration($item) { - const duration = $item('.mainBroadcastCard-durationContent').first().text().trim() - - return durationParser(duration) -} - -function parseImage($item) { - const img = $item('.mainBroadcastCard-imageContent').first().find('img') - const value = img.attr('srcset') || img.data('srcset') - - let url = null - - if (value) { - const sources = value.split(',').map(s => s.trim()) - for (const source of sources) { - const [src, descriptor] = source.split(/\s+/) - if (descriptor === '128w') { - url = src.replace('128x180', '960x540') - break - } - } - } - - return url -} - -function parseCategory($item) { - return $item('.mainBroadcastCard-format').first().text().trim() -} - -function parseTitle($item) { - return $item('.mainBroadcastCard-title').first().text().trim() -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('.mainBroadcastCard').toArray() -} - -function parseSubtitle($item) { - return $item('.mainBroadcastCard-subtitle').text().trim() || null +const durationParser = require('parse-duration').default +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'programme-tv.net', + days: 2, + request: { + headers: { + cookie: 'authId=b7154156fe4fb8acdb6f38e1207c6231' + } + }, + url: function ({ date, channel }) { + return `https://www.programme-tv.net/programme/chaine/${date.format('YYYY-MM-DD')}/programme-${ + channel.site_id + }.html` + }, + parser: function ({ content, date }) { + const programs = [] + const items = parseItems(content) + items.forEach(item => { + const $item = cheerio.load(item) + const title = parseTitle($item) + const subTitle = parseSubtitle($item) + const image = parseImage($item) + const category = parseCategory($item) + const start = parseStart($item, date) + const duration = parseDuration($item) + const stop = start.add(duration, 'ms') + + programs.push({ title, subTitle, image, category, start, stop }) + }) + + return programs + }, + async channels() { + const axios = require('axios') + const data = await axios + .get( + `https://www.programme-tv.net/_esi/channel-list/${dayjs().format( + 'YYYY-MM-DD' + )}/?bouquet=perso&modal=0`, + { + headers: { + cookie: 'authId=b7154156fe4fb8acdb6f38e1207c6231' + } + } + ) + .then(r => r.data) + .catch(console.error) + + let channels = [] + + const $ = cheerio.load(data) + $('.channelList-listItemsLink').each((i, el) => { + const name = $(el).attr('title') + const url = $(el).attr('href') + const [, site_id] = url.match(/\/programme-(.*)\.html$/i) + + channels.push({ + lang: 'fr', + site_id, + name + }) + }) + + return channels + } +} + +function parseStart($item, date) { + let time = $item('.mainBroadcastCard-startingHour').first().text().trim() + time = `${date.format('MM/DD/YYYY')} ${time.replace('h', ':')}` + + return dayjs.tz(time, 'MM/DD/YYYY HH:mm', 'Europe/Paris') +} + +function parseDuration($item) { + const duration = $item('.mainBroadcastCard-durationContent').first().text().trim() + + return durationParser(duration) +} + +function parseImage($item) { + const img = $item('.mainBroadcastCard-imageContent').first().find('img') + const value = img.attr('srcset') || img.data('srcset') + + let url = null + + if (value) { + const sources = value.split(',').map(s => s.trim()) + for (const source of sources) { + const [src, descriptor] = source.split(/\s+/) + if (descriptor === '128w') { + url = src.replace('128x180', '960x540') + break + } + } + } + + return url +} + +function parseCategory($item) { + return $item('.mainBroadcastCard-format').first().text().trim() +} + +function parseTitle($item) { + return $item('.mainBroadcastCard-title').first().text().trim() +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('.mainBroadcastCard').toArray() +} + +function parseSubtitle($item) { + return $item('.mainBroadcastCard-subtitle').text().trim() || null } \ No newline at end of file diff --git a/sites/reportv.com.ar/reportv.com.ar.config.js b/sites/reportv.com.ar/reportv.com.ar.config.js index cfe6141e5..0016fd8d5 100644 --- a/sites/reportv.com.ar/reportv.com.ar.config.js +++ b/sites/reportv.com.ar/reportv.com.ar.config.js @@ -5,7 +5,7 @@ const cheerio = require('cheerio') const utc = require('dayjs/plugin/utc') const timezone = require('dayjs/plugin/timezone') const customParseFormat = require('dayjs/plugin/customParseFormat') -const { startCase } = require('../../scripts/functions') +const startCase = require('lodash.startcase') dayjs.extend(utc) dayjs.extend(timezone) diff --git a/sites/sky.com/sky.com.config.js b/sites/sky.com/sky.com.config.js index 291ecdfc2..bb33b6c2e 100644 --- a/sites/sky.com/sky.com.config.js +++ b/sites/sky.com/sky.com.config.js @@ -3,7 +3,7 @@ const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const doFetch = require('@ntlab/sfetch') const debug = require('debug')('site:sky.com') -const { sortBy } = require('../../scripts/functions') +const sortBy = require('lodash.sortby') dayjs.extend(utc) diff --git a/sites/streamingtvguides.com/streamingtvguides.com.config.js b/sites/streamingtvguides.com/streamingtvguides.com.config.js index ccff2d3f3..75f0f9764 100644 --- a/sites/streamingtvguides.com/streamingtvguides.com.config.js +++ b/sites/streamingtvguides.com/streamingtvguides.com.config.js @@ -2,7 +2,8 @@ const cheerio = require('cheerio') const dayjs = require('dayjs') const customParseFormat = require('dayjs/plugin/customParseFormat') const timezone = require('dayjs/plugin/timezone') -const { sortBy, uniqBy } = require('../../scripts/functions') +const sortBy = require('lodash.sortby') +const uniqBy = require('lodash.uniqby') dayjs.extend(customParseFormat) dayjs.extend(timezone) diff --git a/sites/tivie.id/tivie.id.config.js b/sites/tivie.id/tivie.id.config.js index ed3bda766..ec4862f8e 100644 --- a/sites/tivie.id/tivie.id.config.js +++ b/sites/tivie.id/tivie.id.config.js @@ -1,141 +1,141 @@ -const axios = require('axios') -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') -const doFetch = require('@ntlab/sfetch') -const debug = require('debug')('site:tivie.id') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -doFetch.setDebugger(debug) - -const tz = 'Asia/Jakarta' - -module.exports = { - site: 'tivie.id', - days: 2, - url({ channel, date }) { - return `https://tivie.id/channel/${channel.site_id}/${date.format('YYYYMMDD')}` - }, - async parser({ content, date }) { - const programs = [] - if (content) { - const $ = cheerio.load(content) - const items = $('ul[x-data] > li[id*="event-"] > div.w-full') - .toArray() - .map(item => { - const $item = $(item) - const time = $item.find('div:nth-child(1) span:nth-child(1)') - const info = $item.find('div:nth-child(2) h5') - const detail = info.find('a') - const p = { - start: dayjs.tz(`${date.format('YYYY-MM-DD')} ${time.html()}`, 'YYYY-MM-DD HH:mm', tz) - } - if (detail.length) { - const subtitle = detail.find('div') - p.title = parseText(subtitle.length ? subtitle : detail) - p.url = detail.attr('href') - } else { - p.title = parseText(info) - } - if (p.title) { - const [, , season, episode] = p.title.match(/( S(\d+))?, Ep\. (\d+)/) || [ - null, - null, - null, - null - ] - if (season) { - p.season = parseInt(season) - } - if (episode) { - p.episode = parseInt(episode) - } - } - return p - }) - // fetch detailed guide if necessary - const queues = items - .filter(i => i.url) - .map(i => { - const url = i.url - delete i.url - return { i, url } - }) - if (queues.length) { - await doFetch(queues, (queue, res) => { - const $ = cheerio.load(res) - const img = $('#main-content > div > div:nth-child(1) img') - const info = $('#main-content > div > div:nth-child(2)') - const title = parseText(info.find('h2:nth-child(2)')) - if (!queue.i.title.startsWith(title) && !queue.i.title.startsWith('LIVE ')) { - queue.i.subTitle = parseText(info.find('h2:nth-child(2)')) - } - const desc1 = parseText(info.find('div[class=""]:nth-child(3)')) - const desc2 = parseText(info.find('div[class=""]:nth-child(4)')) - if (desc2 == '') { - queue.i.description = desc1.replace('TiViE.id | ', '') - } else { - queue.i.description = desc2.replace('TiViE.id | ', '') - queue.i.date = parseText(info.find('h2:nth-child(3)')) - } - queue.i.categories = parseText(info.find('div[class=""]:nth-child(1)')).split(', ') - queue.i.image = img.length ? img.attr('src') : null - }) - } - // fill start-stop - for (let i = 0; i < items.length; i++) { - if (i < items.length - 1) { - items[i].stop = items[i + 1].start - } else { - items[i].stop = dayjs.tz( - `${date.add(1, 'd').format('YYYY-MM-DD')} 00:00`, - 'YYYY-MM-DD HH:mm', - tz - ) - } - } - // add programs - programs.push(...items) - } - - return programs - }, - async channels({ lang = 'id' }) { - const result = await axios - .get('https://tivie.id/channel') - .then(response => response.data) - .catch(console.error) - - const $ = cheerio.load(result) - const items = $('ul[x-data] li[x-data] div header h2 a').toArray() - const channels = items.map(item => { - const $item = $(item) - const url = $item.attr('href') - return { - lang, - site_id: url.substr(url.lastIndexOf('/') + 1), - name: $item.find('strong').text() - } - }) - - return channels - } -} - -function parseText($item) { - let text = $item.text().replace(/\t/g, '').replace(/\n/g, ' ').trim() - while (true) { - if (text.match(/\s\s/)) { - text = text.replace(/\s\s/g, ' ') - continue - } - break - } - - return text -} +const axios = require('axios') +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const doFetch = require('@ntlab/sfetch') +const debug = require('debug')('site:tivie.id') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +doFetch.setDebugger(debug) + +const tz = 'Asia/Jakarta' + +module.exports = { + site: 'tivie.id', + days: 2, + url({ channel, date }) { + return `https://tivie.id/channel/${channel.site_id}/${date.format('YYYYMMDD')}` + }, + async parser({ content, date }) { + const programs = [] + if (content) { + const $ = cheerio.load(content) + const items = $('ul[x-data] > li[id*="event-"] > div.w-full') + .toArray() + .map(item => { + const $item = $(item) + const time = $item.find('div:nth-child(1) span:nth-child(1)') + const info = $item.find('div:nth-child(2) h5') + const detail = info.find('a') + const p = { + start: dayjs.tz(`${date.format('YYYY-MM-DD')} ${time.html()}`, 'YYYY-MM-DD HH:mm', tz) + } + if (detail.length) { + const subtitle = detail.find('div') + p.title = parseText(subtitle.length ? subtitle : detail) + p.url = detail.attr('href') + } else { + p.title = parseText(info) + } + if (p.title) { + const [, , season, episode] = p.title.match(/( S(\d+))?, Ep\. (\d+)/) || [ + null, + null, + null, + null + ] + if (season) { + p.season = parseInt(season) + } + if (episode) { + p.episode = parseInt(episode) + } + } + return p + }) + // fetch detailed guide if necessary + const queues = items + .filter(i => i.url) + .map(i => { + const url = i.url + delete i.url + return { i, url } + }) + if (queues.length) { + await doFetch(queues, (queue, res) => { + const $ = cheerio.load(res) + const img = $('#main-content > div > div:nth-child(1) img') + const info = $('#main-content > div > div:nth-child(2)') + const title = parseText(info.find('h2:nth-child(2)')) + if (!queue.i.title.startsWith(title) && !queue.i.title.startsWith('LIVE ')) { + queue.i.subTitle = parseText(info.find('h2:nth-child(2)')) + } + const desc1 = parseText(info.find('div[class=""]:nth-child(3)')) + const desc2 = parseText(info.find('div[class=""]:nth-child(4)')) + if (desc2 == '') { + queue.i.description = desc1.replace('TiViE.id | ', '') + } else { + queue.i.description = desc2.replace('TiViE.id | ', '') + queue.i.date = parseText(info.find('h2:nth-child(3)')) + } + queue.i.categories = parseText(info.find('div[class=""]:nth-child(1)')).split(', ') + queue.i.image = img.length ? img.attr('src') : null + }) + } + // fill start-stop + for (let i = 0; i < items.length; i++) { + if (i < items.length - 1) { + items[i].stop = items[i + 1].start + } else { + items[i].stop = dayjs.tz( + `${date.add(1, 'd').format('YYYY-MM-DD')} 00:00`, + 'YYYY-MM-DD HH:mm', + tz + ) + } + } + // add programs + programs.push(...items) + } + + return programs + }, + async channels({ lang = 'id' }) { + const result = await axios + .get('https://tivie.id/channel') + .then(response => response.data) + .catch(console.error) + + const $ = cheerio.load(result) + const items = $('ul[x-data] li[x-data] div header h2 a').toArray() + const channels = items.map(item => { + const $item = $(item) + const url = $item.attr('href') + return { + lang, + site_id: url.substr(url.lastIndexOf('/') + 1), + name: $item.find('strong').text() + } + }) + + return channels + } +} + +function parseText($item) { + let text = $item.text().replace(/\t/g, '').replace(/\n/g, ' ').trim() + while (true) { + if (text.match(/\s\s/)) { + text = text.replace(/\s\s/g, ' ') + continue + } + break + } + + return text +} diff --git a/sites/tv.mail.ru/tv.mail.ru.config.js b/sites/tv.mail.ru/tv.mail.ru.config.js index 5b5bb99cc..ec046c400 100644 --- a/sites/tv.mail.ru/tv.mail.ru.config.js +++ b/sites/tv.mail.ru/tv.mail.ru.config.js @@ -1,6 +1,6 @@ const { DateTime } = require('luxon') const axios = require('axios') -const { uniqBy } = require('../../scripts/functions') +const uniqBy = require('lodash.uniqby') module.exports = { site: 'tv.mail.ru', diff --git a/sites/tv.sfr.fr/tv.sfr.fr.channels.xml b/sites/tv.sfr.fr/tv.sfr.fr.channels.xml index bc19ca9a7..27e8cae8d 100644 --- a/sites/tv.sfr.fr/tv.sfr.fr.channels.xml +++ b/sites/tv.sfr.fr/tv.sfr.fr.channels.xml @@ -1,492 +1,492 @@ - - - TF1 - France 2 - France 3 - France 4 - France 5 - M6 - Arte - LCP-Public Sénat - W9 - TMC - TFX - Gulli - BFM TV - CNews - LCI - franceinfo: - CStar - T18 - NOVO19 - TF1 Séries-Films - L'équipe - 6ter - RMC Story - RMC Découverte - Chérie 25 - i24 News - AFTER FOOT TV - BFM Business - TECH&CO - RMC Sport 1 - RMC Sport Live 2 - BFM MARSEILLE PROVENCE - BFM Lyon - RMC Talk Info - Discovery Channel - TLC - Discovery Investigation - BFM Grands Reportages - France 24 - Euronews Fra - RMC Alerte Secours - 13ème rue - Syfy - E! Entertainment - WARNER TV - MTV - MCM - AB1 - SERIE CLUB - Game One - Game One+1 - Warner TV Next - J-One - BET - Comedy Central - Netflix - Prime Vidéo - Disney+ - Paris Première - Téva - RTL9 - TV Breizh - TV5 Monde - TF1 4K - France 2 UHD - CANAL+ SPORT 360 - CANAL+ FOOT - CANAL+ BOX OFFICE - CANAL+ GRAND ECRAN - CANAL+ DOCS - CANAL+ KIDS - BFM2 - LCP-AN 24/24 - Public Sénat 24/24 - LA CHAINE METEO - RMC Sport Access - RMC Mecanic - beIN SPORTS 1 - beIN SPORTS 2 - beIN SPORTS 3 - DAZN 1 - Equidia - MGG TV - Auto Moto - Journal du Golf TV - Sport en France - OLPLAY - EUROSPORT 1 HD - EUROSPORT 2 HD - OCS - CINE+ frisson - CINE+ émotion - CINE+ family - CINE+ festival - CINE+ classic - DISNEY CHANNEL - DISNEY CHANNEL +1 - Paramount Network - Paramount Network Décalé - TCM Cinéma - Action - INSOMNIA - RMC WOW - RMC Mystère - J'irai dormir chez vous - Ushuaia TV - TREK - Crime District - Marmiton TV - Histoire TV - Toute l'histoire - KTO - Animaux - Chasse et Pêche - Science et Vie TV - Luxe TV - Fashion TV - Men's Up TV - Astrocenter.tv - My Zen TV - MUSEUM TV - Museum TV 4K - EXPLORE - Le Figaro TV - SEASONS - KITCHEN MANIA - Top Santé TV - Maison & Travaux TV - Auto Plus TV - Nickelodeon Junior - Boomerang - Boomerang+1 - Tiji - DREAMWORKS - Nickelodeon - Nickelodeon+1 - Canal J - Cartoon Network - Nickelodeon Teen - Cartoonito - Trace Vanilla - Mangas - Lucky Jack - MTV Hits - M6 Music - RFM TV - NRJ Hits - Trace Latina - Mezzo - Mezzo Live - Melody TV - Trace Urban - Trace Toca - TRACE CARIBBEAN - Trace Gospel - Melody d'Afrique - BFM Grand Lille - BFM Grand Littoral - BFM NICE COTE D'AZUR - BFM TOULON VAR - BFM DICI ALPES DU SUD - BFM DICI HAUTE-PROVENCE - BFM ALSACE - BFM Normandie - vià30 - vià31 - vià34 - vià66 - beIN SPORTS MAX 4 - beIN SPORTS MAX 5 - beIN SPORTS MAX 6 - beIN SPORTS MAX 7 - beIN SPORTS MAX 8 - beIN SPORTS MAX 9 - beIN SPORTS MAX 10 - GOLF+ HD - CANAL+LIVE 1 - CANAL+LIVE 2 - CANAL+LIVE 3 - CANAL+LIVE 4 - CANAL+LIVE 5 - CANAL+LIVE 6 - CANAL+LIVE 7 - RMC Sport Live 3 - RMC Sport Live 4 - XXL - Dorcel TV - Dorcel XXX - Private TV - Hustler TV - VIXEN - Pink TV / Pink X - Man-X - Union TV - DORCEL TV AFRICA - MEN TV - PENTHOUSE HD - Das Erste - SPORT 1 - France 3 Alpes - France 3 Alsace - France 3 Aquitaine - France 3 Auvergne - France 3 Basse-Normandie - France 3 Bourgogne - France 3 Bretagne - France 3 Centre - France 3 Champagne-Ardenne - France 3 via Stella - France 3 Côte d'Azur - France 3 Franche-Comté - France 3 Haute-Normandie - France 3 Languedoc - France 3 Limousin - France 3 Lorraine - France 3 Midi-Pyrénées - France 3 Nord-Pas-de-Calais - France 3 Paris IDF - France 3 Pays de la Loire - France 3 Picardie - France 3 Poitou-Charentes - France 3 Provence-Alpes - France 3 Rhône-Alpes - France 3 Nouvelle Aquitaine - France 3 - Corse - 20 Minutes TV - Télé Bocal - vià93 - Figaro TV IDF - TV78 - Lyon Capitale TV - Télé Grenoble - TL7 Saint Etienne - 8 Mont-Blanc - ILTV - ASTV - Wéo Picardie - Wéo TV, La voix du nord - CRESPIN TELEVISION - Vià MATÉLÉ - 7A Limoges - TV7 Bordeaux - TVPI (TV Biarritz) - ETB1 - ETB2 - ETB3 - KANALDUDE - Grosbliederstroff - TV2COM - TRESSANGE TV - NA TV - Canal 32 - vià Mirabelle - Puissance TV - Télé Schiltigheim - TVMonaco - Mosaik Cristal - TV8 Moselle-Est - vià Vosges - Cannes Lérins TV - Maritima TV - MAURIENNE TV - Angers Télé - Télé Nantes - TV Vendée - LMtv Sarthe - Tébéo - TV Rennes 35 - Val de Loire TV - Zouk TV - Télé Paese - CNN International - BBC NEWS - France 24 ENG - CNBC Europe - Bloomberg - Al Jazeera English - i24 News Anglais - NHK WORLD-JAPAN - Sky News - TGCOM24 - i24 News Arabe - France 24 ARA - Al Jazeera - Medi 1 TV - Al Arabiya - Echorouk TV - Ennahar TV - SIC Noticias - Canal 11 - Rai News 24 - France 24 Espanol - 24 Horas - TVE - DW-TV - WELT - TVN24 - Record News - CGTN - Africa 24 - Canal 2 - V+ - Porto Canal - Local Visao - Benfica TV - A BOLA TV - TV Record - RTP 3 - TVI Internacional - SIC Internacional - Canal Q - RTPI - Rai Uno - Rai Due - Rai Tre - RAI SCUOLA - RAI STORIA - Mediaset Italia - REAL MADRID TV - STAR TVE - Antena 3 - Atres Series - ALL FLAMENCO - TVG EUROPA - TV3 Catalunya - etb basque - TELE MADRID - ANDALUCIA TV - Boomerang Anglais - FilmBox Arthouse - TCM Anglais - TinyTeen - Lang Lab - Lingo Toons - DocuBox HD - FashionBox HD - Pro Sieben - N-TV - RTL Television - RTL2 - Sat 1 - Super RTL - SWR - Vox - ZDF - KABEL EINS - KIKA - RTL NITRO TV - Arte Allemande - 3 SAT - VIVA - iTVN - iTVN Extra - TVP Polonia - Armenia 1 - Antenna 1 - ART CINEMA - ART AFLAM 1 - ART AFLAM 2 - AL HEKAYAT 1 - AL HEKAYAT 2 - TV Romania International - Bahia - Télé Maroc - Samira TV - Canal Algérie - A3 - Beur TV - 2M Maroc - Al Aoula - Arryadia - Assadissa - El Hiwar Ettounsi - Watania 2 - Tunisia 1 - Asharq News - Berbère Jeunesse - Berbère Musique - Berbère TV - Sky News Arabia - CHADA TV - Rotana Comedy - Syria TV - Al Resalah - Alaraby 1 - Echorouk News - El Bilad TV - Dizi - Alaraby 2 - Rotana Aflam+ - Rotana Cinéma+ FR - Rotana Music - Rotana Drama - Rotana Kids - Rotana Cinema - Rotana Classic - Nessma - DMC - Fix et Foxy - Carthage+ - DMC Drama - Iqraa TV - Iqraa International - Al Majd Holy Quran - Al Maghribia - Arrabiâ - Al Jadeed - LBC Sat - Lana TV - NBN - OTV - Murr TV - Rotana M+ - Dubaï TV - ON TV - HANNIBAL TV - Panorama Drama - Panorama Film - Al Masriya - The Israeli Network - Jordan Satellite Channel - Euro Star - Euro D - Habertürk - BEIN MOVIES - SHOW MAX - Show Turk - ATV Avrupa - KANAL 7 AVRUPA - TV8 International - Saudi Channel 1 - A+ - ORTB - NOVELAS - CRTV - Equinoxe TV - CHERIFLA TV - ORTC - TV Congo - Maboke TV - RTNC - NCI - RTI 1 - CDIRECT - Gabon 1ère - RTG - TVM - ORTM - Nollywood TV Epic - Nollywood TV - Pulaagu - Trace Africa - Vox Africa - 2STV - RTS 1 - SEN TV - TFM - SUNU YEUF - Beijing TV - CCTV YULE - CCTV-4 - China Movie Channel (CMC) - Hunan TV - JSBC International - Phoenix CNE - Phoenix Infonews - Shangaï Dragon TV - Great Wall Elite - ZTV World (Zhejiang Star TV) - GRT GBA Satellite TV - CGTN-Français - NTD - KBS World - Colors - Utsav Bharat - Rishtey - Utsav Plus - Zee TV - NHK World Premium - B4U Movies - Geo News - Geo TV - Sony Max - Las Estrellas - Distrito Comedia - De pelicula - RMS - Telehit - tlnovelas + + + TF1 + France 2 + France 3 + France 4 + France 5 + M6 + Arte + LCP-Public Sénat + W9 + TMC + TFX + Gulli + BFM TV + CNews + LCI + franceinfo: + CStar + T18 + NOVO19 + TF1 Séries-Films + L'équipe + 6ter + RMC Story + RMC Découverte + Chérie 25 + i24 News + AFTER FOOT TV + BFM Business + TECH&CO + RMC Sport 1 + RMC Sport Live 2 + BFM MARSEILLE PROVENCE + BFM Lyon + RMC Talk Info + Discovery Channel + TLC + Discovery Investigation + BFM Grands Reportages + France 24 + Euronews Fra + RMC Alerte Secours + 13ème rue + Syfy + E! Entertainment + WARNER TV + MTV + MCM + AB1 + SERIE CLUB + Game One + Game One+1 + Warner TV Next + J-One + BET + Comedy Central + Netflix + Prime Vidéo + Disney+ + Paris Première + Téva + RTL9 + TV Breizh + TV5 Monde + TF1 4K + France 2 UHD + CANAL+ SPORT 360 + CANAL+ FOOT + CANAL+ BOX OFFICE + CANAL+ GRAND ECRAN + CANAL+ DOCS + CANAL+ KIDS + BFM2 + LCP-AN 24/24 + Public Sénat 24/24 + LA CHAINE METEO + RMC Sport Access + RMC Mecanic + beIN SPORTS 1 + beIN SPORTS 2 + beIN SPORTS 3 + DAZN 1 + Equidia + MGG TV + Auto Moto + Journal du Golf TV + Sport en France + OLPLAY + EUROSPORT 1 HD + EUROSPORT 2 HD + OCS + CINE+ frisson + CINE+ émotion + CINE+ family + CINE+ festival + CINE+ classic + DISNEY CHANNEL + DISNEY CHANNEL +1 + Paramount Network + Paramount Network Décalé + TCM Cinéma + Action + INSOMNIA + RMC WOW + RMC Mystère + J'irai dormir chez vous + Ushuaia TV + TREK + Crime District + Marmiton TV + Histoire TV + Toute l'histoire + KTO + Animaux + Chasse et Pêche + Science et Vie TV + Luxe TV + Fashion TV + Men's Up TV + Astrocenter.tv + My Zen TV + MUSEUM TV + Museum TV 4K + EXPLORE + Le Figaro TV + SEASONS + KITCHEN MANIA + Top Santé TV + Maison & Travaux TV + Auto Plus TV + Nickelodeon Junior + Boomerang + Boomerang+1 + Tiji + DREAMWORKS + Nickelodeon + Nickelodeon+1 + Canal J + Cartoon Network + Nickelodeon Teen + Cartoonito + Trace Vanilla + Mangas + Lucky Jack + MTV Hits + M6 Music + RFM TV + NRJ Hits + Trace Latina + Mezzo + Mezzo Live + Melody TV + Trace Urban + Trace Toca + TRACE CARIBBEAN + Trace Gospel + Melody d'Afrique + BFM Grand Lille + BFM Grand Littoral + BFM NICE COTE D'AZUR + BFM TOULON VAR + BFM DICI ALPES DU SUD + BFM DICI HAUTE-PROVENCE + BFM ALSACE + BFM Normandie + vià30 + vià31 + vià34 + vià66 + beIN SPORTS MAX 4 + beIN SPORTS MAX 5 + beIN SPORTS MAX 6 + beIN SPORTS MAX 7 + beIN SPORTS MAX 8 + beIN SPORTS MAX 9 + beIN SPORTS MAX 10 + GOLF+ HD + CANAL+LIVE 1 + CANAL+LIVE 2 + CANAL+LIVE 3 + CANAL+LIVE 4 + CANAL+LIVE 5 + CANAL+LIVE 6 + CANAL+LIVE 7 + RMC Sport Live 3 + RMC Sport Live 4 + XXL + Dorcel TV + Dorcel XXX + Private TV + Hustler TV + VIXEN + Pink TV / Pink X + Man-X + Union TV + DORCEL TV AFRICA + MEN TV + PENTHOUSE HD + Das Erste + SPORT 1 + France 3 Alpes + France 3 Alsace + France 3 Aquitaine + France 3 Auvergne + France 3 Basse-Normandie + France 3 Bourgogne + France 3 Bretagne + France 3 Centre + France 3 Champagne-Ardenne + France 3 via Stella + France 3 Côte d'Azur + France 3 Franche-Comté + France 3 Haute-Normandie + France 3 Languedoc + France 3 Limousin + France 3 Lorraine + France 3 Midi-Pyrénées + France 3 Nord-Pas-de-Calais + France 3 Paris IDF + France 3 Pays de la Loire + France 3 Picardie + France 3 Poitou-Charentes + France 3 Provence-Alpes + France 3 Rhône-Alpes + France 3 Nouvelle Aquitaine + France 3 - Corse + 20 Minutes TV + Télé Bocal + vià93 + Figaro TV IDF + TV78 + Lyon Capitale TV + Télé Grenoble + TL7 Saint Etienne + 8 Mont-Blanc + ILTV + ASTV + Wéo Picardie + Wéo TV, La voix du nord + CRESPIN TELEVISION + Vià MATÉLÉ + 7A Limoges + TV7 Bordeaux + TVPI (TV Biarritz) + ETB1 + ETB2 + ETB3 + KANALDUDE + Grosbliederstroff + TV2COM + TRESSANGE TV + NA TV + Canal 32 + vià Mirabelle + Puissance TV + Télé Schiltigheim + TVMonaco + Mosaik Cristal + TV8 Moselle-Est + vià Vosges + Cannes Lérins TV + Maritima TV + MAURIENNE TV + Angers Télé + Télé Nantes + TV Vendée + LMtv Sarthe + Tébéo + TV Rennes 35 + Val de Loire TV + Zouk TV + Télé Paese + CNN International + BBC NEWS + France 24 ENG + CNBC Europe + Bloomberg + Al Jazeera English + i24 News Anglais + NHK WORLD-JAPAN + Sky News + TGCOM24 + i24 News Arabe + France 24 ARA + Al Jazeera + Medi 1 TV + Al Arabiya + Echorouk TV + Ennahar TV + SIC Noticias + Canal 11 + Rai News 24 + France 24 Espanol + 24 Horas + TVE + DW-TV + WELT + TVN24 + Record News + CGTN + Africa 24 + Canal 2 + V+ + Porto Canal + Local Visao + Benfica TV + A BOLA TV + TV Record + RTP 3 + TVI Internacional + SIC Internacional + Canal Q + RTPI + Rai Uno + Rai Due + Rai Tre + RAI SCUOLA + RAI STORIA + Mediaset Italia + REAL MADRID TV + STAR TVE + Antena 3 + Atres Series + ALL FLAMENCO + TVG EUROPA + TV3 Catalunya + etb basque + TELE MADRID + ANDALUCIA TV + Boomerang Anglais + FilmBox Arthouse + TCM Anglais + TinyTeen + Lang Lab + Lingo Toons + DocuBox HD + FashionBox HD + Pro Sieben + N-TV + RTL Television + RTL2 + Sat 1 + Super RTL + SWR + Vox + ZDF + KABEL EINS + KIKA + RTL NITRO TV + Arte Allemande + 3 SAT + VIVA + iTVN + iTVN Extra + TVP Polonia + Armenia 1 + Antenna 1 + ART CINEMA + ART AFLAM 1 + ART AFLAM 2 + AL HEKAYAT 1 + AL HEKAYAT 2 + TV Romania International + Bahia + Télé Maroc + Samira TV + Canal Algérie + A3 + Beur TV + 2M Maroc + Al Aoula + Arryadia + Assadissa + El Hiwar Ettounsi + Watania 2 + Tunisia 1 + Asharq News + Berbère Jeunesse + Berbère Musique + Berbère TV + Sky News Arabia + CHADA TV + Rotana Comedy + Syria TV + Al Resalah + Alaraby 1 + Echorouk News + El Bilad TV + Dizi + Alaraby 2 + Rotana Aflam+ + Rotana Cinéma+ FR + Rotana Music + Rotana Drama + Rotana Kids + Rotana Cinema + Rotana Classic + Nessma + DMC + Fix et Foxy + Carthage+ + DMC Drama + Iqraa TV + Iqraa International + Al Majd Holy Quran + Al Maghribia + Arrabiâ + Al Jadeed + LBC Sat + Lana TV + NBN + OTV + Murr TV + Rotana M+ + Dubaï TV + ON TV + HANNIBAL TV + Panorama Drama + Panorama Film + Al Masriya + The Israeli Network + Jordan Satellite Channel + Euro Star + Euro D + Habertürk + BEIN MOVIES + SHOW MAX + Show Turk + ATV Avrupa + KANAL 7 AVRUPA + TV8 International + Saudi Channel 1 + A+ + ORTB + NOVELAS + CRTV + Equinoxe TV + CHERIFLA TV + ORTC + TV Congo + Maboke TV + RTNC + NCI + RTI 1 + CDIRECT + Gabon 1ère + RTG + TVM + ORTM + Nollywood TV Epic + Nollywood TV + Pulaagu + Trace Africa + Vox Africa + 2STV + RTS 1 + SEN TV + TFM + SUNU YEUF + Beijing TV + CCTV YULE + CCTV-4 + China Movie Channel (CMC) + Hunan TV + JSBC International + Phoenix CNE + Phoenix Infonews + Shangaï Dragon TV + Great Wall Elite + ZTV World (Zhejiang Star TV) + GRT GBA Satellite TV + CGTN-Français + NTD + KBS World + Colors + Utsav Bharat + Rishtey + Utsav Plus + Zee TV + NHK World Premium + B4U Movies + Geo News + Geo TV + Sony Max + Las Estrellas + Distrito Comedia + De pelicula + RMS + Telehit + tlnovelas \ No newline at end of file diff --git a/sites/tv.sfr.fr/tv.sfr.fr.config.js b/sites/tv.sfr.fr/tv.sfr.fr.config.js index f6c2b64de..c14b7a8a2 100644 --- a/sites/tv.sfr.fr/tv.sfr.fr.config.js +++ b/sites/tv.sfr.fr/tv.sfr.fr.config.js @@ -1,65 +1,65 @@ -const axios = require('axios') -const dayjs = require('dayjs') - -module.exports = { - site: 'tv.sfr.fr', - days: 2, - url({ date }) { - return `https://static-cdn.tv.sfr.net/data/epg/gen8/guide_web_${date.format('YYYYMMDD')}.json` - }, - request: { - maxContentLength: 20 * 1024 * 1024, // 20Mb - cache: { - ttl: 24 * 60 * 60 * 1000 // 1 day - } - }, - parser({ content, channel }) { - let programs = [] - let items = parseItems(content, channel) - items.forEach(item => { - programs.push({ - start: dayjs(item.startDate), - stop: dayjs(item.endDate), - title: item.title, - subTitle: item.subTitle || null, - category: item.genre, - description: item.longSynopsis, - images: item.images.map(img => img.url), - season: item.seasonNumber || null, - episode: item.episodeNumber || null - }) - }) - - return programs - }, - async channels() { - const data = await axios - .get('https://api.sfr.fr/service-channel/api/rest/v2/channels') - .then(r => r.data) - .catch(console.error) - - let channels = {} - Object.values(data.data.chaines).forEach(channel => { - if (!channels[channel.epg_id]) { - channels[channel.epg_id] = { - lang: 'fr', - site_id: channel.epg_id, - name: channel.nom_chaine - } - } - }) - - return Object.values(channels) - } -} - -function parseItems(content, channel) { - try { - const data = JSON.parse(content) - if (!data || !data.epg || !Array.isArray(data.epg[channel.site_id])) return [] - - return data.epg[channel.site_id] - } catch { - return [] - } -} +const axios = require('axios') +const dayjs = require('dayjs') + +module.exports = { + site: 'tv.sfr.fr', + days: 2, + url({ date }) { + return `https://static-cdn.tv.sfr.net/data/epg/gen8/guide_web_${date.format('YYYYMMDD')}.json` + }, + request: { + maxContentLength: 20 * 1024 * 1024, // 20Mb + cache: { + ttl: 24 * 60 * 60 * 1000 // 1 day + } + }, + parser({ content, channel }) { + let programs = [] + let items = parseItems(content, channel) + items.forEach(item => { + programs.push({ + start: dayjs(item.startDate), + stop: dayjs(item.endDate), + title: item.title, + subTitle: item.subTitle || null, + category: item.genre, + description: item.longSynopsis, + images: item.images.map(img => img.url), + season: item.seasonNumber || null, + episode: item.episodeNumber || null + }) + }) + + return programs + }, + async channels() { + const data = await axios + .get('https://api.sfr.fr/service-channel/api/rest/v2/channels') + .then(r => r.data) + .catch(console.error) + + let channels = {} + Object.values(data.data.chaines).forEach(channel => { + if (!channels[channel.epg_id]) { + channels[channel.epg_id] = { + lang: 'fr', + site_id: channel.epg_id, + name: channel.nom_chaine + } + } + }) + + return Object.values(channels) + } +} + +function parseItems(content, channel) { + try { + const data = JSON.parse(content) + if (!data || !data.epg || !Array.isArray(data.epg[channel.site_id])) return [] + + return data.epg[channel.site_id] + } catch { + return [] + } +} diff --git a/sites/tv.sfr.fr/tv.sfr.fr.test.js b/sites/tv.sfr.fr/tv.sfr.fr.test.js index cca7f6b59..29630f9b9 100644 --- a/sites/tv.sfr.fr/tv.sfr.fr.test.js +++ b/sites/tv.sfr.fr/tv.sfr.fr.test.js @@ -1,71 +1,71 @@ -const { parser, url } = require('./tv.sfr.fr.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-01-18', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '192', - xmltv_id: 'TF1.fr' -} - -it('can generate valid url', () => { - expect(url({ date, channel })).toBe( - 'https://static-cdn.tv.sfr.net/data/epg/gen8/guide_web_20250118.json' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') - let results = parser({ content, channel }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(23) - expect(results[0]).toMatchObject({ - start: '2025-01-18T02:05:00.000Z', - stop: '2025-01-18T05:00:00.000Z', - title: 'Programmes de la nuit', - subTitle: null, - category: 'Programme indéterminé', - description: 'Retrouvez tous vos programmes de nuit.', - images: [ - 'http://static-cdn.tv.sfr.net/data/img/pl/3/6/9/5757963.jpg', - 'http://static-cdn.tv.sfr.net/data/img/pl/5/0/8/7616805.jpg' - ], - season: null, - episode: null - }) - expect(results[22]).toMatchObject({ - start: '2025-01-18T22:40:00.000Z', - stop: '2025-01-19T00:00:00.000Z', - title: 'Star Academy', - subTitle: 'Retour au château', - category: 'Téléréalité', - description: - "C'est en direct du plateau que Nikos Aliagas revient sur les prestations des différents académiciens en compagnie du corps professoral. L'occasion de revenir en détails sur le déroulement du prime avec les aspects positifs mais également les éléments sur lesquels les élèves doivent progresser pour espérer faire la différence sur cette fin d'aventure.", - images: [ - 'http://static-cdn.tv.sfr.net/data/img/pl/1/0/0/9517001.jpg', - 'http://static-cdn.tv.sfr.net/data/img/pl/6/7/2/9992276.jpg', - 'http://static-cdn.tv.sfr.net/data/img/pl/9/0/1/9985109.jpg', - 'http://static-cdn.tv.sfr.net/data/img/pl/6/7/5/9491576.jpg' - ], - season: 12, - episode: 15 - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - content: '' - }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./tv.sfr.fr.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-01-18', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '192', + xmltv_id: 'TF1.fr' +} + +it('can generate valid url', () => { + expect(url({ date, channel })).toBe( + 'https://static-cdn.tv.sfr.net/data/epg/gen8/guide_web_20250118.json' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') + let results = parser({ content, channel }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(23) + expect(results[0]).toMatchObject({ + start: '2025-01-18T02:05:00.000Z', + stop: '2025-01-18T05:00:00.000Z', + title: 'Programmes de la nuit', + subTitle: null, + category: 'Programme indéterminé', + description: 'Retrouvez tous vos programmes de nuit.', + images: [ + 'http://static-cdn.tv.sfr.net/data/img/pl/3/6/9/5757963.jpg', + 'http://static-cdn.tv.sfr.net/data/img/pl/5/0/8/7616805.jpg' + ], + season: null, + episode: null + }) + expect(results[22]).toMatchObject({ + start: '2025-01-18T22:40:00.000Z', + stop: '2025-01-19T00:00:00.000Z', + title: 'Star Academy', + subTitle: 'Retour au château', + category: 'Téléréalité', + description: + "C'est en direct du plateau que Nikos Aliagas revient sur les prestations des différents académiciens en compagnie du corps professoral. L'occasion de revenir en détails sur le déroulement du prime avec les aspects positifs mais également les éléments sur lesquels les élèves doivent progresser pour espérer faire la différence sur cette fin d'aventure.", + images: [ + 'http://static-cdn.tv.sfr.net/data/img/pl/1/0/0/9517001.jpg', + 'http://static-cdn.tv.sfr.net/data/img/pl/6/7/2/9992276.jpg', + 'http://static-cdn.tv.sfr.net/data/img/pl/9/0/1/9985109.jpg', + 'http://static-cdn.tv.sfr.net/data/img/pl/6/7/5/9491576.jpg' + ], + season: 12, + episode: 15 + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + content: '' + }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/tvcesoir.fr/tvcesoir.fr.config.js b/sites/tvcesoir.fr/tvcesoir.fr.config.js index 1a9855bf0..5a0636770 100644 --- a/sites/tvcesoir.fr/tvcesoir.fr.config.js +++ b/sites/tvcesoir.fr/tvcesoir.fr.config.js @@ -3,7 +3,7 @@ const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const timezone = require('dayjs/plugin/timezone') const customParseFormat = require('dayjs/plugin/customParseFormat') -const uniqBy = require('../../scripts/functions') +const uniqBy = require('lodash.uniqby') dayjs.extend(utc) dayjs.extend(timezone) diff --git a/sites/tvhebdo.com/tvhebdo.com.config.js b/sites/tvhebdo.com/tvhebdo.com.config.js index 5f77af29c..29f94a44d 100644 --- a/sites/tvhebdo.com/tvhebdo.com.config.js +++ b/sites/tvhebdo.com/tvhebdo.com.config.js @@ -1,7 +1,7 @@ const cheerio = require('cheerio') const axios = require('axios') const { DateTime } = require('luxon') -const { uniqBy } = require('../../scripts/functions') +const uniqBy = require('lodash.uniqby') module.exports = { site: 'tvhebdo.com', diff --git a/sites/tvireland.ie/tvireland.ie.config.js b/sites/tvireland.ie/tvireland.ie.config.js index 0ea0e3493..10f6274be 100644 --- a/sites/tvireland.ie/tvireland.ie.config.js +++ b/sites/tvireland.ie/tvireland.ie.config.js @@ -3,7 +3,7 @@ const dayjs = require('dayjs') const utc = require('dayjs/plugin/utc') const timezone = require('dayjs/plugin/timezone') const customParseFormat = require('dayjs/plugin/customParseFormat') -const { uniqBy } = require('../../scripts/functions') +const uniqBy = require('lodash.uniqby') dayjs.extend(utc) dayjs.extend(timezone) diff --git a/sites/tvmusor.hu/tvmusor.hu.config.js b/sites/tvmusor.hu/tvmusor.hu.config.js index 52ecb2861..a0f92bbc8 100644 --- a/sites/tvmusor.hu/tvmusor.hu.config.js +++ b/sites/tvmusor.hu/tvmusor.hu.config.js @@ -1,6 +1,6 @@ const axios = require('axios') const dayjs = require('dayjs') -const { uniqBy } = require('../../scripts/functions') +const uniqBy = require('lodash.uniqby') module.exports = { site: 'tvmusor.hu', diff --git a/sites/vidio.com/vidio.com.channels.xml b/sites/vidio.com/vidio.com.channels.xml index 54e56fde3..892af07ec 100644 --- a/sites/vidio.com/vidio.com.channels.xml +++ b/sites/vidio.com/vidio.com.channels.xml @@ -1,60 +1,60 @@ - - - ABC Australia - AFRICANEWS TV - Ajwa TV - Aljazeera - ANTV - Arirang - Bein 1 - Bein 2 - Bein 3 - BeritaSatu - BTV - CTV 1 - CTV 2 - CTV 3 - CTV 5 - CTV 6 - Premier League TV - Champions Golf 1 - Champions Golf 2 - News Asia - DAAI TV - Daystar TV - DW English - Elshinta TV - Euro News - GGS TV - Hip Hip Horee! - Horee - Indosiar - Jaktv - jawaposTV - JTV - Kompas TV - Magna TV - Makkah TV - MDTV - Metro TV - Moji - MUSICA - NBA TV - NHK World Japan - Nusantara TV - RTV - ROCK Entertainment - Rock Action - SCTV - SPOTV 2 - SPOTV - Tawaf TV - Trans7 - TRANS TV - TV5Monde - TVN - TVOne - TVRI - U-Channel TV - Zoomoo - + + + ABC Australia + AFRICANEWS TV + Ajwa TV + Aljazeera + ANTV + Arirang + Bein 1 + Bein 2 + Bein 3 + BeritaSatu + BTV + CTV 1 + CTV 2 + CTV 3 + CTV 5 + CTV 6 + Premier League TV + Champions Golf 1 + Champions Golf 2 + News Asia + DAAI TV + Daystar TV + DW English + Elshinta TV + Euro News + GGS TV + Hip Hip Horee! + Horee + Indosiar + Jaktv + jawaposTV + JTV + Kompas TV + Magna TV + Makkah TV + MDTV + Metro TV + Moji + MUSICA + NBA TV + NHK World Japan + Nusantara TV + RTV + ROCK Entertainment + Rock Action + SCTV + SPOTV 2 + SPOTV + Tawaf TV + Trans7 + TRANS TV + TV5Monde + TVN + TVOne + TVRI + U-Channel TV + Zoomoo + diff --git a/sites/vidio.com/vidio.com.config.js b/sites/vidio.com/vidio.com.config.js index 3d0ab272c..000566c45 100644 --- a/sites/vidio.com/vidio.com.config.js +++ b/sites/vidio.com/vidio.com.config.js @@ -1,89 +1,89 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') -const crypto = require('crypto') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -const WEB_CLIENT_SECRET = Buffer.from('dPr0QImQ7bc5o9LMntNba2DOsSbZcjUh') -const WEB_CLIENT_IV = Buffer.from('C8RWsrtFsoeyCyPt') - -module.exports = { - site: 'vidio.com', - days: 2, - url({ date, channel }) { - return `https://api.vidio.com/livestreamings/${channel.site_id}/schedules?filter[date]=${date.format('YYYY-MM-DD')}` - }, - request: { - async headers() { - const session = await loadSessionDetails() - if (!session || !session.api_key) return null - - var cipher = crypto.createCipheriv('aes-256-cbc', WEB_CLIENT_SECRET, WEB_CLIENT_IV) - return { - 'X-API-Key': cipher.update(session.api_key, 'utf8', 'base64') + cipher.final('base64'), - 'X-Secure-Level': 2 - } - } - }, - parser({ content }) { - const programs = [] - const json = JSON.parse(content) - if (Array.isArray(json?.data)) { - for (const program of json.data) { - programs.push({ - title: program.attributes.title, - description: program.attributes.description, - start: dayjs(program.attributes.start_time), - stop: dayjs(program.attributes.end_time), - image: program.attributes.image_landscape_url - }) - } - } - - return programs - }, - async channels() { - const channels = [] - const json = await axios - .get( - 'https://api.vidio.com/livestreamings?stream_type=tv_stream', - { - headers: await this.request.headers() - } - ) - .then(response => response.data) - .catch(console.error) - - if (Array.isArray(json?.data)) { - for (const channel of json.data) { - channels.push({ - lang: 'id', - site_id: channel.id, - name: channel.attributes.title - }) - } - } - - return channels - } -} - -function loadSessionDetails() { - return axios - .post( - 'https://www.vidio.com/auth', - {}, - { - headers: { - 'Content-Type': 'application/json' - } - } - ) - .then(r => r.data) - .catch(console.log) +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const crypto = require('crypto') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +const WEB_CLIENT_SECRET = Buffer.from('dPr0QImQ7bc5o9LMntNba2DOsSbZcjUh') +const WEB_CLIENT_IV = Buffer.from('C8RWsrtFsoeyCyPt') + +module.exports = { + site: 'vidio.com', + days: 2, + url({ date, channel }) { + return `https://api.vidio.com/livestreamings/${channel.site_id}/schedules?filter[date]=${date.format('YYYY-MM-DD')}` + }, + request: { + async headers() { + const session = await loadSessionDetails() + if (!session || !session.api_key) return null + + var cipher = crypto.createCipheriv('aes-256-cbc', WEB_CLIENT_SECRET, WEB_CLIENT_IV) + return { + 'X-API-Key': cipher.update(session.api_key, 'utf8', 'base64') + cipher.final('base64'), + 'X-Secure-Level': 2 + } + } + }, + parser({ content }) { + const programs = [] + const json = JSON.parse(content) + if (Array.isArray(json?.data)) { + for (const program of json.data) { + programs.push({ + title: program.attributes.title, + description: program.attributes.description, + start: dayjs(program.attributes.start_time), + stop: dayjs(program.attributes.end_time), + image: program.attributes.image_landscape_url + }) + } + } + + return programs + }, + async channels() { + const channels = [] + const json = await axios + .get( + 'https://api.vidio.com/livestreamings?stream_type=tv_stream', + { + headers: await this.request.headers() + } + ) + .then(response => response.data) + .catch(console.error) + + if (Array.isArray(json?.data)) { + for (const channel of json.data) { + channels.push({ + lang: 'id', + site_id: channel.id, + name: channel.attributes.title + }) + } + } + + return channels + } +} + +function loadSessionDetails() { + return axios + .post( + 'https://www.vidio.com/auth', + {}, + { + headers: { + 'Content-Type': 'application/json' + } + } + ) + .then(r => r.data) + .catch(console.log) } \ No newline at end of file diff --git a/sites/vidio.com/vidio.com.test.js b/sites/vidio.com/vidio.com.test.js index 2e6689eb5..58a66e65a 100644 --- a/sites/vidio.com/vidio.com.test.js +++ b/sites/vidio.com/vidio.com.test.js @@ -1,67 +1,67 @@ -const { parser, url, request } = require('./vidio.com.config.js') -const fs = require('fs') -const path = require('path') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -const date = dayjs.utc('2025-07-01', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '204', - xmltv_id: 'SCTV.id' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://api.vidio.com/livestreamings/204/schedules?filter[date]=2025-07-01' - ) -}) - -it('can generate valid request headers', async () => { - axios.post.mockImplementation(url => { - if (url === 'https://www.vidio.com/auth') { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/auth.json'))) - }) - } else { - return Promise.resolve({ data: '' }) - } - }) - - const result = await request.headers() - expect(result).toMatchObject({ - 'X-API-Key': - 'CH1ZFsN4N/MIfAds1DL9mP151CNqIpWHqZGRr+LkvUyiq3FRPuP1Kt6aK+pG3nEC1FXt0ZAAJ5FKP8QU8CZ5/jQdSYLVeFwl9NoIkegVpR6b7W2ZwbaF00OPr6ON1/FpLQ3RiUzTPpAqe7f+fwhOr0KrKy8PpCa54OHogaEjI3w=', - 'X-Secure-Level': 2, - }) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const results = parser({ content, channel, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(21) - expect(results[0]).toMatchObject({ - start: '2025-06-30T15:57:00.000Z', - stop: '2025-06-30T17:29:00.000Z', - title: 'Ftv PrimeTime : Cinta Dodol Inilah Yang Membuatku Lengket Padamu', - description: 'Film televisi yang mengangkat kisah romantisme kehidupan dengan konflik yang menarik. tayang setiap hari' - }) -}) - -it('can handle empty guide', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - const results = parser({ content, channel }) - - expect(results).toMatchObject([]) -}) +const { parser, url, request } = require('./vidio.com.config.js') +const fs = require('fs') +const path = require('path') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.utc('2025-07-01', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '204', + xmltv_id: 'SCTV.id' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://api.vidio.com/livestreamings/204/schedules?filter[date]=2025-07-01' + ) +}) + +it('can generate valid request headers', async () => { + axios.post.mockImplementation(url => { + if (url === 'https://www.vidio.com/auth') { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/auth.json'))) + }) + } else { + return Promise.resolve({ data: '' }) + } + }) + + const result = await request.headers() + expect(result).toMatchObject({ + 'X-API-Key': + 'CH1ZFsN4N/MIfAds1DL9mP151CNqIpWHqZGRr+LkvUyiq3FRPuP1Kt6aK+pG3nEC1FXt0ZAAJ5FKP8QU8CZ5/jQdSYLVeFwl9NoIkegVpR6b7W2ZwbaF00OPr6ON1/FpLQ3RiUzTPpAqe7f+fwhOr0KrKy8PpCa54OHogaEjI3w=', + 'X-Secure-Level': 2, + }) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const results = parser({ content, channel, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(21) + expect(results[0]).toMatchObject({ + start: '2025-06-30T15:57:00.000Z', + stop: '2025-06-30T17:29:00.000Z', + title: 'Ftv PrimeTime : Cinta Dodol Inilah Yang Membuatku Lengket Padamu', + description: 'Film televisi yang mengangkat kisah romantisme kehidupan dengan konflik yang menarik. tayang setiap hari' + }) +}) + +it('can handle empty guide', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + const results = parser({ content, channel }) + + expect(results).toMatchObject([]) +}) diff --git a/tests/__data__/expected/epg_grab/base.guide.xml b/tests/__data__/expected/epg_grab/base.guide.xml index 09471f36b..0f9c2ca03 100644 --- a/tests/__data__/expected/epg_grab/base.guide.xml +++ b/tests/__data__/expected/epg_grab/base.guide.xml @@ -1,9 +1,9 @@ - -Channel 2https://example.com36 -Channel 1https://example.com -Channel 1https://example.com -Programme1 (example.com) -Program1 (example.com) -Programme1 (example.com) -Program1 (example.com) + +Channel 2https://example.com36 +Channel 1https://example.com +Channel 1https://example.com +Programme1 (example.com) +Program1 (example.com) +Programme1 (example.com) +Program1 (example.com) \ No newline at end of file diff --git a/tests/__data__/expected/epg_grab/custom_channels.guide.xml b/tests/__data__/expected/epg_grab/custom_channels.guide.xml index e2c7bf4ce..dd84f7dcc 100644 --- a/tests/__data__/expected/epg_grab/custom_channels.guide.xml +++ b/tests/__data__/expected/epg_grab/custom_channels.guide.xml @@ -1,15 +1,15 @@ - -Custom Channel 1https://example.com -Custom Channel 2https://example.com -Channel 1https://example.com -Channel 3https://example2.com -Channel 4https://example2.com -Channel 1https://example2.com -Programme1 (example.com) -Program1 (example.com) -Programme1 (example2.com) -Programme1 (example.com) -Program1 (example.com) -Program1 (example2.com) -Program1 (example2.com) + +Custom Channel 1https://example.com +Custom Channel 2https://example.com +Channel 1https://example.com +Channel 3https://example2.com +Channel 4https://example2.com +Channel 1https://example2.com +Programme1 (example.com) +Program1 (example.com) +Programme1 (example2.com) +Programme1 (example.com) +Program1 (example.com) +Program1 (example2.com) +Program1 (example2.com) \ No newline at end of file diff --git a/tests/__data__/expected/epg_grab/guides/en/example.com.xml b/tests/__data__/expected/epg_grab/guides/en/example.com.xml index c12708245..8b20e42a8 100644 --- a/tests/__data__/expected/epg_grab/guides/en/example.com.xml +++ b/tests/__data__/expected/epg_grab/guides/en/example.com.xml @@ -1,6 +1,6 @@ - -Channel 2https://example.com36 -Channel 1https://example.com -Program1 (example.com) -Program1 (example.com) + +Channel 2https://example.com36 +Channel 1https://example.com +Program1 (example.com) +Program1 (example.com) \ No newline at end of file diff --git a/tests/__data__/expected/epg_grab/lang.guide.xml b/tests/__data__/expected/epg_grab/lang.guide.xml index dc4b8238b..7016b8e87 100644 --- a/tests/__data__/expected/epg_grab/lang.guide.xml +++ b/tests/__data__/expected/epg_grab/lang.guide.xml @@ -1,8 +1,8 @@ - -Channel 1https://example.com -Channel 3https://example.com -Programme1 (example.com) -Programme1 (example.com) -Program1 (example.com) -Program1 (example.com) + +Channel 1https://example.com +Channel 3https://example.com +Programme1 (example.com) +Programme1 (example.com) +Program1 (example.com) +Program1 (example.com) \ No newline at end of file diff --git a/tests/__data__/expected/epg_grab/proxy.guide.xml b/tests/__data__/expected/epg_grab/proxy.guide.xml index 09471f36b..0f9c2ca03 100644 --- a/tests/__data__/expected/epg_grab/proxy.guide.xml +++ b/tests/__data__/expected/epg_grab/proxy.guide.xml @@ -1,9 +1,9 @@ - -Channel 2https://example.com36 -Channel 1https://example.com -Channel 1https://example.com -Programme1 (example.com) -Program1 (example.com) -Programme1 (example.com) -Program1 (example.com) + +Channel 2https://example.com36 +Channel 1https://example.com +Channel 1https://example.com +Programme1 (example.com) +Program1 (example.com) +Programme1 (example.com) +Program1 (example.com) \ No newline at end of file diff --git a/tests/__data__/expected/epg_grab/template.guide.xml b/tests/__data__/expected/epg_grab/template.guide.xml index cd54510cd..8eb794112 100644 --- a/tests/__data__/expected/epg_grab/template.guide.xml +++ b/tests/__data__/expected/epg_grab/template.guide.xml @@ -1,15 +1,15 @@ - -Channel 2https://example.com36 -Channel 1https://example.com -Channel 1https://example.com -Channel 3https://example2.com -Channel 4https://example2.com -Channel 1https://example2.com -Programme1 (example.com) -Program1 (example.com) -Programme1 (example2.com) -Programme1 (example.com) -Program1 (example.com) -Program1 (example2.com) -Program1 (example2.com) + +Channel 2https://example.com36 +Channel 1https://example.com +Channel 1https://example.com +Channel 3https://example2.com +Channel 4https://example2.com +Channel 1https://example2.com +Programme1 (example.com) +Program1 (example.com) +Programme1 (example2.com) +Programme1 (example.com) +Program1 (example.com) +Program1 (example2.com) +Program1 (example2.com) \ No newline at end of file diff --git a/tests/__data__/input/__data__/channels.json b/tests/__data__/input/__data__/channels.json index d838427f1..d2e6a4d5d 100644 --- a/tests/__data__/input/__data__/channels.json +++ b/tests/__data__/input/__data__/channels.json @@ -1,61 +1,61 @@ -[ - { - "id": "Bravo.us", - "name": "Bravo", - "network": null, - "country": "US", - "subdivision": null, - "city": null, - "categories": [], - "is_nsfw": false, - "closed": "2020-01-01", - "replaced_by": "R6.co" - }, - { - "id": "Bravos.us", - "name": "Bravos", - "network": null, - "country": "US", - "subdivision": null, - "city": null, - "categories": [], - "is_nsfw": false - }, - { - "id": "CNNInternational.us", - "name": "CNN International", - "alt_names": ["CNN", "CNN Int"], - "network": null, - "country": "US", - "subdivision": null, - "city": null, - "categories": [ - "news" - ], - "is_nsfw": false - }, - { - "id": "MNetMovies2.za", - "name": "M-Net Movies 2", - "network": null, - "country": "ZA", - "subdivision": null, - "city": null, - "categories": [], - "is_nsfw": false - }, - {"id":"6eren.dk","name":"6'eren","alt_names":[],"network":null,"owners":["Warner Bros. Discovery EMEA"],"country":"DK","subdivision":null,"city":null,"broadcast_area":["c/DK"],"languages":["dan"],"categories":[],"is_nsfw":false,"launched":"2009-01-01","closed":null,"replaced_by":null,"website":"http://www.6-eren.dk/"}, - {"id":"BBCNews.uk","name":"BBC News","alt_names":[],"network":null,"owners":[],"country":"UK","subdivision":null,"city":null,"broadcast_area":["c/UK"],"languages":["eng"],"categories":["news"],"is_nsfw":false,"launched":null,"closed":null,"replaced_by":null,"website":"http://news.bbc.co.uk/"}, - { - "id": "CNN.us", - "name": "CNN", - "network": null, - "country": "US", - "subdivision": null, - "city": null, - "categories": [], - "is_nsfw": false - }, - {"id":"Channel2.us","name":"Channel 2 [API]","alt_names":[],"network":null,"owners":[],"country":"UK","subdivision":null,"city":null,"broadcast_area":["c/US"],"languages":["eng"],"categories":[],"is_nsfw":false,"launched":null,"closed":null,"replaced_by":null,"website":""}, - {"id":"Channel3.us","name":"Channel 3 [API]","alt_names":[],"network":null,"owners":[],"country":"UK","subdivision":null,"city":null,"broadcast_area":["c/US"],"languages":["eng"],"categories":[],"is_nsfw":false,"launched":null,"closed":null,"replaced_by":null,"website":""} +[ + { + "id": "Bravo.us", + "name": "Bravo", + "network": null, + "country": "US", + "subdivision": null, + "city": null, + "categories": [], + "is_nsfw": false, + "closed": "2020-01-01", + "replaced_by": "R6.co" + }, + { + "id": "Bravos.us", + "name": "Bravos", + "network": null, + "country": "US", + "subdivision": null, + "city": null, + "categories": [], + "is_nsfw": false + }, + { + "id": "CNNInternational.us", + "name": "CNN International", + "alt_names": ["CNN", "CNN Int"], + "network": null, + "country": "US", + "subdivision": null, + "city": null, + "categories": [ + "news" + ], + "is_nsfw": false + }, + { + "id": "MNetMovies2.za", + "name": "M-Net Movies 2", + "network": null, + "country": "ZA", + "subdivision": null, + "city": null, + "categories": [], + "is_nsfw": false + }, + {"id":"6eren.dk","name":"6'eren","alt_names":[],"network":null,"owners":["Warner Bros. Discovery EMEA"],"country":"DK","subdivision":null,"city":null,"broadcast_area":["c/DK"],"languages":["dan"],"categories":[],"is_nsfw":false,"launched":"2009-01-01","closed":null,"replaced_by":null,"website":"http://www.6-eren.dk/"}, + {"id":"BBCNews.uk","name":"BBC News","alt_names":[],"network":null,"owners":[],"country":"UK","subdivision":null,"city":null,"broadcast_area":["c/UK"],"languages":["eng"],"categories":["news"],"is_nsfw":false,"launched":null,"closed":null,"replaced_by":null,"website":"http://news.bbc.co.uk/"}, + { + "id": "CNN.us", + "name": "CNN", + "network": null, + "country": "US", + "subdivision": null, + "city": null, + "categories": [], + "is_nsfw": false + }, + {"id":"Channel2.us","name":"Channel 2 [API]","alt_names":[],"network":null,"owners":[],"country":"UK","subdivision":null,"city":null,"broadcast_area":["c/US"],"languages":["eng"],"categories":[],"is_nsfw":false,"launched":null,"closed":null,"replaced_by":null,"website":""}, + {"id":"Channel3.us","name":"Channel 3 [API]","alt_names":[],"network":null,"owners":[],"country":"UK","subdivision":null,"city":null,"broadcast_area":["c/US"],"languages":["eng"],"categories":[],"is_nsfw":false,"launched":null,"closed":null,"replaced_by":null,"website":""} ] \ No newline at end of file diff --git a/tests/__data__/input/epg_grab/example.com/example.com.config.js b/tests/__data__/input/epg_grab/example.com/example.com.config.js index 52370045e..107a761e5 100644 --- a/tests/__data__/input/epg_grab/example.com/example.com.config.js +++ b/tests/__data__/input/epg_grab/example.com/example.com.config.js @@ -1,28 +1,28 @@ -module.exports = { - site: 'example.com', - days: 2, - request: { - timeout: 1000 - }, - url: 'https://example.com', - parser({ channel, date }) { - if (channel.xmltv_id === 'Channel2.us') return [] - else if (channel.xmltv_id === 'Channel1.us' && channel.lang === 'fr') { - return [ - { - title: 'Programme1 (example.com)', - start: `${date.format('YYYY-MM-DD')}T04:30:00.000Z`, - stop: `${date.format('YYYY-MM-DD')}T07:10:00.000Z` - } - ] - } - - return [ - { - title: 'Program1 (example.com)', - start: `${date.format('YYYY-MM-DD')}T04:31:00.000Z`, - stop: `${date.format('YYYY-MM-DD')}T07:10:00.000Z` - } - ] - } -} +module.exports = { + site: 'example.com', + days: 2, + request: { + timeout: 1000 + }, + url: 'https://example.com', + parser({ channel, date }) { + if (channel.xmltv_id === 'Channel2.us') return [] + else if (channel.xmltv_id === 'Channel1.us' && channel.lang === 'fr') { + return [ + { + title: 'Programme1 (example.com)', + start: `${date.format('YYYY-MM-DD')}T04:30:00.000Z`, + stop: `${date.format('YYYY-MM-DD')}T07:10:00.000Z` + } + ] + } + + return [ + { + title: 'Program1 (example.com)', + start: `${date.format('YYYY-MM-DD')}T04:31:00.000Z`, + stop: `${date.format('YYYY-MM-DD')}T07:10:00.000Z` + } + ] + } +} diff --git a/tests/__data__/input/epg_grab/sites/example.com/example.com.config.js b/tests/__data__/input/epg_grab/sites/example.com/example.com.config.js index 52370045e..107a761e5 100644 --- a/tests/__data__/input/epg_grab/sites/example.com/example.com.config.js +++ b/tests/__data__/input/epg_grab/sites/example.com/example.com.config.js @@ -1,28 +1,28 @@ -module.exports = { - site: 'example.com', - days: 2, - request: { - timeout: 1000 - }, - url: 'https://example.com', - parser({ channel, date }) { - if (channel.xmltv_id === 'Channel2.us') return [] - else if (channel.xmltv_id === 'Channel1.us' && channel.lang === 'fr') { - return [ - { - title: 'Programme1 (example.com)', - start: `${date.format('YYYY-MM-DD')}T04:30:00.000Z`, - stop: `${date.format('YYYY-MM-DD')}T07:10:00.000Z` - } - ] - } - - return [ - { - title: 'Program1 (example.com)', - start: `${date.format('YYYY-MM-DD')}T04:31:00.000Z`, - stop: `${date.format('YYYY-MM-DD')}T07:10:00.000Z` - } - ] - } -} +module.exports = { + site: 'example.com', + days: 2, + request: { + timeout: 1000 + }, + url: 'https://example.com', + parser({ channel, date }) { + if (channel.xmltv_id === 'Channel2.us') return [] + else if (channel.xmltv_id === 'Channel1.us' && channel.lang === 'fr') { + return [ + { + title: 'Programme1 (example.com)', + start: `${date.format('YYYY-MM-DD')}T04:30:00.000Z`, + stop: `${date.format('YYYY-MM-DD')}T07:10:00.000Z` + } + ] + } + + return [ + { + title: 'Program1 (example.com)', + start: `${date.format('YYYY-MM-DD')}T04:31:00.000Z`, + stop: `${date.format('YYYY-MM-DD')}T07:10:00.000Z` + } + ] + } +} diff --git a/tests/__data__/input/epg_grab/sites/example2.com/example2.com.config.js b/tests/__data__/input/epg_grab/sites/example2.com/example2.com.config.js index 9e4f58d39..b724fa199 100644 --- a/tests/__data__/input/epg_grab/sites/example2.com/example2.com.config.js +++ b/tests/__data__/input/epg_grab/sites/example2.com/example2.com.config.js @@ -1,23 +1,23 @@ -module.exports = { - site: 'example2.com', - url: 'https://example2.com', - parser({ channel, date }) { - if (channel.lang === 'fr') { - return [ - { - title: 'Programme1 (example2.com)', - start: `${date.format('YYYY-MM-DD')}T04:40:00.000Z`, - stop: `${date.format('YYYY-MM-DD')}T07:10:00.000Z` - } - ] - } - - return [ - { - title: 'Program1 (example2.com)', - start: `${date.format('YYYY-MM-DD')}T04:31:00.000Z`, - stop: `${date.format('YYYY-MM-DD')}T07:10:00.000Z` - } - ] - } -} +module.exports = { + site: 'example2.com', + url: 'https://example2.com', + parser({ channel, date }) { + if (channel.lang === 'fr') { + return [ + { + title: 'Programme1 (example2.com)', + start: `${date.format('YYYY-MM-DD')}T04:40:00.000Z`, + stop: `${date.format('YYYY-MM-DD')}T07:10:00.000Z` + } + ] + } + + return [ + { + title: 'Program1 (example2.com)', + start: `${date.format('YYYY-MM-DD')}T04:31:00.000Z`, + stop: `${date.format('YYYY-MM-DD')}T07:10:00.000Z` + } + ] + } +} diff --git a/tests/commands/api/generate.test.ts b/tests/commands/api/generate.test.ts index b129ed262..2044f0d9c 100644 --- a/tests/commands/api/generate.test.ts +++ b/tests/commands/api/generate.test.ts @@ -1,27 +1,27 @@ -import { execSync } from 'child_process' -import fs from 'fs-extra' -import { pathToFileURL } from 'node:url' - -const ENV_VAR = 'cross-env SITES_DIR=tests/__data__/input/api_generate/sites API_DIR=tests/__data__/output' - -beforeEach(() => { - fs.emptyDirSync('tests/__data__/output') -}) - -describe('api:generate', () => { - it('can generate guides.json', () => { - const cmd = `${ENV_VAR} npm run api:generate` - const stdout = execSync(cmd, { encoding: 'utf8' }) - if (process.env.DEBUG === 'true') console.log(cmd, stdout) - - expect(content('tests/__data__/output/guides.json')).toEqual( - content('tests/__data__/expected/api_generate/guides.json') - ) - }) -}) - -function content(filepath: string) { - return fs.readFileSync(pathToFileURL(filepath), { - encoding: 'utf8' - }) -} +import { execSync } from 'child_process' +import fs from 'fs-extra' +import { pathToFileURL } from 'node:url' + +const ENV_VAR = 'cross-env SITES_DIR=tests/__data__/input/api_generate/sites API_DIR=tests/__data__/output' + +beforeEach(() => { + fs.emptyDirSync('tests/__data__/output') +}) + +describe('api:generate', () => { + it('can generate guides.json', () => { + const cmd = `${ENV_VAR} npm run api:generate` + const stdout = execSync(cmd, { encoding: 'utf8' }) + if (process.env.DEBUG === 'true') console.log(cmd, stdout) + + expect(content('tests/__data__/output/guides.json')).toEqual( + content('tests/__data__/expected/api_generate/guides.json') + ) + }) +}) + +function content(filepath: string) { + return fs.readFileSync(pathToFileURL(filepath), { + encoding: 'utf8' + }) +} diff --git a/tests/commands/channels/edit.test.ts b/tests/commands/channels/edit.test.ts index 0c6405435..b150648a7 100644 --- a/tests/commands/channels/edit.test.ts +++ b/tests/commands/channels/edit.test.ts @@ -1,36 +1,36 @@ -import { execSync } from 'child_process' -import fs from 'fs-extra' - -const ENV_VAR = 'cross-env DATA_DIR=tests/__data__/input/__data__' - -beforeEach(() => { - fs.emptyDirSync('tests/__data__/output') - fs.copySync( - 'tests/__data__/input/channels_edit/example.com.channels.xml', - 'tests/__data__/output/channels.xml' - ) -}) - -describe('channels:edit', () => { - it('shows list of options for a channel', () => { - const cmd = `${ENV_VAR} npm run channels:edit --- tests/__data__/output/channels.xml` - try { - const stdout = execSync(cmd, { encoding: 'utf8' }) - if (process.env.DEBUG === 'true') console.log(cmd, stdout) - checkStdout(stdout) - } catch (error: unknown) { - // NOTE: for Windows only - if (process.env.DEBUG === 'true') console.log(cmd, error) - if (error && typeof error === 'object' && 'stdout' in error) { - checkStdout(error.stdout as string) - } - } - }) -}) - -function checkStdout(stdout: string) { - expect(stdout).toContain('CNNInternational.us (CNN International, CNN, CNN Int)') - expect(stdout).toContain('Type...') - expect(stdout).toContain('Skip') - expect(stdout).toContain("File 'tests/__data__/output/channels.xml' successfully saved") -} +import { execSync } from 'child_process' +import fs from 'fs-extra' + +const ENV_VAR = 'cross-env DATA_DIR=tests/__data__/input/__data__' + +beforeEach(() => { + fs.emptyDirSync('tests/__data__/output') + fs.copySync( + 'tests/__data__/input/channels_edit/example.com.channels.xml', + 'tests/__data__/output/channels.xml' + ) +}) + +describe('channels:edit', () => { + it('shows list of options for a channel', () => { + const cmd = `${ENV_VAR} npm run channels:edit --- tests/__data__/output/channels.xml` + try { + const stdout = execSync(cmd, { encoding: 'utf8' }) + if (process.env.DEBUG === 'true') console.log(cmd, stdout) + checkStdout(stdout) + } catch (error: unknown) { + // NOTE: for Windows only + if (process.env.DEBUG === 'true') console.log(cmd, error) + if (error && typeof error === 'object' && 'stdout' in error) { + checkStdout(error.stdout as string) + } + } + }) +}) + +function checkStdout(stdout: string) { + expect(stdout).toContain('CNNInternational.us (CNN International, CNN, CNN Int)') + expect(stdout).toContain('Type...') + expect(stdout).toContain('Skip') + expect(stdout).toContain("File 'tests/__data__/output/channels.xml' successfully saved") +} diff --git a/tests/commands/epg/grab.test.ts b/tests/commands/epg/grab.test.ts index 3459f99a6..37d35da1e 100644 --- a/tests/commands/epg/grab.test.ts +++ b/tests/commands/epg/grab.test.ts @@ -1,162 +1,162 @@ -import { pathToFileURL } from 'node:url' -import { execSync } from 'child_process' -import { Zip } from '@freearhey/core' -import fs from 'fs-extra' -import path from 'path' - -const ENV_VAR = - 'cross-env SITES_DIR=tests/__data__/input/epg_grab/sites CURR_DATE=2022-10-20 DATA_DIR=tests/__data__/input/__data__' - -beforeEach(() => { - fs.emptyDirSync('tests/__data__/output') -}) - -describe('epg:grab', () => { - it('can grab epg by site name', () => { - const cmd = `${ENV_VAR} npm run grab --- --site=example.com --output="${path.resolve( - 'tests/__data__/output/guide.xml' - )}" --timeout=100` - const stdout = execSync(cmd, { encoding: 'utf8' }) - if (process.env.DEBUG === 'true') console.log(cmd, stdout) - - expect(content('tests/__data__/output/guide.xml')).toEqual( - content('tests/__data__/expected/epg_grab/base.guide.xml') - ) - }) - - it('it will raise an error if the timeout is exceeded', () => { - const cmd = `${ENV_VAR} npm run grab --- --channels=tests/__data__/input/epg_grab/custom.channels.xml --output=tests/__data__/output/guide.xml --timeout=0` - let errorThrown = false - try { - execSync(cmd, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }) - // If no error is thrown, explicitly fail the test - fail('Expected command to throw an error due to timeout, but it did not.') - } catch (error) { - errorThrown = true - if (process.env.DEBUG === 'true') { - const stderr = error.stderr?.toString() || '' - const stdout = error.stdout?.toString() || '' - const combined = stderr + stdout - console.log('stdout:', stdout) - console.log('stderr:', stderr) - console.log('combined:', combined) - console.log('exit code:', error.exitCode) - console.log('Error output:', combined) - } - } - expect(errorThrown).toBe(true) - }) - - it('can grab epg with wildcard as output', () => { - const cmd = `${ENV_VAR} npm run grab --- --channels="tests/__data__/input/epg_grab/sites/example.com/example.com.channels.xml" --output="tests/__data__/output/guides/{lang}/{site}.xml" --timeout=100` - const stdout = execSync(cmd, { encoding: 'utf8' }) - if (process.env.DEBUG === 'true') console.log(cmd, stdout) - - expect(content('tests/__data__/output/guides/en/example.com.xml')).toEqual( - content('tests/__data__/expected/epg_grab/guides/en/example.com.xml') - ) - - expect(content('tests/__data__/output/guides/fr/example.com.xml')).toEqual( - content('tests/__data__/expected/epg_grab/guides/fr/example.com.xml') - ) - }) - - it('can grab epg then language filter enabled', () => { - const cmd = `${ENV_VAR} npm run grab --- --channels=tests/__data__/input/epg_grab/sites/example.com/example.com.channels.xml --output=tests/__data__/output/guides/{lang}/{site}.xml --lang=fr --timeout=100` - const stdout = execSync(cmd, { encoding: 'utf8' }) - if (process.env.DEBUG === 'true') console.log(cmd, stdout) - - expect(content('tests/__data__/output/guides/fr/example.com.xml')).toEqual( - content('tests/__data__/expected/epg_grab/guides/fr/example.com.xml') - ) - }) - - it('can grab epg then using a multi-language filter', () => { - const cmd = `${ENV_VAR} npm run grab --- --channels=tests/__data__/input/epg_grab/example.com/example.com.channels.xml --output=tests/__data__/output/guides/{site}.xml --lang=fr,it --timeout=100` - const stdout = execSync(cmd, { encoding: 'utf8' }) - if (process.env.DEBUG === 'true') console.log(cmd, stdout) - - expect(content('tests/__data__/output/guides/example.com.xml')).toEqual( - content('tests/__data__/expected/epg_grab/lang.guide.xml') - ) - }) - - it('can grab epg via https proxy', () => { - const cmd = `${ENV_VAR} npm run grab --- --site=example.com --proxy=https://bob:123456@proxy.com:1234 --output="${path.resolve( - 'tests/__data__/output/guide.xml' - )}" --timeout=100` - const stdout = execSync(cmd, { encoding: 'utf8' }) - if (process.env.DEBUG === 'true') console.log(cmd, stdout) - - expect(content('tests/__data__/output/guide.xml')).toEqual( - content('tests/__data__/expected/epg_grab/proxy.guide.xml') - ) - }) - - it('can grab epg via socks5 proxy', () => { - const cmd = `${ENV_VAR} npm run grab --- --site=example.com --proxy=socks5://bob:123456@proxy.com:1234 --output="${path.resolve( - 'tests/__data__/output/guide.xml' - )}" --timeout=100` - const stdout = execSync(cmd, { encoding: 'utf8' }) - if (process.env.DEBUG === 'true') console.log(cmd, stdout) - - expect(content('tests/__data__/output/guide.xml')).toEqual( - content('tests/__data__/expected/epg_grab/proxy.guide.xml') - ) - }) - - it('can grab epg with curl option', () => { - const cmd = `${ENV_VAR} npm run grab --- --site=example.com --curl --output="${path.resolve( - 'tests/__data__/output/guide.xml' - )}" --timeout=100` - const stdout = execSync(cmd, { encoding: 'utf8' }) - if (process.env.DEBUG === 'true') console.log(cmd, stdout) - - expect(stdout).toContain('curl https://example.com') - }) - - it('can grab epg with multiple channels.xml files', () => { - const cmd = `${ENV_VAR} npm run grab --- --channels=tests/__data__/input/epg_grab/sites/**/*.channels.xml --output=tests/__data__/output/guide.xml --timeout=100` - const stdout = execSync(cmd, { encoding: 'utf8' }) - if (process.env.DEBUG === 'true') console.log(cmd, stdout) - - expect(content('tests/__data__/output/guide.xml')).toEqual( - content('tests/__data__/expected/epg_grab/template.guide.xml') - ) - }) - - it('can grab epg using custom channels list', () => { - const cmd = `${ENV_VAR} npm run grab --- --channels=tests/__data__/input/epg_grab/custom.channels.xml --output=tests/__data__/output/guide.xml --timeout=100` - const stdout = execSync(cmd, { encoding: 'utf8' }) - if (process.env.DEBUG === 'true') console.log(cmd, stdout) - - expect(content('tests/__data__/output/guide.xml')).toEqual( - content('tests/__data__/expected/epg_grab/custom_channels.guide.xml') - ) - }) - - it('can grab epg with gzip option enabled', () => { - const cmd = `${ENV_VAR} npm run grab --- --channels=tests/__data__/input/epg_grab/sites/**/*.channels.xml --output="${path.resolve( - 'tests/__data__/output/guide.xml' - )}" --gzip --timeout=100` - const stdout = execSync(cmd, { encoding: 'utf8' }) - if (process.env.DEBUG === 'true') console.log(cmd, stdout) - - expect(content('tests/__data__/output/guide.xml')).toEqual( - content('tests/__data__/expected/epg_grab/template.guide.xml') - ) - - const zip = new Zip() - const expected = zip.decompress(fs.readFileSync('tests/__data__/output/guide.xml.gz')) - const result = zip.decompress( - fs.readFileSync('tests/__data__/expected/epg_grab/template.guide.xml.gz') - ) - expect(expected).toEqual(result) - }) -}) - -function content(filepath: string) { - return fs.readFileSync(pathToFileURL(filepath), { - encoding: 'utf8' - }) -} +import { pathToFileURL } from 'node:url' +import { execSync } from 'child_process' +import { Zip } from '@freearhey/core' +import fs from 'fs-extra' +import path from 'path' + +const ENV_VAR = + 'cross-env SITES_DIR=tests/__data__/input/epg_grab/sites CURR_DATE=2022-10-20 DATA_DIR=tests/__data__/input/__data__' + +beforeEach(() => { + fs.emptyDirSync('tests/__data__/output') +}) + +describe('epg:grab', () => { + it('can grab epg by site name', () => { + const cmd = `${ENV_VAR} npm run grab --- --site=example.com --output="${path.resolve( + 'tests/__data__/output/guide.xml' + )}" --timeout=100` + const stdout = execSync(cmd, { encoding: 'utf8' }) + if (process.env.DEBUG === 'true') console.log(cmd, stdout) + + expect(content('tests/__data__/output/guide.xml')).toEqual( + content('tests/__data__/expected/epg_grab/base.guide.xml') + ) + }) + + it('it will raise an error if the timeout is exceeded', () => { + const cmd = `${ENV_VAR} npm run grab --- --channels=tests/__data__/input/epg_grab/custom.channels.xml --output=tests/__data__/output/guide.xml --timeout=0` + let errorThrown = false + try { + execSync(cmd, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }) + // If no error is thrown, explicitly fail the test + fail('Expected command to throw an error due to timeout, but it did not.') + } catch (error) { + errorThrown = true + if (process.env.DEBUG === 'true') { + const stderr = error.stderr?.toString() || '' + const stdout = error.stdout?.toString() || '' + const combined = stderr + stdout + console.log('stdout:', stdout) + console.log('stderr:', stderr) + console.log('combined:', combined) + console.log('exit code:', error.exitCode) + console.log('Error output:', combined) + } + } + expect(errorThrown).toBe(true) + }) + + it('can grab epg with wildcard as output', () => { + const cmd = `${ENV_VAR} npm run grab --- --channels="tests/__data__/input/epg_grab/sites/example.com/example.com.channels.xml" --output="tests/__data__/output/guides/{lang}/{site}.xml" --timeout=100` + const stdout = execSync(cmd, { encoding: 'utf8' }) + if (process.env.DEBUG === 'true') console.log(cmd, stdout) + + expect(content('tests/__data__/output/guides/en/example.com.xml')).toEqual( + content('tests/__data__/expected/epg_grab/guides/en/example.com.xml') + ) + + expect(content('tests/__data__/output/guides/fr/example.com.xml')).toEqual( + content('tests/__data__/expected/epg_grab/guides/fr/example.com.xml') + ) + }) + + it('can grab epg then language filter enabled', () => { + const cmd = `${ENV_VAR} npm run grab --- --channels=tests/__data__/input/epg_grab/sites/example.com/example.com.channels.xml --output=tests/__data__/output/guides/{lang}/{site}.xml --lang=fr --timeout=100` + const stdout = execSync(cmd, { encoding: 'utf8' }) + if (process.env.DEBUG === 'true') console.log(cmd, stdout) + + expect(content('tests/__data__/output/guides/fr/example.com.xml')).toEqual( + content('tests/__data__/expected/epg_grab/guides/fr/example.com.xml') + ) + }) + + it('can grab epg then using a multi-language filter', () => { + const cmd = `${ENV_VAR} npm run grab --- --channels=tests/__data__/input/epg_grab/example.com/example.com.channels.xml --output=tests/__data__/output/guides/{site}.xml --lang=fr,it --timeout=100` + const stdout = execSync(cmd, { encoding: 'utf8' }) + if (process.env.DEBUG === 'true') console.log(cmd, stdout) + + expect(content('tests/__data__/output/guides/example.com.xml')).toEqual( + content('tests/__data__/expected/epg_grab/lang.guide.xml') + ) + }) + + it('can grab epg via https proxy', () => { + const cmd = `${ENV_VAR} npm run grab --- --site=example.com --proxy=https://bob:123456@proxy.com:1234 --output="${path.resolve( + 'tests/__data__/output/guide.xml' + )}" --timeout=100` + const stdout = execSync(cmd, { encoding: 'utf8' }) + if (process.env.DEBUG === 'true') console.log(cmd, stdout) + + expect(content('tests/__data__/output/guide.xml')).toEqual( + content('tests/__data__/expected/epg_grab/proxy.guide.xml') + ) + }) + + it('can grab epg via socks5 proxy', () => { + const cmd = `${ENV_VAR} npm run grab --- --site=example.com --proxy=socks5://bob:123456@proxy.com:1234 --output="${path.resolve( + 'tests/__data__/output/guide.xml' + )}" --timeout=100` + const stdout = execSync(cmd, { encoding: 'utf8' }) + if (process.env.DEBUG === 'true') console.log(cmd, stdout) + + expect(content('tests/__data__/output/guide.xml')).toEqual( + content('tests/__data__/expected/epg_grab/proxy.guide.xml') + ) + }) + + it('can grab epg with curl option', () => { + const cmd = `${ENV_VAR} npm run grab --- --site=example.com --curl --output="${path.resolve( + 'tests/__data__/output/guide.xml' + )}" --timeout=100` + const stdout = execSync(cmd, { encoding: 'utf8' }) + if (process.env.DEBUG === 'true') console.log(cmd, stdout) + + expect(stdout).toContain('curl https://example.com') + }) + + it('can grab epg with multiple channels.xml files', () => { + const cmd = `${ENV_VAR} npm run grab --- --channels=tests/__data__/input/epg_grab/sites/**/*.channels.xml --output=tests/__data__/output/guide.xml --timeout=100` + const stdout = execSync(cmd, { encoding: 'utf8' }) + if (process.env.DEBUG === 'true') console.log(cmd, stdout) + + expect(content('tests/__data__/output/guide.xml')).toEqual( + content('tests/__data__/expected/epg_grab/template.guide.xml') + ) + }) + + it('can grab epg using custom channels list', () => { + const cmd = `${ENV_VAR} npm run grab --- --channels=tests/__data__/input/epg_grab/custom.channels.xml --output=tests/__data__/output/guide.xml --timeout=100` + const stdout = execSync(cmd, { encoding: 'utf8' }) + if (process.env.DEBUG === 'true') console.log(cmd, stdout) + + expect(content('tests/__data__/output/guide.xml')).toEqual( + content('tests/__data__/expected/epg_grab/custom_channels.guide.xml') + ) + }) + + it('can grab epg with gzip option enabled', () => { + const cmd = `${ENV_VAR} npm run grab --- --channels=tests/__data__/input/epg_grab/sites/**/*.channels.xml --output="${path.resolve( + 'tests/__data__/output/guide.xml' + )}" --gzip --timeout=100` + const stdout = execSync(cmd, { encoding: 'utf8' }) + if (process.env.DEBUG === 'true') console.log(cmd, stdout) + + expect(content('tests/__data__/output/guide.xml')).toEqual( + content('tests/__data__/expected/epg_grab/template.guide.xml') + ) + + const zip = new Zip() + const expected = zip.decompress(fs.readFileSync('tests/__data__/output/guide.xml.gz')) + const result = zip.decompress( + fs.readFileSync('tests/__data__/expected/epg_grab/template.guide.xml.gz') + ) + expect(expected).toEqual(result) + }) +}) + +function content(filepath: string) { + return fs.readFileSync(pathToFileURL(filepath), { + encoding: 'utf8' + }) +} diff --git a/tests/commands/sites/init.test.ts b/tests/commands/sites/init.test.ts index caae889bf..3149481b8 100644 --- a/tests/commands/sites/init.test.ts +++ b/tests/commands/sites/init.test.ts @@ -1,41 +1,41 @@ -import { execSync } from 'child_process' -import fs from 'fs-extra' -import { pathToFileURL } from 'node:url' - -const ENV_VAR = 'cross-env SITES_DIR=tests/__data__/output/sites' - -beforeEach(() => { - fs.emptyDirSync('tests/__data__/output') - fs.mkdirSync('tests/__data__/output/sites') -}) - -it('can create new site config from template', () => { - const cmd = `${ENV_VAR} npm run sites:init --- example.com` - - const stdout = execSync(cmd, { encoding: 'utf8' }) - if (process.env.DEBUG === 'true') console.log(cmd, stdout) - - expect(exists('tests/__data__/output/sites/example.com')).toBe(true) - expect(exists('tests/__data__/output/sites/example.com/example.com.test.js')).toBe(true) - expect(exists('tests/__data__/output/sites/example.com/example.com.config.js')).toBe(true) - expect(exists('tests/__data__/output/sites/example.com/readme.md')).toBe(true) - expect(content('tests/__data__/output/sites/example.com/example.com.test.js')).toEqual( - content('tests/__data__/expected/sites_init/example.com.test.js') - ) - expect(content('tests/__data__/output/sites/example.com/example.com.config.js')).toEqual( - content('tests/__data__/expected/sites_init/example.com.config.js') - ) - expect(content('tests/__data__/output/sites/example.com/readme.md')).toEqual( - content('tests/__data__/expected/sites_init/readme.md') - ) -}) - -function content(filepath: string) { - return fs.readFileSync(pathToFileURL(filepath), { - encoding: 'utf8' - }) -} - -function exists(filepath: string) { - return fs.existsSync(pathToFileURL(filepath)) -} +import { execSync } from 'child_process' +import fs from 'fs-extra' +import { pathToFileURL } from 'node:url' + +const ENV_VAR = 'cross-env SITES_DIR=tests/__data__/output/sites' + +beforeEach(() => { + fs.emptyDirSync('tests/__data__/output') + fs.mkdirSync('tests/__data__/output/sites') +}) + +it('can create new site config from template', () => { + const cmd = `${ENV_VAR} npm run sites:init --- example.com` + + const stdout = execSync(cmd, { encoding: 'utf8' }) + if (process.env.DEBUG === 'true') console.log(cmd, stdout) + + expect(exists('tests/__data__/output/sites/example.com')).toBe(true) + expect(exists('tests/__data__/output/sites/example.com/example.com.test.js')).toBe(true) + expect(exists('tests/__data__/output/sites/example.com/example.com.config.js')).toBe(true) + expect(exists('tests/__data__/output/sites/example.com/readme.md')).toBe(true) + expect(content('tests/__data__/output/sites/example.com/example.com.test.js')).toEqual( + content('tests/__data__/expected/sites_init/example.com.test.js') + ) + expect(content('tests/__data__/output/sites/example.com/example.com.config.js')).toEqual( + content('tests/__data__/expected/sites_init/example.com.config.js') + ) + expect(content('tests/__data__/output/sites/example.com/readme.md')).toEqual( + content('tests/__data__/expected/sites_init/readme.md') + ) +}) + +function content(filepath: string) { + return fs.readFileSync(pathToFileURL(filepath), { + encoding: 'utf8' + }) +} + +function exists(filepath: string) { + return fs.existsSync(pathToFileURL(filepath)) +} diff --git a/tests/commands/sites/update.test.ts b/tests/commands/sites/update.test.ts index a11a0bada..2a553a204 100644 --- a/tests/commands/sites/update.test.ts +++ b/tests/commands/sites/update.test.ts @@ -1,28 +1,28 @@ -import { execSync } from 'child_process' -import fs from 'fs-extra' -import { pathToFileURL } from 'node:url' - -const ENV_VAR = 'cross-env SITES_DIR=tests/__data__/input/sites_update/sites ROOT_DIR=tests/__data__/output' - -beforeEach(() => { - fs.emptyDirSync('tests/__data__/output') -}) - -it('can update SITES.md', () => { - const cmd = `${ENV_VAR} npm run sites:update` - - const stdout = execSync(cmd, { encoding: 'utf8' }) - if (process.env.DEBUG === 'true') console.log(cmd, stdout) - - expect(content('tests/__data__/output/SITES.md')).toEqual( - content('tests/__data__/expected/sites_update/SITES.md') - ) -}) - -function content(filepath: string) { - const data = fs.readFileSync(pathToFileURL(filepath), { - encoding: 'utf8' - }) - - return JSON.stringify(data) -} +import { execSync } from 'child_process' +import fs from 'fs-extra' +import { pathToFileURL } from 'node:url' + +const ENV_VAR = 'cross-env SITES_DIR=tests/__data__/input/sites_update/sites ROOT_DIR=tests/__data__/output' + +beforeEach(() => { + fs.emptyDirSync('tests/__data__/output') +}) + +it('can update SITES.md', () => { + const cmd = `${ENV_VAR} npm run sites:update` + + const stdout = execSync(cmd, { encoding: 'utf8' }) + if (process.env.DEBUG === 'true') console.log(cmd, stdout) + + expect(content('tests/__data__/output/SITES.md')).toEqual( + content('tests/__data__/expected/sites_update/SITES.md') + ) +}) + +function content(filepath: string) { + const data = fs.readFileSync(pathToFileURL(filepath), { + encoding: 'utf8' + }) + + return JSON.stringify(data) +} From 29aa427923aaac4492376b492f377256e88e7ce1 Mon Sep 17 00:00:00 2001 From: freearhey <7253922+freearhey@users.noreply.github.com> Date: Thu, 31 Jul 2025 22:29:01 +0300 Subject: [PATCH 32/32] Replace LF endings with CRLF --- scripts/commands/sites/init.ts | 90 +-- scripts/constants.ts | 18 +- scripts/core/apiClient.ts | 32 +- scripts/core/date.js | 28 +- scripts/core/issueParser.ts | 68 +- scripts/templates/_config.js | 32 +- scripts/templates/_test.js | 76 +-- scripts/types/langs.d.ts | 2 +- sites/9tv.co.il/9tv.co.il.config.js | 138 ++-- sites/abc.net.au/abc.net.au.config.js | 244 ++++---- sites/abc.net.au/abc.net.au.test.js | 102 +-- sites/allente.dk/allente.dk.config.js | 130 ++-- sites/allente.fi/allente.fi.config.js | 130 ++-- sites/allente.no/allente.no.config.js | 130 ++-- sites/allente.se/allente.se.config.js | 130 ++-- .../andorradifusio.ad.config.js | 118 ++-- .../andorradifusio.ad.test.js | 94 +-- sites/anteltv.com.uy/anteltv.com.uy.config.js | 216 +++---- sites/anteltv.com.uy/anteltv.com.uy.test.js | 170 ++--- .../antennaeurope.gr.config.js | 118 ++-- .../antennaeurope.gr/antennaeurope.gr.test.js | 92 +-- .../antennapacific.gr.config.js | 118 ++-- .../antennapacific.gr.test.js | 92 +-- .../antennasatellite.gr.config.js | 118 ++-- .../antennasatellite.gr.test.js | 92 +-- .../arianatelevision.com.config.js | 120 ++-- sites/arirang.com/arirang.com.config.js | 324 +++++----- sites/arirang.com/arirang.com.test.js | 142 ++--- sites/artonline.tv/artonline.tv.config.js | 140 ++--- sites/awilime.com/awilime.com.config.js | 172 ++--- sites/awilime.com/awilime.com.test.js | 98 +-- sites/bein.com/bein.com.config.js | 220 +++---- sites/bein.com/bein.com.test.js | 116 ++-- sites/beinsports.com/beinsports.com.config.js | 146 ++--- sites/beinsports.com/beinsports.com.test.js | 86 +-- .../berrymedia.co.kr.config.js | 186 +++--- .../berrymedia.co.kr/berrymedia.co.kr.test.js | 154 ++--- .../cableplus.com.uy.config.js | 266 ++++---- .../cableplus.com.uy/cableplus.com.uy.test.js | 146 ++--- sites/canalplus.com/canalplus.com.config.js | 394 ++++++------ sites/canalplus.com/canalplus.com.test.js | 292 ++++----- sites/cgates.lt/cgates.lt.config.js | 184 +++--- sites/cgates.lt/cgates.lt.test.js | 98 +-- sites/chada.ma/chada.ma.config.js | 110 ++-- .../clickthecity.com.config.js | 200 +++--- .../clickthecity.com/clickthecity.com.test.js | 134 ++-- .../content.astro.com.my.config.js | 274 ++++---- .../content.astro.com.my.test.js | 142 ++--- sites/cosmotetv.gr/cosmotetv.gr.config.js | 170 ++--- sites/cubmu.com/cubmu.com.config.js | 228 +++---- sites/cyta.com.cy/cyta.com.cy.config.js | 118 ++-- sites/cyta.com.cy/cyta.com.cy.test.js | 98 +-- sites/dens.tv/dens.tv.config.js | 146 ++--- sites/dens.tv/dens.tv.test.js | 100 +-- sites/derana.lk/derana.lk.test.js | 100 +-- sites/digea.gr/digea.gr.config.js | 172 ++--- sites/digea.gr/digea.gr.test.js | 138 ++-- .../digiturk.com.tr/digiturk.com.tr.config.js | 172 ++--- sites/digiturk.com.tr/digiturk.com.tr.test.js | 96 +-- sites/directv.com.ar/directv.com.ar.config.js | 200 +++--- sites/directv.com.uy/directv.com.uy.config.js | 170 ++--- sites/directv.com.uy/directv.com.uy.test.js | 152 ++--- sites/directv.com/directv.com.config.js | 236 +++---- sites/directv.com/directv.com.test.js | 192 +++--- sites/dishtv.in/dishtv.in.config.js | 334 +++++----- sites/dishtv.in/dishtv.in.test.js | 280 ++++----- sites/dna.fi/dna.fi.config.js | 198 +++--- sites/dna.fi/dna.fi.test.js | 276 ++++---- sites/dsmart.com.tr/dsmart.com.tr.config.js | 260 ++++---- sites/dsmart.com.tr/dsmart.com.tr.test.js | 164 ++--- sites/dstv.com/dstv.com.test.js | 222 +++---- sites/dtv8.net/dtv8.net.config.js | 180 +++--- sites/dtv8.net/dtv8.net.test.js | 158 ++--- sites/elcinema.com/elcinema.com.config.js | 298 ++++----- sites/elcinema.com/elcinema.com.test.js | 138 ++-- .../ena.skylifetv.co.kr.config.js | 136 ++-- .../ena.skylifetv.co.kr.test.js | 114 ++-- sites/energeek.cl/energeek.cl.config.js | 66 +- sites/energeek.cl/energeek.cl.test.js | 74 +-- .../entertainment.ie.config.js | 192 +++--- .../entertainment.ie/entertainment.ie.test.js | 116 ++-- sites/epg.112114.xyz/epg.112114.xyz.config.js | 90 +-- sites/epg.112114.xyz/epg.112114.xyz.test.js | 84 +-- sites/epg.iptvx.one/epg.iptvx.one.config.js | 128 ++-- sites/epg.iptvx.one/epg.iptvx.one.test.js | 92 +-- .../epg.telemach.ba/epg.telemach.ba.config.js | 200 +++--- sites/epg.telemach.ba/epg.telemach.ba.test.js | 188 +++--- .../epg.telemach.me/epg.telemach.me.config.js | 202 +++--- sites/epg.telemach.me/epg.telemach.me.test.js | 192 +++--- sites/epgmaster.com/epgmaster.com.config.js | 90 +-- sites/epgmaster.com/epgmaster.com.test.js | 90 +-- .../epgshare01.online.config.js | 150 ++--- .../epgshare01.online.test.js | 86 +-- sites/firstmedia.com/firstmedia.com.config.js | 204 +++--- .../foxsports.com.au.config.js | 138 ++-- sites/foxtel.com.au/foxtel.com.au.config.js | 276 ++++---- sites/foxtel.com.au/foxtel.com.au.test.js | 120 ++-- sites/freetv.tv/freetv.tv.config.js | 124 ++-- sites/freeview.co.uk/freeview.co.uk.config.js | 146 ++--- sites/freeview.co.uk/freeview.co.uk.test.js | 110 ++-- sites/frikanalen.no/frikanalen.no.config.js | 104 +-- sites/galamtv.kz/galamtv.kz.config.js | 128 ++-- sites/gatotv.com/gatotv.com.config.js | 204 +++--- sites/gatotv.com/gatotv.com.test.js | 158 ++--- .../getafteritmedia.com.config.js | 128 ++-- .../getafteritmedia.com.test.js | 90 +-- .../gigatv.3bbtv.co.th.config.js | 130 ++-- .../gigatv.3bbtv.co.th.test.js | 90 +-- sites/guiadetv.com/guiadetv.com.config.js | 202 +++--- sites/guiadetv.com/guiadetv.com.test.js | 160 ++--- sites/guida.tv/guida.tv.test.js | 100 +-- sites/guidatv.sky.it/guidatv.sky.it.config.js | 196 +++--- sites/horizon.tv/horizon.tv.config.js | 290 ++++----- sites/hoy.tv/hoy.tv.config.js | 126 ++-- sites/i.mjh.nz/i.mjh.nz.config.js | 376 +++++------ sites/i.mjh.nz/i.mjh.nz.test.js | 94 +-- sites/i24news.tv/i24news.tv.config.js | 130 ++-- sites/iltalehti.fi/iltalehti.fi.config.js | 154 ++--- sites/iltalehti.fi/iltalehti.fi.test.js | 94 +-- sites/indihometv.com/indihometv.com.config.js | 184 +++--- sites/ionplustv.com/ionplustv.com.config.js | 214 +++---- sites/ionplustv.com/ionplustv.com.test.js | 98 +-- sites/ipko.tv/ipko.tv.config.js | 160 ++--- sites/jiotv.com/jiotv.com.config.js | 174 +++--- sites/jiotv.com/jiotv.com.test.js | 172 ++--- sites/kan.org.il/kan.org.il.config.js | 104 +-- sites/knr.gl/knr.gl.config.js | 158 ++--- sites/knr.gl/knr.gl.test.js | 134 ++-- sites/kvf.fo/kvf.fo.config.js | 142 ++--- sites/kvf.fo/kvf.fo.test.js | 84 +-- sites/m.tv.sms.cz/m.tv.sms.cz.config.js | 168 ++--- sites/m.tv.sms.cz/m.tv.sms.cz.test.js | 114 ++-- sites/m.tving.com/m.tving.com.config.js | 188 +++--- sites/m.tving.com/m.tving.com.test.js | 94 +-- sites/magticom.ge/magticom.ge.config.js | 172 ++--- sites/mako.co.il/mako.co.il.config.js | 90 +-- .../makrodigitaltelevision.com.config.js | 52 +- .../makrodigitaltelevision.com.test.js | 78 +-- sites/maxtvgo.mk/maxtvgo.mk.config.js | 144 ++--- .../mediagenie.co.kr.config.js | 154 ++--- .../mediagenie.co.kr/mediagenie.co.kr.test.js | 126 ++-- sites/mediaklikk.hu/mediaklikk.hu.config.js | 170 ++--- sites/mediaklikk.hu/mediaklikk.hu.test.js | 144 ++--- .../mediasetinfinity.mediaset.it.config.js | 194 +++--- .../mediasetinfinity.mediaset.it.test.js | 106 ++-- sites/melita.com/melita.com.config.js | 178 +++--- sites/meo.pt/meo.pt.config.js | 164 ++--- sites/meo.pt/meo.pt.test.js | 120 ++-- sites/meuguia.tv/meuguia.tv.config.js | 210 +++---- sites/meuguia.tv/meuguia.tv.test.js | 120 ++-- sites/mewatch.sg/mewatch.sg.config.js | 200 +++--- sites/mncvision.id/mncvision.id.config.js | 334 +++++----- sites/mncvision.id/mncvision.id.test.js | 264 ++++---- sites/moji.id/moji.id.config.js | 206 +++--- .../mojmaxtv.hrvatskitelekom.hr.test.js | 170 ++--- .../mon-programme-tv.be.config.js | 204 +++--- .../mon-programme-tv.be.test.js | 126 ++-- .../movistarplus.es/movistarplus.es.config.js | 194 +++--- sites/movistarplus.es/movistarplus.es.test.js | 160 ++--- sites/mts.rs/mts.rs.config.js | 110 ++-- sites/mts.rs/mts.rs.test.js | 118 ++-- .../mujtvprogram.cz/mujtvprogram.cz.config.js | 222 +++---- sites/mujtvprogram.cz/mujtvprogram.cz.test.js | 108 ++-- sites/musor.tv/musor.tv.config.js | 186 +++--- sites/musor.tv/musor.tv.test.js | 114 ++-- sites/mysky.com.ph/mysky.com.ph.config.js | 126 ++-- sites/mytelly.co.uk/mytelly.co.uk.config.js | 422 ++++++------- sites/mytelly.co.uk/mytelly.co.uk.test.js | 176 +++--- sites/mytvsuper.com/mytvsuper.com.config.js | 164 ++--- sites/mytvsuper.com/mytvsuper.com.test.js | 136 ++-- sites/neo.io/neo.io.config.js | 160 ++--- .../nhkworldpremium.com.config.js | 112 ++-- .../nhkworldpremium.com.test.js | 174 +++--- sites/nhl.com/nhl.com.config.js | 92 +-- sites/nhl.com/nhl.com.test.js | 88 +-- sites/nostv.pt/nostv.pt.config.js | 142 ++--- sites/nostv.pt/nostv.pt.test.js | 110 ++-- sites/novacyprus.com/novacyprus.com.config.js | 134 ++-- sites/novasports.gr/novasports.gr.config.js | 174 +++--- sites/novasports.gr/novasports.gr.test.js | 92 +-- .../nowplayer.now.com.config.js | 140 ++--- .../nuevosiglo.com.uy.config.js | 212 +++---- .../nuevosiglo.com.uy.test.js | 194 +++--- sites/nzxmltv.com/nzxmltv.com.config.js | 162 ++--- sites/nzxmltv.com/nzxmltv.com.test.js | 80 +-- sites/opto.sic.pt/opto.sic.pt.config.js | 106 ++-- sites/opto.sic.pt/opto.sic.pt.test.js | 104 +-- .../orangetv.orange.es.config.js | 198 +++--- .../orangetv.orange.es.test.js | 170 ++--- sites/osn.com/osn.com.config.js | 146 ++--- sites/osn.com/osn.com.test.js | 122 ++-- sites/pbsguam.org/pbsguam.org.config.js | 82 +-- sites/pickx.be/pickx.be.config.js | 262 ++++---- sites/pickx.be/pickx.be.test.js | 166 ++--- .../player.ee.co.uk/player.ee.co.uk.config.js | 208 +++--- sites/player.ee.co.uk/player.ee.co.uk.test.js | 148 ++--- .../playtv.unifi.com.my.config.js | 170 ++--- .../playtv.unifi.com.my.test.js | 110 ++-- sites/plex.tv/plex.tv.config.js | 182 +++--- sites/plex.tv/plex.tv.test.js | 112 ++-- sites/pluto.tv/pluto.tv.config.js | 98 +-- sites/pluto.tv/pluto.tv.test.js | 118 ++-- .../programacion-tv.elpais.com.config.js | 210 +++---- .../programacion-tv.elpais.com.test.js | 142 ++--- .../programacion.tcc.com.uy.config.js | 204 +++--- .../programacion.tcc.com.uy.test.js | 152 ++--- .../programme-tv.net/programme-tv.net.test.js | 126 ++-- .../programme-tv.vini.pf.config.js | 180 +++--- .../programme.tvb.com.config.js | 184 +++--- .../programme.tvb.com.test.js | 128 ++-- .../programtv.onet.pl.config.js | 178 +++--- sites/raiplay.it/raiplay.it.config.js | 158 ++--- sites/reportv.com.ar/reportv.com.ar.test.js | 222 +++---- sites/rikstv.no/rikstv.no.config.js | 152 ++--- sites/rotana.net/rotana.net.config.js | 378 +++++------ sites/rotana.net/rotana.net.test.js | 226 +++---- sites/rtb.gov.bn/rtb.gov.bn.config.js | 148 ++--- sites/rtb.gov.bn/rtb.gov.bn.test.js | 188 +++--- sites/rthk.hk/rthk.hk.config.js | 178 +++--- sites/rthk.hk/rthk.hk.test.js | 160 ++--- .../rtmklik.rtm.gov.my.config.js | 88 +-- sites/rtp.pt/rtp.pt.test.js | 84 +-- sites/ruv.is/ruv.is.config.js | 158 ++--- sites/ruv.is/ruv.is.test.js | 94 +-- sites/s.mxtv.jp/s.mxtv.jp.config.js | 166 ++--- sites/sat.tv/sat.tv.config.js | 346 +++++----- sites/sat.tv/sat.tv.test.js | 224 +++---- sites/shahid.mbc.net/shahid.mbc.net.config.js | 158 ++--- sites/siba.com.co/siba.com.co.config.js | 112 ++-- sites/singtel.com/singtel.com.config.js | 136 ++-- sites/singtel.com/singtel.com.test.js | 118 ++-- sites/sjonvarp.is/sjonvarp.is.config.js | 180 +++--- sites/sjonvarp.is/sjonvarp.is.test.js | 96 +-- sites/sky.co.nz/sky.co.nz.config.js | 110 ++-- sites/sky.co.nz/sky.co.nz.test.js | 120 ++-- sites/sky.com/sky.com.test.js | 122 ++-- sites/sky.de/sky.de.config.js | 156 ++--- sites/skylife.co.kr/skylife.co.kr.config.js | 162 ++--- sites/skylife.co.kr/skylife.co.kr.test.js | 84 +-- .../skyperfectv.co.jp.config.js | 242 +++---- .../skyperfectv.co.jp.test.js | 106 ++-- sites/snrt.ma/snrt.ma.config.js | 196 +++--- sites/snrt.ma/snrt.ma.test.js | 94 +-- sites/sporttv.pt/sporttv.pt.config.js | 126 ++-- sites/sporttv.pt/sporttv.pt.test.js | 108 ++-- .../starhubtvplus.com.config.js | 176 +++--- .../starhubtvplus.com.test.js | 110 ++-- .../startimestv.com/startimestv.com.config.js | 224 +++---- sites/startimestv.com/startimestv.com.test.js | 96 +-- sites/stod2.is/stod2.is.config.js | 134 ++-- .../streamingtvguides.com.test.js | 106 ++-- .../superguidatv.it/superguidatv.it.config.js | 234 +++---- sites/superguidatv.it/superguidatv.it.test.js | 128 ++-- sites/taiwanplus.com/taiwanplus.com.config.js | 136 ++-- sites/tapdmv.com/tapdmv.com.config.js | 130 ++-- sites/telebilbao.es/telebilbao.es.config.js | 140 ++--- sites/telebilbao.es/telebilbao.es.test.js | 88 +-- sites/teleboy.ch/teleboy.ch.config.js | 150 ++--- sites/teleboy.ch/teleboy.ch.test.js | 124 ++-- sites/telenet.tv/telenet.tv.config.js | 276 ++++---- sites/telenet.tv/telenet.tv.test.js | 178 +++--- sites/teliatv.ee/teliatv.ee.config.js | 134 ++-- sites/telkussa.fi/telkussa.fi.config.js | 90 +-- sites/telkussa.fi/telkussa.fi.test.js | 100 +-- sites/telsu.fi/telsu.fi.config.js | 196 +++--- sites/telsu.fi/telsu.fi.test.js | 88 +-- .../thesportplus.com.config.js | 146 ++--- .../thesportplus.com/thesportplus.com.test.js | 100 +-- sites/tivie.id/tivie.id.test.js | 150 ++--- sites/tivu.tv/tivu.tv.config.js | 178 +++--- sites/tivu.tv/tivu.tv.test.js | 112 ++-- .../toonamiaftermath.com.config.js | 120 ++-- .../toonamiaftermath.com.test.js | 122 ++-- .../turksatkablo.com.tr.config.js | 176 +++--- .../turksatkablo.com.tr.test.js | 96 +-- .../tv-programme.telecablesat.fr.config.js | 230 +++---- .../tv-programme.telecablesat.fr.test.js | 192 +++--- .../tv-spored.siol.net.config.js | 162 ++--- .../tv-spored.siol.net.test.js | 112 ++-- sites/tv.blue.ch/tv.blue.ch.config.js | 170 ++--- sites/tv.cctv.com/tv.cctv.com.config.js | 84 +-- sites/tv.cctv.com/tv.cctv.com.test.js | 102 +-- sites/tv.lv/tv.lv.config.js | 128 ++-- sites/tv.magenta.at/tv.magenta.at.test.js | 270 ++++---- .../tv.movistar.com.pe.config.js | 114 ++-- .../tv.movistar.com.pe.test.js | 90 +-- sites/tv.nu/tv.nu.config.js | 174 +++--- sites/tv.nu/tv.nu.test.js | 96 +-- sites/tv.post.lu/tv.post.lu.config.js | 112 ++-- sites/tv.post.lu/tv.post.lu.test.js | 94 +-- sites/tv.trueid.net/tv.trueid.net.config.js | 148 ++--- sites/tv.trueid.net/tv.trueid.net.test.js | 124 ++-- sites/tv.yandex.ru/tv.yandex.ru.config.js | 590 +++++++++--------- sites/tv24.co.uk/tv24.co.uk.config.js | 182 +++--- sites/tv24.co.uk/tv24.co.uk.test.js | 100 +-- sites/tv24.se/tv24.se.config.js | 326 +++++----- sites/tv24.se/tv24.se.test.js | 150 ++--- sites/tv2go.t-2.net/jquery.md5.js | 528 ++++++++-------- sites/tv2go.t-2.net/tv2go.t-2.net.config.js | 252 ++++---- .../tvarenasport.com.config.js | 264 ++++---- .../tvarenasport.com/tvarenasport.com.test.js | 102 +-- .../tvarenasport.hr/tvarenasport.hr.config.js | 264 ++++---- sites/tvarenasport.hr/tvarenasport.hr.test.js | 106 ++-- .../tvcubana.icrt.cu.config.js | 96 +-- sites/tvgids.nl/tvgids.nl.config.js | 174 +++--- sites/tvgids.nl/tvgids.nl.test.js | 120 ++-- sites/tvguide.com/tvguide.com.config.js | 228 +++---- sites/tvguide.com/tvguide.com.test.js | 190 +++--- .../tvguide.myjcom.jp.config.js | 228 +++---- sites/tvhebdo.com/tvhebdo.com.test.js | 106 ++-- sites/tvheute.at/tvheute.at.config.js | 192 +++--- sites/tvi.iol.pt/tvi.iol.pt.config.js | 152 ++--- sites/tvi.iol.pt/tvi.iol.pt.test.js | 132 ++-- sites/tvim.tv/tvim.tv.config.js | 122 ++-- sites/tvinsider.com/tvinsider.com.test.js | 112 ++-- sites/tvireland.ie/tvireland.ie.test.js | 100 +-- sites/tvkaista.org/tvkaista.org.config.js | 338 +++++----- sites/tvkaista.org/tvkaista.org.test.js | 186 +++--- sites/tvmi.mt/tvmi.mt.config.js | 154 ++--- sites/tvmi.mt/tvmi.mt.test.js | 106 ++-- sites/tvmusor.hu/tvmusor.hu.test.js | 138 ++-- sites/tvmustra.hu/tvmustra.hu.config.js | 156 ++--- sites/tvmustra.hu/tvmustra.hu.test.js | 94 +-- sites/tvpassport.com/tvpassport.com.config.js | 364 +++++------ sites/tvpassport.com/tvpassport.com.test.js | 152 ++--- sites/tvplus.com.tr/tvplus.com.tr.test.js | 154 ++--- sites/tvprofil.com/tvprofil.com.config.js | 330 +++++----- sites/tvprofil.com/tvprofil.com.test.js | 94 +-- sites/tvtv.us/tvtv.us.config.js | 302 ++++----- sites/tvtv.us/tvtv.us.test.js | 344 +++++----- .../v3.myafn.dodmedia.osd.mil.config.js | 152 ++--- .../v3.myafn.dodmedia.osd.mil.test.js | 110 ++-- .../virginmediatelevision.ie.config.js | 164 ++--- .../virginmediatelevision.ie.test.js | 102 +-- .../virgintvgo.virginmedia.com.config.js | 228 +++---- .../virgintvgo.virginmedia.com.test.js | 190 +++--- sites/visionplus.id/visionplus.id.config.js | 142 ++--- sites/visionplus.id/visionplus.id.test.js | 146 ++--- .../vivoplay.com.br/vivoplay.com.br.config.js | 136 ++-- sites/vivoplay.com.br/vivoplay.com.br.test.js | 122 ++-- sites/vtm.be/vtm.be.config.js | 96 +-- sites/vtm.be/vtm.be.test.js | 88 +-- sites/walesi.com.fj/walesi.com.fj.config.js | 182 +++--- sites/walesi.com.fj/walesi.com.fj.test.js | 130 ++-- .../watch.sportsnet.ca.config.js | 138 ++-- .../watch.sportsnet.ca.test.js | 96 +-- sites/watchyour.tv/watchyour.tv.config.js | 112 ++-- sites/watchyour.tv/watchyour.tv.test.js | 90 +-- sites/wavve.com/wavve.com.config.js | 124 ++-- sites/wavve.com/wavve.com.test.js | 86 +-- .../web.magentatv.de/web.magentatv.de.test.js | 276 ++++---- sites/webtv.delta.nl/webtv.delta.nl.config.js | 140 ++--- sites/webtv.delta.nl/webtv.delta.nl.test.js | 134 ++-- sites/winplay.co/winplay.co.config.js | 90 +-- sites/winplay.co/winplay.co.test.js | 136 ++-- .../worldfishingnetwork.com.config.js | 158 ++--- .../worldfishingnetwork.com.test.js | 106 ++-- sites/www3.nhk.or.jp/www3.nhk.or.jp.config.js | 136 ++-- sites/www3.nhk.or.jp/www3.nhk.or.jp.test.js | 86 +-- sites/xem.kplus.vn/xem.kplus.vn.config.js | 292 ++++----- sites/xem.kplus.vn/xem.kplus.vn.test.js | 154 ++--- sites/xumo.tv/xumo.tv.config.js | 268 ++++---- sites/xumo.tv/xumo.tv.test.js | 148 ++--- sites/yes.co.il/yes.co.il.config.js | 114 ++-- sites/yes.co.il/yes.co.il.test.js | 96 +-- sites/zap.co.ao/zap.co.ao.config.js | 96 +-- sites/zap.co.ao/zap.co.ao.test.js | 90 +-- sites/zap2it.com/zap2it.com.config.js | 142 ++--- sites/zap2it.com/zap2it.com.test.js | 148 ++--- sites/ziggogo.tv/ziggogo.tv.config.js | 228 +++---- sites/ziggogo.tv/ziggogo.tv.test.js | 210 +++---- sites/znbc.co.zm/znbc.co.zm.config.js | 130 ++-- sites/znbc.co.zm/znbc.co.zm.test.js | 106 ++-- sites/zuragt.mn/zuragt.mn.config.js | 178 +++--- sites/zuragt.mn/zuragt.mn.test.js | 92 +-- .../expected/sites_init/example.com.config.js | 32 +- .../expected/sites_init/example.com.test.js | 76 +-- .../channels_parse/example.com.config.js | 42 +- tests/commands/channels/parse.test.ts | 60 +- 379 files changed, 29332 insertions(+), 29332 deletions(-) diff --git a/scripts/commands/sites/init.ts b/scripts/commands/sites/init.ts index 44df0c95b..9d3a34a88 100644 --- a/scripts/commands/sites/init.ts +++ b/scripts/commands/sites/init.ts @@ -1,45 +1,45 @@ -import { Logger, Storage } from '@freearhey/core' -import { SITES_DIR } from '../../constants' -import { pathToFileURL } from 'node:url' -import { program } from 'commander' -import fs from 'fs-extra' - -program.argument('', 'Domain name of the site').parse(process.argv) - -const domain = program.args[0] - -async function main() { - const storage = new Storage(SITES_DIR) - const logger = new Logger() - - logger.info(`Initializing "${domain}"...\r\n`) - - const dir = domain - if (await storage.exists(dir)) { - throw new Error(`Folder "${dir}" already exists`) - } - - await storage.createDir(dir) - - logger.info(`Creating "${dir}/${domain}.test.js"...`) - const testTemplate = fs.readFileSync(pathToFileURL('scripts/templates/_test.js'), { - encoding: 'utf8' - }) - await storage.save(`${dir}/${domain}.test.js`, testTemplate.replace(//g, domain)) - - logger.info(`Creating "${dir}/${domain}.config.js"...`) - const configTemplate = fs.readFileSync(pathToFileURL('scripts/templates/_config.js'), { - encoding: 'utf8' - }) - await storage.save(`${dir}/${domain}.config.js`, configTemplate.replace(//g, domain)) - - logger.info(`Creating "${dir}/readme.md"...`) - const readmeTemplate = fs.readFileSync(pathToFileURL('scripts/templates/_readme.md'), { - encoding: 'utf8' - }) - await storage.save(`${dir}/readme.md`, readmeTemplate.replace(//g, domain)) - - logger.info('\r\nDone') -} - -main() +import { Logger, Storage } from '@freearhey/core' +import { SITES_DIR } from '../../constants' +import { pathToFileURL } from 'node:url' +import { program } from 'commander' +import fs from 'fs-extra' + +program.argument('', 'Domain name of the site').parse(process.argv) + +const domain = program.args[0] + +async function main() { + const storage = new Storage(SITES_DIR) + const logger = new Logger() + + logger.info(`Initializing "${domain}"...\r\n`) + + const dir = domain + if (await storage.exists(dir)) { + throw new Error(`Folder "${dir}" already exists`) + } + + await storage.createDir(dir) + + logger.info(`Creating "${dir}/${domain}.test.js"...`) + const testTemplate = fs.readFileSync(pathToFileURL('scripts/templates/_test.js'), { + encoding: 'utf8' + }) + await storage.save(`${dir}/${domain}.test.js`, testTemplate.replace(//g, domain)) + + logger.info(`Creating "${dir}/${domain}.config.js"...`) + const configTemplate = fs.readFileSync(pathToFileURL('scripts/templates/_config.js'), { + encoding: 'utf8' + }) + await storage.save(`${dir}/${domain}.config.js`, configTemplate.replace(//g, domain)) + + logger.info(`Creating "${dir}/readme.md"...`) + const readmeTemplate = fs.readFileSync(pathToFileURL('scripts/templates/_readme.md'), { + encoding: 'utf8' + }) + await storage.save(`${dir}/readme.md`, readmeTemplate.replace(//g, domain)) + + logger.info('\r\nDone') +} + +main() diff --git a/scripts/constants.ts b/scripts/constants.ts index 8af78b1dd..52c5d7987 100644 --- a/scripts/constants.ts +++ b/scripts/constants.ts @@ -1,9 +1,9 @@ -export const ROOT_DIR = process.env.ROOT_DIR || '.' -export const SITES_DIR = process.env.SITES_DIR || './sites' -export const GUIDES_DIR = process.env.GUIDES_DIR || './guides' -export const DATA_DIR = process.env.DATA_DIR || './temp/data' -export const API_DIR = process.env.API_DIR || '.api' -export const DOT_SITES_DIR = process.env.DOT_SITES_DIR || './.sites' -export const TESTING = process.env.NODE_ENV === 'test' ? true : false -export const OWNER = 'iptv-org' -export const REPO = 'epg' +export const ROOT_DIR = process.env.ROOT_DIR || '.' +export const SITES_DIR = process.env.SITES_DIR || './sites' +export const GUIDES_DIR = process.env.GUIDES_DIR || './guides' +export const DATA_DIR = process.env.DATA_DIR || './temp/data' +export const API_DIR = process.env.API_DIR || '.api' +export const DOT_SITES_DIR = process.env.DOT_SITES_DIR || './.sites' +export const TESTING = process.env.NODE_ENV === 'test' ? true : false +export const OWNER = 'iptv-org' +export const REPO = 'epg' diff --git a/scripts/core/apiClient.ts b/scripts/core/apiClient.ts index 931a9b140..e4815a81a 100644 --- a/scripts/core/apiClient.ts +++ b/scripts/core/apiClient.ts @@ -1,16 +1,16 @@ -import axios, { AxiosInstance, AxiosResponse, AxiosRequestConfig } from 'axios' - -export class ApiClient { - instance: AxiosInstance - - constructor() { - this.instance = axios.create({ - baseURL: 'https://iptv-org.github.io/api', - responseType: 'stream' - }) - } - - get(url: string, options: AxiosRequestConfig): Promise { - return this.instance.get(url, options) - } -} +import axios, { AxiosInstance, AxiosResponse, AxiosRequestConfig } from 'axios' + +export class ApiClient { + instance: AxiosInstance + + constructor() { + this.instance = axios.create({ + baseURL: 'https://iptv-org.github.io/api', + responseType: 'stream' + }) + } + + get(url: string, options: AxiosRequestConfig): Promise { + return this.instance.get(url, options) + } +} diff --git a/scripts/core/date.js b/scripts/core/date.js index 08c4d9383..777f9c33f 100644 --- a/scripts/core/date.js +++ b/scripts/core/date.js @@ -1,14 +1,14 @@ -import dayjs from 'dayjs' -import utc from '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') -} - -export default date +import dayjs from 'dayjs' +import utc from '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') +} + +export default date diff --git a/scripts/core/issueParser.ts b/scripts/core/issueParser.ts index e46265148..2db0f9dd3 100644 --- a/scripts/core/issueParser.ts +++ b/scripts/core/issueParser.ts @@ -1,34 +1,34 @@ -import { Dictionary } from '@freearhey/core' -import { Issue } from '../models' - -const FIELDS = new Dictionary({ - Site: 'site' -}) - -export class IssueParser { - parse(issue: { number: number; body: string; labels: { name: string }[] }): Issue { - const fields = issue.body.split('###') - - const data = new Dictionary() - fields.forEach((field: string) => { - const parsed = field.split(/\r?\n/).filter(Boolean) - let _label = parsed.shift() - _label = _label ? _label.trim() : '' - let _value = parsed.join('\r\n') - _value = _value ? _value.trim() : '' - - if (!_label || !_value) return data - - const id: string = FIELDS.get(_label) - const value: string = _value === '_No response_' || _value === 'None' ? '' : _value - - if (!id) return - - data.set(id, value) - }) - - const labels = issue.labels.map(label => label.name) - - return new Issue({ number: issue.number, labels, data }) - } -} +import { Dictionary } from '@freearhey/core' +import { Issue } from '../models' + +const FIELDS = new Dictionary({ + Site: 'site' +}) + +export class IssueParser { + parse(issue: { number: number; body: string; labels: { name: string }[] }): Issue { + const fields = issue.body.split('###') + + const data = new Dictionary() + fields.forEach((field: string) => { + const parsed = field.split(/\r?\n/).filter(Boolean) + let _label = parsed.shift() + _label = _label ? _label.trim() : '' + let _value = parsed.join('\r\n') + _value = _value ? _value.trim() : '' + + if (!_label || !_value) return data + + const id: string = FIELDS.get(_label) + const value: string = _value === '_No response_' || _value === 'None' ? '' : _value + + if (!id) return + + data.set(id, value) + }) + + const labels = issue.labels.map(label => label.name) + + return new Issue({ number: issue.number, labels, data }) + } +} diff --git a/scripts/templates/_config.js b/scripts/templates/_config.js index 2e40921de..b4eb9b46f 100644 --- a/scripts/templates/_config.js +++ b/scripts/templates/_config.js @@ -1,16 +1,16 @@ -module.exports = { - site: '', - url({ channel, date }) { - return `https://example.com/api/${channel.site_id}/${date.format('YYYY-MM-DD')}` - }, - parser({ content }) { - try { - return JSON.parse(content) - } catch { - return [] - } - }, - channels() { - return [] - } -} +module.exports = { + site: '', + url({ channel, date }) { + return `https://example.com/api/${channel.site_id}/${date.format('YYYY-MM-DD')}` + }, + parser({ content }) { + try { + return JSON.parse(content) + } catch { + return [] + } + }, + channels() { + return [] + } +} diff --git a/scripts/templates/_test.js b/scripts/templates/_test.js index 6375d7e70..b02a26488 100644 --- a/scripts/templates/_test.js +++ b/scripts/templates/_test.js @@ -1,38 +1,38 @@ -const { parser, url } = require('./.config.js') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-01-12', 'YYYY-MM-DD').startOf('d') -const channel = { site_id: 'bbc1' } - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe('https://example.com/api/bbc1/2025-01-12') -}) - -it('can parse response', () => { - const content = - '[{"title":"Program 1","start":"2025-01-12T00:00:00.000Z","stop":"2025-01-12T00:30:00.000Z"},{"title":"Program 2","start":"2025-01-12T00:30:00.000Z","stop":"2025-01-12T01:00:00.000Z"}]' - - const results = parser({ content }) - - expect(results.length).toBe(2) - expect(results[0]).toMatchObject({ - title: 'Program 1', - start: '2025-01-12T00:00:00.000Z', - stop: '2025-01-12T00:30:00.000Z' - }) - expect(results[1]).toMatchObject({ - title: 'Program 2', - start: '2025-01-12T00:30:00.000Z', - stop: '2025-01-12T01:00:00.000Z' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ content: '' }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./.config.js') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-01-12', 'YYYY-MM-DD').startOf('d') +const channel = { site_id: 'bbc1' } + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe('https://example.com/api/bbc1/2025-01-12') +}) + +it('can parse response', () => { + const content = + '[{"title":"Program 1","start":"2025-01-12T00:00:00.000Z","stop":"2025-01-12T00:30:00.000Z"},{"title":"Program 2","start":"2025-01-12T00:30:00.000Z","stop":"2025-01-12T01:00:00.000Z"}]' + + const results = parser({ content }) + + expect(results.length).toBe(2) + expect(results[0]).toMatchObject({ + title: 'Program 1', + start: '2025-01-12T00:00:00.000Z', + stop: '2025-01-12T00:30:00.000Z' + }) + expect(results[1]).toMatchObject({ + title: 'Program 2', + start: '2025-01-12T00:30:00.000Z', + stop: '2025-01-12T01:00:00.000Z' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ content: '' }) + + expect(results).toMatchObject([]) +}) diff --git a/scripts/types/langs.d.ts b/scripts/types/langs.d.ts index 74921c685..60fb498a7 100644 --- a/scripts/types/langs.d.ts +++ b/scripts/types/langs.d.ts @@ -1 +1 @@ -declare module 'langs' +declare module 'langs' diff --git a/sites/9tv.co.il/9tv.co.il.config.js b/sites/9tv.co.il/9tv.co.il.config.js index b55f6a10b..02384c5f1 100644 --- a/sites/9tv.co.il/9tv.co.il.config.js +++ b/sites/9tv.co.il/9tv.co.il.config.js @@ -1,69 +1,69 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: '9tv.co.il', - days: 2, - url: function ({ date }) { - return `https://www.9tv.co.il/BroadcastSchedule/getBrodcastSchedule?date=${date.format( - 'DD/MM/YYYY 00:00:00' - )}` - }, - parser: function ({ content, date }) { - const programs = [] - const items = parseItems(content) - items.forEach(item => { - const prev = programs[programs.length - 1] - const $item = cheerio.load(item) - const start = parseStart($item, date) - if (prev) prev.stop = start - const stop = start.add(1, 'h') - programs.push({ - title: parseTitle($item), - image: parseImage($item), - description: parseDescription($item), - start, - stop - }) - }) - - return programs - } -} - -function parseStart($item, date) { - let time = $item('a > div.guide_list_time').text().trim() - - return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'Asia/Jerusalem') -} - -function parseImage($item) { - const backgroundImage = $item('a > div.guide_info_group > div.guide_info_pict').css( - 'background-image' - ) - if (!backgroundImage) return null - const [, relativePath] = backgroundImage.match(/url\((.*)\)/) || [null, null] - - return relativePath ? `https://www.9tv.co.il${relativePath}` : null -} - -function parseDescription($item) { - return $item('a > div.guide_info_group > div.guide_txt_group > div').text().trim() -} - -function parseTitle($item) { - return $item('a > div.guide_info_group > div.guide_txt_group > h3').text().trim() -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('li').toArray() -} +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: '9tv.co.il', + days: 2, + url: function ({ date }) { + return `https://www.9tv.co.il/BroadcastSchedule/getBrodcastSchedule?date=${date.format( + 'DD/MM/YYYY 00:00:00' + )}` + }, + parser: function ({ content, date }) { + const programs = [] + const items = parseItems(content) + items.forEach(item => { + const prev = programs[programs.length - 1] + const $item = cheerio.load(item) + const start = parseStart($item, date) + if (prev) prev.stop = start + const stop = start.add(1, 'h') + programs.push({ + title: parseTitle($item), + image: parseImage($item), + description: parseDescription($item), + start, + stop + }) + }) + + return programs + } +} + +function parseStart($item, date) { + let time = $item('a > div.guide_list_time').text().trim() + + return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'Asia/Jerusalem') +} + +function parseImage($item) { + const backgroundImage = $item('a > div.guide_info_group > div.guide_info_pict').css( + 'background-image' + ) + if (!backgroundImage) return null + const [, relativePath] = backgroundImage.match(/url\((.*)\)/) || [null, null] + + return relativePath ? `https://www.9tv.co.il${relativePath}` : null +} + +function parseDescription($item) { + return $item('a > div.guide_info_group > div.guide_txt_group > div').text().trim() +} + +function parseTitle($item) { + return $item('a > div.guide_info_group > div.guide_txt_group > h3').text().trim() +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('li').toArray() +} diff --git a/sites/abc.net.au/abc.net.au.config.js b/sites/abc.net.au/abc.net.au.config.js index a7e2a3930..eb8e3a87e 100644 --- a/sites/abc.net.au/abc.net.au.config.js +++ b/sites/abc.net.au/abc.net.au.config.js @@ -1,122 +1,122 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'abc.net.au', - days: 3, - request: { - cache: { - ttl: 60 * 60 * 1000 // 1 hour - } - }, - url({ date, channel }) { - const [region] = channel.site_id.split('#') - - return `https://cdn.iview.abc.net.au/epg/processed/${region}_${date.format('YYYY-MM-DD')}.json` - }, - parser({ content, channel }) { - let programs = [] - const items = parseItems(content, channel) - items.forEach(item => { - programs.push({ - title: item.title, - sub_title: item.episode_title, - category: item.genres, - description: item.description, - season: parseSeason(item), - episode: parseEpisode(item), - rating: parseRating(item), - image: parseImage(item), - start: parseTime(item.start_time), - stop: parseTime(item.end_time) - }) - }) - - return programs - }, - async channels({ region = 'syd' }) { - const now = dayjs() - const regions = { - syd: 'Sydney', - mel: 'Melbourne', - bri: 'Brisbane', - gc: 'GoldCoast', - per: 'Perth', - adl: 'Adelaide', - hbr: 'Hobart', - drw: 'Darwin', - cbr: 'Canberra', - nsw: 'New South Wales', - vic: 'Victoria', - tsv: 'Townsville', - qld: 'Queensland', - wa: 'Western Australia', - sa: 'South Australia', - tas: 'Tasmania', - nt: 'Northern Territory' - } - - let channels = [] - const regionName = regions[region] - const data = await axios - .get( - `https://cdn.iview.abc.net.au/epg/processed/${regionName}_${now.format('YYYY-MM-DD')}.json` - ) - .then(r => r.data) - .catch(console.log) - - for (let item of data.schedule) { - channels.push({ - lang: 'en', - site_id: `${regionName}#${item.channel}`, - name: item.channel - }) - } - - return channels - } -} - -function parseItems(content, channel) { - try { - const data = JSON.parse(content) - if (!data) return [] - if (!Array.isArray(data.schedule)) return [] - - const [, channelId] = channel.site_id.split('#') - const channelData = data.schedule.find(i => i.channel == channelId) - return channelData.listing && Array.isArray(channelData.listing) ? channelData.listing : [] - } catch { - return [] - } -} - -function parseSeason(item) { - return item.series_num || null -} -function parseEpisode(item) { - return item.episode_num || null -} -function parseTime(time) { - return dayjs.tz(time, 'YYYY-MM-DD HH:mm', 'Australia/Sydney') -} -function parseImage(item) { - return item.image_file - ? `https://www.abc.net.au/tv/common/images/publicity/${item.image_file}` - : null -} -function parseRating(item) { - return item.rating - ? { - system: 'ACB', - value: item.rating - } - : null -} +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'abc.net.au', + days: 3, + request: { + cache: { + ttl: 60 * 60 * 1000 // 1 hour + } + }, + url({ date, channel }) { + const [region] = channel.site_id.split('#') + + return `https://cdn.iview.abc.net.au/epg/processed/${region}_${date.format('YYYY-MM-DD')}.json` + }, + parser({ content, channel }) { + let programs = [] + const items = parseItems(content, channel) + items.forEach(item => { + programs.push({ + title: item.title, + sub_title: item.episode_title, + category: item.genres, + description: item.description, + season: parseSeason(item), + episode: parseEpisode(item), + rating: parseRating(item), + image: parseImage(item), + start: parseTime(item.start_time), + stop: parseTime(item.end_time) + }) + }) + + return programs + }, + async channels({ region = 'syd' }) { + const now = dayjs() + const regions = { + syd: 'Sydney', + mel: 'Melbourne', + bri: 'Brisbane', + gc: 'GoldCoast', + per: 'Perth', + adl: 'Adelaide', + hbr: 'Hobart', + drw: 'Darwin', + cbr: 'Canberra', + nsw: 'New South Wales', + vic: 'Victoria', + tsv: 'Townsville', + qld: 'Queensland', + wa: 'Western Australia', + sa: 'South Australia', + tas: 'Tasmania', + nt: 'Northern Territory' + } + + let channels = [] + const regionName = regions[region] + const data = await axios + .get( + `https://cdn.iview.abc.net.au/epg/processed/${regionName}_${now.format('YYYY-MM-DD')}.json` + ) + .then(r => r.data) + .catch(console.log) + + for (let item of data.schedule) { + channels.push({ + lang: 'en', + site_id: `${regionName}#${item.channel}`, + name: item.channel + }) + } + + return channels + } +} + +function parseItems(content, channel) { + try { + const data = JSON.parse(content) + if (!data) return [] + if (!Array.isArray(data.schedule)) return [] + + const [, channelId] = channel.site_id.split('#') + const channelData = data.schedule.find(i => i.channel == channelId) + return channelData.listing && Array.isArray(channelData.listing) ? channelData.listing : [] + } catch { + return [] + } +} + +function parseSeason(item) { + return item.series_num || null +} +function parseEpisode(item) { + return item.episode_num || null +} +function parseTime(time) { + return dayjs.tz(time, 'YYYY-MM-DD HH:mm', 'Australia/Sydney') +} +function parseImage(item) { + return item.image_file + ? `https://www.abc.net.au/tv/common/images/publicity/${item.image_file}` + : null +} +function parseRating(item) { + return item.rating + ? { + system: 'ACB', + value: item.rating + } + : null +} diff --git a/sites/abc.net.au/abc.net.au.test.js b/sites/abc.net.au/abc.net.au.test.js index e4f875310..abb4e6314 100644 --- a/sites/abc.net.au/abc.net.au.test.js +++ b/sites/abc.net.au/abc.net.au.test.js @@ -1,51 +1,51 @@ -const { parser, url } = require('./abc.net.au.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -dayjs.extend(utc) - -const date = dayjs.utc('2025-02-04', 'YYYY-MM-DD').startOf('d') -const channel = { site_id: 'Sydney#ABC1' } - -it('can generate valid url', () => { - expect(url({ date, channel })).toBe( - 'https://cdn.iview.abc.net.au/epg/processed/Sydney_2025-02-04.json' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const results = parser({ content, channel }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(30) - expect(results[0]).toMatchObject({ - title: "Julia Zemiro's Home Delivery", - sub_title: 'Maggie Beer', - description: - "The kitchen Maggie Beer made famous in The Cook and the Chef may be in the heart of the Barossa Valley, but our most beloved foodie meets up with Julia where she grew up in Sydney's Lakemba.", - category: ['Entertainment', 'Factual'], - rating: { - system: 'ACB', - value: 'G' - }, - season: null, - episode: null, - image: 'https://www.abc.net.au/tv/common/images/publicity/LE1761H002S00_460.jpg', - start: '2025-02-03T12:40:00.000Z', - stop: '2025-02-03T13:09:00.000Z' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')), - channel - }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./abc.net.au.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +dayjs.extend(utc) + +const date = dayjs.utc('2025-02-04', 'YYYY-MM-DD').startOf('d') +const channel = { site_id: 'Sydney#ABC1' } + +it('can generate valid url', () => { + expect(url({ date, channel })).toBe( + 'https://cdn.iview.abc.net.au/epg/processed/Sydney_2025-02-04.json' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const results = parser({ content, channel }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(30) + expect(results[0]).toMatchObject({ + title: "Julia Zemiro's Home Delivery", + sub_title: 'Maggie Beer', + description: + "The kitchen Maggie Beer made famous in The Cook and the Chef may be in the heart of the Barossa Valley, but our most beloved foodie meets up with Julia where she grew up in Sydney's Lakemba.", + category: ['Entertainment', 'Factual'], + rating: { + system: 'ACB', + value: 'G' + }, + season: null, + episode: null, + image: 'https://www.abc.net.au/tv/common/images/publicity/LE1761H002S00_460.jpg', + start: '2025-02-03T12:40:00.000Z', + stop: '2025-02-03T13:09:00.000Z' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')), + channel + }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/allente.dk/allente.dk.config.js b/sites/allente.dk/allente.dk.config.js index adab26cad..5e80b7b06 100644 --- a/sites/allente.dk/allente.dk.config.js +++ b/sites/allente.dk/allente.dk.config.js @@ -1,65 +1,65 @@ -const dayjs = require('dayjs') - -module.exports = { - site: 'allente.dk', - days: 2, - request: { - cache: { - ttl: 60 * 60 * 1000 // 1 hour - } - }, - url({ date }) { - return `https://cs-vcb.allente.dk/epg/events?date=${date.format('YYYY-MM-DD')}` - }, - parser({ content, channel }) { - let programs = [] - const items = parseItems(content, channel) - items.forEach(item => { - if (!item.details) return - const start = dayjs(item.time) - const stop = start.add(item.details.duration, 'm') - programs.push({ - title: item.title, - category: item.details.categories, - description: item.details.description, - image: item.details.image, - season: parseSeason(item), - episode: parseEpisode(item), - start, - stop - }) - }) - - return programs - }, - async channels() { - const axios = require('axios') - const data = await axios - .get(`https://cs-vcb.allente.dk/epg/events?date=${dayjs().format('YYYY-MM-DD')}`) - .then(r => r.data) - .catch(console.log) - - return data.channels.map(item => { - return { - lang: 'da', - site_id: item.id, - name: item.name - } - }) - } -} - -function parseItems(content, channel) { - const data = JSON.parse(content) - if (!data || !Array.isArray(data.channels)) return [] - const channelData = data.channels.find(i => i.id === channel.site_id) - - return channelData && Array.isArray(channelData.events) ? channelData.events : [] -} - -function parseSeason(item) { - return item.details.season || null -} -function parseEpisode(item) { - return item.details.episode || null -} +const dayjs = require('dayjs') + +module.exports = { + site: 'allente.dk', + days: 2, + request: { + cache: { + ttl: 60 * 60 * 1000 // 1 hour + } + }, + url({ date }) { + return `https://cs-vcb.allente.dk/epg/events?date=${date.format('YYYY-MM-DD')}` + }, + parser({ content, channel }) { + let programs = [] + const items = parseItems(content, channel) + items.forEach(item => { + if (!item.details) return + const start = dayjs(item.time) + const stop = start.add(item.details.duration, 'm') + programs.push({ + title: item.title, + category: item.details.categories, + description: item.details.description, + image: item.details.image, + season: parseSeason(item), + episode: parseEpisode(item), + start, + stop + }) + }) + + return programs + }, + async channels() { + const axios = require('axios') + const data = await axios + .get(`https://cs-vcb.allente.dk/epg/events?date=${dayjs().format('YYYY-MM-DD')}`) + .then(r => r.data) + .catch(console.log) + + return data.channels.map(item => { + return { + lang: 'da', + site_id: item.id, + name: item.name + } + }) + } +} + +function parseItems(content, channel) { + const data = JSON.parse(content) + if (!data || !Array.isArray(data.channels)) return [] + const channelData = data.channels.find(i => i.id === channel.site_id) + + return channelData && Array.isArray(channelData.events) ? channelData.events : [] +} + +function parseSeason(item) { + return item.details.season || null +} +function parseEpisode(item) { + return item.details.episode || null +} diff --git a/sites/allente.fi/allente.fi.config.js b/sites/allente.fi/allente.fi.config.js index 470dca852..cebe9364b 100644 --- a/sites/allente.fi/allente.fi.config.js +++ b/sites/allente.fi/allente.fi.config.js @@ -1,65 +1,65 @@ -const dayjs = require('dayjs') - -module.exports = { - site: 'allente.fi', - days: 2, - request: { - cache: { - ttl: 60 * 60 * 1000 // 1 hour - } - }, - url({ date }) { - return `https://cs-vcb.allente.fi/epg/events?date=${date.format('YYYY-MM-DD')}` - }, - parser({ content, channel }) { - let programs = [] - const items = parseItems(content, channel) - items.forEach(item => { - if (!item.details) return - const start = dayjs(item.time) - const stop = start.add(item.details.duration, 'm') - programs.push({ - title: item.title, - category: item.details.categories, - description: item.details.description, - image: item.details.image, - season: parseSeason(item), - episode: parseEpisode(item), - start, - stop - }) - }) - - return programs - }, - async channels() { - const axios = require('axios') - const data = await axios - .get(`https://cs-vcb.allente.fi/epg/events?date=${dayjs().format('YYYY-MM-DD')}`) - .then(r => r.data) - .catch(console.log) - - return data.channels.map(item => { - return { - lang: 'fi', - site_id: item.id, - name: item.name - } - }) - } -} - -function parseItems(content, channel) { - const data = JSON.parse(content) - if (!data || !Array.isArray(data.channels)) return [] - const channelData = data.channels.find(i => i.id === channel.site_id) - - return channelData && Array.isArray(channelData.events) ? channelData.events : [] -} - -function parseSeason(item) { - return item.details.season || null -} -function parseEpisode(item) { - return item.details.episode || null -} +const dayjs = require('dayjs') + +module.exports = { + site: 'allente.fi', + days: 2, + request: { + cache: { + ttl: 60 * 60 * 1000 // 1 hour + } + }, + url({ date }) { + return `https://cs-vcb.allente.fi/epg/events?date=${date.format('YYYY-MM-DD')}` + }, + parser({ content, channel }) { + let programs = [] + const items = parseItems(content, channel) + items.forEach(item => { + if (!item.details) return + const start = dayjs(item.time) + const stop = start.add(item.details.duration, 'm') + programs.push({ + title: item.title, + category: item.details.categories, + description: item.details.description, + image: item.details.image, + season: parseSeason(item), + episode: parseEpisode(item), + start, + stop + }) + }) + + return programs + }, + async channels() { + const axios = require('axios') + const data = await axios + .get(`https://cs-vcb.allente.fi/epg/events?date=${dayjs().format('YYYY-MM-DD')}`) + .then(r => r.data) + .catch(console.log) + + return data.channels.map(item => { + return { + lang: 'fi', + site_id: item.id, + name: item.name + } + }) + } +} + +function parseItems(content, channel) { + const data = JSON.parse(content) + if (!data || !Array.isArray(data.channels)) return [] + const channelData = data.channels.find(i => i.id === channel.site_id) + + return channelData && Array.isArray(channelData.events) ? channelData.events : [] +} + +function parseSeason(item) { + return item.details.season || null +} +function parseEpisode(item) { + return item.details.episode || null +} diff --git a/sites/allente.no/allente.no.config.js b/sites/allente.no/allente.no.config.js index 3406d64fb..348b521c7 100644 --- a/sites/allente.no/allente.no.config.js +++ b/sites/allente.no/allente.no.config.js @@ -1,65 +1,65 @@ -const dayjs = require('dayjs') - -module.exports = { - site: 'allente.no', - days: 2, - request: { - cache: { - ttl: 60 * 60 * 1000 // 1 hour - } - }, - url({ date }) { - return `https://cs-vcb.allente.no/epg/events?date=${date.format('YYYY-MM-DD')}` - }, - parser({ content, channel }) { - let programs = [] - const items = parseItems(content, channel) - items.forEach(item => { - if (!item.details) return - const start = dayjs(item.time) - const stop = start.add(item.details.duration, 'm') - programs.push({ - title: item.title, - category: item.details.categories, - description: item.details.description, - image: item.details.image, - season: parseSeason(item), - episode: parseEpisode(item), - start, - stop - }) - }) - - return programs - }, - async channels() { - const axios = require('axios') - const data = await axios - .get(`https://cs-vcb.allente.no/epg/events?date=${dayjs().format('YYYY-MM-DD')}`) - .then(r => r.data) - .catch(console.log) - - return data.channels.map(item => { - return { - lang: 'no', - site_id: item.id, - name: item.name - } - }) - } -} - -function parseItems(content, channel) { - const data = JSON.parse(content) - if (!data || !Array.isArray(data.channels)) return [] - const channelData = data.channels.find(i => i.id === channel.site_id) - - return channelData && Array.isArray(channelData.events) ? channelData.events : [] -} - -function parseSeason(item) { - return item.details.season || null -} -function parseEpisode(item) { - return item.details.episode || null -} +const dayjs = require('dayjs') + +module.exports = { + site: 'allente.no', + days: 2, + request: { + cache: { + ttl: 60 * 60 * 1000 // 1 hour + } + }, + url({ date }) { + return `https://cs-vcb.allente.no/epg/events?date=${date.format('YYYY-MM-DD')}` + }, + parser({ content, channel }) { + let programs = [] + const items = parseItems(content, channel) + items.forEach(item => { + if (!item.details) return + const start = dayjs(item.time) + const stop = start.add(item.details.duration, 'm') + programs.push({ + title: item.title, + category: item.details.categories, + description: item.details.description, + image: item.details.image, + season: parseSeason(item), + episode: parseEpisode(item), + start, + stop + }) + }) + + return programs + }, + async channels() { + const axios = require('axios') + const data = await axios + .get(`https://cs-vcb.allente.no/epg/events?date=${dayjs().format('YYYY-MM-DD')}`) + .then(r => r.data) + .catch(console.log) + + return data.channels.map(item => { + return { + lang: 'no', + site_id: item.id, + name: item.name + } + }) + } +} + +function parseItems(content, channel) { + const data = JSON.parse(content) + if (!data || !Array.isArray(data.channels)) return [] + const channelData = data.channels.find(i => i.id === channel.site_id) + + return channelData && Array.isArray(channelData.events) ? channelData.events : [] +} + +function parseSeason(item) { + return item.details.season || null +} +function parseEpisode(item) { + return item.details.episode || null +} diff --git a/sites/allente.se/allente.se.config.js b/sites/allente.se/allente.se.config.js index 972d6ee3e..f8666ef36 100644 --- a/sites/allente.se/allente.se.config.js +++ b/sites/allente.se/allente.se.config.js @@ -1,65 +1,65 @@ -const dayjs = require('dayjs') - -module.exports = { - site: 'allente.se', - days: 2, - request: { - cache: { - ttl: 60 * 60 * 1000 // 1 hour - } - }, - url({ date }) { - return `https://cs-vcb.allente.se/epg/events?date=${date.format('YYYY-MM-DD')}` - }, - parser({ content, channel }) { - let programs = [] - const items = parseItems(content, channel) - items.forEach(item => { - if (!item.details) return - const start = dayjs(item.time) - const stop = start.add(item.details.duration, 'm') - programs.push({ - title: item.title, - category: item.details.categories, - description: item.details.description, - image: item.details.image, - season: parseSeason(item), - episode: parseEpisode(item), - start, - stop - }) - }) - - return programs - }, - async channels() { - const axios = require('axios') - const data = await axios - .get(`https://cs-vcb.allente.se/epg/events?date=${dayjs().format('YYYY-MM-DD')}`) - .then(r => r.data) - .catch(console.log) - - return data.channels.map(item => { - return { - lang: 'sv', - site_id: item.id, - name: item.name - } - }) - } -} - -function parseItems(content, channel) { - const data = JSON.parse(content) - if (!data || !Array.isArray(data.channels)) return [] - const channelData = data.channels.find(i => i.id === channel.site_id) - - return channelData && Array.isArray(channelData.events) ? channelData.events : [] -} - -function parseSeason(item) { - return item.details.season || null -} -function parseEpisode(item) { - return item.details.episode || null -} +const dayjs = require('dayjs') + +module.exports = { + site: 'allente.se', + days: 2, + request: { + cache: { + ttl: 60 * 60 * 1000 // 1 hour + } + }, + url({ date }) { + return `https://cs-vcb.allente.se/epg/events?date=${date.format('YYYY-MM-DD')}` + }, + parser({ content, channel }) { + let programs = [] + const items = parseItems(content, channel) + items.forEach(item => { + if (!item.details) return + const start = dayjs(item.time) + const stop = start.add(item.details.duration, 'm') + programs.push({ + title: item.title, + category: item.details.categories, + description: item.details.description, + image: item.details.image, + season: parseSeason(item), + episode: parseEpisode(item), + start, + stop + }) + }) + + return programs + }, + async channels() { + const axios = require('axios') + const data = await axios + .get(`https://cs-vcb.allente.se/epg/events?date=${dayjs().format('YYYY-MM-DD')}`) + .then(r => r.data) + .catch(console.log) + + return data.channels.map(item => { + return { + lang: 'sv', + site_id: item.id, + name: item.name + } + }) + } +} + +function parseItems(content, channel) { + const data = JSON.parse(content) + if (!data || !Array.isArray(data.channels)) return [] + const channelData = data.channels.find(i => i.id === channel.site_id) + + return channelData && Array.isArray(channelData.events) ? channelData.events : [] +} + +function parseSeason(item) { + return item.details.season || null +} +function parseEpisode(item) { + return item.details.episode || null +} diff --git a/sites/andorradifusio.ad/andorradifusio.ad.config.js b/sites/andorradifusio.ad/andorradifusio.ad.config.js index cf3e5a5de..c20b29001 100644 --- a/sites/andorradifusio.ad/andorradifusio.ad.config.js +++ b/sites/andorradifusio.ad/andorradifusio.ad.config.js @@ -1,59 +1,59 @@ -const cheerio = require('cheerio') -const { DateTime } = require('luxon') - -module.exports = { - site: 'andorradifusio.ad', - days: 2, - url({ channel }) { - return `https://www.andorradifusio.ad/programacio/${channel.site_id}` - }, - parser({ content, date }) { - const programs = [] - const items = parseItems(content, date) - items.forEach(item => { - const prev = programs[programs.length - 1] - let start = parseStart(item, date) - if (prev) { - if (start < prev.start) { - start = start.plus({ days: 1 }) - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.plus({ hours: 1 }) - programs.push({ - title: item.title, - start, - stop - }) - }) - - return programs - } -} - -function parseStart(item, date) { - const dateString = `${date.format('MM/DD/YYYY')} ${item.time}` - - return DateTime.fromFormat(dateString, 'MM/dd/yyyy HH:mm', { zone: 'Europe/Madrid' }).toUTC() -} - -function parseItems(content, date) { - const $ = cheerio.load(content) - const day = DateTime.fromMillis(date.valueOf()).setLocale('ca').toFormat('dd LLLL').toLowerCase() - const column = $('.programacio-dia > h3 > .dia') - .filter((i, el) => $(el).text() === day.slice(0, 6) + '.') - .first() - .parent() - .parent() - const items = [] - const titles = column.find('p').toArray() - column.find('h4').each((i, time) => { - items.push({ - time: $(time).text(), - title: $(titles[i]).text() - }) - }) - - return items -} +const cheerio = require('cheerio') +const { DateTime } = require('luxon') + +module.exports = { + site: 'andorradifusio.ad', + days: 2, + url({ channel }) { + return `https://www.andorradifusio.ad/programacio/${channel.site_id}` + }, + parser({ content, date }) { + const programs = [] + const items = parseItems(content, date) + items.forEach(item => { + const prev = programs[programs.length - 1] + let start = parseStart(item, date) + if (prev) { + if (start < prev.start) { + start = start.plus({ days: 1 }) + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.plus({ hours: 1 }) + programs.push({ + title: item.title, + start, + stop + }) + }) + + return programs + } +} + +function parseStart(item, date) { + const dateString = `${date.format('MM/DD/YYYY')} ${item.time}` + + return DateTime.fromFormat(dateString, 'MM/dd/yyyy HH:mm', { zone: 'Europe/Madrid' }).toUTC() +} + +function parseItems(content, date) { + const $ = cheerio.load(content) + const day = DateTime.fromMillis(date.valueOf()).setLocale('ca').toFormat('dd LLLL').toLowerCase() + const column = $('.programacio-dia > h3 > .dia') + .filter((i, el) => $(el).text() === day.slice(0, 6) + '.') + .first() + .parent() + .parent() + const items = [] + const titles = column.find('p').toArray() + column.find('h4').each((i, time) => { + items.push({ + time: $(time).text(), + title: $(titles[i]).text() + }) + }) + + return items +} diff --git a/sites/andorradifusio.ad/andorradifusio.ad.test.js b/sites/andorradifusio.ad/andorradifusio.ad.test.js index f63c40a90..aaf8e089d 100644 --- a/sites/andorradifusio.ad/andorradifusio.ad.test.js +++ b/sites/andorradifusio.ad/andorradifusio.ad.test.js @@ -1,47 +1,47 @@ -const { parser, url } = require('./andorradifusio.ad.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-06-07', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'atv', - xmltv_id: 'AndorraTV.ad' -} - -it('can generate valid url', () => { - expect(url({ channel })).toBe('https://www.andorradifusio.ad/programacio/atv') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - const results = parser({ content, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2023-06-07T05:00:00.000Z', - stop: '2023-06-07T06:00:00.000Z', - title: 'Club Piolet' - }) - - expect(results[20]).toMatchObject({ - start: '2023-06-07T23:00:00.000Z', - stop: '2023-06-08T00:00:00.000Z', - title: 'Àrea Andorra Difusió' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - content: '' - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./andorradifusio.ad.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-06-07', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'atv', + xmltv_id: 'AndorraTV.ad' +} + +it('can generate valid url', () => { + expect(url({ channel })).toBe('https://www.andorradifusio.ad/programacio/atv') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + const results = parser({ content, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2023-06-07T05:00:00.000Z', + stop: '2023-06-07T06:00:00.000Z', + title: 'Club Piolet' + }) + + expect(results[20]).toMatchObject({ + start: '2023-06-07T23:00:00.000Z', + stop: '2023-06-08T00:00:00.000Z', + title: 'Àrea Andorra Difusió' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + content: '' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/anteltv.com.uy/anteltv.com.uy.config.js b/sites/anteltv.com.uy/anteltv.com.uy.config.js index c0133ea9a..2c799e4ce 100644 --- a/sites/anteltv.com.uy/anteltv.com.uy.config.js +++ b/sites/anteltv.com.uy/anteltv.com.uy.config.js @@ -1,108 +1,108 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -const API_ENDPOINT = 'https://cds-frontend.vera.com.uy/api-contenidos' - -module.exports = { - site: 'anteltv.com.uy', - days: 2, - async url({ date, channel }) { - const session = await loadSessionDetails() - if (!session || !session.token) return null - - return `${API_ENDPOINT}/canales/epg/${ - channel.site_id - }?limit=500&dias_siguientes=0&fecha=${date.format('YYYY-MM-DD')}&token=${session.token}` - }, - request: { - async headers() { - const session = await loadSessionDetails() - if (!session || !session.jwt) return null - - return { - authorization: `Bearer ${session.jwt}`, - 'x-frontend-id': 1196, - 'x-service-id': 3, - 'x-system-id': 1 - } - } - }, - parser({ content }) { - let programs = [] - let items = parseItems(content) - items.forEach(item => { - programs.push({ - title: item.nombre_programa, - sub_title: item.subtitle, - description: item.descripcion_programa, - start: parseStart(item), - stop: parseStop(item) - }) - }) - - return programs - }, - async channels() { - const session = await loadSessionDetails() - if (!session || !session.jwt || !session.token) return null - - const data = await axios - .get(`${API_ENDPOINT}/listas/68?token=${session.token}`, { - headers: { - authorization: `Bearer ${session.jwt}`, - 'x-frontend-id': 1196, - 'x-service-id': 3, - 'x-system-id': 1 - } - }) - .then(r => r.data) - .catch(console.error) - - return data.contenidos.map(c => { - return { - lang: 'es', - site_id: c.public_id, - name: c.nombre - } - }) - } -} - -function parseStart(item) { - return dayjs.tz(item.fecha_hora_inicio, 'YYYY-MM-DD HH:mm:ss', 'America/Montevideo') -} - -function parseStop(item) { - return dayjs.tz(item.fecha_hora_fin, 'YYYY-MM-DD HH:mm:ss', 'America/Montevideo') -} - -function parseItems(content) { - const data = JSON.parse(content) - if (!data || !Array.isArray(data.data)) return [] - - return data.data -} - -function loadSessionDetails() { - return axios - .post( - 'https://veratv-be.vera.com.uy/api/sesiones', - { - tipo: 'anonima' - }, - { - headers: { - 'Content-Type': 'application/json' - } - } - ) - .then(r => r.data) - .catch(console.log) -} +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +const API_ENDPOINT = 'https://cds-frontend.vera.com.uy/api-contenidos' + +module.exports = { + site: 'anteltv.com.uy', + days: 2, + async url({ date, channel }) { + const session = await loadSessionDetails() + if (!session || !session.token) return null + + return `${API_ENDPOINT}/canales/epg/${ + channel.site_id + }?limit=500&dias_siguientes=0&fecha=${date.format('YYYY-MM-DD')}&token=${session.token}` + }, + request: { + async headers() { + const session = await loadSessionDetails() + if (!session || !session.jwt) return null + + return { + authorization: `Bearer ${session.jwt}`, + 'x-frontend-id': 1196, + 'x-service-id': 3, + 'x-system-id': 1 + } + } + }, + parser({ content }) { + let programs = [] + let items = parseItems(content) + items.forEach(item => { + programs.push({ + title: item.nombre_programa, + sub_title: item.subtitle, + description: item.descripcion_programa, + start: parseStart(item), + stop: parseStop(item) + }) + }) + + return programs + }, + async channels() { + const session = await loadSessionDetails() + if (!session || !session.jwt || !session.token) return null + + const data = await axios + .get(`${API_ENDPOINT}/listas/68?token=${session.token}`, { + headers: { + authorization: `Bearer ${session.jwt}`, + 'x-frontend-id': 1196, + 'x-service-id': 3, + 'x-system-id': 1 + } + }) + .then(r => r.data) + .catch(console.error) + + return data.contenidos.map(c => { + return { + lang: 'es', + site_id: c.public_id, + name: c.nombre + } + }) + } +} + +function parseStart(item) { + return dayjs.tz(item.fecha_hora_inicio, 'YYYY-MM-DD HH:mm:ss', 'America/Montevideo') +} + +function parseStop(item) { + return dayjs.tz(item.fecha_hora_fin, 'YYYY-MM-DD HH:mm:ss', 'America/Montevideo') +} + +function parseItems(content) { + const data = JSON.parse(content) + if (!data || !Array.isArray(data.data)) return [] + + return data.data +} + +function loadSessionDetails() { + return axios + .post( + 'https://veratv-be.vera.com.uy/api/sesiones', + { + tipo: 'anonima' + }, + { + headers: { + 'Content-Type': 'application/json' + } + } + ) + .then(r => r.data) + .catch(console.log) +} diff --git a/sites/anteltv.com.uy/anteltv.com.uy.test.js b/sites/anteltv.com.uy/anteltv.com.uy.test.js index 51ea965f2..9e4398d2d 100644 --- a/sites/anteltv.com.uy/anteltv.com.uy.test.js +++ b/sites/anteltv.com.uy/anteltv.com.uy.test.js @@ -1,85 +1,85 @@ -const { parser, url, request } = require('./anteltv.com.uy.config.js') -const fs = require('fs') -const axios = require('axios') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -axios.post.mockImplementation((url, data, opts) => { - if ( - url === 'https://veratv-be.vera.com.uy/api/sesiones' && - JSON.stringify(opts.headers) === - JSON.stringify({ - 'Content-Type': 'application/json' - }) && - JSON.stringify(data) === - JSON.stringify({ - tipo: 'anonima' - }) - ) { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/session.json'))) - }) - } else { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/no_session.json'))) - }) - } -}) - -const date = dayjs.utc('2023-02-11', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '2s6nd', - xmltv_id: 'Canal5.uy' -} - -it('can generate valid url', async () => { - const result = await url({ date, channel }) - - expect(result).toBe( - 'https://cds-frontend.vera.com.uy/api-contenidos/canales/epg/2s6nd?limit=500&dias_siguientes=0&fecha=2023-02-11&token=MpDY52p1V6g511VSABp1015B' - ) -}) - -it('can generate valid request headers', async () => { - const result = await request.headers() - - expect(result).toMatchObject({ - authorization: - 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOnsidGlwbyI6ImFub25pbWEifSwic3ViIjoiTXBEWTUycDFWNmc1MTFWU0FCcDEwMTVCIiwicHJuIjp7ImlkX3NlcnZpY2lvIjozLCJpZF9mcm9udGVuZCI6MTE5NiwiaXAiOiIxNzkuMjcuMTU0LjI0MiIsImlwX3JlZmVyZW5jaWFkYSI6IjE4OC4yNDIuNDguOTMiLCJpZF9kaXNwb3NpdGl2byI6MH0sImF1ZCI6IkFwcHNcL1dlYnMgRnJvbnRlbmRzIiwiaWF0IjoxNjc1ODI3NDU2LCJleHAiOjE2NzU4NDkwNTZ9.8bAQciQl5DOIZF7GgCl6ad-KJUSpqQREetozGv_IH5s', - 'x-frontend-id': 1196, - 'x-service-id': 3, - 'x-system-id': 1 - }) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') - let results = parser({ content }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2023-02-11T02:30:00.000Z', - stop: '2023-02-11T04:00:00.000Z', - title: 'Canal 5 Noticias rep.', - sub_title: '', - description: '' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json'), 'utf8') - }) - - expect(results).toMatchObject([]) -}) +const { parser, url, request } = require('./anteltv.com.uy.config.js') +const fs = require('fs') +const axios = require('axios') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +axios.post.mockImplementation((url, data, opts) => { + if ( + url === 'https://veratv-be.vera.com.uy/api/sesiones' && + JSON.stringify(opts.headers) === + JSON.stringify({ + 'Content-Type': 'application/json' + }) && + JSON.stringify(data) === + JSON.stringify({ + tipo: 'anonima' + }) + ) { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/session.json'))) + }) + } else { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/no_session.json'))) + }) + } +}) + +const date = dayjs.utc('2023-02-11', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '2s6nd', + xmltv_id: 'Canal5.uy' +} + +it('can generate valid url', async () => { + const result = await url({ date, channel }) + + expect(result).toBe( + 'https://cds-frontend.vera.com.uy/api-contenidos/canales/epg/2s6nd?limit=500&dias_siguientes=0&fecha=2023-02-11&token=MpDY52p1V6g511VSABp1015B' + ) +}) + +it('can generate valid request headers', async () => { + const result = await request.headers() + + expect(result).toMatchObject({ + authorization: + 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOnsidGlwbyI6ImFub25pbWEifSwic3ViIjoiTXBEWTUycDFWNmc1MTFWU0FCcDEwMTVCIiwicHJuIjp7ImlkX3NlcnZpY2lvIjozLCJpZF9mcm9udGVuZCI6MTE5NiwiaXAiOiIxNzkuMjcuMTU0LjI0MiIsImlwX3JlZmVyZW5jaWFkYSI6IjE4OC4yNDIuNDguOTMiLCJpZF9kaXNwb3NpdGl2byI6MH0sImF1ZCI6IkFwcHNcL1dlYnMgRnJvbnRlbmRzIiwiaWF0IjoxNjc1ODI3NDU2LCJleHAiOjE2NzU4NDkwNTZ9.8bAQciQl5DOIZF7GgCl6ad-KJUSpqQREetozGv_IH5s', + 'x-frontend-id': 1196, + 'x-service-id': 3, + 'x-system-id': 1 + }) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') + let results = parser({ content }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2023-02-11T02:30:00.000Z', + stop: '2023-02-11T04:00:00.000Z', + title: 'Canal 5 Noticias rep.', + sub_title: '', + description: '' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json'), 'utf8') + }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/antennaeurope.gr/antennaeurope.gr.config.js b/sites/antennaeurope.gr/antennaeurope.gr.config.js index 982685d9b..aa5f01466 100644 --- a/sites/antennaeurope.gr/antennaeurope.gr.config.js +++ b/sites/antennaeurope.gr/antennaeurope.gr.config.js @@ -1,59 +1,59 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'antennaeurope.gr', - days: 2, - url({ date }) { - return `https://www.antennaeurope.gr/el/tvguide.html?date=${date.format('YYYY-MM-DD')}` - }, - parser({ content, date }) { - const programs = [] - const items = parseItems(content, date) - items.forEach(item => { - const $item = cheerio.load(item) - const prev = programs[programs.length - 1] - let start = parseStart($item, date) - if (prev) { - if (start.isBefore(prev.start)) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.add(30, 'm') - programs.push({ - title: parseTitle($item), - start, - stop - }) - }) - - return programs - } -} - -function parseTitle($item) { - return $item('.title').text().trim() -} - -function parseStart($item, date) { - const time = $item('dt.col-time').clone().children().remove().end().text().trim() - - return time - ? dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'Europe/Athens') - : null -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('dl.show').toArray() -} +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'antennaeurope.gr', + days: 2, + url({ date }) { + return `https://www.antennaeurope.gr/el/tvguide.html?date=${date.format('YYYY-MM-DD')}` + }, + parser({ content, date }) { + const programs = [] + const items = parseItems(content, date) + items.forEach(item => { + const $item = cheerio.load(item) + const prev = programs[programs.length - 1] + let start = parseStart($item, date) + if (prev) { + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.add(30, 'm') + programs.push({ + title: parseTitle($item), + start, + stop + }) + }) + + return programs + } +} + +function parseTitle($item) { + return $item('.title').text().trim() +} + +function parseStart($item, date) { + const time = $item('dt.col-time').clone().children().remove().end().text().trim() + + return time + ? dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'Europe/Athens') + : null +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('dl.show').toArray() +} diff --git a/sites/antennaeurope.gr/antennaeurope.gr.test.js b/sites/antennaeurope.gr/antennaeurope.gr.test.js index 7deb858e0..e61d8de77 100644 --- a/sites/antennaeurope.gr/antennaeurope.gr.test.js +++ b/sites/antennaeurope.gr/antennaeurope.gr.test.js @@ -1,46 +1,46 @@ -const { parser, url } = require('./antennaeurope.gr.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-01-21', 'YYYY-MM-DD').startOf('d') - -it('can generate valid url', () => { - expect(url({ date })).toBe('https://www.antennaeurope.gr/el/tvguide.html?date=2025-01-21') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') - let results = parser({ content, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(16) - - expect(results[0]).toMatchObject({ - start: '2025-01-21T03:45:00.000Z', - stop: '2025-01-21T07:50:00.000Z', - title: 'ΚΑΛΗΜΕΡΑ ΕΛΛΑΔΑ' - }) - - expect(results[15]).toMatchObject({ - start: '2025-01-22T01:30:00.000Z', - stop: '2025-01-22T02:00:00.000Z', - title: 'ΤΟ ΠΡΩΙΝΟ' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - date, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') - }) - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./antennaeurope.gr.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-01-21', 'YYYY-MM-DD').startOf('d') + +it('can generate valid url', () => { + expect(url({ date })).toBe('https://www.antennaeurope.gr/el/tvguide.html?date=2025-01-21') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') + let results = parser({ content, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(16) + + expect(results[0]).toMatchObject({ + start: '2025-01-21T03:45:00.000Z', + stop: '2025-01-21T07:50:00.000Z', + title: 'ΚΑΛΗΜΕΡΑ ΕΛΛΑΔΑ' + }) + + expect(results[15]).toMatchObject({ + start: '2025-01-22T01:30:00.000Z', + stop: '2025-01-22T02:00:00.000Z', + title: 'ΤΟ ΠΡΩΙΝΟ' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + date, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') + }) + expect(results).toMatchObject([]) +}) diff --git a/sites/antennapacific.gr/antennapacific.gr.config.js b/sites/antennapacific.gr/antennapacific.gr.config.js index a0b06e29c..5b3aaec17 100644 --- a/sites/antennapacific.gr/antennapacific.gr.config.js +++ b/sites/antennapacific.gr/antennapacific.gr.config.js @@ -1,59 +1,59 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'antennapacific.gr', - days: 2, - url({ date }) { - return `https://www.antennapacific.gr/el/tvguide.html?date=${date.format('YYYY-MM-DD')}` - }, - parser({ content, date }) { - const programs = [] - const items = parseItems(content, date) - items.forEach(item => { - const $item = cheerio.load(item) - const prev = programs[programs.length - 1] - let start = parseStart($item, date) - if (prev) { - if (start.isBefore(prev.start)) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.add(30, 'm') - programs.push({ - title: parseTitle($item), - start, - stop - }) - }) - - return programs - } -} - -function parseTitle($item) { - return $item('.title').text().trim() -} - -function parseStart($item, date) { - const time = $item('dt.col-time').clone().children().remove().end().text().trim() - - return time - ? dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'Europe/Athens') - : null -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('dl.show').toArray() -} +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'antennapacific.gr', + days: 2, + url({ date }) { + return `https://www.antennapacific.gr/el/tvguide.html?date=${date.format('YYYY-MM-DD')}` + }, + parser({ content, date }) { + const programs = [] + const items = parseItems(content, date) + items.forEach(item => { + const $item = cheerio.load(item) + const prev = programs[programs.length - 1] + let start = parseStart($item, date) + if (prev) { + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.add(30, 'm') + programs.push({ + title: parseTitle($item), + start, + stop + }) + }) + + return programs + } +} + +function parseTitle($item) { + return $item('.title').text().trim() +} + +function parseStart($item, date) { + const time = $item('dt.col-time').clone().children().remove().end().text().trim() + + return time + ? dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'Europe/Athens') + : null +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('dl.show').toArray() +} diff --git a/sites/antennapacific.gr/antennapacific.gr.test.js b/sites/antennapacific.gr/antennapacific.gr.test.js index 2047519a7..a1b2e368d 100644 --- a/sites/antennapacific.gr/antennapacific.gr.test.js +++ b/sites/antennapacific.gr/antennapacific.gr.test.js @@ -1,46 +1,46 @@ -const { parser, url } = require('./antennapacific.gr.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-01-21', 'YYYY-MM-DD').startOf('d') - -it('can generate valid url', () => { - expect(url({ date })).toBe('https://www.antennapacific.gr/el/tvguide.html?date=2025-01-21') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') - let results = parser({ content, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(17) - - expect(results[0]).toMatchObject({ - start: '2025-01-21T05:00:00.000Z', - stop: '2025-01-21T06:00:00.000Z', - title: 'ANT1 NEWS - ΚΕΝΤΡΙΚΟ ΔΕΛΤΙΟ' - }) - - expect(results[16]).toMatchObject({ - start: '2025-01-22T02:45:00.000Z', - stop: '2025-01-22T03:15:00.000Z', - title: 'ΚΑΛΗΜΕΡΑ ΕΛΛΑΔΑ' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - date, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') - }) - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./antennapacific.gr.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-01-21', 'YYYY-MM-DD').startOf('d') + +it('can generate valid url', () => { + expect(url({ date })).toBe('https://www.antennapacific.gr/el/tvguide.html?date=2025-01-21') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') + let results = parser({ content, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(17) + + expect(results[0]).toMatchObject({ + start: '2025-01-21T05:00:00.000Z', + stop: '2025-01-21T06:00:00.000Z', + title: 'ANT1 NEWS - ΚΕΝΤΡΙΚΟ ΔΕΛΤΙΟ' + }) + + expect(results[16]).toMatchObject({ + start: '2025-01-22T02:45:00.000Z', + stop: '2025-01-22T03:15:00.000Z', + title: 'ΚΑΛΗΜΕΡΑ ΕΛΛΑΔΑ' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + date, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') + }) + expect(results).toMatchObject([]) +}) diff --git a/sites/antennasatellite.gr/antennasatellite.gr.config.js b/sites/antennasatellite.gr/antennasatellite.gr.config.js index d3a35bde8..a604d9be6 100644 --- a/sites/antennasatellite.gr/antennasatellite.gr.config.js +++ b/sites/antennasatellite.gr/antennasatellite.gr.config.js @@ -1,59 +1,59 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'antennasatellite.gr', - days: 2, - url({ date }) { - return `https://www.antennasatellite.gr/el/tvguide.html?date=${date.format('YYYY-MM-DD')}` - }, - parser({ content, date }) { - const programs = [] - const items = parseItems(content, date) - items.forEach(item => { - const $item = cheerio.load(item) - const prev = programs[programs.length - 1] - let start = parseStart($item, date) - if (prev) { - if (start.isBefore(prev.start)) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.add(30, 'm') - programs.push({ - title: parseTitle($item), - start, - stop - }) - }) - - return programs - } -} - -function parseTitle($item) { - return $item('.title').text().trim() -} - -function parseStart($item, date) { - const time = $item('dt.col-time').clone().children().remove().end().text().trim() - - return time - ? dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'Europe/Athens') - : null -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('dl.show').toArray() -} +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'antennasatellite.gr', + days: 2, + url({ date }) { + return `https://www.antennasatellite.gr/el/tvguide.html?date=${date.format('YYYY-MM-DD')}` + }, + parser({ content, date }) { + const programs = [] + const items = parseItems(content, date) + items.forEach(item => { + const $item = cheerio.load(item) + const prev = programs[programs.length - 1] + let start = parseStart($item, date) + if (prev) { + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.add(30, 'm') + programs.push({ + title: parseTitle($item), + start, + stop + }) + }) + + return programs + } +} + +function parseTitle($item) { + return $item('.title').text().trim() +} + +function parseStart($item, date) { + const time = $item('dt.col-time').clone().children().remove().end().text().trim() + + return time + ? dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'Europe/Athens') + : null +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('dl.show').toArray() +} diff --git a/sites/antennasatellite.gr/antennasatellite.gr.test.js b/sites/antennasatellite.gr/antennasatellite.gr.test.js index 9923108e7..59ada99e2 100644 --- a/sites/antennasatellite.gr/antennasatellite.gr.test.js +++ b/sites/antennasatellite.gr/antennasatellite.gr.test.js @@ -1,46 +1,46 @@ -const { parser, url } = require('./antennasatellite.gr.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-01-21', 'YYYY-MM-DD').startOf('d') - -it('can generate valid url', () => { - expect(url({ date })).toBe('https://www.antennasatellite.gr/el/tvguide.html?date=2025-01-21') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') - let results = parser({ content, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(16) - - expect(results[0]).toMatchObject({ - start: '2025-01-21T04:00:00.000Z', - stop: '2025-01-21T04:40:00.000Z', - title: 'ANT1 NEWS' - }) - - expect(results[15]).toMatchObject({ - start: '2025-01-22T00:50:00.000Z', - stop: '2025-01-22T01:20:00.000Z', - title: 'ΤΟ ΠΡΩΙΝΟ' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - date, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') - }) - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./antennasatellite.gr.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-01-21', 'YYYY-MM-DD').startOf('d') + +it('can generate valid url', () => { + expect(url({ date })).toBe('https://www.antennasatellite.gr/el/tvguide.html?date=2025-01-21') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') + let results = parser({ content, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(16) + + expect(results[0]).toMatchObject({ + start: '2025-01-21T04:00:00.000Z', + stop: '2025-01-21T04:40:00.000Z', + title: 'ANT1 NEWS' + }) + + expect(results[15]).toMatchObject({ + start: '2025-01-22T00:50:00.000Z', + stop: '2025-01-22T01:20:00.000Z', + title: 'ΤΟ ΠΡΩΙΝΟ' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + date, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') + }) + expect(results).toMatchObject([]) +}) diff --git a/sites/arianatelevision.com/arianatelevision.com.config.js b/sites/arianatelevision.com/arianatelevision.com.config.js index d6284e46f..40bc9b798 100644 --- a/sites/arianatelevision.com/arianatelevision.com.config.js +++ b/sites/arianatelevision.com/arianatelevision.com.config.js @@ -1,60 +1,60 @@ -const cheerio = require('cheerio') -const { DateTime } = require('luxon') - -module.exports = { - site: 'arianatelevision.com', - days: 2, - url: 'https://www.arianatelevision.com/program-schedule/', - parser({ content, date }) { - const programs = [] - const items = parseItems(content, date) - items.forEach(item => { - const prev = programs[programs.length - 1] - let start = parseStart(item, date) - if (prev) { - if (start < prev.start) { - start = start.plus({ days: 1 }) - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.plus({ minutes: 30 }) - programs.push({ - title: item.title, - start, - stop - }) - }) - - return programs - } -} - -function parseStart(item, date) { - const time = `${date.format('YYYY-MM-DD')} ${item.start}` - - return DateTime.fromFormat(time, 'yyyy-MM-dd H:mm', { zone: 'Asia/Kabul' }).toUTC() -} - -function parseItems(content, date) { - const $ = cheerio.load(content) - const settings = $('#jtrt_table_settings_508').text() - if (!settings) return [] - const data = JSON.parse(settings) - if (!data || !Array.isArray(data)) return [] - - let rows = data[0] - rows.shift() - const output = [] - rows.forEach(row => { - let day = date.day() + 2 - if (day > 7) day = 1 - if (!row[0] || !row[day]) return - output.push({ - start: row[0].trim(), - title: row[day].trim() - }) - }) - - return output -} +const cheerio = require('cheerio') +const { DateTime } = require('luxon') + +module.exports = { + site: 'arianatelevision.com', + days: 2, + url: 'https://www.arianatelevision.com/program-schedule/', + parser({ content, date }) { + const programs = [] + const items = parseItems(content, date) + items.forEach(item => { + const prev = programs[programs.length - 1] + let start = parseStart(item, date) + if (prev) { + if (start < prev.start) { + start = start.plus({ days: 1 }) + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.plus({ minutes: 30 }) + programs.push({ + title: item.title, + start, + stop + }) + }) + + return programs + } +} + +function parseStart(item, date) { + const time = `${date.format('YYYY-MM-DD')} ${item.start}` + + return DateTime.fromFormat(time, 'yyyy-MM-dd H:mm', { zone: 'Asia/Kabul' }).toUTC() +} + +function parseItems(content, date) { + const $ = cheerio.load(content) + const settings = $('#jtrt_table_settings_508').text() + if (!settings) return [] + const data = JSON.parse(settings) + if (!data || !Array.isArray(data)) return [] + + let rows = data[0] + rows.shift() + const output = [] + rows.forEach(row => { + let day = date.day() + 2 + if (day > 7) day = 1 + if (!row[0] || !row[day]) return + output.push({ + start: row[0].trim(), + title: row[day].trim() + }) + }) + + return output +} diff --git a/sites/arirang.com/arirang.com.config.js b/sites/arirang.com/arirang.com.config.js index 918a6275c..7d20b9122 100644 --- a/sites/arirang.com/arirang.com.config.js +++ b/sites/arirang.com/arirang.com.config.js @@ -1,163 +1,163 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'arirang.com', - output: 'arirang.com.guide.xml', - channels: 'arirang.com.channels.xml', - lang: 'en', - days: 7, - delay: 5000, - url: 'https://www.arirang.com/v1.0/open/external/proxy', - - request: { - method: 'POST', - timeout: 5000, - cache: { ttl: 60 * 60 * 1000 }, - headers: { - Accept: 'application/json, text/plain, */*', - 'Content-Type': 'application/json', - Origin: 'https://www.arirang.com', - Referer: 'https://www.arirang.com/schedule', - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36' - }, - data: function (context) { - const { channel, date } = context - return { - address: 'https://script.arirang.com/api/v1/bis/listScheduleV3.do', - method: 'POST', - headers: {}, - body: { - data: { - dmParam: { - chanId: channel.site_id, - broadYmd: dayjs.tz(date, 'Asia/Seoul').format('YYYYMMDD'), - planNo: '1' - } - } - } - } - } - }, - - logo: function (context) { - return context.channel.logo - }, - - async parser(context) { - const programs = [] - const items = parseItems(context.content) - - for (let item of items) { - const programDetail = await parseProgramDetail(item) - - programs.push({ - title: parseTitle(programDetail), - start: parseStart(item), - stop: parseStop(item), - image: parseImage(programDetail), - category: parseCategory(programDetail), - description: parseDescription(programDetail) - }) - } - - return programs - } -} - -function parseItems(content) { - if (content != '') { - const data = JSON.parse(content) - return !data || !data.responseBody || !Array.isArray(data.responseBody.dsSchWeek) - ? [] - : data.responseBody.dsSchWeek - } else { - return [] - } -} - -function parseStart(item) { - return dayjs.tz(item.broadYmd + ' ' + item.broadHm, 'YYYYMMDD HHmm', 'Asia/Seoul') -} - -function parseStop(item) { - return dayjs - .tz(item.broadYmd + ' ' + item.broadHm, 'YYYYMMDD HHmm', 'Asia/Seoul') - .add(item.broadRun, 'minute') -} - -async function parseProgramDetail(item) { - return axios - .post( - 'https://www.arirang.com/v1.0/open/program/detail', - { - bis_program_code: item.pgmCd - }, - { - headers: { - Accept: 'application/json, text/plain, */*', - 'Content-Type': 'application/json', - Origin: 'https://www.arirang.com', - Referer: 'https://www.arirang.com/schedule', - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36' - }, - timeout: 5000, - cache: { ttl: 60 * 1000 } - } - ) - .then(response => { - // console.log('Retrieved program detail: bis_program_code ' + item.pgmCd) - return response.data - }) - .catch(function () { - // The provider/server may not have details on every single programs. - // console.log('Unavailable program detail: bis_program_code ' + item.pgmCd) - }) -} - -function parseTitle(programDetail) { - if (programDetail && programDetail.title && programDetail.title[0] && programDetail.title[0].text) { - return programDetail.title[0].text - } else { - return '' - } -} - -function parseImage(programDetail) { - if (programDetail && programDetail.image && programDetail.image[0].url) { - return programDetail.image[0].url - } else { - return '' - } -} - -function parseCategory(programDetail) { - if (programDetail && programDetail.category_Info && programDetail.category_Info[0].title) { - return programDetail.category_Info[0].title - } else { - return '' - } -} - -function parseDescription(programDetail) { - if ( - programDetail && - programDetail.content && - programDetail.content[0] && - programDetail.content[0].text - ) { - let description = programDetail.content[0].text - let regex = /(<([^>]+)>)/gi - return description.replace(regex, '') - } else { - return '' - } +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'arirang.com', + output: 'arirang.com.guide.xml', + channels: 'arirang.com.channels.xml', + lang: 'en', + days: 7, + delay: 5000, + url: 'https://www.arirang.com/v1.0/open/external/proxy', + + request: { + method: 'POST', + timeout: 5000, + cache: { ttl: 60 * 60 * 1000 }, + headers: { + Accept: 'application/json, text/plain, */*', + 'Content-Type': 'application/json', + Origin: 'https://www.arirang.com', + Referer: 'https://www.arirang.com/schedule', + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36' + }, + data: function (context) { + const { channel, date } = context + return { + address: 'https://script.arirang.com/api/v1/bis/listScheduleV3.do', + method: 'POST', + headers: {}, + body: { + data: { + dmParam: { + chanId: channel.site_id, + broadYmd: dayjs.tz(date, 'Asia/Seoul').format('YYYYMMDD'), + planNo: '1' + } + } + } + } + } + }, + + logo: function (context) { + return context.channel.logo + }, + + async parser(context) { + const programs = [] + const items = parseItems(context.content) + + for (let item of items) { + const programDetail = await parseProgramDetail(item) + + programs.push({ + title: parseTitle(programDetail), + start: parseStart(item), + stop: parseStop(item), + image: parseImage(programDetail), + category: parseCategory(programDetail), + description: parseDescription(programDetail) + }) + } + + return programs + } +} + +function parseItems(content) { + if (content != '') { + const data = JSON.parse(content) + return !data || !data.responseBody || !Array.isArray(data.responseBody.dsSchWeek) + ? [] + : data.responseBody.dsSchWeek + } else { + return [] + } +} + +function parseStart(item) { + return dayjs.tz(item.broadYmd + ' ' + item.broadHm, 'YYYYMMDD HHmm', 'Asia/Seoul') +} + +function parseStop(item) { + return dayjs + .tz(item.broadYmd + ' ' + item.broadHm, 'YYYYMMDD HHmm', 'Asia/Seoul') + .add(item.broadRun, 'minute') +} + +async function parseProgramDetail(item) { + return axios + .post( + 'https://www.arirang.com/v1.0/open/program/detail', + { + bis_program_code: item.pgmCd + }, + { + headers: { + Accept: 'application/json, text/plain, */*', + 'Content-Type': 'application/json', + Origin: 'https://www.arirang.com', + Referer: 'https://www.arirang.com/schedule', + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36' + }, + timeout: 5000, + cache: { ttl: 60 * 1000 } + } + ) + .then(response => { + // console.log('Retrieved program detail: bis_program_code ' + item.pgmCd) + return response.data + }) + .catch(function () { + // The provider/server may not have details on every single programs. + // console.log('Unavailable program detail: bis_program_code ' + item.pgmCd) + }) +} + +function parseTitle(programDetail) { + if (programDetail && programDetail.title && programDetail.title[0] && programDetail.title[0].text) { + return programDetail.title[0].text + } else { + return '' + } +} + +function parseImage(programDetail) { + if (programDetail && programDetail.image && programDetail.image[0].url) { + return programDetail.image[0].url + } else { + return '' + } +} + +function parseCategory(programDetail) { + if (programDetail && programDetail.category_Info && programDetail.category_Info[0].title) { + return programDetail.category_Info[0].title + } else { + return '' + } +} + +function parseDescription(programDetail) { + if ( + programDetail && + programDetail.content && + programDetail.content[0] && + programDetail.content[0].text + ) { + let description = programDetail.content[0].text + let regex = /(<([^>]+)>)/gi + return description.replace(regex, '') + } else { + return '' + } } \ No newline at end of file diff --git a/sites/arirang.com/arirang.com.test.js b/sites/arirang.com/arirang.com.test.js index 2efbbae99..ed3bf49ba 100644 --- a/sites/arirang.com/arirang.com.test.js +++ b/sites/arirang.com/arirang.com.test.js @@ -1,72 +1,72 @@ -const { url, parser } = require('./arirang.com.config.js') -const fs = require('fs') -const path = require('path') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -dayjs.extend(utc) - -jest.mock('axios') - -const date = dayjs.tz('2025-04-20', 'Asia/Seoul').startOf('d') -const channel = { - xmltv_id: 'ArirangWorld.kr', - site_id: 'CH_W', - name: 'Arirang World', - lang: 'en', - logo: 'https://i.imgur.com/5Aoithj.png' -} -const content = fs.readFileSync(path.resolve(__dirname, '__data__/schedule.json'), 'utf8') -const programDetail = fs.readFileSync(path.resolve(__dirname, '__data__/detail.json'), 'utf8') -const context = { channel: channel, content: content, date: date } - -it('can generate valid url', () => { - expect(url).toBe('https://www.arirang.com/v1.0/open/external/proxy') -}) - -it('can handle empty guide', async () => { - const results = await parser({ channel: channel, content: '', date: date }) - expect(results).toMatchObject([]) -}) - -it('can parse response', async () => { - axios.post.mockImplementation((url, data) => { - if ( - url === 'https://www.arirang.com/v1.0/open/external/proxy' && - JSON.stringify(data) === - JSON.stringify({ - address: 'https://script.arirang.com/api/v1/bis/listScheduleV3.do', - method: 'POST', - headers: {}, - body: { data: { dmParam: { chanId: 'CH_W', broadYmd: '20250420', planNo: '1' } } } - }) - ) { - return Promise.resolve({ - data: JSON.parse(content) - }) - } else if ( - url === 'https://www.arirang.com/v1.0/open/program/detail' && - JSON.stringify(data) === JSON.stringify({ bis_program_code: '2025006T' }) - ) { - return Promise.resolve({ - data: JSON.parse(programDetail) - }) - } else { - return Promise.resolve({ - data: '' - }) - } - }) - - const results = await parser(context) - - expect(results[0]).toMatchObject({ - title: 'Diplomat Archives: Hidden Stories', - start: dayjs.tz(date, 'Asia/Seoul'), - stop: dayjs.tz(date, 'Asia/Seoul').add(30, 'minute'), - image: - 'https://img.arirang.com/v1/AUTH_d52449c16d3b4bbca17d4fffd9fc44af/public/images/202504/2985531324875408146.jpg', - description: 'As of April 2025, S. Korea has established diplomatic relations with a total of 194 countries.\nAmong them are countries that have had ties and exchanges with Korea for hundreds of years.\nWith such long-standing relationships with so many nations,\nmight there be fascinating hidden stories between Korea and the rest of the world that we don’t know yet? \n\n"Diplomat’s Archives: Hidden Stories" begins with this very question.\nTogether with foreign embassies in Korea, the series uncovers and sheds light on meaningful yet lesser-known stories between Korea and other countries.\nThrough this, we aim to reaffirm the deep friendships that have been built over time, highlight how countries are interconnected—bilaterally and multilaterally—\nand emphasize the importance of cooperation on the global stage today.', - category: 'Current Affairs' - }) +const { url, parser } = require('./arirang.com.config.js') +const fs = require('fs') +const path = require('path') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.tz('2025-04-20', 'Asia/Seoul').startOf('d') +const channel = { + xmltv_id: 'ArirangWorld.kr', + site_id: 'CH_W', + name: 'Arirang World', + lang: 'en', + logo: 'https://i.imgur.com/5Aoithj.png' +} +const content = fs.readFileSync(path.resolve(__dirname, '__data__/schedule.json'), 'utf8') +const programDetail = fs.readFileSync(path.resolve(__dirname, '__data__/detail.json'), 'utf8') +const context = { channel: channel, content: content, date: date } + +it('can generate valid url', () => { + expect(url).toBe('https://www.arirang.com/v1.0/open/external/proxy') +}) + +it('can handle empty guide', async () => { + const results = await parser({ channel: channel, content: '', date: date }) + expect(results).toMatchObject([]) +}) + +it('can parse response', async () => { + axios.post.mockImplementation((url, data) => { + if ( + url === 'https://www.arirang.com/v1.0/open/external/proxy' && + JSON.stringify(data) === + JSON.stringify({ + address: 'https://script.arirang.com/api/v1/bis/listScheduleV3.do', + method: 'POST', + headers: {}, + body: { data: { dmParam: { chanId: 'CH_W', broadYmd: '20250420', planNo: '1' } } } + }) + ) { + return Promise.resolve({ + data: JSON.parse(content) + }) + } else if ( + url === 'https://www.arirang.com/v1.0/open/program/detail' && + JSON.stringify(data) === JSON.stringify({ bis_program_code: '2025006T' }) + ) { + return Promise.resolve({ + data: JSON.parse(programDetail) + }) + } else { + return Promise.resolve({ + data: '' + }) + } + }) + + const results = await parser(context) + + expect(results[0]).toMatchObject({ + title: 'Diplomat Archives: Hidden Stories', + start: dayjs.tz(date, 'Asia/Seoul'), + stop: dayjs.tz(date, 'Asia/Seoul').add(30, 'minute'), + image: + 'https://img.arirang.com/v1/AUTH_d52449c16d3b4bbca17d4fffd9fc44af/public/images/202504/2985531324875408146.jpg', + description: 'As of April 2025, S. Korea has established diplomatic relations with a total of 194 countries.\nAmong them are countries that have had ties and exchanges with Korea for hundreds of years.\nWith such long-standing relationships with so many nations,\nmight there be fascinating hidden stories between Korea and the rest of the world that we don’t know yet? \n\n"Diplomat’s Archives: Hidden Stories" begins with this very question.\nTogether with foreign embassies in Korea, the series uncovers and sheds light on meaningful yet lesser-known stories between Korea and other countries.\nThrough this, we aim to reaffirm the deep friendships that have been built over time, highlight how countries are interconnected—bilaterally and multilaterally—\nand emphasize the importance of cooperation on the global stage today.', + category: 'Current Affairs' + }) }) \ No newline at end of file diff --git a/sites/artonline.tv/artonline.tv.config.js b/sites/artonline.tv/artonline.tv.config.js index 2b9fe78c4..1b38c8d8a 100644 --- a/sites/artonline.tv/artonline.tv.config.js +++ b/sites/artonline.tv/artonline.tv.config.js @@ -1,70 +1,70 @@ -process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0 - -const customParseFormat = require('dayjs/plugin/customParseFormat') -const timezone = require('dayjs/plugin/timezone') -const utc = require('dayjs/plugin/utc') -const dayjs = require('dayjs') - -dayjs.extend(customParseFormat) -dayjs.extend(timezone) -dayjs.extend(utc) - -module.exports = { - site: 'artonline.tv', - days: 2, - url: function ({ channel }) { - const [, site_id] = channel.site_id.split('#') - - return `https://www.artonline.tv/Home/Tvlist${site_id}` - }, - request: { - method: 'POST', - headers: { - 'content-type': 'application/x-www-form-urlencoded' - }, - data: function ({ date }) { - const diff = date.diff(dayjs.utc().startOf('d'), 'd') - const params = new URLSearchParams() - params.append('objId', diff) - - return params - } - }, - parser: function ({ content }) { - const programs = [] - if (!content) return programs - const items = JSON.parse(content) - items.forEach(item => { - const image = parseImage(item) - const start = parseStart(item) - const duration = parseDuration(item) - const stop = start.add(duration, 's') - programs.push({ - title: item.title, - description: item.description, - image, - start, - stop - }) - }) - - return programs - } -} - -function parseStart(item) { - const [, M, D, YYYY] = item.adddate.match(/(\d+)\/(\d+)\/(\d+) /) - const [HH, mm] = item.start_Time.split(':') - - return dayjs.tz(`${YYYY}-${M}-${D}T${HH}:${mm}:00`, 'YYYY-M-DTHH:mm:ss', 'Asia/Riyadh') -} - -function parseDuration(item) { - const [, HH, mm, ss] = item.duration.match(/(\d+):(\d+):(\d+)/) - - return parseInt(HH) * 3600 + parseInt(mm) * 60 + parseInt(ss) -} - -function parseImage(item) { - return item.thumbnail ? `https://www.artonline.tv${item.thumbnail}` : null -} +process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0 + +const customParseFormat = require('dayjs/plugin/customParseFormat') +const timezone = require('dayjs/plugin/timezone') +const utc = require('dayjs/plugin/utc') +const dayjs = require('dayjs') + +dayjs.extend(customParseFormat) +dayjs.extend(timezone) +dayjs.extend(utc) + +module.exports = { + site: 'artonline.tv', + days: 2, + url: function ({ channel }) { + const [, site_id] = channel.site_id.split('#') + + return `https://www.artonline.tv/Home/Tvlist${site_id}` + }, + request: { + method: 'POST', + headers: { + 'content-type': 'application/x-www-form-urlencoded' + }, + data: function ({ date }) { + const diff = date.diff(dayjs.utc().startOf('d'), 'd') + const params = new URLSearchParams() + params.append('objId', diff) + + return params + } + }, + parser: function ({ content }) { + const programs = [] + if (!content) return programs + const items = JSON.parse(content) + items.forEach(item => { + const image = parseImage(item) + const start = parseStart(item) + const duration = parseDuration(item) + const stop = start.add(duration, 's') + programs.push({ + title: item.title, + description: item.description, + image, + start, + stop + }) + }) + + return programs + } +} + +function parseStart(item) { + const [, M, D, YYYY] = item.adddate.match(/(\d+)\/(\d+)\/(\d+) /) + const [HH, mm] = item.start_Time.split(':') + + return dayjs.tz(`${YYYY}-${M}-${D}T${HH}:${mm}:00`, 'YYYY-M-DTHH:mm:ss', 'Asia/Riyadh') +} + +function parseDuration(item) { + const [, HH, mm, ss] = item.duration.match(/(\d+):(\d+):(\d+)/) + + return parseInt(HH) * 3600 + parseInt(mm) * 60 + parseInt(ss) +} + +function parseImage(item) { + return item.thumbnail ? `https://www.artonline.tv${item.thumbnail}` : null +} diff --git a/sites/awilime.com/awilime.com.config.js b/sites/awilime.com/awilime.com.config.js index 1617df02f..05ad06ada 100644 --- a/sites/awilime.com/awilime.com.config.js +++ b/sites/awilime.com/awilime.com.config.js @@ -1,86 +1,86 @@ -const cheerio = require('cheerio') -const axios = require('axios') -const { DateTime } = require('luxon') - -module.exports = { - site: 'awilime.com', - days: 2, - url({ channel, date }) { - return `https://www.awilime.com/tv/napi_musor/${channel.site_id}/${date.format('YYYY_MM_DD')}` - }, - parser({ content, date }) { - const programs = [] - const items = parseItems(content) - items.forEach(item => { - const prev = programs[programs.length - 1] - const $item = cheerio.load(item) - let start = parseStart($item, date) - if (!start) return - if (prev) { - prev.stop = start - } - const stop = start.plus({ minute: 30 }) - - programs.push({ - title: parseTitle($item), - sub_title: parseSubTitle($item), - description: parseDescription($item), - start, - stop - }) - }) - - return programs - }, - async channels() { - const html = await axios - .get('https://www.awilime.com/tv/napi_musor') - .then(r => r.data) - .catch(console.log) - const $ = cheerio.load(html) - const items = $('#body > div.tk > div > div').toArray() - - const channels = [] - items.forEach(item => { - const name = $(item).find('a').text().trim() - const url = $(item).find('a').attr('href') - const [, site_id] = url.match(/\/tv\/napi_musor\/(.*)/) || [null, null] - if (!site_id) return - if (channels.find(channel => channel.site_id === site_id)) return - - channels.push({ - lang: 'hu', - site_id, - name - }) - }) - - return channels - } -} - -function parseTitle($item) { - return $item('b > a').text().trim() -} - -function parseSubTitle($item) { - return $item('i').clone().children().remove('s').end().text().trim() -} - -function parseDescription($item) { - return $item('p').text().trim() -} - -function parseStart($item, date) { - let time = $item('b').clone().children().remove().end().text().trim() - if (!time || !/^\d/.test(time)) return null - time = `${date.format('YYYY-MM-DD')} ${time}` - - return DateTime.fromFormat(time, 'yyyy-MM-dd HH:mm', { zone: 'Europe/Budapest' }).toUTC() -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('#body > div.tdc > div.td2 > div').toArray() -} +const cheerio = require('cheerio') +const axios = require('axios') +const { DateTime } = require('luxon') + +module.exports = { + site: 'awilime.com', + days: 2, + url({ channel, date }) { + return `https://www.awilime.com/tv/napi_musor/${channel.site_id}/${date.format('YYYY_MM_DD')}` + }, + parser({ content, date }) { + const programs = [] + const items = parseItems(content) + items.forEach(item => { + const prev = programs[programs.length - 1] + const $item = cheerio.load(item) + let start = parseStart($item, date) + if (!start) return + if (prev) { + prev.stop = start + } + const stop = start.plus({ minute: 30 }) + + programs.push({ + title: parseTitle($item), + sub_title: parseSubTitle($item), + description: parseDescription($item), + start, + stop + }) + }) + + return programs + }, + async channels() { + const html = await axios + .get('https://www.awilime.com/tv/napi_musor') + .then(r => r.data) + .catch(console.log) + const $ = cheerio.load(html) + const items = $('#body > div.tk > div > div').toArray() + + const channels = [] + items.forEach(item => { + const name = $(item).find('a').text().trim() + const url = $(item).find('a').attr('href') + const [, site_id] = url.match(/\/tv\/napi_musor\/(.*)/) || [null, null] + if (!site_id) return + if (channels.find(channel => channel.site_id === site_id)) return + + channels.push({ + lang: 'hu', + site_id, + name + }) + }) + + return channels + } +} + +function parseTitle($item) { + return $item('b > a').text().trim() +} + +function parseSubTitle($item) { + return $item('i').clone().children().remove('s').end().text().trim() +} + +function parseDescription($item) { + return $item('p').text().trim() +} + +function parseStart($item, date) { + let time = $item('b').clone().children().remove().end().text().trim() + if (!time || !/^\d/.test(time)) return null + time = `${date.format('YYYY-MM-DD')} ${time}` + + return DateTime.fromFormat(time, 'yyyy-MM-dd HH:mm', { zone: 'Europe/Budapest' }).toUTC() +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('#body > div.tdc > div.td2 > div').toArray() +} diff --git a/sites/awilime.com/awilime.com.test.js b/sites/awilime.com/awilime.com.test.js index 08da5912b..9ce3f39ee 100644 --- a/sites/awilime.com/awilime.com.test.js +++ b/sites/awilime.com/awilime.com.test.js @@ -1,49 +1,49 @@ -const { parser, url } = require('./awilime.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2024-06-26', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'budapest_europa_tv', - xmltv_id: 'BudapestEuropaTelevizio.hu' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://www.awilime.com/tv/napi_musor/budapest_europa_tv/2024_06_26' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - const results = parser({ content, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(15) - - expect(results[3]).toMatchObject({ - start: '2024-06-26T07:00:00.000Z', - stop: '2024-06-26T08:00:00.000Z', - title: 'Ébredés', - sub_title: 'Amerikai dokumentumfilm (2018)', - description: 'Balla Tibor misszionárius' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: - 'Object moved

    Object moved to here.

    ' - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./awilime.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2024-06-26', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'budapest_europa_tv', + xmltv_id: 'BudapestEuropaTelevizio.hu' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://www.awilime.com/tv/napi_musor/budapest_europa_tv/2024_06_26' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + const results = parser({ content, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(15) + + expect(results[3]).toMatchObject({ + start: '2024-06-26T07:00:00.000Z', + stop: '2024-06-26T08:00:00.000Z', + title: 'Ébredés', + sub_title: 'Amerikai dokumentumfilm (2018)', + description: 'Balla Tibor misszionárius' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: + 'Object moved

    Object moved to here.

    ' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/bein.com/bein.com.config.js b/sites/bein.com/bein.com.config.js index 463e10265..16100b1f4 100644 --- a/sites/bein.com/bein.com.config.js +++ b/sites/bein.com/bein.com.config.js @@ -1,110 +1,110 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const cheerio = require('cheerio') -const { DateTime } = require('luxon') - -module.exports = { - site: 'bein.com', - days: 2, - request: { - cache: { - ttl: 60 * 60 * 1000 // 1 hour - } - }, - url: function ({ date, channel }) { - const [category] = channel.site_id.split('#') - const postid = channel.lang === 'ar' ? '25344' : '25356' - - return `https://www.bein.com/${ - channel.lang - }/epg-ajax-template/?action=epg_fetch&category=${category}&cdate=${date.format( - 'YYYY-MM-DD' - )}&language=${channel.lang.toUpperCase()}&loadindex=0&mins=00&offset=0&postid=${postid}&serviceidentity=bein.net` - }, - parser: function ({ content, channel, date }) { - let programs = [] - const items = parseItems(content, channel) - date = DateTime.fromMillis(date.valueOf()).minus({ days: 1 }) - items.forEach(item => { - const $item = cheerio.load(item) - const title = parseTitle($item) - if (!title) return - const category = parseCategory($item) - const prev = programs[programs.length - 1] - let start = parseTime($item, date) - if (prev) { - if (start < prev.start) { - start = start.plus({ days: 1 }) - date = date.plus({ days: 1 }) - } - prev.stop = start - } - let stop = parseTime($item, start) - if (stop < start) { - stop = stop.plus({ days: 1 }) - } - programs.push({ - title, - category, - start, - stop - }) - }) - - return programs - }, - async channels({ lang }) { - const categories = ['entertainment', 'sports'] - - let channels = [] - for (let category of categories) { - const url = `https://www.bein.com/en/epg-ajax-template/?action=epg_fetch&offset=0&category=${category}&serviceidentity=bein.net&mins=00&cdate=${dayjs().format( - 'YYYY-MM-DD' - )}&language=${lang.toUpperCase()}&postid=25356&loadindex=0` - const data = await axios - .get(url) - .then(r => r.data) - .catch(console.log) - - const $ = cheerio.load(data) - $('.container-tvguide > div').each((i, el) => { - const id = $(el).attr('id') - if (!id || !/^channels_\d+/.test(id)) return - const [, channelId] = id.split('_') - - channels.push({ - lang, - site_id: `${category}#${channelId}`, - name: channelId - }) - }) - } - - return channels - } -} - -function parseTitle($item) { - return $item('.title').text() -} - -function parseCategory($item) { - return $item('.format').text() -} - -function parseTime($item, date) { - let [, time] = $item('.time') - .text() - .match(/^(\d{2}:\d{2})/) || [null, null] - if (!time) return null - time = `${date.toFormat('yyyy-MM-dd')} ${time}` - - return DateTime.fromFormat(time, 'yyyy-MM-dd HH:mm', { zone: 'Asia/Qatar' }).toUTC() -} - -function parseItems(content, channel) { - const [, channelId] = channel.site_id.split('#') - const $ = cheerio.load(content) - - return $(`#channels_${channelId} .slider > ul:first-child > li`).toArray() -} +const axios = require('axios') +const dayjs = require('dayjs') +const cheerio = require('cheerio') +const { DateTime } = require('luxon') + +module.exports = { + site: 'bein.com', + days: 2, + request: { + cache: { + ttl: 60 * 60 * 1000 // 1 hour + } + }, + url: function ({ date, channel }) { + const [category] = channel.site_id.split('#') + const postid = channel.lang === 'ar' ? '25344' : '25356' + + return `https://www.bein.com/${ + channel.lang + }/epg-ajax-template/?action=epg_fetch&category=${category}&cdate=${date.format( + 'YYYY-MM-DD' + )}&language=${channel.lang.toUpperCase()}&loadindex=0&mins=00&offset=0&postid=${postid}&serviceidentity=bein.net` + }, + parser: function ({ content, channel, date }) { + let programs = [] + const items = parseItems(content, channel) + date = DateTime.fromMillis(date.valueOf()).minus({ days: 1 }) + items.forEach(item => { + const $item = cheerio.load(item) + const title = parseTitle($item) + if (!title) return + const category = parseCategory($item) + const prev = programs[programs.length - 1] + let start = parseTime($item, date) + if (prev) { + if (start < prev.start) { + start = start.plus({ days: 1 }) + date = date.plus({ days: 1 }) + } + prev.stop = start + } + let stop = parseTime($item, start) + if (stop < start) { + stop = stop.plus({ days: 1 }) + } + programs.push({ + title, + category, + start, + stop + }) + }) + + return programs + }, + async channels({ lang }) { + const categories = ['entertainment', 'sports'] + + let channels = [] + for (let category of categories) { + const url = `https://www.bein.com/en/epg-ajax-template/?action=epg_fetch&offset=0&category=${category}&serviceidentity=bein.net&mins=00&cdate=${dayjs().format( + 'YYYY-MM-DD' + )}&language=${lang.toUpperCase()}&postid=25356&loadindex=0` + const data = await axios + .get(url) + .then(r => r.data) + .catch(console.log) + + const $ = cheerio.load(data) + $('.container-tvguide > div').each((i, el) => { + const id = $(el).attr('id') + if (!id || !/^channels_\d+/.test(id)) return + const [, channelId] = id.split('_') + + channels.push({ + lang, + site_id: `${category}#${channelId}`, + name: channelId + }) + }) + } + + return channels + } +} + +function parseTitle($item) { + return $item('.title').text() +} + +function parseCategory($item) { + return $item('.format').text() +} + +function parseTime($item, date) { + let [, time] = $item('.time') + .text() + .match(/^(\d{2}:\d{2})/) || [null, null] + if (!time) return null + time = `${date.toFormat('yyyy-MM-dd')} ${time}` + + return DateTime.fromFormat(time, 'yyyy-MM-dd HH:mm', { zone: 'Asia/Qatar' }).toUTC() +} + +function parseItems(content, channel) { + const [, channelId] = channel.site_id.split('#') + const $ = cheerio.load(content) + + return $(`#channels_${channelId} .slider > ul:first-child > li`).toArray() +} diff --git a/sites/bein.com/bein.com.test.js b/sites/bein.com/bein.com.test.js index 9dad1339f..ad56ad649 100644 --- a/sites/bein.com/bein.com.test.js +++ b/sites/bein.com/bein.com.test.js @@ -1,58 +1,58 @@ -const fs = require('fs') -const path = require('path') -const { parser, url } = require('./bein.com.config.js') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-01-19', 'YYYY-MM-DD').startOf('d') -const channel = { site_id: 'entertainment#1', xmltv_id: 'beINMovies1Premiere.qa', lang: 'en' } - -it('can generate valid url', () => { - const result = url({ date, channel }) - expect(result).toBe( - 'https://www.bein.com/en/epg-ajax-template/?action=epg_fetch&category=entertainment&cdate=2023-01-19&language=EN&loadindex=0&mins=00&offset=0&postid=25356&serviceidentity=bein.net' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve('sites/bein.com/__data__/content.html')) - const results = parser({ date, channel, content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2023-01-18T20:15:00.000Z', - stop: '2023-01-18T22:15:00.000Z', - title: 'The Walk', - category: 'Movies' - }) - - expect(results[1]).toMatchObject({ - start: '2023-01-18T22:15:00.000Z', - stop: '2023-01-19T00:00:00.000Z', - title: 'Resident Evil: Welcome To Raccoon City', - category: 'Movies' - }) - - expect(results[10]).toMatchObject({ - start: '2023-01-19T15:30:00.000Z', - stop: '2023-01-19T18:00:00.000Z', - title: 'Spider-Man: No Way Home', - category: 'Movies' - }) -}) - -it('can handle empty guide', () => { - const noContent = fs.readFileSync(path.resolve('sites/bein.com/__data__/no-content.html')) - const result = parser({ - date, - channel, - content: noContent - }) - expect(result).toMatchObject([]) -}) +const fs = require('fs') +const path = require('path') +const { parser, url } = require('./bein.com.config.js') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-01-19', 'YYYY-MM-DD').startOf('d') +const channel = { site_id: 'entertainment#1', xmltv_id: 'beINMovies1Premiere.qa', lang: 'en' } + +it('can generate valid url', () => { + const result = url({ date, channel }) + expect(result).toBe( + 'https://www.bein.com/en/epg-ajax-template/?action=epg_fetch&category=entertainment&cdate=2023-01-19&language=EN&loadindex=0&mins=00&offset=0&postid=25356&serviceidentity=bein.net' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve('sites/bein.com/__data__/content.html')) + const results = parser({ date, channel, content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2023-01-18T20:15:00.000Z', + stop: '2023-01-18T22:15:00.000Z', + title: 'The Walk', + category: 'Movies' + }) + + expect(results[1]).toMatchObject({ + start: '2023-01-18T22:15:00.000Z', + stop: '2023-01-19T00:00:00.000Z', + title: 'Resident Evil: Welcome To Raccoon City', + category: 'Movies' + }) + + expect(results[10]).toMatchObject({ + start: '2023-01-19T15:30:00.000Z', + stop: '2023-01-19T18:00:00.000Z', + title: 'Spider-Man: No Way Home', + category: 'Movies' + }) +}) + +it('can handle empty guide', () => { + const noContent = fs.readFileSync(path.resolve('sites/bein.com/__data__/no-content.html')) + const result = parser({ + date, + channel, + content: noContent + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/beinsports.com/beinsports.com.config.js b/sites/beinsports.com/beinsports.com.config.js index 41b6c8994..cc6c0e483 100644 --- a/sites/beinsports.com/beinsports.com.config.js +++ b/sites/beinsports.com/beinsports.com.config.js @@ -1,73 +1,73 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'beinsports.com', - days: 2, - request: { - headers: { - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36' - } - }, - url: function ({ date, channel }) { - return `https://www.beinsports.com/api/opta/tv-event?&startBefore=${date - .add(1, 'd') - .format('YYYY-MM-DDTHH:mm:ss.SSS')}Z&endAfter=${date.format( - 'YYYY-MM-DDTHH:mm:ss.SSS' - )}Z&channelIds=${channel.site_id}` - }, - parser: function ({ content }) { - let programs = [] - const items = parseItems(content) - if (!items.length == 0) { - items.forEach(item => { - const start = dayjs.utc(item.startDate) - const stop = dayjs.utc(item.endDate) - programs.push({ - title: item.title, - description: item.description, - start, - stop - }) - }) - } - return programs - }, - async channels({ region, lang }) { - const data = await axios - .get(`https://www.beinsports.com/api/opta/tv-channel?region=${lang}-${region}`, this.request) - .then(r => r.data) - .catch(console.log) - - return data.rows.map(item => { - return { - lang, - site_id: item.id, - name: item.name - } - }) - } -} - -function parseItems(content) { - let data - try { - data = JSON.parse(content) - } catch { - return [] - } - - if (!data || !data['rows']) { - return [] - } - - return data.rows -} +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'beinsports.com', + days: 2, + request: { + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36' + } + }, + url: function ({ date, channel }) { + return `https://www.beinsports.com/api/opta/tv-event?&startBefore=${date + .add(1, 'd') + .format('YYYY-MM-DDTHH:mm:ss.SSS')}Z&endAfter=${date.format( + 'YYYY-MM-DDTHH:mm:ss.SSS' + )}Z&channelIds=${channel.site_id}` + }, + parser: function ({ content }) { + let programs = [] + const items = parseItems(content) + if (!items.length == 0) { + items.forEach(item => { + const start = dayjs.utc(item.startDate) + const stop = dayjs.utc(item.endDate) + programs.push({ + title: item.title, + description: item.description, + start, + stop + }) + }) + } + return programs + }, + async channels({ region, lang }) { + const data = await axios + .get(`https://www.beinsports.com/api/opta/tv-channel?region=${lang}-${region}`, this.request) + .then(r => r.data) + .catch(console.log) + + return data.rows.map(item => { + return { + lang, + site_id: item.id, + name: item.name + } + }) + } +} + +function parseItems(content) { + let data + try { + data = JSON.parse(content) + } catch { + return [] + } + + if (!data || !data['rows']) { + return [] + } + + return data.rows +} diff --git a/sites/beinsports.com/beinsports.com.test.js b/sites/beinsports.com/beinsports.com.test.js index 563c18b1a..2d2a8b2c4 100644 --- a/sites/beinsports.com/beinsports.com.test.js +++ b/sites/beinsports.com/beinsports.com.test.js @@ -1,43 +1,43 @@ -const { parser, url } = require('./beinsports.com.config.js') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-10-22T00:00:00.000', '"YYYY-MM-DDTHH:mm:ss.SSS').startOf('d') -const channel = { site_id: 'C244C48D-3B54-406A-94C9-D63B16318267', xmltv_id: 'beINSportsUSA.us' } - -it('can generate valid url', () => { - const result = url({ date, channel }) - expect(result).toBe( - 'https://www.beinsports.com/api/opta/tv-event?&startBefore=2023-10-23T00:00:00.000Z&endAfter=2023-10-22T00:00:00.000Z&channelIds=C244C48D-3B54-406A-94C9-D63B16318267' - ) -}) - -const content = - '{"count":1,"rows":[{"data":{"eventId":"2028126","eventDate":"2023-10-21T10:30:00","utcEventDate":"2023-10-20T23:30:00","duration":"90","programId":"106230","programTypeId":"5","title":"ATP 500"},"duration":5400000,"title":"Tokyo Day 5 QF 2","startDate":"2023-10-20T23:30:00.000Z","endDate":"2023-10-21T01:00:00.000Z","description":"Exclusive coverage of the 2023 ATP Tour on beIN SPORTS","channelId":"164C0EDA-EBCE-4AA6-9DDA-D603E0948B9F"}]}' - -it('can parse response', () => { - const result = parser({ content, channel, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2023-10-20T23:30:00.000Z', - stop: '2023-10-21T01:00:00.000Z', - title: 'Tokyo Day 5 QF 2', - description: 'Exclusive coverage of the 2023 ATP Tour on beIN SPORTS' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: '[]' - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./beinsports.com.config.js') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-10-22T00:00:00.000', '"YYYY-MM-DDTHH:mm:ss.SSS').startOf('d') +const channel = { site_id: 'C244C48D-3B54-406A-94C9-D63B16318267', xmltv_id: 'beINSportsUSA.us' } + +it('can generate valid url', () => { + const result = url({ date, channel }) + expect(result).toBe( + 'https://www.beinsports.com/api/opta/tv-event?&startBefore=2023-10-23T00:00:00.000Z&endAfter=2023-10-22T00:00:00.000Z&channelIds=C244C48D-3B54-406A-94C9-D63B16318267' + ) +}) + +const content = + '{"count":1,"rows":[{"data":{"eventId":"2028126","eventDate":"2023-10-21T10:30:00","utcEventDate":"2023-10-20T23:30:00","duration":"90","programId":"106230","programTypeId":"5","title":"ATP 500"},"duration":5400000,"title":"Tokyo Day 5 QF 2","startDate":"2023-10-20T23:30:00.000Z","endDate":"2023-10-21T01:00:00.000Z","description":"Exclusive coverage of the 2023 ATP Tour on beIN SPORTS","channelId":"164C0EDA-EBCE-4AA6-9DDA-D603E0948B9F"}]}' + +it('can parse response', () => { + const result = parser({ content, channel, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2023-10-20T23:30:00.000Z', + stop: '2023-10-21T01:00:00.000Z', + title: 'Tokyo Day 5 QF 2', + description: 'Exclusive coverage of the 2023 ATP Tour on beIN SPORTS' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: '[]' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/berrymedia.co.kr/berrymedia.co.kr.config.js b/sites/berrymedia.co.kr/berrymedia.co.kr.config.js index 63ab575a6..773de12d8 100644 --- a/sites/berrymedia.co.kr/berrymedia.co.kr.config.js +++ b/sites/berrymedia.co.kr/berrymedia.co.kr.config.js @@ -1,93 +1,93 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -dayjs.Ls.en.weekStart = 1 - -module.exports = { - site: 'berrymedia.co.kr', - days: 2, - url({ channel }) { - return `http://www.berrymedia.co.kr/schedule_proc${channel.site_id}.php` - }, - request: { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', - 'X-Requested-With': 'XMLHttpRequest' - }, - data({ date }) { - let params = new URLSearchParams() - let startOfWeek = date.startOf('week').format('YYYY-MM-DD') - let endOfWeek = date.endOf('week').format('YYYY-MM-DD') - - params.append('week', `${startOfWeek}~${endOfWeek}`) - params.append('day', date.format('YYYY-MM-DD')) - - return params - } - }, - parser({ content, date }) { - const programs = [] - const items = parseItems(content) - items.forEach(item => { - const $item = cheerio.load(item) - const prev = programs[programs.length - 1] - let start = parseStart($item, date) - if (prev) { - if (start.isBefore(prev.start)) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.add(30, 'm') - programs.push({ - title: parseTitle($item), - category: parseCategory($item), - rating: parseRating($item), - start, - stop - }) - }) - - return programs - } -} - -function parseStart($item, date) { - const time = $item('span:nth-child(1)').text().trim() - - return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'Asia/Seoul') -} - -function parseTitle($item) { - return $item('span.sdfsdf').clone().children().remove().end().text().trim() -} - -function parseCategory($item) { - return $item('span:nth-child(2) > p').text().trim() -} - -function parseRating($item) { - const rating = $item('span:nth-child(5) > p:nth-child(1)').text().trim() - - return rating - ? { - system: 'KMRB', - value: rating - } - : null -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('.sc_time dd').toArray() -} +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +dayjs.Ls.en.weekStart = 1 + +module.exports = { + site: 'berrymedia.co.kr', + days: 2, + url({ channel }) { + return `http://www.berrymedia.co.kr/schedule_proc${channel.site_id}.php` + }, + request: { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + 'X-Requested-With': 'XMLHttpRequest' + }, + data({ date }) { + let params = new URLSearchParams() + let startOfWeek = date.startOf('week').format('YYYY-MM-DD') + let endOfWeek = date.endOf('week').format('YYYY-MM-DD') + + params.append('week', `${startOfWeek}~${endOfWeek}`) + params.append('day', date.format('YYYY-MM-DD')) + + return params + } + }, + parser({ content, date }) { + const programs = [] + const items = parseItems(content) + items.forEach(item => { + const $item = cheerio.load(item) + const prev = programs[programs.length - 1] + let start = parseStart($item, date) + if (prev) { + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.add(30, 'm') + programs.push({ + title: parseTitle($item), + category: parseCategory($item), + rating: parseRating($item), + start, + stop + }) + }) + + return programs + } +} + +function parseStart($item, date) { + const time = $item('span:nth-child(1)').text().trim() + + return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'Asia/Seoul') +} + +function parseTitle($item) { + return $item('span.sdfsdf').clone().children().remove().end().text().trim() +} + +function parseCategory($item) { + return $item('span:nth-child(2) > p').text().trim() +} + +function parseRating($item) { + const rating = $item('span:nth-child(5) > p:nth-child(1)').text().trim() + + return rating + ? { + system: 'KMRB', + value: rating + } + : null +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('.sc_time dd').toArray() +} diff --git a/sites/berrymedia.co.kr/berrymedia.co.kr.test.js b/sites/berrymedia.co.kr/berrymedia.co.kr.test.js index b0824ca7a..050694b5f 100644 --- a/sites/berrymedia.co.kr/berrymedia.co.kr.test.js +++ b/sites/berrymedia.co.kr/berrymedia.co.kr.test.js @@ -1,77 +1,77 @@ -const { parser, url, request } = require('./berrymedia.co.kr.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-01-26', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '', - xmltv_id: 'GTV.kr' -} - -it('can generate valid url', () => { - expect(url({ channel })).toBe('http://www.berrymedia.co.kr/schedule_proc.php') -}) - -it('can generate request method', () => { - expect(request.method).toBe('POST') -}) - -it('can generate valid request headers', () => { - expect(request.headers).toMatchObject({ - 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', - 'X-Requested-With': 'XMLHttpRequest' - }) -}) - -it('can generate valid request data', () => { - let params = request.data({ date }) - - expect(params.get('week')).toBe('2023-01-23~2023-01-29') - expect(params.get('day')).toBe('2023-01-26') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') - let results = parser({ content, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2023-01-25T15:00:00.000Z', - stop: '2023-01-25T16:00:00.000Z', - title: '더트롯쇼', - category: '연예/오락', - rating: { - system: 'KMRB', - value: '15' - } - }) - - expect(results[17]).toMatchObject({ - start: '2023-01-26T13:50:00.000Z', - stop: '2023-01-26T14:20:00.000Z', - title: '나는 자연인이다', - category: '교양', - rating: { - system: 'KMRB', - value: 'ALL' - } - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - date, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') - }) - - expect(results).toMatchObject([]) -}) +const { parser, url, request } = require('./berrymedia.co.kr.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-01-26', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '', + xmltv_id: 'GTV.kr' +} + +it('can generate valid url', () => { + expect(url({ channel })).toBe('http://www.berrymedia.co.kr/schedule_proc.php') +}) + +it('can generate request method', () => { + expect(request.method).toBe('POST') +}) + +it('can generate valid request headers', () => { + expect(request.headers).toMatchObject({ + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + 'X-Requested-With': 'XMLHttpRequest' + }) +}) + +it('can generate valid request data', () => { + let params = request.data({ date }) + + expect(params.get('week')).toBe('2023-01-23~2023-01-29') + expect(params.get('day')).toBe('2023-01-26') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') + let results = parser({ content, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2023-01-25T15:00:00.000Z', + stop: '2023-01-25T16:00:00.000Z', + title: '더트롯쇼', + category: '연예/오락', + rating: { + system: 'KMRB', + value: '15' + } + }) + + expect(results[17]).toMatchObject({ + start: '2023-01-26T13:50:00.000Z', + stop: '2023-01-26T14:20:00.000Z', + title: '나는 자연인이다', + category: '교양', + rating: { + system: 'KMRB', + value: 'ALL' + } + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + date, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') + }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/cableplus.com.uy/cableplus.com.uy.config.js b/sites/cableplus.com.uy/cableplus.com.uy.config.js index 65ad148b0..5f4a2601c 100644 --- a/sites/cableplus.com.uy/cableplus.com.uy.config.js +++ b/sites/cableplus.com.uy/cableplus.com.uy.config.js @@ -1,133 +1,133 @@ -const cheerio = require('cheerio') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -const API_ENDPOINT = 'https://www.reportv.com.ar/finder' - -module.exports = { - site: 'cableplus.com.uy', - days: 2, - url: `${API_ENDPOINT}/channel`, - request: { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' - }, - data({ date, channel }) { - const params = new URLSearchParams() - params.append('idAlineacion', '3017') - params.append('idSenial', channel.site_id) - params.append('fecha', date.format('YYYY-MM-DD')) - params.append('hora', '00:00') - - return params - } - }, - parser({ content, date }) { - const programs = [] - const items = parseItems(content, date) - items.forEach(item => { - const $item = cheerio.load(item) - const prev = programs[programs.length - 1] - let start = parseStart($item, date) - if (prev) { - if (start.isBefore(prev.start)) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.add(30, 'm') - programs.push({ - title: parseTitle($item), - categories: parseCategories($item), - image: parseImage($item), - start, - stop - }) - }) - - return programs - }, - async channels() { - const params = new URLSearchParams({ idAlineacion: '3017' }) - const data = await axios - .post(`${API_ENDPOINT}/channelGrid`, params, { - headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' } - }) - .then(r => r.data) - .catch(console.error) - const $ = cheerio.load(data) - - return $('.senial') - .map(function () { - return { - lang: 'es', - site_id: $(this).attr('id'), - name: $(this).find('img').attr('alt') - } - }) - .get() - } -} - -function parseTitle($item) { - return $item('p.evento_titulo.texto_a_continuacion.dotdotdot,.programa-titulo > span:first-child') - .text() - .trim() -} - -function parseImage($item) { - return $item('img').data('src') || $item('img').attr('src') || null -} - -function parseCategories($item) { - return $item('p.evento_genero') - .map(function () { - return $item(this).text().trim() - }) - .toArray() -} - -function parseStart($item, date) { - let time = $item('.grid_fecha_hora').text().trim() - - if (time) { - return dayjs.tz(`${date.format('YYYY')} ${time}`, 'YYYY DD-MM HH:mm[hs.]', 'America/Montevideo') - } - - time = $item('.fechaHora').text().trim() - - return time - ? dayjs.tz(`${date.format('YYYY')} ${time}`, 'YYYY DD/MM HH:mm[hs.]', 'America/Montevideo') - : null -} - -function parseItems(content, date) { - const $ = cheerio.load(content) - - let featuredItems = $('.vista-pc > .programacion-fila > .channel-programa') - .filter(function () { - return $(this).find('.grid_fecha_hora').text().indexOf(date.format('DD-MM')) > -1 - }) - .toArray() - let otherItems = $('#owl-pc > .item-program') - .filter(function () { - return ( - $(this) - .find('.evento_titulo > .horario > p.fechaHora') - .text() - .indexOf(date.format('DD/MM')) > -1 - ) - }) - .toArray() - - return featuredItems.concat(otherItems) -} +const cheerio = require('cheerio') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +const API_ENDPOINT = 'https://www.reportv.com.ar/finder' + +module.exports = { + site: 'cableplus.com.uy', + days: 2, + url: `${API_ENDPOINT}/channel`, + request: { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' + }, + data({ date, channel }) { + const params = new URLSearchParams() + params.append('idAlineacion', '3017') + params.append('idSenial', channel.site_id) + params.append('fecha', date.format('YYYY-MM-DD')) + params.append('hora', '00:00') + + return params + } + }, + parser({ content, date }) { + const programs = [] + const items = parseItems(content, date) + items.forEach(item => { + const $item = cheerio.load(item) + const prev = programs[programs.length - 1] + let start = parseStart($item, date) + if (prev) { + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.add(30, 'm') + programs.push({ + title: parseTitle($item), + categories: parseCategories($item), + image: parseImage($item), + start, + stop + }) + }) + + return programs + }, + async channels() { + const params = new URLSearchParams({ idAlineacion: '3017' }) + const data = await axios + .post(`${API_ENDPOINT}/channelGrid`, params, { + headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' } + }) + .then(r => r.data) + .catch(console.error) + const $ = cheerio.load(data) + + return $('.senial') + .map(function () { + return { + lang: 'es', + site_id: $(this).attr('id'), + name: $(this).find('img').attr('alt') + } + }) + .get() + } +} + +function parseTitle($item) { + return $item('p.evento_titulo.texto_a_continuacion.dotdotdot,.programa-titulo > span:first-child') + .text() + .trim() +} + +function parseImage($item) { + return $item('img').data('src') || $item('img').attr('src') || null +} + +function parseCategories($item) { + return $item('p.evento_genero') + .map(function () { + return $item(this).text().trim() + }) + .toArray() +} + +function parseStart($item, date) { + let time = $item('.grid_fecha_hora').text().trim() + + if (time) { + return dayjs.tz(`${date.format('YYYY')} ${time}`, 'YYYY DD-MM HH:mm[hs.]', 'America/Montevideo') + } + + time = $item('.fechaHora').text().trim() + + return time + ? dayjs.tz(`${date.format('YYYY')} ${time}`, 'YYYY DD/MM HH:mm[hs.]', 'America/Montevideo') + : null +} + +function parseItems(content, date) { + const $ = cheerio.load(content) + + let featuredItems = $('.vista-pc > .programacion-fila > .channel-programa') + .filter(function () { + return $(this).find('.grid_fecha_hora').text().indexOf(date.format('DD-MM')) > -1 + }) + .toArray() + let otherItems = $('#owl-pc > .item-program') + .filter(function () { + return ( + $(this) + .find('.evento_titulo > .horario > p.fechaHora') + .text() + .indexOf(date.format('DD/MM')) > -1 + ) + }) + .toArray() + + return featuredItems.concat(otherItems) +} diff --git a/sites/cableplus.com.uy/cableplus.com.uy.test.js b/sites/cableplus.com.uy/cableplus.com.uy.test.js index 642a8ffa0..1420dfed8 100644 --- a/sites/cableplus.com.uy/cableplus.com.uy.test.js +++ b/sites/cableplus.com.uy/cableplus.com.uy.test.js @@ -1,73 +1,73 @@ -const { parser, url, request } = require('./cableplus.com.uy.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-02-12', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '2035', - xmltv_id: 'APlusV.uy' -} - -it('can generate valid url', () => { - expect(url).toBe('https://www.reportv.com.ar/finder/channel') -}) - -it('can generate valid request method', () => { - expect(request.method).toBe('POST') -}) - -it('can generate valid request headers', () => { - expect(request.headers).toMatchObject({ - 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' - }) -}) - -it('can generate valid request data', () => { - const params = request.data({ date, channel }) - - expect(params.get('idAlineacion')).toBe('3017') - expect(params.get('idSenial')).toBe('2035') - expect(params.get('fecha')).toBe('2023-02-12') - expect(params.get('hora')).toBe('00:00') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') - let results = parser({ content, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(21) - - expect(results[0]).toMatchObject({ - start: '2023-02-12T09:30:00.000Z', - stop: '2023-02-12T10:30:00.000Z', - title: 'Revista agropecuaria', - image: 'https://www.reportv.com.ar/buscador/img/Programas/2797844.jpg', - categories: [] - }) - - expect(results[4]).toMatchObject({ - start: '2023-02-12T12:30:00.000Z', - stop: '2023-02-12T13:30:00.000Z', - title: 'De pago en pago', - image: 'https://www.reportv.com.ar/buscador/img/Programas/3772835.jpg', - categories: ['Cultural'] - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') - }) - expect(result).toMatchObject([]) -}) +const { parser, url, request } = require('./cableplus.com.uy.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-02-12', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '2035', + xmltv_id: 'APlusV.uy' +} + +it('can generate valid url', () => { + expect(url).toBe('https://www.reportv.com.ar/finder/channel') +}) + +it('can generate valid request method', () => { + expect(request.method).toBe('POST') +}) + +it('can generate valid request headers', () => { + expect(request.headers).toMatchObject({ + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' + }) +}) + +it('can generate valid request data', () => { + const params = request.data({ date, channel }) + + expect(params.get('idAlineacion')).toBe('3017') + expect(params.get('idSenial')).toBe('2035') + expect(params.get('fecha')).toBe('2023-02-12') + expect(params.get('hora')).toBe('00:00') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') + let results = parser({ content, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(21) + + expect(results[0]).toMatchObject({ + start: '2023-02-12T09:30:00.000Z', + stop: '2023-02-12T10:30:00.000Z', + title: 'Revista agropecuaria', + image: 'https://www.reportv.com.ar/buscador/img/Programas/2797844.jpg', + categories: [] + }) + + expect(results[4]).toMatchObject({ + start: '2023-02-12T12:30:00.000Z', + stop: '2023-02-12T13:30:00.000Z', + title: 'De pago en pago', + image: 'https://www.reportv.com.ar/buscador/img/Programas/3772835.jpg', + categories: ['Cultural'] + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/canalplus.com/canalplus.com.config.js b/sites/canalplus.com/canalplus.com.config.js index d6d2679bc..9f7fcd5e0 100644 --- a/sites/canalplus.com/canalplus.com.config.js +++ b/sites/canalplus.com/canalplus.com.config.js @@ -1,197 +1,197 @@ -const dayjs = require('dayjs') -const axios = require('axios') -const utc = require('dayjs/plugin/utc') - -dayjs.extend(utc) - -module.exports = { - site: 'canalplus.com', - days: 2, - url: async function ({ channel, date }) { - const [region, site_id] = channel.site_id.split('#') - - const baseUrl = - region === 'pl' - ? 'https://www.canalplus.com/pl/program-tv/' - : `https://www.canalplus.com/${region}/programme-tv/` - - const data = await axios - .get(baseUrl) - .then(r => r.data.toString()) - .catch(err => console.log(err)) - - const token = parseToken(data) - const path = region === 'pl' ? 'mycanalint' : 'mycanal' - const diff = date.diff(dayjs.utc().startOf('d'), 'd') - - return `https://hodor.canalplus.pro/api/v2/${path}/channels/${token}/${site_id}/broadcasts/day/${diff}` - }, - async parser({ content }) { - let programs = [] - const items = parseItems(content) - for (let item of items) { - const prev = programs[programs.length - 1] - const details = await loadProgramDetails(item) - const info = parseInfo(details) - const start = parseStart(item) - if (prev) prev.stop = start - const stop = start.add(1, 'h') - programs.push({ - title: item.title, - description: parseDescription(info), - image: parseImage(info), - actors: parseCast(info, 'Avec :'), - director: parseCast(info, 'De :'), - writer: parseCast(info, 'Scénario :'), - composer: parseCast(info, 'Musique :'), - presenter: parseCast(info, 'Présenté par :'), - date: parseDate(info), - rating: parseRating(info), - start, - stop - }) - } - - return programs - }, - async channels({ country }) { - const paths = { - ad: 'cpafr/ad', - bf: 'cpafr/bf', - bi: 'cpafr/bi', - bj: 'cpafr/bj', - bl: 'cpant/bl', - cd: 'cpafr/cd', - cf: 'cpafr/cf', - cg: 'cpafr/cg', - ch: 'cpche', - ci: 'cpafr/ci', - cm: 'cpafr/cm', - cv: 'cpafr/cv', - dj: 'cpafr/dj', - fr: 'cpfra', - ga: 'cpafr/ga', - gf: 'cpant/gf', - gh: 'cpafr/gh', - gm: 'cpafr/gm', - gn: 'cpafr/gn', - gp: 'cpafr/gp', - gw: 'cpafr/gw', - ht: 'cpant/ht', - mf: 'cpant/mf', - mg: 'cpafr/mg', - ml: 'cpafr/ml', - mq: 'cpant/mq', - mr: 'cpafr/mr', - mu: 'cpmus/mu', - nc: 'cpncl/nc', - ne: 'cpafr/ne', - pf: 'cppyf/pf', - pl: 'cppol', - re: 'cpreu/re', - rw: 'cpafr/rw', - sl: 'cpafr/sl', - sn: 'cpafr/sn', - td: 'cpafr/td', - tg: 'cpafr/tg', - wf: 'cpncl/wf', - yt: 'cpreu/yt' - } - - let channels = [] - const path = paths[country] - const url = `https://secure-webtv-static.canal-plus.com/metadata/${path}/all/v2.2/globalchannels.json` - const data = await axios - .get(url) - .then(r => r.data) - .catch(console.log) - - data.channels.forEach(channel => { - const site_id = country === 'fr' ? `#${channel.id}` : `${country}#${channel.id}` - - if (channel.name === '.') return - - channels.push({ - lang: 'fr', - site_id, - name: channel.name - }) - }) - - return channels - } -} - -function parseToken(data) { - const [, token] = data.match(/"token":"([^"]+)/) || [null, null] - - return token -} - -function parseStart(item) { - return item && item.startTime ? dayjs(item.startTime) : null -} - -function parseImage(info) { - return info ? info.URLImage : null -} - -function parseDescription(info) { - return info ? info.summary : null -} - -function parseInfo(data) { - if (!data || !data.detail || !data.detail.informations) return null - - return data.detail.informations -} - -async function loadProgramDetails(item) { - if (!item.onClick || !item.onClick.URLPage) return {} - - return await axios - .get(item.onClick.URLPage) - .then(r => r.data) - .catch(console.error) -} - -function parseItems(content) { - const data = JSON.parse(content) - if (!data || !Array.isArray(data.timeSlices)) return [] - - return data.timeSlices.reduce((acc, curr) => { - acc = acc.concat(curr.contents) - return acc - }, []) -} - -function parseCast(info, type) { - let people = [] - if (info && info.personnalities) { - const personnalities = info.personnalities.find(i => i.prefix == type) - if (!personnalities) return people - for (let person of personnalities.personnalitiesList) { - people.push(person.title) - } - } - return people -} - -function parseDate(info) { - return info && info.productionYear ? info.productionYear : null -} - -function parseRating(info) { - if (!info || !info.parentalRatings) return null - let rating = info.parentalRatings.find(i => i.authority === 'CSA') - if (!rating || Array.isArray(rating)) return null - if (rating.value === '1') return null - if (rating.value === '2') rating.value = '-10' - if (rating.value === '3') rating.value = '-12' - if (rating.value === '4') rating.value = '-16' - if (rating.value === '5') rating.value = '-18' - return { - system: rating.authority, - value: rating.value - } -} +const dayjs = require('dayjs') +const axios = require('axios') +const utc = require('dayjs/plugin/utc') + +dayjs.extend(utc) + +module.exports = { + site: 'canalplus.com', + days: 2, + url: async function ({ channel, date }) { + const [region, site_id] = channel.site_id.split('#') + + const baseUrl = + region === 'pl' + ? 'https://www.canalplus.com/pl/program-tv/' + : `https://www.canalplus.com/${region}/programme-tv/` + + const data = await axios + .get(baseUrl) + .then(r => r.data.toString()) + .catch(err => console.log(err)) + + const token = parseToken(data) + const path = region === 'pl' ? 'mycanalint' : 'mycanal' + const diff = date.diff(dayjs.utc().startOf('d'), 'd') + + return `https://hodor.canalplus.pro/api/v2/${path}/channels/${token}/${site_id}/broadcasts/day/${diff}` + }, + async parser({ content }) { + let programs = [] + const items = parseItems(content) + for (let item of items) { + const prev = programs[programs.length - 1] + const details = await loadProgramDetails(item) + const info = parseInfo(details) + const start = parseStart(item) + if (prev) prev.stop = start + const stop = start.add(1, 'h') + programs.push({ + title: item.title, + description: parseDescription(info), + image: parseImage(info), + actors: parseCast(info, 'Avec :'), + director: parseCast(info, 'De :'), + writer: parseCast(info, 'Scénario :'), + composer: parseCast(info, 'Musique :'), + presenter: parseCast(info, 'Présenté par :'), + date: parseDate(info), + rating: parseRating(info), + start, + stop + }) + } + + return programs + }, + async channels({ country }) { + const paths = { + ad: 'cpafr/ad', + bf: 'cpafr/bf', + bi: 'cpafr/bi', + bj: 'cpafr/bj', + bl: 'cpant/bl', + cd: 'cpafr/cd', + cf: 'cpafr/cf', + cg: 'cpafr/cg', + ch: 'cpche', + ci: 'cpafr/ci', + cm: 'cpafr/cm', + cv: 'cpafr/cv', + dj: 'cpafr/dj', + fr: 'cpfra', + ga: 'cpafr/ga', + gf: 'cpant/gf', + gh: 'cpafr/gh', + gm: 'cpafr/gm', + gn: 'cpafr/gn', + gp: 'cpafr/gp', + gw: 'cpafr/gw', + ht: 'cpant/ht', + mf: 'cpant/mf', + mg: 'cpafr/mg', + ml: 'cpafr/ml', + mq: 'cpant/mq', + mr: 'cpafr/mr', + mu: 'cpmus/mu', + nc: 'cpncl/nc', + ne: 'cpafr/ne', + pf: 'cppyf/pf', + pl: 'cppol', + re: 'cpreu/re', + rw: 'cpafr/rw', + sl: 'cpafr/sl', + sn: 'cpafr/sn', + td: 'cpafr/td', + tg: 'cpafr/tg', + wf: 'cpncl/wf', + yt: 'cpreu/yt' + } + + let channels = [] + const path = paths[country] + const url = `https://secure-webtv-static.canal-plus.com/metadata/${path}/all/v2.2/globalchannels.json` + const data = await axios + .get(url) + .then(r => r.data) + .catch(console.log) + + data.channels.forEach(channel => { + const site_id = country === 'fr' ? `#${channel.id}` : `${country}#${channel.id}` + + if (channel.name === '.') return + + channels.push({ + lang: 'fr', + site_id, + name: channel.name + }) + }) + + return channels + } +} + +function parseToken(data) { + const [, token] = data.match(/"token":"([^"]+)/) || [null, null] + + return token +} + +function parseStart(item) { + return item && item.startTime ? dayjs(item.startTime) : null +} + +function parseImage(info) { + return info ? info.URLImage : null +} + +function parseDescription(info) { + return info ? info.summary : null +} + +function parseInfo(data) { + if (!data || !data.detail || !data.detail.informations) return null + + return data.detail.informations +} + +async function loadProgramDetails(item) { + if (!item.onClick || !item.onClick.URLPage) return {} + + return await axios + .get(item.onClick.URLPage) + .then(r => r.data) + .catch(console.error) +} + +function parseItems(content) { + const data = JSON.parse(content) + if (!data || !Array.isArray(data.timeSlices)) return [] + + return data.timeSlices.reduce((acc, curr) => { + acc = acc.concat(curr.contents) + return acc + }, []) +} + +function parseCast(info, type) { + let people = [] + if (info && info.personnalities) { + const personnalities = info.personnalities.find(i => i.prefix == type) + if (!personnalities) return people + for (let person of personnalities.personnalitiesList) { + people.push(person.title) + } + } + return people +} + +function parseDate(info) { + return info && info.productionYear ? info.productionYear : null +} + +function parseRating(info) { + if (!info || !info.parentalRatings) return null + let rating = info.parentalRatings.find(i => i.authority === 'CSA') + if (!rating || Array.isArray(rating)) return null + if (rating.value === '1') return null + if (rating.value === '2') rating.value = '-10' + if (rating.value === '3') rating.value = '-12' + if (rating.value === '4') rating.value = '-16' + if (rating.value === '5') rating.value = '-18' + return { + system: rating.authority, + value: rating.value + } +} diff --git a/sites/canalplus.com/canalplus.com.test.js b/sites/canalplus.com/canalplus.com.test.js index 43c2fb0bb..c6aec1224 100644 --- a/sites/canalplus.com/canalplus.com.test.js +++ b/sites/canalplus.com/canalplus.com.test.js @@ -1,146 +1,146 @@ -const { parser, url } = require('./canalplus.com.config.js') -const fs = require('fs') -const path = require('path') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) -jest.mock('axios') - -const channel = { - site_id: 'bi#198', - xmltv_id: 'CanalPlusCinemaFrance.fr' -} - -it('can generate valid url for today', done => { - axios.get.mockImplementation(url => { - if (url === 'https://www.canalplus.com/bi/programme-tv/') { - return Promise.resolve({ - data: fs.readFileSync(path.resolve(__dirname, '__data__/programme-tv.html')) - }) - } else { - return Promise.resolve({ data: '' }) - } - }) - - const today = dayjs.utc().startOf('d') - url({ channel, date: today }) - .then(result => { - expect(result).toBe( - 'https://hodor.canalplus.pro/api/v2/mycanal/channels/f000c6f4ebf44647682b3a0fa66d7d99/198/broadcasts/day/0' - ) - done() - }) - .catch(done) -}) - -it('can generate valid url for tomorrow', done => { - axios.get.mockImplementation(url => { - if (url === 'https://www.canalplus.com/bi/programme-tv/') { - return Promise.resolve({ - data: fs.readFileSync(path.resolve(__dirname, '__data__/programme-tv.html')) - }) - } else { - return Promise.resolve({ data: '' }) - } - }) - - const tomorrow = dayjs.utc().startOf('d').add(1, 'd') - url({ channel, date: tomorrow }) - .then(result => { - expect(result).toBe( - 'https://hodor.canalplus.pro/api/v2/mycanal/channels/f000c6f4ebf44647682b3a0fa66d7d99/198/broadcasts/day/1' - ) - done() - }) - .catch(done) -}) - -it('can parse response', done => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - - axios.get.mockImplementation(url => { - if ( - url === - 'https://hodor.canalplus.pro/api/v2/mycanal/detail/f000c6f4ebf44647682b3a0fa66d7d99/okapi/6564630_50001.json?detailType=detailSeason&objectType=season&broadcastID=PLM_1196447642&episodeId=20482220_50001&brandID=4501558_50001&fromDiff=true' - ) { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program1.json'))) - }) - } else if ( - url === - 'https://hodor.canalplus.pro/api/v2/mycanal/detail/f000c6f4ebf44647682b3a0fa66d7d99/okapi/17230453_50001.json?detailType=detailPage&objectType=unit&broadcastID=PLM_1196447637&fromDiff=true' - ) { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program2.json'))) - }) - } else { - return Promise.resolve({ data: '' }) - } - }) - - parser({ content }) - .then(result => { - result.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2023-01-12T06:28:00.000Z', - stop: '2023-01-12T12:06:00.000Z', - title: 'Le cercle', - description: - "Tant qu'il y aura du cinéma, LE CERCLE sera là. C'est la seule émission télévisée de débats critiques 100% consacrée au cinéma et elle rentre dans sa 18e saison. Chaque semaine, elle offre des joutes enflammées, joyeuses et sans condescendance, sur les films à l'affiche ; et invite avec \"Le questionnaire du CERCLE\" les réalisatrices et réalisateurs à venir partager leur passion cinéphile.", - image: - 'https://thumb.canalplus.pro/http/unsafe/{resolutionXY}/filters:quality({imageQualityPercentage})/img-hapi.canalplus.pro:80/ServiceImage/ImageID/107297573', - presenter: ['Lily Bloom'], - rating: { - system: 'CSA', - value: '-10' - } - }, - { - start: '2023-01-12T12:06:00.000Z', - stop: '2023-01-12T13:06:00.000Z', - title: 'Illusions perdues', - description: - "Pendant la Restauration, Lucien de Rubempré, jeune provincial d'Angoulême, se rêve poète. Il débarque à Paris en quête de gloire. Il a le soutien de Louise de Bargeton, une aristocrate qui croit en son talent. Pour gagner sa vie, Lucien trouve un emploi dans le journal dirigé par le peu scrupuleux Etienne Lousteau...", - image: - 'https://thumb.canalplus.pro/http/unsafe/{resolutionXY}/filters:quality({imageQualityPercentage})/img-hapi.canalplus.pro:80/ServiceImage/ImageID/107356485', - director: ['Xavier Giannoli'], - actors: [ - 'Benjamin Voisin', - 'Cécile de France', - 'Vincent Lacoste', - 'Xavier Dolan', - 'Gérard Depardieu', - 'Salomé Dewaels', - 'Jeanne Balibar', - 'Louis-Do de Lencquesaing', - 'Alexis Barbosa', - 'Jean-François Stévenin', - 'André Marcon', - 'Marie Cornillon' - ], - writer: ['Xavier Giannoli'], - rating: { - system: 'CSA', - value: '-10' - } - } - ]) - done() - }) - .catch(done) -}) - -it('can handle empty guide', async () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - const result = await parser({ content }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./canalplus.com.config.js') +const fs = require('fs') +const path = require('path') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) +jest.mock('axios') + +const channel = { + site_id: 'bi#198', + xmltv_id: 'CanalPlusCinemaFrance.fr' +} + +it('can generate valid url for today', done => { + axios.get.mockImplementation(url => { + if (url === 'https://www.canalplus.com/bi/programme-tv/') { + return Promise.resolve({ + data: fs.readFileSync(path.resolve(__dirname, '__data__/programme-tv.html')) + }) + } else { + return Promise.resolve({ data: '' }) + } + }) + + const today = dayjs.utc().startOf('d') + url({ channel, date: today }) + .then(result => { + expect(result).toBe( + 'https://hodor.canalplus.pro/api/v2/mycanal/channels/f000c6f4ebf44647682b3a0fa66d7d99/198/broadcasts/day/0' + ) + done() + }) + .catch(done) +}) + +it('can generate valid url for tomorrow', done => { + axios.get.mockImplementation(url => { + if (url === 'https://www.canalplus.com/bi/programme-tv/') { + return Promise.resolve({ + data: fs.readFileSync(path.resolve(__dirname, '__data__/programme-tv.html')) + }) + } else { + return Promise.resolve({ data: '' }) + } + }) + + const tomorrow = dayjs.utc().startOf('d').add(1, 'd') + url({ channel, date: tomorrow }) + .then(result => { + expect(result).toBe( + 'https://hodor.canalplus.pro/api/v2/mycanal/channels/f000c6f4ebf44647682b3a0fa66d7d99/198/broadcasts/day/1' + ) + done() + }) + .catch(done) +}) + +it('can parse response', done => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + + axios.get.mockImplementation(url => { + if ( + url === + 'https://hodor.canalplus.pro/api/v2/mycanal/detail/f000c6f4ebf44647682b3a0fa66d7d99/okapi/6564630_50001.json?detailType=detailSeason&objectType=season&broadcastID=PLM_1196447642&episodeId=20482220_50001&brandID=4501558_50001&fromDiff=true' + ) { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program1.json'))) + }) + } else if ( + url === + 'https://hodor.canalplus.pro/api/v2/mycanal/detail/f000c6f4ebf44647682b3a0fa66d7d99/okapi/17230453_50001.json?detailType=detailPage&objectType=unit&broadcastID=PLM_1196447637&fromDiff=true' + ) { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program2.json'))) + }) + } else { + return Promise.resolve({ data: '' }) + } + }) + + parser({ content }) + .then(result => { + result.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2023-01-12T06:28:00.000Z', + stop: '2023-01-12T12:06:00.000Z', + title: 'Le cercle', + description: + "Tant qu'il y aura du cinéma, LE CERCLE sera là. C'est la seule émission télévisée de débats critiques 100% consacrée au cinéma et elle rentre dans sa 18e saison. Chaque semaine, elle offre des joutes enflammées, joyeuses et sans condescendance, sur les films à l'affiche ; et invite avec \"Le questionnaire du CERCLE\" les réalisatrices et réalisateurs à venir partager leur passion cinéphile.", + image: + 'https://thumb.canalplus.pro/http/unsafe/{resolutionXY}/filters:quality({imageQualityPercentage})/img-hapi.canalplus.pro:80/ServiceImage/ImageID/107297573', + presenter: ['Lily Bloom'], + rating: { + system: 'CSA', + value: '-10' + } + }, + { + start: '2023-01-12T12:06:00.000Z', + stop: '2023-01-12T13:06:00.000Z', + title: 'Illusions perdues', + description: + "Pendant la Restauration, Lucien de Rubempré, jeune provincial d'Angoulême, se rêve poète. Il débarque à Paris en quête de gloire. Il a le soutien de Louise de Bargeton, une aristocrate qui croit en son talent. Pour gagner sa vie, Lucien trouve un emploi dans le journal dirigé par le peu scrupuleux Etienne Lousteau...", + image: + 'https://thumb.canalplus.pro/http/unsafe/{resolutionXY}/filters:quality({imageQualityPercentage})/img-hapi.canalplus.pro:80/ServiceImage/ImageID/107356485', + director: ['Xavier Giannoli'], + actors: [ + 'Benjamin Voisin', + 'Cécile de France', + 'Vincent Lacoste', + 'Xavier Dolan', + 'Gérard Depardieu', + 'Salomé Dewaels', + 'Jeanne Balibar', + 'Louis-Do de Lencquesaing', + 'Alexis Barbosa', + 'Jean-François Stévenin', + 'André Marcon', + 'Marie Cornillon' + ], + writer: ['Xavier Giannoli'], + rating: { + system: 'CSA', + value: '-10' + } + } + ]) + done() + }) + .catch(done) +}) + +it('can handle empty guide', async () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + const result = await parser({ content }) + expect(result).toMatchObject([]) +}) diff --git a/sites/cgates.lt/cgates.lt.config.js b/sites/cgates.lt/cgates.lt.config.js index ad83ca2b2..6696c4a88 100644 --- a/sites/cgates.lt/cgates.lt.config.js +++ b/sites/cgates.lt/cgates.lt.config.js @@ -1,92 +1,92 @@ -const dayjs = require('dayjs') -const axios = require('axios') -const cheerio = require('cheerio') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'cgates.lt', - days: 2, - url: function ({ channel }) { - return `https://www.cgates.lt/tv-kanalai/${channel.site_id}/` - }, - parser: function ({ content, date }) { - let programs = [] - const items = parseItems(content, date) - items.forEach(item => { - const prev = programs[programs.length - 1] - const $item = cheerio.load(item) - let start = parseStart($item, date) - if (prev) { - if (start.isBefore(prev.start)) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.add(30, 'm') - programs.push({ - title: parseTitle($item), - description: parseDescription($item), - start, - stop - }) - }) - - return programs - }, - async channels() { - let html = await axios - .get('https://www.cgates.lt/televizija/tv-programa-savaitei/') - .then(r => r.data) - .catch(console.log) - let $ = cheerio.load(html) - const items = $('.vc_tta-panel.vc_active .kanalas_wrap').toArray() - - return items.map(item => { - const name = $(item).find('h6').text().trim() - const link = $(item).find('a').attr('href') - const [, site_id] = link.match(/\/tv-kanalai\/(.*)\//) || [null, null] - - return { - lang: 'lt', - site_id, - name - } - }) - } -} - -function parseTitle($item) { - const title = $item('td:nth-child(2) > .vc_toggle > .vc_toggle_title').text().trim() - - return title || $item('td:nth-child(2)').text().trim() -} - -function parseDescription($item) { - return $item('.vc_toggle_content > p').text().trim() -} - -function parseStart($item, date) { - const time = $item('.laikas') - - return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'Europe/Vilnius') -} - -function parseItems(content, date) { - const $ = cheerio.load(content) - const section = $( - 'article > div:nth-child(2) > div.vc_row.wpb_row.vc_row-fluid > div > div > div > div > div' - ) - .filter(function () { - return $(`.dt-fancy-title:contains("${date.format('YYYY-MM-DD')}")`, this).length === 1 - }) - .first() - - return $('.tv_programa tr', section).toArray() -} +const dayjs = require('dayjs') +const axios = require('axios') +const cheerio = require('cheerio') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'cgates.lt', + days: 2, + url: function ({ channel }) { + return `https://www.cgates.lt/tv-kanalai/${channel.site_id}/` + }, + parser: function ({ content, date }) { + let programs = [] + const items = parseItems(content, date) + items.forEach(item => { + const prev = programs[programs.length - 1] + const $item = cheerio.load(item) + let start = parseStart($item, date) + if (prev) { + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.add(30, 'm') + programs.push({ + title: parseTitle($item), + description: parseDescription($item), + start, + stop + }) + }) + + return programs + }, + async channels() { + let html = await axios + .get('https://www.cgates.lt/televizija/tv-programa-savaitei/') + .then(r => r.data) + .catch(console.log) + let $ = cheerio.load(html) + const items = $('.vc_tta-panel.vc_active .kanalas_wrap').toArray() + + return items.map(item => { + const name = $(item).find('h6').text().trim() + const link = $(item).find('a').attr('href') + const [, site_id] = link.match(/\/tv-kanalai\/(.*)\//) || [null, null] + + return { + lang: 'lt', + site_id, + name + } + }) + } +} + +function parseTitle($item) { + const title = $item('td:nth-child(2) > .vc_toggle > .vc_toggle_title').text().trim() + + return title || $item('td:nth-child(2)').text().trim() +} + +function parseDescription($item) { + return $item('.vc_toggle_content > p').text().trim() +} + +function parseStart($item, date) { + const time = $item('.laikas') + + return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'Europe/Vilnius') +} + +function parseItems(content, date) { + const $ = cheerio.load(content) + const section = $( + 'article > div:nth-child(2) > div.vc_row.wpb_row.vc_row-fluid > div > div > div > div > div' + ) + .filter(function () { + return $(`.dt-fancy-title:contains("${date.format('YYYY-MM-DD')}")`, this).length === 1 + }) + .first() + + return $('.tv_programa tr', section).toArray() +} diff --git a/sites/cgates.lt/cgates.lt.test.js b/sites/cgates.lt/cgates.lt.test.js index 9a0c144ca..3ebd2118c 100644 --- a/sites/cgates.lt/cgates.lt.test.js +++ b/sites/cgates.lt/cgates.lt.test.js @@ -1,49 +1,49 @@ -const { parser, url } = require('./cgates.lt.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-08-30', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'lrt-televizija-hd', - xmltv_id: 'LRTTV.lt' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe('https://www.cgates.lt/tv-kanalai/lrt-televizija-hd/') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - const results = parser({ content, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(35) - expect(results[0]).toMatchObject({ - start: '2022-08-29T21:05:00.000Z', - stop: '2022-08-29T21:30:00.000Z', - title: '31-oji nuovada (District 31), Drama, 2016', - description: - 'Seriale pasakojama apie kasdienius policijos išbandymus ir sunkumus. Vadovybė pertvarko Monrealio miesto policijos struktūrą: išskirsto į 36 policijos nuovadas, kad šios būtų arčiau gyventojų. 31-osios nuovados darbuotojams tenka kone sunkiausias darbas: šiame miesto rajone gyvena socialiai remtinos šeimos, nuolat kovojančios su turtingųjų klase, įsipliekia ir rasinių konfliktų. Be to, čia akivaizdus kartų atotrūkis, o tapti nusikalstamo pasaulio dalimi labai lengva. Serialo siužetas – intensyvus, nauji nusikaltimai tiriami kiekvieną savaitę. Čia vaizduojamas nepagražintas nusikalstamas pasaulis, jo poveikis rajono gyventojams. Policijos nuovados darbuotojai narplios įvairiausių nusikaltimų schemas. Tai ir pagrobimai, įsilaužimai, žmogžudystės, smurtas artimoje aplinkoje, lytiniai nusikaltimai, prekyba narkotikais, teroristinių išpuolių grėsmė ir pan. Šis serialas leis žiūrovui įsigilinti į policijos pareigūnų realybę, pateiks skirtingą požiūrį į kiekvieną nusikaltimą.' - }) - - expect(results[34]).toMatchObject({ - start: '2022-08-30T20:45:00.000Z', - stop: '2022-08-30T21:15:00.000Z', - title: '31-oji nuovada (District 31), Drama, 2016!' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: '' - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./cgates.lt.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-08-30', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'lrt-televizija-hd', + xmltv_id: 'LRTTV.lt' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe('https://www.cgates.lt/tv-kanalai/lrt-televizija-hd/') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + const results = parser({ content, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(35) + expect(results[0]).toMatchObject({ + start: '2022-08-29T21:05:00.000Z', + stop: '2022-08-29T21:30:00.000Z', + title: '31-oji nuovada (District 31), Drama, 2016', + description: + 'Seriale pasakojama apie kasdienius policijos išbandymus ir sunkumus. Vadovybė pertvarko Monrealio miesto policijos struktūrą: išskirsto į 36 policijos nuovadas, kad šios būtų arčiau gyventojų. 31-osios nuovados darbuotojams tenka kone sunkiausias darbas: šiame miesto rajone gyvena socialiai remtinos šeimos, nuolat kovojančios su turtingųjų klase, įsipliekia ir rasinių konfliktų. Be to, čia akivaizdus kartų atotrūkis, o tapti nusikalstamo pasaulio dalimi labai lengva. Serialo siužetas – intensyvus, nauji nusikaltimai tiriami kiekvieną savaitę. Čia vaizduojamas nepagražintas nusikalstamas pasaulis, jo poveikis rajono gyventojams. Policijos nuovados darbuotojai narplios įvairiausių nusikaltimų schemas. Tai ir pagrobimai, įsilaužimai, žmogžudystės, smurtas artimoje aplinkoje, lytiniai nusikaltimai, prekyba narkotikais, teroristinių išpuolių grėsmė ir pan. Šis serialas leis žiūrovui įsigilinti į policijos pareigūnų realybę, pateiks skirtingą požiūrį į kiekvieną nusikaltimą.' + }) + + expect(results[34]).toMatchObject({ + start: '2022-08-30T20:45:00.000Z', + stop: '2022-08-30T21:15:00.000Z', + title: '31-oji nuovada (District 31), Drama, 2016!' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: '' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/chada.ma/chada.ma.config.js b/sites/chada.ma/chada.ma.config.js index b6002c357..0c05bd8c7 100644 --- a/sites/chada.ma/chada.ma.config.js +++ b/sites/chada.ma/chada.ma.config.js @@ -1,55 +1,55 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'chada.ma', - channels: 'chada.ma.channels.xml', - days: 1, - request: { - cache: { - ttl: 60 * 60 * 1000 // 1 hour - } - }, - url() { - return 'https://chada.ma/fr/chada-tv/grille-tv/' - }, - parser: function ({ content }) { - const $ = cheerio.load(content) - const programs = [] - - $('#stopfix .posts-area h2').each((i, element) => { - const timeRange = $(element).text().trim() - const [start, stop] = timeRange.split(' - ').map(t => parseProgramTime(t.trim())) - - const titleElement = $(element).next('div').next('h3') - const title = titleElement.text().trim() - - const description = titleElement.next('div').text().trim() || 'No description available' - - programs.push({ - title, - description, - start, - stop - }) - }) - - return programs - } -} - -function parseProgramTime(timeStr) { - const timeZone = 'Africa/Casablanca' - const currentDate = dayjs().format('YYYY-MM-DD') - - return dayjs - .tz(`${currentDate} ${timeStr}`, 'YYYY-MM-DD HH:mm', timeZone) - .format('YYYY-MM-DDTHH:mm:ssZ') -} +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'chada.ma', + channels: 'chada.ma.channels.xml', + days: 1, + request: { + cache: { + ttl: 60 * 60 * 1000 // 1 hour + } + }, + url() { + return 'https://chada.ma/fr/chada-tv/grille-tv/' + }, + parser: function ({ content }) { + const $ = cheerio.load(content) + const programs = [] + + $('#stopfix .posts-area h2').each((i, element) => { + const timeRange = $(element).text().trim() + const [start, stop] = timeRange.split(' - ').map(t => parseProgramTime(t.trim())) + + const titleElement = $(element).next('div').next('h3') + const title = titleElement.text().trim() + + const description = titleElement.next('div').text().trim() || 'No description available' + + programs.push({ + title, + description, + start, + stop + }) + }) + + return programs + } +} + +function parseProgramTime(timeStr) { + const timeZone = 'Africa/Casablanca' + const currentDate = dayjs().format('YYYY-MM-DD') + + return dayjs + .tz(`${currentDate} ${timeStr}`, 'YYYY-MM-DD HH:mm', timeZone) + .format('YYYY-MM-DDTHH:mm:ssZ') +} diff --git a/sites/clickthecity.com/clickthecity.com.config.js b/sites/clickthecity.com/clickthecity.com.config.js index 9882af558..e726c8720 100644 --- a/sites/clickthecity.com/clickthecity.com.config.js +++ b/sites/clickthecity.com/clickthecity.com.config.js @@ -1,100 +1,100 @@ -const cheerio = require('cheerio') -const axios = require('axios') -const { DateTime } = require('luxon') - -module.exports = { - site: 'clickthecity.com', - days: 2, - url({ channel }) { - return `https://www.clickthecity.com/tv/channels/?netid=${channel.site_id}` - }, - request: { - method: 'POST', - headers: { - 'content-type': 'application/x-www-form-urlencoded' - }, - data({ date }) { - const params = new URLSearchParams() - params.append( - 'optDate', - DateTime.fromMillis(date.valueOf()).setZone('Asia/Manila').toFormat('yyyy-MM-dd') - ) - params.append('optTime', '00:00:00') - - return params - } - }, - parser({ content, date }) { - const programs = [] - const items = parseItems(content) - items.forEach(item => { - const $item = cheerio.load(item) - let start = parseStart($item, date) - let stop = parseStop($item, date) - if (!start || !stop) return - if (start > stop) { - stop = stop.plus({ days: 1 }) - } - - programs.push({ - title: parseTitle($item), - start, - stop - }) - }) - - return programs - }, - async channels() { - const html = await axios - .get('https://www.clickthecity.com/tv/channels/') - .then(r => r.data) - .catch(console.log) - const $ = cheerio.load(html) - const items = $('#channels .col').toArray() - - return items.map(item => { - const name = $(item).find('.card-body').text().trim() - const url = $(item).find('a').attr('href') - const [, site_id] = url.match(/netid=(\d+)/) || [null, null] - - return { - lang: 'en', - site_id, - name - } - }) - } -} - -function parseTitle($item) { - return $item('td > a').text().trim() -} - -function parseStart($item, date) { - const url = $item('td.cPrg > a').attr('href') || '' - let [, time] = url.match(/starttime=(\d{1,2}%3A\d{2}\+(AM|PM))/) || [null, null] - if (!time) return null - time = `${date.format('YYYY-MM-DD')} ${time.replace('%3A', ':').replace('+', ' ')}` - - return DateTime.fromFormat(time, 'yyyy-MM-dd h:mm a', { zone: 'Asia/Manila' }).toUTC() -} - -function parseStop($item, date) { - const url = $item('td.cPrg > a').attr('href') || '' - let [, time] = url.match(/endtime=(\d{1,2}%3A\d{2}\+(AM|PM))/) || [null, null] - if (!time) return null - time = `${date.format('YYYY-MM-DD')} ${time.replace('%3A', ':').replace('+', ' ')}` - - return DateTime.fromFormat(time, 'yyyy-MM-dd h:mm a', { zone: 'Asia/Manila' }).toUTC() -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('#tvlistings > tbody > tr') - .filter(function () { - return $(this).find('td.cPrg').length - }) - .toArray() -} +const cheerio = require('cheerio') +const axios = require('axios') +const { DateTime } = require('luxon') + +module.exports = { + site: 'clickthecity.com', + days: 2, + url({ channel }) { + return `https://www.clickthecity.com/tv/channels/?netid=${channel.site_id}` + }, + request: { + method: 'POST', + headers: { + 'content-type': 'application/x-www-form-urlencoded' + }, + data({ date }) { + const params = new URLSearchParams() + params.append( + 'optDate', + DateTime.fromMillis(date.valueOf()).setZone('Asia/Manila').toFormat('yyyy-MM-dd') + ) + params.append('optTime', '00:00:00') + + return params + } + }, + parser({ content, date }) { + const programs = [] + const items = parseItems(content) + items.forEach(item => { + const $item = cheerio.load(item) + let start = parseStart($item, date) + let stop = parseStop($item, date) + if (!start || !stop) return + if (start > stop) { + stop = stop.plus({ days: 1 }) + } + + programs.push({ + title: parseTitle($item), + start, + stop + }) + }) + + return programs + }, + async channels() { + const html = await axios + .get('https://www.clickthecity.com/tv/channels/') + .then(r => r.data) + .catch(console.log) + const $ = cheerio.load(html) + const items = $('#channels .col').toArray() + + return items.map(item => { + const name = $(item).find('.card-body').text().trim() + const url = $(item).find('a').attr('href') + const [, site_id] = url.match(/netid=(\d+)/) || [null, null] + + return { + lang: 'en', + site_id, + name + } + }) + } +} + +function parseTitle($item) { + return $item('td > a').text().trim() +} + +function parseStart($item, date) { + const url = $item('td.cPrg > a').attr('href') || '' + let [, time] = url.match(/starttime=(\d{1,2}%3A\d{2}\+(AM|PM))/) || [null, null] + if (!time) return null + time = `${date.format('YYYY-MM-DD')} ${time.replace('%3A', ':').replace('+', ' ')}` + + return DateTime.fromFormat(time, 'yyyy-MM-dd h:mm a', { zone: 'Asia/Manila' }).toUTC() +} + +function parseStop($item, date) { + const url = $item('td.cPrg > a').attr('href') || '' + let [, time] = url.match(/endtime=(\d{1,2}%3A\d{2}\+(AM|PM))/) || [null, null] + if (!time) return null + time = `${date.format('YYYY-MM-DD')} ${time.replace('%3A', ':').replace('+', ' ')}` + + return DateTime.fromFormat(time, 'yyyy-MM-dd h:mm a', { zone: 'Asia/Manila' }).toUTC() +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('#tvlistings > tbody > tr') + .filter(function () { + return $(this).find('td.cPrg').length + }) + .toArray() +} diff --git a/sites/clickthecity.com/clickthecity.com.test.js b/sites/clickthecity.com/clickthecity.com.test.js index 5044c7f4a..bfa0b5d35 100644 --- a/sites/clickthecity.com/clickthecity.com.test.js +++ b/sites/clickthecity.com/clickthecity.com.test.js @@ -1,67 +1,67 @@ -const { parser, url, request } = require('./clickthecity.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-06-12', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '5', - xmltv_id: 'TV5.ph' -} - -it('can generate valid url', () => { - expect(url({ channel })).toBe('https://www.clickthecity.com/tv/channels/?netid=5') -}) - -it('can generate valid request method', () => { - expect(request.method).toBe('POST') -}) - -it('can generate valid request headers', () => { - expect(request.headers).toMatchObject({ - 'content-type': 'application/x-www-form-urlencoded' - }) -}) - -it('can generate valid request data', () => { - const result = request.data({ date }) - expect(result.get('optDate')).toBe('2023-06-12') - expect(result.get('optTime')).toBe('00:00:00') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - const results = parser({ content, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(20) - - expect(results[0]).toMatchObject({ - start: '2023-06-11T21:00:00.000Z', - stop: '2023-06-11T22:00:00.000Z', - title: 'Word Of God' - }) - - expect(results[19]).toMatchObject({ - start: '2023-06-12T15:30:00.000Z', - stop: '2023-06-12T16:00:00.000Z', - title: 'La Suerte De Loli' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: - '' - }) - expect(result).toMatchObject([]) -}) +const { parser, url, request } = require('./clickthecity.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-06-12', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '5', + xmltv_id: 'TV5.ph' +} + +it('can generate valid url', () => { + expect(url({ channel })).toBe('https://www.clickthecity.com/tv/channels/?netid=5') +}) + +it('can generate valid request method', () => { + expect(request.method).toBe('POST') +}) + +it('can generate valid request headers', () => { + expect(request.headers).toMatchObject({ + 'content-type': 'application/x-www-form-urlencoded' + }) +}) + +it('can generate valid request data', () => { + const result = request.data({ date }) + expect(result.get('optDate')).toBe('2023-06-12') + expect(result.get('optTime')).toBe('00:00:00') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + const results = parser({ content, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(20) + + expect(results[0]).toMatchObject({ + start: '2023-06-11T21:00:00.000Z', + stop: '2023-06-11T22:00:00.000Z', + title: 'Word Of God' + }) + + expect(results[19]).toMatchObject({ + start: '2023-06-12T15:30:00.000Z', + stop: '2023-06-12T16:00:00.000Z', + title: 'La Suerte De Loli' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: + '' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/content.astro.com.my/content.astro.com.my.config.js b/sites/content.astro.com.my/content.astro.com.my.config.js index 6f261a91f..c49bfc69e 100644 --- a/sites/content.astro.com.my/content.astro.com.my.config.js +++ b/sites/content.astro.com.my/content.astro.com.my.config.js @@ -1,137 +1,137 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') - -dayjs.extend(utc) - -const API_ENDPOINT = 'https://contenthub-api.eco.astro.com.my' - -module.exports = { - site: 'content.astro.com.my', - days: 2, - url: function ({ channel }) { - return `${API_ENDPOINT}/channel/${channel.site_id}.json` - }, - async parser({ content, date }) { - const programs = [] - const items = parseItems(content, date) - for (let item of items) { - const start = dayjs.utc(item.datetimeInUtc) - const duration = parseDuration(item.duration) - const stop = start.add(duration, 's') - const details = await loadProgramDetails(item) - programs.push({ - title: details.title, - sub_title: item.subtitles, - description: details.longSynopsis || details.shortSynopsis, - actors: parseList(details.cast), - directors: parseList(details.director), - image: details.imageUrl, - rating: parseRating(details), - categories: parseCategories(details), - episode: parseEpisode(item), - season: parseSeason(details), - start: start, - stop: stop - }) - } - - return programs - }, - async channels() { - const data = await axios - .get('https://contenthub-api.eco.astro.com.my/channel/all.json') - .then(r => r.data) - .catch(console.log) - - return data.response.map(item => { - return { - lang: 'ms', - site_id: item.id, - name: item.title - } - }) - } -} - -function parseEpisode(item) { - const [, number] = item.title.match(/Ep(\d+)$/) || [null, null] - - return number ? parseInt(number) : null -} - -function parseSeason(details) { - const [, season] = details.title ? details.title.match(/ S(\d+)/) || [null, null] : [null, null] - - return season ? parseInt(season) : null -} - -function parseList(list) { - return typeof list === 'string' ? list.split(',') : [] -} - -function parseRating(details) { - return details.certification - ? { - system: 'LPF', - value: details.certification - } - : null -} - -function parseItems(content, date) { - try { - const data = JSON.parse(content) - const schedules = data.response.schedule - - return schedules[date.format('YYYY-MM-DD')] || [] - } catch { - return [] - } -} - -function parseDuration(duration) { - const match = duration.match(/(\d{2}):(\d{2}):(\d{2})/) - const hours = parseInt(match[1]) - const minutes = parseInt(match[2]) - const seconds = parseInt(match[3]) - - return hours * 3600 + minutes * 60 + seconds -} - -function parseCategories(details) { - const genres = { - 'filter/2': 'Action', - 'filter/4': 'Anime', - 'filter/12': 'Cartoons', - 'filter/16': 'Comedy', - 'filter/19': 'Crime', - 'filter/24': 'Drama', - 'filter/25': 'Educational', - 'filter/36': 'Horror', - 'filter/39': 'Live Action', - 'filter/55': 'Pre-school', - 'filter/56': 'Reality', - 'filter/60': 'Romance', - 'filter/68': 'Talk Show', - 'filter/69': 'Thriller', - 'filter/72': 'Variety', - 'filter/75': 'Series', - 'filter/100': 'Others (Children)' - } - - return Array.isArray(details.subFilter) - ? details.subFilter.map(g => genres[g.toLowerCase()]).filter(Boolean) - : [] -} - -async function loadProgramDetails(item) { - const url = `${API_ENDPOINT}/api/v1/linear-detail?siTrafficKey=${item.siTrafficKey}` - const data = await axios - .get(url) - .then(r => r.data) - .catch(error => console.log(error.message)) - if (!data) return {} - - return data.response || {} -} +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') + +dayjs.extend(utc) + +const API_ENDPOINT = 'https://contenthub-api.eco.astro.com.my' + +module.exports = { + site: 'content.astro.com.my', + days: 2, + url: function ({ channel }) { + return `${API_ENDPOINT}/channel/${channel.site_id}.json` + }, + async parser({ content, date }) { + const programs = [] + const items = parseItems(content, date) + for (let item of items) { + const start = dayjs.utc(item.datetimeInUtc) + const duration = parseDuration(item.duration) + const stop = start.add(duration, 's') + const details = await loadProgramDetails(item) + programs.push({ + title: details.title, + sub_title: item.subtitles, + description: details.longSynopsis || details.shortSynopsis, + actors: parseList(details.cast), + directors: parseList(details.director), + image: details.imageUrl, + rating: parseRating(details), + categories: parseCategories(details), + episode: parseEpisode(item), + season: parseSeason(details), + start: start, + stop: stop + }) + } + + return programs + }, + async channels() { + const data = await axios + .get('https://contenthub-api.eco.astro.com.my/channel/all.json') + .then(r => r.data) + .catch(console.log) + + return data.response.map(item => { + return { + lang: 'ms', + site_id: item.id, + name: item.title + } + }) + } +} + +function parseEpisode(item) { + const [, number] = item.title.match(/Ep(\d+)$/) || [null, null] + + return number ? parseInt(number) : null +} + +function parseSeason(details) { + const [, season] = details.title ? details.title.match(/ S(\d+)/) || [null, null] : [null, null] + + return season ? parseInt(season) : null +} + +function parseList(list) { + return typeof list === 'string' ? list.split(',') : [] +} + +function parseRating(details) { + return details.certification + ? { + system: 'LPF', + value: details.certification + } + : null +} + +function parseItems(content, date) { + try { + const data = JSON.parse(content) + const schedules = data.response.schedule + + return schedules[date.format('YYYY-MM-DD')] || [] + } catch { + return [] + } +} + +function parseDuration(duration) { + const match = duration.match(/(\d{2}):(\d{2}):(\d{2})/) + const hours = parseInt(match[1]) + const minutes = parseInt(match[2]) + const seconds = parseInt(match[3]) + + return hours * 3600 + minutes * 60 + seconds +} + +function parseCategories(details) { + const genres = { + 'filter/2': 'Action', + 'filter/4': 'Anime', + 'filter/12': 'Cartoons', + 'filter/16': 'Comedy', + 'filter/19': 'Crime', + 'filter/24': 'Drama', + 'filter/25': 'Educational', + 'filter/36': 'Horror', + 'filter/39': 'Live Action', + 'filter/55': 'Pre-school', + 'filter/56': 'Reality', + 'filter/60': 'Romance', + 'filter/68': 'Talk Show', + 'filter/69': 'Thriller', + 'filter/72': 'Variety', + 'filter/75': 'Series', + 'filter/100': 'Others (Children)' + } + + return Array.isArray(details.subFilter) + ? details.subFilter.map(g => genres[g.toLowerCase()]).filter(Boolean) + : [] +} + +async function loadProgramDetails(item) { + const url = `${API_ENDPOINT}/api/v1/linear-detail?siTrafficKey=${item.siTrafficKey}` + const data = await axios + .get(url) + .then(r => r.data) + .catch(error => console.log(error.message)) + if (!data) return {} + + return data.response || {} +} diff --git a/sites/content.astro.com.my/content.astro.com.my.test.js b/sites/content.astro.com.my/content.astro.com.my.test.js index 8e9b46609..9a169ada7 100644 --- a/sites/content.astro.com.my/content.astro.com.my.test.js +++ b/sites/content.astro.com.my/content.astro.com.my.test.js @@ -1,71 +1,71 @@ -const { parser, url } = require('./content.astro.com.my.config.js') -const fs = require('fs') -const path = require('path') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -const date = dayjs.utc('2022-10-31', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '425', - xmltv_id: 'TVBClassic.hk' -} - -it('can generate valid url', () => { - expect(url({ channel })).toBe('https://contenthub-api.eco.astro.com.my/channel/425.json') -}) - -it('can parse response', async () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - - axios.get.mockImplementation(url => { - if ( - url === - 'https://contenthub-api.eco.astro.com.my/api/v1/linear-detail?siTrafficKey=1:10000526:47979653' - ) { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program.json'))) - }) - } else { - return Promise.resolve({ data: '' }) - } - }) - - let results = await parser({ content, channel, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(31) - expect(results[0]).toMatchObject({ - start: '2022-10-30T16:10:00.000Z', - stop: '2022-10-30T17:02:00.000Z', - title: 'Triumph in the Skies S1 Ep06', - description: - 'This classic drama depicts the many aspects of two complicated relationships set against an airline company. Will those involved ever find true love?', - actors: ['Francis Ng Chun Yu', 'Joe Ma Tak Chung', 'Flora Chan Wai San'], - directors: ['Joe Ma Tak Chung'], - image: 'https://s3-ap-southeast-1.amazonaws.com/ams-astro/production/images/1035X328883.jpg', - rating: { - system: 'LPF', - value: 'U' - }, - episode: 6, - season: 1, - categories: ['Drama'] - }) -}) - -it('can handle empty guide', async () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) - const results = await parser({ date, content }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./content.astro.com.my.config.js') +const fs = require('fs') +const path = require('path') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.utc('2022-10-31', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '425', + xmltv_id: 'TVBClassic.hk' +} + +it('can generate valid url', () => { + expect(url({ channel })).toBe('https://contenthub-api.eco.astro.com.my/channel/425.json') +}) + +it('can parse response', async () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + + axios.get.mockImplementation(url => { + if ( + url === + 'https://contenthub-api.eco.astro.com.my/api/v1/linear-detail?siTrafficKey=1:10000526:47979653' + ) { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program.json'))) + }) + } else { + return Promise.resolve({ data: '' }) + } + }) + + let results = await parser({ content, channel, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(31) + expect(results[0]).toMatchObject({ + start: '2022-10-30T16:10:00.000Z', + stop: '2022-10-30T17:02:00.000Z', + title: 'Triumph in the Skies S1 Ep06', + description: + 'This classic drama depicts the many aspects of two complicated relationships set against an airline company. Will those involved ever find true love?', + actors: ['Francis Ng Chun Yu', 'Joe Ma Tak Chung', 'Flora Chan Wai San'], + directors: ['Joe Ma Tak Chung'], + image: 'https://s3-ap-southeast-1.amazonaws.com/ams-astro/production/images/1035X328883.jpg', + rating: { + system: 'LPF', + value: 'U' + }, + episode: 6, + season: 1, + categories: ['Drama'] + }) +}) + +it('can handle empty guide', async () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) + const results = await parser({ date, content }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/cosmotetv.gr/cosmotetv.gr.config.js b/sites/cosmotetv.gr/cosmotetv.gr.config.js index 8374f0509..3ec1fde66 100644 --- a/sites/cosmotetv.gr/cosmotetv.gr.config.js +++ b/sites/cosmotetv.gr/cosmotetv.gr.config.js @@ -1,85 +1,85 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -const timezone = require('dayjs/plugin/timezone') - -dayjs.extend(utc) -dayjs.extend(customParseFormat) -dayjs.extend(timezone) - -module.exports = { - site: 'cosmotetv.gr', - days: 5, - request: { - cache: { - ttl: 60 * 60 * 1000 // 1 hour - }, - method: 'GET', - headers: { - referer: 'https://www.cosmotetv.gr/', - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', - Accept: '*/*', - 'Accept-Language': 'en-US,en;q=0.9', - 'Accept-Encoding': 'gzip, deflate, br, zstd', - Origin: 'https://www.cosmotetv.gr', - 'Sec-Ch-Ua': '"Not.A/Brand";v="24", "Chromium";v="131", "Google Chrome";v="131"', - 'Sec-Ch-Ua-Mobile': '?0', - 'Sec-Ch-Ua-Platform': '"Windows"', - 'Sec-Fetch-Dest': 'empty', - 'Sec-Fetch-Mode': 'cors', - 'Sec-Fetch-Site': 'cross-site' - } - }, - url: function ({ date, channel }) { - const startOfDay = dayjs(date).startOf('day').utc().unix() - const endOfDay = dayjs(date).endOf('day').utc().unix() - return `https://mwapi-prod.cosmotetvott.gr/api/v3.4/epg/listings/el?from=${startOfDay}&to=${endOfDay}&callSigns=${channel.site_id}&endingIncludedInRange=false` - }, - parser: function ({ content }) { - let programs = [] - const data = JSON.parse(content) - data.channels.forEach(channel => { - channel.items.forEach(item => { - const start = dayjs(item.startTime).utc().toISOString() - const stop = dayjs(item.endTime).utc().toISOString() - programs.push({ - title: item.title, - description: item.description || 'No description available', - category: item.qoe.genre, - image: item.thumbnails.standard, - start, - stop - }) - }) - }) - return programs - }, - async channels() { - const axios = require('axios') - try { - const response = await axios.get( - 'https://mwapi-prod.cosmotetvott.gr/api/v3.4/epg/channels/all/el', - { - headers: this.request.headers - } - ) - const data = response.data - - if (data && data.channels) { - return data.channels.map(item => ({ - lang: 'el', - site_id: item.callSign, - name: item.title - //logo: item.logos.square - })) - } else { - console.error('Unexpected response structure:', data) - return [] - } - } catch (error) { - console.error('Error fetching channel data:', error) - return [] - } - } -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const timezone = require('dayjs/plugin/timezone') + +dayjs.extend(utc) +dayjs.extend(customParseFormat) +dayjs.extend(timezone) + +module.exports = { + site: 'cosmotetv.gr', + days: 5, + request: { + cache: { + ttl: 60 * 60 * 1000 // 1 hour + }, + method: 'GET', + headers: { + referer: 'https://www.cosmotetv.gr/', + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', + Accept: '*/*', + 'Accept-Language': 'en-US,en;q=0.9', + 'Accept-Encoding': 'gzip, deflate, br, zstd', + Origin: 'https://www.cosmotetv.gr', + 'Sec-Ch-Ua': '"Not.A/Brand";v="24", "Chromium";v="131", "Google Chrome";v="131"', + 'Sec-Ch-Ua-Mobile': '?0', + 'Sec-Ch-Ua-Platform': '"Windows"', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site' + } + }, + url: function ({ date, channel }) { + const startOfDay = dayjs(date).startOf('day').utc().unix() + const endOfDay = dayjs(date).endOf('day').utc().unix() + return `https://mwapi-prod.cosmotetvott.gr/api/v3.4/epg/listings/el?from=${startOfDay}&to=${endOfDay}&callSigns=${channel.site_id}&endingIncludedInRange=false` + }, + parser: function ({ content }) { + let programs = [] + const data = JSON.parse(content) + data.channels.forEach(channel => { + channel.items.forEach(item => { + const start = dayjs(item.startTime).utc().toISOString() + const stop = dayjs(item.endTime).utc().toISOString() + programs.push({ + title: item.title, + description: item.description || 'No description available', + category: item.qoe.genre, + image: item.thumbnails.standard, + start, + stop + }) + }) + }) + return programs + }, + async channels() { + const axios = require('axios') + try { + const response = await axios.get( + 'https://mwapi-prod.cosmotetvott.gr/api/v3.4/epg/channels/all/el', + { + headers: this.request.headers + } + ) + const data = response.data + + if (data && data.channels) { + return data.channels.map(item => ({ + lang: 'el', + site_id: item.callSign, + name: item.title + //logo: item.logos.square + })) + } else { + console.error('Unexpected response structure:', data) + return [] + } + } catch (error) { + console.error('Error fetching channel data:', error) + return [] + } + } +} diff --git a/sites/cubmu.com/cubmu.com.config.js b/sites/cubmu.com/cubmu.com.config.js index ec288a36c..56a2ae6fd 100644 --- a/sites/cubmu.com/cubmu.com.config.js +++ b/sites/cubmu.com/cubmu.com.config.js @@ -1,114 +1,114 @@ -const dayjs = require('dayjs') -const timezone = require('dayjs/plugin/timezone') -const utc = require('dayjs/plugin/utc') - -dayjs.extend(timezone) -dayjs.extend(utc) - -module.exports = { - site: 'cubmu.com', - days: 2, - url({ channel, date }) { - return `https://servicebuss.transvision.co.id/v2/cms/getEPGData?app_id=cubmu&tvs_platform_id=standalone&schedule_date=${date.format( - 'YYYY-MM-DD' - )}&channel_id=${channel.site_id}` - }, - parser({ content, channel }) { - const programs = [] - const items = parseItems(content) - items.forEach(item => { - programs.push({ - title: parseTitle(item), - description: parseDescription(item, channel.lang), - episode: parseEpisode(item), - start: parseStart(item).toISOString(), - stop: parseStop(item).toISOString() - }) - }) - - return programs - }, - async channels({ lang = 'id' }) { - const axios = require('axios') - const cheerio = require('cheerio') - const result = await axios - .get('https://cubmu.com/live-tv') - .then(response => response.data) - .catch(console.error) - - const $ = cheerio.load(result) - - // retrieve service api data - const config = JSON.parse($('#__NEXT_DATA__').text()).runtimeConfig || {} - - const options = { - headers: { - Origin: 'https://cubmu.com', - Referer: 'https://cubmu.com/live-tv' - } - } - // login to service bus - await axios - .post( - `https://servicebuss.transvision.co.id/tvs/login/external?email=${config.email}&password=${config.password}&deviceId=${config.deviceId}&deviceType=${config.deviceType}&deviceModel=${config.deviceModel}&deviceToken=&serial=&platformId=${config.platformId}`, - options - ) - .then(response => response.data) - .catch(console.error) - // list channels - const subscribedChannels = await axios - .post( - `https://servicebuss.transvision.co.id/tvs/subscribe_product/list?platformId=${config.platformId}`, - options - ) - .then(response => response.data) - .catch(console.error) - - const channels = [] - const included = [] - if (Array.isArray(subscribedChannels.channelPackageList)) { - subscribedChannels.channelPackageList.forEach(pkg => { - pkg.channelList.forEach(channel => { - if (included.indexOf(channel.id) < 0) { - included.push(channel.id) - channels.push({ - lang, - site_id: channel.id, - name: channel.name - }) - } - }) - }) - } - - return channels - } -} - -function parseItems(content) { - return content ? JSON.parse(content.trim()).result || [] : [] -} - -function parseTitle(item) { - return item.scehedule_title -} - -function parseDescription(item, lang = 'id') { - return lang === 'id' ? item.schedule_json.primarySynopsis : item.schedule_json.secondarySynopsis -} - -function parseEpisode(item) { - return item.schedule_json.episodeName -} - -function parseStart(item) { - return dayjs.tz(item.schedule_date, 'YYYY-MM-DD HH:mm:ss', 'Asia/Jakarta') -} - -function parseStop(item) { - return dayjs.tz( - [item.schedule_date.split(' ')[0], item.schedule_end_time].join(' '), - 'YYYY-MM-DD HH:mm:ss', - 'Asia/Jakarta' - ) -} +const dayjs = require('dayjs') +const timezone = require('dayjs/plugin/timezone') +const utc = require('dayjs/plugin/utc') + +dayjs.extend(timezone) +dayjs.extend(utc) + +module.exports = { + site: 'cubmu.com', + days: 2, + url({ channel, date }) { + return `https://servicebuss.transvision.co.id/v2/cms/getEPGData?app_id=cubmu&tvs_platform_id=standalone&schedule_date=${date.format( + 'YYYY-MM-DD' + )}&channel_id=${channel.site_id}` + }, + parser({ content, channel }) { + const programs = [] + const items = parseItems(content) + items.forEach(item => { + programs.push({ + title: parseTitle(item), + description: parseDescription(item, channel.lang), + episode: parseEpisode(item), + start: parseStart(item).toISOString(), + stop: parseStop(item).toISOString() + }) + }) + + return programs + }, + async channels({ lang = 'id' }) { + const axios = require('axios') + const cheerio = require('cheerio') + const result = await axios + .get('https://cubmu.com/live-tv') + .then(response => response.data) + .catch(console.error) + + const $ = cheerio.load(result) + + // retrieve service api data + const config = JSON.parse($('#__NEXT_DATA__').text()).runtimeConfig || {} + + const options = { + headers: { + Origin: 'https://cubmu.com', + Referer: 'https://cubmu.com/live-tv' + } + } + // login to service bus + await axios + .post( + `https://servicebuss.transvision.co.id/tvs/login/external?email=${config.email}&password=${config.password}&deviceId=${config.deviceId}&deviceType=${config.deviceType}&deviceModel=${config.deviceModel}&deviceToken=&serial=&platformId=${config.platformId}`, + options + ) + .then(response => response.data) + .catch(console.error) + // list channels + const subscribedChannels = await axios + .post( + `https://servicebuss.transvision.co.id/tvs/subscribe_product/list?platformId=${config.platformId}`, + options + ) + .then(response => response.data) + .catch(console.error) + + const channels = [] + const included = [] + if (Array.isArray(subscribedChannels.channelPackageList)) { + subscribedChannels.channelPackageList.forEach(pkg => { + pkg.channelList.forEach(channel => { + if (included.indexOf(channel.id) < 0) { + included.push(channel.id) + channels.push({ + lang, + site_id: channel.id, + name: channel.name + }) + } + }) + }) + } + + return channels + } +} + +function parseItems(content) { + return content ? JSON.parse(content.trim()).result || [] : [] +} + +function parseTitle(item) { + return item.scehedule_title +} + +function parseDescription(item, lang = 'id') { + return lang === 'id' ? item.schedule_json.primarySynopsis : item.schedule_json.secondarySynopsis +} + +function parseEpisode(item) { + return item.schedule_json.episodeName +} + +function parseStart(item) { + return dayjs.tz(item.schedule_date, 'YYYY-MM-DD HH:mm:ss', 'Asia/Jakarta') +} + +function parseStop(item) { + return dayjs.tz( + [item.schedule_date.split(' ')[0], item.schedule_end_time].join(' '), + 'YYYY-MM-DD HH:mm:ss', + 'Asia/Jakarta' + ) +} diff --git a/sites/cyta.com.cy/cyta.com.cy.config.js b/sites/cyta.com.cy/cyta.com.cy.config.js index f698dfa4c..56c2cb801 100644 --- a/sites/cyta.com.cy/cyta.com.cy.config.js +++ b/sites/cyta.com.cy/cyta.com.cy.config.js @@ -1,59 +1,59 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'cyta.com.cy', - days: 7, - request: { - cache: { - ttl: 60 * 60 * 1000 // 1 hour - } - }, - url: function ({ date, channel }) { - // Get the epoch timestamp - const todayEpoch = date.startOf('day').utc().valueOf() - // Get the epoch timestamp for the next day - const nextDayEpoch = date.add(1, 'day').startOf('day').utc().valueOf() - return `https://epg.cyta.com.cy/api/mediacatalog/fetchEpg?startTimeEpoch=${todayEpoch}&endTimeEpoch=${nextDayEpoch}&language=1&channelIds=${channel.site_id}` - }, - parser: function ({ content }) { - const data = JSON.parse(content) - const programs = [] - - data.channelEpgs.forEach(channel => { - channel.epgPlayables.forEach(epg => { - const start = new Date(epg.startTime).toISOString() - const stop = new Date(epg.endTime).toISOString() - - programs.push({ - title: epg.name, - start, - stop - }) - }) - }) - - return programs - }, - async channels() { - const axios = require('axios') - const data = await axios - .get('https://epg.cyta.com.cy/api/mediacatalog/fetchChannels?language=1') - .then(r => r.data) - .catch(console.log) - - return data.channels.map(item => { - return { - lang: 'el', - site_id: item.id, - name: item.name - } - }) - } -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'cyta.com.cy', + days: 7, + request: { + cache: { + ttl: 60 * 60 * 1000 // 1 hour + } + }, + url: function ({ date, channel }) { + // Get the epoch timestamp + const todayEpoch = date.startOf('day').utc().valueOf() + // Get the epoch timestamp for the next day + const nextDayEpoch = date.add(1, 'day').startOf('day').utc().valueOf() + return `https://epg.cyta.com.cy/api/mediacatalog/fetchEpg?startTimeEpoch=${todayEpoch}&endTimeEpoch=${nextDayEpoch}&language=1&channelIds=${channel.site_id}` + }, + parser: function ({ content }) { + const data = JSON.parse(content) + const programs = [] + + data.channelEpgs.forEach(channel => { + channel.epgPlayables.forEach(epg => { + const start = new Date(epg.startTime).toISOString() + const stop = new Date(epg.endTime).toISOString() + + programs.push({ + title: epg.name, + start, + stop + }) + }) + }) + + return programs + }, + async channels() { + const axios = require('axios') + const data = await axios + .get('https://epg.cyta.com.cy/api/mediacatalog/fetchChannels?language=1') + .then(r => r.data) + .catch(console.log) + + return data.channels.map(item => { + return { + lang: 'el', + site_id: item.id, + name: item.name + } + }) + } +} diff --git a/sites/cyta.com.cy/cyta.com.cy.test.js b/sites/cyta.com.cy/cyta.com.cy.test.js index 95797a921..ebc09b24d 100644 --- a/sites/cyta.com.cy/cyta.com.cy.test.js +++ b/sites/cyta.com.cy/cyta.com.cy.test.js @@ -1,49 +1,49 @@ -const { url, parser } = require('./cyta.com.cy.config.js') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(utc) -dayjs.extend(customParseFormat) - -const date = dayjs.utc('2025-01-03', 'YYYY-MM-DD').startOf('day') -const channel = { - site_id: '561066', - xmltv_id: 'RIK1.cy' -} - -it('can generate valid url', () => { - const generatedUrl = url({ date, channel }) - expect(generatedUrl).toBe( - 'https://epg.cyta.com.cy/api/mediacatalog/fetchEpg?startTimeEpoch=1735862400000&endTimeEpoch=1735948800000&language=1&channelIds=561066' - ) -}) - -it('can parse response', () => { - const content = ` - { - "channelEpgs": [ - { - "epgPlayables": [ - { "name": "Πρώτη Ενημέρωση", "startTime": 1735879500000, "endTime": 1735889400000 } - ] - } - ] - }` - - const result = parser({ content }) - - expect(result).toMatchObject([ - { - title: 'Πρώτη Ενημέρωση', - start: '2025-01-03T04:45:00.000Z', - stop: '2025-01-03T07:30:00.000Z' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: '{"channelEpgs":[]}' - }) - expect(result).toMatchObject([]) -}) +const { url, parser } = require('./cyta.com.cy.config.js') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(utc) +dayjs.extend(customParseFormat) + +const date = dayjs.utc('2025-01-03', 'YYYY-MM-DD').startOf('day') +const channel = { + site_id: '561066', + xmltv_id: 'RIK1.cy' +} + +it('can generate valid url', () => { + const generatedUrl = url({ date, channel }) + expect(generatedUrl).toBe( + 'https://epg.cyta.com.cy/api/mediacatalog/fetchEpg?startTimeEpoch=1735862400000&endTimeEpoch=1735948800000&language=1&channelIds=561066' + ) +}) + +it('can parse response', () => { + const content = ` + { + "channelEpgs": [ + { + "epgPlayables": [ + { "name": "Πρώτη Ενημέρωση", "startTime": 1735879500000, "endTime": 1735889400000 } + ] + } + ] + }` + + const result = parser({ content }) + + expect(result).toMatchObject([ + { + title: 'Πρώτη Ενημέρωση', + start: '2025-01-03T04:45:00.000Z', + stop: '2025-01-03T07:30:00.000Z' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: '{"channelEpgs":[]}' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/dens.tv/dens.tv.config.js b/sites/dens.tv/dens.tv.config.js index 25cd3036a..11049d6c3 100644 --- a/sites/dens.tv/dens.tv.config.js +++ b/sites/dens.tv/dens.tv.config.js @@ -1,73 +1,73 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -const tz = 'Asia/Jakarta' - -module.exports = { - site: 'dens.tv', - days: 2, - url({ channel, date }) { - return `https://www.dens.tv/api/dens3/tv/TvChannels/listEpgByDate?date=${date.format( - 'YYYY-MM-DD' - )}&id_channel=${channel.site_id}&app_type=10` - }, - parser({ content }) { - // parsing - const response = JSON.parse(content) - const programs = [] - - if (Array.isArray(response?.data)) { - response.data.forEach(item => { - const title = item.title - const [, , , season, , , episode] = title.match( - /( (Season |Season|S)(\d+))?( (Episode|Ep) (\d+))/ - ) || [null, null, null, null, null, null, null] - programs.push({ - title, - description: item.description, - season: season ? parseInt(season) : season, - episode: episode ? parseInt(episode) : episode, - start: dayjs.tz(item.start_time, 'YYYY-MM-DD HH:mm:ss', tz), - stop: dayjs.tz(item.end_time, 'YYYY-MM-DD HH:mm:ss', tz) - }) - }) - } - - return programs - }, - async channels() { - const axios = require('axios') - - const categories = { - local: 1, - premium: 2, - international: 3 - } - - const channels = [] - for (const id_category of Object.values(categories)) { - const data = await axios - .get('https://www.dens.tv/api/dens3/tv/TvChannels/listByCategory', { - params: { id_category } - }) - .then(r => r.data) - .catch(console.error) - - data.data.contents.forEach(item => { - channels.push({ - lang: 'id', - site_id: item.meta.id, - name: item.meta.title - }) - }) - } - - return channels - } -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +const tz = 'Asia/Jakarta' + +module.exports = { + site: 'dens.tv', + days: 2, + url({ channel, date }) { + return `https://www.dens.tv/api/dens3/tv/TvChannels/listEpgByDate?date=${date.format( + 'YYYY-MM-DD' + )}&id_channel=${channel.site_id}&app_type=10` + }, + parser({ content }) { + // parsing + const response = JSON.parse(content) + const programs = [] + + if (Array.isArray(response?.data)) { + response.data.forEach(item => { + const title = item.title + const [, , , season, , , episode] = title.match( + /( (Season |Season|S)(\d+))?( (Episode|Ep) (\d+))/ + ) || [null, null, null, null, null, null, null] + programs.push({ + title, + description: item.description, + season: season ? parseInt(season) : season, + episode: episode ? parseInt(episode) : episode, + start: dayjs.tz(item.start_time, 'YYYY-MM-DD HH:mm:ss', tz), + stop: dayjs.tz(item.end_time, 'YYYY-MM-DD HH:mm:ss', tz) + }) + }) + } + + return programs + }, + async channels() { + const axios = require('axios') + + const categories = { + local: 1, + premium: 2, + international: 3 + } + + const channels = [] + for (const id_category of Object.values(categories)) { + const data = await axios + .get('https://www.dens.tv/api/dens3/tv/TvChannels/listByCategory', { + params: { id_category } + }) + .then(r => r.data) + .catch(console.error) + + data.data.contents.forEach(item => { + channels.push({ + lang: 'id', + site_id: item.meta.id, + name: item.meta.title + }) + }) + } + + return channels + } +} diff --git a/sites/dens.tv/dens.tv.test.js b/sites/dens.tv/dens.tv.test.js index 79883d0f2..5200e9947 100644 --- a/sites/dens.tv/dens.tv.test.js +++ b/sites/dens.tv/dens.tv.test.js @@ -1,50 +1,50 @@ -const { url, parser } = require('./dens.tv.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') - -dayjs.extend(utc) - -const date = dayjs.utc('2024-11-24').startOf('d') -const channel = { site_id: '38', xmltv_id: 'AniplusAsia.sg', lang: 'id' } - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://www.dens.tv/api/dens3/tv/TvChannels/listEpgByDate?date=2024-11-24&id_channel=38&app_type=10' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - let results = parser({ content }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(2) - - expect(results[0]).toMatchObject({ - start: '2024-11-23T17:00:00.000Z', - stop: '2024-11-23T17:30:00.000Z', - title: 'Migi & Dali Episode 2', - episode: 2 - }) - - expect(results[1]).toMatchObject({ - start: '2024-11-23T19:30:00.000Z', - stop: '2024-11-23T20:00:00.000Z', - title: 'Attack on Titan Season 3 Episode 7', - season: 3, - episode: 7 - }) -}) - -it('can handle empty guide', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - const results = parser({ content }) - - expect(results).toMatchObject([]) -}) +const { url, parser } = require('./dens.tv.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') + +dayjs.extend(utc) + +const date = dayjs.utc('2024-11-24').startOf('d') +const channel = { site_id: '38', xmltv_id: 'AniplusAsia.sg', lang: 'id' } + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://www.dens.tv/api/dens3/tv/TvChannels/listEpgByDate?date=2024-11-24&id_channel=38&app_type=10' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + let results = parser({ content }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(2) + + expect(results[0]).toMatchObject({ + start: '2024-11-23T17:00:00.000Z', + stop: '2024-11-23T17:30:00.000Z', + title: 'Migi & Dali Episode 2', + episode: 2 + }) + + expect(results[1]).toMatchObject({ + start: '2024-11-23T19:30:00.000Z', + stop: '2024-11-23T20:00:00.000Z', + title: 'Attack on Titan Season 3 Episode 7', + season: 3, + episode: 7 + }) +}) + +it('can handle empty guide', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + const results = parser({ content }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/derana.lk/derana.lk.test.js b/sites/derana.lk/derana.lk.test.js index d8191b561..070259730 100644 --- a/sites/derana.lk/derana.lk.test.js +++ b/sites/derana.lk/derana.lk.test.js @@ -1,50 +1,50 @@ -const { parser, url } = require('./derana.lk.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-05-18', 'YYYY-MM-DD').startOf('d') - -it('can generate valid url', () => { - expect(url({ date })).toBe('https://derana.lk/api/schedules/18-05-2025') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - - let results = parser({ content }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - - return p - }) - - expect(results.length).toBe(20) - expect(results[0]).toMatchObject({ - title: 'Dahami Derana', - image: 'https://derana.lk/storage/uploads/imgs/program/51/20240717062206.jpg', - start: '2025-05-17T23:05:00.000Z', - stop: '2025-05-18T00:55:00.000Z' - }) - expect(results[1]).toMatchObject({ - title: 'Derana Aruna', - image: 'https://derana.lk/storage/uploads/imgs/program/15/20240613075807.jpg', - start: '2025-05-18T00:55:00.000Z', - stop: '2025-05-18T02:00:00.000Z' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - content: { - error: 'An error occurred' - } - }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./derana.lk.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-05-18', 'YYYY-MM-DD').startOf('d') + +it('can generate valid url', () => { + expect(url({ date })).toBe('https://derana.lk/api/schedules/18-05-2025') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + + let results = parser({ content }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + + return p + }) + + expect(results.length).toBe(20) + expect(results[0]).toMatchObject({ + title: 'Dahami Derana', + image: 'https://derana.lk/storage/uploads/imgs/program/51/20240717062206.jpg', + start: '2025-05-17T23:05:00.000Z', + stop: '2025-05-18T00:55:00.000Z' + }) + expect(results[1]).toMatchObject({ + title: 'Derana Aruna', + image: 'https://derana.lk/storage/uploads/imgs/program/15/20240613075807.jpg', + start: '2025-05-18T00:55:00.000Z', + stop: '2025-05-18T02:00:00.000Z' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + content: { + error: 'An error occurred' + } + }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/digea.gr/digea.gr.config.js b/sites/digea.gr/digea.gr.config.js index 32c2b923d..c0599b38c 100644 --- a/sites/digea.gr/digea.gr.config.js +++ b/sites/digea.gr/digea.gr.config.js @@ -1,86 +1,86 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'digea.gr', - days: 2, - url: 'https://www.digea.gr/el/api/epg/get-events', - request: { - method: 'POST', - headers: { - 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8' - }, - data({ date }) { - const data = new URLSearchParams() - data.append('action', 'get_events') - data.append('date', date.format('YYYY-M-D')) - - return data - } - }, - parser({ content, channel }) { - let programs = [] - let items = parseItems(content, channel) - items.forEach(item => { - programs.push({ - title: item.title, - description: item.long_synopsis, - start: parseStart(item), - stop: parseStop(item) - }) - }) - - return programs - }, - async channels() { - const data = await axios - .post( - 'https://www.digea.gr/el/api/epg/get-channels', - new URLSearchParams({ - action: 'get_chanels', - lang: 'el' - }), - { - headers: { - 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8' - } - } - ) - .then(r => r.data) - .catch(console.error) - - return data.map(channel => { - return { - lang: 'el', - site_id: channel.id, - name: channel.name - } - }) - } -} - -function parseStart(item) { - return dayjs.tz(item.actual_time, 'YYYY-MM-DD HH:mm:ss', 'Europe/Athens') -} - -function parseStop(item) { - return dayjs.tz(item.end_time, 'YYYY-MM-DD HH:mm:ss', 'Europe/Athens') -} - -function parseItems(content, channel) { - try { - const data = JSON.parse(content) - if (!Array.isArray(data)) return [] - - return data.filter(p => p.channel_id === channel.site_id) - } catch { - return [] - } -} +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'digea.gr', + days: 2, + url: 'https://www.digea.gr/el/api/epg/get-events', + request: { + method: 'POST', + headers: { + 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8' + }, + data({ date }) { + const data = new URLSearchParams() + data.append('action', 'get_events') + data.append('date', date.format('YYYY-M-D')) + + return data + } + }, + parser({ content, channel }) { + let programs = [] + let items = parseItems(content, channel) + items.forEach(item => { + programs.push({ + title: item.title, + description: item.long_synopsis, + start: parseStart(item), + stop: parseStop(item) + }) + }) + + return programs + }, + async channels() { + const data = await axios + .post( + 'https://www.digea.gr/el/api/epg/get-channels', + new URLSearchParams({ + action: 'get_chanels', + lang: 'el' + }), + { + headers: { + 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8' + } + } + ) + .then(r => r.data) + .catch(console.error) + + return data.map(channel => { + return { + lang: 'el', + site_id: channel.id, + name: channel.name + } + }) + } +} + +function parseStart(item) { + return dayjs.tz(item.actual_time, 'YYYY-MM-DD HH:mm:ss', 'Europe/Athens') +} + +function parseStop(item) { + return dayjs.tz(item.end_time, 'YYYY-MM-DD HH:mm:ss', 'Europe/Athens') +} + +function parseItems(content, channel) { + try { + const data = JSON.parse(content) + if (!Array.isArray(data)) return [] + + return data.filter(p => p.channel_id === channel.site_id) + } catch { + return [] + } +} diff --git a/sites/digea.gr/digea.gr.test.js b/sites/digea.gr/digea.gr.test.js index e3da1ca10..4d4bd523f 100644 --- a/sites/digea.gr/digea.gr.test.js +++ b/sites/digea.gr/digea.gr.test.js @@ -1,69 +1,69 @@ -const { parser, url, request } = require('./digea.gr.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-01-17', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '1100', - xmltv_id: 'AlphaTV.gr' -} - -it('can generate valid url', () => { - expect(url).toBe('https://www.digea.gr/el/api/epg/get-events') -}) - -it('can generate valid request method', () => { - expect(request.method).toBe('POST') -}) - -it('can generate valid request headers', () => { - expect(request.headers).toMatchObject({ - 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8' - }) -}) - -it('can generate valid request data', () => { - const data = request.data({ date }) - - expect(data.get('action')).toBe('get_events') - expect(data.get('date')).toBe('2025-1-17') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') - let results = parser({ content, channel }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(19) - expect(results[0]).toMatchObject({ - start: '2025-01-16T23:30:00.000Z', - stop: '2025-01-17T01:30:00.000Z', - title: '[K12] Το Ξεκαθάρισμα (A Score To Settle)', - description: - "Περιπέτεια αμερικανικής παραγωγής 2019 [Το πρόγραμμα περιέχει σκηνές σεξουαλικές, βίας, χρήσης ναρκωτικών κι άλλων εξαρτησιογόνων ουσιών και απρεπή εκφορά λόγου]. Ο Φρανκ απελευθερώνεται από τη φυλακή πολλά χρόνια μετά αφού κατηγορήθηκε για ένα έγκλημα που δεν διέπραξε. Τώρα, ελεύθερος, ξεκινά μια πορεία εκδίκησης εναντίον των ανθρώπων των οποίων οι πράξεις τον έστειλαν στη φυλακή. Ηθοποιοί: Νίκολας Κέιτζ, Μπέντζαμιν Μπρατ, Νόα Λε Γκρος, Καρολίνα Γουίντρα. Σενάριο: Σον Κου, Τζον Νιούμαν. Σκηνοθεσία: Σον Κου. Διάρκεια: 94'. " - }) - expect(results[18]).toMatchObject({ - start: '2025-01-17T21:30:00.000Z', - stop: '2025-01-17T23:30:00.000Z', - title: '[K8] Βασικά Καλησπέρα Σας', - description: - "Κωμωδία ελληνικής παραγωγής 1982. Δύο πειρατικοί ραδιοσταθμοί, εκ των οποίων ο ένας βάζει λαϊκά άσματα και ο άλλος ροκ μουσική, ανταγωνίζονται για την πρωτιά στην ακροαματικότητα. Ο ανταγωνισμός γίνεται βαθμηδόν όλο και πιο σκληρός, αλλά ξάφνου τα πράγματα αλλάζουν ρότα καθώς ο μεγαλοδύναμος έρως παρεμβαίνει και κάνει το θαύμα του. Παίζουν: Στάθης Ψάλτης, Πάνος Μιχαλόπουλος, Σταμάτης Γαρδέλης, Έφη Πίκουλα, Γιώργος Ρήγας, Γιάννης Μποσταντζόγλου, Σοφία Αλιμπέρτη, Καίτη Φίνου. Σκηνοθεσία - Σενάριο: Γιάννης Δαλιανίδης. Διάρκεια: 89'." - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - content: '[]' - }) - - expect(results).toMatchObject([]) -}) +const { parser, url, request } = require('./digea.gr.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-01-17', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '1100', + xmltv_id: 'AlphaTV.gr' +} + +it('can generate valid url', () => { + expect(url).toBe('https://www.digea.gr/el/api/epg/get-events') +}) + +it('can generate valid request method', () => { + expect(request.method).toBe('POST') +}) + +it('can generate valid request headers', () => { + expect(request.headers).toMatchObject({ + 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8' + }) +}) + +it('can generate valid request data', () => { + const data = request.data({ date }) + + expect(data.get('action')).toBe('get_events') + expect(data.get('date')).toBe('2025-1-17') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') + let results = parser({ content, channel }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(19) + expect(results[0]).toMatchObject({ + start: '2025-01-16T23:30:00.000Z', + stop: '2025-01-17T01:30:00.000Z', + title: '[K12] Το Ξεκαθάρισμα (A Score To Settle)', + description: + "Περιπέτεια αμερικανικής παραγωγής 2019 [Το πρόγραμμα περιέχει σκηνές σεξουαλικές, βίας, χρήσης ναρκωτικών κι άλλων εξαρτησιογόνων ουσιών και απρεπή εκφορά λόγου]. Ο Φρανκ απελευθερώνεται από τη φυλακή πολλά χρόνια μετά αφού κατηγορήθηκε για ένα έγκλημα που δεν διέπραξε. Τώρα, ελεύθερος, ξεκινά μια πορεία εκδίκησης εναντίον των ανθρώπων των οποίων οι πράξεις τον έστειλαν στη φυλακή. Ηθοποιοί: Νίκολας Κέιτζ, Μπέντζαμιν Μπρατ, Νόα Λε Γκρος, Καρολίνα Γουίντρα. Σενάριο: Σον Κου, Τζον Νιούμαν. Σκηνοθεσία: Σον Κου. Διάρκεια: 94'. " + }) + expect(results[18]).toMatchObject({ + start: '2025-01-17T21:30:00.000Z', + stop: '2025-01-17T23:30:00.000Z', + title: '[K8] Βασικά Καλησπέρα Σας', + description: + "Κωμωδία ελληνικής παραγωγής 1982. Δύο πειρατικοί ραδιοσταθμοί, εκ των οποίων ο ένας βάζει λαϊκά άσματα και ο άλλος ροκ μουσική, ανταγωνίζονται για την πρωτιά στην ακροαματικότητα. Ο ανταγωνισμός γίνεται βαθμηδόν όλο και πιο σκληρός, αλλά ξάφνου τα πράγματα αλλάζουν ρότα καθώς ο μεγαλοδύναμος έρως παρεμβαίνει και κάνει το θαύμα του. Παίζουν: Στάθης Ψάλτης, Πάνος Μιχαλόπουλος, Σταμάτης Γαρδέλης, Έφη Πίκουλα, Γιώργος Ρήγας, Γιάννης Μποσταντζόγλου, Σοφία Αλιμπέρτη, Καίτη Φίνου. Σκηνοθεσία - Σενάριο: Γιάννης Δαλιανίδης. Διάρκεια: 89'." + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + content: '[]' + }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/digiturk.com.tr/digiturk.com.tr.config.js b/sites/digiturk.com.tr/digiturk.com.tr.config.js index 23d1b55e9..39fdb8c10 100644 --- a/sites/digiturk.com.tr/digiturk.com.tr.config.js +++ b/sites/digiturk.com.tr/digiturk.com.tr.config.js @@ -1,86 +1,86 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -const tz = 'Europe/Istanbul' - -module.exports = { - site: 'digiturk.com.tr', - days: 2, - url({ date }) { - return `https://www.digiturk.com.tr/Ajax/GetTvGuideFromDigiturk?Day=${ - encodeURIComponent(date.format('MM/DD/YYYY')) - }+00%3A00%3A00` - }, - request: { - cache: { - ttl: 24 * 60 * 60 * 1000 // 1 day - } - }, - parser({ content, channel, date }) { - const programs = [] - if (content) { - const $ = cheerio.load(content) - $('.channelDetail').toArray() - .forEach(item => { - const $item = $(item) - const title = $item.find('.tvGuideResult-box-wholeDates-title') - if (title.length) { - const channelId = title.attr('onclick') - if (channelId) { - const site_id = channelId.match(/\s(\d+)\)/)[1] - if (channel.site_id === site_id) { - const startTime = $item.find('.tvGuideResult-box-wholeDates-time-hour').text().trim() - const duration = $item.find('.tvGuideResult-box-wholeDates-time-totalMinute') - .text().trim().match(/\d+/)[0] - const start = dayjs.tz(`${date.format('YYYY-MM-DD')} ${startTime}`, 'YYYY-MM-DD HH:mm', tz) - const stop = start.add(parseInt(duration), 'm') - programs.push({ - title: title.text().trim(), - start, - stop - }) - } - } - } - }) - } - - return programs - }, - async channels() { - const channels = {} - const axios = require('axios') - const data = await axios - .get(this.url({ date: dayjs() })) - .then(r => r.data) - .catch(console.error) - - const $ = cheerio.load(data) - $('.channelContent').toArray() - .forEach(el => { - const item = $(el) - const channelId = item.find('.channelDetail .tvGuideResult-box-wholeDates-title') - .first() - .attr('onclick') - if (channelId) { - const site_id = channelId.match(/\s(\d+)\)/)[1] - if (channels[site_id] === undefined) { - channels[site_id] = { - lang: 'tr', - site_id, - name: item.find('#channelID').val() - } - } - } - }) - - return Object.values(channels) - } -} +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +const tz = 'Europe/Istanbul' + +module.exports = { + site: 'digiturk.com.tr', + days: 2, + url({ date }) { + return `https://www.digiturk.com.tr/Ajax/GetTvGuideFromDigiturk?Day=${ + encodeURIComponent(date.format('MM/DD/YYYY')) + }+00%3A00%3A00` + }, + request: { + cache: { + ttl: 24 * 60 * 60 * 1000 // 1 day + } + }, + parser({ content, channel, date }) { + const programs = [] + if (content) { + const $ = cheerio.load(content) + $('.channelDetail').toArray() + .forEach(item => { + const $item = $(item) + const title = $item.find('.tvGuideResult-box-wholeDates-title') + if (title.length) { + const channelId = title.attr('onclick') + if (channelId) { + const site_id = channelId.match(/\s(\d+)\)/)[1] + if (channel.site_id === site_id) { + const startTime = $item.find('.tvGuideResult-box-wholeDates-time-hour').text().trim() + const duration = $item.find('.tvGuideResult-box-wholeDates-time-totalMinute') + .text().trim().match(/\d+/)[0] + const start = dayjs.tz(`${date.format('YYYY-MM-DD')} ${startTime}`, 'YYYY-MM-DD HH:mm', tz) + const stop = start.add(parseInt(duration), 'm') + programs.push({ + title: title.text().trim(), + start, + stop + }) + } + } + } + }) + } + + return programs + }, + async channels() { + const channels = {} + const axios = require('axios') + const data = await axios + .get(this.url({ date: dayjs() })) + .then(r => r.data) + .catch(console.error) + + const $ = cheerio.load(data) + $('.channelContent').toArray() + .forEach(el => { + const item = $(el) + const channelId = item.find('.channelDetail .tvGuideResult-box-wholeDates-title') + .first() + .attr('onclick') + if (channelId) { + const site_id = channelId.match(/\s(\d+)\)/)[1] + if (channels[site_id] === undefined) { + channels[site_id] = { + lang: 'tr', + site_id, + name: item.find('#channelID').val() + } + } + } + }) + + return Object.values(channels) + } +} diff --git a/sites/digiturk.com.tr/digiturk.com.tr.test.js b/sites/digiturk.com.tr/digiturk.com.tr.test.js index 9d0f0b22f..1086ee526 100644 --- a/sites/digiturk.com.tr/digiturk.com.tr.test.js +++ b/sites/digiturk.com.tr/digiturk.com.tr.test.js @@ -1,48 +1,48 @@ -const { parser, url } = require('./digiturk.com.tr.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-01-12', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '351', - xmltv_id: 'Nickelodeon.tr' -} - -it('can generate valid url', () => { - const result = url({ date, channel }) - expect(result).toBe( - 'https://www.digiturk.com.tr/Ajax/GetTvGuideFromDigiturk?Day=01%2F12%2F2025+00%3A00%3A00' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.join(__dirname, '__data__', 'content.html')) - const results = parser({ content, channel, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(57) - expect(results[0]).toMatchObject({ - start: '2025-01-11T21:00:00.000Z', - stop: '2025-01-11T21:25:00.000Z', - title: 'Sünger Bob Kare Pantolon' - }) - expect(results[56]).toMatchObject({ - start: '2025-01-12T17:40:00.000Z', - stop: '2025-01-12T18:00:00.000Z', - title: 'Casagrande Ailesi' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ content: '', channel, date }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./digiturk.com.tr.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-01-12', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '351', + xmltv_id: 'Nickelodeon.tr' +} + +it('can generate valid url', () => { + const result = url({ date, channel }) + expect(result).toBe( + 'https://www.digiturk.com.tr/Ajax/GetTvGuideFromDigiturk?Day=01%2F12%2F2025+00%3A00%3A00' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.join(__dirname, '__data__', 'content.html')) + const results = parser({ content, channel, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(57) + expect(results[0]).toMatchObject({ + start: '2025-01-11T21:00:00.000Z', + stop: '2025-01-11T21:25:00.000Z', + title: 'Sünger Bob Kare Pantolon' + }) + expect(results[56]).toMatchObject({ + start: '2025-01-12T17:40:00.000Z', + stop: '2025-01-12T18:00:00.000Z', + title: 'Casagrande Ailesi' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ content: '', channel, date }) + expect(result).toMatchObject([]) +}) diff --git a/sites/directv.com.ar/directv.com.ar.config.js b/sites/directv.com.ar/directv.com.ar.config.js index a483cbb29..9918e2911 100644 --- a/sites/directv.com.ar/directv.com.ar.config.js +++ b/sites/directv.com.ar/directv.com.ar.config.js @@ -1,100 +1,100 @@ -process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0 -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'directv.com.ar', - days: 2, - url: 'https://www.directv.com.ar/guia/ChannelDetail.aspx/GetProgramming', - request: { - method: 'POST', - headers: { - Cookie: 'PGCSS=16; PGLang=S; PGCulture=es-AR;', - Accept: '*/*', - 'Accept-Language': 'es-419,es;q=0.9', - Connection: 'keep-alive', - 'Content-Type': 'application/json; charset=UTF-8', - Origin: 'https://www.directv.com.ar', - Referer: 'https://www.directv.com.ar/guia/ChannelDetail.aspx?id=1740&name=TLCHD', - 'Sec-Fetch-Dest': 'empty', - 'Sec-Fetch-Mode': 'cors', - 'Sec-Fetch-Site': 'same-origin', - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36', - 'sec-ch-ua': '"Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114"', - 'sec-ch-ua-mobile': '?0', - 'sec-ch-ua-platform': '"Windows"' - }, - data({ channel, date }) { - const [channelNum, channelName] = channel.site_id.split('#') - - return { - filterParameters: { - day: date.date(), - time: 0, - minute: 0, - month: date.month() + 1, - year: date.year(), - offSetValue: 0, - homeScreenFilter: '', - filtersScreenFilters: [''], - isHd: '', - isChannelDetails: 'Y', - channelNum, - channelName: channelName.replace('&', '&') - } - } - } - }, - parser({ content, channel }) { - let programs = [] - const items = parseItems(content, channel) - items.forEach(item => { - programs.push({ - title: item.title, - description: item.description, - rating: parseRating(item), - start: parseStart(item), - stop: parseStop(item) - }) - }) - - return programs - } -} - -function parseRating(item) { - return item.rating - ? { - system: 'MPA', - value: item.rating - } - : null -} - -function parseStart(item) { - return dayjs.tz(item.startTimeString, 'M/D/YYYY h:mm:ss A', 'America/Argentina/Buenos_Aires') -} - -function parseStop(item) { - return dayjs.tz(item.endTimeString, 'M/D/YYYY h:mm:ss A', 'America/Argentina/Buenos_Aires') -} - -function parseItems(content, channel) { - if (!content) return [] - let [ChannelNumber, ChannelName] = channel.site_id.split('#') - ChannelName = ChannelName.replace('&', '&') - const data = JSON.parse(content) - if (!data || !Array.isArray(data.d)) return [] - const channelData = data.d.find( - c => c.ChannelNumber == ChannelNumber && c.ChannelName === ChannelName - ) - - return channelData && Array.isArray(channelData.ProgramList) ? channelData.ProgramList : [] -} +process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0 +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'directv.com.ar', + days: 2, + url: 'https://www.directv.com.ar/guia/ChannelDetail.aspx/GetProgramming', + request: { + method: 'POST', + headers: { + Cookie: 'PGCSS=16; PGLang=S; PGCulture=es-AR;', + Accept: '*/*', + 'Accept-Language': 'es-419,es;q=0.9', + Connection: 'keep-alive', + 'Content-Type': 'application/json; charset=UTF-8', + Origin: 'https://www.directv.com.ar', + Referer: 'https://www.directv.com.ar/guia/ChannelDetail.aspx?id=1740&name=TLCHD', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'same-origin', + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36', + 'sec-ch-ua': '"Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"Windows"' + }, + data({ channel, date }) { + const [channelNum, channelName] = channel.site_id.split('#') + + return { + filterParameters: { + day: date.date(), + time: 0, + minute: 0, + month: date.month() + 1, + year: date.year(), + offSetValue: 0, + homeScreenFilter: '', + filtersScreenFilters: [''], + isHd: '', + isChannelDetails: 'Y', + channelNum, + channelName: channelName.replace('&', '&') + } + } + } + }, + parser({ content, channel }) { + let programs = [] + const items = parseItems(content, channel) + items.forEach(item => { + programs.push({ + title: item.title, + description: item.description, + rating: parseRating(item), + start: parseStart(item), + stop: parseStop(item) + }) + }) + + return programs + } +} + +function parseRating(item) { + return item.rating + ? { + system: 'MPA', + value: item.rating + } + : null +} + +function parseStart(item) { + return dayjs.tz(item.startTimeString, 'M/D/YYYY h:mm:ss A', 'America/Argentina/Buenos_Aires') +} + +function parseStop(item) { + return dayjs.tz(item.endTimeString, 'M/D/YYYY h:mm:ss A', 'America/Argentina/Buenos_Aires') +} + +function parseItems(content, channel) { + if (!content) return [] + let [ChannelNumber, ChannelName] = channel.site_id.split('#') + ChannelName = ChannelName.replace('&', '&') + const data = JSON.parse(content) + if (!data || !Array.isArray(data.d)) return [] + const channelData = data.d.find( + c => c.ChannelNumber == ChannelNumber && c.ChannelName === ChannelName + ) + + return channelData && Array.isArray(channelData.ProgramList) ? channelData.ProgramList : [] +} diff --git a/sites/directv.com.uy/directv.com.uy.config.js b/sites/directv.com.uy/directv.com.uy.config.js index c071406c9..f1a828ac4 100644 --- a/sites/directv.com.uy/directv.com.uy.config.js +++ b/sites/directv.com.uy/directv.com.uy.config.js @@ -1,85 +1,85 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'directv.com.uy', - days: 2, - url: 'https://www.directv.com.uy/guia/ChannelDetail.aspx/GetProgramming', - request: { - method: 'POST', - headers: { - 'Content-Type': 'application/json; charset=UTF-8', - Cookie: 'PGCSS=16384; PGLang=S; PGCulture=es-UY;' - }, - data({ channel, date }) { - const [channelNum, channelName] = channel.site_id.split('#') - - return { - filterParameters: { - day: date.date(), - time: 0, - minute: 0, - month: date.month() + 1, - year: date.year(), - offSetValue: 0, - filtersScreenFilters: [''], - isHd: '', - isChannelDetails: 'Y', - channelNum, - channelName: channelName.replace('&', '&') - } - } - } - }, - parser({ content, channel }) { - let programs = [] - const items = parseItems(content, channel) - items.forEach(item => { - programs.push({ - title: item.title, - description: item.description, - rating: parseRating(item), - start: parseStart(item), - stop: parseStop(item) - }) - }) - - return programs - } -} - -function parseRating(item) { - return item.rating - ? { - system: 'MPA', - value: item.rating - } - : null -} - -function parseStart(item) { - return dayjs.tz(item.startTimeString, 'M/D/YYYY h:mm:ss A', 'America/Montevideo') -} - -function parseStop(item) { - return dayjs.tz(item.endTimeString, 'M/D/YYYY h:mm:ss A', 'America/Montevideo') -} - -function parseItems(content, channel) { - if (!content) return [] - let [ChannelNumber, ChannelName] = channel.site_id.split('#') - ChannelName = ChannelName.replace('&', '&') - const data = JSON.parse(content) - if (!data || !Array.isArray(data.d)) return [] - const channelData = data.d.find( - c => c.ChannelNumber == ChannelNumber && c.ChannelName === ChannelName - ) - - return channelData && Array.isArray(channelData.ProgramList) ? channelData.ProgramList : [] -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'directv.com.uy', + days: 2, + url: 'https://www.directv.com.uy/guia/ChannelDetail.aspx/GetProgramming', + request: { + method: 'POST', + headers: { + 'Content-Type': 'application/json; charset=UTF-8', + Cookie: 'PGCSS=16384; PGLang=S; PGCulture=es-UY;' + }, + data({ channel, date }) { + const [channelNum, channelName] = channel.site_id.split('#') + + return { + filterParameters: { + day: date.date(), + time: 0, + minute: 0, + month: date.month() + 1, + year: date.year(), + offSetValue: 0, + filtersScreenFilters: [''], + isHd: '', + isChannelDetails: 'Y', + channelNum, + channelName: channelName.replace('&', '&') + } + } + } + }, + parser({ content, channel }) { + let programs = [] + const items = parseItems(content, channel) + items.forEach(item => { + programs.push({ + title: item.title, + description: item.description, + rating: parseRating(item), + start: parseStart(item), + stop: parseStop(item) + }) + }) + + return programs + } +} + +function parseRating(item) { + return item.rating + ? { + system: 'MPA', + value: item.rating + } + : null +} + +function parseStart(item) { + return dayjs.tz(item.startTimeString, 'M/D/YYYY h:mm:ss A', 'America/Montevideo') +} + +function parseStop(item) { + return dayjs.tz(item.endTimeString, 'M/D/YYYY h:mm:ss A', 'America/Montevideo') +} + +function parseItems(content, channel) { + if (!content) return [] + let [ChannelNumber, ChannelName] = channel.site_id.split('#') + ChannelName = ChannelName.replace('&', '&') + const data = JSON.parse(content) + if (!data || !Array.isArray(data.d)) return [] + const channelData = data.d.find( + c => c.ChannelNumber == ChannelNumber && c.ChannelName === ChannelName + ) + + return channelData && Array.isArray(channelData.ProgramList) ? channelData.ProgramList : [] +} diff --git a/sites/directv.com.uy/directv.com.uy.test.js b/sites/directv.com.uy/directv.com.uy.test.js index 8ee6a26dd..3d1634d49 100644 --- a/sites/directv.com.uy/directv.com.uy.test.js +++ b/sites/directv.com.uy/directv.com.uy.test.js @@ -1,76 +1,76 @@ -const { parser, url, request } = require('./directv.com.uy.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-08-29', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '184#VTV', - xmltv_id: 'VTV.uy' -} - -it('can generate valid url', () => { - expect(url).toBe('https://www.directv.com.uy/guia/ChannelDetail.aspx/GetProgramming') -}) - -it('can generate valid request method', () => { - expect(request.method).toBe('POST') -}) - -it('can generate valid request headers', () => { - expect(request.headers).toMatchObject({ - 'Content-Type': 'application/json; charset=UTF-8', - Cookie: 'PGCSS=16384; PGLang=S; PGCulture=es-UY;' - }) -}) - -it('can generate valid request data', () => { - expect(request.data({ channel, date })).toMatchObject({ - filterParameters: { - day: 29, - time: 0, - minute: 0, - month: 8, - year: 2022, - offSetValue: 0, - filtersScreenFilters: [''], - isHd: '', - isChannelDetails: 'Y', - channelNum: '184', - channelName: 'VTV' - } - }) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const results = parser({ content, channel }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2022-08-29T03:00:00.000Z', - stop: '2022-08-29T05:00:00.000Z', - title: 'Peñarol vs. Danubio : Fútbol Uruguayo Primera División - Peñarol vs. Danubio', - description: - 'Jornada 5 del Torneo Clausura 2022. Peñarol recibe a Danubio en el estadio Campeón del Siglo. Los carboneros llevan 3 partidos sin caer (2PG 1PE), mientras que los franjeados acumulan 6 juegos sin derrotas (4PG 2PE).', - rating: { - system: 'MPA', - value: 'NR' - } - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: '', - channel - }) - expect(result).toMatchObject([]) -}) +const { parser, url, request } = require('./directv.com.uy.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-08-29', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '184#VTV', + xmltv_id: 'VTV.uy' +} + +it('can generate valid url', () => { + expect(url).toBe('https://www.directv.com.uy/guia/ChannelDetail.aspx/GetProgramming') +}) + +it('can generate valid request method', () => { + expect(request.method).toBe('POST') +}) + +it('can generate valid request headers', () => { + expect(request.headers).toMatchObject({ + 'Content-Type': 'application/json; charset=UTF-8', + Cookie: 'PGCSS=16384; PGLang=S; PGCulture=es-UY;' + }) +}) + +it('can generate valid request data', () => { + expect(request.data({ channel, date })).toMatchObject({ + filterParameters: { + day: 29, + time: 0, + minute: 0, + month: 8, + year: 2022, + offSetValue: 0, + filtersScreenFilters: [''], + isHd: '', + isChannelDetails: 'Y', + channelNum: '184', + channelName: 'VTV' + } + }) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const results = parser({ content, channel }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2022-08-29T03:00:00.000Z', + stop: '2022-08-29T05:00:00.000Z', + title: 'Peñarol vs. Danubio : Fútbol Uruguayo Primera División - Peñarol vs. Danubio', + description: + 'Jornada 5 del Torneo Clausura 2022. Peñarol recibe a Danubio en el estadio Campeón del Siglo. Los carboneros llevan 3 partidos sin caer (2PG 1PE), mientras que los franjeados acumulan 6 juegos sin derrotas (4PG 2PE).', + rating: { + system: 'MPA', + value: 'NR' + } + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: '', + channel + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/directv.com/directv.com.config.js b/sites/directv.com/directv.com.config.js index 5d8c924e3..9eab97500 100644 --- a/sites/directv.com/directv.com.config.js +++ b/sites/directv.com/directv.com.config.js @@ -1,118 +1,118 @@ -const cheerio = require('cheerio') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') - -dayjs.extend(utc) - -module.exports = { - site: 'directv.com', - days: 2, - request: { - cache: { - ttl: 60 * 60 * 1000 // 1 hour - }, - headers: { - 'Accept-Language': 'en-US,en;q=0.5', - Connection: 'keep-alive' - } - }, - url({ date, channel }) { - const [channelId, childId] = channel.site_id.split('#') - return `https://www.directv.com/json/channelschedule?channels=${channelId}&startTime=${date.format()}&hours=24&chId=${childId}` - }, - async parser({ content, channel }) { - const programs = [] - const items = parseItems(content, channel) - for (let item of items) { - if (item.programID === '-1') continue - const detail = await loadProgramDetail(item.programID) - const start = parseStart(item) - const stop = start.add(item.duration, 'm') - programs.push({ - title: item.title, - sub_title: item.episodeTitle, - description: parseDescription(detail), - rating: parseRating(item), - date: parseYear(detail), - category: item.subcategoryList, - season: item.seasonNumber, - episode: item.episodeNumber, - image: parseImage(item), - start, - stop - }) - } - - return programs - }, - async channels() { - const codes = [10001] - - let channels = [] - for (let code of codes) { - const html = await axios - .get('https://www.directv.com/guide', { - headers: { - cookie: `dtve-prospect-zip=${code}` - } - }) - .then(r => r.data) - .catch(console.log) - - const $ = cheerio.load(html) - const script = $('#dtvClientData').html() - const [, json] = script.match(/var dtvClientData = (.*);/) || [null, null] - const data = JSON.parse(json) - - data.guideData.channels.forEach(item => { - channels.push({ - lang: 'en', - site_id: item.chNum, - name: item.chName - }) - }) - } - - return channels - } -} - -function parseDescription(detail) { - return detail ? detail.description : null -} -function parseYear(detail) { - return detail ? detail.releaseYear : null -} -function parseRating(item) { - return item.rating - ? { - system: 'MPA', - value: item.rating - } - : null -} -function parseImage(item) { - return item.primaryImageUrl ? `https://www.directv.com${item.primaryImageUrl}` : null -} -function loadProgramDetail(programID) { - return axios - .get(`https://www.directv.com/json/program/flip/${programID}`) - .then(r => r.data) - .then(d => d.programDetail) - .catch(console.err) -} - -function parseStart(item) { - return dayjs.utc(item.airTime) -} - -function parseItems(content, channel) { - const data = JSON.parse(content) - if (!data) return [] - if (!Array.isArray(data.schedule)) return [] - - const [, childId] = channel.site_id.split('#') - const channelData = data.schedule.find(i => i.chId == childId) - return channelData.schedules && Array.isArray(channelData.schedules) ? channelData.schedules : [] -} +const cheerio = require('cheerio') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') + +dayjs.extend(utc) + +module.exports = { + site: 'directv.com', + days: 2, + request: { + cache: { + ttl: 60 * 60 * 1000 // 1 hour + }, + headers: { + 'Accept-Language': 'en-US,en;q=0.5', + Connection: 'keep-alive' + } + }, + url({ date, channel }) { + const [channelId, childId] = channel.site_id.split('#') + return `https://www.directv.com/json/channelschedule?channels=${channelId}&startTime=${date.format()}&hours=24&chId=${childId}` + }, + async parser({ content, channel }) { + const programs = [] + const items = parseItems(content, channel) + for (let item of items) { + if (item.programID === '-1') continue + const detail = await loadProgramDetail(item.programID) + const start = parseStart(item) + const stop = start.add(item.duration, 'm') + programs.push({ + title: item.title, + sub_title: item.episodeTitle, + description: parseDescription(detail), + rating: parseRating(item), + date: parseYear(detail), + category: item.subcategoryList, + season: item.seasonNumber, + episode: item.episodeNumber, + image: parseImage(item), + start, + stop + }) + } + + return programs + }, + async channels() { + const codes = [10001] + + let channels = [] + for (let code of codes) { + const html = await axios + .get('https://www.directv.com/guide', { + headers: { + cookie: `dtve-prospect-zip=${code}` + } + }) + .then(r => r.data) + .catch(console.log) + + const $ = cheerio.load(html) + const script = $('#dtvClientData').html() + const [, json] = script.match(/var dtvClientData = (.*);/) || [null, null] + const data = JSON.parse(json) + + data.guideData.channels.forEach(item => { + channels.push({ + lang: 'en', + site_id: item.chNum, + name: item.chName + }) + }) + } + + return channels + } +} + +function parseDescription(detail) { + return detail ? detail.description : null +} +function parseYear(detail) { + return detail ? detail.releaseYear : null +} +function parseRating(item) { + return item.rating + ? { + system: 'MPA', + value: item.rating + } + : null +} +function parseImage(item) { + return item.primaryImageUrl ? `https://www.directv.com${item.primaryImageUrl}` : null +} +function loadProgramDetail(programID) { + return axios + .get(`https://www.directv.com/json/program/flip/${programID}`) + .then(r => r.data) + .then(d => d.programDetail) + .catch(console.err) +} + +function parseStart(item) { + return dayjs.utc(item.airTime) +} + +function parseItems(content, channel) { + const data = JSON.parse(content) + if (!data) return [] + if (!Array.isArray(data.schedule)) return [] + + const [, childId] = channel.site_id.split('#') + const channelData = data.schedule.find(i => i.chId == childId) + return channelData.schedules && Array.isArray(channelData.schedules) ? channelData.schedules : [] +} diff --git a/sites/directv.com/directv.com.test.js b/sites/directv.com/directv.com.test.js index a4abfa0ca..6684a18d1 100644 --- a/sites/directv.com/directv.com.test.js +++ b/sites/directv.com/directv.com.test.js @@ -1,96 +1,96 @@ -const { parser, url } = require('./directv.com.config.js') -const fs = require('fs') -const path = require('path') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -const date = dayjs.utc('2023-01-15', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '249#249', - xmltv_id: 'ComedyCentralEast.us' -} - -it('can generate valid url', () => { - const result = url({ date, channel }) - expect(result).toBe( - 'https://www.directv.com/json/channelschedule?channels=249&startTime=2023-01-15T00:00:00Z&hours=24&chId=249' - ) -}) - -it('can parse response', done => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - - axios.get.mockImplementation(url => { - if (url === 'https://www.directv.com/json/program/flip/MV001173520000') { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program1.json'))) - }) - } else if (url === 'https://www.directv.com/json/program/flip/EP002298270445') { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program2.json'))) - }) - } else { - return Promise.resolve({ data: '' }) - } - }) - - parser({ content, channel }) - .then(result => { - result = result.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2023-01-14T23:00:00.000Z', - stop: '2023-01-15T01:00:00.000Z', - title: 'Men in Black II', - description: - 'Kay (Tommy Lee Jones) and Jay (Will Smith) reunite to provide our best line of defense against a seductress who levels the toughest challenge yet to the MIBs mission statement: protecting the earth from the scum of the universe. While investigating a routine crime, Jay uncovers a plot masterminded by Serleena (Boyle), a Kylothian monster who disguises herself as a lingerie model. When Serleena takes the MIB building hostage, there is only one person Jay can turn to -- his former MIB partner.', - date: '2002', - image: 'https://www.directv.com/db_photos/movies/AllPhotosAPGI/29160/29160_aa.jpg', - category: ['Comedy', 'Movies Anywhere', 'Action/Adventure', 'Science Fiction'], - rating: { - system: 'MPA', - value: 'TV14' - } - }, - { - start: '2023-01-15T06:00:00.000Z', - stop: '2023-01-15T06:30:00.000Z', - title: 'South Park', - sub_title: 'Goth Kids 3: Dawn of the Posers', - description: 'The goth kids are sent to a camp for troubled children.', - image: - 'https://www.directv.com/db_photos/showcards/v5/AllPhotos/184338/p184338_b_v5_aa.jpg', - category: ['Series', 'Animation', 'Comedy'], - season: 17, - episode: 4, - rating: { - system: 'MPA', - value: 'TVMA' - } - } - ]) - done() - }) - .catch(done) -}) - -it('can handle empty guide', done => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/no-content.json')) - parser({ content, channel }) - .then(result => { - expect(result).toMatchObject([]) - done() - }) - .catch(done) -}) +const { parser, url } = require('./directv.com.config.js') +const fs = require('fs') +const path = require('path') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.utc('2023-01-15', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '249#249', + xmltv_id: 'ComedyCentralEast.us' +} + +it('can generate valid url', () => { + const result = url({ date, channel }) + expect(result).toBe( + 'https://www.directv.com/json/channelschedule?channels=249&startTime=2023-01-15T00:00:00Z&hours=24&chId=249' + ) +}) + +it('can parse response', done => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + + axios.get.mockImplementation(url => { + if (url === 'https://www.directv.com/json/program/flip/MV001173520000') { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program1.json'))) + }) + } else if (url === 'https://www.directv.com/json/program/flip/EP002298270445') { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program2.json'))) + }) + } else { + return Promise.resolve({ data: '' }) + } + }) + + parser({ content, channel }) + .then(result => { + result = result.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2023-01-14T23:00:00.000Z', + stop: '2023-01-15T01:00:00.000Z', + title: 'Men in Black II', + description: + 'Kay (Tommy Lee Jones) and Jay (Will Smith) reunite to provide our best line of defense against a seductress who levels the toughest challenge yet to the MIBs mission statement: protecting the earth from the scum of the universe. While investigating a routine crime, Jay uncovers a plot masterminded by Serleena (Boyle), a Kylothian monster who disguises herself as a lingerie model. When Serleena takes the MIB building hostage, there is only one person Jay can turn to -- his former MIB partner.', + date: '2002', + image: 'https://www.directv.com/db_photos/movies/AllPhotosAPGI/29160/29160_aa.jpg', + category: ['Comedy', 'Movies Anywhere', 'Action/Adventure', 'Science Fiction'], + rating: { + system: 'MPA', + value: 'TV14' + } + }, + { + start: '2023-01-15T06:00:00.000Z', + stop: '2023-01-15T06:30:00.000Z', + title: 'South Park', + sub_title: 'Goth Kids 3: Dawn of the Posers', + description: 'The goth kids are sent to a camp for troubled children.', + image: + 'https://www.directv.com/db_photos/showcards/v5/AllPhotos/184338/p184338_b_v5_aa.jpg', + category: ['Series', 'Animation', 'Comedy'], + season: 17, + episode: 4, + rating: { + system: 'MPA', + value: 'TVMA' + } + } + ]) + done() + }) + .catch(done) +}) + +it('can handle empty guide', done => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/no-content.json')) + parser({ content, channel }) + .then(result => { + expect(result).toMatchObject([]) + done() + }) + .catch(done) +}) diff --git a/sites/dishtv.in/dishtv.in.config.js b/sites/dishtv.in/dishtv.in.config.js index 250aa3b85..b803e87c5 100644 --- a/sites/dishtv.in/dishtv.in.config.js +++ b/sites/dishtv.in/dishtv.in.config.js @@ -1,167 +1,167 @@ -const axios = require('axios') -const dayjs = require('dayjs') - -let authToken - -module.exports = { - site: 'dishtv.in', - days: 2, - url: 'https://epg.mysmartstick.com/dishtv/api/v1/epg/entities/programs', - request: { - method: 'POST', - async headers() { - await fetchToken() - - return { - Authorization: authToken - } - }, - data({ channel, date }) { - return { - allowPastEvents: true, - channelid: channel.site_id, - date: date.format('DD/MM/YYYY') - } - } - }, - parser: ({ content }) => { - const programs = [] - const items = parseItems(content) - items.forEach(item => { - programs.push({ - title: parseTitle(item), - description: parseDescription(item), - category: parseCategory(item), - actors: item.credits.actors, - directors: item.credits.directors, - producers: item.credits.producers, - date: item.productionyear, - icon: parseIcon(item), - image: parseImage(item), - episode: parseEpisode(item), - start: dayjs(item.start), - stop: dayjs(item.stop) - }) - }) - - return programs - }, - async channels() { - await fetchToken() - - const totalPages = await fetchPages() - - const queue = Array.from(Array(totalPages).keys()).map(i => { - const data = new FormData() - data.append('pageNum', i + 1) - - return { - method: 'post', - url: 'https://www.dishtv.in/services/epg/channels', - data, - headers: { - 'authorization-token': authToken - } - } - }) - - const channels = [] - for (let item of queue) { - const data = await axios(item) - .then(r => r.data) - .catch(console.error) - - data.programDetailsByChannel.forEach(channel => { - channels.push({ - lang: 'en', - site_id: channel.channelid, - name: channel.channelname - }) - }) - } - - return channels - } -} - -function parseTitle(item) { - return Object.values(item.regional) - .map(region => ({ - lang: region.languagecode, - value: region.title - })) - .filter(i => Boolean(i.value)) -} - -function parseDescription(item) { - return Object.values(item.regional) - .map(region => ({ - lang: region.languagecode, - value: region.desc - })) - .filter(i => Boolean(i.value)) -} - -function parseCategory(item) { - return Object.values(item.regional) - .map(region => ({ - lang: region.languagecode, - value: region.genre - })) - .filter(i => Boolean(i.value)) -} - -function parseEpisode(item) { - return item['episode-num'] ? parseInt(item['episode-num']) : null -} - -function parseIcon(item) { - return item.programmeurl || null -} - -function parseImage(item) { - return item?.images?.landscape?.['1280x720'] ? item.images.landscape['1280x720'] : null -} - -function parseItems(content) { - try { - const data = JSON.parse(content) - - return Array.isArray(data) ? data : [] - } catch { - return [] - } -} - -async function fetchToken() { - if (authToken) return - - const data = await axios - .post('https://www.dishtv.in/services/epg/signin', null, { - headers: { - 'sec-fetch-dest': 'empty', - 'sec-fetch-mode': 'cors', - 'sec-fetch-site': 'same-origin', - 'x-requested-with': 'XMLHttpRequest', - Referer: 'https://www.dishtv.in/channel-guide.html' - } - }) - .then(r => r.data) - .catch(console.error) - - authToken = data.token -} - -async function fetchPages() { - const formData = new FormData() - formData.append('pageNum', 1) - - const data = await axios - .post('https://www.dishtv.in/services/epg/channels', formData, { - headers: { 'authorization-token': authToken } - }) - .then(r => r.data) - .catch(console.error) - - return data.totalPages ? parseInt(data.totalPages) : 0 -} +const axios = require('axios') +const dayjs = require('dayjs') + +let authToken + +module.exports = { + site: 'dishtv.in', + days: 2, + url: 'https://epg.mysmartstick.com/dishtv/api/v1/epg/entities/programs', + request: { + method: 'POST', + async headers() { + await fetchToken() + + return { + Authorization: authToken + } + }, + data({ channel, date }) { + return { + allowPastEvents: true, + channelid: channel.site_id, + date: date.format('DD/MM/YYYY') + } + } + }, + parser: ({ content }) => { + const programs = [] + const items = parseItems(content) + items.forEach(item => { + programs.push({ + title: parseTitle(item), + description: parseDescription(item), + category: parseCategory(item), + actors: item.credits.actors, + directors: item.credits.directors, + producers: item.credits.producers, + date: item.productionyear, + icon: parseIcon(item), + image: parseImage(item), + episode: parseEpisode(item), + start: dayjs(item.start), + stop: dayjs(item.stop) + }) + }) + + return programs + }, + async channels() { + await fetchToken() + + const totalPages = await fetchPages() + + const queue = Array.from(Array(totalPages).keys()).map(i => { + const data = new FormData() + data.append('pageNum', i + 1) + + return { + method: 'post', + url: 'https://www.dishtv.in/services/epg/channels', + data, + headers: { + 'authorization-token': authToken + } + } + }) + + const channels = [] + for (let item of queue) { + const data = await axios(item) + .then(r => r.data) + .catch(console.error) + + data.programDetailsByChannel.forEach(channel => { + channels.push({ + lang: 'en', + site_id: channel.channelid, + name: channel.channelname + }) + }) + } + + return channels + } +} + +function parseTitle(item) { + return Object.values(item.regional) + .map(region => ({ + lang: region.languagecode, + value: region.title + })) + .filter(i => Boolean(i.value)) +} + +function parseDescription(item) { + return Object.values(item.regional) + .map(region => ({ + lang: region.languagecode, + value: region.desc + })) + .filter(i => Boolean(i.value)) +} + +function parseCategory(item) { + return Object.values(item.regional) + .map(region => ({ + lang: region.languagecode, + value: region.genre + })) + .filter(i => Boolean(i.value)) +} + +function parseEpisode(item) { + return item['episode-num'] ? parseInt(item['episode-num']) : null +} + +function parseIcon(item) { + return item.programmeurl || null +} + +function parseImage(item) { + return item?.images?.landscape?.['1280x720'] ? item.images.landscape['1280x720'] : null +} + +function parseItems(content) { + try { + const data = JSON.parse(content) + + return Array.isArray(data) ? data : [] + } catch { + return [] + } +} + +async function fetchToken() { + if (authToken) return + + const data = await axios + .post('https://www.dishtv.in/services/epg/signin', null, { + headers: { + 'sec-fetch-dest': 'empty', + 'sec-fetch-mode': 'cors', + 'sec-fetch-site': 'same-origin', + 'x-requested-with': 'XMLHttpRequest', + Referer: 'https://www.dishtv.in/channel-guide.html' + } + }) + .then(r => r.data) + .catch(console.error) + + authToken = data.token +} + +async function fetchPages() { + const formData = new FormData() + formData.append('pageNum', 1) + + const data = await axios + .post('https://www.dishtv.in/services/epg/channels', formData, { + headers: { 'authorization-token': authToken } + }) + .then(r => r.data) + .catch(console.error) + + return data.totalPages ? parseInt(data.totalPages) : 0 +} diff --git a/sites/dishtv.in/dishtv.in.test.js b/sites/dishtv.in/dishtv.in.test.js index 2137d72ed..fd8eddac6 100644 --- a/sites/dishtv.in/dishtv.in.test.js +++ b/sites/dishtv.in/dishtv.in.test.js @@ -1,140 +1,140 @@ -const { parser, url, request } = require('./dishtv.in.config.js') -const fs = require('fs') -const path = require('path') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -axios.post.mockImplementation((url, data, params) => { - if ( - url === 'https://www.dishtv.in/services/epg/signin' && - data === null && - JSON.stringify(params) === - JSON.stringify({ - headers: { - 'sec-fetch-dest': 'empty', - 'sec-fetch-mode': 'cors', - 'sec-fetch-site': 'same-origin', - 'x-requested-with': 'XMLHttpRequest', - Referer: 'https://www.dishtv.in/channel-guide.html' - } - }) - ) { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/session.json')) - - return Promise.resolve({ - data: JSON.parse(content) - }) - } else { - return Promise.resolve({ - data: '' - }) - } -}) - -const date = dayjs.utc('2025-01-26', 'YYYY-MM-DD').startOf('d') -const channel = { site_id: '142639', xmltv_id: 'AndpriveHD.in' } - -it('can generate valid url', () => { - expect(url).toBe('https://epg.mysmartstick.com/dishtv/api/v1/epg/entities/programs') -}) - -it('can generate valid request method', () => { - expect(request.method).toBe('POST') -}) - -it('can generate valid request headers', async () => { - expect(await request.headers()).toMatchObject({ - Authorization: - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnRpZCI6ImRpc2h0di13ZWJzaXRlIiwicGxhdGZvcm0iOiJkaXNodHYiLCJpYXQiOjE3Mzc2ODIxNjEsImV4cCI6MTczNzc2ODU2MX0.sPrYfodVTbf1kJ-wGICDlnH-Yt3J0-mB-M2YROU8v2Q' - }) -}) - -it('can generate valid request data', () => { - expect(request.data({ channel, date })).toMatchObject({ - allowPastEvents: true, - channelid: '142639', - date: '26/01/2025' - }) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - let results = parser({ content }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(16) - expect(results[0]).toMatchObject({ - start: '2025-01-26T00:30:00.000Z', - stop: '2025-01-26T02:05:00.000Z', - title: [ - { lang: 'en', value: 'Train to Busan 2: Peninsula' }, - { lang: 'hi', value: 'ट्रेन टू बुसान 2: पेनीनसुला' }, - { lang: 'ta', value: 'ட்ரெயின் டு பூசன் ப்ரெசென்ட்ஸ்: பெனின்சுலா' }, - { lang: 'te', value: 'ట్రేన్ టు బూసాన్ ప్రజెంట్స్: పెనిన్సులా' } - ], - description: [ - { - lang: 'en', - value: - 'Jung Seok, a former soldier, along with his teammates, sets out on a mission to battle hordes of post-apocalyptic zombies in the Korean peninsula wastelands.' - }, - { - lang: 'hi', - value: - 'एक भूतपूर्व सैनिक जंग सोक अपने साथियों के साथ कोरियाई प्रायद्वीप के बंजर इलाकों में सर्वनाश के बाद की जोंबी से लड़ने के मिशन पर निकलता है।' - }, - { - lang: 'ta', - value: - 'கொரிய தீபகற்பத்தின் தரிசு நிலங்களில் அபோகாலிப்டிக் ஜாம்பிக்களின் கூட்டத்தை எதிர்த்து தன் குழுவுடன் போரிடும் ஜங் சியோக்.' - }, - { - lang: 'te', - value: - 'మాజీ సైనికుడు జంగ్ సియోక్ తన సహచరులతో కలిసి కొరియా ద్వీపకల్పంలో పోస్ట్-అపోకలిప్టిక్ జాంబీలతో యుద్దానికి సిద్దమవుతాడు.' - } - ], - category: [ - { lang: 'en', value: 'Film' }, - { lang: 'hi', value: 'फ़िल्म' }, - { lang: 'ta', value: '??????????' }, - { lang: 'te', value: 'సినిమా' }, - { lang: 'mr', value: 'चित्रपट' } - ], - actors: [ - 'Gang Dong-won', - 'Lee Jung-hyun', - 'Lee Re', - 'Kwon Hae-hyo', - 'John D. Michaels', - 'Kim Min-jae', - 'Kim Doyun', - 'Lee Ye-won', - 'Daniel Joey Albright', - 'Pierce Conran', - 'Geoffrey Giuliano', - 'Milan-Devi LaBrey' - ], - producers: [], - directors: ['Yeon Sang-ho'], - icon: 'https://dtil.tmsimg.com/assets/p17850257_v_h9_al.jpg?lock=880x660', - image: 'https://dtil.tmsimg.com/assets/p17850257_v_h8_am.jpg?lock=1280x720', - date: '2020' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ content: '[]' }) - - expect(results).toMatchObject([]) -}) +const { parser, url, request } = require('./dishtv.in.config.js') +const fs = require('fs') +const path = require('path') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +axios.post.mockImplementation((url, data, params) => { + if ( + url === 'https://www.dishtv.in/services/epg/signin' && + data === null && + JSON.stringify(params) === + JSON.stringify({ + headers: { + 'sec-fetch-dest': 'empty', + 'sec-fetch-mode': 'cors', + 'sec-fetch-site': 'same-origin', + 'x-requested-with': 'XMLHttpRequest', + Referer: 'https://www.dishtv.in/channel-guide.html' + } + }) + ) { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/session.json')) + + return Promise.resolve({ + data: JSON.parse(content) + }) + } else { + return Promise.resolve({ + data: '' + }) + } +}) + +const date = dayjs.utc('2025-01-26', 'YYYY-MM-DD').startOf('d') +const channel = { site_id: '142639', xmltv_id: 'AndpriveHD.in' } + +it('can generate valid url', () => { + expect(url).toBe('https://epg.mysmartstick.com/dishtv/api/v1/epg/entities/programs') +}) + +it('can generate valid request method', () => { + expect(request.method).toBe('POST') +}) + +it('can generate valid request headers', async () => { + expect(await request.headers()).toMatchObject({ + Authorization: + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnRpZCI6ImRpc2h0di13ZWJzaXRlIiwicGxhdGZvcm0iOiJkaXNodHYiLCJpYXQiOjE3Mzc2ODIxNjEsImV4cCI6MTczNzc2ODU2MX0.sPrYfodVTbf1kJ-wGICDlnH-Yt3J0-mB-M2YROU8v2Q' + }) +}) + +it('can generate valid request data', () => { + expect(request.data({ channel, date })).toMatchObject({ + allowPastEvents: true, + channelid: '142639', + date: '26/01/2025' + }) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + let results = parser({ content }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(16) + expect(results[0]).toMatchObject({ + start: '2025-01-26T00:30:00.000Z', + stop: '2025-01-26T02:05:00.000Z', + title: [ + { lang: 'en', value: 'Train to Busan 2: Peninsula' }, + { lang: 'hi', value: 'ट्रेन टू बुसान 2: पेनीनसुला' }, + { lang: 'ta', value: 'ட்ரெயின் டு பூசன் ப்ரெசென்ட்ஸ்: பெனின்சுலா' }, + { lang: 'te', value: 'ట్రేన్ టు బూసాన్ ప్రజెంట్స్: పెనిన్సులా' } + ], + description: [ + { + lang: 'en', + value: + 'Jung Seok, a former soldier, along with his teammates, sets out on a mission to battle hordes of post-apocalyptic zombies in the Korean peninsula wastelands.' + }, + { + lang: 'hi', + value: + 'एक भूतपूर्व सैनिक जंग सोक अपने साथियों के साथ कोरियाई प्रायद्वीप के बंजर इलाकों में सर्वनाश के बाद की जोंबी से लड़ने के मिशन पर निकलता है।' + }, + { + lang: 'ta', + value: + 'கொரிய தீபகற்பத்தின் தரிசு நிலங்களில் அபோகாலிப்டிக் ஜாம்பிக்களின் கூட்டத்தை எதிர்த்து தன் குழுவுடன் போரிடும் ஜங் சியோக்.' + }, + { + lang: 'te', + value: + 'మాజీ సైనికుడు జంగ్ సియోక్ తన సహచరులతో కలిసి కొరియా ద్వీపకల్పంలో పోస్ట్-అపోకలిప్టిక్ జాంబీలతో యుద్దానికి సిద్దమవుతాడు.' + } + ], + category: [ + { lang: 'en', value: 'Film' }, + { lang: 'hi', value: 'फ़िल्म' }, + { lang: 'ta', value: '??????????' }, + { lang: 'te', value: 'సినిమా' }, + { lang: 'mr', value: 'चित्रपट' } + ], + actors: [ + 'Gang Dong-won', + 'Lee Jung-hyun', + 'Lee Re', + 'Kwon Hae-hyo', + 'John D. Michaels', + 'Kim Min-jae', + 'Kim Doyun', + 'Lee Ye-won', + 'Daniel Joey Albright', + 'Pierce Conran', + 'Geoffrey Giuliano', + 'Milan-Devi LaBrey' + ], + producers: [], + directors: ['Yeon Sang-ho'], + icon: 'https://dtil.tmsimg.com/assets/p17850257_v_h9_al.jpg?lock=880x660', + image: 'https://dtil.tmsimg.com/assets/p17850257_v_h8_am.jpg?lock=1280x720', + date: '2020' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ content: '[]' }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/dna.fi/dna.fi.config.js b/sites/dna.fi/dna.fi.config.js index 228254a21..dae07a222 100644 --- a/sites/dna.fi/dna.fi.config.js +++ b/sites/dna.fi/dna.fi.config.js @@ -1,99 +1,99 @@ -const axios = require('axios') -const dayjs = require('dayjs') - -module.exports = { - site: 'dna.fi', - days: 2, - url({ date, channel }) { - const beginTimestamp = date.add(2, 'h').valueOf() - const endTimestamp = date.add(1, 'd').add(2, 'h').subtract(1, 's').valueOf() - - return `https://mts-pro-envoy-vip.dna.fi/hbx/api/pub/xrtv/g/media?q=channel:${channel.site_id}&q=profile:pr&q=start-interval:${beginTimestamp}/${endTimestamp}` - }, - parser({ content, date }) { - let programs = [] - let items = parseItems(content, date) - items.forEach(item => { - const data = item?._embedded?.['xrtv:meta']?.data - programs.push({ - title: data?.title, - subtitle: data?.episode_title, - description: data?.description, - season: data?.season_number, - episode: data?.episode_number, - date: data?.year, - categories: parseCategories(item), - rating: parseRating(data), - images: parseImages(item), - directors: parseCast(data, 'director'), - actors: parseCast(data, 'actors'), - start: dayjs(data?.start), - stop: dayjs(data?.end) - }) - }) - - return programs - }, - async channels() { - const data = await axios - .get('https://mts-pro-envoy-vip.dna.fi/hbx/api/pub/xrtv/g/media?q=profile:ch&limit=1000') - .then(r => r.data) - .catch(console.error) - - return data._embedded['xrtv:media-item'].map(c => { - return { - lang: 'fi', - site_id: c.datalistTerm, - name: c.name - } - }) - } -} - -function parseCast(data, role) { - if (!data[role] || !data[role].value) return [] - - return data[role].value.split(', ').map(name => ({ - lang: data[role].lang, - value: name - })) -} - -function parseCategories(item) { - const categories = item?._embedded?.['xrtv:media-category'] - - return Array.isArray(categories) ? categories.map(category => category.name) : [] -} - -function parseRating(data) { - if (!data.age_rating) return null - - return { - system: 'VET', - value: data.age_rating - } -} - -function parseImages(item) { - const images = item?._embedded?.['xrtv:image'] - - return Array.isArray(images) ? images.map(image => image.src) : [] -} - -function parseItems(content, date) { - try { - const data = JSON.parse(content) - let items = data?._embedded?.['xrtv:media-item'] - items = Array.isArray(items) ? items : [] - items = items.filter(item => { - const start = item?._embedded?.['xrtv:meta']?.data?.start - if (!start) return false - - return date.isSame(dayjs(start), 'day') - }) - - return items - } catch { - return [] - } -} +const axios = require('axios') +const dayjs = require('dayjs') + +module.exports = { + site: 'dna.fi', + days: 2, + url({ date, channel }) { + const beginTimestamp = date.add(2, 'h').valueOf() + const endTimestamp = date.add(1, 'd').add(2, 'h').subtract(1, 's').valueOf() + + return `https://mts-pro-envoy-vip.dna.fi/hbx/api/pub/xrtv/g/media?q=channel:${channel.site_id}&q=profile:pr&q=start-interval:${beginTimestamp}/${endTimestamp}` + }, + parser({ content, date }) { + let programs = [] + let items = parseItems(content, date) + items.forEach(item => { + const data = item?._embedded?.['xrtv:meta']?.data + programs.push({ + title: data?.title, + subtitle: data?.episode_title, + description: data?.description, + season: data?.season_number, + episode: data?.episode_number, + date: data?.year, + categories: parseCategories(item), + rating: parseRating(data), + images: parseImages(item), + directors: parseCast(data, 'director'), + actors: parseCast(data, 'actors'), + start: dayjs(data?.start), + stop: dayjs(data?.end) + }) + }) + + return programs + }, + async channels() { + const data = await axios + .get('https://mts-pro-envoy-vip.dna.fi/hbx/api/pub/xrtv/g/media?q=profile:ch&limit=1000') + .then(r => r.data) + .catch(console.error) + + return data._embedded['xrtv:media-item'].map(c => { + return { + lang: 'fi', + site_id: c.datalistTerm, + name: c.name + } + }) + } +} + +function parseCast(data, role) { + if (!data[role] || !data[role].value) return [] + + return data[role].value.split(', ').map(name => ({ + lang: data[role].lang, + value: name + })) +} + +function parseCategories(item) { + const categories = item?._embedded?.['xrtv:media-category'] + + return Array.isArray(categories) ? categories.map(category => category.name) : [] +} + +function parseRating(data) { + if (!data.age_rating) return null + + return { + system: 'VET', + value: data.age_rating + } +} + +function parseImages(item) { + const images = item?._embedded?.['xrtv:image'] + + return Array.isArray(images) ? images.map(image => image.src) : [] +} + +function parseItems(content, date) { + try { + const data = JSON.parse(content) + let items = data?._embedded?.['xrtv:media-item'] + items = Array.isArray(items) ? items : [] + items = items.filter(item => { + const start = item?._embedded?.['xrtv:meta']?.data?.start + if (!start) return false + + return date.isSame(dayjs(start), 'day') + }) + + return items + } catch { + return [] + } +} diff --git a/sites/dna.fi/dna.fi.test.js b/sites/dna.fi/dna.fi.test.js index 9373e5941..d4420f039 100644 --- a/sites/dna.fi/dna.fi.test.js +++ b/sites/dna.fi/dna.fi.test.js @@ -1,138 +1,138 @@ -const { parser, url } = require('./dna.fi.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-01-15', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'ch-216356', - xmltv_id: 'MTV3.fi' -} - -it('can generate valid url', async () => { - expect(url({ date, channel })).toBe( - 'https://mts-pro-envoy-vip.dna.fi/hbx/api/pub/xrtv/g/media?q=channel:ch-216356&q=profile:pr&q=start-interval:1736906400000/1736992799000' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') - let results = parser({ date, content }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(20) - expect(results[0]).toMatchObject({ - start: '2025-01-15T02:30:00.000Z', - stop: '2025-01-15T03:22:00.000Z', - title: { - lang: 'fi', - value: 'Next Level Chef' - }, - subtitle: { - lang: 'fi', - value: 'Brunssi' - }, - season: 1, - episode: 6, - rating: { - system: 'VET', - value: 'S' - }, - date: '2022', - images: [ - 'https://mts-pro-cache-vip.dna.fi/meme/v2/37f/3851073346622580374_aspect_ratio_16_9_1.jpg' - ], - description: { - lang: 'fi', - value: - 'Kausi 1, 6/11. Brunssi. Päivän haasteessa valmistetaan rentoa brunssiruokaa. Yksi kilpailija tekee valtaisan virheen myöhästyessään annosten luovutuksesta. Amerikkalainen tosi-tv-sarja.' - }, - categories: ['Reality TV', 'Entertainment', 'TV Show', 'Next Level Chef', 'Series 1'] - }) - expect(results[5]).toMatchObject({ - title: { - lang: 'fi', - value: 'Kauniit ja rohkeat (S)' - }, - subtitle: { - lang: 'fi', - value: 'Parantava syleily' - }, - start: '2025-01-15T08:30:00.000Z', - stop: '2025-01-15T09:00:00.000Z', - season: 37, - episode: 9380, - rating: { - system: 'VET', - value: 'S' - }, - date: '2023', - images: [ - 'https://mts-pro-cache-vip.dna.fi/meme/v2/79e/6509488401145439178_aspect_ratio_16_9_1.jpg' - ], - description: { - lang: 'fi', - value: - 'Steffy on vähällä yllättää Hopen ja Carterin kesken herkän hetken. Ridgen kannustamana Taylor suostuu kokeilemaan Shandran parannusmenetelmää, ja pitkään padotut tunteet saavat viimein vapautua.' - }, - categories: [ - 'Soap', - 'Drama', - 'Romance', - 'Series', - 'TV Show', - 'The Bold and the Beautiful', - 'Series 37' - ], - actors: [{ lang: 'en', value: 'Katherine Kelly Lang' }] - }) - expect(results[19]).toMatchObject({ - start: '2025-01-15T16:30:00.000Z', - stop: '2025-01-15T17:00:00.000Z', - title: { - lang: 'fi', - value: 'Emmerdale (S)' - }, - subtitle: { - lang: 'fi', - value: 'Epäilyksen varjossa' - }, - season: 54, - episode: 9845, - rating: { - system: 'VET', - value: 'S' - }, - date: '2023', - images: [ - 'https://mts-pro-cache-vip.dna.fi/meme/v2/5e8/5978592001161112833_aspect_ratio_16_9_1.jpg' - ], - description: { - lang: 'fi', - value: - 'Caleb haistaa palaneen käryä Craigin kuolemaan liittyen. Mackenzien yllätysvierailu antaa vahvistuksen Chloen päätökselle. Lydia pohtii, pitäisikö hänen mennä Craigin hautajaisiin. Dawnin supistukset säikäyttävät Rhonan.' - }, - categories: ['Soap', 'Drama', 'Romance', 'Series', 'TV Show', 'Emmerdale', 'Series 54'], - directors: [ - { lang: 'en', value: 'Ian Bevitt' }, - { lang: 'en', value: 'Munir Malik' } - ] - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - date, - content: '' - }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./dna.fi.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-01-15', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'ch-216356', + xmltv_id: 'MTV3.fi' +} + +it('can generate valid url', async () => { + expect(url({ date, channel })).toBe( + 'https://mts-pro-envoy-vip.dna.fi/hbx/api/pub/xrtv/g/media?q=channel:ch-216356&q=profile:pr&q=start-interval:1736906400000/1736992799000' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') + let results = parser({ date, content }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(20) + expect(results[0]).toMatchObject({ + start: '2025-01-15T02:30:00.000Z', + stop: '2025-01-15T03:22:00.000Z', + title: { + lang: 'fi', + value: 'Next Level Chef' + }, + subtitle: { + lang: 'fi', + value: 'Brunssi' + }, + season: 1, + episode: 6, + rating: { + system: 'VET', + value: 'S' + }, + date: '2022', + images: [ + 'https://mts-pro-cache-vip.dna.fi/meme/v2/37f/3851073346622580374_aspect_ratio_16_9_1.jpg' + ], + description: { + lang: 'fi', + value: + 'Kausi 1, 6/11. Brunssi. Päivän haasteessa valmistetaan rentoa brunssiruokaa. Yksi kilpailija tekee valtaisan virheen myöhästyessään annosten luovutuksesta. Amerikkalainen tosi-tv-sarja.' + }, + categories: ['Reality TV', 'Entertainment', 'TV Show', 'Next Level Chef', 'Series 1'] + }) + expect(results[5]).toMatchObject({ + title: { + lang: 'fi', + value: 'Kauniit ja rohkeat (S)' + }, + subtitle: { + lang: 'fi', + value: 'Parantava syleily' + }, + start: '2025-01-15T08:30:00.000Z', + stop: '2025-01-15T09:00:00.000Z', + season: 37, + episode: 9380, + rating: { + system: 'VET', + value: 'S' + }, + date: '2023', + images: [ + 'https://mts-pro-cache-vip.dna.fi/meme/v2/79e/6509488401145439178_aspect_ratio_16_9_1.jpg' + ], + description: { + lang: 'fi', + value: + 'Steffy on vähällä yllättää Hopen ja Carterin kesken herkän hetken. Ridgen kannustamana Taylor suostuu kokeilemaan Shandran parannusmenetelmää, ja pitkään padotut tunteet saavat viimein vapautua.' + }, + categories: [ + 'Soap', + 'Drama', + 'Romance', + 'Series', + 'TV Show', + 'The Bold and the Beautiful', + 'Series 37' + ], + actors: [{ lang: 'en', value: 'Katherine Kelly Lang' }] + }) + expect(results[19]).toMatchObject({ + start: '2025-01-15T16:30:00.000Z', + stop: '2025-01-15T17:00:00.000Z', + title: { + lang: 'fi', + value: 'Emmerdale (S)' + }, + subtitle: { + lang: 'fi', + value: 'Epäilyksen varjossa' + }, + season: 54, + episode: 9845, + rating: { + system: 'VET', + value: 'S' + }, + date: '2023', + images: [ + 'https://mts-pro-cache-vip.dna.fi/meme/v2/5e8/5978592001161112833_aspect_ratio_16_9_1.jpg' + ], + description: { + lang: 'fi', + value: + 'Caleb haistaa palaneen käryä Craigin kuolemaan liittyen. Mackenzien yllätysvierailu antaa vahvistuksen Chloen päätökselle. Lydia pohtii, pitäisikö hänen mennä Craigin hautajaisiin. Dawnin supistukset säikäyttävät Rhonan.' + }, + categories: ['Soap', 'Drama', 'Romance', 'Series', 'TV Show', 'Emmerdale', 'Series 54'], + directors: [ + { lang: 'en', value: 'Ian Bevitt' }, + { lang: 'en', value: 'Munir Malik' } + ] + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + date, + content: '' + }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/dsmart.com.tr/dsmart.com.tr.config.js b/sites/dsmart.com.tr/dsmart.com.tr.config.js index 4a188b625..c5a63a4da 100644 --- a/sites/dsmart.com.tr/dsmart.com.tr.config.js +++ b/sites/dsmart.com.tr/dsmart.com.tr.config.js @@ -1,130 +1,130 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -const duration = require('dayjs/plugin/duration') -const doFetch = require('@ntlab/sfetch') -const debug = require('debug')('site:dsmart.com.tr') - -dayjs.extend(utc) -dayjs.extend(customParseFormat) -dayjs.extend(duration) - -doFetch.setDebugger(debug) - -const channelsWithSchedule = true -const pageLimit = 10 -const caches = {} - -module.exports = { - site: 'dsmart.com.tr', - days: 2, - request: { - cache: { - ttl: 24 * 60 * 60 * 1000 // 1 day - } - }, - url({ date, page = 1 }) { - return `https://www.dsmart.com.tr/api/v1/public/epg/schedules?page=${ - page - }&limit=${ - pageLimit - }&day=${ - date.format('YYYY-MM-DD') - }` - }, - async parser({ content, channel, date, useCache = true }) { - const programs = [] - if (content) { - if (typeof content === 'string') { - content = JSON.parse(content) - } - if (useCache) { - const cacheKey = date.format('YYYYMMDD') - // cache whole channels for the day - if (caches[cacheKey] === undefined) { - if (content?.data?.total) { - const queues = [] - const pages = Math.ceil(content.data.total / pageLimit) - for (let page = 2; page <= pages; page++) { - queues.push(module.exports.url({ date, page })) - } - await doFetch(queues, (url, res) => { - if (Array.isArray(res?.data?.channels)) { - content.data.channels.push(...res.data.channels) - } - }) - caches[cacheKey] = content - } - } else { - content = caches[cacheKey] - } - } - if (Array.isArray(content?.data?.channels)) { - content.data.channels - .filter(i => i._id === channel.site_id) - .forEach(i => { - if (i.schedule.length) { - let dayStart, ofs - programs.push(...i.schedule - .map(p => { - const baseDate = dayjs.utc(p.day) - const startDate = dayjs.utc(p.start_date) - // calculate base offset if needed - if (!dayStart) { - dayStart = startDate - ofs = dayjs.duration(dayjs.utc(`${p.day.substr(0, 11)}${p.start_date.substr(11)}`).diff(baseDate)) - .asSeconds() - } - const delta = dayjs.duration(startDate.diff(dayStart)).asSeconds() - // ignore days in duration - const [h, m, s] = (p.duration.includes(',') ? p.duration.split(',')[1].trim() : p.duration) - .split(':').map(Number) - const duration = (h * 3600) + (m * 60) + s - const start = baseDate.add(ofs + delta, 's') - const stop = start.add(duration, 's') - return { - title: p.program_name, - description: p.description, - category: p.genre && p.genre.includes('/') ? - p.genre.split('/').map(g => `${g.substr(0, 1).toUpperCase()}${g.substr(1)}`) : null, - start, - stop - } - }) - ) - } - }) - } - } - - return programs - }, - async channels() { - const channels = [] - const f = page => this.url({ date: dayjs(), page }) - let pages, page = 1 - const queues = [f(page)] - await doFetch(queues, (url, res) => { - if (!pages && res.data.total) { - pages = Math.ceil(res.data.total / pageLimit) - while (page < pages) { - queues.push(f(++page)) - } - } - if (Array.isArray(res?.data?.channels)) { - channels.push(...res.data.channels - .filter(i => (channelsWithSchedule && i.schedule.length) || !channelsWithSchedule) - .map(i => { - return { - lang: 'tr', - name: i.channel_name, - site_id: i._id - } - }) - ) - } - }) - - return channels - } -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const duration = require('dayjs/plugin/duration') +const doFetch = require('@ntlab/sfetch') +const debug = require('debug')('site:dsmart.com.tr') + +dayjs.extend(utc) +dayjs.extend(customParseFormat) +dayjs.extend(duration) + +doFetch.setDebugger(debug) + +const channelsWithSchedule = true +const pageLimit = 10 +const caches = {} + +module.exports = { + site: 'dsmart.com.tr', + days: 2, + request: { + cache: { + ttl: 24 * 60 * 60 * 1000 // 1 day + } + }, + url({ date, page = 1 }) { + return `https://www.dsmart.com.tr/api/v1/public/epg/schedules?page=${ + page + }&limit=${ + pageLimit + }&day=${ + date.format('YYYY-MM-DD') + }` + }, + async parser({ content, channel, date, useCache = true }) { + const programs = [] + if (content) { + if (typeof content === 'string') { + content = JSON.parse(content) + } + if (useCache) { + const cacheKey = date.format('YYYYMMDD') + // cache whole channels for the day + if (caches[cacheKey] === undefined) { + if (content?.data?.total) { + const queues = [] + const pages = Math.ceil(content.data.total / pageLimit) + for (let page = 2; page <= pages; page++) { + queues.push(module.exports.url({ date, page })) + } + await doFetch(queues, (url, res) => { + if (Array.isArray(res?.data?.channels)) { + content.data.channels.push(...res.data.channels) + } + }) + caches[cacheKey] = content + } + } else { + content = caches[cacheKey] + } + } + if (Array.isArray(content?.data?.channels)) { + content.data.channels + .filter(i => i._id === channel.site_id) + .forEach(i => { + if (i.schedule.length) { + let dayStart, ofs + programs.push(...i.schedule + .map(p => { + const baseDate = dayjs.utc(p.day) + const startDate = dayjs.utc(p.start_date) + // calculate base offset if needed + if (!dayStart) { + dayStart = startDate + ofs = dayjs.duration(dayjs.utc(`${p.day.substr(0, 11)}${p.start_date.substr(11)}`).diff(baseDate)) + .asSeconds() + } + const delta = dayjs.duration(startDate.diff(dayStart)).asSeconds() + // ignore days in duration + const [h, m, s] = (p.duration.includes(',') ? p.duration.split(',')[1].trim() : p.duration) + .split(':').map(Number) + const duration = (h * 3600) + (m * 60) + s + const start = baseDate.add(ofs + delta, 's') + const stop = start.add(duration, 's') + return { + title: p.program_name, + description: p.description, + category: p.genre && p.genre.includes('/') ? + p.genre.split('/').map(g => `${g.substr(0, 1).toUpperCase()}${g.substr(1)}`) : null, + start, + stop + } + }) + ) + } + }) + } + } + + return programs + }, + async channels() { + const channels = [] + const f = page => this.url({ date: dayjs(), page }) + let pages, page = 1 + const queues = [f(page)] + await doFetch(queues, (url, res) => { + if (!pages && res.data.total) { + pages = Math.ceil(res.data.total / pageLimit) + while (page < pages) { + queues.push(f(++page)) + } + } + if (Array.isArray(res?.data?.channels)) { + channels.push(...res.data.channels + .filter(i => (channelsWithSchedule && i.schedule.length) || !channelsWithSchedule) + .map(i => { + return { + lang: 'tr', + name: i.channel_name, + site_id: i._id + } + }) + ) + } + }) + + return channels + } +} diff --git a/sites/dsmart.com.tr/dsmart.com.tr.test.js b/sites/dsmart.com.tr/dsmart.com.tr.test.js index 19bb9732d..67550a762 100644 --- a/sites/dsmart.com.tr/dsmart.com.tr.test.js +++ b/sites/dsmart.com.tr/dsmart.com.tr.test.js @@ -1,82 +1,82 @@ -const { parser, url } = require('./dsmart.com.tr.config.js') -const axios = require('axios') -const dayjs = require('dayjs') -const fs = require('fs') -const path = require('path') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -const date = dayjs.utc('2025-01-13', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '5fe07f5dcfef0b1593275822', - xmltv_id: 'Sinema1001.tr' -} - -axios.get.mockImplementation(url => { - const result = {} - const urls = { - 'https://www.dsmart.com.tr/api/v1/public/epg/schedules?page=1&limit=10&day=2025-01-13': - 'content1.json', - 'https://www.dsmart.com.tr/api/v1/public/epg/schedules?page=2&limit=10&day=2025-01-13': - 'content2.json', - } - if (urls[url] !== undefined) { - result.data = fs.readFileSync(path.join(__dirname, '__data__', urls[url])).toString() - if (!urls[url].startsWith('content1')) { - result.data = JSON.parse(result.data) - } - } - - return Promise.resolve(result) -}) - - -it('can generate valid url', () => { - expect(url({ date })).toBe( - 'https://www.dsmart.com.tr/api/v1/public/epg/schedules?page=1&limit=10&day=2025-01-13' - ) -}) - -it('can parse response', async () => { - const content = fs.readFileSync(path.join(__dirname, '__data__', 'content1.json')).toString() - const results = (await parser({ content, channel, date })).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(11) - - expect(results[0]).toMatchObject({ - start: '2025-01-12T21:30:00.000Z', - stop: '2025-01-12T23:30:00.000Z', - title: 'Taksi Şoförü', - description: - 'Vietnam savaşının izlerinin etkisindeki bir asker ve New York sokakları. Travis Bickle, geceleri taksi şoförlüğü yaptığı New York’ta bir yandan da gündelik yaşama ayak uydurmaya çalışır. Çürümeye yüz tutmuş bir topluma karşı tutulan bir ayna niteliğindeki film, yönetmen Martin Scorsese’nin kariyerinin en önemli filmlerinden biri olarak kabul görür.', - category: ['Sinema', 'Genel'] - }) - expect(results[10]).toMatchObject({ - start: '2025-01-13T19:00:00.000Z', - stop: '2025-01-13T21:00:00.000Z', - title: 'Senin Adın', - description: - 'Dağların sardığı bir bölgede yaşayan Mitsuha, hayatından çok da memnun olmayan liseli bir kızdır. Babası vali olarak çalışmakta ve seçim kampanyaları ile uğraşmaktadır. Evde kendisi, kardeşi ve büyükannesi dışında kimse yoktur. Kırsal kesimdeki yaşamı onu bunaltmaktadır ve esas isteği Tokyo\'nun muhteşem şehir hayatının bir parçası olmaktır. Diğer tarafta ise Taki vardır.', - category: ['Sinema', 'Genel'] - }) -}) - -it('can handle empty guide', async () => { - const results = await parser({ - channel, - date, - content: fs.readFileSync(path.join(__dirname, '__data__', 'no_content.json')).toString(), - useCache: false - }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./dsmart.com.tr.config.js') +const axios = require('axios') +const dayjs = require('dayjs') +const fs = require('fs') +const path = require('path') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.utc('2025-01-13', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '5fe07f5dcfef0b1593275822', + xmltv_id: 'Sinema1001.tr' +} + +axios.get.mockImplementation(url => { + const result = {} + const urls = { + 'https://www.dsmart.com.tr/api/v1/public/epg/schedules?page=1&limit=10&day=2025-01-13': + 'content1.json', + 'https://www.dsmart.com.tr/api/v1/public/epg/schedules?page=2&limit=10&day=2025-01-13': + 'content2.json', + } + if (urls[url] !== undefined) { + result.data = fs.readFileSync(path.join(__dirname, '__data__', urls[url])).toString() + if (!urls[url].startsWith('content1')) { + result.data = JSON.parse(result.data) + } + } + + return Promise.resolve(result) +}) + + +it('can generate valid url', () => { + expect(url({ date })).toBe( + 'https://www.dsmart.com.tr/api/v1/public/epg/schedules?page=1&limit=10&day=2025-01-13' + ) +}) + +it('can parse response', async () => { + const content = fs.readFileSync(path.join(__dirname, '__data__', 'content1.json')).toString() + const results = (await parser({ content, channel, date })).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(11) + + expect(results[0]).toMatchObject({ + start: '2025-01-12T21:30:00.000Z', + stop: '2025-01-12T23:30:00.000Z', + title: 'Taksi Şoförü', + description: + 'Vietnam savaşının izlerinin etkisindeki bir asker ve New York sokakları. Travis Bickle, geceleri taksi şoförlüğü yaptığı New York’ta bir yandan da gündelik yaşama ayak uydurmaya çalışır. Çürümeye yüz tutmuş bir topluma karşı tutulan bir ayna niteliğindeki film, yönetmen Martin Scorsese’nin kariyerinin en önemli filmlerinden biri olarak kabul görür.', + category: ['Sinema', 'Genel'] + }) + expect(results[10]).toMatchObject({ + start: '2025-01-13T19:00:00.000Z', + stop: '2025-01-13T21:00:00.000Z', + title: 'Senin Adın', + description: + 'Dağların sardığı bir bölgede yaşayan Mitsuha, hayatından çok da memnun olmayan liseli bir kızdır. Babası vali olarak çalışmakta ve seçim kampanyaları ile uğraşmaktadır. Evde kendisi, kardeşi ve büyükannesi dışında kimse yoktur. Kırsal kesimdeki yaşamı onu bunaltmaktadır ve esas isteği Tokyo\'nun muhteşem şehir hayatının bir parçası olmaktır. Diğer tarafta ise Taki vardır.', + category: ['Sinema', 'Genel'] + }) +}) + +it('can handle empty guide', async () => { + const results = await parser({ + channel, + date, + content: fs.readFileSync(path.join(__dirname, '__data__', 'no_content.json')).toString(), + useCache: false + }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/dstv.com/dstv.com.test.js b/sites/dstv.com/dstv.com.test.js index c2065a589..220afa949 100644 --- a/sites/dstv.com/dstv.com.test.js +++ b/sites/dstv.com/dstv.com.test.js @@ -1,111 +1,111 @@ -const { parser, url } = require('./dstv.com.config.js') -const axios = require('axios') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -const API_ENDPOINT = 'https://www.dstv.com/umbraco/api/TvGuide' - -const date = dayjs.utc('2022-11-22', 'YYYY-MM-DD').startOf('d') -const channelZA = { - site_id: 'zaf#201', - xmltv_id: 'SuperSportGrandstand.za' -} -const channelNG = { - site_id: 'nga#201', - xmltv_id: 'SuperSportGrandstand.za' -} - -it('can generate valid url for zaf', () => { - expect(url({ channel: channelZA, date })).toBe( - `${API_ENDPOINT}/GetProgrammes?d=2022-11-22&country=zaf` - ) -}) - -it('can generate valid url for nga', () => { - expect(url({ channel: channelNG, date })).toBe( - `${API_ENDPOINT}/GetProgrammes?d=2022-11-22&package=DStv%20Premium&country=nga` - ) -}) - -it('can parse response for ZA', async () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_zaf.json')) - - axios.get.mockImplementation(url => { - if (url === `${API_ENDPOINT}/GetProgramme?id=8b237235-aa17-4bb8-9ea6-097e7a813336`) { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program_zaf.json'))) - }) - } else { - return Promise.resolve({ data: '' }) - } - }) - - let results = await parser({ content, channel: channelZA }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[1]).toMatchObject({ - start: '2022-11-21T23:00:00.000Z', - stop: '2022-11-22T00:00:00.000Z', - title: 'UFC FN HL: Nzechukwu v Cutelaba', - description: - "'UFC Fight Night Highlights - Heavyweight Bout: Kennedy Nzechukwu vs Ion Cutelaba'. From The UFC APEX Center - Las Vegas, USA.", - image: - 'https://03mcdecdnimagerepository.blob.core.windows.net/epguideimage/img/271546_UFC Fight Night.png', - category: ['All Sport', 'Mixed Martial Arts'] - }) -}) - -it('can parse response for NG', async () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_nga.json')) - - axios.get.mockImplementation(url => { - if (url === `${API_ENDPOINT}/GetProgramme?id=6d58931e-2192-486a-a202-14720136d204`) { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program_nga.json'))) - }) - } else { - return Promise.resolve({ data: '' }) - } - }) - - let results = await parser({ content, channel: channelNG }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2022-11-21T23:00:00.000Z', - stop: '2022-11-22T00:00:00.000Z', - title: 'UFC FN HL: Nzechukwu v Cutelaba', - description: - "'UFC Fight Night Highlights - Heavyweight Bout: Kennedy Nzechukwu vs Ion Cutelaba'. From The UFC APEX Center - Las Vegas, USA.", - image: - 'https://03mcdecdnimagerepository.blob.core.windows.net/epguideimage/img/271546_UFC Fight Night.png', - category: ['All Sport', 'Mixed Martial Arts'] - }) -}) - -it('can handle empty guide', done => { - parser({ - content: '{"Total":0,"Channels":[]}', - channel: channelZA - }) - .then(result => { - expect(result).toMatchObject([]) - done() - }) - .catch(done) -}) +const { parser, url } = require('./dstv.com.config.js') +const axios = require('axios') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const API_ENDPOINT = 'https://www.dstv.com/umbraco/api/TvGuide' + +const date = dayjs.utc('2022-11-22', 'YYYY-MM-DD').startOf('d') +const channelZA = { + site_id: 'zaf#201', + xmltv_id: 'SuperSportGrandstand.za' +} +const channelNG = { + site_id: 'nga#201', + xmltv_id: 'SuperSportGrandstand.za' +} + +it('can generate valid url for zaf', () => { + expect(url({ channel: channelZA, date })).toBe( + `${API_ENDPOINT}/GetProgrammes?d=2022-11-22&country=zaf` + ) +}) + +it('can generate valid url for nga', () => { + expect(url({ channel: channelNG, date })).toBe( + `${API_ENDPOINT}/GetProgrammes?d=2022-11-22&package=DStv%20Premium&country=nga` + ) +}) + +it('can parse response for ZA', async () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_zaf.json')) + + axios.get.mockImplementation(url => { + if (url === `${API_ENDPOINT}/GetProgramme?id=8b237235-aa17-4bb8-9ea6-097e7a813336`) { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program_zaf.json'))) + }) + } else { + return Promise.resolve({ data: '' }) + } + }) + + let results = await parser({ content, channel: channelZA }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[1]).toMatchObject({ + start: '2022-11-21T23:00:00.000Z', + stop: '2022-11-22T00:00:00.000Z', + title: 'UFC FN HL: Nzechukwu v Cutelaba', + description: + "'UFC Fight Night Highlights - Heavyweight Bout: Kennedy Nzechukwu vs Ion Cutelaba'. From The UFC APEX Center - Las Vegas, USA.", + image: + 'https://03mcdecdnimagerepository.blob.core.windows.net/epguideimage/img/271546_UFC Fight Night.png', + category: ['All Sport', 'Mixed Martial Arts'] + }) +}) + +it('can parse response for NG', async () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_nga.json')) + + axios.get.mockImplementation(url => { + if (url === `${API_ENDPOINT}/GetProgramme?id=6d58931e-2192-486a-a202-14720136d204`) { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program_nga.json'))) + }) + } else { + return Promise.resolve({ data: '' }) + } + }) + + let results = await parser({ content, channel: channelNG }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2022-11-21T23:00:00.000Z', + stop: '2022-11-22T00:00:00.000Z', + title: 'UFC FN HL: Nzechukwu v Cutelaba', + description: + "'UFC Fight Night Highlights - Heavyweight Bout: Kennedy Nzechukwu vs Ion Cutelaba'. From The UFC APEX Center - Las Vegas, USA.", + image: + 'https://03mcdecdnimagerepository.blob.core.windows.net/epguideimage/img/271546_UFC Fight Night.png', + category: ['All Sport', 'Mixed Martial Arts'] + }) +}) + +it('can handle empty guide', done => { + parser({ + content: '{"Total":0,"Channels":[]}', + channel: channelZA + }) + .then(result => { + expect(result).toMatchObject([]) + done() + }) + .catch(done) +}) diff --git a/sites/dtv8.net/dtv8.net.config.js b/sites/dtv8.net/dtv8.net.config.js index 4d68b20f1..8f4afadca 100644 --- a/sites/dtv8.net/dtv8.net.config.js +++ b/sites/dtv8.net/dtv8.net.config.js @@ -1,90 +1,90 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'dtv8.net', - days: 2, - url({ date }) { - const day = date.format('dddd') - - return `https://dtv8.net/tv-listings/${day.toLowerCase()}/` - }, - parser({ content, date }) { - let programs = [] - - const items = parseItems(content) - items.forEach(item => { - const $item = cheerio.load(item) - let prev = programs[programs.length - 1] - let start = parseStart($item, date) - if (prev) { - if (start < prev.start) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.add(30, 'm') - - programs.push({ - title: parseTitle($item), - description: parseDescription($item), - image: parseImage($item), - start, - stop - }) - }) - - return programs - }, - channels() { - return [] - } -} - -function parseTitle($item) { - return $item( - 'td:nth-child(2) > strong:nth-child(1),td:nth-child(2) > span > strong,td:nth-child(2) > span > b' - ).text() -} - -function parseDescription($item) { - return ( - $item( - 'td:nth-child(2) > strong:nth-child(3) > span,td:nth-child(2) > p:nth-child(3) > strong > span' - ).text() || null - ) -} - -function parseImage($item) { - return $item('td:nth-child(1) > img.size-full').attr('src') || null -} - -function parseStart($item, date) { - const time = $item('td:nth-child(1)').text() - - return dayjs.tz( - `${date.format('YYYY-MM-DD')} ${time}`, - 'YYYY-MM-DD HH:mm [hrs.]', - 'America/Guyana' - ) -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('table tr') - .filter((i, el) => { - const firstColumn = $(el).find('td').text() - - return Boolean(firstColumn) && !firstColumn.includes('Time') - }) - .toArray() -} +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'dtv8.net', + days: 2, + url({ date }) { + const day = date.format('dddd') + + return `https://dtv8.net/tv-listings/${day.toLowerCase()}/` + }, + parser({ content, date }) { + let programs = [] + + const items = parseItems(content) + items.forEach(item => { + const $item = cheerio.load(item) + let prev = programs[programs.length - 1] + let start = parseStart($item, date) + if (prev) { + if (start < prev.start) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.add(30, 'm') + + programs.push({ + title: parseTitle($item), + description: parseDescription($item), + image: parseImage($item), + start, + stop + }) + }) + + return programs + }, + channels() { + return [] + } +} + +function parseTitle($item) { + return $item( + 'td:nth-child(2) > strong:nth-child(1),td:nth-child(2) > span > strong,td:nth-child(2) > span > b' + ).text() +} + +function parseDescription($item) { + return ( + $item( + 'td:nth-child(2) > strong:nth-child(3) > span,td:nth-child(2) > p:nth-child(3) > strong > span' + ).text() || null + ) +} + +function parseImage($item) { + return $item('td:nth-child(1) > img.size-full').attr('src') || null +} + +function parseStart($item, date) { + const time = $item('td:nth-child(1)').text() + + return dayjs.tz( + `${date.format('YYYY-MM-DD')} ${time}`, + 'YYYY-MM-DD HH:mm [hrs.]', + 'America/Guyana' + ) +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('table tr') + .filter((i, el) => { + const firstColumn = $(el).find('td').text() + + return Boolean(firstColumn) && !firstColumn.includes('Time') + }) + .toArray() +} diff --git a/sites/dtv8.net/dtv8.net.test.js b/sites/dtv8.net/dtv8.net.test.js index 027519ad9..fc37443af 100644 --- a/sites/dtv8.net/dtv8.net.test.js +++ b/sites/dtv8.net/dtv8.net.test.js @@ -1,79 +1,79 @@ -const { parser, url } = require('./dtv8.net.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-02-21', 'YYYY-MM-DD').startOf('d') - -it('can generate valid url', () => { - expect(url({ date })).toBe('https://dtv8.net/tv-listings/friday/') -}) - -it('can parse response for friday', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_fri.html')) - - let results = parser({ content, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - - return p - }) - - expect(results.length).toBe(18) - expect(results[9]).toMatchObject({ - title: 'Smallville', - image: 'http://dtv8.net/wp-content/uploads/71P0aShCBXL._SL1300_.jpg', - description: - 'A young Clark Kent struggles to find his place in the world as he learns to harness his alien powers for good and deals with the typical troubles of teenage life in Smallville, Kansas.', - start: '2025-02-21T21:00:00.000Z', - stop: '2025-02-21T22:00:00.000Z' - }) - expect(results[15]).toMatchObject({ - title: 'Law & Order', - image: null, - description: - 'In God We Trust: A young lawyer with a secret past is found dead; Price and Baxter debate the pros and cons of prison as a punishment versus alternative justice options.', - start: '2025-02-22T01:45:00.000Z', - stop: '2025-02-22T02:30:00.000Z' - }) -}) - -it('can parse response for saturday', () => { - const date = dayjs.utc('2025-02-22', 'YYYY-MM-DD').startOf('d') - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_sat.html')) - - let results = parser({ content, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - - return p - }) - - expect(results.length).toBe(11) - expect(results[0]).toMatchObject({ - title: 'Sign On', - image: null, - description: null, - start: '2025-02-22T13:55:00.000Z', - stop: '2025-02-22T14:00:00.000Z' - }) - expect(results[10]).toMatchObject({ - title: 'Sign Off', - image: null, - description: null, - start: '2025-02-23T04:00:00.000Z', - stop: '2025-02-23T04:30:00.000Z' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ content: '' }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./dtv8.net.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-02-21', 'YYYY-MM-DD').startOf('d') + +it('can generate valid url', () => { + expect(url({ date })).toBe('https://dtv8.net/tv-listings/friday/') +}) + +it('can parse response for friday', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_fri.html')) + + let results = parser({ content, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + + return p + }) + + expect(results.length).toBe(18) + expect(results[9]).toMatchObject({ + title: 'Smallville', + image: 'http://dtv8.net/wp-content/uploads/71P0aShCBXL._SL1300_.jpg', + description: + 'A young Clark Kent struggles to find his place in the world as he learns to harness his alien powers for good and deals with the typical troubles of teenage life in Smallville, Kansas.', + start: '2025-02-21T21:00:00.000Z', + stop: '2025-02-21T22:00:00.000Z' + }) + expect(results[15]).toMatchObject({ + title: 'Law & Order', + image: null, + description: + 'In God We Trust: A young lawyer with a secret past is found dead; Price and Baxter debate the pros and cons of prison as a punishment versus alternative justice options.', + start: '2025-02-22T01:45:00.000Z', + stop: '2025-02-22T02:30:00.000Z' + }) +}) + +it('can parse response for saturday', () => { + const date = dayjs.utc('2025-02-22', 'YYYY-MM-DD').startOf('d') + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_sat.html')) + + let results = parser({ content, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + + return p + }) + + expect(results.length).toBe(11) + expect(results[0]).toMatchObject({ + title: 'Sign On', + image: null, + description: null, + start: '2025-02-22T13:55:00.000Z', + stop: '2025-02-22T14:00:00.000Z' + }) + expect(results[10]).toMatchObject({ + title: 'Sign Off', + image: null, + description: null, + start: '2025-02-23T04:00:00.000Z', + stop: '2025-02-23T04:30:00.000Z' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ content: '' }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/elcinema.com/elcinema.com.config.js b/sites/elcinema.com/elcinema.com.config.js index c6c4b85a9..7b925723f 100644 --- a/sites/elcinema.com/elcinema.com.config.js +++ b/sites/elcinema.com/elcinema.com.config.js @@ -1,149 +1,149 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') -require('dayjs/locale/ar') - -dayjs.extend(customParseFormat) -dayjs.extend(timezone) -dayjs.extend(utc) - -const headers = { - 'User-Agent': -'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 OPR/115.0.0.0' } - -module.exports = { - site: 'elcinema.com', - days: 2, - request: { headers }, - url({ channel }) { - const lang = channel.lang === 'en' ? 'en/' : '/' - - return `https://elcinema.com/${lang}tvguide/${channel.site_id}/` - }, - parser({ content, channel, date }) { - const programs = [] - const items = parseItems(content, channel, date) - items.forEach(item => { - const start = parseStart(item, date) - const duration = parseDuration(item) - const stop = start.add(duration, 'm') - programs.push({ - title: parseTitle(item), - description: parseDescription(item), - category: parseCategory(item), - image: parseImage(item), - start, - stop - }) - }) - - return programs - }, - async channels({ lang }) { - const axios = require('axios') - const data = await axios - .get(`https://elcinema.com/${lang}/tvguide/`, { - headers: headers - }) - .then(r => r.data) - .catch(console.log) - - const $ = cheerio.load(data) - - return $('.tv-line') - .map((i, el) => { - const link = $(el).find('.channel > div > div.hide-for-small-only > a') - const name = $(link).text() - const href = $(link).attr('href') - const [, site_id] = href.match(/\/(\d+)\/$/) - - return { - lang, - site_id, - name - } - }) - .get() - } -} - -function parseImage(item) { - const $ = cheerio.load(item) - const imgSrc = - $('.row > div.columns.small-3.large-1 > a > img').data('src') || - $('.row > div.columns.small-5.large-1 > img').data('src') - - return imgSrc || null -} - -function parseCategory(item) { - const $ = cheerio.load(item) - const category = $('.row > div.columns.small-6.large-3 > ul > li:nth-child(2)').text() - - return category.replace(/\(\d+\)/, '').trim() || null -} - -function parseDuration(item) { - const $ = cheerio.load(item) - const duration = - $('.row > div.columns.small-3.large-2 > ul > li:nth-child(2) > span').text() || - $('.row > div.columns.small-7.large-11 > ul > li:nth-child(2) > span').text() - - return duration.replace(/\D/g, '') || '' -} - -function parseStart(item, initDate) { - const $ = cheerio.load(item) - let time = - $('.row > div.columns.small-3.large-2 > ul > li:nth-child(1)').text() || - $('.row > div.columns.small-7.large-11 > ul > li:nth-child(2)').text() || - '' - - time = time - .replace(/\[.*\]/, '') - .replace('مساءً', 'PM') - .replace('صباحًا', 'AM') - .trim() - - time = `${initDate.format('YYYY-MM-DD')} ${time}` - - return dayjs.tz(time, 'YYYY-MM-DD hh:mm A', dayjs.tz.guess()) -} - -function parseTitle(item) { - const $ = cheerio.load(item) - - return ( - $('.row > div.columns.small-6.large-3 > ul > li:nth-child(1) > a').text() || - $('.row > div.columns.small-7.large-11 > ul > li:nth-child(1)').text() || - null - ) -} - -function parseDescription(item) { - const $ = cheerio.load(item) - const excerpt = $('.row > div.columns.small-12.large-6 > ul > li:nth-child(3)').text() || '' - - return excerpt.replace('...اقرأ المزيد', '').replace('...Read more', '') -} - -function parseItems(content, channel, date) { - const $ = cheerio.load(content) - - const dateString = date.locale(channel.lang).format('dddd D') - - const list = $('.dates') - .filter((i, el) => { - let parsedDateString = $(el).text().trim() - parsedDateString = parsedDateString.replace(/\s\s+/g, ' ') - - return parsedDateString.includes(dateString) - }) - .first() - .parent() - .next() - - return $('.padded-half', list).toArray() -} +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') +require('dayjs/locale/ar') + +dayjs.extend(customParseFormat) +dayjs.extend(timezone) +dayjs.extend(utc) + +const headers = { + 'User-Agent': +'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 OPR/115.0.0.0' } + +module.exports = { + site: 'elcinema.com', + days: 2, + request: { headers }, + url({ channel }) { + const lang = channel.lang === 'en' ? 'en/' : '/' + + return `https://elcinema.com/${lang}tvguide/${channel.site_id}/` + }, + parser({ content, channel, date }) { + const programs = [] + const items = parseItems(content, channel, date) + items.forEach(item => { + const start = parseStart(item, date) + const duration = parseDuration(item) + const stop = start.add(duration, 'm') + programs.push({ + title: parseTitle(item), + description: parseDescription(item), + category: parseCategory(item), + image: parseImage(item), + start, + stop + }) + }) + + return programs + }, + async channels({ lang }) { + const axios = require('axios') + const data = await axios + .get(`https://elcinema.com/${lang}/tvguide/`, { + headers: headers + }) + .then(r => r.data) + .catch(console.log) + + const $ = cheerio.load(data) + + return $('.tv-line') + .map((i, el) => { + const link = $(el).find('.channel > div > div.hide-for-small-only > a') + const name = $(link).text() + const href = $(link).attr('href') + const [, site_id] = href.match(/\/(\d+)\/$/) + + return { + lang, + site_id, + name + } + }) + .get() + } +} + +function parseImage(item) { + const $ = cheerio.load(item) + const imgSrc = + $('.row > div.columns.small-3.large-1 > a > img').data('src') || + $('.row > div.columns.small-5.large-1 > img').data('src') + + return imgSrc || null +} + +function parseCategory(item) { + const $ = cheerio.load(item) + const category = $('.row > div.columns.small-6.large-3 > ul > li:nth-child(2)').text() + + return category.replace(/\(\d+\)/, '').trim() || null +} + +function parseDuration(item) { + const $ = cheerio.load(item) + const duration = + $('.row > div.columns.small-3.large-2 > ul > li:nth-child(2) > span').text() || + $('.row > div.columns.small-7.large-11 > ul > li:nth-child(2) > span').text() + + return duration.replace(/\D/g, '') || '' +} + +function parseStart(item, initDate) { + const $ = cheerio.load(item) + let time = + $('.row > div.columns.small-3.large-2 > ul > li:nth-child(1)').text() || + $('.row > div.columns.small-7.large-11 > ul > li:nth-child(2)').text() || + '' + + time = time + .replace(/\[.*\]/, '') + .replace('مساءً', 'PM') + .replace('صباحًا', 'AM') + .trim() + + time = `${initDate.format('YYYY-MM-DD')} ${time}` + + return dayjs.tz(time, 'YYYY-MM-DD hh:mm A', dayjs.tz.guess()) +} + +function parseTitle(item) { + const $ = cheerio.load(item) + + return ( + $('.row > div.columns.small-6.large-3 > ul > li:nth-child(1) > a').text() || + $('.row > div.columns.small-7.large-11 > ul > li:nth-child(1)').text() || + null + ) +} + +function parseDescription(item) { + const $ = cheerio.load(item) + const excerpt = $('.row > div.columns.small-12.large-6 > ul > li:nth-child(3)').text() || '' + + return excerpt.replace('...اقرأ المزيد', '').replace('...Read more', '') +} + +function parseItems(content, channel, date) { + const $ = cheerio.load(content) + + const dateString = date.locale(channel.lang).format('dddd D') + + const list = $('.dates') + .filter((i, el) => { + let parsedDateString = $(el).text().trim() + parsedDateString = parsedDateString.replace(/\s\s+/g, ' ') + + return parsedDateString.includes(dateString) + }) + .first() + .parent() + .next() + + return $('.padded-half', list).toArray() +} diff --git a/sites/elcinema.com/elcinema.com.test.js b/sites/elcinema.com/elcinema.com.test.js index 92a794fea..d80c37bab 100644 --- a/sites/elcinema.com/elcinema.com.test.js +++ b/sites/elcinema.com/elcinema.com.test.js @@ -1,69 +1,69 @@ -const { parser, url } = require('./elcinema.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-08-28', 'YYYY-MM-DD').startOf('d') -const channelAR = { - lang: 'ar', - site_id: '1254', - xmltv_id: 'OSNSeries.ae' -} -const channelEN = { - lang: 'en', - site_id: '1254', - xmltv_id: 'OSNSeries.ae' -} - -it('can generate valid url', () => { - expect(url({ channel: channelEN })).toBe('https://elcinema.com/en/tvguide/1254/') -}) - -it('can parse response (en)', () => { - const contentEN = fs.readFileSync(path.resolve(__dirname, '__data__/content.en.html')) - const results = parser({ date, channel: channelEN, content: contentEN }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2022-08-27T14:25:00.000Z', - stop: '2022-08-27T15:15:00.000Z', - title: 'Station 19 S5', - image: - 'https://media.elcinema.com/uploads/_150x200_ec30d1a2251c8edf83334be4860184c74d2534d7ba508a334ad66fa59acc4926.jpg', - category: 'Series' - }) -}) - -it('can parse response (ar)', () => { - const contentAR = fs.readFileSync(path.resolve(__dirname, '__data__/content.ar.html')) - const results = parser({ date, channel: channelAR, content: contentAR }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2022-08-27T14:25:00.000Z', - stop: '2022-08-27T15:15:00.000Z', - title: 'Station 19 S5', - image: - 'https://media.elcinema.com/uploads/_150x200_ec30d1a2251c8edf83334be4860184c74d2534d7ba508a334ad66fa59acc4926.jpg', - category: 'مسلسل' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel: channelEN, - content: '' - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./elcinema.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-08-28', 'YYYY-MM-DD').startOf('d') +const channelAR = { + lang: 'ar', + site_id: '1254', + xmltv_id: 'OSNSeries.ae' +} +const channelEN = { + lang: 'en', + site_id: '1254', + xmltv_id: 'OSNSeries.ae' +} + +it('can generate valid url', () => { + expect(url({ channel: channelEN })).toBe('https://elcinema.com/en/tvguide/1254/') +}) + +it('can parse response (en)', () => { + const contentEN = fs.readFileSync(path.resolve(__dirname, '__data__/content.en.html')) + const results = parser({ date, channel: channelEN, content: contentEN }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2022-08-27T14:25:00.000Z', + stop: '2022-08-27T15:15:00.000Z', + title: 'Station 19 S5', + image: + 'https://media.elcinema.com/uploads/_150x200_ec30d1a2251c8edf83334be4860184c74d2534d7ba508a334ad66fa59acc4926.jpg', + category: 'Series' + }) +}) + +it('can parse response (ar)', () => { + const contentAR = fs.readFileSync(path.resolve(__dirname, '__data__/content.ar.html')) + const results = parser({ date, channel: channelAR, content: contentAR }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2022-08-27T14:25:00.000Z', + stop: '2022-08-27T15:15:00.000Z', + title: 'Station 19 S5', + image: + 'https://media.elcinema.com/uploads/_150x200_ec30d1a2251c8edf83334be4860184c74d2534d7ba508a334ad66fa59acc4926.jpg', + category: 'مسلسل' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel: channelEN, + content: '' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/ena.skylifetv.co.kr/ena.skylifetv.co.kr.config.js b/sites/ena.skylifetv.co.kr/ena.skylifetv.co.kr.config.js index e94f170a1..d8401689d 100644 --- a/sites/ena.skylifetv.co.kr/ena.skylifetv.co.kr.config.js +++ b/sites/ena.skylifetv.co.kr/ena.skylifetv.co.kr.config.js @@ -1,68 +1,68 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'ena.skylifetv.co.kr', - days: 2, - url({ channel, date }) { - return `http://ena.skylifetv.co.kr/${channel.site_id}/?day=${date.format('YYYYMMDD')}&sc_dvsn=U` - }, - parser({ content, date }) { - const programs = [] - const items = parseItems(content, date) - items.forEach(item => { - const $item = cheerio.load(item) - const start = parseStart($item, date) - const duration = parseDuration($item) - const stop = start.add(duration, 'm') - programs.push({ - title: parseTitle($item), - rating: parseRating($item), - start, - stop - }) - }) - - return programs - } -} - -function parseTitle($item) { - return $item('.col2 > .tit').text().trim() -} - -function parseRating($item) { - const rating = $item('.col4').text().trim() - - return rating - ? { - system: 'KMRB', - value: rating - } - : null -} - -function parseDuration($item) { - const duration = $item('.col5').text().trim() - - return duration ? parseInt(duration) : 30 -} - -function parseStart($item, date) { - const time = $item('.col1').text().trim() - - return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'Asia/Seoul') -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('.tbl_schedule > tbody > tr').toArray() -} +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'ena.skylifetv.co.kr', + days: 2, + url({ channel, date }) { + return `http://ena.skylifetv.co.kr/${channel.site_id}/?day=${date.format('YYYYMMDD')}&sc_dvsn=U` + }, + parser({ content, date }) { + const programs = [] + const items = parseItems(content, date) + items.forEach(item => { + const $item = cheerio.load(item) + const start = parseStart($item, date) + const duration = parseDuration($item) + const stop = start.add(duration, 'm') + programs.push({ + title: parseTitle($item), + rating: parseRating($item), + start, + stop + }) + }) + + return programs + } +} + +function parseTitle($item) { + return $item('.col2 > .tit').text().trim() +} + +function parseRating($item) { + const rating = $item('.col4').text().trim() + + return rating + ? { + system: 'KMRB', + value: rating + } + : null +} + +function parseDuration($item) { + const duration = $item('.col5').text().trim() + + return duration ? parseInt(duration) : 30 +} + +function parseStart($item, date) { + const time = $item('.col1').text().trim() + + return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'Asia/Seoul') +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('.tbl_schedule > tbody > tr').toArray() +} diff --git a/sites/ena.skylifetv.co.kr/ena.skylifetv.co.kr.test.js b/sites/ena.skylifetv.co.kr/ena.skylifetv.co.kr.test.js index 7528ce91e..6233e1830 100644 --- a/sites/ena.skylifetv.co.kr/ena.skylifetv.co.kr.test.js +++ b/sites/ena.skylifetv.co.kr/ena.skylifetv.co.kr.test.js @@ -1,57 +1,57 @@ -const { parser, url } = require('./ena.skylifetv.co.kr.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-01-27', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'ENA', - xmltv_id: 'ENA.kr' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe('http://ena.skylifetv.co.kr/ENA/?day=20230127&sc_dvsn=U') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') - let results = parser({ content, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2023-01-26T16:05:00.000Z', - stop: '2023-01-26T17:20:00.000Z', - title: '법쩐 6화', - rating: { - system: 'KMRB', - value: '15' - } - }) - - expect(results[17]).toMatchObject({ - start: '2023-01-27T14:10:00.000Z', - stop: '2023-01-27T15:25:00.000Z', - title: '남이 될 수 있을까 4화', - rating: { - system: 'KMRB', - value: '15' - } - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - date, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') - }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./ena.skylifetv.co.kr.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-01-27', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'ENA', + xmltv_id: 'ENA.kr' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe('http://ena.skylifetv.co.kr/ENA/?day=20230127&sc_dvsn=U') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') + let results = parser({ content, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2023-01-26T16:05:00.000Z', + stop: '2023-01-26T17:20:00.000Z', + title: '법쩐 6화', + rating: { + system: 'KMRB', + value: '15' + } + }) + + expect(results[17]).toMatchObject({ + start: '2023-01-27T14:10:00.000Z', + stop: '2023-01-27T15:25:00.000Z', + title: '남이 될 수 있을까 4화', + rating: { + system: 'KMRB', + value: '15' + } + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + date, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') + }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/energeek.cl/energeek.cl.config.js b/sites/energeek.cl/energeek.cl.config.js index dfbfb7d93..c4447f21a 100644 --- a/sites/energeek.cl/energeek.cl.config.js +++ b/sites/energeek.cl/energeek.cl.config.js @@ -1,33 +1,33 @@ -const parser = require('epg-parser') - -module.exports = { - site: 'energeek.cl', - days: 2, - request: { - cache: { - ttl: 60 * 60 * 1000 // 1 hour - } - }, - url: 'https://raw.githubusercontent.com/luisms123/tdt/master/guiaenergeek.xml', - parser: function ({ content, channel, date }) { - let programs = [] - const items = parseItems(content, channel, date) - items.forEach(item => { - programs.push({ - title: item.title?.[0]?.value, - description: item.desc?.[0]?.value, - icon: item.icon?.[0]?.src, - start: item.start, - stop: item.stop - }) - }) - - return programs - } -} - -function parseItems(content, channel, date) { - const { programs } = parser.parse(content) - - return programs.filter(p => p.channel === channel.site_id && date.isSame(p.start, 'day')) -} +const parser = require('epg-parser') + +module.exports = { + site: 'energeek.cl', + days: 2, + request: { + cache: { + ttl: 60 * 60 * 1000 // 1 hour + } + }, + url: 'https://raw.githubusercontent.com/luisms123/tdt/master/guiaenergeek.xml', + parser: function ({ content, channel, date }) { + let programs = [] + const items = parseItems(content, channel, date) + items.forEach(item => { + programs.push({ + title: item.title?.[0]?.value, + description: item.desc?.[0]?.value, + icon: item.icon?.[0]?.src, + start: item.start, + stop: item.stop + }) + }) + + return programs + } +} + +function parseItems(content, channel, date) { + const { programs } = parser.parse(content) + + return programs.filter(p => p.channel === channel.site_id && date.isSame(p.start, 'day')) +} diff --git a/sites/energeek.cl/energeek.cl.test.js b/sites/energeek.cl/energeek.cl.test.js index 540d63cef..f3e2c19f2 100644 --- a/sites/energeek.cl/energeek.cl.test.js +++ b/sites/energeek.cl/energeek.cl.test.js @@ -1,37 +1,37 @@ -const { parser, url } = require('./energeek.cl.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-11-29', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'EnerGeek Retro', - xmltv_id: 'EnerGeekRetro.cl' -} - -it('can generate valid url', () => { - expect(url).toBe('https://raw.githubusercontent.com/luisms123/tdt/master/guiaenergeek.xml') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.xml')) - let results = parser({ content, channel, date }) - - expect(results[0]).toMatchObject({ - start: '2022-11-29T03:00:00.000Z', - stop: '2022-11-29T03:30:00.000Z', - title: 'Noir', - description: - 'Kirika Yuumura es una adolescente japonesa que no recuerda nada de su pasado, salvo la palabra NOIR, por lo que decidirá contactar con Mireille Bouquet, una asesina profesional para que la ayude a investigar. Ambas forman un equipo muy eficiente, que resuelve un trabajo tras otro con gran éxito, hasta que aparece un grupo conocido como "Les Soldats", relacionados con el pasado de Kirika. Estos tratarán de eliminar a las dos chicas, antes de que indaguen más hondo sobre la verdad acerca de Noir', - icon: 'https://pics.filmaffinity.com/nowaru_noir_tv_series-225888552-mmed.jpg' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ content: '', channel, date }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./energeek.cl.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-11-29', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'EnerGeek Retro', + xmltv_id: 'EnerGeekRetro.cl' +} + +it('can generate valid url', () => { + expect(url).toBe('https://raw.githubusercontent.com/luisms123/tdt/master/guiaenergeek.xml') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.xml')) + let results = parser({ content, channel, date }) + + expect(results[0]).toMatchObject({ + start: '2022-11-29T03:00:00.000Z', + stop: '2022-11-29T03:30:00.000Z', + title: 'Noir', + description: + 'Kirika Yuumura es una adolescente japonesa que no recuerda nada de su pasado, salvo la palabra NOIR, por lo que decidirá contactar con Mireille Bouquet, una asesina profesional para que la ayude a investigar. Ambas forman un equipo muy eficiente, que resuelve un trabajo tras otro con gran éxito, hasta que aparece un grupo conocido como "Les Soldats", relacionados con el pasado de Kirika. Estos tratarán de eliminar a las dos chicas, antes de que indaguen más hondo sobre la verdad acerca de Noir', + icon: 'https://pics.filmaffinity.com/nowaru_noir_tv_series-225888552-mmed.jpg' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ content: '', channel, date }) + expect(result).toMatchObject([]) +}) diff --git a/sites/entertainment.ie/entertainment.ie.config.js b/sites/entertainment.ie/entertainment.ie.config.js index 04ff4ec6e..3bba2f048 100644 --- a/sites/entertainment.ie/entertainment.ie.config.js +++ b/sites/entertainment.ie/entertainment.ie.config.js @@ -1,96 +1,96 @@ -const axios = require('axios') -const cheerio = require('cheerio') -const { DateTime } = require('luxon') - -module.exports = { - site: 'entertainment.ie', - days: 2, - url: function ({ date, channel }) { - return `https://entertainment.ie/tv/${channel.site_id}/?date=${date.format( - 'DD-MM-YYYY' - )}&time=all-day` - }, - parser: function ({ content, date }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - const prev = programs[programs.length - 1] - const $item = cheerio.load(item) - let start = parseStart($item, date) - if (!start) return - if (prev && start < prev.start) { - start = start.plus({ days: 1 }) - } - const duration = parseDuration($item) - const stop = start.plus({ minutes: duration }) - programs.push({ - title: parseTitle($item), - description: parseDescription($item), - categories: parseCategories($item), - image: parseImage($item), - start, - stop - }) - }) - - return programs - }, - async channels() { - const data = await axios - .get('https://entertainment.ie/tv/all-channels/') - .then(r => r.data) - .catch(console.log) - const $ = cheerio.load(data) - let channels = $('.tv-filter-container > tv-filter').attr(':channels') - channels = JSON.parse(channels) - - return channels.map(c => { - return { - lang: 'en', - site_id: c.slug, - name: c.name - } - }) - } -} - -function parseImage($item) { - return $item('.text-holder > .btn-hold > .btn-wrap > a.btn-share').data('img') -} - -function parseTitle($item) { - return $item('.text-holder h3').text().trim() -} - -function parseDescription($item) { - return $item('.text-holder > .btn-hold > .btn-wrap > a.btn-share').data('description') -} - -function parseCategories($item) { - const genres = $item('.text-holder > .btn-hold > .btn-wrap > a.btn-share').data('genres') - - return genres ? genres.split(', ') : [] -} - -function parseStart($item, date) { - let d = $item('.text-holder > .btn-hold > .btn-wrap > a.btn-share').data('time') - let [, time] = d ? d.split(', ') : [null, null] - - return time - ? DateTime.fromFormat(`${date.format('YYYY-MM-DD')} ${time}`, 'yyyy-MM-dd HH:mm', { - zone: 'UTC' - }).toUTC() - : null -} - -function parseDuration($item) { - const duration = $item('.text-holder > .btn-hold > .btn-wrap > a.btn-share').data('duration') - - return parseInt(duration) -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('.info-list > li').toArray() -} +const axios = require('axios') +const cheerio = require('cheerio') +const { DateTime } = require('luxon') + +module.exports = { + site: 'entertainment.ie', + days: 2, + url: function ({ date, channel }) { + return `https://entertainment.ie/tv/${channel.site_id}/?date=${date.format( + 'DD-MM-YYYY' + )}&time=all-day` + }, + parser: function ({ content, date }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + const prev = programs[programs.length - 1] + const $item = cheerio.load(item) + let start = parseStart($item, date) + if (!start) return + if (prev && start < prev.start) { + start = start.plus({ days: 1 }) + } + const duration = parseDuration($item) + const stop = start.plus({ minutes: duration }) + programs.push({ + title: parseTitle($item), + description: parseDescription($item), + categories: parseCategories($item), + image: parseImage($item), + start, + stop + }) + }) + + return programs + }, + async channels() { + const data = await axios + .get('https://entertainment.ie/tv/all-channels/') + .then(r => r.data) + .catch(console.log) + const $ = cheerio.load(data) + let channels = $('.tv-filter-container > tv-filter').attr(':channels') + channels = JSON.parse(channels) + + return channels.map(c => { + return { + lang: 'en', + site_id: c.slug, + name: c.name + } + }) + } +} + +function parseImage($item) { + return $item('.text-holder > .btn-hold > .btn-wrap > a.btn-share').data('img') +} + +function parseTitle($item) { + return $item('.text-holder h3').text().trim() +} + +function parseDescription($item) { + return $item('.text-holder > .btn-hold > .btn-wrap > a.btn-share').data('description') +} + +function parseCategories($item) { + const genres = $item('.text-holder > .btn-hold > .btn-wrap > a.btn-share').data('genres') + + return genres ? genres.split(', ') : [] +} + +function parseStart($item, date) { + let d = $item('.text-holder > .btn-hold > .btn-wrap > a.btn-share').data('time') + let [, time] = d ? d.split(', ') : [null, null] + + return time + ? DateTime.fromFormat(`${date.format('YYYY-MM-DD')} ${time}`, 'yyyy-MM-dd HH:mm', { + zone: 'UTC' + }).toUTC() + : null +} + +function parseDuration($item) { + const duration = $item('.text-holder > .btn-hold > .btn-wrap > a.btn-share').data('duration') + + return parseInt(duration) +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('.info-list > li').toArray() +} diff --git a/sites/entertainment.ie/entertainment.ie.test.js b/sites/entertainment.ie/entertainment.ie.test.js index eaf660dd5..5a35e68e5 100644 --- a/sites/entertainment.ie/entertainment.ie.test.js +++ b/sites/entertainment.ie/entertainment.ie.test.js @@ -1,58 +1,58 @@ -const fs = require('fs') -const path = require('path') -const { parser, url } = require('./entertainment.ie.config.js') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-06-29', 'YYYY-MM-DD').startOf('d') -const channel = { site_id: 'rte2', xmltv_id: 'RTE2.ie' } - -it('can generate valid url', () => { - expect(url({ date, channel })).toBe( - 'https://entertainment.ie/tv/rte2/?date=29-06-2023&time=all-day' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - const results = parser({ date, content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(51) - - expect(results[0]).toMatchObject({ - start: '2023-06-29T06:00:00.000Z', - stop: '2023-06-29T08:00:00.000Z', - title: 'EuroNews', - description: 'European and international headlines live via satellite', - image: - 'https://img.resized.co/entertainment/eyJkYXRhIjoie1widXJsXCI6XCJodHRwczpcXFwvXFxcL3R2LmFzc2V0cy5wcmVzc2Fzc29jaWF0aW9uLmlvXFxcLzcxZDdkYWY2LWQxMjItNTliYy1iMGRjLTFkMjc2ODg1MzhkNC5qcGdcIixcIndpZHRoXCI6NDgwLFwiaGVpZ2h0XCI6Mjg4LFwiZGVmYXVsdFwiOlwiaHR0cHM6XFxcL1xcXC9lbnRlcnRhaW5tZW50LmllXFxcL2ltYWdlc1xcXC9uby1pbWFnZS5wbmdcIn0iLCJoYXNoIjoiZDhjYzA0NzFhMGZhOTI1Yjc5ODI0M2E3OWZjMGI2ZGJmMDIxMjllNyJ9/71d7daf6-d122-59bc-b0dc-1d27688538d4.jpg', - categories: ['Factual'] - }) - - expect(results[50]).toMatchObject({ - start: '2023-06-30T02:25:00.000Z', - stop: '2023-06-30T06:00:00.000Z', - title: 'EuroNews', - description: 'European and international headlines live via satellite', - image: - 'https://img.resized.co/entertainment/eyJkYXRhIjoie1widXJsXCI6XCJodHRwczpcXFwvXFxcL3R2LmFzc2V0cy5wcmVzc2Fzc29jaWF0aW9uLmlvXFxcLzcxZDdkYWY2LWQxMjItNTliYy1iMGRjLTFkMjc2ODg1MzhkNC5qcGdcIixcIndpZHRoXCI6NDgwLFwiaGVpZ2h0XCI6Mjg4LFwiZGVmYXVsdFwiOlwiaHR0cHM6XFxcL1xcXC9lbnRlcnRhaW5tZW50LmllXFxcL2ltYWdlc1xcXC9uby1pbWFnZS5wbmdcIn0iLCJoYXNoIjoiZDhjYzA0NzFhMGZhOTI1Yjc5ODI0M2E3OWZjMGI2ZGJmMDIxMjllNyJ9/71d7daf6-d122-59bc-b0dc-1d27688538d4.jpg', - categories: ['Factual'] - }) -}) - -it('can handle empty guide', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/no-content.html')) - const result = parser({ - date, - channel, - content - }) - expect(result).toMatchObject([]) -}) +const fs = require('fs') +const path = require('path') +const { parser, url } = require('./entertainment.ie.config.js') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-06-29', 'YYYY-MM-DD').startOf('d') +const channel = { site_id: 'rte2', xmltv_id: 'RTE2.ie' } + +it('can generate valid url', () => { + expect(url({ date, channel })).toBe( + 'https://entertainment.ie/tv/rte2/?date=29-06-2023&time=all-day' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + const results = parser({ date, content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(51) + + expect(results[0]).toMatchObject({ + start: '2023-06-29T06:00:00.000Z', + stop: '2023-06-29T08:00:00.000Z', + title: 'EuroNews', + description: 'European and international headlines live via satellite', + image: + 'https://img.resized.co/entertainment/eyJkYXRhIjoie1widXJsXCI6XCJodHRwczpcXFwvXFxcL3R2LmFzc2V0cy5wcmVzc2Fzc29jaWF0aW9uLmlvXFxcLzcxZDdkYWY2LWQxMjItNTliYy1iMGRjLTFkMjc2ODg1MzhkNC5qcGdcIixcIndpZHRoXCI6NDgwLFwiaGVpZ2h0XCI6Mjg4LFwiZGVmYXVsdFwiOlwiaHR0cHM6XFxcL1xcXC9lbnRlcnRhaW5tZW50LmllXFxcL2ltYWdlc1xcXC9uby1pbWFnZS5wbmdcIn0iLCJoYXNoIjoiZDhjYzA0NzFhMGZhOTI1Yjc5ODI0M2E3OWZjMGI2ZGJmMDIxMjllNyJ9/71d7daf6-d122-59bc-b0dc-1d27688538d4.jpg', + categories: ['Factual'] + }) + + expect(results[50]).toMatchObject({ + start: '2023-06-30T02:25:00.000Z', + stop: '2023-06-30T06:00:00.000Z', + title: 'EuroNews', + description: 'European and international headlines live via satellite', + image: + 'https://img.resized.co/entertainment/eyJkYXRhIjoie1widXJsXCI6XCJodHRwczpcXFwvXFxcL3R2LmFzc2V0cy5wcmVzc2Fzc29jaWF0aW9uLmlvXFxcLzcxZDdkYWY2LWQxMjItNTliYy1iMGRjLTFkMjc2ODg1MzhkNC5qcGdcIixcIndpZHRoXCI6NDgwLFwiaGVpZ2h0XCI6Mjg4LFwiZGVmYXVsdFwiOlwiaHR0cHM6XFxcL1xcXC9lbnRlcnRhaW5tZW50LmllXFxcL2ltYWdlc1xcXC9uby1pbWFnZS5wbmdcIn0iLCJoYXNoIjoiZDhjYzA0NzFhMGZhOTI1Yjc5ODI0M2E3OWZjMGI2ZGJmMDIxMjllNyJ9/71d7daf6-d122-59bc-b0dc-1d27688538d4.jpg', + categories: ['Factual'] + }) +}) + +it('can handle empty guide', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/no-content.html')) + const result = parser({ + date, + channel, + content + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/epg.112114.xyz/epg.112114.xyz.config.js b/sites/epg.112114.xyz/epg.112114.xyz.config.js index dc010944d..e512fa208 100644 --- a/sites/epg.112114.xyz/epg.112114.xyz.config.js +++ b/sites/epg.112114.xyz/epg.112114.xyz.config.js @@ -1,45 +1,45 @@ -const axios = require('axios') -const parser = require('epg-parser') - -module.exports = { - site: 'epg.112114.xyz', - days: 1, - url: 'https://epg.112114.xyz/pp.xml', - request: { - cache: { - ttl: 24 * 60 * 60 * 1000 // 1 day - } - }, - parser: function ({ content, channel, date }) { - let programs = [] - const items = parseItems(content, channel, date) - items.forEach(item => { - programs.push({ - title: item.title?.[0]?.value, - start: item.start, - stop: item.stop - }) - }) - - return programs - }, - async channels() { - const data = await axios - .get('https://epg.112114.xyz/pp.xml') - .then(r => r.data) - .catch(console.log) - const { channels } = parser.parse(data) - - return channels.map(channel => ({ - lang: 'zh', - site_id: channel.id, - name: channel.displayName[0].value - })) - } -} - -function parseItems(content, channel, date) { - const { programs } = parser.parse(content) - - return programs.filter(p => p.channel === channel.site_id && date.isSame(p.start, 'day')) -} +const axios = require('axios') +const parser = require('epg-parser') + +module.exports = { + site: 'epg.112114.xyz', + days: 1, + url: 'https://epg.112114.xyz/pp.xml', + request: { + cache: { + ttl: 24 * 60 * 60 * 1000 // 1 day + } + }, + parser: function ({ content, channel, date }) { + let programs = [] + const items = parseItems(content, channel, date) + items.forEach(item => { + programs.push({ + title: item.title?.[0]?.value, + start: item.start, + stop: item.stop + }) + }) + + return programs + }, + async channels() { + const data = await axios + .get('https://epg.112114.xyz/pp.xml') + .then(r => r.data) + .catch(console.log) + const { channels } = parser.parse(data) + + return channels.map(channel => ({ + lang: 'zh', + site_id: channel.id, + name: channel.displayName[0].value + })) + } +} + +function parseItems(content, channel, date) { + const { programs } = parser.parse(content) + + return programs.filter(p => p.channel === channel.site_id && date.isSame(p.start, 'day')) +} diff --git a/sites/epg.112114.xyz/epg.112114.xyz.test.js b/sites/epg.112114.xyz/epg.112114.xyz.test.js index ea6647c80..9e0b7acd7 100644 --- a/sites/epg.112114.xyz/epg.112114.xyz.test.js +++ b/sites/epg.112114.xyz/epg.112114.xyz.test.js @@ -1,42 +1,42 @@ -const { parser, url } = require('./epg.112114.xyz.config.js') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const fs = require('fs') -const path = require('path') - -dayjs.extend(utc) -dayjs.extend(timezone) - -const date = dayjs.utc('2025-01-11', 'YYYY-MM-DD').startOf('d') -const channel = { site_id: 'BTV文艺', xmltv_id: 'BRTVArtsChannel.cn', lang: 'zh' } - -it('can generate valid url', () => { - expect(url).toBe('https://epg.112114.xyz/pp.xml') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.xml')) - const results = parser({ date, content, channel }) - - expect(results.length).toBe(28) - expect(results[0]).toMatchObject({ - start: '2025-01-11T00:07:00.000Z', - stop: '2025-01-11T00:24:00.000Z', - title: '每日文艺播报' - }) - expect(results[27]).toMatchObject({ - start: '2025-01-11T15:16:00.000Z', - stop: '2025-01-11T15:59:00.000Z', - title: '笑动剧场' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: '' - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./epg.112114.xyz.config.js') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const fs = require('fs') +const path = require('path') + +dayjs.extend(utc) +dayjs.extend(timezone) + +const date = dayjs.utc('2025-01-11', 'YYYY-MM-DD').startOf('d') +const channel = { site_id: 'BTV文艺', xmltv_id: 'BRTVArtsChannel.cn', lang: 'zh' } + +it('can generate valid url', () => { + expect(url).toBe('https://epg.112114.xyz/pp.xml') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.xml')) + const results = parser({ date, content, channel }) + + expect(results.length).toBe(28) + expect(results[0]).toMatchObject({ + start: '2025-01-11T00:07:00.000Z', + stop: '2025-01-11T00:24:00.000Z', + title: '每日文艺播报' + }) + expect(results[27]).toMatchObject({ + start: '2025-01-11T15:16:00.000Z', + stop: '2025-01-11T15:59:00.000Z', + title: '笑动剧场' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: '' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/epg.iptvx.one/epg.iptvx.one.config.js b/sites/epg.iptvx.one/epg.iptvx.one.config.js index 2b4d9419d..2087e8ae0 100644 --- a/sites/epg.iptvx.one/epg.iptvx.one.config.js +++ b/sites/epg.iptvx.one/epg.iptvx.one.config.js @@ -1,64 +1,64 @@ -const axios = require('axios') -const iconv = require('iconv-lite') -const parser = require('epg-parser') -const { ungzip } = require('pako') - -let cachedContent - -module.exports = { - site: 'epg.iptvx.one', - days: 2, - url: 'https://iptvx.one/epg/epg_noarch.xml.gz', - request: { - maxContentLength: 500000000, // 500 MB - cache: { - ttl: 24 * 60 * 60 * 1000 // 1 day - } - }, - parser: function ({ buffer, channel, date, cached }) { - if (!cached) cachedContent = undefined - - let programs = [] - const items = parseItems(buffer, channel, date) - items.forEach(item => { - programs.push({ - title: item.title?.[0]?.value, - description: item.desc?.[0]?.value, - start: item.start, - stop: item.stop - }) - }) - - return programs - }, - async channels() { - const data = await axios - .get('https://epg.iptvx.one/api/channels.json') - .then(r => r.data) - .catch(console.log) - - return data.channels.map(channel => { - const [name] = channel.chan_names.split(' • ') - - return { - lang: 'ru', - site_id: channel.chan_id, - name - } - }) - } -} - -function parseItems(buffer, channel, date) { - if (!buffer) return [] - - if (!cachedContent) { - const content = ungzip(buffer) - const encoded = iconv.decode(content, 'utf8') - cachedContent = parser.parse(encoded) - } - - const { programs } = cachedContent - - return programs.filter(p => p.channel === channel.site_id && date.isSame(p.start, 'day')) -} +const axios = require('axios') +const iconv = require('iconv-lite') +const parser = require('epg-parser') +const { ungzip } = require('pako') + +let cachedContent + +module.exports = { + site: 'epg.iptvx.one', + days: 2, + url: 'https://iptvx.one/epg/epg_noarch.xml.gz', + request: { + maxContentLength: 500000000, // 500 MB + cache: { + ttl: 24 * 60 * 60 * 1000 // 1 day + } + }, + parser: function ({ buffer, channel, date, cached }) { + if (!cached) cachedContent = undefined + + let programs = [] + const items = parseItems(buffer, channel, date) + items.forEach(item => { + programs.push({ + title: item.title?.[0]?.value, + description: item.desc?.[0]?.value, + start: item.start, + stop: item.stop + }) + }) + + return programs + }, + async channels() { + const data = await axios + .get('https://epg.iptvx.one/api/channels.json') + .then(r => r.data) + .catch(console.log) + + return data.channels.map(channel => { + const [name] = channel.chan_names.split(' • ') + + return { + lang: 'ru', + site_id: channel.chan_id, + name + } + }) + } +} + +function parseItems(buffer, channel, date) { + if (!buffer) return [] + + if (!cachedContent) { + const content = ungzip(buffer) + const encoded = iconv.decode(content, 'utf8') + cachedContent = parser.parse(encoded) + } + + const { programs } = cachedContent + + return programs.filter(p => p.channel === channel.site_id && date.isSame(p.start, 'day')) +} diff --git a/sites/epg.iptvx.one/epg.iptvx.one.test.js b/sites/epg.iptvx.one/epg.iptvx.one.test.js index a35d14a4c..1d3a38886 100644 --- a/sites/epg.iptvx.one/epg.iptvx.one.test.js +++ b/sites/epg.iptvx.one/epg.iptvx.one.test.js @@ -1,46 +1,46 @@ -const { parser, url } = require('./epg.iptvx.one.config.js') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const fs = require('fs') -const path = require('path') - -dayjs.extend(utc) -dayjs.extend(timezone) - -const date = dayjs.utc('2025-01-13', 'YYYY-MM-DD').startOf('d') -const channel = { site_id: '12-omsk', xmltv_id: 'Channel12.ru' } - -it('can generate valid url', () => { - expect(url).toBe('https://iptvx.one/epg/epg_noarch.xml.gz') -}) - -it('can parse response', () => { - const buffer = fs.readFileSync(path.resolve(__dirname, '__data__/content.xml.gz')) - const results = parser({ date, buffer, channel }) - - expect(results.length).toBe(29) - expect(results[0]).toMatchObject({ - start: '2025-01-13T00:00:00.000Z', - stop: '2025-01-13T00:55:00.000Z', - title: 'Акценты недели', - description: - 'Программа расскажет зрителям о том, как развивались самые яркие события недели, поможет расставить акценты над самыми обсуждаемыми новостями. Россия, ток-шоу' - }) - expect(results[28]).toMatchObject({ - start: '2025-01-13T22:15:00.000Z', - stop: '2025-01-14T00:00:00.000Z', - title: 'д/с Необыкновенные люди', - description: - 'Герои цикла – врачи, спортсмены, представители творческих профессий, волонтеры и многие-многие другие. Их деятельность связана с жизнью особенных людей. Россия, док. сериал' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - buffer: '' - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./epg.iptvx.one.config.js') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const fs = require('fs') +const path = require('path') + +dayjs.extend(utc) +dayjs.extend(timezone) + +const date = dayjs.utc('2025-01-13', 'YYYY-MM-DD').startOf('d') +const channel = { site_id: '12-omsk', xmltv_id: 'Channel12.ru' } + +it('can generate valid url', () => { + expect(url).toBe('https://iptvx.one/epg/epg_noarch.xml.gz') +}) + +it('can parse response', () => { + const buffer = fs.readFileSync(path.resolve(__dirname, '__data__/content.xml.gz')) + const results = parser({ date, buffer, channel }) + + expect(results.length).toBe(29) + expect(results[0]).toMatchObject({ + start: '2025-01-13T00:00:00.000Z', + stop: '2025-01-13T00:55:00.000Z', + title: 'Акценты недели', + description: + 'Программа расскажет зрителям о том, как развивались самые яркие события недели, поможет расставить акценты над самыми обсуждаемыми новостями. Россия, ток-шоу' + }) + expect(results[28]).toMatchObject({ + start: '2025-01-13T22:15:00.000Z', + stop: '2025-01-14T00:00:00.000Z', + title: 'д/с Необыкновенные люди', + description: + 'Герои цикла – врачи, спортсмены, представители творческих профессий, волонтеры и многие-многие другие. Их деятельность связана с жизнью особенных людей. Россия, док. сериал' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + buffer: '' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/epg.telemach.ba/epg.telemach.ba.config.js b/sites/epg.telemach.ba/epg.telemach.ba.config.js index ccb9c9780..b927fc020 100644 --- a/sites/epg.telemach.ba/epg.telemach.ba.config.js +++ b/sites/epg.telemach.ba/epg.telemach.ba.config.js @@ -1,100 +1,100 @@ -const dayjs = require('dayjs') -const axios = require('axios') - -const BASIC_TOKEN = - 'MjdlMTFmNWUtODhlMi00OGU0LWJkNDItOGUxNWFiYmM2NmY1OjEyejJzMXJ3bXdhZmsxMGNkdzl0cjloOWFjYjZwdjJoZDhscXZ0aGc=' - -let session - -module.exports = { - site: 'epg.telemach.ba', - days: 3, - url({ channel, date }) { - return `https://api-web.ug-be.cdn.united.cloud/v1/public/events/epg?fromTime=${date.format( - 'YYYY-MM-DDTHH:mm:ss-00:00' - )}&toTime=${date - .add(1, 'days') - .subtract(1, 's') - .format('YYYY-MM-DDTHH:mm:ss-00:00')}&communityId=12&languageId=59&cid=${channel.site_id}` - }, - request: { - async headers() { - if (!session) { - session = await loadSessionDetails() - if (!session || !session.access_token) return null - } - - return { - Authorization: `Bearer ${session.access_token}` - } - } - }, - parser({ content }) { - try { - const programs = [] - const data = JSON.parse(content) - for (const channelId in data) { - if (Array.isArray(data[channelId])) { - data[channelId].forEach(item => { - programs.push({ - title: item.title, - description: item.shortDescription, - image: parseImage(item), - season: item.seasonNumber, - episode: item.episodeNumber, - start: dayjs(item.startTime), - stop: dayjs(item.endTime) - }) - }) - } - } - - return programs - } catch { - return [] - } - }, - async channels() { - const session = await loadSessionDetails() - if (!session || !session.access_token) return null - - const data = await axios - .get( - 'https://api-web.ug-be.cdn.united.cloud/v1/public/channels?channelType=TV&communityId=12&languageId=59&imageSize=L', - { - headers: { - Authorization: `Bearer ${session.access_token}` - } - } - ) - .then(r => r.data) - .catch(console.error) - - return data.map(item => ({ - lang: 'hr', - site_id: item.id, - name: item.name - })) - } -} - -function parseImage(item) { - const baseURL = 'https://images-web.ug-be.cdn.united.cloud' - - return Array.isArray(item?.images) && item.images[0] ? `${baseURL}${item.images[0].path}` : null -} - -function loadSessionDetails() { - return axios - .post( - 'https://api-web.ug-be.cdn.united.cloud/oauth/token?grant_type=client_credentials', - {}, - { - headers: { - Authorization: `Basic ${BASIC_TOKEN}` - } - } - ) - .then(r => r.data) - .catch(console.log) -} +const dayjs = require('dayjs') +const axios = require('axios') + +const BASIC_TOKEN = + 'MjdlMTFmNWUtODhlMi00OGU0LWJkNDItOGUxNWFiYmM2NmY1OjEyejJzMXJ3bXdhZmsxMGNkdzl0cjloOWFjYjZwdjJoZDhscXZ0aGc=' + +let session + +module.exports = { + site: 'epg.telemach.ba', + days: 3, + url({ channel, date }) { + return `https://api-web.ug-be.cdn.united.cloud/v1/public/events/epg?fromTime=${date.format( + 'YYYY-MM-DDTHH:mm:ss-00:00' + )}&toTime=${date + .add(1, 'days') + .subtract(1, 's') + .format('YYYY-MM-DDTHH:mm:ss-00:00')}&communityId=12&languageId=59&cid=${channel.site_id}` + }, + request: { + async headers() { + if (!session) { + session = await loadSessionDetails() + if (!session || !session.access_token) return null + } + + return { + Authorization: `Bearer ${session.access_token}` + } + } + }, + parser({ content }) { + try { + const programs = [] + const data = JSON.parse(content) + for (const channelId in data) { + if (Array.isArray(data[channelId])) { + data[channelId].forEach(item => { + programs.push({ + title: item.title, + description: item.shortDescription, + image: parseImage(item), + season: item.seasonNumber, + episode: item.episodeNumber, + start: dayjs(item.startTime), + stop: dayjs(item.endTime) + }) + }) + } + } + + return programs + } catch { + return [] + } + }, + async channels() { + const session = await loadSessionDetails() + if (!session || !session.access_token) return null + + const data = await axios + .get( + 'https://api-web.ug-be.cdn.united.cloud/v1/public/channels?channelType=TV&communityId=12&languageId=59&imageSize=L', + { + headers: { + Authorization: `Bearer ${session.access_token}` + } + } + ) + .then(r => r.data) + .catch(console.error) + + return data.map(item => ({ + lang: 'hr', + site_id: item.id, + name: item.name + })) + } +} + +function parseImage(item) { + const baseURL = 'https://images-web.ug-be.cdn.united.cloud' + + return Array.isArray(item?.images) && item.images[0] ? `${baseURL}${item.images[0].path}` : null +} + +function loadSessionDetails() { + return axios + .post( + 'https://api-web.ug-be.cdn.united.cloud/oauth/token?grant_type=client_credentials', + {}, + { + headers: { + Authorization: `Basic ${BASIC_TOKEN}` + } + } + ) + .then(r => r.data) + .catch(console.log) +} diff --git a/sites/epg.telemach.ba/epg.telemach.ba.test.js b/sites/epg.telemach.ba/epg.telemach.ba.test.js index 56836b4da..518193442 100644 --- a/sites/epg.telemach.ba/epg.telemach.ba.test.js +++ b/sites/epg.telemach.ba/epg.telemach.ba.test.js @@ -1,94 +1,94 @@ -const { parser, url, request } = require('./epg.telemach.ba.config.js') -const fs = require('fs') -const axios = require('axios') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -axios.post.mockImplementation((url, data, opts) => { - if ( - url === 'https://api-web.ug-be.cdn.united.cloud/oauth/token?grant_type=client_credentials' && - JSON.stringify(opts.headers) === - JSON.stringify({ - Authorization: - 'Basic MjdlMTFmNWUtODhlMi00OGU0LWJkNDItOGUxNWFiYmM2NmY1OjEyejJzMXJ3bXdhZmsxMGNkdzl0cjloOWFjYjZwdjJoZDhscXZ0aGc=' - }) - ) { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/session.json'))) - }) - } else { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/no_session.json'))) - }) - } -}) - -const date = dayjs.utc('2025-01-20', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '1607', - xmltv_id: 'N1HD.hr' -} - -it('can generate valid url', async () => { - const result = url({ date, channel }) - - expect(result).toBe( - 'https://api-web.ug-be.cdn.united.cloud/v1/public/events/epg?fromTime=2025-01-20T00:00:00-00:00&toTime=2025-01-20T23:59:59-00:00&communityId=12&languageId=59&cid=1607' - ) -}) - -it('can generate valid request headers', async () => { - const result = await request.headers() - - expect(result).toMatchObject({ - Authorization: - 'Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsidWMtaW5mby1zZXJ2aWNlIl0sInNjb3BlIjpbInJlYWQiXSwiZXhwIjoxNzM3Mzc3NDUxLCJhdXRob3JpdGllcyI6WyJST0xFX1BVQkxJQ19FUEciXSwianRpIjoiUVBubHdRSDczS1EwSnU0WDZwRTc2Zm5mUmRnIiwiY2xpZW50X2lkIjoiMjdlMTFmNWUtODhlMi00OGU0LWJkNDItOGUxNWFiYmM2NmY1In0.LqJAZUWEqIOcLrRSMpxZxnF-f1arKbHgfweLMXt-MBjCDbVJD39OQEsh_b68mtePAoa3n8LRbf3IFT40Ys5Vbe-k_Btm4a9gdEGr6cNi_4HGk4Bto6RUDvCp59VRfoRZhWe145Q2b5TS6szmC4Ws2YWIcZU5vrJcYs2GZiCk6U11MOcd1i52WmZj8cLPq0ZPDB_bzmTgYkvkVa7zOzUOPSl4M8T6fPUa__vVKUt0jOgtFoHeue2mQVgISC2puEGsBN0jJwvJ8PzM6IVxXrQno3MBv0VJy_qILiFPcxRePGRAmKLuEqagvikO7P_XQgFjZgg-j8u8wX2WwO0Yxft0Pg' - }) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') - let results = parser({ content }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(35) - expect(results[0]).toMatchObject({ - start: '2025-01-20T00:00:00.000Z', - stop: '2025-01-20T00:30:00.000Z', - title: 'DW Euromaxx', - description: - 'Euromaxx je lifestyle Europe magazine, koji nam donosi zanimljivosti iz evropskih gradova, priče o načinu života ljudi i upoznaje nas sa njihovim kulturama.', - image: - 'https://images-web.ug-be.cdn.united.cloud/2021/02/18/06/05/21/stb_xl_cd4f72e01d308ecce782e29b69af7de6707b9e85.jpg', - season: null, - episode: null - }) - expect(results[34]).toMatchObject({ - start: '2025-01-20T23:50:00.000Z', - stop: '2025-01-21T00:00:00.000Z', - title: 'DW Shift', - description: 'Tjedni magazin koji nam donosi najnovije vijesti vezane za Internet.', - image: - 'https://images-web.ug-be.cdn.united.cloud/2023/06/09/13/07/53/stb_xl_0849d5d70c1337651b85b6335e340e15bd5d6a73_340fc454bc73019d052cf936ebee5da3.jpg', - season: null, - episode: null - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json'), 'utf8') - }) - - expect(results).toMatchObject([]) -}) +const { parser, url, request } = require('./epg.telemach.ba.config.js') +const fs = require('fs') +const axios = require('axios') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +axios.post.mockImplementation((url, data, opts) => { + if ( + url === 'https://api-web.ug-be.cdn.united.cloud/oauth/token?grant_type=client_credentials' && + JSON.stringify(opts.headers) === + JSON.stringify({ + Authorization: + 'Basic MjdlMTFmNWUtODhlMi00OGU0LWJkNDItOGUxNWFiYmM2NmY1OjEyejJzMXJ3bXdhZmsxMGNkdzl0cjloOWFjYjZwdjJoZDhscXZ0aGc=' + }) + ) { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/session.json'))) + }) + } else { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/no_session.json'))) + }) + } +}) + +const date = dayjs.utc('2025-01-20', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '1607', + xmltv_id: 'N1HD.hr' +} + +it('can generate valid url', async () => { + const result = url({ date, channel }) + + expect(result).toBe( + 'https://api-web.ug-be.cdn.united.cloud/v1/public/events/epg?fromTime=2025-01-20T00:00:00-00:00&toTime=2025-01-20T23:59:59-00:00&communityId=12&languageId=59&cid=1607' + ) +}) + +it('can generate valid request headers', async () => { + const result = await request.headers() + + expect(result).toMatchObject({ + Authorization: + 'Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsidWMtaW5mby1zZXJ2aWNlIl0sInNjb3BlIjpbInJlYWQiXSwiZXhwIjoxNzM3Mzc3NDUxLCJhdXRob3JpdGllcyI6WyJST0xFX1BVQkxJQ19FUEciXSwianRpIjoiUVBubHdRSDczS1EwSnU0WDZwRTc2Zm5mUmRnIiwiY2xpZW50X2lkIjoiMjdlMTFmNWUtODhlMi00OGU0LWJkNDItOGUxNWFiYmM2NmY1In0.LqJAZUWEqIOcLrRSMpxZxnF-f1arKbHgfweLMXt-MBjCDbVJD39OQEsh_b68mtePAoa3n8LRbf3IFT40Ys5Vbe-k_Btm4a9gdEGr6cNi_4HGk4Bto6RUDvCp59VRfoRZhWe145Q2b5TS6szmC4Ws2YWIcZU5vrJcYs2GZiCk6U11MOcd1i52WmZj8cLPq0ZPDB_bzmTgYkvkVa7zOzUOPSl4M8T6fPUa__vVKUt0jOgtFoHeue2mQVgISC2puEGsBN0jJwvJ8PzM6IVxXrQno3MBv0VJy_qILiFPcxRePGRAmKLuEqagvikO7P_XQgFjZgg-j8u8wX2WwO0Yxft0Pg' + }) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') + let results = parser({ content }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(35) + expect(results[0]).toMatchObject({ + start: '2025-01-20T00:00:00.000Z', + stop: '2025-01-20T00:30:00.000Z', + title: 'DW Euromaxx', + description: + 'Euromaxx je lifestyle Europe magazine, koji nam donosi zanimljivosti iz evropskih gradova, priče o načinu života ljudi i upoznaje nas sa njihovim kulturama.', + image: + 'https://images-web.ug-be.cdn.united.cloud/2021/02/18/06/05/21/stb_xl_cd4f72e01d308ecce782e29b69af7de6707b9e85.jpg', + season: null, + episode: null + }) + expect(results[34]).toMatchObject({ + start: '2025-01-20T23:50:00.000Z', + stop: '2025-01-21T00:00:00.000Z', + title: 'DW Shift', + description: 'Tjedni magazin koji nam donosi najnovije vijesti vezane za Internet.', + image: + 'https://images-web.ug-be.cdn.united.cloud/2023/06/09/13/07/53/stb_xl_0849d5d70c1337651b85b6335e340e15bd5d6a73_340fc454bc73019d052cf936ebee5da3.jpg', + season: null, + episode: null + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json'), 'utf8') + }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/epg.telemach.me/epg.telemach.me.config.js b/sites/epg.telemach.me/epg.telemach.me.config.js index 46522150a..d559882ad 100644 --- a/sites/epg.telemach.me/epg.telemach.me.config.js +++ b/sites/epg.telemach.me/epg.telemach.me.config.js @@ -1,101 +1,101 @@ -const dayjs = require('dayjs') -const axios = require('axios') - -const BASIC_TOKEN = - 'MjdlMTFmNWUtODhlMi00OGU0LWJkNDItOGUxNWFiYmM2NmY1OjEyejJzMXJ3bXdhZmsxMGNkdzl0cjloOWFjYjZwdjJoZDhscXZ0aGc=' - -let session - -module.exports = { - site: 'epg.telemach.me', - days: 3, - url({ channel, date }) { - return `https://api-web.ug-be.cdn.united.cloud/v1/public/events/epg?fromTime=${date.format( - 'YYYY-MM-DDTHH:mm:ss-00:00' - )}&toTime=${date - .add(1, 'days') - .subtract(1, 's') - .format('YYYY-MM-DDTHH:mm:ss-00:00')}&communityId=5&languageId=10001&cid=${channel.site_id}` - }, - request: { - async headers() { - if (!session) { - session = await loadSessionDetails() - if (!session || !session.access_token) return null - } - - return { - Authorization: `Bearer ${session.access_token}`, - Referer: 'https://epg.telemach.me/' - } - } - }, - parser({ content }) { - try { - const programs = [] - const data = JSON.parse(content) - for (const channelId in data) { - if (Array.isArray(data[channelId])) { - data[channelId].forEach(item => { - programs.push({ - title: item.title, - description: item.shortDescription, - image: parseImage(item), - season: item.seasonNumber, - episode: item.episodeNumber, - start: dayjs(item.startTime), - stop: dayjs(item.endTime) - }) - }) - } - } - - return programs - } catch { - return [] - } - }, - async channels() { - const session = await loadSessionDetails() - if (!session || !session.access_token) return null - - const data = await axios - .get( - 'https://api-web.ug-be.cdn.united.cloud/v1/public/channels?channelType=TV&communityId=5&languageId=10001&imageSize=L', - { - headers: { - Authorization: `Bearer ${session.access_token}` - } - } - ) - .then(r => r.data) - .catch(console.error) - - return data.map(item => ({ - lang: 'bs', - site_id: item.id, - name: item.name - })) - } -} - -function parseImage(item) { - const baseURL = 'https://images-web.ug-be.cdn.united.cloud' - - return Array.isArray(item?.images) && item.images[0] ? `${baseURL}${item.images[0].path}` : null -} - -function loadSessionDetails() { - return axios - .post( - 'https://api-web.ug-be.cdn.united.cloud/oauth/token?grant_type=client_credentials', - {}, - { - headers: { - Authorization: `Basic ${BASIC_TOKEN}` - } - } - ) - .then(r => r.data) - .catch(console.log) -} +const dayjs = require('dayjs') +const axios = require('axios') + +const BASIC_TOKEN = + 'MjdlMTFmNWUtODhlMi00OGU0LWJkNDItOGUxNWFiYmM2NmY1OjEyejJzMXJ3bXdhZmsxMGNkdzl0cjloOWFjYjZwdjJoZDhscXZ0aGc=' + +let session + +module.exports = { + site: 'epg.telemach.me', + days: 3, + url({ channel, date }) { + return `https://api-web.ug-be.cdn.united.cloud/v1/public/events/epg?fromTime=${date.format( + 'YYYY-MM-DDTHH:mm:ss-00:00' + )}&toTime=${date + .add(1, 'days') + .subtract(1, 's') + .format('YYYY-MM-DDTHH:mm:ss-00:00')}&communityId=5&languageId=10001&cid=${channel.site_id}` + }, + request: { + async headers() { + if (!session) { + session = await loadSessionDetails() + if (!session || !session.access_token) return null + } + + return { + Authorization: `Bearer ${session.access_token}`, + Referer: 'https://epg.telemach.me/' + } + } + }, + parser({ content }) { + try { + const programs = [] + const data = JSON.parse(content) + for (const channelId in data) { + if (Array.isArray(data[channelId])) { + data[channelId].forEach(item => { + programs.push({ + title: item.title, + description: item.shortDescription, + image: parseImage(item), + season: item.seasonNumber, + episode: item.episodeNumber, + start: dayjs(item.startTime), + stop: dayjs(item.endTime) + }) + }) + } + } + + return programs + } catch { + return [] + } + }, + async channels() { + const session = await loadSessionDetails() + if (!session || !session.access_token) return null + + const data = await axios + .get( + 'https://api-web.ug-be.cdn.united.cloud/v1/public/channels?channelType=TV&communityId=5&languageId=10001&imageSize=L', + { + headers: { + Authorization: `Bearer ${session.access_token}` + } + } + ) + .then(r => r.data) + .catch(console.error) + + return data.map(item => ({ + lang: 'bs', + site_id: item.id, + name: item.name + })) + } +} + +function parseImage(item) { + const baseURL = 'https://images-web.ug-be.cdn.united.cloud' + + return Array.isArray(item?.images) && item.images[0] ? `${baseURL}${item.images[0].path}` : null +} + +function loadSessionDetails() { + return axios + .post( + 'https://api-web.ug-be.cdn.united.cloud/oauth/token?grant_type=client_credentials', + {}, + { + headers: { + Authorization: `Basic ${BASIC_TOKEN}` + } + } + ) + .then(r => r.data) + .catch(console.log) +} diff --git a/sites/epg.telemach.me/epg.telemach.me.test.js b/sites/epg.telemach.me/epg.telemach.me.test.js index ab0ac6c40..5299ab27f 100644 --- a/sites/epg.telemach.me/epg.telemach.me.test.js +++ b/sites/epg.telemach.me/epg.telemach.me.test.js @@ -1,96 +1,96 @@ -const { parser, url, request } = require('./epg.telemach.me.config.js') -const fs = require('fs') -const axios = require('axios') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -axios.post.mockImplementation((url, data, opts) => { - if ( - url === 'https://api-web.ug-be.cdn.united.cloud/oauth/token?grant_type=client_credentials' && - JSON.stringify(opts.headers) === - JSON.stringify({ - Authorization: - 'Basic MjdlMTFmNWUtODhlMi00OGU0LWJkNDItOGUxNWFiYmM2NmY1OjEyejJzMXJ3bXdhZmsxMGNkdzl0cjloOWFjYjZwdjJoZDhscXZ0aGc=' - }) - ) { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/session.json'))) - }) - } else { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/no_session.json'))) - }) - } -}) - -const date = dayjs.utc('2025-01-20', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '92', - xmltv_id: 'PinkKids.rs' -} - -it('can generate valid url', async () => { - const result = url({ date, channel }) - - expect(result).toBe( - 'https://api-web.ug-be.cdn.united.cloud/v1/public/events/epg?fromTime=2025-01-20T00:00:00-00:00&toTime=2025-01-20T23:59:59-00:00&communityId=5&languageId=10001&cid=92' - ) -}) - -it('can generate valid request headers', async () => { - const result = await request.headers() - - expect(result).toMatchObject({ - Authorization: - 'Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsidWMtaW5mby1zZXJ2aWNlIl0sInNjb3BlIjpbInJlYWQiXSwiZXhwIjoxNzM3Mzc3NDUxLCJhdXRob3JpdGllcyI6WyJST0xFX1BVQkxJQ19FUEciXSwianRpIjoiUVBubHdRSDczS1EwSnU0WDZwRTc2Zm5mUmRnIiwiY2xpZW50X2lkIjoiMjdlMTFmNWUtODhlMi00OGU0LWJkNDItOGUxNWFiYmM2NmY1In0.LqJAZUWEqIOcLrRSMpxZxnF-f1arKbHgfweLMXt-MBjCDbVJD39OQEsh_b68mtePAoa3n8LRbf3IFT40Ys5Vbe-k_Btm4a9gdEGr6cNi_4HGk4Bto6RUDvCp59VRfoRZhWe145Q2b5TS6szmC4Ws2YWIcZU5vrJcYs2GZiCk6U11MOcd1i52WmZj8cLPq0ZPDB_bzmTgYkvkVa7zOzUOPSl4M8T6fPUa__vVKUt0jOgtFoHeue2mQVgISC2puEGsBN0jJwvJ8PzM6IVxXrQno3MBv0VJy_qILiFPcxRePGRAmKLuEqagvikO7P_XQgFjZgg-j8u8wX2WwO0Yxft0Pg', - Referer: 'https://epg.telemach.me/' - }) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') - let results = parser({ content }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(55) - expect(results[0]).toMatchObject({ - start: '2025-01-19T23:20:00.000Z', - stop: '2025-01-20T00:10:00.000Z', - title: 'Pinkove Zvezdice', - description: - 'Četvrta sezona najgledanijeg dečijeg muzičkog takmičenja, "Pinkove zvezdice" došlo do promena, pa će tako gledaoci imati priliku da najtalentovaniju decu gledaju na novoj, spektakularnoj sceni. Nova...', - image: - 'https://images-web.ug-be.cdn.united.cloud/2023/06/22/11/19/19/stb_xl_115752ec1e05872b86ceda7726d347f533e17f43_340fc454bc73019d052cf936ebee5da3.jpg', - season: null, - episode: null - }) - expect(results[54]).toMatchObject({ - start: '2025-01-20T23:50:00.000Z', - stop: '2025-01-21T00:10:00.000Z', - title: 'Hajdi', - description: - 'Život nekada nije jednostavan. To najbolje zna Hajdi. Nakon što je ostala siroče, njena tetka je odvodi visoko u Alpe kod njenog dede. Ona uz nove prijatelje i dedu uskoro zavoli svoj novi život. Ipak...', - image: - 'https://images-web.ug-be.cdn.united.cloud/2024/05/10/14/49/09/stb_xl_7d1c73ee4df7de5c4157e9daccae098d50ee853d_99230e7f5bdc95451f37aa31f8425b4d.jpg', - season: null, - episode: null - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json'), 'utf8') - }) - - expect(results).toMatchObject([]) -}) +const { parser, url, request } = require('./epg.telemach.me.config.js') +const fs = require('fs') +const axios = require('axios') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +axios.post.mockImplementation((url, data, opts) => { + if ( + url === 'https://api-web.ug-be.cdn.united.cloud/oauth/token?grant_type=client_credentials' && + JSON.stringify(opts.headers) === + JSON.stringify({ + Authorization: + 'Basic MjdlMTFmNWUtODhlMi00OGU0LWJkNDItOGUxNWFiYmM2NmY1OjEyejJzMXJ3bXdhZmsxMGNkdzl0cjloOWFjYjZwdjJoZDhscXZ0aGc=' + }) + ) { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/session.json'))) + }) + } else { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/no_session.json'))) + }) + } +}) + +const date = dayjs.utc('2025-01-20', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '92', + xmltv_id: 'PinkKids.rs' +} + +it('can generate valid url', async () => { + const result = url({ date, channel }) + + expect(result).toBe( + 'https://api-web.ug-be.cdn.united.cloud/v1/public/events/epg?fromTime=2025-01-20T00:00:00-00:00&toTime=2025-01-20T23:59:59-00:00&communityId=5&languageId=10001&cid=92' + ) +}) + +it('can generate valid request headers', async () => { + const result = await request.headers() + + expect(result).toMatchObject({ + Authorization: + 'Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsidWMtaW5mby1zZXJ2aWNlIl0sInNjb3BlIjpbInJlYWQiXSwiZXhwIjoxNzM3Mzc3NDUxLCJhdXRob3JpdGllcyI6WyJST0xFX1BVQkxJQ19FUEciXSwianRpIjoiUVBubHdRSDczS1EwSnU0WDZwRTc2Zm5mUmRnIiwiY2xpZW50X2lkIjoiMjdlMTFmNWUtODhlMi00OGU0LWJkNDItOGUxNWFiYmM2NmY1In0.LqJAZUWEqIOcLrRSMpxZxnF-f1arKbHgfweLMXt-MBjCDbVJD39OQEsh_b68mtePAoa3n8LRbf3IFT40Ys5Vbe-k_Btm4a9gdEGr6cNi_4HGk4Bto6RUDvCp59VRfoRZhWe145Q2b5TS6szmC4Ws2YWIcZU5vrJcYs2GZiCk6U11MOcd1i52WmZj8cLPq0ZPDB_bzmTgYkvkVa7zOzUOPSl4M8T6fPUa__vVKUt0jOgtFoHeue2mQVgISC2puEGsBN0jJwvJ8PzM6IVxXrQno3MBv0VJy_qILiFPcxRePGRAmKLuEqagvikO7P_XQgFjZgg-j8u8wX2WwO0Yxft0Pg', + Referer: 'https://epg.telemach.me/' + }) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') + let results = parser({ content }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(55) + expect(results[0]).toMatchObject({ + start: '2025-01-19T23:20:00.000Z', + stop: '2025-01-20T00:10:00.000Z', + title: 'Pinkove Zvezdice', + description: + 'Četvrta sezona najgledanijeg dečijeg muzičkog takmičenja, "Pinkove zvezdice" došlo do promena, pa će tako gledaoci imati priliku da najtalentovaniju decu gledaju na novoj, spektakularnoj sceni. Nova...', + image: + 'https://images-web.ug-be.cdn.united.cloud/2023/06/22/11/19/19/stb_xl_115752ec1e05872b86ceda7726d347f533e17f43_340fc454bc73019d052cf936ebee5da3.jpg', + season: null, + episode: null + }) + expect(results[54]).toMatchObject({ + start: '2025-01-20T23:50:00.000Z', + stop: '2025-01-21T00:10:00.000Z', + title: 'Hajdi', + description: + 'Život nekada nije jednostavan. To najbolje zna Hajdi. Nakon što je ostala siroče, njena tetka je odvodi visoko u Alpe kod njenog dede. Ona uz nove prijatelje i dedu uskoro zavoli svoj novi život. Ipak...', + image: + 'https://images-web.ug-be.cdn.united.cloud/2024/05/10/14/49/09/stb_xl_7d1c73ee4df7de5c4157e9daccae098d50ee853d_99230e7f5bdc95451f37aa31f8425b4d.jpg', + season: null, + episode: null + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json'), 'utf8') + }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/epgmaster.com/epgmaster.com.config.js b/sites/epgmaster.com/epgmaster.com.config.js index deedd45ab..e1ed791a4 100644 --- a/sites/epgmaster.com/epgmaster.com.config.js +++ b/sites/epgmaster.com/epgmaster.com.config.js @@ -1,45 +1,45 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const TOKEN = '1610283054' - -module.exports = { - site: 'epgmaster.com', - url({ channel }) { - return `https://epgmaster.com/api/channels/${channel.site_id}/epgs?token=${TOKEN}` - }, - parser({ content, date }) { - return parseItems(content, date).map(item => { - return { - title: item.programName, - start: parseStart(item), - stop: parseStop(item) - } - }) - } -} - -function parseStart(item) { - return dayjs.utc(`${item.startDate} ${item.startTime}`, 'YYYY-MM-DD HH:mm:ss') -} - -function parseStop(item) { - return dayjs.utc(`${item.startDate} ${item.endTime}`, 'YYYY-MM-DD HH:mm:ss') -} - -function parseItems(content, date) { - try { - const data = JSON.parse(content) - if (!data || !Array.isArray(data)) return [] - const filtered = data.find(group => date.format('YYYY-MM-DD') === group.date) - if (!filtered || !Array.isArray(filtered.epgTokenList)) return [] - - return filtered.epgTokenList - } catch { - return [] - } -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const TOKEN = '1610283054' + +module.exports = { + site: 'epgmaster.com', + url({ channel }) { + return `https://epgmaster.com/api/channels/${channel.site_id}/epgs?token=${TOKEN}` + }, + parser({ content, date }) { + return parseItems(content, date).map(item => { + return { + title: item.programName, + start: parseStart(item), + stop: parseStop(item) + } + }) + } +} + +function parseStart(item) { + return dayjs.utc(`${item.startDate} ${item.startTime}`, 'YYYY-MM-DD HH:mm:ss') +} + +function parseStop(item) { + return dayjs.utc(`${item.startDate} ${item.endTime}`, 'YYYY-MM-DD HH:mm:ss') +} + +function parseItems(content, date) { + try { + const data = JSON.parse(content) + if (!data || !Array.isArray(data)) return [] + const filtered = data.find(group => date.format('YYYY-MM-DD') === group.date) + if (!filtered || !Array.isArray(filtered.epgTokenList)) return [] + + return filtered.epgTokenList + } catch { + return [] + } +} diff --git a/sites/epgmaster.com/epgmaster.com.test.js b/sites/epgmaster.com/epgmaster.com.test.js index f1f5c46bd..544d34b8e 100644 --- a/sites/epgmaster.com/epgmaster.com.test.js +++ b/sites/epgmaster.com/epgmaster.com.test.js @@ -1,45 +1,45 @@ -const { parser, url } = require('./epgmaster.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-05-18', 'YYYY-MM-DD').startOf('d') -const channel = { site_id: 'ntv' } - -it('can generate valid url', () => { - expect(url({ channel })).toBe('https://epgmaster.com/api/channels/ntv/epgs?token=1610283054') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - - let results = parser({ content, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - - return p - }) - - expect(results.length).toBe(46) - expect(results[0]).toMatchObject({ - title: 'Krishi Teleflim-Bharosa Yuwama', - start: '2025-05-18T00:00:00.000Z', - stop: '2025-05-18T00:15:00.000Z' - }) - expect(results[1]).toMatchObject({ - title: 'News in Nepali [Rec.]', - start: '2025-05-18T00:15:00.000Z', - stop: '2025-05-18T00:45:00.000Z' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ content: '', date }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./epgmaster.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-05-18', 'YYYY-MM-DD').startOf('d') +const channel = { site_id: 'ntv' } + +it('can generate valid url', () => { + expect(url({ channel })).toBe('https://epgmaster.com/api/channels/ntv/epgs?token=1610283054') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + + let results = parser({ content, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + + return p + }) + + expect(results.length).toBe(46) + expect(results[0]).toMatchObject({ + title: 'Krishi Teleflim-Bharosa Yuwama', + start: '2025-05-18T00:00:00.000Z', + stop: '2025-05-18T00:15:00.000Z' + }) + expect(results[1]).toMatchObject({ + title: 'News in Nepali [Rec.]', + start: '2025-05-18T00:15:00.000Z', + stop: '2025-05-18T00:45:00.000Z' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ content: '', date }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/epgshare01.online/epgshare01.online.config.js b/sites/epgshare01.online/epgshare01.online.config.js index 767e09b33..f3cd17b73 100644 --- a/sites/epgshare01.online/epgshare01.online.config.js +++ b/sites/epgshare01.online/epgshare01.online.config.js @@ -1,75 +1,75 @@ -const axios = require('axios') -const iconv = require('iconv-lite') -const parser = require('epg-parser') -const { ungzip } = require('pako') - -let cachedContent - -module.exports = { - site: 'epgshare01.online', - days: 2, - url({ channel }) { - const [tag] = channel.site_id.split('#') - - return `https://epgshare01.online/epgshare01/epg_ripper_${tag}.xml.gz` - }, - request: { - cache: { - ttl: 24 * 60 * 60 * 1000 // 1 day - }, - maxContentLength: 100000000 // 100 MB - }, - parser({ buffer, channel, date, cached }) { - if (!cached) cachedContent = undefined - - let programs = [] - const items = parseItems(buffer, channel, date) - items.forEach(item => { - programs.push({ - title: item.title?.[0]?.value, - description: item.desc?.[0]?.value, - start: item.start, - stop: item.stop - }) - }) - - return programs - }, - async channels({ tag }) { - const buffer = await axios - .get(`https://epgshare01.online/epgshare01/epg_ripper_${tag}.xml.gz`, { - responseType: 'arraybuffer' - }) - .then(r => r.data) - .catch(console.error) - - const content = ungzip(buffer) - const encoded = iconv.decode(content, 'utf8') - const { channels } = parser.parse(encoded) - - return channels.map(channel => { - const displayName = channel.displayName[0] - - return { - lang: displayName.lang || 'en', - site_id: `${tag}#${channel.id}`, - name: displayName.value - } - }) - } -} - -function parseItems(buffer, channel, date) { - if (!buffer) return [] - - if (!cachedContent) { - const content = ungzip(buffer) - const encoded = iconv.decode(content, 'utf8') - cachedContent = parser.parse(encoded) - } - - const { programs } = cachedContent - const [, channelId] = channel.site_id.split('#') - - return programs.filter(p => p.channel === channelId && date.isSame(p.start, 'day')) -} +const axios = require('axios') +const iconv = require('iconv-lite') +const parser = require('epg-parser') +const { ungzip } = require('pako') + +let cachedContent + +module.exports = { + site: 'epgshare01.online', + days: 2, + url({ channel }) { + const [tag] = channel.site_id.split('#') + + return `https://epgshare01.online/epgshare01/epg_ripper_${tag}.xml.gz` + }, + request: { + cache: { + ttl: 24 * 60 * 60 * 1000 // 1 day + }, + maxContentLength: 100000000 // 100 MB + }, + parser({ buffer, channel, date, cached }) { + if (!cached) cachedContent = undefined + + let programs = [] + const items = parseItems(buffer, channel, date) + items.forEach(item => { + programs.push({ + title: item.title?.[0]?.value, + description: item.desc?.[0]?.value, + start: item.start, + stop: item.stop + }) + }) + + return programs + }, + async channels({ tag }) { + const buffer = await axios + .get(`https://epgshare01.online/epgshare01/epg_ripper_${tag}.xml.gz`, { + responseType: 'arraybuffer' + }) + .then(r => r.data) + .catch(console.error) + + const content = ungzip(buffer) + const encoded = iconv.decode(content, 'utf8') + const { channels } = parser.parse(encoded) + + return channels.map(channel => { + const displayName = channel.displayName[0] + + return { + lang: displayName.lang || 'en', + site_id: `${tag}#${channel.id}`, + name: displayName.value + } + }) + } +} + +function parseItems(buffer, channel, date) { + if (!buffer) return [] + + if (!cachedContent) { + const content = ungzip(buffer) + const encoded = iconv.decode(content, 'utf8') + cachedContent = parser.parse(encoded) + } + + const { programs } = cachedContent + const [, channelId] = channel.site_id.split('#') + + return programs.filter(p => p.channel === channelId && date.isSame(p.start, 'day')) +} diff --git a/sites/epgshare01.online/epgshare01.online.test.js b/sites/epgshare01.online/epgshare01.online.test.js index 88015351a..f76463785 100644 --- a/sites/epgshare01.online/epgshare01.online.test.js +++ b/sites/epgshare01.online/epgshare01.online.test.js @@ -1,43 +1,43 @@ -const { parser, url } = require('./epgshare01.online.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-02-09', 'YYYY-MM-DD').startOf('d') -const channel = { site_id: 'ALJAZEERA1#AlJazeera.English.net' } - -it('can generate valid url', () => { - expect(url({ channel })).toBe('https://epgshare01.online/epgshare01/epg_ripper_ALJAZEERA1.xml.gz') -}) - -it('can parse response', () => { - const buffer = fs.readFileSync(path.resolve(__dirname, '__data__/content.xml.gz')) - - const results = parser({ buffer, channel, date, cached: false }) - - expect(results.length).toBe(40) - expect(results[0]).toMatchObject({ - title: 'The Palestine Laboratory', - description: - "Exposing how Israel's sales of military technology is aiding state control around the world.", - start: '2025-02-09T00:00:00.000Z', - stop: '2025-02-09T01:00:00.000Z' - }) - expect(results[39]).toMatchObject({ - title: 'Inside Story', - description: - 'Beyond the headlines to the heart of the news of the day. Al Jazeera gets the Inside Story from some of the best minds from around the globe.', - start: '2025-02-09T23:30:00.000Z', - stop: '2025-02-10T00:00:00.000Z' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ content: '', channel, date, cached: false }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./epgshare01.online.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-02-09', 'YYYY-MM-DD').startOf('d') +const channel = { site_id: 'ALJAZEERA1#AlJazeera.English.net' } + +it('can generate valid url', () => { + expect(url({ channel })).toBe('https://epgshare01.online/epgshare01/epg_ripper_ALJAZEERA1.xml.gz') +}) + +it('can parse response', () => { + const buffer = fs.readFileSync(path.resolve(__dirname, '__data__/content.xml.gz')) + + const results = parser({ buffer, channel, date, cached: false }) + + expect(results.length).toBe(40) + expect(results[0]).toMatchObject({ + title: 'The Palestine Laboratory', + description: + "Exposing how Israel's sales of military technology is aiding state control around the world.", + start: '2025-02-09T00:00:00.000Z', + stop: '2025-02-09T01:00:00.000Z' + }) + expect(results[39]).toMatchObject({ + title: 'Inside Story', + description: + 'Beyond the headlines to the heart of the news of the day. Al Jazeera gets the Inside Story from some of the best minds from around the globe.', + start: '2025-02-09T23:30:00.000Z', + stop: '2025-02-10T00:00:00.000Z' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ content: '', channel, date, cached: false }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/firstmedia.com/firstmedia.com.config.js b/sites/firstmedia.com/firstmedia.com.config.js index 7c6865fc8..d80b29a6a 100644 --- a/sites/firstmedia.com/firstmedia.com.config.js +++ b/sites/firstmedia.com/firstmedia.com.config.js @@ -1,102 +1,102 @@ -const dayjs = require('dayjs') -const timezone = require('dayjs/plugin/timezone') -const utc = require('dayjs/plugin/utc') - -dayjs.extend(timezone) -dayjs.extend(utc) - -module.exports = { - site: 'firstmedia.com', - days: 2, - url({ channel, date }) { - return `https://api.firstmedia.com/api/content/tv-guide/list?date=${date.format( - 'DD/MM/YYYY' - )}&channel=${channel.site_id}&startTime=1&endTime=24` - }, - parser({ content, channel, date }) { - if (!content || !channel || !date) return [] - - const programs = [] - const items = parseItems(content, channel.site_id) - .map(item => { - item.start = toDelta(item.date, item.startTime) - item.stop = toDelta(item.date, item.endTime) - return item - }) - .sort((a, b) => a.start - b.start) - - const dt = date.tz('Asia/Jakarta').startOf('d') - let lastStop - items.forEach(item => { - if (lastStop === undefined || item.start >= lastStop) { - lastStop = item.stop - programs.push({ - title: parseTitle(item), - description: parseDescription(item), - start: asDate(parseStart({ item, date: dt })), - stop: asDate(parseStop({ item, date: dt })) - }) - } - }) - - return programs - }, - async channels() { - const axios = require('axios') - const result = await axios - .get( - `https://api.firstmedia.com/api/content/tv-guide/list?date=${dayjs().format( - 'DD/MM/YYYY' - )}&channel=&startTime=0&endTime=24` - ) - .then(response => response.data) - .catch(console.error) - - const channels = [] - if (result.data && result.data.entries) { - Object.values(result.data.entries).forEach(schedules => { - if (schedules.length) { - channels.push({ - lang: 'en', - site_id: schedules[0].channel.no, - name: schedules[0].channel.name - }) - } - }) - } - - return channels - } -} - -function parseItems(content, channel) { - return JSON.parse(content.trim()).data.entries[channel] || [] -} - -function parseTitle(item) { - return item.title -} - -function parseDescription(item) { - return item.long_description -} - -function parseStart({ item, date }) { - return date.add(item.start, 'ms') -} - -function parseStop({ item, date }) { - return date.add(item.stop, 'ms') -} - -function toDelta(from, to) { - return toDate(to).diff(toDate(from), 'milliseconds') -} - -function toDate(date) { - return dayjs(date, 'YYYY-MM-DD HH:mm:ss') -} - -function asDate(date) { - return date.toISOString() -} +const dayjs = require('dayjs') +const timezone = require('dayjs/plugin/timezone') +const utc = require('dayjs/plugin/utc') + +dayjs.extend(timezone) +dayjs.extend(utc) + +module.exports = { + site: 'firstmedia.com', + days: 2, + url({ channel, date }) { + return `https://api.firstmedia.com/api/content/tv-guide/list?date=${date.format( + 'DD/MM/YYYY' + )}&channel=${channel.site_id}&startTime=1&endTime=24` + }, + parser({ content, channel, date }) { + if (!content || !channel || !date) return [] + + const programs = [] + const items = parseItems(content, channel.site_id) + .map(item => { + item.start = toDelta(item.date, item.startTime) + item.stop = toDelta(item.date, item.endTime) + return item + }) + .sort((a, b) => a.start - b.start) + + const dt = date.tz('Asia/Jakarta').startOf('d') + let lastStop + items.forEach(item => { + if (lastStop === undefined || item.start >= lastStop) { + lastStop = item.stop + programs.push({ + title: parseTitle(item), + description: parseDescription(item), + start: asDate(parseStart({ item, date: dt })), + stop: asDate(parseStop({ item, date: dt })) + }) + } + }) + + return programs + }, + async channels() { + const axios = require('axios') + const result = await axios + .get( + `https://api.firstmedia.com/api/content/tv-guide/list?date=${dayjs().format( + 'DD/MM/YYYY' + )}&channel=&startTime=0&endTime=24` + ) + .then(response => response.data) + .catch(console.error) + + const channels = [] + if (result.data && result.data.entries) { + Object.values(result.data.entries).forEach(schedules => { + if (schedules.length) { + channels.push({ + lang: 'en', + site_id: schedules[0].channel.no, + name: schedules[0].channel.name + }) + } + }) + } + + return channels + } +} + +function parseItems(content, channel) { + return JSON.parse(content.trim()).data.entries[channel] || [] +} + +function parseTitle(item) { + return item.title +} + +function parseDescription(item) { + return item.long_description +} + +function parseStart({ item, date }) { + return date.add(item.start, 'ms') +} + +function parseStop({ item, date }) { + return date.add(item.stop, 'ms') +} + +function toDelta(from, to) { + return toDate(to).diff(toDate(from), 'milliseconds') +} + +function toDate(date) { + return dayjs(date, 'YYYY-MM-DD HH:mm:ss') +} + +function asDate(date) { + return date.toISOString() +} diff --git a/sites/foxsports.com.au/foxsports.com.au.config.js b/sites/foxsports.com.au/foxsports.com.au.config.js index b7fc17ad7..be91f498e 100644 --- a/sites/foxsports.com.au/foxsports.com.au.config.js +++ b/sites/foxsports.com.au/foxsports.com.au.config.js @@ -1,69 +1,69 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') - -dayjs.extend(utc) - -module.exports = { - site: 'foxsports.com.au', - days: 3, - request: { - cache: { - ttl: 60 * 60 * 1000 // 1 hour - } - }, - url({ date }) { - return `https://tvguide.foxsports.com.au/granite-api/programmes.json?from=${date.format( - 'YYYY-MM-DD' - )}&to=${date.add(1, 'd').format('YYYY-MM-DD')}` - }, - parser({ content, channel }) { - let programs = [] - const items = parseItems(content, channel) - items.forEach(item => { - programs.push({ - title: item.programmeTitle, - sub_title: item.title, - category: item.genreTitle, - description: item.synopsis, - start: dayjs.utc(item.startTime), - stop: dayjs.utc(item.endTime) - }) - }) - - return programs - }, - async channels() { - const axios = require('axios') - const data = await axios - .get( - `https://tvguide.foxsports.com.au/granite-api/programmes.json?from=${dayjs().format( - 'YYYY-MM-DD' - )}&to=${dayjs().add(1, 'd').format('YYYY-MM-DD')}` - ) - .then(r => r.data) - .catch(console.log) - - let channels = {} - data['channel-programme'].forEach(item => { - if (channels[item.channelId]) return - - channels[item.channelId] = { - lang: 'en', - site_id: item.channelId, - name: item.channelName - } - }) - - return Object.values(channels) - } -} - -function parseItems(content, channel) { - const data = JSON.parse(content) - if (!data) return [] - const programmes = data['channel-programme'] - if (!Array.isArray(programmes)) return [] - - const channelData = programmes.filter(i => i.channelId == channel.site_id) - return channelData && Array.isArray(channelData) ? channelData : [] -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') + +dayjs.extend(utc) + +module.exports = { + site: 'foxsports.com.au', + days: 3, + request: { + cache: { + ttl: 60 * 60 * 1000 // 1 hour + } + }, + url({ date }) { + return `https://tvguide.foxsports.com.au/granite-api/programmes.json?from=${date.format( + 'YYYY-MM-DD' + )}&to=${date.add(1, 'd').format('YYYY-MM-DD')}` + }, + parser({ content, channel }) { + let programs = [] + const items = parseItems(content, channel) + items.forEach(item => { + programs.push({ + title: item.programmeTitle, + sub_title: item.title, + category: item.genreTitle, + description: item.synopsis, + start: dayjs.utc(item.startTime), + stop: dayjs.utc(item.endTime) + }) + }) + + return programs + }, + async channels() { + const axios = require('axios') + const data = await axios + .get( + `https://tvguide.foxsports.com.au/granite-api/programmes.json?from=${dayjs().format( + 'YYYY-MM-DD' + )}&to=${dayjs().add(1, 'd').format('YYYY-MM-DD')}` + ) + .then(r => r.data) + .catch(console.log) + + let channels = {} + data['channel-programme'].forEach(item => { + if (channels[item.channelId]) return + + channels[item.channelId] = { + lang: 'en', + site_id: item.channelId, + name: item.channelName + } + }) + + return Object.values(channels) + } +} + +function parseItems(content, channel) { + const data = JSON.parse(content) + if (!data) return [] + const programmes = data['channel-programme'] + if (!Array.isArray(programmes)) return [] + + const channelData = programmes.filter(i => i.channelId == channel.site_id) + return channelData && Array.isArray(channelData) ? channelData : [] +} diff --git a/sites/foxtel.com.au/foxtel.com.au.config.js b/sites/foxtel.com.au/foxtel.com.au.config.js index da6f04b1b..279234107 100644 --- a/sites/foxtel.com.au/foxtel.com.au.config.js +++ b/sites/foxtel.com.au/foxtel.com.au.config.js @@ -1,138 +1,138 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const cheerio = require('cheerio') - -module.exports = { - site: 'foxtel.com.au', - days: 2, - url({ channel, date }) { - return `https://www.foxtel.com.au/tv-guide/channel/${channel.site_id}/${date.format( - 'YYYY/MM/DD' - )}` - }, - request: { - headers: { - 'Accept-Language': 'en-US,en;', - Cookie: 'AAMC_foxtel_0=REGION|7', - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' - } - }, - parser: function ({ content, date }) { - let programs = [] - const items = parseItems(content) - for (let item of items) { - const $item = cheerio.load(item) - const prev = programs[programs.length - 1] - let start = parseStart($item) - if (prev) { - if (start.isBefore(prev.start)) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.add(30, 'm') - programs.push({ - title: parseTitle($item), - sub_title: parseSubTitle($item), - image: parseImage($item), - rating: parseRating($item), - season: parseSeason($item), - episode: parseEpisode($item), - start, - stop - }) - } - - return programs - }, - async channels() { - const data = await axios - .get('https://www.foxtel.com.au/webepg/ws/foxtel/channels?regionId=8336', { - headers: { - 'User-Agent': 'insomnia/2022.7.5' - } - }) - .then(r => r.data) - .catch(console.log) - - return data.channels.map(item => { - const slug = item.name - .replace(/\+/g, '-') - .replace(/&/g, '') - .replace(/[^a-z0-9\s]/gi, '') - .replace(/\s/g, '-') - - return { - lang: 'en', - name: item.name, - site_id: `${slug}/${item.channelTag}` - } - }) - } -} - -function parseSeason($item) { - let seasonString = $item('.epg-event-description > div > abbr:nth-child(1)').attr('title') - if (!seasonString) return null - let [, season] = seasonString.match(/^Season: (\d+)/) || [null, null] - - return season ? parseInt(season) : null -} - -function parseEpisode($item) { - let episodeString = $item('.epg-event-description > div > abbr:nth-child(2)').attr('title') - if (!episodeString) return null - let [, episode] = episodeString.match(/^Episode: (\d+)/) || [null, null] - - return episode ? parseInt(episode) : null -} - -function parseImage($item) { - return $item('.epg-event-thumbnail > img').attr('src') -} - -function parseTitle($item) { - return $item('.epg-event-description').clone().children().remove().end().text().trim() -} - -function parseSubTitle($item) { - let subtitle = $item('.epg-event-description > div') - .clone() - .children() - .remove() - .end() - .text() - .trim() - .split(',') - - subtitle = subtitle.pop() - const [, rating] = subtitle.match(/\(([^)]+)\)$/) || [null, null] - - return subtitle.replace(`(${rating})`, '').trim() -} - -function parseRating($item) { - const subtitle = $item('.epg-event-description > div').text().trim() - const [, rating] = subtitle.match(/\(([^)]+)\)$/) || [null, null] - - return rating - ? { - system: 'ACB', - value: rating - } - : null -} - -function parseStart($item) { - const unix = $item('*').data('scheduled-date') - - return dayjs(parseInt(unix)) -} - -function parseItems(content) { - if (!content) return [] - const $ = cheerio.load(content) - - return $('#epg-channel-events > a').toArray() -} +const axios = require('axios') +const dayjs = require('dayjs') +const cheerio = require('cheerio') + +module.exports = { + site: 'foxtel.com.au', + days: 2, + url({ channel, date }) { + return `https://www.foxtel.com.au/tv-guide/channel/${channel.site_id}/${date.format( + 'YYYY/MM/DD' + )}` + }, + request: { + headers: { + 'Accept-Language': 'en-US,en;', + Cookie: 'AAMC_foxtel_0=REGION|7', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' + } + }, + parser: function ({ content, date }) { + let programs = [] + const items = parseItems(content) + for (let item of items) { + const $item = cheerio.load(item) + const prev = programs[programs.length - 1] + let start = parseStart($item) + if (prev) { + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.add(30, 'm') + programs.push({ + title: parseTitle($item), + sub_title: parseSubTitle($item), + image: parseImage($item), + rating: parseRating($item), + season: parseSeason($item), + episode: parseEpisode($item), + start, + stop + }) + } + + return programs + }, + async channels() { + const data = await axios + .get('https://www.foxtel.com.au/webepg/ws/foxtel/channels?regionId=8336', { + headers: { + 'User-Agent': 'insomnia/2022.7.5' + } + }) + .then(r => r.data) + .catch(console.log) + + return data.channels.map(item => { + const slug = item.name + .replace(/\+/g, '-') + .replace(/&/g, '') + .replace(/[^a-z0-9\s]/gi, '') + .replace(/\s/g, '-') + + return { + lang: 'en', + name: item.name, + site_id: `${slug}/${item.channelTag}` + } + }) + } +} + +function parseSeason($item) { + let seasonString = $item('.epg-event-description > div > abbr:nth-child(1)').attr('title') + if (!seasonString) return null + let [, season] = seasonString.match(/^Season: (\d+)/) || [null, null] + + return season ? parseInt(season) : null +} + +function parseEpisode($item) { + let episodeString = $item('.epg-event-description > div > abbr:nth-child(2)').attr('title') + if (!episodeString) return null + let [, episode] = episodeString.match(/^Episode: (\d+)/) || [null, null] + + return episode ? parseInt(episode) : null +} + +function parseImage($item) { + return $item('.epg-event-thumbnail > img').attr('src') +} + +function parseTitle($item) { + return $item('.epg-event-description').clone().children().remove().end().text().trim() +} + +function parseSubTitle($item) { + let subtitle = $item('.epg-event-description > div') + .clone() + .children() + .remove() + .end() + .text() + .trim() + .split(',') + + subtitle = subtitle.pop() + const [, rating] = subtitle.match(/\(([^)]+)\)$/) || [null, null] + + return subtitle.replace(`(${rating})`, '').trim() +} + +function parseRating($item) { + const subtitle = $item('.epg-event-description > div').text().trim() + const [, rating] = subtitle.match(/\(([^)]+)\)$/) || [null, null] + + return rating + ? { + system: 'ACB', + value: rating + } + : null +} + +function parseStart($item) { + const unix = $item('*').data('scheduled-date') + + return dayjs(parseInt(unix)) +} + +function parseItems(content) { + if (!content) return [] + const $ = cheerio.load(content) + + return $('#epg-channel-events > a').toArray() +} diff --git a/sites/foxtel.com.au/foxtel.com.au.test.js b/sites/foxtel.com.au/foxtel.com.au.test.js index 3485c6f2a..ef715635e 100644 --- a/sites/foxtel.com.au/foxtel.com.au.test.js +++ b/sites/foxtel.com.au/foxtel.com.au.test.js @@ -1,60 +1,60 @@ -const { parser, url, request } = require('./foxtel.com.au.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-11-08', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'Channel-9-Sydney/NIN', - xmltv_id: 'Channel9Sydney.au' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://www.foxtel.com.au/tv-guide/channel/Channel-9-Sydney/NIN/2022/11/08' - ) -}) - -it('can generate valid request headers', () => { - expect(request.headers).toMatchObject({ - 'Accept-Language': 'en-US,en;', - Cookie: 'AAMC_foxtel_0=REGION|7' - }) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - - let results = parser({ content }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2022-11-07T12:40:00.000Z', - stop: '2022-11-07T13:30:00.000Z', - title: 'The Equalizer', - sub_title: 'Glory', - image: - 'https://images1.resources.foxtel.com.au/store2/mount1/16/3/69e0v.jpg?maxheight=90&limit=91aa1c7a2c485aeeba0706941f79f111adb35830', - rating: { - system: 'ACB', - value: 'M' - }, - season: 1, - episode: 2 - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: fs.readFileSync(path.resolve(__dirname, '__data__/no-content.html')) - }) - expect(result).toMatchObject([]) -}) +const { parser, url, request } = require('./foxtel.com.au.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-11-08', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'Channel-9-Sydney/NIN', + xmltv_id: 'Channel9Sydney.au' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://www.foxtel.com.au/tv-guide/channel/Channel-9-Sydney/NIN/2022/11/08' + ) +}) + +it('can generate valid request headers', () => { + expect(request.headers).toMatchObject({ + 'Accept-Language': 'en-US,en;', + Cookie: 'AAMC_foxtel_0=REGION|7' + }) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + + let results = parser({ content }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2022-11-07T12:40:00.000Z', + stop: '2022-11-07T13:30:00.000Z', + title: 'The Equalizer', + sub_title: 'Glory', + image: + 'https://images1.resources.foxtel.com.au/store2/mount1/16/3/69e0v.jpg?maxheight=90&limit=91aa1c7a2c485aeeba0706941f79f111adb35830', + rating: { + system: 'ACB', + value: 'M' + }, + season: 1, + episode: 2 + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: fs.readFileSync(path.resolve(__dirname, '__data__/no-content.html')) + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/freetv.tv/freetv.tv.config.js b/sites/freetv.tv/freetv.tv.config.js index 4b21ba88c..02cbc4c7d 100644 --- a/sites/freetv.tv/freetv.tv.config.js +++ b/sites/freetv.tv/freetv.tv.config.js @@ -1,62 +1,62 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'freetv.tv', - days: 2, - url: function ({ channel, date }) { - const localDate = dayjs(date).tz('Asia/Jerusalem') - const since = localDate.startOf('day').format('YYYY-MM-DDTHH:mmZZ') - const till = localDate.add(1, 'day').startOf('day').format('YYYY-MM-DDTHH:mmZZ') - - return `https://web.freetv.tv/api/products/lives/programmes?liveId[]=${ - channel.site_id - }&since=${encodeURIComponent(since)}&till=${encodeURIComponent(till)}&lang=HEB&platform=BROWSER` - }, - parser: function ({ content }) { - const programs = [] - let items = [] - - try { - items = JSON.parse(content) - } catch { - return programs - } - - items.forEach(item => { - const start = parseStart(item) - const stop = parseStop(item) - if (!start.isValid() || !stop.isValid()) return - - programs.push({ - title: item.title, - description: item.description || item.lead, - image: getImageUrl(item), - icon: getImageUrl(item), - start, - stop - }) - }) - - return programs - } -} - -function parseStart(item) { - return item.since ? dayjs.utc(item.since).tz('Asia/Jerusalem') : null -} - -function parseStop(item) { - return item.till ? dayjs.utc(item.till).tz('Asia/Jerusalem') : null -} - -function getImageUrl(item) { - const url = item.images?.['16x9']?.[0]?.url - return url ? `https:${url}` : null -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'freetv.tv', + days: 2, + url: function ({ channel, date }) { + const localDate = dayjs(date).tz('Asia/Jerusalem') + const since = localDate.startOf('day').format('YYYY-MM-DDTHH:mmZZ') + const till = localDate.add(1, 'day').startOf('day').format('YYYY-MM-DDTHH:mmZZ') + + return `https://web.freetv.tv/api/products/lives/programmes?liveId[]=${ + channel.site_id + }&since=${encodeURIComponent(since)}&till=${encodeURIComponent(till)}&lang=HEB&platform=BROWSER` + }, + parser: function ({ content }) { + const programs = [] + let items = [] + + try { + items = JSON.parse(content) + } catch { + return programs + } + + items.forEach(item => { + const start = parseStart(item) + const stop = parseStop(item) + if (!start.isValid() || !stop.isValid()) return + + programs.push({ + title: item.title, + description: item.description || item.lead, + image: getImageUrl(item), + icon: getImageUrl(item), + start, + stop + }) + }) + + return programs + } +} + +function parseStart(item) { + return item.since ? dayjs.utc(item.since).tz('Asia/Jerusalem') : null +} + +function parseStop(item) { + return item.till ? dayjs.utc(item.till).tz('Asia/Jerusalem') : null +} + +function getImageUrl(item) { + const url = item.images?.['16x9']?.[0]?.url + return url ? `https:${url}` : null +} diff --git a/sites/freeview.co.uk/freeview.co.uk.config.js b/sites/freeview.co.uk/freeview.co.uk.config.js index fab60372d..9b77a2570 100644 --- a/sites/freeview.co.uk/freeview.co.uk.config.js +++ b/sites/freeview.co.uk/freeview.co.uk.config.js @@ -1,73 +1,73 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const parseDuration = require('parse-duration').default - -dayjs.extend(utc) - -module.exports = { - site: 'freeview.co.uk', - days: 2, - url({ date, channel }) { - const [networkId] = channel.site_id.split('#') - const startTimestamp = date.startOf('d').unix() - - return `https://www.freeview.co.uk/api/tv-guide?nid=${networkId}&start=${startTimestamp}` - }, - parser({ content, channel }) { - let programs = [] - let items = parseItems(content, channel) - items.forEach(item => { - const start = parseStart(item) - const duration = parseDuration(item.duration) - const stop = start.add(duration, 'ms') - programs.push({ - title: item.main_title, - subtitle: item.secondary_title, - image: parseImage(item), - start, - stop - }) - }) - - return programs - }, - async channels() { - const networkId = '64257' // Great London - const startTimestamp = dayjs.utc().startOf('d').unix() - const data = await axios - .get(`https://www.freeview.co.uk/api/tv-guide?nid=${networkId}&start=${startTimestamp}`) - .then(r => r.data) - .catch(console.log) - - return data.data.programs.map(item => ({ - lang: 'en', - site_id: `${networkId}#${item.service_id}`, - name: item.title - })) - } -} - -function parseImage(item) { - return item.image_url ? `${item.image_url}?w=800` : null -} - -function parseStart(item) { - return dayjs(item.start_time) -} - -function parseItems(content, channel) { - try { - const data = JSON.parse(content) - const programs = data?.data?.programs - if (!Array.isArray(programs)) return [] - const [, channelId] = channel.site_id.split('#') - const channelData = programs.find(p => p.service_id === channelId) - const channelPrograms = channelData?.events - if (!Array.isArray(channelPrograms)) return [] - - return channelPrograms - } catch { - return [] - } -} +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const parseDuration = require('parse-duration').default + +dayjs.extend(utc) + +module.exports = { + site: 'freeview.co.uk', + days: 2, + url({ date, channel }) { + const [networkId] = channel.site_id.split('#') + const startTimestamp = date.startOf('d').unix() + + return `https://www.freeview.co.uk/api/tv-guide?nid=${networkId}&start=${startTimestamp}` + }, + parser({ content, channel }) { + let programs = [] + let items = parseItems(content, channel) + items.forEach(item => { + const start = parseStart(item) + const duration = parseDuration(item.duration) + const stop = start.add(duration, 'ms') + programs.push({ + title: item.main_title, + subtitle: item.secondary_title, + image: parseImage(item), + start, + stop + }) + }) + + return programs + }, + async channels() { + const networkId = '64257' // Great London + const startTimestamp = dayjs.utc().startOf('d').unix() + const data = await axios + .get(`https://www.freeview.co.uk/api/tv-guide?nid=${networkId}&start=${startTimestamp}`) + .then(r => r.data) + .catch(console.log) + + return data.data.programs.map(item => ({ + lang: 'en', + site_id: `${networkId}#${item.service_id}`, + name: item.title + })) + } +} + +function parseImage(item) { + return item.image_url ? `${item.image_url}?w=800` : null +} + +function parseStart(item) { + return dayjs(item.start_time) +} + +function parseItems(content, channel) { + try { + const data = JSON.parse(content) + const programs = data?.data?.programs + if (!Array.isArray(programs)) return [] + const [, channelId] = channel.site_id.split('#') + const channelData = programs.find(p => p.service_id === channelId) + const channelPrograms = channelData?.events + if (!Array.isArray(channelPrograms)) return [] + + return channelPrograms + } catch { + return [] + } +} diff --git a/sites/freeview.co.uk/freeview.co.uk.test.js b/sites/freeview.co.uk/freeview.co.uk.test.js index b45fd266f..7c33151a1 100644 --- a/sites/freeview.co.uk/freeview.co.uk.test.js +++ b/sites/freeview.co.uk/freeview.co.uk.test.js @@ -1,55 +1,55 @@ -const { parser, url } = require('./freeview.co.uk.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-01-16', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '64257#4164', - xmltv_id: 'BBCOneLondon.uk' -} - -it('can generate valid url', () => { - expect(url({ date, channel })).toBe( - 'https://www.freeview.co.uk/api/tv-guide?nid=64257&start=1736985600' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - let results = parser({ content, channel }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(25) - expect(results[0]).toMatchObject({ - start: '2025-01-16T00:00:00.000Z', - stop: '2025-01-16T00:45:00.000Z', - title: 'The Weakest Link', - subtitle: 'Series 4: Episode 7', - image: 'https://img.freeviewplay.tv/p0b041486e4378cbf074511098f74e78f?w=800' - }) - expect(results[24]).toMatchObject({ - start: '2025-01-16T23:40:00.000Z', - stop: '2025-01-17T00:10:00.000Z', - title: 'Newscast', - subtitle: 'Series 5: 16/01/2025', - image: 'https://img.freeviewplay.tv/pb43e790fe10fe5ba668caf22224bc312?w=800' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - content: '[]', - channel - }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./freeview.co.uk.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-01-16', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '64257#4164', + xmltv_id: 'BBCOneLondon.uk' +} + +it('can generate valid url', () => { + expect(url({ date, channel })).toBe( + 'https://www.freeview.co.uk/api/tv-guide?nid=64257&start=1736985600' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + let results = parser({ content, channel }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(25) + expect(results[0]).toMatchObject({ + start: '2025-01-16T00:00:00.000Z', + stop: '2025-01-16T00:45:00.000Z', + title: 'The Weakest Link', + subtitle: 'Series 4: Episode 7', + image: 'https://img.freeviewplay.tv/p0b041486e4378cbf074511098f74e78f?w=800' + }) + expect(results[24]).toMatchObject({ + start: '2025-01-16T23:40:00.000Z', + stop: '2025-01-17T00:10:00.000Z', + title: 'Newscast', + subtitle: 'Series 5: 16/01/2025', + image: 'https://img.freeviewplay.tv/pb43e790fe10fe5ba668caf22224bc312?w=800' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + content: '[]', + channel + }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/frikanalen.no/frikanalen.no.config.js b/sites/frikanalen.no/frikanalen.no.config.js index a5886752f..e66a6efe0 100644 --- a/sites/frikanalen.no/frikanalen.no.config.js +++ b/sites/frikanalen.no/frikanalen.no.config.js @@ -1,52 +1,52 @@ -const dayjs = require('dayjs') - -module.exports = { - site: 'frikanalen.no', - days: 2, - url({ date }) { - return `https://frikanalen.no/api/scheduleitems/?date=${date.format( - 'YYYY-MM-DD' - )}&format=json&limit=100` - }, - parser({ content }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - programs.push({ - title: parseTitle(item), - category: parseCategory(item), - description: parseDescription(item), - start: parseStart(item), - stop: parseStop(item) - }) - }) - - return programs - } -} - -function parseTitle(item) { - return item.video.name -} - -function parseCategory(item) { - return item.video.categories -} - -function parseDescription(item) { - return item.video.header -} - -function parseStart(item) { - return dayjs(item.starttime) -} - -function parseStop(item) { - return dayjs(item.endtime) -} - -function parseItems(content) { - const data = JSON.parse(content) - - return data && Array.isArray(data.results) ? data.results : [] -} +const dayjs = require('dayjs') + +module.exports = { + site: 'frikanalen.no', + days: 2, + url({ date }) { + return `https://frikanalen.no/api/scheduleitems/?date=${date.format( + 'YYYY-MM-DD' + )}&format=json&limit=100` + }, + parser({ content }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + programs.push({ + title: parseTitle(item), + category: parseCategory(item), + description: parseDescription(item), + start: parseStart(item), + stop: parseStop(item) + }) + }) + + return programs + } +} + +function parseTitle(item) { + return item.video.name +} + +function parseCategory(item) { + return item.video.categories +} + +function parseDescription(item) { + return item.video.header +} + +function parseStart(item) { + return dayjs(item.starttime) +} + +function parseStop(item) { + return dayjs(item.endtime) +} + +function parseItems(content) { + const data = JSON.parse(content) + + return data && Array.isArray(data.results) ? data.results : [] +} diff --git a/sites/galamtv.kz/galamtv.kz.config.js b/sites/galamtv.kz/galamtv.kz.config.js index f64b91992..625980baf 100644 --- a/sites/galamtv.kz/galamtv.kz.config.js +++ b/sites/galamtv.kz/galamtv.kz.config.js @@ -1,64 +1,64 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'galamtv.kz', - timezone: 'Asia/Almaty', - days: 2, - request: { - method: 'GET', - headers: { - Referer: 'https://galamtv.kz/', - Origin: 'https://galamtv.kz', - Accept: '*/*', - 'Accept-Encoding': 'gzip, deflate, br, zstd' - } - }, - url({ channel, date }) { - const todayEpoch = date.startOf('day').unix() - const nextDayEpoch = date.add(1, 'day').startOf('day').unix() - return `https://galam.server-api.lfstrm.tv/channels/${channel.site_id}/programs?period=${todayEpoch}:${nextDayEpoch}` - }, - parser: function ({ content }) { - let programs = [] - const data = JSON.parse(content) - const programsData = data.programs || [] - - programsData.forEach(program => { - const start = dayjs.unix(program.scheduleInfo.start) - const stop = dayjs.unix(program.scheduleInfo.end) - - programs.push({ - title: program.metaInfo.title, - description: program.metaInfo.description, - image: program.mediaInfo.thumbnails[0].url, - start, - stop - }) - }) - - return programs - }, - async channels() { - try { - const response = await axios.get('https://galam.server-api.lfstrm.tv/channels-now') - return response.data.channels.map(item => { - return { - lang: 'kk', - site_id: item.channels.id, - name: item.channels.info.metaInfo.title - } - }) - } catch (error) { - console.error('Error fetching channels:', error) - return [] - } - } -} +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'galamtv.kz', + timezone: 'Asia/Almaty', + days: 2, + request: { + method: 'GET', + headers: { + Referer: 'https://galamtv.kz/', + Origin: 'https://galamtv.kz', + Accept: '*/*', + 'Accept-Encoding': 'gzip, deflate, br, zstd' + } + }, + url({ channel, date }) { + const todayEpoch = date.startOf('day').unix() + const nextDayEpoch = date.add(1, 'day').startOf('day').unix() + return `https://galam.server-api.lfstrm.tv/channels/${channel.site_id}/programs?period=${todayEpoch}:${nextDayEpoch}` + }, + parser: function ({ content }) { + let programs = [] + const data = JSON.parse(content) + const programsData = data.programs || [] + + programsData.forEach(program => { + const start = dayjs.unix(program.scheduleInfo.start) + const stop = dayjs.unix(program.scheduleInfo.end) + + programs.push({ + title: program.metaInfo.title, + description: program.metaInfo.description, + image: program.mediaInfo.thumbnails[0].url, + start, + stop + }) + }) + + return programs + }, + async channels() { + try { + const response = await axios.get('https://galam.server-api.lfstrm.tv/channels-now') + return response.data.channels.map(item => { + return { + lang: 'kk', + site_id: item.channels.id, + name: item.channels.info.metaInfo.title + } + }) + } catch (error) { + console.error('Error fetching channels:', error) + return [] + } + } +} diff --git a/sites/gatotv.com/gatotv.com.config.js b/sites/gatotv.com/gatotv.com.config.js index b814da0a7..8eadaa9ee 100644 --- a/sites/gatotv.com/gatotv.com.config.js +++ b/sites/gatotv.com/gatotv.com.config.js @@ -1,102 +1,102 @@ -const axios = require('axios') -const cheerio = require('cheerio') -const url = require('url') -const path = require('path') -const { DateTime } = require('luxon') - -module.exports = { - site: 'gatotv.com', - days: 2, - url({ channel, date }) { - return `https://www.gatotv.com/canal/${channel.site_id}/${date.format('YYYY-MM-DD')}` - }, - parser({ content, date }) { - let programs = [] - const items = parseItems(content) - date = date.subtract(1, 'd') - items.forEach((item, i) => { - const $item = cheerio.load(item) - let start = parseStart($item, date) - if (i === 0 && start.hour >= 5) { - start = start.plus({ days: 1 }) - date = date.add(1, 'd') - } - let stop = parseStop($item, date) - if (stop < start) { - stop = stop.plus({ days: 1 }) - date = date.add(1, 'd') - } - - programs.push({ - title: parseTitle($item), - description: parseDescription($item), - image: parseImage($item), - start, - stop - }) - }) - - return programs - }, - async channels() { - const data = await axios - .get('https://www.gatotv.com/guia_tv/completa') - .then(response => response.data) - .catch(console.log) - - const $ = cheerio.load(data) - const items = $('.tbl_EPG_row,.tbl_EPG_rowAlternate').toArray() - - return items.map(item => { - const $item = cheerio.load(item) - const link = $item('td:nth-child(1) > div:nth-child(2) > a:nth-child(3)').attr('href') - const parsed = url.parse(link) - - return { - lang: 'es', - site_id: path.basename(parsed.pathname), - name: $item('td:nth-child(1) > div:nth-child(2) > a:nth-child(3)').text() - } - }) - } -} - -function parseTitle($item) { - return $item( - 'td:nth-child(4) > div > div > a > span,td:nth-child(3) > div > div > span,td:nth-child(3) > div > div > a > span' - ).text() -} - -function parseDescription($item) { - return $item('td:nth-child(4) > div').clone().children().remove().end().text().trim() -} - -function parseImage($item) { - return $item('td:nth-child(3) > a > img').attr('src') -} - -function parseStart($item, date) { - const time = $item('td:nth-child(1) > div > time').attr('datetime') - - return DateTime.fromFormat(`${date.format('YYYY-MM-DD')} ${time}`, 'yyyy-MM-dd HH:mm', { - zone: 'EST' - }).toUTC() -} - -function parseStop($item, date) { - const time = $item('td:nth-child(2) > div > time').attr('datetime') - - return DateTime.fromFormat(`${date.format('YYYY-MM-DD')} ${time}`, 'yyyy-MM-dd HH:mm', { - zone: 'EST' - }).toUTC() -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $( - 'body > div.div_content > table:nth-child(8) > tbody > tr:nth-child(2) > td:nth-child(1) > table.tbl_EPG' - ) - .find('.tbl_EPG_row,.tbl_EPG_rowAlternate,.tbl_EPG_row_selected') - .toArray() -} +const axios = require('axios') +const cheerio = require('cheerio') +const url = require('url') +const path = require('path') +const { DateTime } = require('luxon') + +module.exports = { + site: 'gatotv.com', + days: 2, + url({ channel, date }) { + return `https://www.gatotv.com/canal/${channel.site_id}/${date.format('YYYY-MM-DD')}` + }, + parser({ content, date }) { + let programs = [] + const items = parseItems(content) + date = date.subtract(1, 'd') + items.forEach((item, i) => { + const $item = cheerio.load(item) + let start = parseStart($item, date) + if (i === 0 && start.hour >= 5) { + start = start.plus({ days: 1 }) + date = date.add(1, 'd') + } + let stop = parseStop($item, date) + if (stop < start) { + stop = stop.plus({ days: 1 }) + date = date.add(1, 'd') + } + + programs.push({ + title: parseTitle($item), + description: parseDescription($item), + image: parseImage($item), + start, + stop + }) + }) + + return programs + }, + async channels() { + const data = await axios + .get('https://www.gatotv.com/guia_tv/completa') + .then(response => response.data) + .catch(console.log) + + const $ = cheerio.load(data) + const items = $('.tbl_EPG_row,.tbl_EPG_rowAlternate').toArray() + + return items.map(item => { + const $item = cheerio.load(item) + const link = $item('td:nth-child(1) > div:nth-child(2) > a:nth-child(3)').attr('href') + const parsed = url.parse(link) + + return { + lang: 'es', + site_id: path.basename(parsed.pathname), + name: $item('td:nth-child(1) > div:nth-child(2) > a:nth-child(3)').text() + } + }) + } +} + +function parseTitle($item) { + return $item( + 'td:nth-child(4) > div > div > a > span,td:nth-child(3) > div > div > span,td:nth-child(3) > div > div > a > span' + ).text() +} + +function parseDescription($item) { + return $item('td:nth-child(4) > div').clone().children().remove().end().text().trim() +} + +function parseImage($item) { + return $item('td:nth-child(3) > a > img').attr('src') +} + +function parseStart($item, date) { + const time = $item('td:nth-child(1) > div > time').attr('datetime') + + return DateTime.fromFormat(`${date.format('YYYY-MM-DD')} ${time}`, 'yyyy-MM-dd HH:mm', { + zone: 'EST' + }).toUTC() +} + +function parseStop($item, date) { + const time = $item('td:nth-child(2) > div > time').attr('datetime') + + return DateTime.fromFormat(`${date.format('YYYY-MM-DD')} ${time}`, 'yyyy-MM-dd HH:mm', { + zone: 'EST' + }).toUTC() +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $( + 'body > div.div_content > table:nth-child(8) > tbody > tr:nth-child(2) > td:nth-child(1) > table.tbl_EPG' + ) + .find('.tbl_EPG_row,.tbl_EPG_rowAlternate,.tbl_EPG_row_selected') + .toArray() +} diff --git a/sites/gatotv.com/gatotv.com.test.js b/sites/gatotv.com/gatotv.com.test.js index f33dd541c..ac8a6f5b4 100644 --- a/sites/gatotv.com/gatotv.com.test.js +++ b/sites/gatotv.com/gatotv.com.test.js @@ -1,79 +1,79 @@ -const { parser, url } = require('./gatotv.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -let date = dayjs.utc('2023-06-13', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'm_0', - xmltv_id: '0porMovistarPlus.es' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe('https://www.gatotv.com/canal/m_0/2023-06-13') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_0.html'), 'utf8') - const results = parser({ date, channel, content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - - return p - }) - - expect(results[0]).toMatchObject({ - start: '2023-06-13T04:30:00.000Z', - stop: '2023-06-13T05:32:00.000Z', - title: 'Supergarcía' - }) - - expect(results[1]).toMatchObject({ - start: '2023-06-13T05:32:00.000Z', - stop: '2023-06-13T06:59:00.000Z', - title: 'La resistencia' - }) - - expect(results[25]).toMatchObject({ - start: '2023-06-14T04:46:00.000Z', - stop: '2023-06-14T05:00:00.000Z', - title: 'Una familia absolutamente normal' - }) -}) - -it('can parse response when the guide starts from midnight', () => { - date = date.add(1, 'd') - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_1.html'), 'utf8') - const results = parser({ date, channel, content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - - return p - }) - - expect(results[0]).toMatchObject({ - start: '2023-06-14T05:00:00.000Z', - stop: '2023-06-14T05:32:00.000Z', - title: 'Ilustres Ignorantes' - }) - - expect(results[26]).toMatchObject({ - start: '2023-06-15T04:30:00.000Z', - stop: '2023-06-15T05:30:00.000Z', - title: 'Showriano' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - date, - channel, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') - }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./gatotv.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +let date = dayjs.utc('2023-06-13', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'm_0', + xmltv_id: '0porMovistarPlus.es' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe('https://www.gatotv.com/canal/m_0/2023-06-13') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_0.html'), 'utf8') + const results = parser({ date, channel, content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + + return p + }) + + expect(results[0]).toMatchObject({ + start: '2023-06-13T04:30:00.000Z', + stop: '2023-06-13T05:32:00.000Z', + title: 'Supergarcía' + }) + + expect(results[1]).toMatchObject({ + start: '2023-06-13T05:32:00.000Z', + stop: '2023-06-13T06:59:00.000Z', + title: 'La resistencia' + }) + + expect(results[25]).toMatchObject({ + start: '2023-06-14T04:46:00.000Z', + stop: '2023-06-14T05:00:00.000Z', + title: 'Una familia absolutamente normal' + }) +}) + +it('can parse response when the guide starts from midnight', () => { + date = date.add(1, 'd') + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_1.html'), 'utf8') + const results = parser({ date, channel, content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + + return p + }) + + expect(results[0]).toMatchObject({ + start: '2023-06-14T05:00:00.000Z', + stop: '2023-06-14T05:32:00.000Z', + title: 'Ilustres Ignorantes' + }) + + expect(results[26]).toMatchObject({ + start: '2023-06-15T04:30:00.000Z', + stop: '2023-06-15T05:30:00.000Z', + title: 'Showriano' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + date, + channel, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') + }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/getafteritmedia.com/getafteritmedia.com.config.js b/sites/getafteritmedia.com/getafteritmedia.com.config.js index a86068cbf..5fdcaed40 100644 --- a/sites/getafteritmedia.com/getafteritmedia.com.config.js +++ b/sites/getafteritmedia.com/getafteritmedia.com.config.js @@ -1,64 +1,64 @@ -const table2array = require('table2array') -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') -const isoWeek = require('dayjs/plugin/isoWeek') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) -dayjs.extend(isoWeek) - -module.exports = { - site: 'getafteritmedia.com', - days: 2, - url: 'https://docs.google.com/spreadsheets/d/e/2PACX-1vQcDmb9OnO0HpbjINfGaepqgGTp3VSmPs7hs654n3sRKrq4Q9y6uPSEvVvq9MwTLYG_n_V7vh0rFYP9/pubhtml', - parser({ content, channel, date }) { - const programs = [] - const items = parseItems(content, channel, date) - items.forEach(item => { - const prev = programs[programs.length - 1] - let start = parseStart(item, date) - if (prev) { - if (start.isBefore(prev.start)) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.add(30, 'm') - programs.push({ - title: item.title, - start, - stop - }) - }) - - return programs - } -} - -function parseStart(item, date) { - return dayjs.tz( - `${date.format('YYYY-MM-DD')} ${item.time}`, - 'YYYY-MM-DD HH:mm A', - 'America/New_York' - ) -} - -function parseItems(content, channel, date) { - const day = date.isoWeekday() - const $ = cheerio.load(content) - const table = $.html($(`#${channel.site_id} table`)) - let data = table2array(table) - data.splice(0, 5) - - return data.map(row => { - return { - time: row[1], - title: row[day + 1] - } - }) -} +const table2array = require('table2array') +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const isoWeek = require('dayjs/plugin/isoWeek') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) +dayjs.extend(isoWeek) + +module.exports = { + site: 'getafteritmedia.com', + days: 2, + url: 'https://docs.google.com/spreadsheets/d/e/2PACX-1vQcDmb9OnO0HpbjINfGaepqgGTp3VSmPs7hs654n3sRKrq4Q9y6uPSEvVvq9MwTLYG_n_V7vh0rFYP9/pubhtml', + parser({ content, channel, date }) { + const programs = [] + const items = parseItems(content, channel, date) + items.forEach(item => { + const prev = programs[programs.length - 1] + let start = parseStart(item, date) + if (prev) { + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.add(30, 'm') + programs.push({ + title: item.title, + start, + stop + }) + }) + + return programs + } +} + +function parseStart(item, date) { + return dayjs.tz( + `${date.format('YYYY-MM-DD')} ${item.time}`, + 'YYYY-MM-DD HH:mm A', + 'America/New_York' + ) +} + +function parseItems(content, channel, date) { + const day = date.isoWeekday() + const $ = cheerio.load(content) + const table = $.html($(`#${channel.site_id} table`)) + let data = table2array(table) + data.splice(0, 5) + + return data.map(row => { + return { + time: row[1], + title: row[day + 1] + } + }) +} diff --git a/sites/getafteritmedia.com/getafteritmedia.com.test.js b/sites/getafteritmedia.com/getafteritmedia.com.test.js index 5c5b5cb02..099370706 100644 --- a/sites/getafteritmedia.com/getafteritmedia.com.test.js +++ b/sites/getafteritmedia.com/getafteritmedia.com.test.js @@ -1,45 +1,45 @@ -const { parser, url } = require('./getafteritmedia.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-11-26', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '494637005', - xmltv_id: 'REVNWebFeed.us' -} - -it('can generate valid url', () => { - expect(url).toBe( - 'https://docs.google.com/spreadsheets/d/e/2PACX-1vQcDmb9OnO0HpbjINfGaepqgGTp3VSmPs7hs654n3sRKrq4Q9y6uPSEvVvq9MwTLYG_n_V7vh0rFYP9/pubhtml' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') - let results = parser({ content, channel, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2022-11-26T05:00:00.000Z', - stop: '2022-11-26T05:30:00.000Z', - title: 'The Appraisers' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: '' - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./getafteritmedia.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-11-26', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '494637005', + xmltv_id: 'REVNWebFeed.us' +} + +it('can generate valid url', () => { + expect(url).toBe( + 'https://docs.google.com/spreadsheets/d/e/2PACX-1vQcDmb9OnO0HpbjINfGaepqgGTp3VSmPs7hs654n3sRKrq4Q9y6uPSEvVvq9MwTLYG_n_V7vh0rFYP9/pubhtml' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') + let results = parser({ content, channel, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2022-11-26T05:00:00.000Z', + stop: '2022-11-26T05:30:00.000Z', + title: 'The Appraisers' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: '' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/gigatv.3bbtv.co.th/gigatv.3bbtv.co.th.config.js b/sites/gigatv.3bbtv.co.th/gigatv.3bbtv.co.th.config.js index 61405de0d..30a7c480b 100644 --- a/sites/gigatv.3bbtv.co.th/gigatv.3bbtv.co.th.config.js +++ b/sites/gigatv.3bbtv.co.th/gigatv.3bbtv.co.th.config.js @@ -1,65 +1,65 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'gigatv.3bbtv.co.th', - days: 1, - url({ channel }) { - return `https://gigatv.3bbtv.co.th/wp-content/themes/changwattana/epg/${channel.site_id}.json` - }, - parser: function ({ content, date }) { - let programs = [] - const items = parseItems(content, date) - items.forEach(item => { - programs.push({ - title: item.programName, - start: parseTime(item.startTime), - stop: parseTime(item.endTime) - }) - }) - - return programs - }, - async channels() { - const data = await axios - .get('https://gigatv.3bbtv.co.th/wp-content/themes/changwattana/epg/channel.json') - .then(r => r.data) - .catch(console.log) - - const channels = [] - data.forEach(group => { - group.channel_list.forEach(channel => { - channels.push({ - lang: 'th', - site_id: channel.channel_id, - name: channel.channel_name - }) - }) - }) - - return channels - } -} - -function parseTime(string) { - return dayjs.tz(string, 'YYYY-MM-DD HH:mm:ss', 'Asia/Bangkok') -} - -function parseItems(content, date) { - try { - let data = JSON.parse(content) - if (!Array.isArray(data)) return [] - data = data.filter(p => date.isSame(parseTime(p.startTime), 'day')) - - return data - } catch { - return [] - } -} +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'gigatv.3bbtv.co.th', + days: 1, + url({ channel }) { + return `https://gigatv.3bbtv.co.th/wp-content/themes/changwattana/epg/${channel.site_id}.json` + }, + parser: function ({ content, date }) { + let programs = [] + const items = parseItems(content, date) + items.forEach(item => { + programs.push({ + title: item.programName, + start: parseTime(item.startTime), + stop: parseTime(item.endTime) + }) + }) + + return programs + }, + async channels() { + const data = await axios + .get('https://gigatv.3bbtv.co.th/wp-content/themes/changwattana/epg/channel.json') + .then(r => r.data) + .catch(console.log) + + const channels = [] + data.forEach(group => { + group.channel_list.forEach(channel => { + channels.push({ + lang: 'th', + site_id: channel.channel_id, + name: channel.channel_name + }) + }) + }) + + return channels + } +} + +function parseTime(string) { + return dayjs.tz(string, 'YYYY-MM-DD HH:mm:ss', 'Asia/Bangkok') +} + +function parseItems(content, date) { + try { + let data = JSON.parse(content) + if (!Array.isArray(data)) return [] + data = data.filter(p => date.isSame(parseTime(p.startTime), 'day')) + + return data + } catch { + return [] + } +} diff --git a/sites/gigatv.3bbtv.co.th/gigatv.3bbtv.co.th.test.js b/sites/gigatv.3bbtv.co.th/gigatv.3bbtv.co.th.test.js index 22f952245..37866c9d9 100644 --- a/sites/gigatv.3bbtv.co.th/gigatv.3bbtv.co.th.test.js +++ b/sites/gigatv.3bbtv.co.th/gigatv.3bbtv.co.th.test.js @@ -1,45 +1,45 @@ -const { parser, url } = require('./gigatv.3bbtv.co.th.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-01-12', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '222', - xmltv_id: 'ThainessTV.th' -} - -it('can generate valid url', () => { - expect(url({ channel })).toBe( - 'https://gigatv.3bbtv.co.th/wp-content/themes/changwattana/epg/222.json' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - let results = parser({ content, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(32) - expect(results[0]).toMatchObject({ - start: '2025-01-12T00:00:00.000Z', - stop: '2025-01-12T00:30:00.000Z', - title: 'THAILAND FORM ABOVE : TAK' - }) - expect(results[31]).toMatchObject({ - start: '2025-01-12T23:30:00.000Z', - stop: '2025-01-13T00:00:00.000Z', - title: 'MAESA ELEPHANT CAMP' - }) -}) - -it('can handle empty guide', () => { - expect(parser({ content: '', date })).toMatchObject([]) -}) +const { parser, url } = require('./gigatv.3bbtv.co.th.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-01-12', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '222', + xmltv_id: 'ThainessTV.th' +} + +it('can generate valid url', () => { + expect(url({ channel })).toBe( + 'https://gigatv.3bbtv.co.th/wp-content/themes/changwattana/epg/222.json' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + let results = parser({ content, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(32) + expect(results[0]).toMatchObject({ + start: '2025-01-12T00:00:00.000Z', + stop: '2025-01-12T00:30:00.000Z', + title: 'THAILAND FORM ABOVE : TAK' + }) + expect(results[31]).toMatchObject({ + start: '2025-01-12T23:30:00.000Z', + stop: '2025-01-13T00:00:00.000Z', + title: 'MAESA ELEPHANT CAMP' + }) +}) + +it('can handle empty guide', () => { + expect(parser({ content: '', date })).toMatchObject([]) +}) diff --git a/sites/guiadetv.com/guiadetv.com.config.js b/sites/guiadetv.com/guiadetv.com.config.js index b63f49b29..d0e0a2782 100644 --- a/sites/guiadetv.com/guiadetv.com.config.js +++ b/sites/guiadetv.com/guiadetv.com.config.js @@ -1,101 +1,101 @@ -const cheerio = require('cheerio') -const axios = require('axios') -const dayjs = require('dayjs') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) - -require('dayjs/locale/pt') - -module.exports = { - site: 'guiadetv.com', - days: 2, - url({ channel }) { - return `https://www.guiadetv.com/canal/${channel.site_id}` - }, - parser({ content, date }) { - const programs = [] - const items = parseItems(content, date) - items.forEach(item => { - const prev = programs[programs.length - 1] - const $item = cheerio.load(item) - const title = parseTitle($item) - let start = parseStart($item) - if (!start || !title) return - if (prev) { - prev.stop = start - } - const stop = start.add(30, 'm') - - programs.push({ - title, - description: parseDescription($item), - category: parseCategory($item), - start, - stop - }) - }) - - return programs - }, - async channels() { - const categories = [ - 'variedades', - 'tv-aberta', - 'noticias', - 'infantil', - 'filmes-e-series', - 'esportes', - 'documentarios' - ] - const promises = categories.map(category => - axios.get(`https://www.guiadetv.com/categorias/${category}.html`) - ) - - const channels = [] - const results = await Promise.all(promises).catch(console.log) - results.forEach(r => { - const $ = cheerio.load(r.data) - $('.cardchannel').each((i, el) => { - const link = $(el).find('a') - const name = link.attr('title') - const url = link.attr('href') - const site_id = url.replace('https://www.guiadetv.com/canal/', '') - - channels.push({ - lang: 'pt', - name, - site_id - }) - }) - }) - - return channels - } -} - -function parseTitle($item) { - return $item('h3').text().trim() -} - -function parseDescription($item) { - return $item('p').clone().children().remove().end().text().trim() || null -} - -function parseCategory($item) { - return $item('p > i').text().trim() || null -} - -function parseStart($item) { - const dt = $item('b span:nth-child(1)').data('dt') || $item('b').data('dt') - if (!dt) return null - - return dayjs(dt, 'YYYY-MM-DD HH:mm:ssZ') -} - -function parseItems(content, date) { - const $ = cheerio.load(content) - const localDate = date.locale('pt').format('D MMMM YYYY') - - return $(`.row:contains(${localDate})`).nextUntil('.row:not(.mt-1)').toArray() -} +const cheerio = require('cheerio') +const axios = require('axios') +const dayjs = require('dayjs') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) + +require('dayjs/locale/pt') + +module.exports = { + site: 'guiadetv.com', + days: 2, + url({ channel }) { + return `https://www.guiadetv.com/canal/${channel.site_id}` + }, + parser({ content, date }) { + const programs = [] + const items = parseItems(content, date) + items.forEach(item => { + const prev = programs[programs.length - 1] + const $item = cheerio.load(item) + const title = parseTitle($item) + let start = parseStart($item) + if (!start || !title) return + if (prev) { + prev.stop = start + } + const stop = start.add(30, 'm') + + programs.push({ + title, + description: parseDescription($item), + category: parseCategory($item), + start, + stop + }) + }) + + return programs + }, + async channels() { + const categories = [ + 'variedades', + 'tv-aberta', + 'noticias', + 'infantil', + 'filmes-e-series', + 'esportes', + 'documentarios' + ] + const promises = categories.map(category => + axios.get(`https://www.guiadetv.com/categorias/${category}.html`) + ) + + const channels = [] + const results = await Promise.all(promises).catch(console.log) + results.forEach(r => { + const $ = cheerio.load(r.data) + $('.cardchannel').each((i, el) => { + const link = $(el).find('a') + const name = link.attr('title') + const url = link.attr('href') + const site_id = url.replace('https://www.guiadetv.com/canal/', '') + + channels.push({ + lang: 'pt', + name, + site_id + }) + }) + }) + + return channels + } +} + +function parseTitle($item) { + return $item('h3').text().trim() +} + +function parseDescription($item) { + return $item('p').clone().children().remove().end().text().trim() || null +} + +function parseCategory($item) { + return $item('p > i').text().trim() || null +} + +function parseStart($item) { + const dt = $item('b span:nth-child(1)').data('dt') || $item('b').data('dt') + if (!dt) return null + + return dayjs(dt, 'YYYY-MM-DD HH:mm:ssZ') +} + +function parseItems(content, date) { + const $ = cheerio.load(content) + const localDate = date.locale('pt').format('D MMMM YYYY') + + return $(`.row:contains(${localDate})`).nextUntil('.row:not(.mt-1)').toArray() +} diff --git a/sites/guiadetv.com/guiadetv.com.test.js b/sites/guiadetv.com/guiadetv.com.test.js index d28bd6dd8..3d7c41ca5 100644 --- a/sites/guiadetv.com/guiadetv.com.test.js +++ b/sites/guiadetv.com/guiadetv.com.test.js @@ -1,80 +1,80 @@ -const { parser, url } = require('./guiadetv.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-01-18', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'canal-rural', - xmltv_id: 'CanalRural.br' -} - -it('can generate valid url', () => { - expect(url({ channel })).toBe('https://www.guiadetv.com/canal/canal-rural') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - const results = parser({ content, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(16) - expect(results[0]).toMatchObject({ - start: '2025-01-18T03:00:00.000Z', - stop: '2025-01-18T04:00:00.000Z', - title: 'Leilão', - description: null, - category: null - }) - expect(results[2]).toMatchObject({ - start: '2025-01-18T06:00:00.000Z', - stop: '2025-01-18T09:00:00.000Z', - title: 'TV Verdade', - description: null, - category: 'Jornalismo' - }) - expect(results[15]).toMatchObject({ - start: '2025-01-19T00:00:00.000Z', - stop: '2025-01-19T00:30:00.000Z', - title: 'Leilão', - description: null, - category: null - }) -}) - -it('can parse response for current day', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - const results = parser({ content, date: dayjs.utc('2025-01-15', 'YYYY-MM-DD').startOf('d') }).map( - p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - } - ) - - expect(results.length).toBe(7) - expect(results[0]).toMatchObject({ - start: '2025-01-15T21:15:00.000Z', - stop: '2025-01-15T21:45:00.000Z', - title: 'Planeta Campo Talks', - description: - 'Grandes reportagens, notícias, entrevistas e debates com foco em ações de sustentabilidade e indicadores ESG. Informações para apoiar o produtor rural a plantar e criar com olhar para o futuro.', - category: null - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - date, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) - }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./guiadetv.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-01-18', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'canal-rural', + xmltv_id: 'CanalRural.br' +} + +it('can generate valid url', () => { + expect(url({ channel })).toBe('https://www.guiadetv.com/canal/canal-rural') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + const results = parser({ content, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(16) + expect(results[0]).toMatchObject({ + start: '2025-01-18T03:00:00.000Z', + stop: '2025-01-18T04:00:00.000Z', + title: 'Leilão', + description: null, + category: null + }) + expect(results[2]).toMatchObject({ + start: '2025-01-18T06:00:00.000Z', + stop: '2025-01-18T09:00:00.000Z', + title: 'TV Verdade', + description: null, + category: 'Jornalismo' + }) + expect(results[15]).toMatchObject({ + start: '2025-01-19T00:00:00.000Z', + stop: '2025-01-19T00:30:00.000Z', + title: 'Leilão', + description: null, + category: null + }) +}) + +it('can parse response for current day', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + const results = parser({ content, date: dayjs.utc('2025-01-15', 'YYYY-MM-DD').startOf('d') }).map( + p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + } + ) + + expect(results.length).toBe(7) + expect(results[0]).toMatchObject({ + start: '2025-01-15T21:15:00.000Z', + stop: '2025-01-15T21:45:00.000Z', + title: 'Planeta Campo Talks', + description: + 'Grandes reportagens, notícias, entrevistas e debates com foco em ações de sustentabilidade e indicadores ESG. Informações para apoiar o produtor rural a plantar e criar com olhar para o futuro.', + category: null + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + date, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) + }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/guida.tv/guida.tv.test.js b/sites/guida.tv/guida.tv.test.js index 7c2d32c37..fda470ca8 100644 --- a/sites/guida.tv/guida.tv.test.js +++ b/sites/guida.tv/guida.tv.test.js @@ -1,50 +1,50 @@ -const { parser, url } = require('./guida.tv.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-11-24', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '826573/rai-1', - xmltv_id: 'Rai1.it' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://www.guida.tv/programmi-tv/palinsesto/canale/826573/rai-1.html?dt=2023-11-24' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - const results = parser({ content, channel, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2023-11-24T00:10:00.000Z', - stop: '2023-11-24T01:05:00.000Z', - title: 'Viva Rai2!' - }) - - expect(results[30]).toMatchObject({ - start: '2023-11-24T23:00:00.000Z', - stop: '2023-11-24T23:30:00.000Z', - title: 'TV 7' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: '' - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./guida.tv.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-11-24', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '826573/rai-1', + xmltv_id: 'Rai1.it' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://www.guida.tv/programmi-tv/palinsesto/canale/826573/rai-1.html?dt=2023-11-24' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + const results = parser({ content, channel, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2023-11-24T00:10:00.000Z', + stop: '2023-11-24T01:05:00.000Z', + title: 'Viva Rai2!' + }) + + expect(results[30]).toMatchObject({ + start: '2023-11-24T23:00:00.000Z', + stop: '2023-11-24T23:30:00.000Z', + title: 'TV 7' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: '' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/guidatv.sky.it/guidatv.sky.it.config.js b/sites/guidatv.sky.it/guidatv.sky.it.config.js index ddafb89d8..0649292f9 100644 --- a/sites/guidatv.sky.it/guidatv.sky.it.config.js +++ b/sites/guidatv.sky.it/guidatv.sky.it.config.js @@ -1,98 +1,98 @@ -const dayjs = require('dayjs') - -module.exports = { - site: 'guidatv.sky.it', - days: 2, - url: function ({ date, channel }) { - const [env, id] = channel.site_id.split('#') - return `https://apid.sky.it/gtv/v1/events?from=${date.format('YYYY-MM-DD')}T00:00:00Z&to=${date - .add(1, 'd') - .format('YYYY-MM-DD')}T00:00:00Z&pageSize=999&pageNum=0&env=${env}&channels=${id}` - }, - parser: function ({ content }) { - const programs = [] - const data = JSON.parse(content) - const items = data.events - if (!items.length) return programs - items.forEach(item => { - programs.push({ - title: item.eventTitle, - description: item.eventSynopsis, - category: parseCategory(item), - season: parseSeason(item), - episode: parseEpisode(item), - start: parseStart(item), - stop: parseStop(item), - url: parseURL(item), - image: parseImage(item) - }) - }) - - return programs - }, - async channels() { - const axios = require('axios') - const cheerio = require('cheerio') - - const data = await axios - .get('https://guidatv.sky.it/canali') - .then(r => r.data) - .catch(console.log) - - const $ = cheerio.load(data) - - let channels = [] - $('.c-channelsCard__container').each((i, el) => { - const name = $(el).find('.c-channelsCard__title').text() - const url = $(el).find('.c-channelsCard__link').attr('href') - const [, channelId] = url.match(/\/(\d+)$/) - - channels.push({ - lang: 'it', - site_id: `DTH#${channelId}`, - name - }) - }) - - return channels - } -} - -function parseCategory(item) { - let category = item.content.genre.name || null - const subcategory = item.content.subgenre.name || null - if (category && subcategory) { - category += `/${subcategory}` - } - return category -} - -function parseStart(item) { - return item.starttime ? dayjs(item.starttime) : null -} - -function parseStop(item) { - return item.endtime ? dayjs(item.endtime) : null -} - -function parseURL(item) { - return item.content.url ? `https://guidatv.sky.it${item.content.url}` : null -} - -function parseImage(item) { - const cover = item.content.imagesMap ? item.content.imagesMap.find(i => i.key === 'cover') : null - - return cover && cover.img && cover.img.url ? `https://guidatv.sky.it${cover.img.url}` : null -} - -function parseSeason(item) { - if (!item.content.seasonNumber) return null - if (String(item.content.seasonNumber).length > 2) return null - return item.content.seasonNumber -} - -function parseEpisode(item) { - if (!item.content.episodeNumber) return null - if (String(item.content.episodeNumber).length > 3) return null - return item.content.episodeNumber -} +const dayjs = require('dayjs') + +module.exports = { + site: 'guidatv.sky.it', + days: 2, + url: function ({ date, channel }) { + const [env, id] = channel.site_id.split('#') + return `https://apid.sky.it/gtv/v1/events?from=${date.format('YYYY-MM-DD')}T00:00:00Z&to=${date + .add(1, 'd') + .format('YYYY-MM-DD')}T00:00:00Z&pageSize=999&pageNum=0&env=${env}&channels=${id}` + }, + parser: function ({ content }) { + const programs = [] + const data = JSON.parse(content) + const items = data.events + if (!items.length) return programs + items.forEach(item => { + programs.push({ + title: item.eventTitle, + description: item.eventSynopsis, + category: parseCategory(item), + season: parseSeason(item), + episode: parseEpisode(item), + start: parseStart(item), + stop: parseStop(item), + url: parseURL(item), + image: parseImage(item) + }) + }) + + return programs + }, + async channels() { + const axios = require('axios') + const cheerio = require('cheerio') + + const data = await axios + .get('https://guidatv.sky.it/canali') + .then(r => r.data) + .catch(console.log) + + const $ = cheerio.load(data) + + let channels = [] + $('.c-channelsCard__container').each((i, el) => { + const name = $(el).find('.c-channelsCard__title').text() + const url = $(el).find('.c-channelsCard__link').attr('href') + const [, channelId] = url.match(/\/(\d+)$/) + + channels.push({ + lang: 'it', + site_id: `DTH#${channelId}`, + name + }) + }) + + return channels + } +} + +function parseCategory(item) { + let category = item.content.genre.name || null + const subcategory = item.content.subgenre.name || null + if (category && subcategory) { + category += `/${subcategory}` + } + return category +} + +function parseStart(item) { + return item.starttime ? dayjs(item.starttime) : null +} + +function parseStop(item) { + return item.endtime ? dayjs(item.endtime) : null +} + +function parseURL(item) { + return item.content.url ? `https://guidatv.sky.it${item.content.url}` : null +} + +function parseImage(item) { + const cover = item.content.imagesMap ? item.content.imagesMap.find(i => i.key === 'cover') : null + + return cover && cover.img && cover.img.url ? `https://guidatv.sky.it${cover.img.url}` : null +} + +function parseSeason(item) { + if (!item.content.seasonNumber) return null + if (String(item.content.seasonNumber).length > 2) return null + return item.content.seasonNumber +} + +function parseEpisode(item) { + if (!item.content.episodeNumber) return null + if (String(item.content.episodeNumber).length > 3) return null + return item.content.episodeNumber +} diff --git a/sites/horizon.tv/horizon.tv.config.js b/sites/horizon.tv/horizon.tv.config.js index 225021ca5..0444317cc 100644 --- a/sites/horizon.tv/horizon.tv.config.js +++ b/sites/horizon.tv/horizon.tv.config.js @@ -1,145 +1,145 @@ -const axios = require('axios') -const dayjs = require('dayjs') - -const API_ENDPOINT = 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web' - -module.exports = { - site: 'horizon.tv', - days: 3, - request: { - cache: { - ttl: 60 * 60 * 1000 // 1 hour - } - }, - url: function ({ date }) { - return `${API_ENDPOINT}/programschedules/${date.format('YYYYMMDD')}/1` - }, - async parser({ content, channel, date }) { - let programs = [] - let items = parseItems(content, channel) - if (!items.length) return programs - const d = date.format('YYYYMMDD') - const promises = [ - axios.get(`${API_ENDPOINT}/programschedules/${d}/2`), - axios.get(`${API_ENDPOINT}/programschedules/${d}/3`), - axios.get(`${API_ENDPOINT}/programschedules/${d}/4`) - ] - await Promise.allSettled(promises) - .then(results => { - results.forEach(r => { - if (r.status === 'fulfilled') { - items = items.concat(parseItems(r.value.data, channel)) - } - }) - }) - .catch(console.error) - for (let item of items) { - const detail = await loadProgramDetails(item) - programs.push({ - title: item.t, - description: parseDescription(detail), - category: parseCategory(detail), - season: parseSeason(detail), - episode: parseEpisode(detail), - actors: parseActors(detail), - directors: parseDirectors(detail), - date: parseYear(detail), - start: parseStart(item), - stop: parseStop(item) - }) - } - - return programs - }, - async channels() { - const data = await axios - .get(`${API_ENDPOINT}/channels`) - .then(r => r.data) - .catch(console.log) - - return data.channels.map(item => { - return { - lang: 'sk', - site_id: item.id.replace('lgi-obolite-sk-prod-master:5-', ''), - name: item.title - } - }) - } -} - -async function loadProgramDetails(item) { - if (!item.i) return {} - const url = `${API_ENDPOINT}/listings/${item.i}` - const data = await axios - .get(url) - .then(r => r.data) - .catch(console.log) - return data || {} -} - -function parseStart(item) { - return dayjs(item.s) -} - -function parseStop(item) { - return dayjs(item.e) -} - -function parseItems(content, channel) { - if (!content) return [] - const data = typeof content === 'string' ? JSON.parse(content) : content - if (!data || !Array.isArray(data.entries)) return [] - const entity = data.entries.find(e => e.o === `lgi-obolite-sk-prod-master:${channel.site_id}`) - return entity ? entity.l : [] -} - -function parseDescription(detail) { - if (!detail) return [] - if (!detail.program) return [] - return detail.program.longDescription || null -} - -function parseCategory(detail) { - if (!detail) return [] - if (!detail.program) return [] - if (!detail.program.categories) return [] - let categories = [] - detail.program.categories.forEach(category => { - categories.push(category.title) - }) - return categories -} - -function parseSeason(detail) { - if (!detail) return null - if (!detail.program) return null - if (!detail.program.seriesNumber) return null - if (String(detail.program.seriesNumber).length > 2) return null - return detail.program.seriesNumber -} - -function parseEpisode(detail) { - if (!detail) return null - if (!detail.program) return null - if (!detail.program.seriesEpisodeNumber) return null - if (String(detail.program.seriesEpisodeNumber).length > 3) return null - return detail.program.seriesEpisodeNumber -} - -function parseDirectors(detail) { - if (!detail) return [] - if (!detail.program) return [] - return detail.program.directors || [] -} - -function parseActors(detail) { - if (!detail) return [] - if (!detail.program) return [] - return detail.program.cast || [] -} - -function parseYear(detail) { - if (!detail) return null - if (!detail.program) return null - return detail.program.year || null -} +const axios = require('axios') +const dayjs = require('dayjs') + +const API_ENDPOINT = 'https://legacy-static.oesp.horizon.tv/oesp/v4/SK/slk/web' + +module.exports = { + site: 'horizon.tv', + days: 3, + request: { + cache: { + ttl: 60 * 60 * 1000 // 1 hour + } + }, + url: function ({ date }) { + return `${API_ENDPOINT}/programschedules/${date.format('YYYYMMDD')}/1` + }, + async parser({ content, channel, date }) { + let programs = [] + let items = parseItems(content, channel) + if (!items.length) return programs + const d = date.format('YYYYMMDD') + const promises = [ + axios.get(`${API_ENDPOINT}/programschedules/${d}/2`), + axios.get(`${API_ENDPOINT}/programschedules/${d}/3`), + axios.get(`${API_ENDPOINT}/programschedules/${d}/4`) + ] + await Promise.allSettled(promises) + .then(results => { + results.forEach(r => { + if (r.status === 'fulfilled') { + items = items.concat(parseItems(r.value.data, channel)) + } + }) + }) + .catch(console.error) + for (let item of items) { + const detail = await loadProgramDetails(item) + programs.push({ + title: item.t, + description: parseDescription(detail), + category: parseCategory(detail), + season: parseSeason(detail), + episode: parseEpisode(detail), + actors: parseActors(detail), + directors: parseDirectors(detail), + date: parseYear(detail), + start: parseStart(item), + stop: parseStop(item) + }) + } + + return programs + }, + async channels() { + const data = await axios + .get(`${API_ENDPOINT}/channels`) + .then(r => r.data) + .catch(console.log) + + return data.channels.map(item => { + return { + lang: 'sk', + site_id: item.id.replace('lgi-obolite-sk-prod-master:5-', ''), + name: item.title + } + }) + } +} + +async function loadProgramDetails(item) { + if (!item.i) return {} + const url = `${API_ENDPOINT}/listings/${item.i}` + const data = await axios + .get(url) + .then(r => r.data) + .catch(console.log) + return data || {} +} + +function parseStart(item) { + return dayjs(item.s) +} + +function parseStop(item) { + return dayjs(item.e) +} + +function parseItems(content, channel) { + if (!content) return [] + const data = typeof content === 'string' ? JSON.parse(content) : content + if (!data || !Array.isArray(data.entries)) return [] + const entity = data.entries.find(e => e.o === `lgi-obolite-sk-prod-master:${channel.site_id}`) + return entity ? entity.l : [] +} + +function parseDescription(detail) { + if (!detail) return [] + if (!detail.program) return [] + return detail.program.longDescription || null +} + +function parseCategory(detail) { + if (!detail) return [] + if (!detail.program) return [] + if (!detail.program.categories) return [] + let categories = [] + detail.program.categories.forEach(category => { + categories.push(category.title) + }) + return categories +} + +function parseSeason(detail) { + if (!detail) return null + if (!detail.program) return null + if (!detail.program.seriesNumber) return null + if (String(detail.program.seriesNumber).length > 2) return null + return detail.program.seriesNumber +} + +function parseEpisode(detail) { + if (!detail) return null + if (!detail.program) return null + if (!detail.program.seriesEpisodeNumber) return null + if (String(detail.program.seriesEpisodeNumber).length > 3) return null + return detail.program.seriesEpisodeNumber +} + +function parseDirectors(detail) { + if (!detail) return [] + if (!detail.program) return [] + return detail.program.directors || [] +} + +function parseActors(detail) { + if (!detail) return [] + if (!detail.program) return [] + return detail.program.cast || [] +} + +function parseYear(detail) { + if (!detail) return null + if (!detail.program) return null + return detail.program.year || null +} diff --git a/sites/hoy.tv/hoy.tv.config.js b/sites/hoy.tv/hoy.tv.config.js index 30cc2b823..7ae7ad7ef 100644 --- a/sites/hoy.tv/hoy.tv.config.js +++ b/sites/hoy.tv/hoy.tv.config.js @@ -1,63 +1,63 @@ -const axios = require('axios') -const convert = require('xml-js') -const dayjs = require('dayjs') -const timezone = require('dayjs/plugin/timezone') - -dayjs.extend(timezone) - -module.exports = { - site: 'hoy.tv', - days: 2, - request: { - cache: { - ttl: 60 * 60 * 1000 // 1h - } - }, - url: function ({ channel, date }) { - return `https://epg-file.hoy.tv/hoy/OTT${channel.site_id}${date.format('YYYYMMDD')}.xml` - }, - parser({ content, date }) { - const data = convert.xml2js(content, { - compact: true, - ignoreDeclaration: true, - ignoreAttributes: true - }) - - const programs = [] - - for (let item of data.ProgramGuide.Channel.EpgItem) { - const start = dayjs.tz(item.EpgStartDateTime._text, 'YYYY-MM-DD HH:mm:ss', 'Asia/Hong_Kong') - - if (!date.isSame(start, 'day')) { - continue - } - - const epIndex = item.EpisodeInfo.EpisodeIndex._text - const subtitle = parseInt(epIndex) > 0 ? `第${epIndex}集` : undefined - - programs.push({ - title: `${item.ComScore.ns_st_pr._text}${item.EpgOtherInfo?._text || ''}`, - sub_title: subtitle, - description: item.EpisodeInfo.EpisodeLongDescription._text, - start, - stop: dayjs.tz(item.EpgEndDateTime._text, 'YYYY-MM-DD HH:mm:ss', 'Asia/Hong_Kong') - }) - } - - return programs - }, - async channels() { - const data = await axios - .get('https://api2.hoy.tv/api/v2/a/channel') - .then(r => r.data) - .catch(console.error) - - return data.data.map(c => { - return { - site_id: c.videos.id, - name: c.name.zh_hk, - lang: 'zh' - } - }) - } -} +const axios = require('axios') +const convert = require('xml-js') +const dayjs = require('dayjs') +const timezone = require('dayjs/plugin/timezone') + +dayjs.extend(timezone) + +module.exports = { + site: 'hoy.tv', + days: 2, + request: { + cache: { + ttl: 60 * 60 * 1000 // 1h + } + }, + url: function ({ channel, date }) { + return `https://epg-file.hoy.tv/hoy/OTT${channel.site_id}${date.format('YYYYMMDD')}.xml` + }, + parser({ content, date }) { + const data = convert.xml2js(content, { + compact: true, + ignoreDeclaration: true, + ignoreAttributes: true + }) + + const programs = [] + + for (let item of data.ProgramGuide.Channel.EpgItem) { + const start = dayjs.tz(item.EpgStartDateTime._text, 'YYYY-MM-DD HH:mm:ss', 'Asia/Hong_Kong') + + if (!date.isSame(start, 'day')) { + continue + } + + const epIndex = item.EpisodeInfo.EpisodeIndex._text + const subtitle = parseInt(epIndex) > 0 ? `第${epIndex}集` : undefined + + programs.push({ + title: `${item.ComScore.ns_st_pr._text}${item.EpgOtherInfo?._text || ''}`, + sub_title: subtitle, + description: item.EpisodeInfo.EpisodeLongDescription._text, + start, + stop: dayjs.tz(item.EpgEndDateTime._text, 'YYYY-MM-DD HH:mm:ss', 'Asia/Hong_Kong') + }) + } + + return programs + }, + async channels() { + const data = await axios + .get('https://api2.hoy.tv/api/v2/a/channel') + .then(r => r.data) + .catch(console.error) + + return data.data.map(c => { + return { + site_id: c.videos.id, + name: c.name.zh_hk, + lang: 'zh' + } + }) + } +} diff --git a/sites/i.mjh.nz/i.mjh.nz.config.js b/sites/i.mjh.nz/i.mjh.nz.config.js index d5c82f082..d2ec46206 100644 --- a/sites/i.mjh.nz/i.mjh.nz.config.js +++ b/sites/i.mjh.nz/i.mjh.nz.config.js @@ -1,188 +1,188 @@ -const dayjs = require('dayjs') -const axios = require('axios') -const parser = require('epg-parser') -const isBetween = require('dayjs/plugin/isBetween') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(isBetween) -dayjs.extend(customParseFormat) - -const API_ENDPOINT = 'https://raw.githubusercontent.com/matthuisman/i.mjh.nz/master' - -module.exports = { - site: 'i.mjh.nz', - days: 2, - request: { - cache: { - ttl: 3 * 60 * 60 * 1000 // 3h - }, - maxContentLength: 100 * 1024 * 1024 // 100Mb - }, - url({ channel }) { - const [path] = channel.site_id.split('#') - - return `${API_ENDPOINT}/${path}.xml` - }, - parser({ content, channel, date }) { - const items = parseItems(content, channel, date) - - const programs = items.map(item => { - return { - ...item, - title: getTitle(item), - description: getDescription(item), - categories: getCategories(item), - icon: getIcon(item) - } - }) - - return mergeMovieParts(programs) - }, - async channels({ provider }) { - const providers = { - pluto: [ - { path: 'PlutoTV/br', lang: 'pt' }, - { path: 'PlutoTV/ca', lang: 'en' }, - { path: 'PlutoTV/cl', lang: 'es' }, - { path: 'PlutoTV/de', lang: 'de' }, - { path: 'PlutoTV/dk', lang: 'da' }, - { path: 'PlutoTV/es', lang: 'es' }, - { path: 'PlutoTV/fr', lang: 'fr' }, - { path: 'PlutoTV/gb', lang: 'en' }, - { path: 'PlutoTV/it', lang: 'it' }, - { path: 'PlutoTV/mx', lang: 'es' }, - { path: 'PlutoTV/no', lang: 'no' }, - { path: 'PlutoTV/se', lang: 'sv' }, - { path: 'PlutoTV/us', lang: 'en' } - ], - plex: [ - { path: 'Plex/au', lang: 'en' }, - { path: 'Plex/ca', lang: 'en' }, - { path: 'Plex/es', lang: 'es' }, - { path: 'Plex/mx', lang: 'es' }, - { path: 'Plex/nz', lang: 'en' }, - { path: 'Plex/us', lang: 'en' } - ], - samsung: [ - { path: 'SamsungTVPlus/at', lang: 'de' }, - { path: 'SamsungTVPlus/ca', lang: 'en' }, - { path: 'SamsungTVPlus/ch', lang: 'de' }, - { path: 'SamsungTVPlus/de', lang: 'de' }, - { path: 'SamsungTVPlus/es', lang: 'es' }, - { path: 'SamsungTVPlus/fr', lang: 'fr' }, - { path: 'SamsungTVPlus/gb', lang: 'en' }, - { path: 'SamsungTVPlus/in', lang: 'en' }, - { path: 'SamsungTVPlus/it', lang: 'it' }, - { path: 'SamsungTVPlus/kr', lang: 'ko' }, - { path: 'SamsungTVPlus/us', lang: 'en' } - ], - skygo: [{ path: 'SkyGo/epg', lang: 'en' }], - stirr: [{ path: 'Stirr/all', lang: 'en' }], - foxtel: [{ path: 'Foxtel/epg', lang: 'en' }], - binge: [{ path: 'Binge/epg', lang: 'en' }], - dstv: [{ path: 'DStv/za', lang: 'en' }], - flash: [{ path: 'Flash/epg', lang: 'en' }], - kayo: [{ path: 'Kayo/epg', lang: 'en' }], - metv: [{ path: 'MeTV/epg', lang: 'en' }], - optus: [{ path: 'Optus/epg', lang: 'en' }], - pbs: [{ path: 'PBS/all', lang: 'en' }], - roku: [{ path: 'Roku/epg', lang: 'en' }], - singtel: [{ path: 'Singtel/epg', lang: 'en' }], - skysportnow: [{ path: 'SkySportNow/epg', lang: 'en' }], - au: [ - { path: 'au/Adelaide/epg', lang: 'en' }, - { path: 'au/Brisbane/epg', lang: 'en' }, - { path: 'au/Canberra/epg', lang: 'en' }, - { path: 'au/Darwin/epg', lang: 'en' }, - { path: 'au/Hobart/epg', lang: 'en' }, - { path: 'au/Melbourne/epg', lang: 'en' }, - { path: 'au/Perth/epg', lang: 'en' }, - { path: 'au/Sydney/epg', lang: 'en' } - ], - hgtvgo: [{ path: 'hgtv_go/epg', lang: 'en' }], - nz: [{ path: 'nz/epg', lang: 'en' }] - } - - const channels = [] - - const providerOptions = providers[provider] - for (const option of providerOptions) { - const xml = await axios - .get(`${API_ENDPOINT}/${option.path}.xml`) - .then(r => r.data) - .catch(console.error) - const data = parser.parse(xml) - - data.channels.forEach(item => { - channels.push({ - lang: option.lang, - site_id: `${option.path}#${item.id}`, - name: item.name[0].value - }) - }) - } - - return channels - } -} - -function mergeMovieParts(programs) { - const output = [] - - programs.forEach(prog => { - let prev = output[output.length - 1] - let found = - prev && - prog.categories.includes('Movie') && - prev.title === prog.title && - prev.description === prog.description - - if (found) { - prev.stop = prog.stop - } else { - output.push(prog) - } - }) - - return output -} - -function getTitle(item) { - return item.title.length ? item.title[0].value : null -} - -function getDescription(item) { - return item.desc.length ? item.desc[0].value : null -} - -function getCategories(item) { - return item.category.map(c => c.value) -} - -function getIcon(item) { - return item.icon && item.icon.length ? item.icon[0].src : null -} - -function parseItems(content, channel, date) { - try { - const curr_day = date - const next_day = date.add(1, 'd') - const [, site_id] = channel.site_id.split('#') - const data = parser.parse(content) - if (!data || !Array.isArray(data.programs)) return [] - - return data.programs - .filter( - p => - p.channel === site_id && dayjs(p.start, 'YYYYMMDDHHmmss ZZ').isBetween(curr_day, next_day) - ) - .map(p => { - if (Array.isArray(p.date) && p.date.length) { - p.date = p.date[0] - } - return p - }) - } catch { - return [] - } -} +const dayjs = require('dayjs') +const axios = require('axios') +const parser = require('epg-parser') +const isBetween = require('dayjs/plugin/isBetween') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(isBetween) +dayjs.extend(customParseFormat) + +const API_ENDPOINT = 'https://raw.githubusercontent.com/matthuisman/i.mjh.nz/master' + +module.exports = { + site: 'i.mjh.nz', + days: 2, + request: { + cache: { + ttl: 3 * 60 * 60 * 1000 // 3h + }, + maxContentLength: 100 * 1024 * 1024 // 100Mb + }, + url({ channel }) { + const [path] = channel.site_id.split('#') + + return `${API_ENDPOINT}/${path}.xml` + }, + parser({ content, channel, date }) { + const items = parseItems(content, channel, date) + + const programs = items.map(item => { + return { + ...item, + title: getTitle(item), + description: getDescription(item), + categories: getCategories(item), + icon: getIcon(item) + } + }) + + return mergeMovieParts(programs) + }, + async channels({ provider }) { + const providers = { + pluto: [ + { path: 'PlutoTV/br', lang: 'pt' }, + { path: 'PlutoTV/ca', lang: 'en' }, + { path: 'PlutoTV/cl', lang: 'es' }, + { path: 'PlutoTV/de', lang: 'de' }, + { path: 'PlutoTV/dk', lang: 'da' }, + { path: 'PlutoTV/es', lang: 'es' }, + { path: 'PlutoTV/fr', lang: 'fr' }, + { path: 'PlutoTV/gb', lang: 'en' }, + { path: 'PlutoTV/it', lang: 'it' }, + { path: 'PlutoTV/mx', lang: 'es' }, + { path: 'PlutoTV/no', lang: 'no' }, + { path: 'PlutoTV/se', lang: 'sv' }, + { path: 'PlutoTV/us', lang: 'en' } + ], + plex: [ + { path: 'Plex/au', lang: 'en' }, + { path: 'Plex/ca', lang: 'en' }, + { path: 'Plex/es', lang: 'es' }, + { path: 'Plex/mx', lang: 'es' }, + { path: 'Plex/nz', lang: 'en' }, + { path: 'Plex/us', lang: 'en' } + ], + samsung: [ + { path: 'SamsungTVPlus/at', lang: 'de' }, + { path: 'SamsungTVPlus/ca', lang: 'en' }, + { path: 'SamsungTVPlus/ch', lang: 'de' }, + { path: 'SamsungTVPlus/de', lang: 'de' }, + { path: 'SamsungTVPlus/es', lang: 'es' }, + { path: 'SamsungTVPlus/fr', lang: 'fr' }, + { path: 'SamsungTVPlus/gb', lang: 'en' }, + { path: 'SamsungTVPlus/in', lang: 'en' }, + { path: 'SamsungTVPlus/it', lang: 'it' }, + { path: 'SamsungTVPlus/kr', lang: 'ko' }, + { path: 'SamsungTVPlus/us', lang: 'en' } + ], + skygo: [{ path: 'SkyGo/epg', lang: 'en' }], + stirr: [{ path: 'Stirr/all', lang: 'en' }], + foxtel: [{ path: 'Foxtel/epg', lang: 'en' }], + binge: [{ path: 'Binge/epg', lang: 'en' }], + dstv: [{ path: 'DStv/za', lang: 'en' }], + flash: [{ path: 'Flash/epg', lang: 'en' }], + kayo: [{ path: 'Kayo/epg', lang: 'en' }], + metv: [{ path: 'MeTV/epg', lang: 'en' }], + optus: [{ path: 'Optus/epg', lang: 'en' }], + pbs: [{ path: 'PBS/all', lang: 'en' }], + roku: [{ path: 'Roku/epg', lang: 'en' }], + singtel: [{ path: 'Singtel/epg', lang: 'en' }], + skysportnow: [{ path: 'SkySportNow/epg', lang: 'en' }], + au: [ + { path: 'au/Adelaide/epg', lang: 'en' }, + { path: 'au/Brisbane/epg', lang: 'en' }, + { path: 'au/Canberra/epg', lang: 'en' }, + { path: 'au/Darwin/epg', lang: 'en' }, + { path: 'au/Hobart/epg', lang: 'en' }, + { path: 'au/Melbourne/epg', lang: 'en' }, + { path: 'au/Perth/epg', lang: 'en' }, + { path: 'au/Sydney/epg', lang: 'en' } + ], + hgtvgo: [{ path: 'hgtv_go/epg', lang: 'en' }], + nz: [{ path: 'nz/epg', lang: 'en' }] + } + + const channels = [] + + const providerOptions = providers[provider] + for (const option of providerOptions) { + const xml = await axios + .get(`${API_ENDPOINT}/${option.path}.xml`) + .then(r => r.data) + .catch(console.error) + const data = parser.parse(xml) + + data.channels.forEach(item => { + channels.push({ + lang: option.lang, + site_id: `${option.path}#${item.id}`, + name: item.name[0].value + }) + }) + } + + return channels + } +} + +function mergeMovieParts(programs) { + const output = [] + + programs.forEach(prog => { + let prev = output[output.length - 1] + let found = + prev && + prog.categories.includes('Movie') && + prev.title === prog.title && + prev.description === prog.description + + if (found) { + prev.stop = prog.stop + } else { + output.push(prog) + } + }) + + return output +} + +function getTitle(item) { + return item.title.length ? item.title[0].value : null +} + +function getDescription(item) { + return item.desc.length ? item.desc[0].value : null +} + +function getCategories(item) { + return item.category.map(c => c.value) +} + +function getIcon(item) { + return item.icon && item.icon.length ? item.icon[0].src : null +} + +function parseItems(content, channel, date) { + try { + const curr_day = date + const next_day = date.add(1, 'd') + const [, site_id] = channel.site_id.split('#') + const data = parser.parse(content) + if (!data || !Array.isArray(data.programs)) return [] + + return data.programs + .filter( + p => + p.channel === site_id && dayjs(p.start, 'YYYYMMDDHHmmss ZZ').isBetween(curr_day, next_day) + ) + .map(p => { + if (Array.isArray(p.date) && p.date.length) { + p.date = p.date[0] + } + return p + }) + } catch { + return [] + } +} diff --git a/sites/i.mjh.nz/i.mjh.nz.test.js b/sites/i.mjh.nz/i.mjh.nz.test.js index d3d3f879c..27fd8f4fc 100644 --- a/sites/i.mjh.nz/i.mjh.nz.test.js +++ b/sites/i.mjh.nz/i.mjh.nz.test.js @@ -1,47 +1,47 @@ -const { parser, url } = require('./i.mjh.nz.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-06-23', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'Plex/all#5e20b730f2f8d5003d739db7-5eea605674085f0040ddc7a6', - xmltv_id: 'DarkMatterTV.us', - lang: 'en' -} - -it('can generate valid url', () => { - expect(url({ channel })).toBe( - 'https://raw.githubusercontent.com/matthuisman/i.mjh.nz/master/Plex/all.xml' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.xml')) - const results = parser({ content, channel, date }) - - expect(results.length).toBe(11) - expect(results[0]).toMatchObject({ - start: '2023-06-23T07:14:32.000Z', - stop: '2023-06-23T09:09:36.000Z', - title: 'Killers Within', - date: '20180101', - description: - 'With her son being held captive by a criminal gang, police officer Amanda Doyle, together with her ex-husband and three unlikely allies, takes part in a desperate plot to hold a wealthy banker and his family to ransom. But this is no ordinary family.', - icon: 'https://provider-static.plex.tv/epg/images/thumbnails/darkmatter-tv-fallback.jpg', - categories: ['Movie'] - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: '404: Not Found', - channel, - date - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./i.mjh.nz.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-06-23', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'Plex/all#5e20b730f2f8d5003d739db7-5eea605674085f0040ddc7a6', + xmltv_id: 'DarkMatterTV.us', + lang: 'en' +} + +it('can generate valid url', () => { + expect(url({ channel })).toBe( + 'https://raw.githubusercontent.com/matthuisman/i.mjh.nz/master/Plex/all.xml' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.xml')) + const results = parser({ content, channel, date }) + + expect(results.length).toBe(11) + expect(results[0]).toMatchObject({ + start: '2023-06-23T07:14:32.000Z', + stop: '2023-06-23T09:09:36.000Z', + title: 'Killers Within', + date: '20180101', + description: + 'With her son being held captive by a criminal gang, police officer Amanda Doyle, together with her ex-husband and three unlikely allies, takes part in a desperate plot to hold a wealthy banker and his family to ransom. But this is no ordinary family.', + icon: 'https://provider-static.plex.tv/epg/images/thumbnails/darkmatter-tv-fallback.jpg', + categories: ['Movie'] + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: '404: Not Found', + channel, + date + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/i24news.tv/i24news.tv.config.js b/sites/i24news.tv/i24news.tv.config.js index b8539910c..a0f9dc09e 100644 --- a/sites/i24news.tv/i24news.tv.config.js +++ b/sites/i24news.tv/i24news.tv.config.js @@ -1,65 +1,65 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'i24news.tv', - days: 2, - url: function ({ channel }) { - return `https://api.i24news.tv/v2/${channel.site_id}/schedules` - }, - parser: function ({ content, date }) { - let programs = [] - const items = parseItems(content, date) - items.forEach(item => { - if (!item.show) return - programs.push({ - title: item.show.title, - description: item.show.body, - image: parseImage(item), - start: parseStart(item, date), - stop: parseStop(item, date) - }) - }) - - return programs - } -} - -function parseImage(item) { - return item.show.image ? item.show.image.href : null -} - -function parseStart(item, date) { - if (!item.startHour) return null - - return dayjs.tz( - `${date.format('YYYY-MM-DD')} ${item.startHour}`, - 'YYYY-MM-DD HH:mm', - 'Asia/Jerusalem' - ) -} - -function parseStop(item, date) { - if (!item.endHour) return null - - return dayjs.tz( - `${date.format('YYYY-MM-DD')} ${item.endHour}`, - 'YYYY-MM-DD HH:mm', - 'Asia/Jerusalem' - ) -} - -function parseItems(content, date) { - const data = JSON.parse(content) - if (!Array.isArray(data)) return [] - let day = date.day() - 1 - day = day < 0 ? 6 : day - - return data.filter(item => item.day === day) -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'i24news.tv', + days: 2, + url: function ({ channel }) { + return `https://api.i24news.tv/v2/${channel.site_id}/schedules` + }, + parser: function ({ content, date }) { + let programs = [] + const items = parseItems(content, date) + items.forEach(item => { + if (!item.show) return + programs.push({ + title: item.show.title, + description: item.show.body, + image: parseImage(item), + start: parseStart(item, date), + stop: parseStop(item, date) + }) + }) + + return programs + } +} + +function parseImage(item) { + return item.show.image ? item.show.image.href : null +} + +function parseStart(item, date) { + if (!item.startHour) return null + + return dayjs.tz( + `${date.format('YYYY-MM-DD')} ${item.startHour}`, + 'YYYY-MM-DD HH:mm', + 'Asia/Jerusalem' + ) +} + +function parseStop(item, date) { + if (!item.endHour) return null + + return dayjs.tz( + `${date.format('YYYY-MM-DD')} ${item.endHour}`, + 'YYYY-MM-DD HH:mm', + 'Asia/Jerusalem' + ) +} + +function parseItems(content, date) { + const data = JSON.parse(content) + if (!Array.isArray(data)) return [] + let day = date.day() - 1 + day = day < 0 ? 6 : day + + return data.filter(item => item.day === day) +} diff --git a/sites/iltalehti.fi/iltalehti.fi.config.js b/sites/iltalehti.fi/iltalehti.fi.config.js index 4302794ba..43ad5f8e4 100644 --- a/sites/iltalehti.fi/iltalehti.fi.config.js +++ b/sites/iltalehti.fi/iltalehti.fi.config.js @@ -1,77 +1,77 @@ -const axios = require('axios') -const dayjs = require('dayjs') - -module.exports = { - site: 'iltalehti.fi', - days: 2, - request: { - cache: { - ttl: 60 * 60 * 1000 // 1 hour - } - }, - url: function ({ channel, date }) { - const [group] = channel.site_id.split('#') - - return `https://telkku.com/api/channel-groups/default_builtin_channelgroup${group}/offering?startTime=00%3A00%3A00.000&duration=PT24H&inclusionPolicy=IncludeOngoingAlso&limit=1000&tvDate=${date.format( - 'YYYY-MM-DD' - )}&view=PublicationDetails` - }, - parser: function ({ content, channel }) { - let programs = [] - const items = getItems(content, channel) - items.forEach(item => { - programs.push({ - title: item.title, - description: item.description, - image: getImage(item), - start: getStart(item), - stop: getStop(item) - }) - }) - - return programs - }, - async channels() { - const data = await axios - .get('https://telkku.com/api/channel-groups') - .then(r => r.data) - .catch(console.log) - - let items = [] - data.response.forEach(group => { - group.channels.forEach(channel => { - items.push({ - lang: 'fi', - site_id: `${group.id}#${channel.id}`, - name: channel.name - }) - }) - }) - - return items - } -} - -function getImage(item) { - const image = item.images.find(i => i.type === 'default' && i.sizeTag === '1200x630') - - return image ? image.url : null -} - -function getStart(item) { - return dayjs(item.startTime) -} - -function getStop(item) { - return dayjs(item.endTime) -} - -function getItems(content, channel) { - const [, channelId] = channel.site_id.split('#') - const data = JSON.parse(content) - if (!data || !data.response || !Array.isArray(data.response.publicationsByChannel)) return [] - const channelData = data.response.publicationsByChannel.find(i => i.channel.id === channelId) - if (!channelData || !Array.isArray(channelData.publications)) return [] - - return channelData.publications -} +const axios = require('axios') +const dayjs = require('dayjs') + +module.exports = { + site: 'iltalehti.fi', + days: 2, + request: { + cache: { + ttl: 60 * 60 * 1000 // 1 hour + } + }, + url: function ({ channel, date }) { + const [group] = channel.site_id.split('#') + + return `https://telkku.com/api/channel-groups/default_builtin_channelgroup${group}/offering?startTime=00%3A00%3A00.000&duration=PT24H&inclusionPolicy=IncludeOngoingAlso&limit=1000&tvDate=${date.format( + 'YYYY-MM-DD' + )}&view=PublicationDetails` + }, + parser: function ({ content, channel }) { + let programs = [] + const items = getItems(content, channel) + items.forEach(item => { + programs.push({ + title: item.title, + description: item.description, + image: getImage(item), + start: getStart(item), + stop: getStop(item) + }) + }) + + return programs + }, + async channels() { + const data = await axios + .get('https://telkku.com/api/channel-groups') + .then(r => r.data) + .catch(console.log) + + let items = [] + data.response.forEach(group => { + group.channels.forEach(channel => { + items.push({ + lang: 'fi', + site_id: `${group.id}#${channel.id}`, + name: channel.name + }) + }) + }) + + return items + } +} + +function getImage(item) { + const image = item.images.find(i => i.type === 'default' && i.sizeTag === '1200x630') + + return image ? image.url : null +} + +function getStart(item) { + return dayjs(item.startTime) +} + +function getStop(item) { + return dayjs(item.endTime) +} + +function getItems(content, channel) { + const [, channelId] = channel.site_id.split('#') + const data = JSON.parse(content) + if (!data || !data.response || !Array.isArray(data.response.publicationsByChannel)) return [] + const channelData = data.response.publicationsByChannel.find(i => i.channel.id === channelId) + if (!channelData || !Array.isArray(channelData.publications)) return [] + + return channelData.publications +} diff --git a/sites/iltalehti.fi/iltalehti.fi.test.js b/sites/iltalehti.fi/iltalehti.fi.test.js index 954eb422d..14477a3c1 100644 --- a/sites/iltalehti.fi/iltalehti.fi.test.js +++ b/sites/iltalehti.fi/iltalehti.fi.test.js @@ -1,47 +1,47 @@ -const { parser, url } = require('./iltalehti.fi.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-10-29', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '1#yle-tv1', - xmltv_id: 'YleTV1.fi' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://telkku.com/api/channel-groups/default_builtin_channelgroup1/offering?startTime=00%3A00%3A00.000&duration=PT24H&inclusionPolicy=IncludeOngoingAlso&limit=1000&tvDate=2022-10-29&view=PublicationDetails' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const results = parser({ content, channel }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2022-10-28T20:50:00.000Z', - stop: '2022-10-28T21:20:00.000Z', - title: 'Puoli seitsemän', - description: - 'Vieraana näyttelijä Elias Salonen. Puoli seiskassa vietetään sekä halloweeniä että joulua, kun Olli-Pekka tapaa todellisen jouluttajan. Juontajina Anniina Valtonen, Tuulianna Tola ja Olli-Pekka Kursi.', - image: - 'https://thumbor.prod.telkku.com/YTglotoUl7aJtzPtYnvM9tH03sY=/1200x630/smart/filters:quality(86):format(jpeg)/img.prod.telkku.com/program-images/0f885238ac16ce167a9d80eace450254.jpg' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: fs.readFileSync(path.resolve(__dirname, '__data__/no-content.json')), - channel - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./iltalehti.fi.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-10-29', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '1#yle-tv1', + xmltv_id: 'YleTV1.fi' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://telkku.com/api/channel-groups/default_builtin_channelgroup1/offering?startTime=00%3A00%3A00.000&duration=PT24H&inclusionPolicy=IncludeOngoingAlso&limit=1000&tvDate=2022-10-29&view=PublicationDetails' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const results = parser({ content, channel }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2022-10-28T20:50:00.000Z', + stop: '2022-10-28T21:20:00.000Z', + title: 'Puoli seitsemän', + description: + 'Vieraana näyttelijä Elias Salonen. Puoli seiskassa vietetään sekä halloweeniä että joulua, kun Olli-Pekka tapaa todellisen jouluttajan. Juontajina Anniina Valtonen, Tuulianna Tola ja Olli-Pekka Kursi.', + image: + 'https://thumbor.prod.telkku.com/YTglotoUl7aJtzPtYnvM9tH03sY=/1200x630/smart/filters:quality(86):format(jpeg)/img.prod.telkku.com/program-images/0f885238ac16ce167a9d80eace450254.jpg' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: fs.readFileSync(path.resolve(__dirname, '__data__/no-content.json')), + channel + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/indihometv.com/indihometv.com.config.js b/sites/indihometv.com/indihometv.com.config.js index cf943af82..48e581656 100644 --- a/sites/indihometv.com/indihometv.com.config.js +++ b/sites/indihometv.com/indihometv.com.config.js @@ -1,92 +1,92 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -const tz = 'Asia/Jakarta' - -module.exports = { - site: 'indihometv.com', - days: 2, - url({ channel }) { - return `https://www.indihometv.com/livetv/${channel.site_id}` - }, - parser({ content, date }) { - const programs = [] - const [$, items] = parseItems(content, date) - items.forEach(item => { - const prev = programs[programs.length - 1] - const $item = $(item) - let start = parseStart($item, date) - if (prev && start.isBefore(prev.start)) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } - let stop = parseStop($item, date) - if (stop.isBefore(start)) { - stop = stop.add(1, 'd') - date = date.add(1, 'd') - } - programs.push({ - title: parseTitle($item), - start, - stop - }) - }) - - return programs - }, - async channels() { - const axios = require('axios') - const cheerio = require('cheerio') - const data = await axios - .get('https://www.indihometv.com/tv/live') - .then(response => response.data) - .catch(console.error) - - const $ = cheerio.load(data) - const items = $('#channelContainer a.channel-item').toArray() - const channels = items.map(item => { - const $item = $(item) - - return { - lang: 'id', - site_id: $item.data('url').substr($item.data('url').lastIndexOf('/') + 1), - name: $item.data('name') - } - }) - - return channels - } -} - -function parseStart($item, date) { - const timeString = $item.find('p').text() - const [, start] = timeString.match(/(\d{2}:\d{2}) -/) || [null, null] - const dateString = `${date.format('YYYY-MM-DD')} ${start}` - - return dayjs.tz(dateString, 'YYYY-MM-DD HH:mm', tz) -} - -function parseStop($item, date) { - const timeString = $item.find('p').text() - const [, stop] = timeString.match(/- (\d{2}:\d{2})/) || [null, null] - const dateString = `${date.format('YYYY-MM-DD')} ${stop}` - - return dayjs.tz(dateString, 'YYYY-MM-DD HH:mm', tz) -} - -function parseTitle($item) { - return $item.find('b').text() -} - -function parseItems(content, date) { - const $ = cheerio.load(content) - - return [$, $(`#pills-${date.format('YYYY-MM-DD')} .schedule-item`).toArray()] -} +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +const tz = 'Asia/Jakarta' + +module.exports = { + site: 'indihometv.com', + days: 2, + url({ channel }) { + return `https://www.indihometv.com/livetv/${channel.site_id}` + }, + parser({ content, date }) { + const programs = [] + const [$, items] = parseItems(content, date) + items.forEach(item => { + const prev = programs[programs.length - 1] + const $item = $(item) + let start = parseStart($item, date) + if (prev && start.isBefore(prev.start)) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } + let stop = parseStop($item, date) + if (stop.isBefore(start)) { + stop = stop.add(1, 'd') + date = date.add(1, 'd') + } + programs.push({ + title: parseTitle($item), + start, + stop + }) + }) + + return programs + }, + async channels() { + const axios = require('axios') + const cheerio = require('cheerio') + const data = await axios + .get('https://www.indihometv.com/tv/live') + .then(response => response.data) + .catch(console.error) + + const $ = cheerio.load(data) + const items = $('#channelContainer a.channel-item').toArray() + const channels = items.map(item => { + const $item = $(item) + + return { + lang: 'id', + site_id: $item.data('url').substr($item.data('url').lastIndexOf('/') + 1), + name: $item.data('name') + } + }) + + return channels + } +} + +function parseStart($item, date) { + const timeString = $item.find('p').text() + const [, start] = timeString.match(/(\d{2}:\d{2}) -/) || [null, null] + const dateString = `${date.format('YYYY-MM-DD')} ${start}` + + return dayjs.tz(dateString, 'YYYY-MM-DD HH:mm', tz) +} + +function parseStop($item, date) { + const timeString = $item.find('p').text() + const [, stop] = timeString.match(/- (\d{2}:\d{2})/) || [null, null] + const dateString = `${date.format('YYYY-MM-DD')} ${stop}` + + return dayjs.tz(dateString, 'YYYY-MM-DD HH:mm', tz) +} + +function parseTitle($item) { + return $item.find('b').text() +} + +function parseItems(content, date) { + const $ = cheerio.load(content) + + return [$, $(`#pills-${date.format('YYYY-MM-DD')} .schedule-item`).toArray()] +} diff --git a/sites/ionplustv.com/ionplustv.com.config.js b/sites/ionplustv.com/ionplustv.com.config.js index 77073521e..231bea599 100644 --- a/sites/ionplustv.com/ionplustv.com.config.js +++ b/sites/ionplustv.com/ionplustv.com.config.js @@ -1,107 +1,107 @@ -const dayjs = require('dayjs') -const cheerio = require('cheerio') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'ionplustv.com', - days: 2, - url({ date }) { - return `https://ionplustv.com/schedule/${date.format('YYYY-MM-DD')}` - }, - parser: function ({ content, date }) { - let programs = [] - const items = parseItems(content) - for (let item of items) { - const $item = cheerio.load(item) - const prev = programs[programs.length - 1] - let start = parseStart($item, date) - if (prev) { - if (start.isBefore(prev.start)) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } - prev.stop = start - } - const duration = parseDuration($item) - let stop = start.add(duration, 'm') - - programs.push({ - title: parseTitle($item), - sub_title: parseSubTitle($item), - description: parseDescription($item), - image: parseImage($item), - rating: parseRating($item), - start, - stop - }) - } - - return programs - } -} - -function parseDescription($item) { - return $item('.panel-body > div > div > div > p:nth-child(2)').text().trim() -} - -function parseImage($item) { - return $item('.video-thumbnail img').attr('src') -} - -function parseTitle($item) { - return $item('.show-title').text().trim() -} - -function parseSubTitle($item) { - return $item('.panel-title > div > div > div > div:nth-child(2) > p') - .text() - .trim() - .replace(/\s\s+/g, ' ') -} - -function parseRating($item) { - const [, rating] = $item('.tv-rating') - .text() - .match(/([^(]+)/) || [null, null] - - return rating - ? { - system: 'MPA', - value: rating.trim() - } - : null -} - -function parseStart($item, date) { - let time = $item('.panel-title h2').clone().children().remove().end().text().trim() - time = time.includes(':') ? time : time + ':00' - const meridiem = $item('.panel-title h2 > .meridiem').text().trim() - - return dayjs.tz( - `${date.format('YYYY-MM-DD')} ${time} ${meridiem}`, - 'YYYY-MM-DD H:mm A', - 'America/New_York' - ) -} - -function parseDuration($item) { - const [, duration] = $item('.tv-rating') - .text() - .trim() - .match(/\((\d+)/) || [null, null] - - return parseInt(duration) -} - -function parseItems(content) { - if (!content) return [] - const $ = cheerio.load(content) - - return $('#accordion > div').toArray() -} +const dayjs = require('dayjs') +const cheerio = require('cheerio') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'ionplustv.com', + days: 2, + url({ date }) { + return `https://ionplustv.com/schedule/${date.format('YYYY-MM-DD')}` + }, + parser: function ({ content, date }) { + let programs = [] + const items = parseItems(content) + for (let item of items) { + const $item = cheerio.load(item) + const prev = programs[programs.length - 1] + let start = parseStart($item, date) + if (prev) { + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } + prev.stop = start + } + const duration = parseDuration($item) + let stop = start.add(duration, 'm') + + programs.push({ + title: parseTitle($item), + sub_title: parseSubTitle($item), + description: parseDescription($item), + image: parseImage($item), + rating: parseRating($item), + start, + stop + }) + } + + return programs + } +} + +function parseDescription($item) { + return $item('.panel-body > div > div > div > p:nth-child(2)').text().trim() +} + +function parseImage($item) { + return $item('.video-thumbnail img').attr('src') +} + +function parseTitle($item) { + return $item('.show-title').text().trim() +} + +function parseSubTitle($item) { + return $item('.panel-title > div > div > div > div:nth-child(2) > p') + .text() + .trim() + .replace(/\s\s+/g, ' ') +} + +function parseRating($item) { + const [, rating] = $item('.tv-rating') + .text() + .match(/([^(]+)/) || [null, null] + + return rating + ? { + system: 'MPA', + value: rating.trim() + } + : null +} + +function parseStart($item, date) { + let time = $item('.panel-title h2').clone().children().remove().end().text().trim() + time = time.includes(':') ? time : time + ':00' + const meridiem = $item('.panel-title h2 > .meridiem').text().trim() + + return dayjs.tz( + `${date.format('YYYY-MM-DD')} ${time} ${meridiem}`, + 'YYYY-MM-DD H:mm A', + 'America/New_York' + ) +} + +function parseDuration($item) { + const [, duration] = $item('.tv-rating') + .text() + .trim() + .match(/\((\d+)/) || [null, null] + + return parseInt(duration) +} + +function parseItems(content) { + if (!content) return [] + const $ = cheerio.load(content) + + return $('#accordion > div').toArray() +} diff --git a/sites/ionplustv.com/ionplustv.com.test.js b/sites/ionplustv.com/ionplustv.com.test.js index 1a76e1f4a..21ce2fd34 100644 --- a/sites/ionplustv.com/ionplustv.com.test.js +++ b/sites/ionplustv.com/ionplustv.com.test.js @@ -1,49 +1,49 @@ -const { parser, url } = require('./ionplustv.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-11-08', 'YYYY-MM-DD').startOf('d') - -it('can generate valid url', () => { - expect(url({ date })).toBe('https://ionplustv.com/schedule/2022-11-08') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - - let results = parser({ content, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2022-11-08T10:00:00.000Z', - stop: '2022-11-08T11:00:00.000Z', - title: 'All For Nothing?', - sub_title: '226 : Randy & Sarita Vs. Jean-marcel & Melodie', - image: - 'https://ionplustv.com/static/programs/shows/all-for-nothing/show-banner-all-for-nothing-5ab162f2d8ee6-897aca6d7d9a7d4e2026ca3b592d8b2a047238fa.png', - rating: { - system: 'MPA', - value: 'TV-PG+L' - }, - description: - "Randy and Sarita want to take their relationship to the next level and move-in together. Blending their families will require space for seven so they must sell Randy's dated bungalow for top dollar. Paul and Penny have differing opinions on the best plan for this house, but they do agree that all the wallpaper boarders must go! Having struggled to get the demolition started, Randy and Sarita turn up the reno pace in the second week which includes gambling on a poker night fundraiser. In preparation for retirement, Jean-Marcel and Melodie are ready to downsize. Having been out of the real estate market for ages, they have no idea how to ˜wow' the buyers of today. Armed with Paul and Penny's job list to bring their house into the now, they make major progress on day one. Flu, leaks, and a free shower insert that won't fit into their bathroom slow down their pace giving the competition a chance to overtake their early lead." - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - content: fs.readFileSync(path.resolve(__dirname, '__data__/no-content.html')), - date - }) - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./ionplustv.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-11-08', 'YYYY-MM-DD').startOf('d') + +it('can generate valid url', () => { + expect(url({ date })).toBe('https://ionplustv.com/schedule/2022-11-08') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + + let results = parser({ content, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2022-11-08T10:00:00.000Z', + stop: '2022-11-08T11:00:00.000Z', + title: 'All For Nothing?', + sub_title: '226 : Randy & Sarita Vs. Jean-marcel & Melodie', + image: + 'https://ionplustv.com/static/programs/shows/all-for-nothing/show-banner-all-for-nothing-5ab162f2d8ee6-897aca6d7d9a7d4e2026ca3b592d8b2a047238fa.png', + rating: { + system: 'MPA', + value: 'TV-PG+L' + }, + description: + "Randy and Sarita want to take their relationship to the next level and move-in together. Blending their families will require space for seven so they must sell Randy's dated bungalow for top dollar. Paul and Penny have differing opinions on the best plan for this house, but they do agree that all the wallpaper boarders must go! Having struggled to get the demolition started, Randy and Sarita turn up the reno pace in the second week which includes gambling on a poker night fundraiser. In preparation for retirement, Jean-Marcel and Melodie are ready to downsize. Having been out of the real estate market for ages, they have no idea how to ˜wow' the buyers of today. Armed with Paul and Penny's job list to bring their house into the now, they make major progress on day one. Flu, leaks, and a free shower insert that won't fit into their bathroom slow down their pace giving the competition a chance to overtake their early lead." + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + content: fs.readFileSync(path.resolve(__dirname, '__data__/no-content.html')), + date + }) + expect(results).toMatchObject([]) +}) diff --git a/sites/ipko.tv/ipko.tv.config.js b/sites/ipko.tv/ipko.tv.config.js index 55803f1e1..3b5fd4116 100644 --- a/sites/ipko.tv/ipko.tv.config.js +++ b/sites/ipko.tv/ipko.tv.config.js @@ -1,80 +1,80 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'ipko.tv', - timezone: 'Europe/Belgrade', - days: 5, - url() { - return 'https://stargate.ipko.tv/api/titan.tv.WebEpg/GetWebEpgData' - }, - request: { - method: 'POST', - headers: { - Host: 'stargate.ipko.tv', - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0', - Accept: 'application/json, text/plain, */*', - 'Accept-Language': 'nl,en-US;q=0.7,en;q=0.3', - 'Content-Type': 'application/json', - 'X-AppLayout': '1', - 'x-language': 'sq', - Origin: 'https://ipko.tv', - 'Sec-Fetch-Dest': 'empty', - 'Sec-Fetch-Mode': 'cors', - 'Sec-Fetch-Site': 'cross-site', - 'Sec-GPC': '1', - Connection: 'keep-alive' - }, - data({ channel, date }) { - const todayEpoch = date.startOf('day').unix() - const nextDayEpoch = date.add(1, 'day').startOf('day').unix() - return JSON.stringify({ - ch_ext_id: channel.site_id, - from: todayEpoch, - to: nextDayEpoch - }) - } - }, - parser: function ({ content }) { - const programs = [] - const data = JSON.parse(content) - data.shows.forEach(show => { - const start = dayjs.unix(show.show_start).utc() - const stop = dayjs.unix(show.show_end).utc() - const programData = { - title: show.title, - description: show.summary || 'No description available', - start: start.toISOString(), - stop: stop.toISOString(), - thumbnail: show.thumbnail - } - programs.push(programData) - }) - return programs - }, - async channels() { - const response = await axios.post( - 'https://stargate.ipko.tv/api/titan.tv.WebEpg/ZapList', - JSON.stringify({ includeRadioStations: true }), - { - headers: this.request.headers - } - ) - - const data = response.data.data - return data.map(item => ({ - lang: 'sq', - name: String(item.channel.title), - site_id: String(item.channel.id) - //logo: String(item.channel.logo) - })) - } -} +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'ipko.tv', + timezone: 'Europe/Belgrade', + days: 5, + url() { + return 'https://stargate.ipko.tv/api/titan.tv.WebEpg/GetWebEpgData' + }, + request: { + method: 'POST', + headers: { + Host: 'stargate.ipko.tv', + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0', + Accept: 'application/json, text/plain, */*', + 'Accept-Language': 'nl,en-US;q=0.7,en;q=0.3', + 'Content-Type': 'application/json', + 'X-AppLayout': '1', + 'x-language': 'sq', + Origin: 'https://ipko.tv', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + 'Sec-GPC': '1', + Connection: 'keep-alive' + }, + data({ channel, date }) { + const todayEpoch = date.startOf('day').unix() + const nextDayEpoch = date.add(1, 'day').startOf('day').unix() + return JSON.stringify({ + ch_ext_id: channel.site_id, + from: todayEpoch, + to: nextDayEpoch + }) + } + }, + parser: function ({ content }) { + const programs = [] + const data = JSON.parse(content) + data.shows.forEach(show => { + const start = dayjs.unix(show.show_start).utc() + const stop = dayjs.unix(show.show_end).utc() + const programData = { + title: show.title, + description: show.summary || 'No description available', + start: start.toISOString(), + stop: stop.toISOString(), + thumbnail: show.thumbnail + } + programs.push(programData) + }) + return programs + }, + async channels() { + const response = await axios.post( + 'https://stargate.ipko.tv/api/titan.tv.WebEpg/ZapList', + JSON.stringify({ includeRadioStations: true }), + { + headers: this.request.headers + } + ) + + const data = response.data.data + return data.map(item => ({ + lang: 'sq', + name: String(item.channel.title), + site_id: String(item.channel.id) + //logo: String(item.channel.logo) + })) + } +} diff --git a/sites/jiotv.com/jiotv.com.config.js b/sites/jiotv.com/jiotv.com.config.js index ed30001cd..3a7535050 100644 --- a/sites/jiotv.com/jiotv.com.config.js +++ b/sites/jiotv.com/jiotv.com.config.js @@ -1,87 +1,87 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'jiotv.com', - days: 2, - url({ date, channel }) { - const offset = date.diff(dayjs.utc().startOf('d'), 'd') - - return `https://jiotvapi.cdn.jio.com/apis/v1.3/getepg/get?channel_id=${channel.site_id}&offset=${offset}` - }, - parser({ content }) { - let programs = [] - let items = parseItems(content) - items.forEach(item => { - programs.push({ - title: item.showname, - description: item.episode_desc || item.description, - directors: parseList(item.director), - actors: parseList(item.starCast), - categories: item.showGenre, - episode: parseEpisode(item), - keywords: item.keywords, - icon: parseIcon(item), - image: parseImage(item), - start: dayjs(item.startEpoch), - stop: dayjs(item.endEpoch) - }) - }) - - return programs - }, - async channels() { - const data = await axios - .get( - 'https://jiotvapi.cdn.jio.com/apis/v3.0/getMobileChannelList/get/?langId=6&devicetype=phone&os=android&usertype=JIO&version=343' - ) - .then(r => r.data) - .catch(console.error) - - return data.result.map(c => { - return { - lang: 'en', - site_id: c.channel_id, - name: c.channel_name - } - }) - } -} - -function parseEpisode(item) { - return item.episode_num > 0 ? item.episode_num : null -} - -function parseList(string) { - return string.split(', ').filter(Boolean) -} - -function parseIcon(item) { - return item.episodeThumbnail - ? `https://jiotvimages.cdn.jio.com/dare_images/shows/700/-/${item.episodeThumbnail}` - : null -} - -function parseImage(item) { - return item.episodePoster - ? `https://jiotvimages.cdn.jio.com/dare_images/shows/700/-/${item.episodePoster}` - : null -} - -function parseItems(content) { - try { - const data = JSON.parse(content) - if (!data || !Array.isArray(data.epg)) return [] - - return data.epg - } catch { - return [] - } -} +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'jiotv.com', + days: 2, + url({ date, channel }) { + const offset = date.diff(dayjs.utc().startOf('d'), 'd') + + return `https://jiotvapi.cdn.jio.com/apis/v1.3/getepg/get?channel_id=${channel.site_id}&offset=${offset}` + }, + parser({ content }) { + let programs = [] + let items = parseItems(content) + items.forEach(item => { + programs.push({ + title: item.showname, + description: item.episode_desc || item.description, + directors: parseList(item.director), + actors: parseList(item.starCast), + categories: item.showGenre, + episode: parseEpisode(item), + keywords: item.keywords, + icon: parseIcon(item), + image: parseImage(item), + start: dayjs(item.startEpoch), + stop: dayjs(item.endEpoch) + }) + }) + + return programs + }, + async channels() { + const data = await axios + .get( + 'https://jiotvapi.cdn.jio.com/apis/v3.0/getMobileChannelList/get/?langId=6&devicetype=phone&os=android&usertype=JIO&version=343' + ) + .then(r => r.data) + .catch(console.error) + + return data.result.map(c => { + return { + lang: 'en', + site_id: c.channel_id, + name: c.channel_name + } + }) + } +} + +function parseEpisode(item) { + return item.episode_num > 0 ? item.episode_num : null +} + +function parseList(string) { + return string.split(', ').filter(Boolean) +} + +function parseIcon(item) { + return item.episodeThumbnail + ? `https://jiotvimages.cdn.jio.com/dare_images/shows/700/-/${item.episodeThumbnail}` + : null +} + +function parseImage(item) { + return item.episodePoster + ? `https://jiotvimages.cdn.jio.com/dare_images/shows/700/-/${item.episodePoster}` + : null +} + +function parseItems(content) { + try { + const data = JSON.parse(content) + if (!data || !Array.isArray(data.epg)) return [] + + return data.epg + } catch { + return [] + } +} diff --git a/sites/jiotv.com/jiotv.com.test.js b/sites/jiotv.com/jiotv.com.test.js index 5738c9307..c25318179 100644 --- a/sites/jiotv.com/jiotv.com.test.js +++ b/sites/jiotv.com/jiotv.com.test.js @@ -1,86 +1,86 @@ -const { parser, url } = require('./jiotv.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.useFakeTimers().setSystemTime(new Date('2025-01-15')) - -const date = dayjs.utc('2025-01-17', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '146', - xmltv_id: 'HistoryTV18HD.in' -} - -it('can generate valid url', () => { - expect(url({ date, channel })).toBe( - 'https://jiotvapi.cdn.jio.com/apis/v1.3/getepg/get?channel_id=146&offset=2' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') - let results = parser({ content }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(46) - expect(results[1]).toMatchObject({ - start: '2025-01-16T19:13:00.000Z', - stop: '2025-01-16T19:57:00.000Z', - title: "History's Greatest Heists With Pierce Brosnan", - description: - "Daring criminals burrow beneath London's streets to infiltrate a Lloyds Bank vault. However, their heist takes an unexpected turn when a radio hobbyist stumbles upon their communications.", - categories: ['History'], - directors: ['Brendan G Murphy'], - actors: ['Brent Picha', 'William Sibley', 'Bobby Williams'], - episode: 3, - keywords: [ - 'Heist', - 'Criminal mastermind', - 'Consequences', - 'Historical account', - 'Historical significance', - 'Criminal offence' - ], - icon: 'https://jiotvimages.cdn.jio.com/dare_images/shows/700/-/2025-01-17/250117146001_s.jpg', - image: 'https://jiotvimages.cdn.jio.com/dare_images/shows/700/-/2025-01-17/250117146001.jpg' - }) - expect(results[45]).toMatchObject({ - start: '2025-01-17T18:29:00.000Z', - stop: '2025-01-17T18:29:59.000Z', - title: "History's Greatest Escapes with Morgan Freeman", - description: - 'In French Guiana, when petty thief Rene Belbenoit faces harsh imprisonment, determined to break free, he endures years of gruelling conditions and attempts numerous daring escapes.', - categories: ['Crime'], - directors: ['Mitch Marcus'], - actors: [], - episode: 5, - keywords: [ - 'Imprisoned', - 'Prison', - 'Prison Break', - 'Prison film', - 'Set in a prison', - 'Escape', - 'Survival', - 'Survival Instinct' - ], - icon: 'https://jiotvimages.cdn.jio.com/dare_images/shows/700/-/2025-01-17/250117146045_s.jpg', - image: 'https://jiotvimages.cdn.jio.com/dare_images/shows/700/-/2025-01-17/250117146045.jpg' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - content: '' - }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./jiotv.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.useFakeTimers().setSystemTime(new Date('2025-01-15')) + +const date = dayjs.utc('2025-01-17', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '146', + xmltv_id: 'HistoryTV18HD.in' +} + +it('can generate valid url', () => { + expect(url({ date, channel })).toBe( + 'https://jiotvapi.cdn.jio.com/apis/v1.3/getepg/get?channel_id=146&offset=2' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') + let results = parser({ content }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(46) + expect(results[1]).toMatchObject({ + start: '2025-01-16T19:13:00.000Z', + stop: '2025-01-16T19:57:00.000Z', + title: "History's Greatest Heists With Pierce Brosnan", + description: + "Daring criminals burrow beneath London's streets to infiltrate a Lloyds Bank vault. However, their heist takes an unexpected turn when a radio hobbyist stumbles upon their communications.", + categories: ['History'], + directors: ['Brendan G Murphy'], + actors: ['Brent Picha', 'William Sibley', 'Bobby Williams'], + episode: 3, + keywords: [ + 'Heist', + 'Criminal mastermind', + 'Consequences', + 'Historical account', + 'Historical significance', + 'Criminal offence' + ], + icon: 'https://jiotvimages.cdn.jio.com/dare_images/shows/700/-/2025-01-17/250117146001_s.jpg', + image: 'https://jiotvimages.cdn.jio.com/dare_images/shows/700/-/2025-01-17/250117146001.jpg' + }) + expect(results[45]).toMatchObject({ + start: '2025-01-17T18:29:00.000Z', + stop: '2025-01-17T18:29:59.000Z', + title: "History's Greatest Escapes with Morgan Freeman", + description: + 'In French Guiana, when petty thief Rene Belbenoit faces harsh imprisonment, determined to break free, he endures years of gruelling conditions and attempts numerous daring escapes.', + categories: ['Crime'], + directors: ['Mitch Marcus'], + actors: [], + episode: 5, + keywords: [ + 'Imprisoned', + 'Prison', + 'Prison Break', + 'Prison film', + 'Set in a prison', + 'Escape', + 'Survival', + 'Survival Instinct' + ], + icon: 'https://jiotvimages.cdn.jio.com/dare_images/shows/700/-/2025-01-17/250117146045_s.jpg', + image: 'https://jiotvimages.cdn.jio.com/dare_images/shows/700/-/2025-01-17/250117146045.jpg' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + content: '' + }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/kan.org.il/kan.org.il.config.js b/sites/kan.org.il/kan.org.il.config.js index 01aaec094..999e155ce 100644 --- a/sites/kan.org.il/kan.org.il.config.js +++ b/sites/kan.org.il/kan.org.il.config.js @@ -1,52 +1,52 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'kan.org.il', - days: 2, - url: function ({ channel, date }) { - return `https://www.kan.org.il/tv-guide/tv_guidePrograms.ashx?stationID=${ - channel.site_id - }&day=${date.format('DD/MM/YYYY')}` - }, - parser: function ({ content }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - programs.push({ - title: item.title, - description: item.live_desc, - image: item.picture_code, - start: parseStart(item), - stop: parseStop(item) - }) - }) - - return programs - } -} - -function parseStart(item) { - if (!item.start_time) return null - - return dayjs.tz(item.start_time, 'YYYY-MM-DDTHH:mm:ss', 'Asia/Jerusalem') -} - -function parseStop(item) { - if (!item.end_time) return null - - return dayjs.tz(item.end_time, 'YYYY-MM-DDTHH:mm:ss', 'Asia/Jerusalem') -} - -function parseItems(content) { - const data = JSON.parse(content) - if (!Array.isArray(data)) return [] - - return data -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'kan.org.il', + days: 2, + url: function ({ channel, date }) { + return `https://www.kan.org.il/tv-guide/tv_guidePrograms.ashx?stationID=${ + channel.site_id + }&day=${date.format('DD/MM/YYYY')}` + }, + parser: function ({ content }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + programs.push({ + title: item.title, + description: item.live_desc, + image: item.picture_code, + start: parseStart(item), + stop: parseStop(item) + }) + }) + + return programs + } +} + +function parseStart(item) { + if (!item.start_time) return null + + return dayjs.tz(item.start_time, 'YYYY-MM-DDTHH:mm:ss', 'Asia/Jerusalem') +} + +function parseStop(item) { + if (!item.end_time) return null + + return dayjs.tz(item.end_time, 'YYYY-MM-DDTHH:mm:ss', 'Asia/Jerusalem') +} + +function parseItems(content) { + const data = JSON.parse(content) + if (!Array.isArray(data)) return [] + + return data +} diff --git a/sites/knr.gl/knr.gl.config.js b/sites/knr.gl/knr.gl.config.js index 1091e2eb0..e8b7dbed5 100644 --- a/sites/knr.gl/knr.gl.config.js +++ b/sites/knr.gl/knr.gl.config.js @@ -1,79 +1,79 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'knr.gl', - days: 2, - url: 'https://knr.gl/kl/tv/aallakaatitassat?ajax_form=1', - request: { - method: 'POST', - headers: { - 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8' - }, - data({ date }) { - const params = new URLSearchParams() - params.append('list_date', date.format('YYYY-MM-DD')) - params.append('form_id', 'knr_radio_tv_program_overview_form') - params.append('_triggering_element_name', 'list_date') - - return params - } - }, - parser({ content, date }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - const prev = programs[programs.length - 1] - const start = parseStart(item, date) - const stop = start.add(1, 'h') - if (prev) prev.stop = start - programs.push({ - title: item.title, - description: item.description, - start, - stop - }) - }) - - return programs - } -} - -function parseStart(item, date) { - const time = `${date.format('YYYY-MM-DD')} ${item.time}` - - return dayjs.tz(time, 'YYYY-MM-DD HH:mm', 'America/Nuuk') -} - -function parseItems(content) { - const data = JSON.parse(content) - if (data.length !== 1 || !data[0].data) return [] - const $ = cheerio.load(data[0].data) - - const items = [] - $('.overview-program__list__item').each((i, el) => { - const title = $(el).find('.overview-program__text').text().trim() - const description = $(el) - .find('.overview-program__sublist__item') - .first() - .text() - .trim() - .replace(/(\r\n|\n|\r)/gm, ' ') - const time = $(el).find('.overview-program__time').text().trim() - - items.push({ - title, - description, - time - }) - }) - - return items -} +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'knr.gl', + days: 2, + url: 'https://knr.gl/kl/tv/aallakaatitassat?ajax_form=1', + request: { + method: 'POST', + headers: { + 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8' + }, + data({ date }) { + const params = new URLSearchParams() + params.append('list_date', date.format('YYYY-MM-DD')) + params.append('form_id', 'knr_radio_tv_program_overview_form') + params.append('_triggering_element_name', 'list_date') + + return params + } + }, + parser({ content, date }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + const prev = programs[programs.length - 1] + const start = parseStart(item, date) + const stop = start.add(1, 'h') + if (prev) prev.stop = start + programs.push({ + title: item.title, + description: item.description, + start, + stop + }) + }) + + return programs + } +} + +function parseStart(item, date) { + const time = `${date.format('YYYY-MM-DD')} ${item.time}` + + return dayjs.tz(time, 'YYYY-MM-DD HH:mm', 'America/Nuuk') +} + +function parseItems(content) { + const data = JSON.parse(content) + if (data.length !== 1 || !data[0].data) return [] + const $ = cheerio.load(data[0].data) + + const items = [] + $('.overview-program__list__item').each((i, el) => { + const title = $(el).find('.overview-program__text').text().trim() + const description = $(el) + .find('.overview-program__sublist__item') + .first() + .text() + .trim() + .replace(/(\r\n|\n|\r)/gm, ' ') + const time = $(el).find('.overview-program__time').text().trim() + + items.push({ + title, + description, + time + }) + }) + + return items +} diff --git a/sites/knr.gl/knr.gl.test.js b/sites/knr.gl/knr.gl.test.js index aa1c8cd12..99f0a90ac 100644 --- a/sites/knr.gl/knr.gl.test.js +++ b/sites/knr.gl/knr.gl.test.js @@ -1,67 +1,67 @@ -const { parser, url, request } = require('./knr.gl.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2021-11-22', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '#', - xmltv_id: 'KNR.gl' -} - -it('can generate valid url', () => { - expect(url).toBe('https://knr.gl/kl/tv/aallakaatitassat?ajax_form=1') -}) - -it('can generate valid request method', () => { - expect(request.method).toBe('POST') -}) - -it('can generate valid request headers', () => { - expect(request.headers).toMatchObject({}) -}) - -it('can generate valid request data', () => { - const params = request.data({ date }) - - expect(params.get('list_date')).toBe('2021-11-22') - expect(params.get('form_id')).toBe('knr_radio_tv_program_overview_form') - expect(params.get('_triggering_element_name')).toBe('list_date') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const results = parser({ content, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2021-11-22T11:00:00.000Z', - stop: '2021-11-22T11:30:00.000Z', - title: 'Issittormiuaqqat' - }) - - expect(results[4]).toMatchObject({ - start: '2021-11-22T13:00:00.000Z', - stop: '2021-11-22T13:30:00.000Z', - title: 'KNR2: Tusagassiortunik katersortitsineq - Erik Jensen', - description: - 'Naalakkersuisoq Erik Jensen tusagassiortunut 21.november nal. 10.00-11.00 katersortitsissaaq attassisinnaanermut siuariartornermullu pilersaarut pillugu (holdbarheds- og vækstplan).' - }) -}) - -it('can handle empty guide', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - const result = parser({ - date, - channel, - content - }) - expect(result).toMatchObject([]) -}) +const { parser, url, request } = require('./knr.gl.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2021-11-22', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '#', + xmltv_id: 'KNR.gl' +} + +it('can generate valid url', () => { + expect(url).toBe('https://knr.gl/kl/tv/aallakaatitassat?ajax_form=1') +}) + +it('can generate valid request method', () => { + expect(request.method).toBe('POST') +}) + +it('can generate valid request headers', () => { + expect(request.headers).toMatchObject({}) +}) + +it('can generate valid request data', () => { + const params = request.data({ date }) + + expect(params.get('list_date')).toBe('2021-11-22') + expect(params.get('form_id')).toBe('knr_radio_tv_program_overview_form') + expect(params.get('_triggering_element_name')).toBe('list_date') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const results = parser({ content, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2021-11-22T11:00:00.000Z', + stop: '2021-11-22T11:30:00.000Z', + title: 'Issittormiuaqqat' + }) + + expect(results[4]).toMatchObject({ + start: '2021-11-22T13:00:00.000Z', + stop: '2021-11-22T13:30:00.000Z', + title: 'KNR2: Tusagassiortunik katersortitsineq - Erik Jensen', + description: + 'Naalakkersuisoq Erik Jensen tusagassiortunut 21.november nal. 10.00-11.00 katersortitsissaaq attassisinnaanermut siuariartornermullu pilersaarut pillugu (holdbarheds- og vækstplan).' + }) +}) + +it('can handle empty guide', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + const result = parser({ + date, + channel, + content + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/kvf.fo/kvf.fo.config.js b/sites/kvf.fo/kvf.fo.config.js index f53814122..f95a27b98 100644 --- a/sites/kvf.fo/kvf.fo.config.js +++ b/sites/kvf.fo/kvf.fo.config.js @@ -1,71 +1,71 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'kvf.fo', - days: 2, - url({ date }) { - return `https://kvf.fo/nskra/sv?date=${date.format('YYYY-MM-DD')}` - }, - parser({ content, date }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - const prev = programs[programs.length - 1] - const $item = cheerio.load(item) - let start = parseStart($item, date) - if (!start) return - if (prev && start.isBefore(prev.stop)) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } - let stop = parseStop($item, date) - if (stop.isBefore(start)) { - stop = stop.add(1, 'd') - date = date.add(1, 'd') - } - programs.push({ - title: parseTitle($item), - start, - stop - }) - }) - - return programs - } -} - -function parseStart($item, date) { - const string = $item('.s-normal > .s-time1').text().trim() - let [time] = string.match(/^(\d{2}:\d{2})/g) || [null] - if (!time) return null - time = `${date.format('YYYY-MM-DD')} ${time}` - - return dayjs.tz(time, 'YYYY-MM-DD HH:mm', 'Atlantic/Faroe') -} - -function parseStop($item, date) { - const string = $item('.s-normal > .s-time1').text().trim() - let [time] = string.match(/(\d{2}:\d{2})$/g) || [null] - if (!time) return null - time = `${date.format('YYYY-MM-DD')} ${time}` - - return dayjs.tz(time, 'YYYY-MM-DD HH:mm', 'Atlantic/Faroe') -} - -function parseTitle($item) { - return $item('.s-normal > .s-heiti').text() -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('.view > .view-content > div.views-row').toArray() -} +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'kvf.fo', + days: 2, + url({ date }) { + return `https://kvf.fo/nskra/sv?date=${date.format('YYYY-MM-DD')}` + }, + parser({ content, date }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + const prev = programs[programs.length - 1] + const $item = cheerio.load(item) + let start = parseStart($item, date) + if (!start) return + if (prev && start.isBefore(prev.stop)) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } + let stop = parseStop($item, date) + if (stop.isBefore(start)) { + stop = stop.add(1, 'd') + date = date.add(1, 'd') + } + programs.push({ + title: parseTitle($item), + start, + stop + }) + }) + + return programs + } +} + +function parseStart($item, date) { + const string = $item('.s-normal > .s-time1').text().trim() + let [time] = string.match(/^(\d{2}:\d{2})/g) || [null] + if (!time) return null + time = `${date.format('YYYY-MM-DD')} ${time}` + + return dayjs.tz(time, 'YYYY-MM-DD HH:mm', 'Atlantic/Faroe') +} + +function parseStop($item, date) { + const string = $item('.s-normal > .s-time1').text().trim() + let [time] = string.match(/(\d{2}:\d{2})$/g) || [null] + if (!time) return null + time = `${date.format('YYYY-MM-DD')} ${time}` + + return dayjs.tz(time, 'YYYY-MM-DD HH:mm', 'Atlantic/Faroe') +} + +function parseTitle($item) { + return $item('.s-normal > .s-heiti').text() +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('.view > .view-content > div.views-row').toArray() +} diff --git a/sites/kvf.fo/kvf.fo.test.js b/sites/kvf.fo/kvf.fo.test.js index 70b839f1e..db4a087e8 100644 --- a/sites/kvf.fo/kvf.fo.test.js +++ b/sites/kvf.fo/kvf.fo.test.js @@ -1,42 +1,42 @@ -const { parser, url } = require('./kvf.fo.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2021-11-21', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '#', - xmltv_id: 'KVFSjonvarp.fo' -} - -it('can generate valid url', () => { - expect(url({ date })).toBe('https://kvf.fo/nskra/sv?date=2021-11-21') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, './__data__/example.html')) - const result = parser({ content, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result[2]).toMatchObject({ - start: '2021-11-21T18:05:00.000Z', - stop: '2021-11-21T18:30:00.000Z', - title: 'Letibygd 13' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: ' ' - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./kvf.fo.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2021-11-21', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '#', + xmltv_id: 'KVFSjonvarp.fo' +} + +it('can generate valid url', () => { + expect(url({ date })).toBe('https://kvf.fo/nskra/sv?date=2021-11-21') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, './__data__/example.html')) + const result = parser({ content, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result[2]).toMatchObject({ + start: '2021-11-21T18:05:00.000Z', + stop: '2021-11-21T18:30:00.000Z', + title: 'Letibygd 13' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: ' ' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/m.tv.sms.cz/m.tv.sms.cz.config.js b/sites/m.tv.sms.cz/m.tv.sms.cz.config.js index 92861212a..f1e673269 100644 --- a/sites/m.tv.sms.cz/m.tv.sms.cz.config.js +++ b/sites/m.tv.sms.cz/m.tv.sms.cz.config.js @@ -1,84 +1,84 @@ -const cheerio = require('cheerio') -const iconv = require('iconv-lite') -const { DateTime } = require('luxon') - -module.exports = { - site: 'm.tv.sms.cz', - days: 2, - url: function ({ date, channel }) { - return `https://m.tv.sms.cz/index.php?stanice=${channel.site_id}&cas=0&den=${date.format( - 'YYYY-MM-DD' - )}` - }, - parser: function ({ buffer, date }) { - const programs = [] - const items = parseItems(buffer) - items.forEach(item => { - const prev = programs[programs.length - 1] - const $item = cheerio.load(item) - let start = parseStart($item, date) - if (prev) { - if (start < prev.start) { - start = start.plus({ days: 1 }) - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.plus({ hours: 1 }) - programs.push({ - title: parseTitle($item), - description: parseDescription($item), - start, - stop - }) - }) - - return programs - }, - async channels() { - const axios = require('axios') - const data = await axios - .get('https://m.tv.sms.cz/?zmen_stanice=true') - .then(r => r.data) - .catch(console.log) - - let channels = [] - const $ = cheerio.load(data) - $('.stanice').each((i, el) => { - const name = $(el).attr('title') - const site_id = $(el).find('input').attr('value').replace(/\|/g, '') - - if (!name) return - - channels.push({ - lang: 'cs', - site_id, - name - }) - }) - - return channels - } -} - -function parseStart($item, date) { - const timeString = $item('div > span').text().trim() - const dateString = `${date.format('MM/DD/YYYY')} ${timeString}` - - return DateTime.fromFormat(dateString, 'MM/dd/yyyy HH.mm', { zone: 'Europe/Prague' }).toUTC() -} - -function parseDescription($item) { - return $item('a.nazev > div.detail').text().trim() -} - -function parseTitle($item) { - return $item('a.nazev > div:nth-child(1)').text().trim() -} - -function parseItems(buffer) { - const string = iconv.decode(buffer, 'win1250') - const $ = cheerio.load(string) - - return $('#obsah > div > div.porady > div.porad').toArray() -} +const cheerio = require('cheerio') +const iconv = require('iconv-lite') +const { DateTime } = require('luxon') + +module.exports = { + site: 'm.tv.sms.cz', + days: 2, + url: function ({ date, channel }) { + return `https://m.tv.sms.cz/index.php?stanice=${channel.site_id}&cas=0&den=${date.format( + 'YYYY-MM-DD' + )}` + }, + parser: function ({ buffer, date }) { + const programs = [] + const items = parseItems(buffer) + items.forEach(item => { + const prev = programs[programs.length - 1] + const $item = cheerio.load(item) + let start = parseStart($item, date) + if (prev) { + if (start < prev.start) { + start = start.plus({ days: 1 }) + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.plus({ hours: 1 }) + programs.push({ + title: parseTitle($item), + description: parseDescription($item), + start, + stop + }) + }) + + return programs + }, + async channels() { + const axios = require('axios') + const data = await axios + .get('https://m.tv.sms.cz/?zmen_stanice=true') + .then(r => r.data) + .catch(console.log) + + let channels = [] + const $ = cheerio.load(data) + $('.stanice').each((i, el) => { + const name = $(el).attr('title') + const site_id = $(el).find('input').attr('value').replace(/\|/g, '') + + if (!name) return + + channels.push({ + lang: 'cs', + site_id, + name + }) + }) + + return channels + } +} + +function parseStart($item, date) { + const timeString = $item('div > span').text().trim() + const dateString = `${date.format('MM/DD/YYYY')} ${timeString}` + + return DateTime.fromFormat(dateString, 'MM/dd/yyyy HH.mm', { zone: 'Europe/Prague' }).toUTC() +} + +function parseDescription($item) { + return $item('a.nazev > div.detail').text().trim() +} + +function parseTitle($item) { + return $item('a.nazev > div:nth-child(1)').text().trim() +} + +function parseItems(buffer) { + const string = iconv.decode(buffer, 'win1250') + const $ = cheerio.load(string) + + return $('#obsah > div > div.porady > div.porad').toArray() +} diff --git a/sites/m.tv.sms.cz/m.tv.sms.cz.test.js b/sites/m.tv.sms.cz/m.tv.sms.cz.test.js index ac5a32168..9c5806fe7 100644 --- a/sites/m.tv.sms.cz/m.tv.sms.cz.test.js +++ b/sites/m.tv.sms.cz/m.tv.sms.cz.test.js @@ -1,57 +1,57 @@ -const { parser, url } = require('./m.tv.sms.cz.config.js') -const iconv = require('iconv-lite') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-06-11', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'Cero', - xmltv_id: '0.es' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://m.tv.sms.cz/index.php?stanice=Cero&cas=0&den=2023-06-11' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - const buffer = iconv.encode(content, 'win1250') - const results = parser({ buffer, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2023-06-11T03:21:00.000Z', - stop: '2023-06-11T04:08:00.000Z', - title: 'Conspiraciones al descubierto: La bomba atómica alemana y el hundimiento del Titanic', - description: 'Documentales' - }) - - expect(results[25]).toMatchObject({ - start: '2023-06-12T02:23:00.000Z', - stop: '2023-06-12T03:23:00.000Z', - title: 'Rapa I (6)', - description: 'Series' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - buffer: iconv.encode( - Buffer.from( - '' - ), - 'win1250' - ) - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./m.tv.sms.cz.config.js') +const iconv = require('iconv-lite') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-06-11', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'Cero', + xmltv_id: '0.es' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://m.tv.sms.cz/index.php?stanice=Cero&cas=0&den=2023-06-11' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + const buffer = iconv.encode(content, 'win1250') + const results = parser({ buffer, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2023-06-11T03:21:00.000Z', + stop: '2023-06-11T04:08:00.000Z', + title: 'Conspiraciones al descubierto: La bomba atómica alemana y el hundimiento del Titanic', + description: 'Documentales' + }) + + expect(results[25]).toMatchObject({ + start: '2023-06-12T02:23:00.000Z', + stop: '2023-06-12T03:23:00.000Z', + title: 'Rapa I (6)', + description: 'Series' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + buffer: iconv.encode( + Buffer.from( + '' + ), + 'win1250' + ) + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/m.tving.com/m.tving.com.config.js b/sites/m.tving.com/m.tving.com.config.js index 77ef4ea24..8b3a750d9 100644 --- a/sites/m.tving.com/m.tving.com.config.js +++ b/sites/m.tving.com/m.tving.com.config.js @@ -1,94 +1,94 @@ -const axios = require('axios') -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'm.tving.com', - days: 2, - url: function ({ channel, date }) { - return `https://api.tving.com/v2/media/schedules/${channel.site_id}/${date.format( - 'YYYYMMDD' - )}?callback=cb&pageNo=1&pageSize=500&screenCode=CSSD0200&networkCode=CSND0900&osCode=CSOD0900&teleCode=CSCD0900&apiKey=4263d7d76161f4a19a9efe9ca7903ec4` - }, - parser: function ({ content }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - programs.push({ - title: item.program.name.ko, - description: item.program.synopsis.ko, - categories: parseCategories(item), - date: item.program.product_year, - directors: item.program.director, - actors: item.program.actor, - start: parseStart(item), - stop: parseStop(item), - image: parseImage(item) - }) - }) - - return programs - }, - async channels() { - let items = await axios - .get('https://m.tving.com/guide/schedule.tving') - .then(r => r.data) - .then(html => { - let $ = cheerio.load(html) - - return $('ul.cb > li').toArray() - }) - .catch(console.log) - - return items.map(item => { - let $item = cheerio.load(item) - let [, site_id] = $item('a') - .attr('href') - .match(/\?id=(.*)/) || [null, null] - let name = $item('img').attr('alt') - - return { - lang: 'ko', - site_id, - name - } - }) - } -} - -function parseImage(item) { - return item.program.image.length ? `https://image.tving.com${item.program.image[0].url}` : null -} - -function parseStart(item) { - return dayjs.tz(item.broadcast_start_time.toString(), 'YYYYMMDDHHmmss', 'Asia/Seoul') -} - -function parseStop(item) { - return dayjs.tz(item.broadcast_end_time.toString(), 'YYYYMMDDHHmmss', 'Asia/Seoul') -} - -function parseCategories(item) { - const categories = [] - - if (item.category1_name) categories.push(item.category1_name.ko) - if (item.category2_name) categories.push(item.category2_name.ko) - - return categories.filter(Boolean) -} - -function parseItems(content) { - let data = (content.match(/cb\((.*)\)/) || [null, null])[1] - if (!data) return [] - let json = JSON.parse(data) - if (!json || !json.body || !Array.isArray(json.body.result)) return [] - - return json.body.result -} +const axios = require('axios') +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'm.tving.com', + days: 2, + url: function ({ channel, date }) { + return `https://api.tving.com/v2/media/schedules/${channel.site_id}/${date.format( + 'YYYYMMDD' + )}?callback=cb&pageNo=1&pageSize=500&screenCode=CSSD0200&networkCode=CSND0900&osCode=CSOD0900&teleCode=CSCD0900&apiKey=4263d7d76161f4a19a9efe9ca7903ec4` + }, + parser: function ({ content }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + programs.push({ + title: item.program.name.ko, + description: item.program.synopsis.ko, + categories: parseCategories(item), + date: item.program.product_year, + directors: item.program.director, + actors: item.program.actor, + start: parseStart(item), + stop: parseStop(item), + image: parseImage(item) + }) + }) + + return programs + }, + async channels() { + let items = await axios + .get('https://m.tving.com/guide/schedule.tving') + .then(r => r.data) + .then(html => { + let $ = cheerio.load(html) + + return $('ul.cb > li').toArray() + }) + .catch(console.log) + + return items.map(item => { + let $item = cheerio.load(item) + let [, site_id] = $item('a') + .attr('href') + .match(/\?id=(.*)/) || [null, null] + let name = $item('img').attr('alt') + + return { + lang: 'ko', + site_id, + name + } + }) + } +} + +function parseImage(item) { + return item.program.image.length ? `https://image.tving.com${item.program.image[0].url}` : null +} + +function parseStart(item) { + return dayjs.tz(item.broadcast_start_time.toString(), 'YYYYMMDDHHmmss', 'Asia/Seoul') +} + +function parseStop(item) { + return dayjs.tz(item.broadcast_end_time.toString(), 'YYYYMMDDHHmmss', 'Asia/Seoul') +} + +function parseCategories(item) { + const categories = [] + + if (item.category1_name) categories.push(item.category1_name.ko) + if (item.category2_name) categories.push(item.category2_name.ko) + + return categories.filter(Boolean) +} + +function parseItems(content) { + let data = (content.match(/cb\((.*)\)/) || [null, null])[1] + if (!data) return [] + let json = JSON.parse(data) + if (!json || !json.body || !Array.isArray(json.body.result)) return [] + + return json.body.result +} diff --git a/sites/m.tving.com/m.tving.com.test.js b/sites/m.tving.com/m.tving.com.test.js index afb7a07fe..3f5e764ba 100644 --- a/sites/m.tving.com/m.tving.com.test.js +++ b/sites/m.tving.com/m.tving.com.test.js @@ -1,47 +1,47 @@ -const { parser, url } = require('./m.tving.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-01-23', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'C00551', - xmltv_id: 'tvN.kr' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://api.tving.com/v2/media/schedules/C00551/20230123?callback=cb&pageNo=1&pageSize=500&screenCode=CSSD0200&networkCode=CSND0900&osCode=CSOD0900&teleCode=CSCD0900&apiKey=4263d7d76161f4a19a9efe9ca7903ec4' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.txt'), 'utf8') - const results = parser({ content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - title: '외계+인 1부', - description: '외계+인 1부', - image: 'https://image.tving.com/upload/cms/caip/CAIP0200/P001661154.jpg', - date: 2022, - categories: [], - directors: ['최동훈'], - actors: ['김우빈', '류준열'], - start: '2023-01-22T13:40:00.000Z', - stop: '2023-01-22T15:00:00.000Z' - }) -}) - -it('can handle empty guide', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.txt'), 'utf8') - - expect(parser({ content })).toMatchObject([]) -}) +const { parser, url } = require('./m.tving.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-01-23', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'C00551', + xmltv_id: 'tvN.kr' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://api.tving.com/v2/media/schedules/C00551/20230123?callback=cb&pageNo=1&pageSize=500&screenCode=CSSD0200&networkCode=CSND0900&osCode=CSOD0900&teleCode=CSCD0900&apiKey=4263d7d76161f4a19a9efe9ca7903ec4' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.txt'), 'utf8') + const results = parser({ content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + title: '외계+인 1부', + description: '외계+인 1부', + image: 'https://image.tving.com/upload/cms/caip/CAIP0200/P001661154.jpg', + date: 2022, + categories: [], + directors: ['최동훈'], + actors: ['김우빈', '류준열'], + start: '2023-01-22T13:40:00.000Z', + stop: '2023-01-22T15:00:00.000Z' + }) +}) + +it('can handle empty guide', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.txt'), 'utf8') + + expect(parser({ content })).toMatchObject([]) +}) diff --git a/sites/magticom.ge/magticom.ge.config.js b/sites/magticom.ge/magticom.ge.config.js index 2a936ba1d..3611e0789 100644 --- a/sites/magticom.ge/magticom.ge.config.js +++ b/sites/magticom.ge/magticom.ge.config.js @@ -1,86 +1,86 @@ -const cheerio = require('cheerio') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'magticom.ge', - days: 2, - url: 'https://www.magticom.ge/request/channel-program.php', - request: { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', - Referer: 'https://www.magticom.ge/en/tv/tv-services/tv-guide' - }, - data({ channel, date }) { - const params = new URLSearchParams() - params.append('channelId', channel.site_id) - params.append('start', date.unix()) - params.append('end', date.add(1, 'd').unix()) - - return params - } - }, - parser({ content }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - programs.push({ - title: item.title, - description: item.info, - start: parseStart(item), - stop: parseStop(item) - }) - }) - - return programs - }, - async channels() { - const html = await axios - .get('https://www.magticom.ge/en/tv/tv-services/tv-guide') - .then(r => r.data) - .catch(console.log) - - const $ = cheerio.load(html) - const channels = $( - '#article > article > div > div > div.tv-guide > div.tv-guide-channels > div.tv-guide-channel' - ).toArray() - - return channels.map(item => { - const $item = cheerio.load(item) - const channelId = $item('*').data('id') - return { - lang: 'ka', - site_id: channelId, - name: $item('.tv-guide-channel-title > div > div').text() - } - }) - } -} - -function parseStart(item) { - return dayjs.tz(item.startTimestamp, 'YYYY-MM-DDTHH:mm:ss', 'Asia/Tbilisi') -} - -function parseStop(item) { - return dayjs.tz(item.endTimestamp, 'YYYY-MM-DDTHH:mm:ss', 'Asia/Tbilisi') -} - -function parseItems(content) { - let data - try { - data = JSON.parse(content) - } catch { - return [] - } - if (!data || !Array.isArray(data)) return [] - - return data -} +const cheerio = require('cheerio') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'magticom.ge', + days: 2, + url: 'https://www.magticom.ge/request/channel-program.php', + request: { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + Referer: 'https://www.magticom.ge/en/tv/tv-services/tv-guide' + }, + data({ channel, date }) { + const params = new URLSearchParams() + params.append('channelId', channel.site_id) + params.append('start', date.unix()) + params.append('end', date.add(1, 'd').unix()) + + return params + } + }, + parser({ content }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + programs.push({ + title: item.title, + description: item.info, + start: parseStart(item), + stop: parseStop(item) + }) + }) + + return programs + }, + async channels() { + const html = await axios + .get('https://www.magticom.ge/en/tv/tv-services/tv-guide') + .then(r => r.data) + .catch(console.log) + + const $ = cheerio.load(html) + const channels = $( + '#article > article > div > div > div.tv-guide > div.tv-guide-channels > div.tv-guide-channel' + ).toArray() + + return channels.map(item => { + const $item = cheerio.load(item) + const channelId = $item('*').data('id') + return { + lang: 'ka', + site_id: channelId, + name: $item('.tv-guide-channel-title > div > div').text() + } + }) + } +} + +function parseStart(item) { + return dayjs.tz(item.startTimestamp, 'YYYY-MM-DDTHH:mm:ss', 'Asia/Tbilisi') +} + +function parseStop(item) { + return dayjs.tz(item.endTimestamp, 'YYYY-MM-DDTHH:mm:ss', 'Asia/Tbilisi') +} + +function parseItems(content) { + let data + try { + data = JSON.parse(content) + } catch { + return [] + } + if (!data || !Array.isArray(data)) return [] + + return data +} diff --git a/sites/mako.co.il/mako.co.il.config.js b/sites/mako.co.il/mako.co.il.config.js index 0a6a0b199..08ff9d719 100644 --- a/sites/mako.co.il/mako.co.il.config.js +++ b/sites/mako.co.il/mako.co.il.config.js @@ -1,45 +1,45 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'mako.co.il', - days: 2, - url: 'https://www.mako.co.il/AjaxPage?jspName=EPGResponse.jsp', - parser: function ({ content, date }) { - let programs = [] - const items = parseItems(content, date) - items.forEach(item => { - const start = parseStart(item) - const stop = start.add(item.DurationMs, 'ms') - programs.push({ - title: item.ProgramName, - description: item.EventDescription, - image: item.Picture, - start, - stop - }) - }) - - return programs - } -} - -function parseStart(item) { - if (!item.StartTimeUTC) return null - - return dayjs(item.StartTimeUTC) -} - -function parseItems(content, date) { - const data = JSON.parse(content) - if (!data || !Array.isArray(data.programs)) return [] - const d = date.format('DD/MM/YYYY') - - return data.programs.filter(item => item.Date.startsWith(d)) -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'mako.co.il', + days: 2, + url: 'https://www.mako.co.il/AjaxPage?jspName=EPGResponse.jsp', + parser: function ({ content, date }) { + let programs = [] + const items = parseItems(content, date) + items.forEach(item => { + const start = parseStart(item) + const stop = start.add(item.DurationMs, 'ms') + programs.push({ + title: item.ProgramName, + description: item.EventDescription, + image: item.Picture, + start, + stop + }) + }) + + return programs + } +} + +function parseStart(item) { + if (!item.StartTimeUTC) return null + + return dayjs(item.StartTimeUTC) +} + +function parseItems(content, date) { + const data = JSON.parse(content) + if (!data || !Array.isArray(data.programs)) return [] + const d = date.format('DD/MM/YYYY') + + return data.programs.filter(item => item.Date.startsWith(d)) +} diff --git a/sites/makrodigitaltelevision.com/makrodigitaltelevision.com.config.js b/sites/makrodigitaltelevision.com/makrodigitaltelevision.com.config.js index 8a35a1050..7a3588234 100644 --- a/sites/makrodigitaltelevision.com/makrodigitaltelevision.com.config.js +++ b/sites/makrodigitaltelevision.com/makrodigitaltelevision.com.config.js @@ -1,26 +1,26 @@ -const parser = require('epg-parser') - -module.exports = { - site: 'makrodigitaltelevision.com', - days: 3, - url: 'https://makrodigitaltelevision.com/epg.xml', - parser({ content, date, channel }) { - let programs = [] - const items = parseItems(content, channel, date) - items.forEach(item => { - programs.push({ - title: item.title?.[0]?.value, - start: item.start, - stop: item.stop - }) - }) - - return programs - } -} - -function parseItems(content, channel, date) { - const { programs } = parser.parse(content) - - return programs.filter(p => p.channel === channel.site_id && date.isSame(p.start, 'day')) -} +const parser = require('epg-parser') + +module.exports = { + site: 'makrodigitaltelevision.com', + days: 3, + url: 'https://makrodigitaltelevision.com/epg.xml', + parser({ content, date, channel }) { + let programs = [] + const items = parseItems(content, channel, date) + items.forEach(item => { + programs.push({ + title: item.title?.[0]?.value, + start: item.start, + stop: item.stop + }) + }) + + return programs + } +} + +function parseItems(content, channel, date) { + const { programs } = parser.parse(content) + + return programs.filter(p => p.channel === channel.site_id && date.isSame(p.start, 'day')) +} diff --git a/sites/makrodigitaltelevision.com/makrodigitaltelevision.com.test.js b/sites/makrodigitaltelevision.com/makrodigitaltelevision.com.test.js index 56de5851f..4c559b086 100644 --- a/sites/makrodigitaltelevision.com/makrodigitaltelevision.com.test.js +++ b/sites/makrodigitaltelevision.com/makrodigitaltelevision.com.test.js @@ -1,39 +1,39 @@ -const { parser, url } = require('./makrodigitaltelevision.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-02-16', 'YYYY-MM-DD').startOf('d') -const channel = { site_id: '17' } - -it('can generate valid url', () => { - expect(url).toBe('https://makrodigitaltelevision.com/epg.xml') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.xml')) - - const results = parser({ content, channel, date }) - - expect(results.length).toBe(8) - expect(results[0]).toMatchObject({ - title: 'Programación Infantil', - start: '2025-02-16T13:00:00.000Z', - stop: '2025-02-16T17:00:00.000Z' - }) - expect(results[7]).toMatchObject({ - title: 'Comunicación Cristiana', - start: '2025-02-16T23:30:00.000Z', - stop: '2025-02-17T00:00:00.000Z' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ content: '' }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./makrodigitaltelevision.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-02-16', 'YYYY-MM-DD').startOf('d') +const channel = { site_id: '17' } + +it('can generate valid url', () => { + expect(url).toBe('https://makrodigitaltelevision.com/epg.xml') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.xml')) + + const results = parser({ content, channel, date }) + + expect(results.length).toBe(8) + expect(results[0]).toMatchObject({ + title: 'Programación Infantil', + start: '2025-02-16T13:00:00.000Z', + stop: '2025-02-16T17:00:00.000Z' + }) + expect(results[7]).toMatchObject({ + title: 'Comunicación Cristiana', + start: '2025-02-16T23:30:00.000Z', + stop: '2025-02-17T00:00:00.000Z' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ content: '' }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/maxtvgo.mk/maxtvgo.mk.config.js b/sites/maxtvgo.mk/maxtvgo.mk.config.js index 9573d1842..b553f728e 100644 --- a/sites/maxtvgo.mk/maxtvgo.mk.config.js +++ b/sites/maxtvgo.mk/maxtvgo.mk.config.js @@ -1,72 +1,72 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) - -module.exports = { - site: 'maxtvgo.mk', - days: 2, - url: function ({ channel, date }) { - return `https://prd-static-mkt.spectar.tv/rev-1636968171/client_api.php/epg/list/instance_id/1/language/mk/channel_id/${ - channel.site_id - }/start/${date.format('YYYYMMDDHHmmss')}/stop/${date - .add(1, 'd') - .format('YYYYMMDDHHmmss')}/include_current/true/format/json` - }, - parser: function ({ content }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - programs.push({ - title: item.title, - category: item.category, - description: parseDescription(item), - image: parseImage(item), - start: parseStart(item), - stop: parseStop(item) - }) - }) - - return programs - }, - async channels() { - const channels = await axios - .get( - 'https://prd-static-mkt.spectar.tv/rev-1636968171/client_api.php/channel/all/application_id/deep_blue/device_configuration/2/instance_id/1/language/mk/http_proto/https/format/json' - ) - .then(r => r.data) - .catch(console.log) - - return channels.map(item => { - return { - lang: 'mk', - site_id: item.id, - name: item.name - } - }) - } -} - -function parseStart(item) { - return dayjs(item['@attributes'].start, 'YYYYMMDDHHmmss ZZ') -} - -function parseStop(item) { - return dayjs(item['@attributes'].stop, 'YYYYMMDDHHmmss ZZ') -} - -function parseDescription(item) { - return typeof item.desc === 'string' ? item.desc : null -} - -function parseImage(item) { - return item.icon['@attributes'].src -} - -function parseItems(content) { - const data = JSON.parse(content) - if (!data || !Array.isArray(data.programme)) return [] - - return data.programme -} +const axios = require('axios') +const dayjs = require('dayjs') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) + +module.exports = { + site: 'maxtvgo.mk', + days: 2, + url: function ({ channel, date }) { + return `https://prd-static-mkt.spectar.tv/rev-1636968171/client_api.php/epg/list/instance_id/1/language/mk/channel_id/${ + channel.site_id + }/start/${date.format('YYYYMMDDHHmmss')}/stop/${date + .add(1, 'd') + .format('YYYYMMDDHHmmss')}/include_current/true/format/json` + }, + parser: function ({ content }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + programs.push({ + title: item.title, + category: item.category, + description: parseDescription(item), + image: parseImage(item), + start: parseStart(item), + stop: parseStop(item) + }) + }) + + return programs + }, + async channels() { + const channels = await axios + .get( + 'https://prd-static-mkt.spectar.tv/rev-1636968171/client_api.php/channel/all/application_id/deep_blue/device_configuration/2/instance_id/1/language/mk/http_proto/https/format/json' + ) + .then(r => r.data) + .catch(console.log) + + return channels.map(item => { + return { + lang: 'mk', + site_id: item.id, + name: item.name + } + }) + } +} + +function parseStart(item) { + return dayjs(item['@attributes'].start, 'YYYYMMDDHHmmss ZZ') +} + +function parseStop(item) { + return dayjs(item['@attributes'].stop, 'YYYYMMDDHHmmss ZZ') +} + +function parseDescription(item) { + return typeof item.desc === 'string' ? item.desc : null +} + +function parseImage(item) { + return item.icon['@attributes'].src +} + +function parseItems(content) { + const data = JSON.parse(content) + if (!data || !Array.isArray(data.programme)) return [] + + return data.programme +} diff --git a/sites/mediagenie.co.kr/mediagenie.co.kr.config.js b/sites/mediagenie.co.kr/mediagenie.co.kr.config.js index 35fcb0078..32f85cbef 100644 --- a/sites/mediagenie.co.kr/mediagenie.co.kr.config.js +++ b/sites/mediagenie.co.kr/mediagenie.co.kr.config.js @@ -1,77 +1,77 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'mediagenie.co.kr', - days: 1, - url({ channel, date }) { - return `https://mediagenie.co.kr/${channel.site_id}/?qd=${date.format('YYYYMMDD')}` - }, - request: { - headers: { - cookie: 'CUPID=d5ed6b77012aef2b4d4365ffd3a1a3a4' - } - }, - parser({ content, date }) { - const programs = [] - const items = parseItems(content) - items.forEach(item => { - const $item = cheerio.load(item) - const prev = programs[programs.length - 1] - let start = parseStart($item, date) - if (!start) return - if (prev) { - if (start.isBefore(prev.start)) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.add(30, 'm') - programs.push({ - title: parseTitle($item), - rating: parseRating($item), - start, - stop - }) - }) - - return programs - } -} - -function parseTitle($item) { - return $item('.col2').clone().children().remove().end().text().trim() -} - -function parseRating($item) { - const rating = $item('.col6').text().trim() - - return rating - ? { - system: 'KMRB', - value: rating - } - : null -} - -function parseStart($item, date) { - const time = $item('.col1').text().trim() - - if (!/^\d{2}:\d{2}$/.test(time)) return null - - return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'Asia/Seoul') -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('.tbl > tbody > tr').toArray() -} +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'mediagenie.co.kr', + days: 1, + url({ channel, date }) { + return `https://mediagenie.co.kr/${channel.site_id}/?qd=${date.format('YYYYMMDD')}` + }, + request: { + headers: { + cookie: 'CUPID=d5ed6b77012aef2b4d4365ffd3a1a3a4' + } + }, + parser({ content, date }) { + const programs = [] + const items = parseItems(content) + items.forEach(item => { + const $item = cheerio.load(item) + const prev = programs[programs.length - 1] + let start = parseStart($item, date) + if (!start) return + if (prev) { + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.add(30, 'm') + programs.push({ + title: parseTitle($item), + rating: parseRating($item), + start, + stop + }) + }) + + return programs + } +} + +function parseTitle($item) { + return $item('.col2').clone().children().remove().end().text().trim() +} + +function parseRating($item) { + const rating = $item('.col6').text().trim() + + return rating + ? { + system: 'KMRB', + value: rating + } + : null +} + +function parseStart($item, date) { + const time = $item('.col1').text().trim() + + if (!/^\d{2}:\d{2}$/.test(time)) return null + + return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'Asia/Seoul') +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('.tbl > tbody > tr').toArray() +} diff --git a/sites/mediagenie.co.kr/mediagenie.co.kr.test.js b/sites/mediagenie.co.kr/mediagenie.co.kr.test.js index 05315da53..44f4f9e84 100644 --- a/sites/mediagenie.co.kr/mediagenie.co.kr.test.js +++ b/sites/mediagenie.co.kr/mediagenie.co.kr.test.js @@ -1,63 +1,63 @@ -const { parser, url, request } = require('./mediagenie.co.kr.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-01-25', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'ENA_DRAMA', - xmltv_id: 'ENADRAMA.kr' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe('https://mediagenie.co.kr/ENA_DRAMA/?qd=20230125') -}) - -it('can generate valid request headers', () => { - expect(request.headers).toMatchObject({ - cookie: 'CUPID=d5ed6b77012aef2b4d4365ffd3a1a3a4' - }) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') - let results = parser({ content, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2023-01-24T15:20:00.000Z', - stop: '2023-01-24T16:34:00.000Z', - title: '대행사', - rating: { - system: 'KMRB', - value: '15' - } - }) - - expect(results[16]).toMatchObject({ - start: '2023-01-25T14:27:00.000Z', - stop: '2023-01-25T14:57:00.000Z', - title: '법쩐', - rating: { - system: 'KMRB', - value: '15' - } - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - date, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') - }) - - expect(results).toMatchObject([]) -}) +const { parser, url, request } = require('./mediagenie.co.kr.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-01-25', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'ENA_DRAMA', + xmltv_id: 'ENADRAMA.kr' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe('https://mediagenie.co.kr/ENA_DRAMA/?qd=20230125') +}) + +it('can generate valid request headers', () => { + expect(request.headers).toMatchObject({ + cookie: 'CUPID=d5ed6b77012aef2b4d4365ffd3a1a3a4' + }) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') + let results = parser({ content, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2023-01-24T15:20:00.000Z', + stop: '2023-01-24T16:34:00.000Z', + title: '대행사', + rating: { + system: 'KMRB', + value: '15' + } + }) + + expect(results[16]).toMatchObject({ + start: '2023-01-25T14:27:00.000Z', + stop: '2023-01-25T14:57:00.000Z', + title: '법쩐', + rating: { + system: 'KMRB', + value: '15' + } + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + date, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') + }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/mediaklikk.hu/mediaklikk.hu.config.js b/sites/mediaklikk.hu/mediaklikk.hu.config.js index 673fb4b3b..1a6bf2b69 100644 --- a/sites/mediaklikk.hu/mediaklikk.hu.config.js +++ b/sites/mediaklikk.hu/mediaklikk.hu.config.js @@ -1,85 +1,85 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'mediaklikk.hu', - days: 2, - url: 'https://mediaklikk.hu/wp-content/plugins/hms-global-widgets/widgets/programGuide/programGuideInterface.php', - request: { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - }, - data: function ({ date, channel }) { - const params = new URLSearchParams() - params.append('ChannelIds', `${channel.site_id},`) - params.append('Date', date.format('YYYY-MM-DD')) - - return params - } - }, - parser: function ({ content }) { - const programs = [] - const items = parseItems(content) - items.forEach(item => { - const $item = cheerio.load(item) - const start = parseStart($item) - let stop = parseStop($item) - if (!stop) stop = start.add(30, 'm') - programs.push({ - title: parseTitle($item), - description: parseDescription($item), - image: parseImage($item), - start, - stop - }) - }) - - return programs - } -} - -function parseStart($item) { - const timeString = $item('*').data('from') - - return dayjs(timeString, 'YYYY-MM-DD HH:mm:ssZZ') -} - -function parseStop($item) { - const timeString = $item('*').data('till') - if (!timeString || /^\+/.test(timeString)) return null - - try { - return dayjs(timeString, 'YYYY-MM-DD HH:mm:ssZZ') - } catch { - return null - } -} - -function parseTitle($item) { - return $item('.program_info > h1').text().trim() -} - -function parseDescription($item) { - return $item('.program_about > .program_description > p').text().trim() -} - -function parseImage($item) { - const backgroundImage = $item('.program_about > .program_photo').css('background-image') - if (!backgroundImage) return null - const [, imageUrl] = backgroundImage.match(/url\('(.*)'\)/) || [null, null] - if (!imageUrl) return null - - return `https:${imageUrl}` -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('li.program_body').toArray() -} +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'mediaklikk.hu', + days: 2, + url: 'https://mediaklikk.hu/wp-content/plugins/hms-global-widgets/widgets/programGuide/programGuideInterface.php', + request: { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + data: function ({ date, channel }) { + const params = new URLSearchParams() + params.append('ChannelIds', `${channel.site_id},`) + params.append('Date', date.format('YYYY-MM-DD')) + + return params + } + }, + parser: function ({ content }) { + const programs = [] + const items = parseItems(content) + items.forEach(item => { + const $item = cheerio.load(item) + const start = parseStart($item) + let stop = parseStop($item) + if (!stop) stop = start.add(30, 'm') + programs.push({ + title: parseTitle($item), + description: parseDescription($item), + image: parseImage($item), + start, + stop + }) + }) + + return programs + } +} + +function parseStart($item) { + const timeString = $item('*').data('from') + + return dayjs(timeString, 'YYYY-MM-DD HH:mm:ssZZ') +} + +function parseStop($item) { + const timeString = $item('*').data('till') + if (!timeString || /^\+/.test(timeString)) return null + + try { + return dayjs(timeString, 'YYYY-MM-DD HH:mm:ssZZ') + } catch { + return null + } +} + +function parseTitle($item) { + return $item('.program_info > h1').text().trim() +} + +function parseDescription($item) { + return $item('.program_about > .program_description > p').text().trim() +} + +function parseImage($item) { + const backgroundImage = $item('.program_about > .program_photo').css('background-image') + if (!backgroundImage) return null + const [, imageUrl] = backgroundImage.match(/url\('(.*)'\)/) || [null, null] + if (!imageUrl) return null + + return `https:${imageUrl}` +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('li.program_body').toArray() +} diff --git a/sites/mediaklikk.hu/mediaklikk.hu.test.js b/sites/mediaklikk.hu/mediaklikk.hu.test.js index 7ab04fbe9..f4dd06a1e 100644 --- a/sites/mediaklikk.hu/mediaklikk.hu.test.js +++ b/sites/mediaklikk.hu/mediaklikk.hu.test.js @@ -1,72 +1,72 @@ -const { parser, url, request } = require('./mediaklikk.hu.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-03-10', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '3', - xmltv_id: 'DuneTV.hu' -} - -it('can generate valid url', () => { - expect(url).toBe( - 'https://mediaklikk.hu/wp-content/plugins/hms-global-widgets/widgets/programGuide/programGuideInterface.php' - ) -}) - -it('can generate valid request method', () => { - expect(request.method).toBe('POST') -}) - -it('can generate valid request headers', () => { - expect(request.headers).toMatchObject({ - 'Content-Type': 'application/x-www-form-urlencoded' - }) -}) - -it('can generate valid request data', () => { - const result = request.data({ date, channel }) - expect(result.get('ChannelIds')).toBe('3,') - expect(result.get('Date')).toBe('2022-03-10') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - const results = parser({ content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2022-10-27T22:00:46.000Z', - stop: '2022-10-27T22:54:00.000Z', - title: 'A hegyi doktor - I. évad', - description: - 'Maxl iskolatársának, Vroninak az anyja egy autóbalesetben meghal. A 20 éves testvér, Vinzenz magához szeretné venni a lányt, ám a gyámüggyel problémái akadnak, ezért megpróbálja elszöktetni.(Eredeti hang digitálisan.)', - image: - 'https://mediaklikk.hu/wp-content/uploads/sites/4/2019/10/A-hegyi-doktor-I-évad-e1571318391226-150x150.jpg' - }) - - expect(results[56]).toMatchObject({ - start: '2022-10-28T20:35:05.000Z', - stop: '2022-10-28T21:05:05.000Z', - title: 'Szemtől szemben (1967)', - description: - 'Brad Fletcher bostoni történelemtanár, aki a délnyugati határvidéken kúrálja tüdőbetegségét, egy véletlen folytán összeakad Beauregard Bennett körözött útonállóval, akit végül maga segít a menekülésben. A tanárt lenyűgözi a törvényen kívüliek világa és felismeri, hogy értelmi felsőbbrendűségével bámulatosan tudja irányítani az embereket. Bennett csakhamar azt veszi észre, hogy a peremre szorult saját bandájában. Eközben a Pinkerton ügynökség beépített embere is csapdába igyekszik csalni mindnyájukat.' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: '' - }) - expect(result).toMatchObject([]) -}) +const { parser, url, request } = require('./mediaklikk.hu.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-03-10', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '3', + xmltv_id: 'DuneTV.hu' +} + +it('can generate valid url', () => { + expect(url).toBe( + 'https://mediaklikk.hu/wp-content/plugins/hms-global-widgets/widgets/programGuide/programGuideInterface.php' + ) +}) + +it('can generate valid request method', () => { + expect(request.method).toBe('POST') +}) + +it('can generate valid request headers', () => { + expect(request.headers).toMatchObject({ + 'Content-Type': 'application/x-www-form-urlencoded' + }) +}) + +it('can generate valid request data', () => { + const result = request.data({ date, channel }) + expect(result.get('ChannelIds')).toBe('3,') + expect(result.get('Date')).toBe('2022-03-10') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + const results = parser({ content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2022-10-27T22:00:46.000Z', + stop: '2022-10-27T22:54:00.000Z', + title: 'A hegyi doktor - I. évad', + description: + 'Maxl iskolatársának, Vroninak az anyja egy autóbalesetben meghal. A 20 éves testvér, Vinzenz magához szeretné venni a lányt, ám a gyámüggyel problémái akadnak, ezért megpróbálja elszöktetni.(Eredeti hang digitálisan.)', + image: + 'https://mediaklikk.hu/wp-content/uploads/sites/4/2019/10/A-hegyi-doktor-I-évad-e1571318391226-150x150.jpg' + }) + + expect(results[56]).toMatchObject({ + start: '2022-10-28T20:35:05.000Z', + stop: '2022-10-28T21:05:05.000Z', + title: 'Szemtől szemben (1967)', + description: + 'Brad Fletcher bostoni történelemtanár, aki a délnyugati határvidéken kúrálja tüdőbetegségét, egy véletlen folytán összeakad Beauregard Bennett körözött útonállóval, akit végül maga segít a menekülésben. A tanárt lenyűgözi a törvényen kívüliek világa és felismeri, hogy értelmi felsőbbrendűségével bámulatosan tudja irányítani az embereket. Bennett csakhamar azt veszi észre, hogy a peremre szorult saját bandájában. Eközben a Pinkerton ügynökség beépített embere is csapdába igyekszik csalni mindnyájukat.' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: '' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/mediasetinfinity.mediaset.it/mediasetinfinity.mediaset.it.config.js b/sites/mediasetinfinity.mediaset.it/mediasetinfinity.mediaset.it.config.js index 3a1689539..bcbda09a6 100644 --- a/sites/mediasetinfinity.mediaset.it/mediasetinfinity.mediaset.it.config.js +++ b/sites/mediasetinfinity.mediaset.it/mediasetinfinity.mediaset.it.config.js @@ -1,97 +1,97 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -const timezone = require('dayjs/plugin/timezone') - -dayjs.extend(utc) -dayjs.extend(customParseFormat) -dayjs.extend(timezone) - -module.exports = { - site: 'mediasetinfinity.mediaset.it', - days: 2, - url: function ({ date, channel }) { - // Get the epoch timestamp - const todayEpoch = date.startOf('day').utc().valueOf() - // Get the epoch timestamp for the next day - const nextDayEpoch = date.add(1, 'day').startOf('day').utc().valueOf() - return `https://api-ott-prod-fe.mediaset.net/PROD/play/feed/allListingFeedEpg/v2.0?byListingTime=${todayEpoch}~${nextDayEpoch}&byCallSign=${channel.site_id}` - }, - parser: function ({ content }) { - const programs = [] - const data = JSON.parse(content) - - if ( - !data.response || - !data.response.entries || - !data.response.entries[0] || - !data.response.entries[0].listings - ) { - // If the structure is not as expected, return an empty array - return programs - } - - const listings = data.response.entries[0].listings - - listings.forEach(listing => { - const title = listing.mediasetlisting$epgTitle - const subTitle = listing.program.title - const season = parseSeason(listing) - const episode = parseEpisode(listing) - - if (listing.program.title && listing.startTime && listing.endTime) { - programs.push({ - title: title || subTitle, - sub_title: title && title != subTitle ? subTitle : null, - description: listing.program.description || null, - category: listing.program.mediasetprogram$skyGenre || null, - season: episode && !season ? '0' : season, - episode: episode, - start: parseTime(listing.startTime), - stop: parseTime(listing.endTime), - image: getMaxResolutionThumbnails(listing) - }) - } - }) - - return programs - } -} - -function parseTime(timestamp) { - return dayjs(timestamp).utc().format('YYYY-MM-DD HH:mm') -} - -function parseSeason(item) { - if (!item.mediasetlisting$shortDescription) return null - const season = item.mediasetlisting$shortDescription.match(/S(\d+)\s/) - return season ? season[1] : null -} - -function parseEpisode(item) { - if (!item.mediasetlisting$shortDescription) return null - const episode = item.mediasetlisting$shortDescription.match(/Ep(\d+)\s/) - return episode ? episode[1] : null -} - -function getMaxResolutionThumbnails(item) { - const thumbnails = item.program.thumbnails || null - const maxResolutionThumbnails = {} - - for (const key in thumbnails) { - const type = key.split('-')[0] // Estrarre il tipo di thumbnail - const { width, height, url, title } = thumbnails[key] - - if ( - !maxResolutionThumbnails[type] || - width * height > maxResolutionThumbnails[type].width * maxResolutionThumbnails[type].height - ) { - maxResolutionThumbnails[type] = { width, height, url, title } - } - } - if (maxResolutionThumbnails.image_keyframe_poster) - return maxResolutionThumbnails.image_keyframe_poster.url - else if (maxResolutionThumbnails.image_header_poster) - return maxResolutionThumbnails.image_header_poster.url - else return null -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const timezone = require('dayjs/plugin/timezone') + +dayjs.extend(utc) +dayjs.extend(customParseFormat) +dayjs.extend(timezone) + +module.exports = { + site: 'mediasetinfinity.mediaset.it', + days: 2, + url: function ({ date, channel }) { + // Get the epoch timestamp + const todayEpoch = date.startOf('day').utc().valueOf() + // Get the epoch timestamp for the next day + const nextDayEpoch = date.add(1, 'day').startOf('day').utc().valueOf() + return `https://api-ott-prod-fe.mediaset.net/PROD/play/feed/allListingFeedEpg/v2.0?byListingTime=${todayEpoch}~${nextDayEpoch}&byCallSign=${channel.site_id}` + }, + parser: function ({ content }) { + const programs = [] + const data = JSON.parse(content) + + if ( + !data.response || + !data.response.entries || + !data.response.entries[0] || + !data.response.entries[0].listings + ) { + // If the structure is not as expected, return an empty array + return programs + } + + const listings = data.response.entries[0].listings + + listings.forEach(listing => { + const title = listing.mediasetlisting$epgTitle + const subTitle = listing.program.title + const season = parseSeason(listing) + const episode = parseEpisode(listing) + + if (listing.program.title && listing.startTime && listing.endTime) { + programs.push({ + title: title || subTitle, + sub_title: title && title != subTitle ? subTitle : null, + description: listing.program.description || null, + category: listing.program.mediasetprogram$skyGenre || null, + season: episode && !season ? '0' : season, + episode: episode, + start: parseTime(listing.startTime), + stop: parseTime(listing.endTime), + image: getMaxResolutionThumbnails(listing) + }) + } + }) + + return programs + } +} + +function parseTime(timestamp) { + return dayjs(timestamp).utc().format('YYYY-MM-DD HH:mm') +} + +function parseSeason(item) { + if (!item.mediasetlisting$shortDescription) return null + const season = item.mediasetlisting$shortDescription.match(/S(\d+)\s/) + return season ? season[1] : null +} + +function parseEpisode(item) { + if (!item.mediasetlisting$shortDescription) return null + const episode = item.mediasetlisting$shortDescription.match(/Ep(\d+)\s/) + return episode ? episode[1] : null +} + +function getMaxResolutionThumbnails(item) { + const thumbnails = item.program.thumbnails || null + const maxResolutionThumbnails = {} + + for (const key in thumbnails) { + const type = key.split('-')[0] // Estrarre il tipo di thumbnail + const { width, height, url, title } = thumbnails[key] + + if ( + !maxResolutionThumbnails[type] || + width * height > maxResolutionThumbnails[type].width * maxResolutionThumbnails[type].height + ) { + maxResolutionThumbnails[type] = { width, height, url, title } + } + } + if (maxResolutionThumbnails.image_keyframe_poster) + return maxResolutionThumbnails.image_keyframe_poster.url + else if (maxResolutionThumbnails.image_header_poster) + return maxResolutionThumbnails.image_header_poster.url + else return null +} diff --git a/sites/mediasetinfinity.mediaset.it/mediasetinfinity.mediaset.it.test.js b/sites/mediasetinfinity.mediaset.it/mediasetinfinity.mediaset.it.test.js index c1c8ce111..409631676 100644 --- a/sites/mediasetinfinity.mediaset.it/mediasetinfinity.mediaset.it.test.js +++ b/sites/mediasetinfinity.mediaset.it/mediasetinfinity.mediaset.it.test.js @@ -1,53 +1,53 @@ -const { parser, url } = require('./mediasetinfinity.mediaset.it.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2024-01-20', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'LB', - xmltv_id: '20.it' -} - -it('can generate valid url', () => { - expect( - url({ - channel, - date - }) - ).toBe( - 'https://api-ott-prod-fe.mediaset.net/PROD/play/feed/allListingFeedEpg/v2.0?byListingTime=1705708800000~1705795200000&byCallSign=LB' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') - const results = parser({ content, date }).map(p => { - return p - }) - - expect(results[3]).toMatchObject({ - start: '2024-01-20 02:14', - stop: '2024-01-20 02:54', - title: 'Chicago Fire', - sub_title: 'Ep. 22 - Io non ti lascio', - description: - 'Severide e Kidd continuano a indagare su un vecchio caso doloso di Benny. Notizie inaspettate portano Brett a meditare su una grande decisione.', - category: 'Intrattenimento', - season: '7', - episode: '22', - image: - 'https://static2.mediasetplay.mediaset.it/Mediaset_Italia_Production_-_Main/F309370301002204/media/0/0/1ef76b73-3173-43bd-9c16-73986a0ec131/46896726-11e7-4438-b947-d2ae53f58c0b.jpg' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: '[]' - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./mediasetinfinity.mediaset.it.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2024-01-20', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'LB', + xmltv_id: '20.it' +} + +it('can generate valid url', () => { + expect( + url({ + channel, + date + }) + ).toBe( + 'https://api-ott-prod-fe.mediaset.net/PROD/play/feed/allListingFeedEpg/v2.0?byListingTime=1705708800000~1705795200000&byCallSign=LB' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') + const results = parser({ content, date }).map(p => { + return p + }) + + expect(results[3]).toMatchObject({ + start: '2024-01-20 02:14', + stop: '2024-01-20 02:54', + title: 'Chicago Fire', + sub_title: 'Ep. 22 - Io non ti lascio', + description: + 'Severide e Kidd continuano a indagare su un vecchio caso doloso di Benny. Notizie inaspettate portano Brett a meditare su una grande decisione.', + category: 'Intrattenimento', + season: '7', + episode: '22', + image: + 'https://static2.mediasetplay.mediaset.it/Mediaset_Italia_Production_-_Main/F309370301002204/media/0/0/1ef76b73-3173-43bd-9c16-73986a0ec131/46896726-11e7-4438-b947-d2ae53f58c0b.jpg' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: '[]' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/melita.com/melita.com.config.js b/sites/melita.com/melita.com.config.js index d344170ca..93386c7af 100644 --- a/sites/melita.com/melita.com.config.js +++ b/sites/melita.com/melita.com.config.js @@ -1,89 +1,89 @@ -const axios = require('axios') -const dayjs = require('dayjs') - -module.exports = { - site: 'melita.com', - days: 2, - url: function ({ channel, date }) { - return `https://androme.melitacable.com/api/epg/v1/schedule/channel/${ - channel.site_id - }/from/${date.format('YYYY-MM-DDTHH:mmZ')}/until/${date - .add(1, 'd') - .format('YYYY-MM-DDTHH:mmZ')}` - }, - parser: function ({ content }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - programs.push({ - title: item.title, - description: item.shortSynopsis, - image: parseImage(item), - category: item.tags, - season: item.season, - episode: item.episode, - start: parseStart(item), - stop: parseStop(item) - }) - }) - - return programs - }, - async channels() { - const channels = await axios - .get('https://androme.melitacable.com/api/epg/v2/channel') - .then(r => r.data) - .catch(console.log) - - return channels - .filter(i => !i.audioOnly && i.enabled) - .map(i => { - return { - lang: 'en', - name: i.name, - site_id: i.id - } - }) - } -} - -function parseStart(item) { - if (!item.published || !item.published.start) return null - - return dayjs(item.published.start) -} - -function parseStop(item) { - if (!item.published || !item.published.end) return null - - return dayjs(item.published.end) -} - -function parseImage(item) { - return item.posterImage ? item.posterImage + '?form=epg-card-6' : null -} - -function parseItems(content) { - const data = JSON.parse(content) - if ( - !data || - !data.schedules || - !data.programs || - !data.seasons || - !data.series || - !Array.isArray(data.schedules) - ) - return [] - - return data.schedules - .map(i => { - const program = data.programs.find(p => p.id === i.program) || {} - if (!program.season) return null - const season = data.seasons.find(s => s.id === program.season) || {} - if (!season.series) return null - const series = data.series.find(s => s.id === season.series) - - return { ...i, ...program, ...season, ...series } - }) - .filter(i => i) -} +const axios = require('axios') +const dayjs = require('dayjs') + +module.exports = { + site: 'melita.com', + days: 2, + url: function ({ channel, date }) { + return `https://androme.melitacable.com/api/epg/v1/schedule/channel/${ + channel.site_id + }/from/${date.format('YYYY-MM-DDTHH:mmZ')}/until/${date + .add(1, 'd') + .format('YYYY-MM-DDTHH:mmZ')}` + }, + parser: function ({ content }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + programs.push({ + title: item.title, + description: item.shortSynopsis, + image: parseImage(item), + category: item.tags, + season: item.season, + episode: item.episode, + start: parseStart(item), + stop: parseStop(item) + }) + }) + + return programs + }, + async channels() { + const channels = await axios + .get('https://androme.melitacable.com/api/epg/v2/channel') + .then(r => r.data) + .catch(console.log) + + return channels + .filter(i => !i.audioOnly && i.enabled) + .map(i => { + return { + lang: 'en', + name: i.name, + site_id: i.id + } + }) + } +} + +function parseStart(item) { + if (!item.published || !item.published.start) return null + + return dayjs(item.published.start) +} + +function parseStop(item) { + if (!item.published || !item.published.end) return null + + return dayjs(item.published.end) +} + +function parseImage(item) { + return item.posterImage ? item.posterImage + '?form=epg-card-6' : null +} + +function parseItems(content) { + const data = JSON.parse(content) + if ( + !data || + !data.schedules || + !data.programs || + !data.seasons || + !data.series || + !Array.isArray(data.schedules) + ) + return [] + + return data.schedules + .map(i => { + const program = data.programs.find(p => p.id === i.program) || {} + if (!program.season) return null + const season = data.seasons.find(s => s.id === program.season) || {} + if (!season.series) return null + const series = data.series.find(s => s.id === season.series) + + return { ...i, ...program, ...season, ...series } + }) + .filter(i => i) +} diff --git a/sites/meo.pt/meo.pt.config.js b/sites/meo.pt/meo.pt.config.js index c8054d69d..9d93ab20d 100644 --- a/sites/meo.pt/meo.pt.config.js +++ b/sites/meo.pt/meo.pt.config.js @@ -1,82 +1,82 @@ -const { DateTime } = require('luxon') - -module.exports = { - site: 'meo.pt', - days: 2, - url: 'https://authservice.apps.meo.pt/Services/GridTv/GridTvMng.svc/getProgramsFromChannels', - request: { - method: 'POST', - headers: { - Origin: 'https://www.meo.pt', - 'User-Agent': 'Mozilla/5.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; en-US Trident/4.0)' - }, - data: function ({ channel, date }) { - return { - service: 'channelsguide', - channels: [channel.site_id], - dateStart: date.format('YYYY-MM-DDT00:00:00-00:00'), - dateEnd: date.add(1, 'd').format('YYYY-MM-DDT00:00:00-00:00'), - accountID: '' - } - } - }, - parser({ content }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - const start = parseStart(item) - let stop = parseStop(item) - if (stop < start) { - stop = stop.plus({ days: 1 }) - } - programs.push({ - title: item.name, - start, - stop - }) - }) - - return programs - }, - async channels() { - const axios = require('axios') - const data = await axios - .post('https://authservice.apps.meo.pt/Services/GridTv/GridTvMng.svc/getGridAnon', null, { - headers: { - Origin: 'https://www.meo.pt' - } - }) - .then(r => r.data) - .catch(console.log) - - return data.d.channels - .map(item => { - return { - lang: 'pt', - site_id: item.sigla, - name: item.name - } - }) - .filter(channel => channel.site_id) - } -} - -function parseStart(item) { - return DateTime.fromFormat(`${item.date} ${item.timeIni}`, 'd-M-yyyy HH:mm', { - zone: 'Europe/Lisbon' - }).toUTC() -} - -function parseStop(item) { - return DateTime.fromFormat(`${item.date} ${item.timeEnd}`, 'd-M-yyyy HH:mm', { - zone: 'Europe/Lisbon' - }).toUTC() -} - -function parseItems(content) { - if (!content) return [] - const data = JSON.parse(content) - const programs = data?.d?.channels?.[0]?.programs - - return Array.isArray(programs) ? programs : [] -} +const { DateTime } = require('luxon') + +module.exports = { + site: 'meo.pt', + days: 2, + url: 'https://authservice.apps.meo.pt/Services/GridTv/GridTvMng.svc/getProgramsFromChannels', + request: { + method: 'POST', + headers: { + Origin: 'https://www.meo.pt', + 'User-Agent': 'Mozilla/5.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; en-US Trident/4.0)' + }, + data: function ({ channel, date }) { + return { + service: 'channelsguide', + channels: [channel.site_id], + dateStart: date.format('YYYY-MM-DDT00:00:00-00:00'), + dateEnd: date.add(1, 'd').format('YYYY-MM-DDT00:00:00-00:00'), + accountID: '' + } + } + }, + parser({ content }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + const start = parseStart(item) + let stop = parseStop(item) + if (stop < start) { + stop = stop.plus({ days: 1 }) + } + programs.push({ + title: item.name, + start, + stop + }) + }) + + return programs + }, + async channels() { + const axios = require('axios') + const data = await axios + .post('https://authservice.apps.meo.pt/Services/GridTv/GridTvMng.svc/getGridAnon', null, { + headers: { + Origin: 'https://www.meo.pt' + } + }) + .then(r => r.data) + .catch(console.log) + + return data.d.channels + .map(item => { + return { + lang: 'pt', + site_id: item.sigla, + name: item.name + } + }) + .filter(channel => channel.site_id) + } +} + +function parseStart(item) { + return DateTime.fromFormat(`${item.date} ${item.timeIni}`, 'd-M-yyyy HH:mm', { + zone: 'Europe/Lisbon' + }).toUTC() +} + +function parseStop(item) { + return DateTime.fromFormat(`${item.date} ${item.timeEnd}`, 'd-M-yyyy HH:mm', { + zone: 'Europe/Lisbon' + }).toUTC() +} + +function parseItems(content) { + if (!content) return [] + const data = JSON.parse(content) + const programs = data?.d?.channels?.[0]?.programs + + return Array.isArray(programs) ? programs : [] +} diff --git a/sites/meo.pt/meo.pt.test.js b/sites/meo.pt/meo.pt.test.js index 7decee75f..c1b455cd2 100644 --- a/sites/meo.pt/meo.pt.test.js +++ b/sites/meo.pt/meo.pt.test.js @@ -1,60 +1,60 @@ -const { parser, url, request } = require('./meo.pt.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-12-02', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'RTPM', - xmltv_id: 'RTPMadeira.pt' -} - -it('can generate valid url', () => { - expect(url).toBe( - 'https://authservice.apps.meo.pt/Services/GridTv/GridTvMng.svc/getProgramsFromChannels' - ) -}) - -it('can generate valid request method', () => { - expect(request.method).toBe('POST') -}) - -it('can generate valid request headers', () => { - expect(request.headers).toMatchObject({ - Origin: 'https://www.meo.pt' - }) -}) - -it('can generate valid request method', () => { - expect(request.data({ channel, date })).toMatchObject({ - service: 'channelsguide', - channels: ['RTPM'], - dateStart: '2022-12-02T00:00:00-00:00', - dateEnd: '2022-12-03T00:00:00-00:00', - accountID: '' - }) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - let results = parser({ content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2022-12-01T23:35:00.000Z', - stop: '2022-12-02T00:17:00.000Z', - title: 'Walker, O Ranger Do Texas T6 - Ep. 14' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ content: '', channel, date }) - expect(result).toMatchObject([]) -}) +const { parser, url, request } = require('./meo.pt.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-12-02', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'RTPM', + xmltv_id: 'RTPMadeira.pt' +} + +it('can generate valid url', () => { + expect(url).toBe( + 'https://authservice.apps.meo.pt/Services/GridTv/GridTvMng.svc/getProgramsFromChannels' + ) +}) + +it('can generate valid request method', () => { + expect(request.method).toBe('POST') +}) + +it('can generate valid request headers', () => { + expect(request.headers).toMatchObject({ + Origin: 'https://www.meo.pt' + }) +}) + +it('can generate valid request method', () => { + expect(request.data({ channel, date })).toMatchObject({ + service: 'channelsguide', + channels: ['RTPM'], + dateStart: '2022-12-02T00:00:00-00:00', + dateEnd: '2022-12-03T00:00:00-00:00', + accountID: '' + }) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + let results = parser({ content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2022-12-01T23:35:00.000Z', + stop: '2022-12-02T00:17:00.000Z', + title: 'Walker, O Ranger Do Texas T6 - Ep. 14' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ content: '', channel, date }) + expect(result).toMatchObject([]) +}) diff --git a/sites/meuguia.tv/meuguia.tv.config.js b/sites/meuguia.tv/meuguia.tv.config.js index e86f2bfae..1fdd3a081 100644 --- a/sites/meuguia.tv/meuguia.tv.config.js +++ b/sites/meuguia.tv/meuguia.tv.config.js @@ -1,105 +1,105 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'meuguia.tv', - days: 2, - url({ channel }) { - return `https://meuguia.tv/programacao/canal/${channel.site_id}` - }, - parser({ content, date }) { - const programs = [] - parseItems(content, date).forEach(item => { - if (dayjs.utc(item.start).isSame(date, 'day')) { - programs.push(item) - } - }) - - return programs - }, - async channels() { - const channels = [] - const axios = require('axios') - const baseUrl = 'https://meuguia.tv' - - let seq = 0 - const queues = [baseUrl] - while (true) { - if (!queues.length) { - break - } - const url = queues.shift() - const content = await axios - .get(url) - .then(response => response.data) - .catch(console.error) - - if (content) { - const [$, items] = getItems(content) - if (seq === 0) { - queues.push(...items.map(category => baseUrl + $(category).attr('href'))) - } else { - items.forEach(item => { - const href = $(item).attr('href') - channels.push({ - lang: 'pt', - site_id: href.substr(href.lastIndexOf('/') + 1), - name: $(item).find('.licontent h2').text().trim() - }) - }) - } - } - seq++ - } - - return channels - } -} - -function getItems(content) { - const $ = cheerio.load(content) - return [$, $('div.mw ul li a').toArray()] -} - -function parseItems(content, date) { - const result = [] - const $ = cheerio.load(content) - - let lastDate - for (const item of $('ul.mw li').toArray()) { - const $item = $(item) - if ($item.hasClass('subheader')) { - lastDate = `${$item.text().split(', ')[1]}/${date.format('YYYY')}` - } else if ($item.hasClass('divider')) { - // ignore - } else if (lastDate) { - const data = { title: $item.find('a').attr('title').trim() } - const ep = data.title.match(/T(\d+) EP(\d+)/) - if (ep) { - data.season = parseInt(ep[1]) - data.episode = parseInt(ep[2]) - } - data.start = dayjs.tz( - `${lastDate} ${$item.find('.time').text()}`, - 'DD/MM/YYYY HH:mm', - 'America/Sao_Paulo' - ) - result.push(data) - } - } - // use stop time from next item - if (result.length > 1) { - for (let i = 0; i < result.length - 1; i++) { - result[i].stop = result[i + 1].start - } - } - - return result -} +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'meuguia.tv', + days: 2, + url({ channel }) { + return `https://meuguia.tv/programacao/canal/${channel.site_id}` + }, + parser({ content, date }) { + const programs = [] + parseItems(content, date).forEach(item => { + if (dayjs.utc(item.start).isSame(date, 'day')) { + programs.push(item) + } + }) + + return programs + }, + async channels() { + const channels = [] + const axios = require('axios') + const baseUrl = 'https://meuguia.tv' + + let seq = 0 + const queues = [baseUrl] + while (true) { + if (!queues.length) { + break + } + const url = queues.shift() + const content = await axios + .get(url) + .then(response => response.data) + .catch(console.error) + + if (content) { + const [$, items] = getItems(content) + if (seq === 0) { + queues.push(...items.map(category => baseUrl + $(category).attr('href'))) + } else { + items.forEach(item => { + const href = $(item).attr('href') + channels.push({ + lang: 'pt', + site_id: href.substr(href.lastIndexOf('/') + 1), + name: $(item).find('.licontent h2').text().trim() + }) + }) + } + } + seq++ + } + + return channels + } +} + +function getItems(content) { + const $ = cheerio.load(content) + return [$, $('div.mw ul li a').toArray()] +} + +function parseItems(content, date) { + const result = [] + const $ = cheerio.load(content) + + let lastDate + for (const item of $('ul.mw li').toArray()) { + const $item = $(item) + if ($item.hasClass('subheader')) { + lastDate = `${$item.text().split(', ')[1]}/${date.format('YYYY')}` + } else if ($item.hasClass('divider')) { + // ignore + } else if (lastDate) { + const data = { title: $item.find('a').attr('title').trim() } + const ep = data.title.match(/T(\d+) EP(\d+)/) + if (ep) { + data.season = parseInt(ep[1]) + data.episode = parseInt(ep[2]) + } + data.start = dayjs.tz( + `${lastDate} ${$item.find('.time').text()}`, + 'DD/MM/YYYY HH:mm', + 'America/Sao_Paulo' + ) + result.push(data) + } + } + // use stop time from next item + if (result.length > 1) { + for (let i = 0; i < result.length - 1; i++) { + result[i].stop = result[i + 1].start + } + } + + return result +} diff --git a/sites/meuguia.tv/meuguia.tv.test.js b/sites/meuguia.tv/meuguia.tv.test.js index 3fe2ce948..c121039c7 100644 --- a/sites/meuguia.tv/meuguia.tv.test.js +++ b/sites/meuguia.tv/meuguia.tv.test.js @@ -1,60 +1,60 @@ -const { parser, url } = require('./meuguia.tv.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') - -dayjs.extend(utc) - -const date = dayjs.utc('2023-11-21').startOf('d') -const channel = { - site_id: 'AXN', - xmltv_id: 'AXN.id' -} -it('can generate valid url', () => { - expect(url({ channel })).toBe('https://meuguia.tv/programacao/canal/AXN') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - const result = parser({ content, channel, date }).map(p => { - p.start = p.start.toJSON() - if (p.stop) { - p.stop = p.stop.toJSON() - } - return p - }) - - expect(result).toMatchObject([ - { - title: 'Hawaii Five-0 : T10 EP4 - Tiny Is the Flower, Yet It Scents the Grasses Around It', - start: '2023-11-21T21:20:00.000Z', - stop: '2023-11-21T22:15:00.000Z', - season: 10, - episode: 4 - }, - { - title: - "Hawaii Five-0 : T10 EP5 - Don't Blame Ghosts and Spirits for One's Troubles; A Human Is Responsible", - start: '2023-11-21T22:15:00.000Z', - stop: '2023-11-21T23:10:00.000Z', - season: 10, - episode: 5 - }, - { - title: 'NCIS : T5 EP15 - In the Zone', - start: '2023-11-21T23:10:00.000Z', - season: 5, - episode: 15 - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: '' - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./meuguia.tv.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') + +dayjs.extend(utc) + +const date = dayjs.utc('2023-11-21').startOf('d') +const channel = { + site_id: 'AXN', + xmltv_id: 'AXN.id' +} +it('can generate valid url', () => { + expect(url({ channel })).toBe('https://meuguia.tv/programacao/canal/AXN') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + const result = parser({ content, channel, date }).map(p => { + p.start = p.start.toJSON() + if (p.stop) { + p.stop = p.stop.toJSON() + } + return p + }) + + expect(result).toMatchObject([ + { + title: 'Hawaii Five-0 : T10 EP4 - Tiny Is the Flower, Yet It Scents the Grasses Around It', + start: '2023-11-21T21:20:00.000Z', + stop: '2023-11-21T22:15:00.000Z', + season: 10, + episode: 4 + }, + { + title: + "Hawaii Five-0 : T10 EP5 - Don't Blame Ghosts and Spirits for One's Troubles; A Human Is Responsible", + start: '2023-11-21T22:15:00.000Z', + stop: '2023-11-21T23:10:00.000Z', + season: 10, + episode: 5 + }, + { + title: 'NCIS : T5 EP15 - In the Zone', + start: '2023-11-21T23:10:00.000Z', + season: 5, + episode: 15 + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: '' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/mewatch.sg/mewatch.sg.config.js b/sites/mewatch.sg/mewatch.sg.config.js index 1b793c83a..0d77c665a 100644 --- a/sites/mewatch.sg/mewatch.sg.config.js +++ b/sites/mewatch.sg/mewatch.sg.config.js @@ -1,100 +1,100 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'mewatch.sg', - days: 2, - url: function ({ channel, date }) { - const utcDate = date.isUTC() ? date.tz(dayjs.tz.guess(), true).utc() : date.utc() - - return `https://cdn.mewatch.sg/api/schedules?channels=${channel.site_id}&date=${utcDate.format( - 'YYYY-MM-DD' - )}&duration=24&ff=idp,ldp,rpt,cd&hour=${utcDate.format( - 'HH' - )}&intersect=true&lang=en&segments=all` - }, - parser: function ({ content, channel }) { - let programs = [] - const items = parseItems(content, channel) - items.forEach(item => { - const info = item.item - programs.push({ - title: info.title, - description: info.description, - image: info.images.tile, - episode: info.episodeNumber, - season: info.seasonNumber, - start: parseStart(item), - stop: parseStop(item), - rating: parseRating(info) - }) - }) - - return programs - }, - async channels() { - const axios = require('axios') - const cheerio = require('cheerio') - const data = await axios - .get('https://www.mewatch.sg/channel-guide') - .then(r => r.data) - .catch(console.log) - - let channels = [] - const $ = cheerio.load(data) - $('#side-nav > div > div > div > nav:nth-child(1) > ul > li > ul > li').each((i, el) => { - const name = $(el).find('a > span').text() - const url = $(el).find('a').attr('href') - const [, site_id = null] = url.match(/\/(\d+)\?player-fullscreen/) ?? [] - - if (!site_id) { - return - } - - channels.push({ - lang: 'en', - name, - site_id - }) - }) - - return channels - } -} - -function parseStart(item) { - return dayjs(item.startDate) -} - -function parseStop(item) { - return dayjs(item.endDate) -} - -function parseRating(info) { - const classification = info.classification - if (classification && classification.code) { - const [, system, value] = classification.code.match(/^([A-Z]+)-([A-Z0-9]+)/) || [ - null, - null, - null - ] - - return { system, value } - } - - return null -} - -function parseItems(content, channel) { - const data = JSON.parse(content) - if (!data || !Array.isArray(data)) return [] - const channelData = data.find(i => i.channelId === channel.site_id) - - return channelData && Array.isArray(channelData.schedules) ? channelData.schedules : [] -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'mewatch.sg', + days: 2, + url: function ({ channel, date }) { + const utcDate = date.isUTC() ? date.tz(dayjs.tz.guess(), true).utc() : date.utc() + + return `https://cdn.mewatch.sg/api/schedules?channels=${channel.site_id}&date=${utcDate.format( + 'YYYY-MM-DD' + )}&duration=24&ff=idp,ldp,rpt,cd&hour=${utcDate.format( + 'HH' + )}&intersect=true&lang=en&segments=all` + }, + parser: function ({ content, channel }) { + let programs = [] + const items = parseItems(content, channel) + items.forEach(item => { + const info = item.item + programs.push({ + title: info.title, + description: info.description, + image: info.images.tile, + episode: info.episodeNumber, + season: info.seasonNumber, + start: parseStart(item), + stop: parseStop(item), + rating: parseRating(info) + }) + }) + + return programs + }, + async channels() { + const axios = require('axios') + const cheerio = require('cheerio') + const data = await axios + .get('https://www.mewatch.sg/channel-guide') + .then(r => r.data) + .catch(console.log) + + let channels = [] + const $ = cheerio.load(data) + $('#side-nav > div > div > div > nav:nth-child(1) > ul > li > ul > li').each((i, el) => { + const name = $(el).find('a > span').text() + const url = $(el).find('a').attr('href') + const [, site_id = null] = url.match(/\/(\d+)\?player-fullscreen/) ?? [] + + if (!site_id) { + return + } + + channels.push({ + lang: 'en', + name, + site_id + }) + }) + + return channels + } +} + +function parseStart(item) { + return dayjs(item.startDate) +} + +function parseStop(item) { + return dayjs(item.endDate) +} + +function parseRating(info) { + const classification = info.classification + if (classification && classification.code) { + const [, system, value] = classification.code.match(/^([A-Z]+)-([A-Z0-9]+)/) || [ + null, + null, + null + ] + + return { system, value } + } + + return null +} + +function parseItems(content, channel) { + const data = JSON.parse(content) + if (!data || !Array.isArray(data)) return [] + const channelData = data.find(i => i.channelId === channel.site_id) + + return channelData && Array.isArray(channelData.schedules) ? channelData.schedules : [] +} diff --git a/sites/mncvision.id/mncvision.id.config.js b/sites/mncvision.id/mncvision.id.config.js index 88f2b430c..40896953d 100644 --- a/sites/mncvision.id/mncvision.id.config.js +++ b/sites/mncvision.id/mncvision.id.config.js @@ -1,167 +1,167 @@ -const axios = require('axios') -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') -const doFetch = require('@ntlab/sfetch') -const debug = require('debug')('site:mncvision.id') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -doFetch.setCheckResult(false).setDebugger(debug) - -const languages = { en: 'english', id: 'indonesia' } -const cookies = {} -const timeout = 30000 - -module.exports = { - site: 'mncvision.id', - days: 2, - url: 'https://www.mncvision.id/schedule/table', - request: { - method: 'POST', - data({ channel, date }) { - const formData = new URLSearchParams() - formData.append('search_model', 'channel') - formData.append('af0rmelement', 'aformelement') - formData.append('fdate', date.format('YYYY-MM-DD')) - formData.append('fchannel', channel.site_id) - formData.append('submit', 'Search') - - return formData - }, - async headers({ channel }) { - const headers = { - 'Content-Type': 'application/x-www-form-urlencoded' - } - if (channel) { - if (!cookies[channel.lang]) { - cookies[channel.lang] = await loadLangCookies(channel) - } - if (cookies[channel.lang]) { - headers.Cookie = cookies[channel.lang] - } - } - return headers - }, - jar: null - }, - async parser({ content, headers, date, channel }) { - if (!cookies[channel.lang]) { - cookies[channel.lang] = parseCookies(headers) - } - - return await parseItems(content, date, cookies[channel.lang]) - }, - async channels({ lang = 'id' }) { - const result = await axios - .get('https://www.mncvision.id/schedule') - .then(response => response.data) - .catch(console.error) - - const $ = cheerio.load(result) - const items = $('select[name="fchannel"] option').toArray() - const channels = items.map(item => { - const $item = $(item) - - return { - lang, - site_id: $item.attr('value'), - name: $item.text().split(' - ')[0].trim() - } - }) - - return channels - } -} - -function parseSeason($item) { - const title = parseTitle($item) - const [, season] = title.match(/ S(\d+)/) || [null, null] - - return season ? parseInt(season) : null -} - -function parseEpisode($item) { - const title = parseTitle($item) - const [, episode] = title.match(/ Ep (\d+)/) || [null, null] - - return episode ? parseInt(episode) : null -} - -function parseDuration($item) { - let duration = $item.find('td:nth-child(3)').text() - const match = duration.match(/(\d{2}):(\d{2})/) - const hours = parseInt(match[1]) - const minutes = parseInt(match[2]) - - return hours * 60 + minutes -} - -function parseStart($item, date) { - let time = $item.find('td:nth-child(1)').text() - time = `${date.format('DD/MM/YYYY')} ${time}` - - return dayjs.tz(time, 'DD/MM/YYYY HH:mm', 'Asia/Jakarta') -} - -function parseTitle($item) { - return $item.find('td:nth-child(2) > a').text() -} - -async function parseItems(content, date, cookies) { - const programs = [] - const $ = cheerio.load(content) - const items = $('tr[valign="top"]').toArray() - if (items.length) { - const queues = [] - for (const item of items) { - const $item = $(item) - const url = $item.find('a').attr('href') - const headers = { - 'X-Requested-With': 'XMLHttpRequest', - Cookie: cookies - } - queues.push({ i: $item, url, params: { headers, timeout } }) - } - await doFetch(queues, (queue, res) => { - const $item = queue.i - const description = res ? cheerio.load(res)('.synopsis').text().trim() : null - const start = parseStart($item, date) - const duration = parseDuration($item) - const stop = start.add(duration, 'm') - programs.push({ - title: parseTitle($item), - season: parseSeason($item), - episode: parseEpisode($item), - description: description && description !== '-' ? description : null, - start, - stop - }) - }) - } - - return programs -} - -function loadLangCookies(channel) { - const url = `https://www.mncvision.id/language_switcher/setlang/${languages[channel.lang]}/` - - return axios - .get(url, { timeout }) - .then(r => parseCookies(r.headers)) - .catch(error => console.error(error.message)) -} - -function parseCookies(headers) { - const cookies = [] - if (Array.isArray(headers['set-cookie'])) { - headers['set-cookie'].forEach(cookie => { - cookies.push(cookie.split('; ')[0]) - }) - } - return cookies.length ? cookies.join('; ') : null -} +const axios = require('axios') +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const doFetch = require('@ntlab/sfetch') +const debug = require('debug')('site:mncvision.id') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +doFetch.setCheckResult(false).setDebugger(debug) + +const languages = { en: 'english', id: 'indonesia' } +const cookies = {} +const timeout = 30000 + +module.exports = { + site: 'mncvision.id', + days: 2, + url: 'https://www.mncvision.id/schedule/table', + request: { + method: 'POST', + data({ channel, date }) { + const formData = new URLSearchParams() + formData.append('search_model', 'channel') + formData.append('af0rmelement', 'aformelement') + formData.append('fdate', date.format('YYYY-MM-DD')) + formData.append('fchannel', channel.site_id) + formData.append('submit', 'Search') + + return formData + }, + async headers({ channel }) { + const headers = { + 'Content-Type': 'application/x-www-form-urlencoded' + } + if (channel) { + if (!cookies[channel.lang]) { + cookies[channel.lang] = await loadLangCookies(channel) + } + if (cookies[channel.lang]) { + headers.Cookie = cookies[channel.lang] + } + } + return headers + }, + jar: null + }, + async parser({ content, headers, date, channel }) { + if (!cookies[channel.lang]) { + cookies[channel.lang] = parseCookies(headers) + } + + return await parseItems(content, date, cookies[channel.lang]) + }, + async channels({ lang = 'id' }) { + const result = await axios + .get('https://www.mncvision.id/schedule') + .then(response => response.data) + .catch(console.error) + + const $ = cheerio.load(result) + const items = $('select[name="fchannel"] option').toArray() + const channels = items.map(item => { + const $item = $(item) + + return { + lang, + site_id: $item.attr('value'), + name: $item.text().split(' - ')[0].trim() + } + }) + + return channels + } +} + +function parseSeason($item) { + const title = parseTitle($item) + const [, season] = title.match(/ S(\d+)/) || [null, null] + + return season ? parseInt(season) : null +} + +function parseEpisode($item) { + const title = parseTitle($item) + const [, episode] = title.match(/ Ep (\d+)/) || [null, null] + + return episode ? parseInt(episode) : null +} + +function parseDuration($item) { + let duration = $item.find('td:nth-child(3)').text() + const match = duration.match(/(\d{2}):(\d{2})/) + const hours = parseInt(match[1]) + const minutes = parseInt(match[2]) + + return hours * 60 + minutes +} + +function parseStart($item, date) { + let time = $item.find('td:nth-child(1)').text() + time = `${date.format('DD/MM/YYYY')} ${time}` + + return dayjs.tz(time, 'DD/MM/YYYY HH:mm', 'Asia/Jakarta') +} + +function parseTitle($item) { + return $item.find('td:nth-child(2) > a').text() +} + +async function parseItems(content, date, cookies) { + const programs = [] + const $ = cheerio.load(content) + const items = $('tr[valign="top"]').toArray() + if (items.length) { + const queues = [] + for (const item of items) { + const $item = $(item) + const url = $item.find('a').attr('href') + const headers = { + 'X-Requested-With': 'XMLHttpRequest', + Cookie: cookies + } + queues.push({ i: $item, url, params: { headers, timeout } }) + } + await doFetch(queues, (queue, res) => { + const $item = queue.i + const description = res ? cheerio.load(res)('.synopsis').text().trim() : null + const start = parseStart($item, date) + const duration = parseDuration($item) + const stop = start.add(duration, 'm') + programs.push({ + title: parseTitle($item), + season: parseSeason($item), + episode: parseEpisode($item), + description: description && description !== '-' ? description : null, + start, + stop + }) + }) + } + + return programs +} + +function loadLangCookies(channel) { + const url = `https://www.mncvision.id/language_switcher/setlang/${languages[channel.lang]}/` + + return axios + .get(url, { timeout }) + .then(r => parseCookies(r.headers)) + .catch(error => console.error(error.message)) +} + +function parseCookies(headers) { + const cookies = [] + if (Array.isArray(headers['set-cookie'])) { + headers['set-cookie'].forEach(cookie => { + cookies.push(cookie.split('; ')[0]) + }) + } + return cookies.length ? cookies.join('; ') : null +} diff --git a/sites/mncvision.id/mncvision.id.test.js b/sites/mncvision.id/mncvision.id.test.js index 1ce385d63..481ffa95c 100644 --- a/sites/mncvision.id/mncvision.id.test.js +++ b/sites/mncvision.id/mncvision.id.test.js @@ -1,132 +1,132 @@ -const { parser, url, request } = require('./mncvision.id.config.js') -const fs = require('fs') -const path = require('path') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -const date = dayjs.utc('2023-11-19').startOf('d') -const channel = { - site_id: '154', - xmltv_id: 'AXN.id', - lang: 'id' -} -const indonesiaHeaders = { - 'set-cookie': [ - 's1nd0vL=uo6gsashc1rmloqbb50m6b13qkglfvpl; expires=Sat, 18-Nov-2023 20:45:02 GMT; Max-Age=7200; path=/; HttpOnly' - ] -} -const englishHeaders = { - 'set-cookie': [ - 's1nd0vL=imtot2v1cs0pbemaohj9fee3hlbqo699; expires=Sat, 18-Nov-2023 20:38:31 GMT; Max-Age=7200; path=/; HttpOnly' - ] -} - -axios.get.mockImplementation((url, opts) => { - if (url === 'https://www.mncvision.id/language_switcher/setlang/indonesia/') { - return Promise.resolve({ - headers: indonesiaHeaders - }) - } - if (url === 'https://www.mncvision.id/language_switcher/setlang/english/') { - return Promise.resolve({ - headers: englishHeaders - }) - } - if ( - url === 'https://www.mncvision.id/schedule/detail/20231119001500154/Blue-Bloods-S13-Ep-19/1' - ) { - const getCookie = headers => { - if (Array.isArray(headers['set-cookie'])) { - return headers['set-cookie'][0].split('; ')[0] - } - } - if (opts.headers['Cookie'] === getCookie(indonesiaHeaders)) { - return Promise.resolve({ - data: fs.readFileSync(path.resolve(__dirname, '__data__/program_id.html')) - }) - } - if (opts.headers['Cookie'] === getCookie(englishHeaders)) { - return Promise.resolve({ - data: fs.readFileSync(path.resolve(__dirname, '__data__/program_en.html')) - }) - } - } - - return Promise.resolve({ data: '' }) -}) - -it('can generate valid url', () => { - expect(url).toBe('https://www.mncvision.id/schedule/table') -}) - -it('can generate valid request method', () => { - expect(request.method).toBe('POST') -}) - -it('can generate valid request headers', async () => { - expect(await request.headers({ channel })).toMatchObject({ - 'Content-Type': 'application/x-www-form-urlencoded' - }) -}) - -it('can generate valid request data', () => { - const data = request.data({ channel, date }) - expect(data.get('search_model')).toBe('channel') - expect(data.get('af0rmelement')).toBe('aformelement') - expect(data.get('fdate')).toBe('2023-11-19') - expect(data.get('fchannel')).toBe('154') - expect(data.get('submit')).toBe('Search') -}) - -it('can parse response', async () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - const indonesiaResults = ( - await parser({ date, content, channel, headers: indonesiaHeaders }) - ).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - expect(indonesiaResults[0]).toMatchObject({ - start: '2023-11-18T17:15:00.000Z', - stop: '2023-11-18T18:05:00.000Z', - title: 'Blue Bloods S13, Ep 19', - episode: 19, - description: - 'Jamie bekerja sama dengan FDNY untuk menemukan pelaku pembakaran yang bertanggung jawab atas kebakaran hebat yang terjadi di fasilitas penyimpanan bukti milik NYPD.' - }) - - const englishResults = ( - await parser({ date, content, channel: { ...channel, lang: 'en' }, headers: englishHeaders }) - ).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - expect(englishResults[0]).toMatchObject({ - start: '2023-11-18T17:15:00.000Z', - stop: '2023-11-18T18:05:00.000Z', - title: 'Blue Bloods S13, Ep 19', - episode: 19, - description: - 'Jamie partners with the FDNY to find the arsonist responsible for a massive fire at an NYPD evidence storage facility.' - }) -}) - -it('can handle empty guide', async () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) - const results = await parser({ - date, - channel, - content, - headers: indonesiaHeaders - }) - expect(results).toMatchObject([]) -}) +const { parser, url, request } = require('./mncvision.id.config.js') +const fs = require('fs') +const path = require('path') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.utc('2023-11-19').startOf('d') +const channel = { + site_id: '154', + xmltv_id: 'AXN.id', + lang: 'id' +} +const indonesiaHeaders = { + 'set-cookie': [ + 's1nd0vL=uo6gsashc1rmloqbb50m6b13qkglfvpl; expires=Sat, 18-Nov-2023 20:45:02 GMT; Max-Age=7200; path=/; HttpOnly' + ] +} +const englishHeaders = { + 'set-cookie': [ + 's1nd0vL=imtot2v1cs0pbemaohj9fee3hlbqo699; expires=Sat, 18-Nov-2023 20:38:31 GMT; Max-Age=7200; path=/; HttpOnly' + ] +} + +axios.get.mockImplementation((url, opts) => { + if (url === 'https://www.mncvision.id/language_switcher/setlang/indonesia/') { + return Promise.resolve({ + headers: indonesiaHeaders + }) + } + if (url === 'https://www.mncvision.id/language_switcher/setlang/english/') { + return Promise.resolve({ + headers: englishHeaders + }) + } + if ( + url === 'https://www.mncvision.id/schedule/detail/20231119001500154/Blue-Bloods-S13-Ep-19/1' + ) { + const getCookie = headers => { + if (Array.isArray(headers['set-cookie'])) { + return headers['set-cookie'][0].split('; ')[0] + } + } + if (opts.headers['Cookie'] === getCookie(indonesiaHeaders)) { + return Promise.resolve({ + data: fs.readFileSync(path.resolve(__dirname, '__data__/program_id.html')) + }) + } + if (opts.headers['Cookie'] === getCookie(englishHeaders)) { + return Promise.resolve({ + data: fs.readFileSync(path.resolve(__dirname, '__data__/program_en.html')) + }) + } + } + + return Promise.resolve({ data: '' }) +}) + +it('can generate valid url', () => { + expect(url).toBe('https://www.mncvision.id/schedule/table') +}) + +it('can generate valid request method', () => { + expect(request.method).toBe('POST') +}) + +it('can generate valid request headers', async () => { + expect(await request.headers({ channel })).toMatchObject({ + 'Content-Type': 'application/x-www-form-urlencoded' + }) +}) + +it('can generate valid request data', () => { + const data = request.data({ channel, date }) + expect(data.get('search_model')).toBe('channel') + expect(data.get('af0rmelement')).toBe('aformelement') + expect(data.get('fdate')).toBe('2023-11-19') + expect(data.get('fchannel')).toBe('154') + expect(data.get('submit')).toBe('Search') +}) + +it('can parse response', async () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + const indonesiaResults = ( + await parser({ date, content, channel, headers: indonesiaHeaders }) + ).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + expect(indonesiaResults[0]).toMatchObject({ + start: '2023-11-18T17:15:00.000Z', + stop: '2023-11-18T18:05:00.000Z', + title: 'Blue Bloods S13, Ep 19', + episode: 19, + description: + 'Jamie bekerja sama dengan FDNY untuk menemukan pelaku pembakaran yang bertanggung jawab atas kebakaran hebat yang terjadi di fasilitas penyimpanan bukti milik NYPD.' + }) + + const englishResults = ( + await parser({ date, content, channel: { ...channel, lang: 'en' }, headers: englishHeaders }) + ).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + expect(englishResults[0]).toMatchObject({ + start: '2023-11-18T17:15:00.000Z', + stop: '2023-11-18T18:05:00.000Z', + title: 'Blue Bloods S13, Ep 19', + episode: 19, + description: + 'Jamie partners with the FDNY to find the arsonist responsible for a massive fire at an NYPD evidence storage facility.' + }) +}) + +it('can handle empty guide', async () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) + const results = await parser({ + date, + channel, + content, + headers: indonesiaHeaders + }) + expect(results).toMatchObject([]) +}) diff --git a/sites/moji.id/moji.id.config.js b/sites/moji.id/moji.id.config.js index ee6d6db22..f6205a1c6 100644 --- a/sites/moji.id/moji.id.config.js +++ b/sites/moji.id/moji.id.config.js @@ -1,103 +1,103 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -const currentYear = new Date().getFullYear() -const tz = 'Asia/Jakarta' - -module.exports = { - site: 'moji.id', - days: 2, - url: 'https://moji.id/schedule', - logo: function (context) { - return context.channel.logo - }, - parser: function (context) { - const programs = [] - const items = parseItems(context) - - items.forEach(item => { - programs.push({ - title: item.progTitle, - description: item.progDesc, - start: item.progStart, - stop: item.progStop - }) - }) - - return programs - } -} - -function parseItems(context) { - const $ = cheerio.load(context.content) - const schDayMonths = $('.date-slider .month').toArray() - const schPrograms = $('.desc-slider .list-slider').toArray() - const monthDate = dayjs(context.date).format('MMM DD') - const items = [] - - schDayMonths.forEach((schDayMonth, i) => { - if (monthDate == $(schDayMonth).text()) { - const schDayPrograms = $(schPrograms[i]).find('.accordion').toArray() - schDayPrograms.forEach((program, i) => { - const itemDay = { - progStart: parseStart($(schDayMonth), $(program)), - progStop: parseStop( - $(schDayMonth), - schDayPrograms[i + 1] ? $(schDayPrograms[i + 1]) : null - ), - progTitle: parseTitle($(program)), - progDesc: parseDescription($(program)) - } - items.push(itemDay) - }) - } - }) - - return items -} - -function parseTitle(item) { - return item.find('.name-prog').text() -} - -function parseDescription(item) { - return item.find('.content-acc span').text() -} - -function parseStart(schDayMonth, item) { - const monthDate = schDayMonth.text().split(' ') - const startTime = item.find('.pkl').text() - - return dayjs.tz( - `${currentYear}-${monthDate[0]}-${monthDate[1]} ${startTime}`, - 'YYYY-MMM-DD HH:mm', - tz - ) -} - -function parseStop(schDayMonth, itemNext) { - const monthDate = schDayMonth.text().split(' ') - if (itemNext) { - const stopTime = itemNext.find('.pkl').text() - return dayjs.tz( - `${currentYear}-${monthDate[0]}-${monthDate[1]} ${stopTime}`, - 'YYYY-MMM-DD HH:mm', - tz - ) - } else { - return dayjs.tz( - `${currentYear}-${monthDate[0]}-${(parseInt(monthDate[1]) + 1) - .toString() - .padStart(2, '0')} 00:00`, - 'YYYY-MMM-DD HH:mm', - tz - ) - } -} +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +const currentYear = new Date().getFullYear() +const tz = 'Asia/Jakarta' + +module.exports = { + site: 'moji.id', + days: 2, + url: 'https://moji.id/schedule', + logo: function (context) { + return context.channel.logo + }, + parser: function (context) { + const programs = [] + const items = parseItems(context) + + items.forEach(item => { + programs.push({ + title: item.progTitle, + description: item.progDesc, + start: item.progStart, + stop: item.progStop + }) + }) + + return programs + } +} + +function parseItems(context) { + const $ = cheerio.load(context.content) + const schDayMonths = $('.date-slider .month').toArray() + const schPrograms = $('.desc-slider .list-slider').toArray() + const monthDate = dayjs(context.date).format('MMM DD') + const items = [] + + schDayMonths.forEach((schDayMonth, i) => { + if (monthDate == $(schDayMonth).text()) { + const schDayPrograms = $(schPrograms[i]).find('.accordion').toArray() + schDayPrograms.forEach((program, i) => { + const itemDay = { + progStart: parseStart($(schDayMonth), $(program)), + progStop: parseStop( + $(schDayMonth), + schDayPrograms[i + 1] ? $(schDayPrograms[i + 1]) : null + ), + progTitle: parseTitle($(program)), + progDesc: parseDescription($(program)) + } + items.push(itemDay) + }) + } + }) + + return items +} + +function parseTitle(item) { + return item.find('.name-prog').text() +} + +function parseDescription(item) { + return item.find('.content-acc span').text() +} + +function parseStart(schDayMonth, item) { + const monthDate = schDayMonth.text().split(' ') + const startTime = item.find('.pkl').text() + + return dayjs.tz( + `${currentYear}-${monthDate[0]}-${monthDate[1]} ${startTime}`, + 'YYYY-MMM-DD HH:mm', + tz + ) +} + +function parseStop(schDayMonth, itemNext) { + const monthDate = schDayMonth.text().split(' ') + if (itemNext) { + const stopTime = itemNext.find('.pkl').text() + return dayjs.tz( + `${currentYear}-${monthDate[0]}-${monthDate[1]} ${stopTime}`, + 'YYYY-MMM-DD HH:mm', + tz + ) + } else { + return dayjs.tz( + `${currentYear}-${monthDate[0]}-${(parseInt(monthDate[1]) + 1) + .toString() + .padStart(2, '0')} 00:00`, + 'YYYY-MMM-DD HH:mm', + tz + ) + } +} diff --git a/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.test.js b/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.test.js index c91a7eb92..4e4c228cf 100644 --- a/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.test.js +++ b/sites/mojmaxtv.hrvatskitelekom.hr/mojmaxtv.hrvatskitelekom.hr.test.js @@ -1,85 +1,85 @@ -const { parser, url, request } = require('./mojmaxtv.hrvatskitelekom.hr.config.js') -const fs = require('fs') -const path = require('path') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-01-24', 'YYYY-MM-DD').startOf('d') -const channel = { site_id: '274913832105', xmltv_id: 'HRT1.hr' } - -jest.mock('axios') - -axios.get.mockImplementation(url => { - if ( - url === - 'https://tv-hr-prod.yo-digital.com/hr-bifrost/epg/channel/schedules?date=2025-01-24&hour_offset=3&hour_range=3&channelMap_id&filler=true&app_language=hr&natco_code=hr' - ) { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/content_3.json'))) - }) - } else if ( - url === - 'https://tv-hr-prod.yo-digital.com/hr-bifrost/epg/channel/schedules?date=2025-01-24&hour_offset=21&hour_range=3&channelMap_id&filler=true&app_language=hr&natco_code=hr' - ) { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/content_21.json'))) - }) - } else { - return Promise.resolve({ - data: {} - }) - } -}) - -it('can generate valid url', () => { - expect(url({ date })).toBe( - 'https://tv-hr-prod.yo-digital.com/hr-bifrost/epg/channel/schedules?date=2025-01-24&hour_offset=0&hour_range=3&channelMap_id&filler=true&app_language=hr&natco_code=hr' - ) -}) - -it('can generate valid request headers', () => { - expect(request.headers).toMatchObject({ - app_key: 'GWaBW4RTloLwpUgYVzOiW5zUxFLmoMj5', - app_version: '02.0.1080', - }) -}) - -it('can parse response', async () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_0.json')) - - const results = await parser({ content, channel, date }) - - expect(results.length).toBe(17) - expect(results[0]).toMatchObject({ - title: 'Planet Zemlja: Junaci', - categories: ['Dokumentarni'], - season: 3, - episode: 8, - date: '2023', - start: '2025-01-23T23:16:00.00Z', - stop: '2025-01-24T00:08:00.00Z' - }) - expect(results[16]).toMatchObject({ - title: 'Harry Haft, film', - categories: ['Film', 'Drama', 'Biografski'], - season: null, - episode: null, - date: '2021', - start: '2025-01-24T21:50:00.00Z', - stop: '2025-01-25T00:00:00.00Z' - }) -}) - -it('can handle empty guide', async () => { - const results = await parser({ - date, - channel, - content: '{}' - }) - - expect(results).toMatchObject([]) -}) +const { parser, url, request } = require('./mojmaxtv.hrvatskitelekom.hr.config.js') +const fs = require('fs') +const path = require('path') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-01-24', 'YYYY-MM-DD').startOf('d') +const channel = { site_id: '274913832105', xmltv_id: 'HRT1.hr' } + +jest.mock('axios') + +axios.get.mockImplementation(url => { + if ( + url === + 'https://tv-hr-prod.yo-digital.com/hr-bifrost/epg/channel/schedules?date=2025-01-24&hour_offset=3&hour_range=3&channelMap_id&filler=true&app_language=hr&natco_code=hr' + ) { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/content_3.json'))) + }) + } else if ( + url === + 'https://tv-hr-prod.yo-digital.com/hr-bifrost/epg/channel/schedules?date=2025-01-24&hour_offset=21&hour_range=3&channelMap_id&filler=true&app_language=hr&natco_code=hr' + ) { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/content_21.json'))) + }) + } else { + return Promise.resolve({ + data: {} + }) + } +}) + +it('can generate valid url', () => { + expect(url({ date })).toBe( + 'https://tv-hr-prod.yo-digital.com/hr-bifrost/epg/channel/schedules?date=2025-01-24&hour_offset=0&hour_range=3&channelMap_id&filler=true&app_language=hr&natco_code=hr' + ) +}) + +it('can generate valid request headers', () => { + expect(request.headers).toMatchObject({ + app_key: 'GWaBW4RTloLwpUgYVzOiW5zUxFLmoMj5', + app_version: '02.0.1080', + }) +}) + +it('can parse response', async () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_0.json')) + + const results = await parser({ content, channel, date }) + + expect(results.length).toBe(17) + expect(results[0]).toMatchObject({ + title: 'Planet Zemlja: Junaci', + categories: ['Dokumentarni'], + season: 3, + episode: 8, + date: '2023', + start: '2025-01-23T23:16:00.00Z', + stop: '2025-01-24T00:08:00.00Z' + }) + expect(results[16]).toMatchObject({ + title: 'Harry Haft, film', + categories: ['Film', 'Drama', 'Biografski'], + season: null, + episode: null, + date: '2021', + start: '2025-01-24T21:50:00.00Z', + stop: '2025-01-25T00:00:00.00Z' + }) +}) + +it('can handle empty guide', async () => { + const results = await parser({ + date, + channel, + content: '{}' + }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/mon-programme-tv.be/mon-programme-tv.be.config.js b/sites/mon-programme-tv.be/mon-programme-tv.be.config.js index 23307f4f8..7e105adc5 100644 --- a/sites/mon-programme-tv.be/mon-programme-tv.be.config.js +++ b/sites/mon-programme-tv.be/mon-programme-tv.be.config.js @@ -1,102 +1,102 @@ -const cheerio = require('cheerio') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') - -dayjs.extend(utc) -dayjs.extend(timezone) - -module.exports = { - site: 'mon-programme-tv.be', - days: 2, - url({ date, channel }) { - return `https://www.mon-programme-tv.be/chaine/${date.format('DDMMYYYY')}/${ - channel.site_id - }.html` - }, - parser: function ({ content, date }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - const $item = cheerio.load(item) - const prev = programs[programs.length - 1] - let start = parseStart($item, date) - if (prev) { - if (start.isBefore(prev.start)) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.add(30, 'm') - programs.push({ - title: parseTitle($item), - description: parseDescription($item), - category: parseCategory($item), - image: parseImage($item), - start, - stop - }) - }) - - return programs - }, - async channels() { - const data = await axios - .get('https://www.mon-programme-tv.be/chaine/toutes-les-chaines-television.html') - .then(r => r.data) - .catch(console.log) - const $ = cheerio.load(data) - - const channels = [] - $('.list-chaines > ul > li').each((i, el) => { - const [, site_id] = $(el) - .find('a') - .attr('href') - .match(/\/chaine\/(.*).html/) || [null, null] - const [, name] = $(el) - .find('a') - .attr('title') - .match(/Programme TV ce soir (.*)/) || [null, null] - - if (!site_id || !name) return - - channels.push({ - site_id, - name, - lang: 'fr' - }) - }) - - return channels - } -} - -function parseTitle($item) { - return $item('.title').text().trim() -} - -function parseDescription($item) { - return $item('.episode').text().trim() -} - -function parseCategory($item) { - return $item('.type').text().trim() -} - -function parseImage($item) { - return $item('.image img').data('src') -} - -function parseStart($item, date) { - const time = $item('.hour').text().trim() - - return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'Europe/Brussels') -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('.box').toArray() -} +const cheerio = require('cheerio') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') + +dayjs.extend(utc) +dayjs.extend(timezone) + +module.exports = { + site: 'mon-programme-tv.be', + days: 2, + url({ date, channel }) { + return `https://www.mon-programme-tv.be/chaine/${date.format('DDMMYYYY')}/${ + channel.site_id + }.html` + }, + parser: function ({ content, date }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + const $item = cheerio.load(item) + const prev = programs[programs.length - 1] + let start = parseStart($item, date) + if (prev) { + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.add(30, 'm') + programs.push({ + title: parseTitle($item), + description: parseDescription($item), + category: parseCategory($item), + image: parseImage($item), + start, + stop + }) + }) + + return programs + }, + async channels() { + const data = await axios + .get('https://www.mon-programme-tv.be/chaine/toutes-les-chaines-television.html') + .then(r => r.data) + .catch(console.log) + const $ = cheerio.load(data) + + const channels = [] + $('.list-chaines > ul > li').each((i, el) => { + const [, site_id] = $(el) + .find('a') + .attr('href') + .match(/\/chaine\/(.*).html/) || [null, null] + const [, name] = $(el) + .find('a') + .attr('title') + .match(/Programme TV ce soir (.*)/) || [null, null] + + if (!site_id || !name) return + + channels.push({ + site_id, + name, + lang: 'fr' + }) + }) + + return channels + } +} + +function parseTitle($item) { + return $item('.title').text().trim() +} + +function parseDescription($item) { + return $item('.episode').text().trim() +} + +function parseCategory($item) { + return $item('.type').text().trim() +} + +function parseImage($item) { + return $item('.image img').data('src') +} + +function parseStart($item, date) { + const time = $item('.hour').text().trim() + + return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'Europe/Brussels') +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('.box').toArray() +} diff --git a/sites/mon-programme-tv.be/mon-programme-tv.be.test.js b/sites/mon-programme-tv.be/mon-programme-tv.be.test.js index c6dc381a0..10c6e2b0a 100644 --- a/sites/mon-programme-tv.be/mon-programme-tv.be.test.js +++ b/sites/mon-programme-tv.be/mon-programme-tv.be.test.js @@ -1,63 +1,63 @@ -const { parser, url } = require('./mon-programme-tv.be.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-01-19', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '1873/programme-television-ln24', - xmltv_id: 'LN24.be' -} - -it('can generate valid url', () => { - expect(url({ date, channel })).toBe( - 'https://www.mon-programme-tv.be/chaine/19012023/1873/programme-television-ln24.html' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - const results = parser({ content, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2023-01-19T05:30:00.000Z', - stop: '2023-01-19T05:55:00.000Z', - title: 'LN Matin', - category: 'Magazine Actualité', - image: 'https://dnsmptv-img.pragma-consult.be/imgs/picto/132/Reportage_1.jpg' - }) - - expect(results[1]).toMatchObject({ - start: '2023-01-19T05:55:00.000Z', - stop: '2023-01-19T06:00:00.000Z', - title: 'Météo', - category: 'Météo', - image: 'https://dnsmptv-img.pragma-consult.be/imgs/picto/132/Meteo.jpg' - }) - - expect(results[8]).toMatchObject({ - start: '2023-01-19T08:00:00.000Z', - stop: '2023-01-19T08:05:00.000Z', - title: 'Le journal', - description: "L'information de la mi-journée avec des JT...", - category: 'Journal', - image: 'https://dnsmptv-img.pragma-consult.be/imgs/picto/132/journal.jpg' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')), - date - }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./mon-programme-tv.be.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-01-19', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '1873/programme-television-ln24', + xmltv_id: 'LN24.be' +} + +it('can generate valid url', () => { + expect(url({ date, channel })).toBe( + 'https://www.mon-programme-tv.be/chaine/19012023/1873/programme-television-ln24.html' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + const results = parser({ content, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2023-01-19T05:30:00.000Z', + stop: '2023-01-19T05:55:00.000Z', + title: 'LN Matin', + category: 'Magazine Actualité', + image: 'https://dnsmptv-img.pragma-consult.be/imgs/picto/132/Reportage_1.jpg' + }) + + expect(results[1]).toMatchObject({ + start: '2023-01-19T05:55:00.000Z', + stop: '2023-01-19T06:00:00.000Z', + title: 'Météo', + category: 'Météo', + image: 'https://dnsmptv-img.pragma-consult.be/imgs/picto/132/Meteo.jpg' + }) + + expect(results[8]).toMatchObject({ + start: '2023-01-19T08:00:00.000Z', + stop: '2023-01-19T08:05:00.000Z', + title: 'Le journal', + description: "L'information de la mi-journée avec des JT...", + category: 'Journal', + image: 'https://dnsmptv-img.pragma-consult.be/imgs/picto/132/journal.jpg' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')), + date + }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/movistarplus.es/movistarplus.es.config.js b/sites/movistarplus.es/movistarplus.es.config.js index c58b51901..ef94c0a91 100644 --- a/sites/movistarplus.es/movistarplus.es.config.js +++ b/sites/movistarplus.es/movistarplus.es.config.js @@ -1,97 +1,97 @@ -const axios = require('axios') -const cheerio = require('cheerio') -const dayjs = require('dayjs') - -module.exports = { - site: 'movistarplus.es', - days: 2, - url({ channel, date }) { - return `https://www.movistarplus.es/programacion-tv/${channel.site_id}/${date.format('YYYY-MM-DD')}` - }, - async parser({ content }) { - let programs = [] - let items = parseItems(content) - if (!items.length) return programs - - const $ = cheerio.load(content) - const programElements = $('div[id^="ele-"]').get() - - for (let i = 0; i < items.length; i++) { - const el = items[i] - let description = null - - if (programElements[i]) { - const programDiv = $(programElements[i]) - const programLink = programDiv.find('a').attr('href') - - if (programLink) { - const idMatch = programLink.match(/id=(\d+)/) - if (idMatch && idMatch[1]) { - description = await getProgramDescription(programLink).catch(() => null) - } - } - } - - programs.push({ - title: el.item.name, - description: description, - start: dayjs(el.item.startDate), - stop: dayjs(el.item.endDate) - }) - } - - return programs - }, - async channels() { - const html = await axios - .get('https://www.movistarplus.es/programacion-tv') - .then(r => r.data) - .catch(console.log) - - const $ = cheerio.load(html) - let scheme = $('script:contains(ItemList)').html() - scheme = JSON.parse(scheme) - - return scheme.itemListElement.map(el => { - const urlParts = el.item.url.split('/') - const site_id = urlParts.pop().toLowerCase() - - return { - lang: 'es', - name: el.item.name, - site_id - } - }) - } -} - -function parseItems(content) { - try { - const $ = cheerio.load(content) - let scheme = $('script:contains("@type": "ItemList")').html() - scheme = JSON.parse(scheme) - if (!scheme || !Array.isArray(scheme.itemListElement)) return [] - - return scheme.itemListElement - } catch { - return [] - } -} - -async function getProgramDescription(programUrl) { - try { - const response = await axios.get(programUrl, { - headers: { - 'Referer': 'https://www.movistarplus.es/programacion-tv/' - } - }) - - const $ = cheerio.load(response.data) - const description = $('.show-content .text p').first().text().trim() || null - - return description - } catch (error) { - console.error(`Error fetching description from ${programUrl}:`, error.message) - return null - } -} +const axios = require('axios') +const cheerio = require('cheerio') +const dayjs = require('dayjs') + +module.exports = { + site: 'movistarplus.es', + days: 2, + url({ channel, date }) { + return `https://www.movistarplus.es/programacion-tv/${channel.site_id}/${date.format('YYYY-MM-DD')}` + }, + async parser({ content }) { + let programs = [] + let items = parseItems(content) + if (!items.length) return programs + + const $ = cheerio.load(content) + const programElements = $('div[id^="ele-"]').get() + + for (let i = 0; i < items.length; i++) { + const el = items[i] + let description = null + + if (programElements[i]) { + const programDiv = $(programElements[i]) + const programLink = programDiv.find('a').attr('href') + + if (programLink) { + const idMatch = programLink.match(/id=(\d+)/) + if (idMatch && idMatch[1]) { + description = await getProgramDescription(programLink).catch(() => null) + } + } + } + + programs.push({ + title: el.item.name, + description: description, + start: dayjs(el.item.startDate), + stop: dayjs(el.item.endDate) + }) + } + + return programs + }, + async channels() { + const html = await axios + .get('https://www.movistarplus.es/programacion-tv') + .then(r => r.data) + .catch(console.log) + + const $ = cheerio.load(html) + let scheme = $('script:contains(ItemList)').html() + scheme = JSON.parse(scheme) + + return scheme.itemListElement.map(el => { + const urlParts = el.item.url.split('/') + const site_id = urlParts.pop().toLowerCase() + + return { + lang: 'es', + name: el.item.name, + site_id + } + }) + } +} + +function parseItems(content) { + try { + const $ = cheerio.load(content) + let scheme = $('script:contains("@type": "ItemList")').html() + scheme = JSON.parse(scheme) + if (!scheme || !Array.isArray(scheme.itemListElement)) return [] + + return scheme.itemListElement + } catch { + return [] + } +} + +async function getProgramDescription(programUrl) { + try { + const response = await axios.get(programUrl, { + headers: { + 'Referer': 'https://www.movistarplus.es/programacion-tv/' + } + }) + + const $ = cheerio.load(response.data) + const description = $('.show-content .text p').first().text().trim() || null + + return description + } catch (error) { + console.error(`Error fetching description from ${programUrl}:`, error.message) + return null + } +} diff --git a/sites/movistarplus.es/movistarplus.es.test.js b/sites/movistarplus.es/movistarplus.es.test.js index 2e288d15f..1f81dd55a 100644 --- a/sites/movistarplus.es/movistarplus.es.test.js +++ b/sites/movistarplus.es/movistarplus.es.test.js @@ -1,80 +1,80 @@ -const { parser, url } = require('./movistarplus.es.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) -const axios = require('axios') -jest.mock('axios') - -const date = dayjs.utc('2025-05-30', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'sexta', - xmltv_id: 'LaSexta.es' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://www.movistarplus.es/programacion-tv/sexta/2025-05-30' - ) -}) - -it('can parse response', async () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - - axios.get.mockImplementation(url => { - if ( - url === - 'https://www.movistarplus.es/entretenimiento/venta-prime-t1/ficha?tipo=E&id=3414523' - ) { - return Promise.resolve({ - data: fs.readFileSync(path.resolve(__dirname, '__data__/program1.html')) - }) - } else if ( - url === - 'https://www.movistarplus.es/deportes/programa/pokerstars-casino-1/ficha?tipo=E&id=2057641' - ) { - return Promise.resolve({ - data: fs.readFileSync(path.resolve(__dirname, '__data__/program2.html')) - }) - } else { - return Promise.resolve({ data: '' }) - } - }) - - let results = await parser({ content, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(23) - expect(results[0]).toMatchObject({ - start: '2025-05-30T03:15:00.000Z', - stop: '2025-05-30T04:25:00.000Z', - title: 'Venta Prime', - description: - 'Espacio de televenta.' - }) - expect(results[19]).toMatchObject({ - start: '2025-05-31T00:45:00.000Z', - stop: '2025-05-31T01:25:00.000Z', - title: 'Pokerstars casino', - description: - 'El programa trae cada día toda la emoción de su ruleta en vivo, Spin & Win, una versión exclusiva del clásico juego de casino.' - }) -}) - - -it('can handle empty guide', async () => { - const results = await parser({ - date, - channel, - content: '[]' - }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./movistarplus.es.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) +const axios = require('axios') +jest.mock('axios') + +const date = dayjs.utc('2025-05-30', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'sexta', + xmltv_id: 'LaSexta.es' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://www.movistarplus.es/programacion-tv/sexta/2025-05-30' + ) +}) + +it('can parse response', async () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + + axios.get.mockImplementation(url => { + if ( + url === + 'https://www.movistarplus.es/entretenimiento/venta-prime-t1/ficha?tipo=E&id=3414523' + ) { + return Promise.resolve({ + data: fs.readFileSync(path.resolve(__dirname, '__data__/program1.html')) + }) + } else if ( + url === + 'https://www.movistarplus.es/deportes/programa/pokerstars-casino-1/ficha?tipo=E&id=2057641' + ) { + return Promise.resolve({ + data: fs.readFileSync(path.resolve(__dirname, '__data__/program2.html')) + }) + } else { + return Promise.resolve({ data: '' }) + } + }) + + let results = await parser({ content, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(23) + expect(results[0]).toMatchObject({ + start: '2025-05-30T03:15:00.000Z', + stop: '2025-05-30T04:25:00.000Z', + title: 'Venta Prime', + description: + 'Espacio de televenta.' + }) + expect(results[19]).toMatchObject({ + start: '2025-05-31T00:45:00.000Z', + stop: '2025-05-31T01:25:00.000Z', + title: 'Pokerstars casino', + description: + 'El programa trae cada día toda la emoción de su ruleta en vivo, Spin & Win, una versión exclusiva del clásico juego de casino.' + }) +}) + + +it('can handle empty guide', async () => { + const results = await parser({ + date, + channel, + content: '[]' + }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/mts.rs/mts.rs.config.js b/sites/mts.rs/mts.rs.config.js index 0ad5215b0..aee11d40a 100644 --- a/sites/mts.rs/mts.rs.config.js +++ b/sites/mts.rs/mts.rs.config.js @@ -1,55 +1,55 @@ -const axios = require('axios') -const dayjs = require('dayjs') - -module.exports = { - site: 'mts.rs', - days: 2, - url({ date }) { - return `https://mts.rs/hybris/ecommerce/b2c/v1/products/search?sort=pozicija-rastuce&searchQueryContext=CHANNEL_PROGRAM&query=:pozicija-rastuce:tip-kanala-radio:TV kanali:channelProgramDates:${date.format( - 'YYYY-MM-DD' - )}&pageSize=10000` - }, - request: { - maxContentLength: 10000000 // 10 Mb - }, - parser({ content, channel }) { - const items = parseItems(content, channel) - - return items.map(item => { - return { - title: item.title, - category: item.category, - description: item.description, - image: item?.picture?.url || null, - start: dayjs(item.start), - stop: dayjs(item.end) - } - }) - }, - async channels() { - const data = await axios - .get(module.exports.url({ date: dayjs() })) - .then(r => r.data) - .catch(console.error) - - return data.products.map(channel => ({ - lang: 'bs', - name: channel.name, - site_id: encodeURIComponent(channel.code) - })) - } -} - -function parseItems(content, channel) { - try { - const data = JSON.parse(content) - if (!data || !Array.isArray(data.products)) return [] - - const channelData = data.products.find(c => c.code === channel.site_id) - if (!channelData || !Array.isArray(channelData.programs)) return [] - - return channelData.programs - } catch { - return [] - } -} +const axios = require('axios') +const dayjs = require('dayjs') + +module.exports = { + site: 'mts.rs', + days: 2, + url({ date }) { + return `https://mts.rs/hybris/ecommerce/b2c/v1/products/search?sort=pozicija-rastuce&searchQueryContext=CHANNEL_PROGRAM&query=:pozicija-rastuce:tip-kanala-radio:TV kanali:channelProgramDates:${date.format( + 'YYYY-MM-DD' + )}&pageSize=10000` + }, + request: { + maxContentLength: 10000000 // 10 Mb + }, + parser({ content, channel }) { + const items = parseItems(content, channel) + + return items.map(item => { + return { + title: item.title, + category: item.category, + description: item.description, + image: item?.picture?.url || null, + start: dayjs(item.start), + stop: dayjs(item.end) + } + }) + }, + async channels() { + const data = await axios + .get(module.exports.url({ date: dayjs() })) + .then(r => r.data) + .catch(console.error) + + return data.products.map(channel => ({ + lang: 'bs', + name: channel.name, + site_id: encodeURIComponent(channel.code) + })) + } +} + +function parseItems(content, channel) { + try { + const data = JSON.parse(content) + if (!data || !Array.isArray(data.products)) return [] + + const channelData = data.products.find(c => c.code === channel.site_id) + if (!channelData || !Array.isArray(channelData.programs)) return [] + + return channelData.programs + } catch { + return [] + } +} diff --git a/sites/mts.rs/mts.rs.test.js b/sites/mts.rs/mts.rs.test.js index 27d3d9707..24611dacd 100644 --- a/sites/mts.rs/mts.rs.test.js +++ b/sites/mts.rs/mts.rs.test.js @@ -1,59 +1,59 @@ -const { parser, url } = require('./mts.rs.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-01-23', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'rts_1_hd', - xmltv_id: 'RTS1HD.rs' -} - -it('can generate valid url', () => { - expect(url({ date })).toBe( - 'https://mts.rs/hybris/ecommerce/b2c/v1/products/search?sort=pozicija-rastuce&searchQueryContext=CHANNEL_PROGRAM&query=:pozicija-rastuce:tip-kanala-radio:TV kanali:channelProgramDates:2025-01-23&pageSize=10000' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const results = parser({ channel, content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(31) - expect(results[0]).toMatchObject({ - start: '2025-01-22T23:25:00.000Z', - stop: '2025-01-23T00:15:00.000Z', - title: 'Jeloustoun', - category: 'Tv-serijali', - image: - 'https://mediasb2c.mts.rs/medias/5-72517fcb4505f9d7809814598fed5ce6d84571a1-99415C04AED37264BC49C11115B94633.jpg?context=bWFzdGVyfHJvb3R8Nzc4MjN8aW1hZ2UvanBlZ3xhRFpsTDJoa01pODBOakF6T0RnME9UVTROVEU0TWk4MVh6Y3lOVEUzWm1OaU5EVXdOV1k1WkRjNE1EazRNVFExT1RobVpXUTFZMlUyWkRnME5UY3hZVEZmT1RrME1UVkRNRFJCUlVRek56STJORUpETkRsRE1URXhNVFZDT1RRMk16TXVhbkJufGUwZDIyMWU4MDIxZWVhZjY5MDY0ODQ0YjI5OWVjMGJjMDNlNWI3ZjMwNmE0MjYwMWJlMWQxNGFiMzNlMzU1NDE', - description: - 'Serija prati život Džona Datona, koga tumači oskarovac Kevin Kostner, koji mora da se bori sa spoljnim i unutrašnjim pretnjama kako bi zaštitio svoju porodicu, ranč i imanje. Smeštena u divlje prostranstvo Montane, serija istražuje složene moralne dileme, borbe za opstanak i porodične sukobe u modernom zapadnom okruženju. Sa prelepim pejzažima i napetim zapletima, Jeloustoun nudi priču o ljubavi, lojalnosti, moći i borbi za očuvanje tradicije. Kevin Kostner nije samo glumac u seriji, već i jedan od producenata. Njegovo bogato iskustvo u filmskoj industriji, uključujući režiju i produkciju, pomoglo je da Jeloustoun bude verodostojan i autentičan prikaz života na ranču. Serija je dobila silne nagrade, a među njima i Zlatni globus za najbolju televizijsku seriju (drama) 2021. godine, dok je Kevin Kostner je osvojio nagradu za Najboljeg glumca u dramskoj televizijskoj seriji, iste godine godine. Nekoliko puta je bila nominovana za nagradu Emi.' - }) - expect(results[30]).toMatchObject({ - start: '2025-01-23T23:30:00.000Z', - stop: '2025-01-24T00:20:00.000Z', - title: 'Jeloustoun', - category: 'Tv-serijali', - image: - 'https://mediasb2c.mts.rs/medias/5-72517fcb4505f9d7809814598fed5ce6d84571a1-99415C04AED37264BC49C11115B94633.jpg?context=bWFzdGVyfHJvb3R8Nzc4MjN8aW1hZ2UvanBlZ3xhRFpsTDJoa01pODBOakF6T0RnME9UVTROVEU0TWk4MVh6Y3lOVEUzWm1OaU5EVXdOV1k1WkRjNE1EazRNVFExT1RobVpXUTFZMlUyWkRnME5UY3hZVEZmT1RrME1UVkRNRFJCUlVRek56STJORUpETkRsRE1URXhNVFZDT1RRMk16TXVhbkJufGUwZDIyMWU4MDIxZWVhZjY5MDY0ODQ0YjI5OWVjMGJjMDNlNWI3ZjMwNmE0MjYwMWJlMWQxNGFiMzNlMzU1NDE', - description: - 'Serija prati život Džona Datona, koga tumači oskarovac Kevin Kostner, koji mora da se bori sa spoljnim i unutrašnjim pretnjama kako bi zaštitio svoju porodicu, ranč i imanje. Smeštena u divlje prostranstvo Montane, serija istražuje složene moralne dileme, borbe za opstanak i porodične sukobe u modernom zapadnom okruženju. Sa prelepim pejzažima i napetim zapletima, Jeloustoun nudi priču o ljubavi, lojalnosti, moći i borbi za očuvanje tradicije. Kevin Kostner nije samo glumac u seriji, već i jedan od producenata. Njegovo bogato iskustvo u filmskoj industriji, uključujući režiju i produkciju, pomoglo je da Jeloustoun bude verodostojan i autentičan prikaz života na ranču. Serija je dobila silne nagrade, a među njima i Zlatni globus za najbolju televizijsku seriju (drama) 2021. godine, dok je Kevin Kostner je osvojio nagradu za Najboljeg glumca u dramskoj televizijskoj seriji, iste godine godine. Nekoliko puta je bila nominovana za nagradu Emi.' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - channel, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./mts.rs.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-01-23', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'rts_1_hd', + xmltv_id: 'RTS1HD.rs' +} + +it('can generate valid url', () => { + expect(url({ date })).toBe( + 'https://mts.rs/hybris/ecommerce/b2c/v1/products/search?sort=pozicija-rastuce&searchQueryContext=CHANNEL_PROGRAM&query=:pozicija-rastuce:tip-kanala-radio:TV kanali:channelProgramDates:2025-01-23&pageSize=10000' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const results = parser({ channel, content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(31) + expect(results[0]).toMatchObject({ + start: '2025-01-22T23:25:00.000Z', + stop: '2025-01-23T00:15:00.000Z', + title: 'Jeloustoun', + category: 'Tv-serijali', + image: + 'https://mediasb2c.mts.rs/medias/5-72517fcb4505f9d7809814598fed5ce6d84571a1-99415C04AED37264BC49C11115B94633.jpg?context=bWFzdGVyfHJvb3R8Nzc4MjN8aW1hZ2UvanBlZ3xhRFpsTDJoa01pODBOakF6T0RnME9UVTROVEU0TWk4MVh6Y3lOVEUzWm1OaU5EVXdOV1k1WkRjNE1EazRNVFExT1RobVpXUTFZMlUyWkRnME5UY3hZVEZmT1RrME1UVkRNRFJCUlVRek56STJORUpETkRsRE1URXhNVFZDT1RRMk16TXVhbkJufGUwZDIyMWU4MDIxZWVhZjY5MDY0ODQ0YjI5OWVjMGJjMDNlNWI3ZjMwNmE0MjYwMWJlMWQxNGFiMzNlMzU1NDE', + description: + 'Serija prati život Džona Datona, koga tumači oskarovac Kevin Kostner, koji mora da se bori sa spoljnim i unutrašnjim pretnjama kako bi zaštitio svoju porodicu, ranč i imanje. Smeštena u divlje prostranstvo Montane, serija istražuje složene moralne dileme, borbe za opstanak i porodične sukobe u modernom zapadnom okruženju. Sa prelepim pejzažima i napetim zapletima, Jeloustoun nudi priču o ljubavi, lojalnosti, moći i borbi za očuvanje tradicije. Kevin Kostner nije samo glumac u seriji, već i jedan od producenata. Njegovo bogato iskustvo u filmskoj industriji, uključujući režiju i produkciju, pomoglo je da Jeloustoun bude verodostojan i autentičan prikaz života na ranču. Serija je dobila silne nagrade, a među njima i Zlatni globus za najbolju televizijsku seriju (drama) 2021. godine, dok je Kevin Kostner je osvojio nagradu za Najboljeg glumca u dramskoj televizijskoj seriji, iste godine godine. Nekoliko puta je bila nominovana za nagradu Emi.' + }) + expect(results[30]).toMatchObject({ + start: '2025-01-23T23:30:00.000Z', + stop: '2025-01-24T00:20:00.000Z', + title: 'Jeloustoun', + category: 'Tv-serijali', + image: + 'https://mediasb2c.mts.rs/medias/5-72517fcb4505f9d7809814598fed5ce6d84571a1-99415C04AED37264BC49C11115B94633.jpg?context=bWFzdGVyfHJvb3R8Nzc4MjN8aW1hZ2UvanBlZ3xhRFpsTDJoa01pODBOakF6T0RnME9UVTROVEU0TWk4MVh6Y3lOVEUzWm1OaU5EVXdOV1k1WkRjNE1EazRNVFExT1RobVpXUTFZMlUyWkRnME5UY3hZVEZmT1RrME1UVkRNRFJCUlVRek56STJORUpETkRsRE1URXhNVFZDT1RRMk16TXVhbkJufGUwZDIyMWU4MDIxZWVhZjY5MDY0ODQ0YjI5OWVjMGJjMDNlNWI3ZjMwNmE0MjYwMWJlMWQxNGFiMzNlMzU1NDE', + description: + 'Serija prati život Džona Datona, koga tumači oskarovac Kevin Kostner, koji mora da se bori sa spoljnim i unutrašnjim pretnjama kako bi zaštitio svoju porodicu, ranč i imanje. Smeštena u divlje prostranstvo Montane, serija istražuje složene moralne dileme, borbe za opstanak i porodične sukobe u modernom zapadnom okruženju. Sa prelepim pejzažima i napetim zapletima, Jeloustoun nudi priču o ljubavi, lojalnosti, moći i borbi za očuvanje tradicije. Kevin Kostner nije samo glumac u seriji, već i jedan od producenata. Njegovo bogato iskustvo u filmskoj industriji, uključujući režiju i produkciju, pomoglo je da Jeloustoun bude verodostojan i autentičan prikaz života na ranču. Serija je dobila silne nagrade, a među njima i Zlatni globus za najbolju televizijsku seriju (drama) 2021. godine, dok je Kevin Kostner je osvojio nagradu za Najboljeg glumca u dramskoj televizijskoj seriji, iste godine godine. Nekoliko puta je bila nominovana za nagradu Emi.' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + channel, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/mujtvprogram.cz/mujtvprogram.cz.config.js b/sites/mujtvprogram.cz/mujtvprogram.cz.config.js index 0a8ddb95c..34d2dc566 100644 --- a/sites/mujtvprogram.cz/mujtvprogram.cz.config.js +++ b/sites/mujtvprogram.cz/mujtvprogram.cz.config.js @@ -1,111 +1,111 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') -const convert = require('xml-js') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'mujtvprogram.cz', - days: 2, - url({ channel, date }) { - const diff = date.diff(dayjs.utc().startOf('d'), 'd') - return `https://services.mujtvprogram.cz/tvprogram2services/services/tvprogrammelist_mobile.php?channel_cid=${channel.site_id}&day=${diff}` - }, - parser({ content }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - programs.push({ - title: item.name._text, - start: parseTime(item.startDate._text), - stop: parseTime(item.endDate._text), - description: parseDescription(item), - category: parseCategory(item), - date: item.year._text || null, - director: parseList(item.directors), - actor: parseList(item.actors) - }) - }) - return programs - }, - async channels() { - const cheerio = require('cheerio') - const axios = require('axios') - - let channels = [] - - const categories = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - for (let category of categories) { - const params = new URLSearchParams() - params.append('localization', 1) - params.append('list_for_selector', 1) - params.append('category_kid', category) - - const data = await axios - .post( - 'https://services.mujtvprogram.cz/tvprogram2services/services/tvchannellist_mobile.php', - params, - { - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - } - } - ) - .then(r => r.data) - .catch(console.log) - - const $ = cheerio.load(data, { xmlMode: true }) - - $('channel').each((i, el) => { - let lang = $(el).find('lang').text() - if (lang === 'cz') lang = 'cs' - - channels.push({ - lang, - site_id: $(el).find('cid').text(), - name: $(el).find('name').first().text() - }) - }) - } - - return channels - } -} - -function parseItems(content) { - try { - const data = convert.xml2js(content, { - compact: true, - ignoreDeclaration: true, - ignoreAttributes: true - }) - if (!data) return [] - const programmes = data['tv-program-programmes'].programme - return programmes && Array.isArray(programmes) ? programmes : [] - } catch { - return [] - } -} -function parseDescription(item) { - if (item.longDescription) return item.longDescription._text - if (item.shortDescription) return item.shortDescription._text - return null -} - -function parseList(list) { - if (!list) return [] - if (!list._text) return [] - return typeof list._text === 'string' ? list._text.split(', ') : [] -} -function parseTime(time) { - return dayjs.tz(time, 'DD.MM.YYYY HH.mm', 'Europe/Prague') -} - -function parseCategory(item) { - if (!item['programme-type']) return null - return item['programme-type'].name._text -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const convert = require('xml-js') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'mujtvprogram.cz', + days: 2, + url({ channel, date }) { + const diff = date.diff(dayjs.utc().startOf('d'), 'd') + return `https://services.mujtvprogram.cz/tvprogram2services/services/tvprogrammelist_mobile.php?channel_cid=${channel.site_id}&day=${diff}` + }, + parser({ content }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + programs.push({ + title: item.name._text, + start: parseTime(item.startDate._text), + stop: parseTime(item.endDate._text), + description: parseDescription(item), + category: parseCategory(item), + date: item.year._text || null, + director: parseList(item.directors), + actor: parseList(item.actors) + }) + }) + return programs + }, + async channels() { + const cheerio = require('cheerio') + const axios = require('axios') + + let channels = [] + + const categories = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + for (let category of categories) { + const params = new URLSearchParams() + params.append('localization', 1) + params.append('list_for_selector', 1) + params.append('category_kid', category) + + const data = await axios + .post( + 'https://services.mujtvprogram.cz/tvprogram2services/services/tvchannellist_mobile.php', + params, + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + } + } + ) + .then(r => r.data) + .catch(console.log) + + const $ = cheerio.load(data, { xmlMode: true }) + + $('channel').each((i, el) => { + let lang = $(el).find('lang').text() + if (lang === 'cz') lang = 'cs' + + channels.push({ + lang, + site_id: $(el).find('cid').text(), + name: $(el).find('name').first().text() + }) + }) + } + + return channels + } +} + +function parseItems(content) { + try { + const data = convert.xml2js(content, { + compact: true, + ignoreDeclaration: true, + ignoreAttributes: true + }) + if (!data) return [] + const programmes = data['tv-program-programmes'].programme + return programmes && Array.isArray(programmes) ? programmes : [] + } catch { + return [] + } +} +function parseDescription(item) { + if (item.longDescription) return item.longDescription._text + if (item.shortDescription) return item.shortDescription._text + return null +} + +function parseList(list) { + if (!list) return [] + if (!list._text) return [] + return typeof list._text === 'string' ? list._text.split(', ') : [] +} +function parseTime(time) { + return dayjs.tz(time, 'DD.MM.YYYY HH.mm', 'Europe/Prague') +} + +function parseCategory(item) { + if (!item['programme-type']) return null + return item['programme-type'].name._text +} diff --git a/sites/mujtvprogram.cz/mujtvprogram.cz.test.js b/sites/mujtvprogram.cz/mujtvprogram.cz.test.js index 568e8b120..a83d95e0f 100644 --- a/sites/mujtvprogram.cz/mujtvprogram.cz.test.js +++ b/sites/mujtvprogram.cz/mujtvprogram.cz.test.js @@ -1,54 +1,54 @@ -const { parser, url } = require('./mujtvprogram.cz.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const channel = { - site_id: '1', - xmltv_id: 'CT1.cz' -} - -it('can generate valid url for today', () => { - const date = dayjs.utc().startOf('d') - expect(url({ channel, date })).toBe( - 'https://services.mujtvprogram.cz/tvprogram2services/services/tvprogrammelist_mobile.php?channel_cid=1&day=0' - ) -}) - -it('can generate valid url for tomorrow', () => { - const date = dayjs.utc().startOf('d').add(1, 'd') - expect(url({ channel, date })).toBe( - 'https://services.mujtvprogram.cz/tvprogram2services/services/tvprogrammelist_mobile.php?channel_cid=1&day=1' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - - let results = parser({ content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - expect(results[3]).toMatchObject({ - title: 'Čepice', - description: - 'Jarka (J. Bohdalová) vyčítá manželovi Jiřímu (F. Řehák), že jí nepomáhá při předvánočním úklidu. Vzápětí ale náhodou najde ve skříni ukrytou dámskou čepici a napadne ji, že jde o Jiřího dárek pro ni pod stromeček. Její chování se ihned změní. Jen muži naznačí, že by chtěla čepici jiné barvy. Manžel jí ovšem řekne, že čepici si u něj schoval kamarád Venca (M. Šulc). Zklamaná žena to prozradí Vencově manželce Božce (A. Tománková). Na Štědrý den však Božka najde pod stromečkem jen rtěnku...', - category: 'film', - date: '1983', - director: ['Mudra F.'], - actor: ['Bohdalová J.', 'Řehák F.', 'Šulc M.'], - start: '2022-12-23T08:00:00.000Z', - stop: '2022-12-23T08:20:00.000Z' - }) -}) - -it('can handle empty guide', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) - const result = parser(content, channel) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./mujtvprogram.cz.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const channel = { + site_id: '1', + xmltv_id: 'CT1.cz' +} + +it('can generate valid url for today', () => { + const date = dayjs.utc().startOf('d') + expect(url({ channel, date })).toBe( + 'https://services.mujtvprogram.cz/tvprogram2services/services/tvprogrammelist_mobile.php?channel_cid=1&day=0' + ) +}) + +it('can generate valid url for tomorrow', () => { + const date = dayjs.utc().startOf('d').add(1, 'd') + expect(url({ channel, date })).toBe( + 'https://services.mujtvprogram.cz/tvprogram2services/services/tvprogrammelist_mobile.php?channel_cid=1&day=1' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + + let results = parser({ content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + expect(results[3]).toMatchObject({ + title: 'Čepice', + description: + 'Jarka (J. Bohdalová) vyčítá manželovi Jiřímu (F. Řehák), že jí nepomáhá při předvánočním úklidu. Vzápětí ale náhodou najde ve skříni ukrytou dámskou čepici a napadne ji, že jde o Jiřího dárek pro ni pod stromeček. Její chování se ihned změní. Jen muži naznačí, že by chtěla čepici jiné barvy. Manžel jí ovšem řekne, že čepici si u něj schoval kamarád Venca (M. Šulc). Zklamaná žena to prozradí Vencově manželce Božce (A. Tománková). Na Štědrý den však Božka najde pod stromečkem jen rtěnku...', + category: 'film', + date: '1983', + director: ['Mudra F.'], + actor: ['Bohdalová J.', 'Řehák F.', 'Šulc M.'], + start: '2022-12-23T08:00:00.000Z', + stop: '2022-12-23T08:20:00.000Z' + }) +}) + +it('can handle empty guide', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) + const result = parser(content, channel) + expect(result).toMatchObject([]) +}) diff --git a/sites/musor.tv/musor.tv.config.js b/sites/musor.tv/musor.tv.config.js index d645a811c..d6fadb311 100644 --- a/sites/musor.tv/musor.tv.config.js +++ b/sites/musor.tv/musor.tv.config.js @@ -1,93 +1,93 @@ -const cheerio = require('cheerio') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(customParseFormat) - -const headers = { - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 OPR/115.0.0.0' -} - -module.exports = { - site: 'musor.tv', - days: 2, - request: { headers }, - url({ channel, date }) { - return dayjs.utc().isSame(date, 'd') - ? `https://musor.tv/mai/tvmusor/${channel.site_id}` - : `https://musor.tv/napi/tvmusor/${channel.site_id}/${date.format('YYYY.MM.DD')}` - }, - parser({ content }) { - const programs = [] - const [$, items] = parseItems(content) - items.forEach(item => { - const prev = programs[programs.length - 1] - const $item = $(item) - let start = parseStart($item) - if (prev) prev.stop = start - const stop = start.add(30, 'm') - programs.push({ - title: parseTitle($item), - description: parseDescription($item), - image: parseImage($item), - start, - stop - }) - }) - - return programs - }, - async channels() { - const html = await axios - .get('https://musor.tv/', { headers }) - .then(r => r.data) - .catch(console.error) - - const $ = cheerio.load(html) - const channels = $('body > div.big_content > div > nav > table > tbody > tr > td > a').toArray() - return channels - .map(item => { - const $item = $(item) - const url = $item.attr('href') - if (!url.startsWith('//musor.tv/mai/tvmusor/')) return null - const site_id = url.replace('//musor.tv/mai/tvmusor/', '') - return { - lang: 'hu', - site_id, - name: $item.text() - } - }) - .filter(i => i) - } -} - -function parseImage($item) { - const imgSrc = $item.find('div.smartpe_screenshot > img').attr('src') - - return imgSrc ? `https:${imgSrc}` : null -} - -function parseTitle($item) { - return $item.find('div:nth-child(2) > div > h3 > a').text().trim() -} - -function parseDescription($item) { - return $item.find('div:nth-child(5) > div > div').text().trim() -} - -function parseStart($item) { - let datetime = $item.find('div:nth-child(1) > div > div > div > div > time').attr('content') - if (!datetime) return null - - return dayjs.utc(datetime.replace('GMT', 'T'), 'YYYY-MM-DDTHH:mm:ss') -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return [$, $('div.multicolumndayprogarea > div.smartpe_progentry').toArray()] -} +const cheerio = require('cheerio') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(customParseFormat) + +const headers = { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 OPR/115.0.0.0' +} + +module.exports = { + site: 'musor.tv', + days: 2, + request: { headers }, + url({ channel, date }) { + return dayjs.utc().isSame(date, 'd') + ? `https://musor.tv/mai/tvmusor/${channel.site_id}` + : `https://musor.tv/napi/tvmusor/${channel.site_id}/${date.format('YYYY.MM.DD')}` + }, + parser({ content }) { + const programs = [] + const [$, items] = parseItems(content) + items.forEach(item => { + const prev = programs[programs.length - 1] + const $item = $(item) + let start = parseStart($item) + if (prev) prev.stop = start + const stop = start.add(30, 'm') + programs.push({ + title: parseTitle($item), + description: parseDescription($item), + image: parseImage($item), + start, + stop + }) + }) + + return programs + }, + async channels() { + const html = await axios + .get('https://musor.tv/', { headers }) + .then(r => r.data) + .catch(console.error) + + const $ = cheerio.load(html) + const channels = $('body > div.big_content > div > nav > table > tbody > tr > td > a').toArray() + return channels + .map(item => { + const $item = $(item) + const url = $item.attr('href') + if (!url.startsWith('//musor.tv/mai/tvmusor/')) return null + const site_id = url.replace('//musor.tv/mai/tvmusor/', '') + return { + lang: 'hu', + site_id, + name: $item.text() + } + }) + .filter(i => i) + } +} + +function parseImage($item) { + const imgSrc = $item.find('div.smartpe_screenshot > img').attr('src') + + return imgSrc ? `https:${imgSrc}` : null +} + +function parseTitle($item) { + return $item.find('div:nth-child(2) > div > h3 > a').text().trim() +} + +function parseDescription($item) { + return $item.find('div:nth-child(5) > div > div').text().trim() +} + +function parseStart($item) { + let datetime = $item.find('div:nth-child(1) > div > div > div > div > time').attr('content') + if (!datetime) return null + + return dayjs.utc(datetime.replace('GMT', 'T'), 'YYYY-MM-DDTHH:mm:ss') +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return [$, $('div.multicolumndayprogarea > div.smartpe_progentry').toArray()] +} diff --git a/sites/musor.tv/musor.tv.test.js b/sites/musor.tv/musor.tv.test.js index 98bbd4be5..e709284ea 100644 --- a/sites/musor.tv/musor.tv.test.js +++ b/sites/musor.tv/musor.tv.test.js @@ -1,57 +1,57 @@ -const { parser, url } = require('./musor.tv.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-11-19', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'HATOS_CSATORNA', - xmltv_id: 'Hatoscsatorna.hu' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe('https://musor.tv/napi/tvmusor/HATOS_CSATORNA/2022.11.19') -}) - -it('can generate valid url for today', () => { - const today = dayjs.utc().startOf('d') - - expect(url({ channel, date: today })).toBe('https://musor.tv/mai/tvmusor/HATOS_CSATORNA') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - const results = parser({ content, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2022-11-19T23:00:00.000Z', - stop: '2022-11-19T23:30:00.000Z', - title: 'Egészségtér', - description: - 'Egészségtér címmel új természetgyógyászattal foglalkozó magazinműsor indult hetente fél órás időtartamban a hatoscsatornán. A műsor derűs, objektív hangvételével és szakmailag magas színvonalú ismeretterjesztő jellegével az e' - }) - - expect(results[1]).toMatchObject({ - start: '2022-11-19T23:30:00.000Z', - stop: '2022-11-20T00:00:00.000Z', - title: 'Tradíció Klipek', - description: 'Tradíció Klipek Birinyi József néprajzi, vallási, népzenei, népszokás filmjeiből.' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - content: '' - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./musor.tv.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-11-19', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'HATOS_CSATORNA', + xmltv_id: 'Hatoscsatorna.hu' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe('https://musor.tv/napi/tvmusor/HATOS_CSATORNA/2022.11.19') +}) + +it('can generate valid url for today', () => { + const today = dayjs.utc().startOf('d') + + expect(url({ channel, date: today })).toBe('https://musor.tv/mai/tvmusor/HATOS_CSATORNA') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + const results = parser({ content, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2022-11-19T23:00:00.000Z', + stop: '2022-11-19T23:30:00.000Z', + title: 'Egészségtér', + description: + 'Egészségtér címmel új természetgyógyászattal foglalkozó magazinműsor indult hetente fél órás időtartamban a hatoscsatornán. A műsor derűs, objektív hangvételével és szakmailag magas színvonalú ismeretterjesztő jellegével az e' + }) + + expect(results[1]).toMatchObject({ + start: '2022-11-19T23:30:00.000Z', + stop: '2022-11-20T00:00:00.000Z', + title: 'Tradíció Klipek', + description: 'Tradíció Klipek Birinyi József néprajzi, vallási, népzenei, népszokás filmjeiből.' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + content: '' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/mysky.com.ph/mysky.com.ph.config.js b/sites/mysky.com.ph/mysky.com.ph.config.js index c04df7bd6..04c4efe79 100644 --- a/sites/mysky.com.ph/mysky.com.ph.config.js +++ b/sites/mysky.com.ph/mysky.com.ph.config.js @@ -1,63 +1,63 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'mysky.com.ph', - days: 2, - url: 'https://skyepg.mysky.com.ph/Main/getEventsbyType', - request: { - cache: { - ttl: 60 * 60 * 1000 // 1 hour - } - }, - parser: function ({ content, channel, date }) { - let programs = [] - const items = parseItems(content, channel, date) - items.forEach(item => { - programs.push({ - title: item.name, - description: item.userData.description, - start: parseStart(item), - stop: parseStop(item) - }) - }) - - return programs - }, - async channels() { - const items = await axios - .get('https://skyepg.mysky.com.ph/Main/getEventsbyType') - .then(r => r.data.location) - .catch(console.log) - - return items.map(item => ({ - lang: 'en', - site_id: item.id, - name: item.name - })) - } -} - -function parseStart(item) { - return dayjs.tz(item.start, 'YYYY/MM/DD HH:mm', 'Asia/Manila') -} - -function parseStop(item) { - return dayjs.tz(item.end, 'YYYY/MM/DD HH:mm', 'Asia/Manila') -} - -function parseItems(content, channel, date) { - if (!content) return [] - const data = JSON.parse(content) - if (!data || !Array.isArray(data.events)) return [] - const d = date.format('YYYY/MM/DD') - - return data.events.filter(i => i.location == channel.site_id && i.start.includes(d)) -} +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'mysky.com.ph', + days: 2, + url: 'https://skyepg.mysky.com.ph/Main/getEventsbyType', + request: { + cache: { + ttl: 60 * 60 * 1000 // 1 hour + } + }, + parser: function ({ content, channel, date }) { + let programs = [] + const items = parseItems(content, channel, date) + items.forEach(item => { + programs.push({ + title: item.name, + description: item.userData.description, + start: parseStart(item), + stop: parseStop(item) + }) + }) + + return programs + }, + async channels() { + const items = await axios + .get('https://skyepg.mysky.com.ph/Main/getEventsbyType') + .then(r => r.data.location) + .catch(console.log) + + return items.map(item => ({ + lang: 'en', + site_id: item.id, + name: item.name + })) + } +} + +function parseStart(item) { + return dayjs.tz(item.start, 'YYYY/MM/DD HH:mm', 'Asia/Manila') +} + +function parseStop(item) { + return dayjs.tz(item.end, 'YYYY/MM/DD HH:mm', 'Asia/Manila') +} + +function parseItems(content, channel, date) { + if (!content) return [] + const data = JSON.parse(content) + if (!data || !Array.isArray(data.events)) return [] + const d = date.format('YYYY/MM/DD') + + return data.events.filter(i => i.location == channel.site_id && i.start.includes(d)) +} diff --git a/sites/mytelly.co.uk/mytelly.co.uk.config.js b/sites/mytelly.co.uk/mytelly.co.uk.config.js index dbfd58e11..26afdab58 100644 --- a/sites/mytelly.co.uk/mytelly.co.uk.config.js +++ b/sites/mytelly.co.uk/mytelly.co.uk.config.js @@ -1,211 +1,211 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') -const doFetch = require('@ntlab/sfetch') -const debug = require('debug')('site:mytelly.co.uk') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -doFetch.setDebugger(debug) - -const detailedGuide = true -const tz = 'Europe/London' - -module.exports = { - site: 'mytelly.co.uk', - days: 2, - request: { - headers: { - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 OPR/117.0.0.0' - } - }, - url({ date, channel }) { - return `https://www.mytelly.co.uk/tv-guide/listings/channel/${ - channel.site_id - }.html?dt=${date.format('YYYY-MM-DD')}` - }, - async parser({ content, date }) { - const programs = [] - - if (content) { - const queues = [] - const $ = cheerio.load(content) - - $('table.table > tbody > tr') - .toArray() - .forEach(el => { - const td = $(el).find('td:eq(1)') - const title = td.find('h5 a') - if (detailedGuide) { - queues.push({ url: title.attr('href'), params: module.exports.request }) - } else { - const subtitle = td.find('h6') - const time = $(el).find('td:eq(0)') - let start = parseTime(date, time.text().trim()) - const prev = programs[programs.length - 1] - if (prev) { - if (start.isBefore(prev.start)) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.add(30, 'm') - programs.push({ - title: parseText(title), - subTitle: parseText(subtitle), - start, - stop - }) - } - }) - - if (queues.length) { - await doFetch(queues, (url, res) => { - const $ = cheerio.load(res) - const time = $('center > h5 > b').text() - const title = parseText($('.inner-heading.sub h2')) - const subTitle = parseText($('.tab-pane > h5 > strong')) - const description = parseText($('.tab-pane > .tvbody > p')) - const image = $('.program-media-image img').attr('src') - const category = $('.schedule-attributes-genres span') - .toArray() - .map(el => $(el).text()) - const casts = $('.single-cast-head:not([id])') - .toArray() - .map(el => { - const cast = { name: parseText($(el).find('a')) } - const [, role] = $(el) - .text() - .match(/\((.*)\)/) || [null, null] - if (role) { - cast.role = role - } - return cast - }) - const [start, stop] = parseStartStop(date, time) - let season, episode - if (subTitle) { - const [, ses, epi] = subTitle.match(/Season (\d+), Episode (\d+)/) || [null, null] - if (ses) { - season = parseInt(ses) - } - if (epi) { - episode = parseInt(epi) - } - } - programs.push({ - title, - subTitle, - description, - image, - category, - season, - episode, - actor: casts.filter(c => c.role === 'Actor').map(c => c.name), - director: casts.filter(c => c.role === 'Director').map(c => c.name), - presenter: casts.filter(c => c.role === 'Presenter').map(c => c.name), - start, - stop - }) - }) - } - } - - return programs - }, - async channels() { - const channels = {} - const queues = [{ t: 'p', url: 'https://www.mytelly.co.uk/getform', params: this.request }] - await doFetch(queues, (queue, res) => { - // process form -> provider - if (queue.t === 'p') { - const $ = cheerio.load(res) - $('#guide_provider option') - .toArray() - .forEach(el => { - const opt = $(el) - const provider = opt.attr('value') - queues.push({ - t: 'r', - url: 'https://www.mytelly.co.uk/getregions', - params: { ...this.request, provider } - }) - }) - } - // process provider -> region - if (queue.t === 'r') { - const now = dayjs() - for (const r of Object.values(res)) { - const params = { - provider: queue.params.provider, - region: r.title, - TVperiod: 'Night', - date: now.format('YYYY-MM-DD'), - st: 0, - u_time: now.format('HHmm'), - is_mobile: 1 - } - queues.push({ - t: 's', - method: 'post', - url: 'https://www.mytelly.co.uk/tv-guide/schedule', - params: { ...this.request, data: params } - }) - } - } - // process schedule -> channels - if (queue.t === 's') { - const $ = cheerio.load(res) - $('.channelname').each((i, el) => { - const name = $(el).find('center > a:eq(1)').text() - const url = $(el).find('center > a:eq(1)').attr('href') - const [, number, slug] = url.match(/\/(\d+)\/(.*)\.html$/) - const site_id = `${number}/${slug}` - if (channels[site_id] === undefined) { - channels[site_id] = { - lang: 'en', - site_id, - name - } - } - }) - } - }) - - return Object.values(channels) - } -} - -function parseStartStop(date, time) { - const [s, e] = time.split(' - ') - const start = parseTime(date, s) - let stop = parseTime(date, e) - if (stop.isBefore(start)) { - stop = stop.add(1, 'd') - } - - return [start, stop] -} - -function parseTime(date, time) { - return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD H:mm a', tz) -} - -function parseText($item) { - let text = $item.text().replace(/\t/g, '').replace(/\n/g, ' ').trim() - while (true) { - if (text.match(/\s\s/)) { - text = text.replace(/\s\s/g, ' ') - continue - } - break - } - - return text -} +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const doFetch = require('@ntlab/sfetch') +const debug = require('debug')('site:mytelly.co.uk') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +doFetch.setDebugger(debug) + +const detailedGuide = true +const tz = 'Europe/London' + +module.exports = { + site: 'mytelly.co.uk', + days: 2, + request: { + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 OPR/117.0.0.0' + } + }, + url({ date, channel }) { + return `https://www.mytelly.co.uk/tv-guide/listings/channel/${ + channel.site_id + }.html?dt=${date.format('YYYY-MM-DD')}` + }, + async parser({ content, date }) { + const programs = [] + + if (content) { + const queues = [] + const $ = cheerio.load(content) + + $('table.table > tbody > tr') + .toArray() + .forEach(el => { + const td = $(el).find('td:eq(1)') + const title = td.find('h5 a') + if (detailedGuide) { + queues.push({ url: title.attr('href'), params: module.exports.request }) + } else { + const subtitle = td.find('h6') + const time = $(el).find('td:eq(0)') + let start = parseTime(date, time.text().trim()) + const prev = programs[programs.length - 1] + if (prev) { + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.add(30, 'm') + programs.push({ + title: parseText(title), + subTitle: parseText(subtitle), + start, + stop + }) + } + }) + + if (queues.length) { + await doFetch(queues, (url, res) => { + const $ = cheerio.load(res) + const time = $('center > h5 > b').text() + const title = parseText($('.inner-heading.sub h2')) + const subTitle = parseText($('.tab-pane > h5 > strong')) + const description = parseText($('.tab-pane > .tvbody > p')) + const image = $('.program-media-image img').attr('src') + const category = $('.schedule-attributes-genres span') + .toArray() + .map(el => $(el).text()) + const casts = $('.single-cast-head:not([id])') + .toArray() + .map(el => { + const cast = { name: parseText($(el).find('a')) } + const [, role] = $(el) + .text() + .match(/\((.*)\)/) || [null, null] + if (role) { + cast.role = role + } + return cast + }) + const [start, stop] = parseStartStop(date, time) + let season, episode + if (subTitle) { + const [, ses, epi] = subTitle.match(/Season (\d+), Episode (\d+)/) || [null, null] + if (ses) { + season = parseInt(ses) + } + if (epi) { + episode = parseInt(epi) + } + } + programs.push({ + title, + subTitle, + description, + image, + category, + season, + episode, + actor: casts.filter(c => c.role === 'Actor').map(c => c.name), + director: casts.filter(c => c.role === 'Director').map(c => c.name), + presenter: casts.filter(c => c.role === 'Presenter').map(c => c.name), + start, + stop + }) + }) + } + } + + return programs + }, + async channels() { + const channels = {} + const queues = [{ t: 'p', url: 'https://www.mytelly.co.uk/getform', params: this.request }] + await doFetch(queues, (queue, res) => { + // process form -> provider + if (queue.t === 'p') { + const $ = cheerio.load(res) + $('#guide_provider option') + .toArray() + .forEach(el => { + const opt = $(el) + const provider = opt.attr('value') + queues.push({ + t: 'r', + url: 'https://www.mytelly.co.uk/getregions', + params: { ...this.request, provider } + }) + }) + } + // process provider -> region + if (queue.t === 'r') { + const now = dayjs() + for (const r of Object.values(res)) { + const params = { + provider: queue.params.provider, + region: r.title, + TVperiod: 'Night', + date: now.format('YYYY-MM-DD'), + st: 0, + u_time: now.format('HHmm'), + is_mobile: 1 + } + queues.push({ + t: 's', + method: 'post', + url: 'https://www.mytelly.co.uk/tv-guide/schedule', + params: { ...this.request, data: params } + }) + } + } + // process schedule -> channels + if (queue.t === 's') { + const $ = cheerio.load(res) + $('.channelname').each((i, el) => { + const name = $(el).find('center > a:eq(1)').text() + const url = $(el).find('center > a:eq(1)').attr('href') + const [, number, slug] = url.match(/\/(\d+)\/(.*)\.html$/) + const site_id = `${number}/${slug}` + if (channels[site_id] === undefined) { + channels[site_id] = { + lang: 'en', + site_id, + name + } + } + }) + } + }) + + return Object.values(channels) + } +} + +function parseStartStop(date, time) { + const [s, e] = time.split(' - ') + const start = parseTime(date, s) + let stop = parseTime(date, e) + if (stop.isBefore(start)) { + stop = stop.add(1, 'd') + } + + return [start, stop] +} + +function parseTime(date, time) { + return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD H:mm a', tz) +} + +function parseText($item) { + let text = $item.text().replace(/\t/g, '').replace(/\n/g, ' ').trim() + while (true) { + if (text.match(/\s\s/)) { + text = text.replace(/\s\s/g, ' ') + continue + } + break + } + + return text +} diff --git a/sites/mytelly.co.uk/mytelly.co.uk.test.js b/sites/mytelly.co.uk/mytelly.co.uk.test.js index 6199a416f..ff2856c4c 100644 --- a/sites/mytelly.co.uk/mytelly.co.uk.test.js +++ b/sites/mytelly.co.uk/mytelly.co.uk.test.js @@ -1,88 +1,88 @@ -const { parser, url } = require('./mytelly.co.uk.config.js') -const axios = require('axios') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -const date = dayjs.utc('2024-12-07', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '713/bbc-one-london', - xmltv_id: 'BBCOneLondon.uk' -} - -axios.get.mockImplementation(url => { - if ( - url === - 'https://www.mytelly.co.uk/tv-guide/listings/programme?cid=713&pid=1906433&tm=2024-12-07+00%3A00%3A00' - ) { - return Promise.resolve({ - data: fs.readFileSync(path.join(__dirname, '__data__', 'programme.html')) - }) - } - if ( - url === - 'https://www.mytelly.co.uk/tv-guide/listings/programme?cid=713&pid=5656624&tm=2024-12-07+23%3A35%3A00' - ) { - return Promise.resolve({ - data: fs.readFileSync(path.join(__dirname, '__data__', 'programme2.html')) - }) - } - - return Promise.resolve({ data: '' }) -}) - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://www.mytelly.co.uk/tv-guide/listings/channel/713/bbc-one-london.html?dt=2024-12-07' - ) -}) - -it('can parse response', async () => { - const content = fs.readFileSync(path.join(__dirname, '__data__', 'content.html')) - const results = (await parser({ content, channel, date })).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(2) - expect(results[0]).toMatchObject({ - start: '2024-12-07T00:00:00.000Z', - stop: '2024-12-07T02:05:00.000Z', - title: 'Captain Phillips', - description: - 'An American cargo ship sets a dangerous course around the coast of Somalia, while inland, four men are pressed into service as pirates by the local warlords. The captain is taken hostage when the raiding party hijacks the vessel, resulting in a tense five-day crisis. Fact-based thriller, starring Tom Hanks and Barkhad Abdi', - image: - 'https://d16ia5iwuvax6y.cloudfront.net/uk-prog-images/c44ce7b0d3ae602c0c93ece5af140815.jpg?k=VeeNdUjml3bSHdlZ0OXbGLy%2BmsLdYPwTV6iAxGkzq4dsylOCGGE7OWlqwSWt0cd0Qtrin4DkEMC0Zzdp8ZeNk2vNIQzjMF0DG0h3IeTR5NM%3D', - category: ['Factual', 'Movie/Drama', 'Thriller'] - }) - expect(results[1]).toMatchObject({ - start: '2024-12-07T23:35:00.000Z', - stop: '2024-12-08T00:40:00.000Z', - title: 'The Rap Game UK', - subTitle: 'Past and Pressure Season 6, Episode 5', - description: - 'The artists are tasked with writing a song about their heritage. For some, the pressure of the competition proves too much for them to match. In their final challenge, they are put face to face with industry experts who grill them about their plans after the competition. Some impress, while others leave the mentors confused', - image: - 'https://d16ia5iwuvax6y.cloudfront.net/uk-prog-images/2039278182b27cc279570b9ab9b89379.jpg?k=VeeNdUjml3bSHdlZ0OXbGLy%2BmsLdYPwTV6iAxGkzq4cDhR7jXTNFW3tgwQCdOPUobhXwlT81mIsqOe93HPusDG6tw1aoeYOgafojtynNWxc%3D', - category: ['Challenge/Reality Show', 'Show/Game Show'], - season: 6, - episode: 5 - }) -}) - -it('can handle empty guide', async () => { - const result = await parser({ - date, - channel, - content: '' - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./mytelly.co.uk.config.js') +const axios = require('axios') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.utc('2024-12-07', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '713/bbc-one-london', + xmltv_id: 'BBCOneLondon.uk' +} + +axios.get.mockImplementation(url => { + if ( + url === + 'https://www.mytelly.co.uk/tv-guide/listings/programme?cid=713&pid=1906433&tm=2024-12-07+00%3A00%3A00' + ) { + return Promise.resolve({ + data: fs.readFileSync(path.join(__dirname, '__data__', 'programme.html')) + }) + } + if ( + url === + 'https://www.mytelly.co.uk/tv-guide/listings/programme?cid=713&pid=5656624&tm=2024-12-07+23%3A35%3A00' + ) { + return Promise.resolve({ + data: fs.readFileSync(path.join(__dirname, '__data__', 'programme2.html')) + }) + } + + return Promise.resolve({ data: '' }) +}) + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://www.mytelly.co.uk/tv-guide/listings/channel/713/bbc-one-london.html?dt=2024-12-07' + ) +}) + +it('can parse response', async () => { + const content = fs.readFileSync(path.join(__dirname, '__data__', 'content.html')) + const results = (await parser({ content, channel, date })).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(2) + expect(results[0]).toMatchObject({ + start: '2024-12-07T00:00:00.000Z', + stop: '2024-12-07T02:05:00.000Z', + title: 'Captain Phillips', + description: + 'An American cargo ship sets a dangerous course around the coast of Somalia, while inland, four men are pressed into service as pirates by the local warlords. The captain is taken hostage when the raiding party hijacks the vessel, resulting in a tense five-day crisis. Fact-based thriller, starring Tom Hanks and Barkhad Abdi', + image: + 'https://d16ia5iwuvax6y.cloudfront.net/uk-prog-images/c44ce7b0d3ae602c0c93ece5af140815.jpg?k=VeeNdUjml3bSHdlZ0OXbGLy%2BmsLdYPwTV6iAxGkzq4dsylOCGGE7OWlqwSWt0cd0Qtrin4DkEMC0Zzdp8ZeNk2vNIQzjMF0DG0h3IeTR5NM%3D', + category: ['Factual', 'Movie/Drama', 'Thriller'] + }) + expect(results[1]).toMatchObject({ + start: '2024-12-07T23:35:00.000Z', + stop: '2024-12-08T00:40:00.000Z', + title: 'The Rap Game UK', + subTitle: 'Past and Pressure Season 6, Episode 5', + description: + 'The artists are tasked with writing a song about their heritage. For some, the pressure of the competition proves too much for them to match. In their final challenge, they are put face to face with industry experts who grill them about their plans after the competition. Some impress, while others leave the mentors confused', + image: + 'https://d16ia5iwuvax6y.cloudfront.net/uk-prog-images/2039278182b27cc279570b9ab9b89379.jpg?k=VeeNdUjml3bSHdlZ0OXbGLy%2BmsLdYPwTV6iAxGkzq4cDhR7jXTNFW3tgwQCdOPUobhXwlT81mIsqOe93HPusDG6tw1aoeYOgafojtynNWxc%3D', + category: ['Challenge/Reality Show', 'Show/Game Show'], + season: 6, + episode: 5 + }) +}) + +it('can handle empty guide', async () => { + const result = await parser({ + date, + channel, + content: '' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/mytvsuper.com/mytvsuper.com.config.js b/sites/mytvsuper.com/mytvsuper.com.config.js index 7d3bb17fd..8a73c0e98 100644 --- a/sites/mytvsuper.com/mytvsuper.com.config.js +++ b/sites/mytvsuper.com/mytvsuper.com.config.js @@ -1,82 +1,82 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') - -dayjs.extend(utc) -dayjs.extend(timezone) - -const API_ENDPOINT = 'https://content-api.mytvsuper.com/v1' - -module.exports = { - site: 'mytvsuper.com', - days: 2, - request: { - cache: { - ttl: 60 * 60 * 1000 // 1h - } - }, - url: function ({ channel, date }) { - return `${API_ENDPOINT}/epg?network_code=${channel.site_id}&from=${date.format( - 'YYYYMMDD' - )}&to=${date.format('YYYYMMDD')}&platform=web` - }, - parser({ content, channel, date }) { - const programs = [] - const items = parseItems(content, date) - for (let item of items) { - const prev = programs[programs.length - 1] - const start = parseStart(item) - const stop = start.add(30, 'm') - if (prev) { - prev.stop = start - } - programs.push({ - title: parseTitle(item, channel), - description: parseDescription(item, channel), - episode: parseInt(item.episode_no), - start: start, - stop: stop - }) - } - - return programs - }, - async channels({ lang }) { - const data = await axios - .get(`${API_ENDPOINT}/channel/list?platform=web`) - .then(r => r.data) - .catch(console.error) - - return data.channels.map(c => { - const name = lang === 'en' ? c.name_en : c.name_tc - - return { - site_id: c.network_code, - name, - lang - } - }) - } -} - -function parseTitle(item, channel) { - return channel.lang === 'en' ? item.programme_title_en : item.programme_title_tc -} - -function parseDescription(item, channel) { - return channel.lang === 'en' ? item.episode_synopsis_en : item.episode_synopsis_tc -} - -function parseStart(item) { - return dayjs.tz(item.start_datetime, 'Asia/Hong_Kong') -} - -function parseItems(content, date) { - const data = JSON.parse(content) - if (!Array.isArray(data) || !data.length || !Array.isArray(data[0].item)) return [] - const dayData = data[0].item.find(i => i.date === date.format('YYYY-MM-DD')) - if (!dayData || !Array.isArray(dayData.epg)) return [] - - return dayData.epg -} +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') + +dayjs.extend(utc) +dayjs.extend(timezone) + +const API_ENDPOINT = 'https://content-api.mytvsuper.com/v1' + +module.exports = { + site: 'mytvsuper.com', + days: 2, + request: { + cache: { + ttl: 60 * 60 * 1000 // 1h + } + }, + url: function ({ channel, date }) { + return `${API_ENDPOINT}/epg?network_code=${channel.site_id}&from=${date.format( + 'YYYYMMDD' + )}&to=${date.format('YYYYMMDD')}&platform=web` + }, + parser({ content, channel, date }) { + const programs = [] + const items = parseItems(content, date) + for (let item of items) { + const prev = programs[programs.length - 1] + const start = parseStart(item) + const stop = start.add(30, 'm') + if (prev) { + prev.stop = start + } + programs.push({ + title: parseTitle(item, channel), + description: parseDescription(item, channel), + episode: parseInt(item.episode_no), + start: start, + stop: stop + }) + } + + return programs + }, + async channels({ lang }) { + const data = await axios + .get(`${API_ENDPOINT}/channel/list?platform=web`) + .then(r => r.data) + .catch(console.error) + + return data.channels.map(c => { + const name = lang === 'en' ? c.name_en : c.name_tc + + return { + site_id: c.network_code, + name, + lang + } + }) + } +} + +function parseTitle(item, channel) { + return channel.lang === 'en' ? item.programme_title_en : item.programme_title_tc +} + +function parseDescription(item, channel) { + return channel.lang === 'en' ? item.episode_synopsis_en : item.episode_synopsis_tc +} + +function parseStart(item) { + return dayjs.tz(item.start_datetime, 'Asia/Hong_Kong') +} + +function parseItems(content, date) { + const data = JSON.parse(content) + if (!Array.isArray(data) || !data.length || !Array.isArray(data[0].item)) return [] + const dayData = data[0].item.find(i => i.date === date.format('YYYY-MM-DD')) + if (!dayData || !Array.isArray(dayData.epg)) return [] + + return dayData.epg +} diff --git a/sites/mytvsuper.com/mytvsuper.com.test.js b/sites/mytvsuper.com/mytvsuper.com.test.js index 767735bfd..c7a3abb05 100644 --- a/sites/mytvsuper.com/mytvsuper.com.test.js +++ b/sites/mytvsuper.com/mytvsuper.com.test.js @@ -1,68 +1,68 @@ -const { parser, url } = require('./mytvsuper.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -const date = dayjs.utc('2022-11-15', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'B', - xmltv_id: 'J2.hk', - lang: 'zh' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://content-api.mytvsuper.com/v1/epg?network_code=B&from=20221115&to=20221115&platform=web' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - let results = parser({ content, channel, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2022-11-14T22:00:00.000Z', - stop: '2022-11-14T23:00:00.000Z', - title: '想見你#3[粵/普][PG]', - description: - '韻如因父母離婚都不要自己而跑出家門,遇到子維,兩人互吐心事。雨萱順著照片上的唱片行線索,找到一家同名咖啡店,從文磊處得知照片中人是已經過世的韻如,從而推測那個男生也不是詮勝,但她內心反而更加痛苦。', - episode: 1000003 - }) -}) - -it('can parse response in English', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const channelEN = { ...channel, lang: 'en' } - let results = parser({ content, channel: channelEN, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2022-11-14T22:00:00.000Z', - stop: '2022-11-14T23:00:00.000Z', - title: 'Someday or One Day#3[Can/Man][PG]', - description: 'Description', - episode: 1000003 - }) -}) - -it('can handle empty guide', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - const results = parser({ date, channel, content }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./mytvsuper.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.utc('2022-11-15', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'B', + xmltv_id: 'J2.hk', + lang: 'zh' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://content-api.mytvsuper.com/v1/epg?network_code=B&from=20221115&to=20221115&platform=web' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + let results = parser({ content, channel, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2022-11-14T22:00:00.000Z', + stop: '2022-11-14T23:00:00.000Z', + title: '想見你#3[粵/普][PG]', + description: + '韻如因父母離婚都不要自己而跑出家門,遇到子維,兩人互吐心事。雨萱順著照片上的唱片行線索,找到一家同名咖啡店,從文磊處得知照片中人是已經過世的韻如,從而推測那個男生也不是詮勝,但她內心反而更加痛苦。', + episode: 1000003 + }) +}) + +it('can parse response in English', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const channelEN = { ...channel, lang: 'en' } + let results = parser({ content, channel: channelEN, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2022-11-14T22:00:00.000Z', + stop: '2022-11-14T23:00:00.000Z', + title: 'Someday or One Day#3[Can/Man][PG]', + description: 'Description', + episode: 1000003 + }) +}) + +it('can handle empty guide', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + const results = parser({ date, channel, content }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/neo.io/neo.io.config.js b/sites/neo.io/neo.io.config.js index bea58fcac..e0c5ae202 100644 --- a/sites/neo.io/neo.io.config.js +++ b/sites/neo.io/neo.io.config.js @@ -1,80 +1,80 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'neo.io', - timezone: 'Europe/Ljubljana', - days: 5, - url() { - return 'https://stargate.telekom.si/api/titan.tv.WebEpg/GetWebEpgData' - }, - request: { - method: 'POST', - headers: { - Host: 'stargate.telekom.si', - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0', - Accept: 'application/json, text/plain, */*', - 'Accept-Language': 'nl,en-US;q=0.7,en;q=0.3', - 'Content-Type': 'application/json', - 'X-AppLayout': '1', - 'x-language': 'sl', - Origin: 'https://neo.io', - 'Sec-Fetch-Dest': 'empty', - 'Sec-Fetch-Mode': 'cors', - 'Sec-Fetch-Site': 'cross-site', - 'Sec-GPC': '1', - Connection: 'keep-alive' - }, - data({ channel, date }) { - const todayEpoch = date.startOf('day').unix() - const nextDayEpoch = date.add(1, 'day').startOf('day').unix() - return JSON.stringify({ - ch_ext_id: channel.site_id, - from: todayEpoch, - to: nextDayEpoch - }) - } - }, - parser: function ({ content }) { - const programs = [] - const data = JSON.parse(content) - data.shows.forEach(show => { - const start = dayjs.unix(show.show_start).utc() - const stop = dayjs.unix(show.show_end).utc() - const programData = { - title: show.title, - description: show.summary || 'No description available', - start: start.toISOString(), - stop: stop.toISOString(), - thumbnail: show.thumbnail - } - programs.push(programData) - }) - return programs - }, - async channels() { - const response = await axios.post( - 'https://stargate.telekom.si/api/titan.tv.WebEpg/ZapList', - JSON.stringify({ includeRadioStations: true }), - { - headers: this.request.headers - } - ) - - const data = response.data.data - return data.map(item => ({ - lang: 'sq', - name: String(item.channel.title), - site_id: String(item.channel.id) - //logo: String(item.channel.logo) - })) - } -} +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'neo.io', + timezone: 'Europe/Ljubljana', + days: 5, + url() { + return 'https://stargate.telekom.si/api/titan.tv.WebEpg/GetWebEpgData' + }, + request: { + method: 'POST', + headers: { + Host: 'stargate.telekom.si', + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0', + Accept: 'application/json, text/plain, */*', + 'Accept-Language': 'nl,en-US;q=0.7,en;q=0.3', + 'Content-Type': 'application/json', + 'X-AppLayout': '1', + 'x-language': 'sl', + Origin: 'https://neo.io', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'cross-site', + 'Sec-GPC': '1', + Connection: 'keep-alive' + }, + data({ channel, date }) { + const todayEpoch = date.startOf('day').unix() + const nextDayEpoch = date.add(1, 'day').startOf('day').unix() + return JSON.stringify({ + ch_ext_id: channel.site_id, + from: todayEpoch, + to: nextDayEpoch + }) + } + }, + parser: function ({ content }) { + const programs = [] + const data = JSON.parse(content) + data.shows.forEach(show => { + const start = dayjs.unix(show.show_start).utc() + const stop = dayjs.unix(show.show_end).utc() + const programData = { + title: show.title, + description: show.summary || 'No description available', + start: start.toISOString(), + stop: stop.toISOString(), + thumbnail: show.thumbnail + } + programs.push(programData) + }) + return programs + }, + async channels() { + const response = await axios.post( + 'https://stargate.telekom.si/api/titan.tv.WebEpg/ZapList', + JSON.stringify({ includeRadioStations: true }), + { + headers: this.request.headers + } + ) + + const data = response.data.data + return data.map(item => ({ + lang: 'sq', + name: String(item.channel.title), + site_id: String(item.channel.id) + //logo: String(item.channel.logo) + })) + } +} diff --git a/sites/nhkworldpremium.com/nhkworldpremium.com.config.js b/sites/nhkworldpremium.com/nhkworldpremium.com.config.js index 79ba59bf3..a514c6b3e 100644 --- a/sites/nhkworldpremium.com/nhkworldpremium.com.config.js +++ b/sites/nhkworldpremium.com/nhkworldpremium.com.config.js @@ -1,56 +1,56 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') - -dayjs.extend(utc) -dayjs.extend(timezone) - -module.exports = { - site: 'nhkworldpremium.com', - days: 7, - request: { - cache: { - ttl: 60 * 60 * 1000 // 1 hour - } - }, - url({ channel }) { - return `https://nhkworldpremium.com/backend/api/v1/front/episodes?lang=${channel.lang}` - }, - parser({ content, date }) { - let programs = [] - const items = parseItems(content, date) - items.forEach(item => { - const start = dayjs.tz(item.schedule, 'Asia/Seoul') - const duration = parseDuration(item) - const stop = start.add(duration, 's') - programs.push({ - title: item.programTitle, - sub_title: item.episodeTitle, - start, - stop - }) - }) - - return programs - } -} - -function parseDuration(item) { - const [h, m, s] = item.period.split(':') - - if (!h || !m || !s) return 0 - - return parseInt(h) * 3600 + parseInt(m) * 60 + parseInt(s) -} - -function parseItems(content, date) { - try { - const data = JSON.parse(content) - - if (!data || !data.item || !Array.isArray(data.item.episodes)) return [] - - return data.item.episodes.filter(ep => ep.schedule.startsWith(date.format('YYYY-MM-DD'))) - } catch { - return [] - } -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') + +dayjs.extend(utc) +dayjs.extend(timezone) + +module.exports = { + site: 'nhkworldpremium.com', + days: 7, + request: { + cache: { + ttl: 60 * 60 * 1000 // 1 hour + } + }, + url({ channel }) { + return `https://nhkworldpremium.com/backend/api/v1/front/episodes?lang=${channel.lang}` + }, + parser({ content, date }) { + let programs = [] + const items = parseItems(content, date) + items.forEach(item => { + const start = dayjs.tz(item.schedule, 'Asia/Seoul') + const duration = parseDuration(item) + const stop = start.add(duration, 's') + programs.push({ + title: item.programTitle, + sub_title: item.episodeTitle, + start, + stop + }) + }) + + return programs + } +} + +function parseDuration(item) { + const [h, m, s] = item.period.split(':') + + if (!h || !m || !s) return 0 + + return parseInt(h) * 3600 + parseInt(m) * 60 + parseInt(s) +} + +function parseItems(content, date) { + try { + const data = JSON.parse(content) + + if (!data || !data.item || !Array.isArray(data.item.episodes)) return [] + + return data.item.episodes.filter(ep => ep.schedule.startsWith(date.format('YYYY-MM-DD'))) + } catch { + return [] + } +} diff --git a/sites/nhkworldpremium.com/nhkworldpremium.com.test.js b/sites/nhkworldpremium.com/nhkworldpremium.com.test.js index fc181b1e1..f6e2ab040 100644 --- a/sites/nhkworldpremium.com/nhkworldpremium.com.test.js +++ b/sites/nhkworldpremium.com/nhkworldpremium.com.test.js @@ -1,87 +1,87 @@ -const { parser, url } = require('./nhkworldpremium.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -const date = dayjs.utc('2023-07-10', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '#', - xmltv_id: 'NHKWorldPremium.jp', - lang: 'en' -} - -it('can generate valid url', () => { - expect(url({ channel })).toBe('https://nhkworldpremium.com/backend/api/v1/front/episodes?lang=en') -}) - -it('can generate valid url for Japanese guide', () => { - const channel = { - site_id: '#', - xmltv_id: 'NHKWorldPremium.jp', - lang: 'ja' - } - - expect(url({ channel })).toBe('https://nhkworldpremium.com/backend/api/v1/front/episodes?lang=ja') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_en.json')) - let results = parser({ content, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(56) - expect(results[0]).toMatchObject({ - start: '2023-07-09T15:35:00.000Z', - stop: '2023-07-09T16:20:00.000Z', - title: 'NHK Amateur Singing Contest', - sub_title: '"Maizuru City, Kyoto Prefecture"' - }) - - expect(results[55]).toMatchObject({ - start: '2023-07-10T14:35:00.000Z', - stop: '2023-07-10T15:15:00.000Z', - title: 'International News Report 2023', - sub_title: null - }) -}) - -it('can parse response with Japanese guide', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_ja.json')) - let results = parser({ content, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(56) - expect(results[0]).toMatchObject({ - start: '2023-07-09T15:35:00.000Z', - stop: '2023-07-09T16:20:00.000Z', - title: 'NHKのど自慢', - sub_title: '【京都から生放送!▽前川清・相川七瀬】' - }) - - expect(results[55]).toMatchObject({ - start: '2023-07-10T14:35:00.000Z', - stop: '2023-07-10T15:15:00.000Z', - title: '国際報道2023', - sub_title: null - }) -}) - -it('can handle empty guide', () => { - const results = parser({ content: {}, date }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./nhkworldpremium.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.utc('2023-07-10', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '#', + xmltv_id: 'NHKWorldPremium.jp', + lang: 'en' +} + +it('can generate valid url', () => { + expect(url({ channel })).toBe('https://nhkworldpremium.com/backend/api/v1/front/episodes?lang=en') +}) + +it('can generate valid url for Japanese guide', () => { + const channel = { + site_id: '#', + xmltv_id: 'NHKWorldPremium.jp', + lang: 'ja' + } + + expect(url({ channel })).toBe('https://nhkworldpremium.com/backend/api/v1/front/episodes?lang=ja') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_en.json')) + let results = parser({ content, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(56) + expect(results[0]).toMatchObject({ + start: '2023-07-09T15:35:00.000Z', + stop: '2023-07-09T16:20:00.000Z', + title: 'NHK Amateur Singing Contest', + sub_title: '"Maizuru City, Kyoto Prefecture"' + }) + + expect(results[55]).toMatchObject({ + start: '2023-07-10T14:35:00.000Z', + stop: '2023-07-10T15:15:00.000Z', + title: 'International News Report 2023', + sub_title: null + }) +}) + +it('can parse response with Japanese guide', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_ja.json')) + let results = parser({ content, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(56) + expect(results[0]).toMatchObject({ + start: '2023-07-09T15:35:00.000Z', + stop: '2023-07-09T16:20:00.000Z', + title: 'NHKのど自慢', + sub_title: '【京都から生放送!▽前川清・相川七瀬】' + }) + + expect(results[55]).toMatchObject({ + start: '2023-07-10T14:35:00.000Z', + stop: '2023-07-10T15:15:00.000Z', + title: '国際報道2023', + sub_title: null + }) +}) + +it('can handle empty guide', () => { + const results = parser({ content: {}, date }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/nhl.com/nhl.com.config.js b/sites/nhl.com/nhl.com.config.js index 461276525..ab059ef56 100644 --- a/sites/nhl.com/nhl.com.config.js +++ b/sites/nhl.com/nhl.com.config.js @@ -1,46 +1,46 @@ -const dayjs = require('dayjs') - -module.exports = { - site: 'nhl.com', - // I'm not sure what `endDate` represents but they only return 1 day of - // results, with `endTime`s ocassionally in the following day. - days: 1, - url: ({ date }) => - `https://api-web.nhle.com/v1/network/tv-schedule/${date.toJSON().split('T')[0]}`, - parser({ content }) { - const programs = [] - const items = parseItems(content) - for (const item of items) { - programs.push({ - title: item.title, - description: item.description === item.title ? undefined : item.description, - category: 'Sports', - // image: parseImage(item), - start: parseStart(item), - stop: parseStop(item) - }) - } - - return programs - } -} - -// Unfortunately I couldn't determine how these are -// supposed to be formatted. Pointers appreciated! -// function parseImage(item) { -// const uri = item.broadcastImageUrl - -// return uri ? `https://???/${uri}` : null -// } - -function parseStart(item) { - return dayjs(item.startTime) -} - -function parseStop(item) { - return dayjs(item.endTime) -} - -function parseItems(content) { - return JSON.parse(content).broadcasts -} +const dayjs = require('dayjs') + +module.exports = { + site: 'nhl.com', + // I'm not sure what `endDate` represents but they only return 1 day of + // results, with `endTime`s ocassionally in the following day. + days: 1, + url: ({ date }) => + `https://api-web.nhle.com/v1/network/tv-schedule/${date.toJSON().split('T')[0]}`, + parser({ content }) { + const programs = [] + const items = parseItems(content) + for (const item of items) { + programs.push({ + title: item.title, + description: item.description === item.title ? undefined : item.description, + category: 'Sports', + // image: parseImage(item), + start: parseStart(item), + stop: parseStop(item) + }) + } + + return programs + } +} + +// Unfortunately I couldn't determine how these are +// supposed to be formatted. Pointers appreciated! +// function parseImage(item) { +// const uri = item.broadcastImageUrl + +// return uri ? `https://???/${uri}` : null +// } + +function parseStart(item) { + return dayjs(item.startTime) +} + +function parseStop(item) { + return dayjs(item.endTime) +} + +function parseItems(content) { + return JSON.parse(content).broadcasts +} diff --git a/sites/nhl.com/nhl.com.test.js b/sites/nhl.com/nhl.com.test.js index 49ce4a0e7..1e4ed97b0 100644 --- a/sites/nhl.com/nhl.com.test.js +++ b/sites/nhl.com/nhl.com.test.js @@ -1,44 +1,44 @@ -const { parser, url } = require('./nhl.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2024-11-21', 'YYYY-MM-DD').startOf('d') - -it('can generate valid url', () => { - expect(url({ date })).toBe('https://api-web.nhle.com/v1/network/tv-schedule/2024-11-21') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - let results = parser({ content, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2024-11-21T12:00:00.000Z', - stop: '2024-11-21T13:00:00.000Z', - title: 'On The Fly', - category: 'Sports' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - content: JSON.stringify({ - // extra props not necessary but they form a valid response - date: '2024-11-21', - startDate: '2024-11-07', - endDate: '2024-12-05', - broadcasts: [] - }) - }) - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./nhl.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2024-11-21', 'YYYY-MM-DD').startOf('d') + +it('can generate valid url', () => { + expect(url({ date })).toBe('https://api-web.nhle.com/v1/network/tv-schedule/2024-11-21') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + let results = parser({ content, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2024-11-21T12:00:00.000Z', + stop: '2024-11-21T13:00:00.000Z', + title: 'On The Fly', + category: 'Sports' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + content: JSON.stringify({ + // extra props not necessary but they form a valid response + date: '2024-11-21', + startDate: '2024-11-07', + endDate: '2024-12-05', + broadcasts: [] + }) + }) + expect(results).toMatchObject([]) +}) diff --git a/sites/nostv.pt/nostv.pt.config.js b/sites/nostv.pt/nostv.pt.config.js index ffeb7a6eb..26ae443c4 100644 --- a/sites/nostv.pt/nostv.pt.config.js +++ b/sites/nostv.pt/nostv.pt.config.js @@ -1,71 +1,71 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') - -dayjs.extend(utc) - -const headers = { - 'X-Apikey': 'xe1dgrShwdR1DVOKGmsj8Ut4QLlGyOFI', - 'X-Core-Appversion': '2.20.0.3', - 'X-Core-Contentratinglimit': '0', - 'X-Core-Deviceid': '', - 'X-Core-Devicetype': 'web', - Origin: 'https://nostv.pt', - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' -} - -module.exports = { - site: 'nostv.pt', - days: 2, - url({ channel, date }) { - return `https://api.clg.nos.pt/nostv/ott/schedule/range/contents/guest?channels=${channel.site_id - }&minDate=${date.format('YYYY-MM-DD')}T00:00:00Z&maxDate=${date.format( - 'YYYY-MM-DD' - )}T23:59:59Z&isDateInclusive=true&client_id=${headers['X-Apikey']}` - }, - request: { headers }, - parser({ content }) { - const programs = [] - if (content) { - const items = Array.isArray(content) ? content : JSON.parse(content) - items.forEach(item => { - const image = item.Images - ? `https://mage.stream.nos.pt/mage/v1/Images?sourceUri=${item.Images[0].Url}&profile=ott_1_452x340&client_id=${headers['X-Apikey']}` - : null - programs.push({ - title: item.Metadata?.Title, - sub_title: item.Metadata?.SubTitle ? item.Metadata?.SubTitle : null, - description: item.Metadata?.Description, - season: item.Metadata?.Season, - episode: item.Metadata?.Episode, - icon: { - src: image - }, - image, - start: dayjs.utc(item.UtcDateTimeStart), - stop: dayjs.utc(item.UtcDateTimeEnd) - }) - }) - } - - return programs - }, - async channels() { - const result = await axios - .get( - `https://api.clg.nos.pt/nostv/ott/channels/guest?client_id=${headers['X-Apikey']}`, - { headers } - ) - .then(r => r.data) - .catch(console.error) - - return result.map(item => { - return { - lang: 'pt', - site_id: item.ServiceId, - name: item.Name - } - }) - } -} +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') + +dayjs.extend(utc) + +const headers = { + 'X-Apikey': 'xe1dgrShwdR1DVOKGmsj8Ut4QLlGyOFI', + 'X-Core-Appversion': '2.20.0.3', + 'X-Core-Contentratinglimit': '0', + 'X-Core-Deviceid': '', + 'X-Core-Devicetype': 'web', + Origin: 'https://nostv.pt', + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' +} + +module.exports = { + site: 'nostv.pt', + days: 2, + url({ channel, date }) { + return `https://api.clg.nos.pt/nostv/ott/schedule/range/contents/guest?channels=${channel.site_id + }&minDate=${date.format('YYYY-MM-DD')}T00:00:00Z&maxDate=${date.format( + 'YYYY-MM-DD' + )}T23:59:59Z&isDateInclusive=true&client_id=${headers['X-Apikey']}` + }, + request: { headers }, + parser({ content }) { + const programs = [] + if (content) { + const items = Array.isArray(content) ? content : JSON.parse(content) + items.forEach(item => { + const image = item.Images + ? `https://mage.stream.nos.pt/mage/v1/Images?sourceUri=${item.Images[0].Url}&profile=ott_1_452x340&client_id=${headers['X-Apikey']}` + : null + programs.push({ + title: item.Metadata?.Title, + sub_title: item.Metadata?.SubTitle ? item.Metadata?.SubTitle : null, + description: item.Metadata?.Description, + season: item.Metadata?.Season, + episode: item.Metadata?.Episode, + icon: { + src: image + }, + image, + start: dayjs.utc(item.UtcDateTimeStart), + stop: dayjs.utc(item.UtcDateTimeEnd) + }) + }) + } + + return programs + }, + async channels() { + const result = await axios + .get( + `https://api.clg.nos.pt/nostv/ott/channels/guest?client_id=${headers['X-Apikey']}`, + { headers } + ) + .then(r => r.data) + .catch(console.error) + + return result.map(item => { + return { + lang: 'pt', + site_id: item.ServiceId, + name: item.Name + } + }) + } +} diff --git a/sites/nostv.pt/nostv.pt.test.js b/sites/nostv.pt/nostv.pt.test.js index 939b8aadc..c7892bc74 100644 --- a/sites/nostv.pt/nostv.pt.test.js +++ b/sites/nostv.pt/nostv.pt.test.js @@ -1,55 +1,55 @@ -const { parser, url } = require('./nostv.pt.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-12-11').startOf('d') -const channel = { - site_id: '510', - xmltv_id: 'SPlus.pt' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://api.clg.nos.pt/nostv/ott/schedule/range/contents/guest?channels=510&minDate=2023-12-11T00:00:00Z&maxDate=2023-12-11T23:59:59Z&isDateInclusive=true&client_id=xe1dgrShwdR1DVOKGmsj8Ut4QLlGyOFI' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/data.json')) - const results = parser({ content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - const image = 'https://mage.stream.nos.pt/mage/v1/Images?sourceUri=http://vip.pam.local.internal/PAM.Images/Store/8329ed1aec5d4c0faa2056972256ff9f&profile=ott_1_452x340&client_id=xe1dgrShwdR1DVOKGmsj8Ut4QLlGyOFI' - - expect(results[0]).toMatchObject({ - start: '2023-12-11T16:30:00.000Z', - stop: '2023-12-11T17:00:00.000Z', - title: 'Village Vets', - description: - 'A história de dois melhores amigos veterinários e o seu extraordinário trabalho na Austrália.', - season: 1, - episode: 12, - icon: { - src: image - }, - image - }) -}) - -it('can handle empty guide', async () => { - const results = await parser({ - date, - content: '[]' - }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./nostv.pt.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-12-11').startOf('d') +const channel = { + site_id: '510', + xmltv_id: 'SPlus.pt' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://api.clg.nos.pt/nostv/ott/schedule/range/contents/guest?channels=510&minDate=2023-12-11T00:00:00Z&maxDate=2023-12-11T23:59:59Z&isDateInclusive=true&client_id=xe1dgrShwdR1DVOKGmsj8Ut4QLlGyOFI' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/data.json')) + const results = parser({ content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + const image = 'https://mage.stream.nos.pt/mage/v1/Images?sourceUri=http://vip.pam.local.internal/PAM.Images/Store/8329ed1aec5d4c0faa2056972256ff9f&profile=ott_1_452x340&client_id=xe1dgrShwdR1DVOKGmsj8Ut4QLlGyOFI' + + expect(results[0]).toMatchObject({ + start: '2023-12-11T16:30:00.000Z', + stop: '2023-12-11T17:00:00.000Z', + title: 'Village Vets', + description: + 'A história de dois melhores amigos veterinários e o seu extraordinário trabalho na Austrália.', + season: 1, + episode: 12, + icon: { + src: image + }, + image + }) +}) + +it('can handle empty guide', async () => { + const results = await parser({ + date, + content: '[]' + }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/novacyprus.com/novacyprus.com.config.js b/sites/novacyprus.com/novacyprus.com.config.js index 6bf63c84b..be6c9ad06 100644 --- a/sites/novacyprus.com/novacyprus.com.config.js +++ b/sites/novacyprus.com/novacyprus.com.config.js @@ -1,67 +1,67 @@ -process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0 - -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'novacyprus.com', - days: 2, - url({ date }) { - return `https://www.novacyprus.com/api/v1/tvprogram/from/${date.format('YYYYMMDD')}/to/${date - .add(1, 'd') - .format('YYYYMMDD')}` - }, - parser({ content, channel }) { - let programs = [] - const items = parseItems(content, channel) - items.forEach(item => { - const start = parseStart(item) - const stop = start.add(item.slotDuration, 'm') - programs.push({ - title: item.title, - description: item.description, - image: parseImage(item), - start, - stop - }) - }) - - return programs - }, - async channels() { - const channels = await axios - .get('https://www.novacyprus.com/api/v1/guide/dailychannels') - .then(r => r.data) - .catch(console.log) - - return channels.map(item => { - return { - lang: 'el', - site_id: item.ChannelId, - name: item.nameEl - } - }) - } -} - -function parseStart(item) { - return dayjs.tz(item.datetime, 'YYYY-MM-DD HH:mm:ss', 'Asia/Nicosia') -} - -function parseImage(item) { - return item.mediaItems.length ? item.mediaItems[0].CdnUrl : null -} - -function parseItems(content, channel) { - const data = JSON.parse(content) - if (!data || !Array.isArray(data.nodes)) return [] - - return data.nodes.filter(i => i.ChannelId === channel.site_id) -} +process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0 + +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'novacyprus.com', + days: 2, + url({ date }) { + return `https://www.novacyprus.com/api/v1/tvprogram/from/${date.format('YYYYMMDD')}/to/${date + .add(1, 'd') + .format('YYYYMMDD')}` + }, + parser({ content, channel }) { + let programs = [] + const items = parseItems(content, channel) + items.forEach(item => { + const start = parseStart(item) + const stop = start.add(item.slotDuration, 'm') + programs.push({ + title: item.title, + description: item.description, + image: parseImage(item), + start, + stop + }) + }) + + return programs + }, + async channels() { + const channels = await axios + .get('https://www.novacyprus.com/api/v1/guide/dailychannels') + .then(r => r.data) + .catch(console.log) + + return channels.map(item => { + return { + lang: 'el', + site_id: item.ChannelId, + name: item.nameEl + } + }) + } +} + +function parseStart(item) { + return dayjs.tz(item.datetime, 'YYYY-MM-DD HH:mm:ss', 'Asia/Nicosia') +} + +function parseImage(item) { + return item.mediaItems.length ? item.mediaItems[0].CdnUrl : null +} + +function parseItems(content, channel) { + const data = JSON.parse(content) + if (!data || !Array.isArray(data.nodes)) return [] + + return data.nodes.filter(i => i.ChannelId === channel.site_id) +} diff --git a/sites/novasports.gr/novasports.gr.config.js b/sites/novasports.gr/novasports.gr.config.js index 5e452c579..0721f670e 100644 --- a/sites/novasports.gr/novasports.gr.config.js +++ b/sites/novasports.gr/novasports.gr.config.js @@ -1,87 +1,87 @@ -const axios = require('axios') -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') - -dayjs.extend(utc) -dayjs.extend(timezone) - -module.exports = { - site: 'novasports.gr', - days: 2, - url: function ({ date }) { - return `https://www.novasports.gr/wp-admin/admin-ajax.php?action=nova_get_template&template=tv-program/broadcast&dt=${date.format( - 'YYYY-MM-DD' - )}` - }, - parser: function ({ content, channel, date }) { - let programs = [] - const items = parseItems(content, channel) - items.forEach(item => { - const $item = cheerio.load(item) - const prev = programs[programs.length - 1] - let start = parseStart($item, date) - let stop = start.add(30, 'm') - if (prev) { - if (start.isBefore(prev.start)) { - start = start.add(1, 'd') - stop = stop.add(1, 'd') - } - prev.stop = start - } - programs.push({ - title: parseTitle($item), - description: parseDescription($item), - start, - stop - }) - }) - - return programs - }, - async channels() { - const html = await axios - .get( - 'https://www.novasports.gr/wp-admin/admin-ajax.php?action=nova_get_template&template=tv-program/broadcast&dt=2022-10-29' - ) - .then(r => r.data) - .catch(console.log) - const $ = cheerio.load(html) - const items = $( - '#mc-broadcast-content:nth-child(2) > div > #channelist-slider > div.channelist-wrapper.slider-wrapper.content > div > div' - ).toArray() - return items.map(item => { - const name = $(item).find('.channel').text().trim() - - return { - lang: 'el', - site_id: name, - name - } - }) - } -} - -function parseTitle($item) { - return $item('.title').text().trim() -} - -function parseDescription($item) { - return $item('.subtitle').text().trim() -} - -function parseStart($item, date) { - const timeString = $item('.time').text().trim() - - return dayjs.tz(`${date.format('YYYY-MM-DD')} ${timeString}`, 'YYYY-MM-DD HH:mm', 'Europe/Athens') -} - -function parseItems(content, channel) { - const $ = cheerio.load(content) - const $channelElement = $( - `#mc-broadcast-content:nth-child(2) > div > #channelist-slider > div.channelist-wrapper.slider-wrapper.content > div > div:contains("${channel.site_id}")` - ) - - return $channelElement.find('.channel-program > div').toArray() -} +const axios = require('axios') +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') + +dayjs.extend(utc) +dayjs.extend(timezone) + +module.exports = { + site: 'novasports.gr', + days: 2, + url: function ({ date }) { + return `https://www.novasports.gr/wp-admin/admin-ajax.php?action=nova_get_template&template=tv-program/broadcast&dt=${date.format( + 'YYYY-MM-DD' + )}` + }, + parser: function ({ content, channel, date }) { + let programs = [] + const items = parseItems(content, channel) + items.forEach(item => { + const $item = cheerio.load(item) + const prev = programs[programs.length - 1] + let start = parseStart($item, date) + let stop = start.add(30, 'm') + if (prev) { + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') + stop = stop.add(1, 'd') + } + prev.stop = start + } + programs.push({ + title: parseTitle($item), + description: parseDescription($item), + start, + stop + }) + }) + + return programs + }, + async channels() { + const html = await axios + .get( + 'https://www.novasports.gr/wp-admin/admin-ajax.php?action=nova_get_template&template=tv-program/broadcast&dt=2022-10-29' + ) + .then(r => r.data) + .catch(console.log) + const $ = cheerio.load(html) + const items = $( + '#mc-broadcast-content:nth-child(2) > div > #channelist-slider > div.channelist-wrapper.slider-wrapper.content > div > div' + ).toArray() + return items.map(item => { + const name = $(item).find('.channel').text().trim() + + return { + lang: 'el', + site_id: name, + name + } + }) + } +} + +function parseTitle($item) { + return $item('.title').text().trim() +} + +function parseDescription($item) { + return $item('.subtitle').text().trim() +} + +function parseStart($item, date) { + const timeString = $item('.time').text().trim() + + return dayjs.tz(`${date.format('YYYY-MM-DD')} ${timeString}`, 'YYYY-MM-DD HH:mm', 'Europe/Athens') +} + +function parseItems(content, channel) { + const $ = cheerio.load(content) + const $channelElement = $( + `#mc-broadcast-content:nth-child(2) > div > #channelist-slider > div.channelist-wrapper.slider-wrapper.content > div > div:contains("${channel.site_id}")` + ) + + return $channelElement.find('.channel-program > div').toArray() +} diff --git a/sites/novasports.gr/novasports.gr.test.js b/sites/novasports.gr/novasports.gr.test.js index 21766fd2b..4b9362221 100644 --- a/sites/novasports.gr/novasports.gr.test.js +++ b/sites/novasports.gr/novasports.gr.test.js @@ -1,46 +1,46 @@ -const { parser, url } = require('./novasports.gr.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-10-29', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'Novasports Premier League', - xmltv_id: 'NovasportsPremierLeague.gr' -} - -it('can generate valid url', () => { - expect(url({ date })).toBe( - 'https://www.novasports.gr/wp-admin/admin-ajax.php?action=nova_get_template&template=tv-program/broadcast&dt=2022-10-29' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - const results = parser({ content, channel, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2022-10-29T07:00:00.000Z', - stop: '2022-10-29T07:30:00.000Z', - title: 'Classic Match', - description: 'Τσέλσι - Μάντσεστερ Γ. (1999/00)' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')), - channel, - date - }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./novasports.gr.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-10-29', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'Novasports Premier League', + xmltv_id: 'NovasportsPremierLeague.gr' +} + +it('can generate valid url', () => { + expect(url({ date })).toBe( + 'https://www.novasports.gr/wp-admin/admin-ajax.php?action=nova_get_template&template=tv-program/broadcast&dt=2022-10-29' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + const results = parser({ content, channel, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2022-10-29T07:00:00.000Z', + stop: '2022-10-29T07:30:00.000Z', + title: 'Classic Match', + description: 'Τσέλσι - Μάντσεστερ Γ. (1999/00)' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')), + channel, + date + }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/nowplayer.now.com/nowplayer.now.com.config.js b/sites/nowplayer.now.com/nowplayer.now.com.config.js index 14697d29c..126b040bd 100644 --- a/sites/nowplayer.now.com/nowplayer.now.com.config.js +++ b/sites/nowplayer.now.com/nowplayer.now.com.config.js @@ -1,70 +1,70 @@ -const axios = require('axios') -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') - -dayjs.extend(utc) - -module.exports = { - site: 'nowplayer.now.com', - days: 2, - url: function ({ channel, date }) { - const diff = date.diff(dayjs.utc().startOf('d'), 'd') + 1 - - return `https://nowplayer.now.com/tvguide/epglist?channelIdList[]=${channel.site_id}&day=${diff}` - }, - request: { - headers({ channel }) { - return { - Cookie: `LANG=${channel.lang}; Expires=null; Path=/; Domain=nowplayer.now.com` - } - } - }, - parser: function ({ content }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - programs.push({ - title: item.name, - start: parseStart(item), - stop: parseStop(item) - }) - }) - - return programs - }, - async channels({ lang }) { - const html = await axios - .get('https://nowplayer.now.com/channels', { headers: { Accept: 'text/html' } }) - .then(r => r.data) - .catch(console.log) - - let channels = [] - - const $ = cheerio.load(html) - $('body > div.container > .tv-guide-s-g > div > div').each((i, el) => { - channels.push({ - lang, - site_id: $(el).find('.guide-g-play > p.channel').text().replace('CH', ''), - name: $(el).find('.thumbnail > a > span.image > p').text() - }) - }) - - return channels - } -} - -function parseStart(item) { - return dayjs(item.start) -} - -function parseStop(item) { - return dayjs(item.end) -} - -function parseItems(content) { - const data = JSON.parse(content) - if (!data || !Array.isArray(data)) return [] - - return Array.isArray(data[0]) ? data[0] : [] -} +const axios = require('axios') +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') + +dayjs.extend(utc) + +module.exports = { + site: 'nowplayer.now.com', + days: 2, + url: function ({ channel, date }) { + const diff = date.diff(dayjs.utc().startOf('d'), 'd') + 1 + + return `https://nowplayer.now.com/tvguide/epglist?channelIdList[]=${channel.site_id}&day=${diff}` + }, + request: { + headers({ channel }) { + return { + Cookie: `LANG=${channel.lang}; Expires=null; Path=/; Domain=nowplayer.now.com` + } + } + }, + parser: function ({ content }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + programs.push({ + title: item.name, + start: parseStart(item), + stop: parseStop(item) + }) + }) + + return programs + }, + async channels({ lang }) { + const html = await axios + .get('https://nowplayer.now.com/channels', { headers: { Accept: 'text/html' } }) + .then(r => r.data) + .catch(console.log) + + let channels = [] + + const $ = cheerio.load(html) + $('body > div.container > .tv-guide-s-g > div > div').each((i, el) => { + channels.push({ + lang, + site_id: $(el).find('.guide-g-play > p.channel').text().replace('CH', ''), + name: $(el).find('.thumbnail > a > span.image > p').text() + }) + }) + + return channels + } +} + +function parseStart(item) { + return dayjs(item.start) +} + +function parseStop(item) { + return dayjs(item.end) +} + +function parseItems(content) { + const data = JSON.parse(content) + if (!data || !Array.isArray(data)) return [] + + return Array.isArray(data[0]) ? data[0] : [] +} diff --git a/sites/nuevosiglo.com.uy/nuevosiglo.com.uy.config.js b/sites/nuevosiglo.com.uy/nuevosiglo.com.uy.config.js index 6dd2c7e1d..162ec21a3 100644 --- a/sites/nuevosiglo.com.uy/nuevosiglo.com.uy.config.js +++ b/sites/nuevosiglo.com.uy/nuevosiglo.com.uy.config.js @@ -1,106 +1,106 @@ -const axios = require('axios') -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -const API_ENDPOINT = 'https://www.nuevosiglo.com.uy/programacion/getGrilla' - -module.exports = { - site: 'nuevosiglo.com.uy', - days: 2, - url({ date }) { - return `${API_ENDPOINT}?fecha=${date.format('YYYY/MM/DD')}` - }, - request: { - cache: { - ttl: 60 * 60 * 1000 // 1 hour - } - }, - async parser({ content, channel }) { - const programs = [] - const items = parseItems(content, channel) - for (let item of items) { - const $item = cheerio.load(item) - const programId = parseProgramId($item) - const details = await loadProgramDetails(programId) - if (!details) continue - programs.push({ - title: details.main_title, - description: details.short_argument, - image: parseImage(details), - actors: parseActors(details), - rating: parseRating(details), - date: details.year, - start: parseStart(details), - stop: parseStop(details) - }) - } - - return programs - }, - async channels() { - const data = await axios - .get(`${API_ENDPOINT}?fecha=${dayjs().format('YYYY/MM/DD')}`) - .then(r => r.data) - .catch(console.error) - const $ = cheerio.load(data) - - return $('img') - .map(function () { - return { - lang: 'es', - site_id: $(this).attr('alt').replace(/&/gi, '&'), - name: $(this).attr('alt') - } - }) - .get() - } -} - -function parseProgramId($item) { - return $item('*').data('schedule') -} - -function loadProgramDetails(programId) { - return axios - .get(`https://www.nuevosiglo.com.uy/Programacion/getScheduleXId/${programId}`) - .then(r => r.data) - .catch(console.log) -} - -function parseRating(details) { - return details.parental_rating - ? { - system: 'MPAA', - value: details.parental_rating - } - : null -} - -function parseActors(details) { - return details.actors.split(', ') -} - -function parseImage(details) { - return details.image ? `https://img-ns.s3.amazonaws.com/grid_data/${details.image}` : null -} - -function parseStart(details) { - return dayjs.tz(details.time_start, 'YYYY-MM-DD HH:mm:ss', 'America/Montevideo') -} - -function parseStop(details) { - return dayjs.tz(details.time_end, 'YYYY-MM-DD HH:mm:ss', 'America/Montevideo') -} - -function parseItems(content, channel) { - const $ = cheerio.load(content) - - return $(`img[alt="${channel.site_id}"]`).first().nextUntil('img').toArray() -} +const axios = require('axios') +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +const API_ENDPOINT = 'https://www.nuevosiglo.com.uy/programacion/getGrilla' + +module.exports = { + site: 'nuevosiglo.com.uy', + days: 2, + url({ date }) { + return `${API_ENDPOINT}?fecha=${date.format('YYYY/MM/DD')}` + }, + request: { + cache: { + ttl: 60 * 60 * 1000 // 1 hour + } + }, + async parser({ content, channel }) { + const programs = [] + const items = parseItems(content, channel) + for (let item of items) { + const $item = cheerio.load(item) + const programId = parseProgramId($item) + const details = await loadProgramDetails(programId) + if (!details) continue + programs.push({ + title: details.main_title, + description: details.short_argument, + image: parseImage(details), + actors: parseActors(details), + rating: parseRating(details), + date: details.year, + start: parseStart(details), + stop: parseStop(details) + }) + } + + return programs + }, + async channels() { + const data = await axios + .get(`${API_ENDPOINT}?fecha=${dayjs().format('YYYY/MM/DD')}`) + .then(r => r.data) + .catch(console.error) + const $ = cheerio.load(data) + + return $('img') + .map(function () { + return { + lang: 'es', + site_id: $(this).attr('alt').replace(/&/gi, '&'), + name: $(this).attr('alt') + } + }) + .get() + } +} + +function parseProgramId($item) { + return $item('*').data('schedule') +} + +function loadProgramDetails(programId) { + return axios + .get(`https://www.nuevosiglo.com.uy/Programacion/getScheduleXId/${programId}`) + .then(r => r.data) + .catch(console.log) +} + +function parseRating(details) { + return details.parental_rating + ? { + system: 'MPAA', + value: details.parental_rating + } + : null +} + +function parseActors(details) { + return details.actors.split(', ') +} + +function parseImage(details) { + return details.image ? `https://img-ns.s3.amazonaws.com/grid_data/${details.image}` : null +} + +function parseStart(details) { + return dayjs.tz(details.time_start, 'YYYY-MM-DD HH:mm:ss', 'America/Montevideo') +} + +function parseStop(details) { + return dayjs.tz(details.time_end, 'YYYY-MM-DD HH:mm:ss', 'America/Montevideo') +} + +function parseItems(content, channel) { + const $ = cheerio.load(content) + + return $(`img[alt="${channel.site_id}"]`).first().nextUntil('img').toArray() +} diff --git a/sites/nuevosiglo.com.uy/nuevosiglo.com.uy.test.js b/sites/nuevosiglo.com.uy/nuevosiglo.com.uy.test.js index fbd2bf02c..bb4721afb 100644 --- a/sites/nuevosiglo.com.uy/nuevosiglo.com.uy.test.js +++ b/sites/nuevosiglo.com.uy/nuevosiglo.com.uy.test.js @@ -1,97 +1,97 @@ -const { parser, url } = require('./nuevosiglo.com.uy.config.js') -const fs = require('fs') -const path = require('path') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -const date = dayjs.utc('2023-02-10', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'HBO', - xmltv_id: 'HBOLatinAmerica.us' -} - -it('can generate valid url', () => { - expect(url({ date })).toBe( - 'https://www.nuevosiglo.com.uy/programacion/getGrilla?fecha=2023/02/10' - ) -}) - -it('can parse response', async () => { - axios.get.mockImplementation(url => { - if (url === 'https://www.nuevosiglo.com.uy/Programacion/getScheduleXId/133769227') { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program1.json'))) - }) - } else if (url === 'https://www.nuevosiglo.com.uy/Programacion/getScheduleXId/133769239') { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program2.json'))) - }) - } else { - return Promise.resolve({ data: '' }) - } - }) - - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') - let results = await parser({ content, channel }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2023-02-10T01:11:00.000Z', - stop: '2023-02-10T03:46:00.000Z', - title: 'Jurassic World: Dominion', - description: - 'Años después de la destrucción de Isla Nublar, los dinosaurios viven y cazan junto a los humanos. Este equilibrio determinará, si los humanos seguirán siendo los depredadores máximos en un planeta que comparten con las criaturas temibles.', - image: 'https://img-ns.s3.amazonaws.com/grid_data/23354476.jpg', - date: '2022', - rating: { - system: 'MPAA', - value: 'PG-13' - }, - actors: ['Jeff Goldblum', 'Sam Neill', 'Bryce Dallas Howard'] - }) - - expect(results[1]).toMatchObject({ - start: '2023-02-11T02:06:00.000Z', - stop: '2023-02-11T04:16:00.000Z', - title: 'Black Adam', - description: - 'Black Adam es liberado de su tumba casi cinco mil años después de haber sido encarcelado y recibir sus poderes de los antiguos dioses. Ahora está listo para desatar su forma única de justicia en el mundo.', - image: 'https://img-ns.s3.amazonaws.com/grid_data/24638423.jpg', - date: '2022', - rating: { - system: 'MPAA', - value: 'PG-13' - }, - actors: [ - 'Aldis Hodge', - 'Dwayne Johnson', - 'Noah Centineo', - 'Sarah Shahi', - 'Marwan Kenzari', - 'Pierce Brosnan', - 'Quintessa Swindell', - 'Mohammed Amer', - 'Bodhi Sabongui', - 'James Cusati-Moyer' - ] - }) -}) - -it('can handle empty guide', async () => { - const results = await parser({ - channel, - content: '' - }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./nuevosiglo.com.uy.config.js') +const fs = require('fs') +const path = require('path') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.utc('2023-02-10', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'HBO', + xmltv_id: 'HBOLatinAmerica.us' +} + +it('can generate valid url', () => { + expect(url({ date })).toBe( + 'https://www.nuevosiglo.com.uy/programacion/getGrilla?fecha=2023/02/10' + ) +}) + +it('can parse response', async () => { + axios.get.mockImplementation(url => { + if (url === 'https://www.nuevosiglo.com.uy/Programacion/getScheduleXId/133769227') { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program1.json'))) + }) + } else if (url === 'https://www.nuevosiglo.com.uy/Programacion/getScheduleXId/133769239') { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program2.json'))) + }) + } else { + return Promise.resolve({ data: '' }) + } + }) + + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') + let results = await parser({ content, channel }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2023-02-10T01:11:00.000Z', + stop: '2023-02-10T03:46:00.000Z', + title: 'Jurassic World: Dominion', + description: + 'Años después de la destrucción de Isla Nublar, los dinosaurios viven y cazan junto a los humanos. Este equilibrio determinará, si los humanos seguirán siendo los depredadores máximos en un planeta que comparten con las criaturas temibles.', + image: 'https://img-ns.s3.amazonaws.com/grid_data/23354476.jpg', + date: '2022', + rating: { + system: 'MPAA', + value: 'PG-13' + }, + actors: ['Jeff Goldblum', 'Sam Neill', 'Bryce Dallas Howard'] + }) + + expect(results[1]).toMatchObject({ + start: '2023-02-11T02:06:00.000Z', + stop: '2023-02-11T04:16:00.000Z', + title: 'Black Adam', + description: + 'Black Adam es liberado de su tumba casi cinco mil años después de haber sido encarcelado y recibir sus poderes de los antiguos dioses. Ahora está listo para desatar su forma única de justicia en el mundo.', + image: 'https://img-ns.s3.amazonaws.com/grid_data/24638423.jpg', + date: '2022', + rating: { + system: 'MPAA', + value: 'PG-13' + }, + actors: [ + 'Aldis Hodge', + 'Dwayne Johnson', + 'Noah Centineo', + 'Sarah Shahi', + 'Marwan Kenzari', + 'Pierce Brosnan', + 'Quintessa Swindell', + 'Mohammed Amer', + 'Bodhi Sabongui', + 'James Cusati-Moyer' + ] + }) +}) + +it('can handle empty guide', async () => { + const results = await parser({ + channel, + content: '' + }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/nzxmltv.com/nzxmltv.com.config.js b/sites/nzxmltv.com/nzxmltv.com.config.js index 43029c5b3..488cdb86a 100644 --- a/sites/nzxmltv.com/nzxmltv.com.config.js +++ b/sites/nzxmltv.com/nzxmltv.com.config.js @@ -1,81 +1,81 @@ -const parser = require('epg-parser') - -module.exports = { - site: 'nzxmltv.com', - days: 2, - request: { - cache: { - ttl: 3600000 // 1 hour - }, - maxContentLength: 104857600 // 100 MB - }, - url({ channel }) { - const [path] = channel.site_id.split('#') - - return `https://nzxmltv.com/${path}.xml` - }, - parser({ content, channel, date }) { - const programs = [] - parseItems(content, channel, date).forEach(item => { - const program = { - title: item.title?.[0]?.value, - description: item.desc?.[0]?.value, - icon: item.icon?.[0]?.src, - start: item.start, - stop: item.stop - } - if (item.episodeNum) { - item.episodeNum.forEach(ep => { - if (ep.system === 'xmltv_ns') { - const [season, episode] = ep.value.split('.') - program.season = parseInt(season) + 1 - program.episode = parseInt(episode) + 1 - return true - } - }) - } - programs.push(program) - }) - - return programs - }, - async channels({ provider }) { - const axios = require('axios') - const cheerio = require('cheerio') - - const providers = { - freeview: 'xmltv/guide', - sky: 'sky/guide', - redbull: 'iptv/redbull', - pluto: 'iptv/plutotv' - } - - const channels = [] - const path = providers[provider] - const xml = await axios - .get(`https://nzxmltv.com/${path}.xml`) - .then(r => r.data) - .catch(console.error) - - const $ = cheerio.load(xml) - $('tv channel').each((i, el) => { - const disp = $(el).find('display-name') - const channelId = $(el).attr('id') - - channels.push({ - lang: disp.attr('lang').substr(0, 2), - site_id: `${path}#${channelId}`, - name: disp.text().trim() - }) - }) - - return channels - } -} - -function parseItems(content, channel, date) { - const { programs } = parser.parse(content) - const [, channelId] = channel.site_id.split('#') - - return programs.filter(p => p.channel === channelId && date.isSame(p.start, 'day')) -} +const parser = require('epg-parser') + +module.exports = { + site: 'nzxmltv.com', + days: 2, + request: { + cache: { + ttl: 3600000 // 1 hour + }, + maxContentLength: 104857600 // 100 MB + }, + url({ channel }) { + const [path] = channel.site_id.split('#') + + return `https://nzxmltv.com/${path}.xml` + }, + parser({ content, channel, date }) { + const programs = [] + parseItems(content, channel, date).forEach(item => { + const program = { + title: item.title?.[0]?.value, + description: item.desc?.[0]?.value, + icon: item.icon?.[0]?.src, + start: item.start, + stop: item.stop + } + if (item.episodeNum) { + item.episodeNum.forEach(ep => { + if (ep.system === 'xmltv_ns') { + const [season, episode] = ep.value.split('.') + program.season = parseInt(season) + 1 + program.episode = parseInt(episode) + 1 + return true + } + }) + } + programs.push(program) + }) + + return programs + }, + async channels({ provider }) { + const axios = require('axios') + const cheerio = require('cheerio') + + const providers = { + freeview: 'xmltv/guide', + sky: 'sky/guide', + redbull: 'iptv/redbull', + pluto: 'iptv/plutotv' + } + + const channels = [] + const path = providers[provider] + const xml = await axios + .get(`https://nzxmltv.com/${path}.xml`) + .then(r => r.data) + .catch(console.error) + + const $ = cheerio.load(xml) + $('tv channel').each((i, el) => { + const disp = $(el).find('display-name') + const channelId = $(el).attr('id') + + channels.push({ + lang: disp.attr('lang').substr(0, 2), + site_id: `${path}#${channelId}`, + name: disp.text().trim() + }) + }) + + return channels + } +} + +function parseItems(content, channel, date) { + const { programs } = parser.parse(content) + const [, channelId] = channel.site_id.split('#') + + return programs.filter(p => p.channel === channelId && date.isSame(p.start, 'day')) +} diff --git a/sites/nzxmltv.com/nzxmltv.com.test.js b/sites/nzxmltv.com/nzxmltv.com.test.js index e6ef97a87..f1af8d89b 100644 --- a/sites/nzxmltv.com/nzxmltv.com.test.js +++ b/sites/nzxmltv.com/nzxmltv.com.test.js @@ -1,40 +1,40 @@ -const { parser, url } = require('./nzxmltv.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-11-21').startOf('d') -const channel = { - site_id: 'xmltv/guide#1', - xmltv_id: 'TVNZ1.nz' -} - -it('can generate valid url', () => { - expect(url({ channel })).toBe('https://nzxmltv.com/xmltv/guide.xml') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.xml')) - const results = parser({ content, channel, date }) - - expect(results[0]).toMatchObject({ - start: '2023-11-21T10:30:00.000Z', - stop: '2023-11-21T11:25:00.000Z', - title: 'Sunday', - description: - 'On Sunday, an unmissable show with stories about divorce, weight loss, and the incomprehensible devastation of Gaza.', - season: 2023, - episode: 37, - icon: 'https://www.thetvdb.com/banners/posters/5dbebff2986f2.jpg' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ content: '', channel, date }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./nzxmltv.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-11-21').startOf('d') +const channel = { + site_id: 'xmltv/guide#1', + xmltv_id: 'TVNZ1.nz' +} + +it('can generate valid url', () => { + expect(url({ channel })).toBe('https://nzxmltv.com/xmltv/guide.xml') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.xml')) + const results = parser({ content, channel, date }) + + expect(results[0]).toMatchObject({ + start: '2023-11-21T10:30:00.000Z', + stop: '2023-11-21T11:25:00.000Z', + title: 'Sunday', + description: + 'On Sunday, an unmissable show with stories about divorce, weight loss, and the incomprehensible devastation of Gaza.', + season: 2023, + episode: 37, + icon: 'https://www.thetvdb.com/banners/posters/5dbebff2986f2.jpg' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ content: '', channel, date }) + expect(result).toMatchObject([]) +}) diff --git a/sites/opto.sic.pt/opto.sic.pt.config.js b/sites/opto.sic.pt/opto.sic.pt.config.js index 8c14ec508..d6b64cf81 100644 --- a/sites/opto.sic.pt/opto.sic.pt.config.js +++ b/sites/opto.sic.pt/opto.sic.pt.config.js @@ -1,53 +1,53 @@ -const axios = require('axios') -const dayjs = require('dayjs') - -module.exports = { - site: 'opto.sic.pt', - days: 2, - url({ date, channel }) { - const startDate = date.unix() - const endDate = date.add(1, 'd').unix() - - return `https://opto.sic.pt/api/v1/content/epg?startDate=${startDate}&endDate=${endDate}&channels=${channel.site_id}` - }, - parser({ content }) { - let programs = [] - let items = parseItems(content) - items.forEach(item => { - programs.push({ - title: item.title, - episode: item.episode_number || null, - season: item.season_number || null, - start: dayjs.unix(item.start_time), - stop: dayjs.unix(item.end_time) - }) - }) - - return programs - }, - async channels() { - const data = await axios - .get('https://opto.sic.pt/api/v1/content/channel') - .then(r => r.data) - .catch(console.error) - - return data.map(channel => { - return { - lang: 'pt', - site_id: channel.id, - name: channel.name - } - }) - } -} - -function parseItems(content) { - try { - const data = JSON.parse(content) - if (!Array.isArray(data)) return [] - - return data - } catch { - return [] - } -} +const axios = require('axios') +const dayjs = require('dayjs') + +module.exports = { + site: 'opto.sic.pt', + days: 2, + url({ date, channel }) { + const startDate = date.unix() + const endDate = date.add(1, 'd').unix() + + return `https://opto.sic.pt/api/v1/content/epg?startDate=${startDate}&endDate=${endDate}&channels=${channel.site_id}` + }, + parser({ content }) { + let programs = [] + let items = parseItems(content) + items.forEach(item => { + programs.push({ + title: item.title, + episode: item.episode_number || null, + season: item.season_number || null, + start: dayjs.unix(item.start_time), + stop: dayjs.unix(item.end_time) + }) + }) + + return programs + }, + async channels() { + const data = await axios + .get('https://opto.sic.pt/api/v1/content/channel') + .then(r => r.data) + .catch(console.error) + + return data.map(channel => { + return { + lang: 'pt', + site_id: channel.id, + name: channel.name + } + }) + } +} + +function parseItems(content) { + try { + const data = JSON.parse(content) + if (!Array.isArray(data)) return [] + + return data + } catch { + return [] + } +} diff --git a/sites/opto.sic.pt/opto.sic.pt.test.js b/sites/opto.sic.pt/opto.sic.pt.test.js index 7d718f5f3..c9d696a55 100644 --- a/sites/opto.sic.pt/opto.sic.pt.test.js +++ b/sites/opto.sic.pt/opto.sic.pt.test.js @@ -1,52 +1,52 @@ -const { parser, url } = require('./opto.sic.pt.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-01-17', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '38719848-2a57-42e3-8640-63a9aa39f107', - xmltv_id: 'SICNoticias.pt' -} - -it('can generate valid url', () => { - expect(url({ date, channel })).toBe( - 'https://opto.sic.pt/api/v1/content/epg?startDate=1737072000&endDate=1737158400&channels=38719848-2a57-42e3-8640-63a9aa39f107' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') - let results = parser({ content }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(17) - expect(results[0]).toMatchObject({ - start: '2025-01-17T00:00:00.000Z', - stop: '2025-01-17T01:45:00.000Z', - title: 'JORNAL DA MEIA-NOITE', - episode: 16, - season: null - }) - expect(results[16]).toMatchObject({ - start: '2025-01-17T23:00:00.000Z', - stop: '2025-01-18T00:00:00.000Z', - title: 'EXPRESSO DA MEIA-NOITE', - episode: 2, - season: null - }) -}) - -it('can handle empty guide', () => { - const results = parser({ content: '' }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./opto.sic.pt.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-01-17', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '38719848-2a57-42e3-8640-63a9aa39f107', + xmltv_id: 'SICNoticias.pt' +} + +it('can generate valid url', () => { + expect(url({ date, channel })).toBe( + 'https://opto.sic.pt/api/v1/content/epg?startDate=1737072000&endDate=1737158400&channels=38719848-2a57-42e3-8640-63a9aa39f107' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') + let results = parser({ content }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(17) + expect(results[0]).toMatchObject({ + start: '2025-01-17T00:00:00.000Z', + stop: '2025-01-17T01:45:00.000Z', + title: 'JORNAL DA MEIA-NOITE', + episode: 16, + season: null + }) + expect(results[16]).toMatchObject({ + start: '2025-01-17T23:00:00.000Z', + stop: '2025-01-18T00:00:00.000Z', + title: 'EXPRESSO DA MEIA-NOITE', + episode: 2, + season: null + }) +}) + +it('can handle empty guide', () => { + const results = parser({ content: '' }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/orangetv.orange.es/orangetv.orange.es.config.js b/sites/orangetv.orange.es/orangetv.orange.es.config.js index 0988fb62a..4c3bdc300 100644 --- a/sites/orangetv.orange.es/orangetv.orange.es.config.js +++ b/sites/orangetv.orange.es/orangetv.orange.es.config.js @@ -1,99 +1,99 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const doFetch = require('@ntlab/sfetch') -const debug = require('debug')('site:orangetv.orange.es') - -dayjs.extend(utc) - -doFetch.setDebugger(debug) - -const API_PROGRAM_ENDPOINT = 'https://epg.orangetv.orange.es/epg/Smartphone_Android/1_PRO' -const API_CHANNEL_ENDPOINT = - 'https://pc.orangetv.orange.es/pc/api/rtv/v1/GetChannelList?bouquet_id=1&model_external_id=PC&filter_unsupported_channels=false&client=json' -const API_IMAGE_ENDPOINT = 'https://pc.orangetv.orange.es/pc/api/rtv/v1/images' - -module.exports = { - site: 'orangetv.orange.es', - days: 2, - request: { - cache: { - ttl: 24 * 60 * 60 * 1000 // 1 day - } - }, - url({ date, segment = 1 }) { - return `${API_PROGRAM_ENDPOINT}/${date.format('YYYYMMDD')}_8h_${segment}.json` - }, - async parser({ content, channel, date }) { - const programs = [] - const items = parseItems(content, channel) - if (items.length) { - const queues = [ - module.exports.url({ date, segment: 2 }), - module.exports.url({ date, segment: 3 }) - ] - await doFetch(queues, (url, res) => { - items.push(...parseItems(res, channel)) - }) - programs.push( - ...items.map(item => { - return { - title: item.name, - sub_title: item.seriesName, - description: item.description, - category: parseGenres(item), - season: item.seriesSeason ? parseInt(item.seriesSeason) : null, - episode: item.episodeId ? parseInt(item.episodeId) : null, - icon: parseIcon(item), - start: dayjs.utc(item.startDate), - stop: dayjs.utc(item.endDate) - } - }) - ) - } - - return programs - }, - async channels() { - const axios = require('axios') - const data = await axios - .get(API_CHANNEL_ENDPOINT) - .then(r => r.data) - .catch(console.error) - - return data.response.map(item => { - return { - lang: 'es', - name: item.name, - site_id: item.externalChannelId - } - }) - } -} - -function parseIcon(item) { - if (item.attachments.length) { - const cover = item.attachments.find(i => i.name.match(/cover/i)) - if (cover) { - return `${API_IMAGE_ENDPOINT}${cover.value}` - } - } -} - -function parseGenres(item) { - return item.genres.map(i => i.name) -} - -function parseItems(content, channel) { - const result = [] - const json = - typeof content === 'string' ? JSON.parse(content) : Array.isArray(content) ? content : [] - if (Array.isArray(json)) { - json - .filter(i => i.channelExternalId === channel.site_id) - .forEach(i => { - result.push(...i.programs) - }) - } - - return result -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const doFetch = require('@ntlab/sfetch') +const debug = require('debug')('site:orangetv.orange.es') + +dayjs.extend(utc) + +doFetch.setDebugger(debug) + +const API_PROGRAM_ENDPOINT = 'https://epg.orangetv.orange.es/epg/Smartphone_Android/1_PRO' +const API_CHANNEL_ENDPOINT = + 'https://pc.orangetv.orange.es/pc/api/rtv/v1/GetChannelList?bouquet_id=1&model_external_id=PC&filter_unsupported_channels=false&client=json' +const API_IMAGE_ENDPOINT = 'https://pc.orangetv.orange.es/pc/api/rtv/v1/images' + +module.exports = { + site: 'orangetv.orange.es', + days: 2, + request: { + cache: { + ttl: 24 * 60 * 60 * 1000 // 1 day + } + }, + url({ date, segment = 1 }) { + return `${API_PROGRAM_ENDPOINT}/${date.format('YYYYMMDD')}_8h_${segment}.json` + }, + async parser({ content, channel, date }) { + const programs = [] + const items = parseItems(content, channel) + if (items.length) { + const queues = [ + module.exports.url({ date, segment: 2 }), + module.exports.url({ date, segment: 3 }) + ] + await doFetch(queues, (url, res) => { + items.push(...parseItems(res, channel)) + }) + programs.push( + ...items.map(item => { + return { + title: item.name, + sub_title: item.seriesName, + description: item.description, + category: parseGenres(item), + season: item.seriesSeason ? parseInt(item.seriesSeason) : null, + episode: item.episodeId ? parseInt(item.episodeId) : null, + icon: parseIcon(item), + start: dayjs.utc(item.startDate), + stop: dayjs.utc(item.endDate) + } + }) + ) + } + + return programs + }, + async channels() { + const axios = require('axios') + const data = await axios + .get(API_CHANNEL_ENDPOINT) + .then(r => r.data) + .catch(console.error) + + return data.response.map(item => { + return { + lang: 'es', + name: item.name, + site_id: item.externalChannelId + } + }) + } +} + +function parseIcon(item) { + if (item.attachments.length) { + const cover = item.attachments.find(i => i.name.match(/cover/i)) + if (cover) { + return `${API_IMAGE_ENDPOINT}${cover.value}` + } + } +} + +function parseGenres(item) { + return item.genres.map(i => i.name) +} + +function parseItems(content, channel) { + const result = [] + const json = + typeof content === 'string' ? JSON.parse(content) : Array.isArray(content) ? content : [] + if (Array.isArray(json)) { + json + .filter(i => i.channelExternalId === channel.site_id) + .forEach(i => { + result.push(...i.programs) + }) + } + + return result +} diff --git a/sites/orangetv.orange.es/orangetv.orange.es.test.js b/sites/orangetv.orange.es/orangetv.orange.es.test.js index 7e92021cc..981dd8274 100644 --- a/sites/orangetv.orange.es/orangetv.orange.es.test.js +++ b/sites/orangetv.orange.es/orangetv.orange.es.test.js @@ -1,85 +1,85 @@ -const { parser, url } = require('./orangetv.orange.es.config.js') -const path = require('path') -const fs = require('fs') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -const date = dayjs.utc('2025-01-12', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '1010', - xmltv_id: 'La1.es' -} - -axios.get.mockImplementation(url => { - const result = {} - const urls = { - 'https://epg.orangetv.orange.es/epg/Smartphone_Android/1_PRO/20250112_8h_1.json': - 'data1.json', - 'https://epg.orangetv.orange.es/epg/Smartphone_Android/1_PRO/20250112_8h_2.json': - 'data2.json', - 'https://epg.orangetv.orange.es/epg/Smartphone_Android/1_PRO/20250112_8h_3.json': - 'data3.json', - } - if (urls[url] !== undefined) { - result.data = fs.readFileSync(path.join(__dirname, '__data__', urls[url])).toString() - if (!urls[url].startsWith('data1')) { - result.data = JSON.parse(result.data) - } - } - - return Promise.resolve(result) -}) - -it('can generate valid url', () => { - expect(url({ date })).toBe( - 'https://epg.orangetv.orange.es/epg/Smartphone_Android/1_PRO/20250112_8h_1.json' - ) -}) - -it('can parse response', async () => { - const content = fs.readFileSync(path.join(__dirname, '__data__', 'data1.json')).toString() - const results = (await parser({ content, channel, date })).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(18) - expect(results[0]).toMatchObject({ - start: '2025-01-11T22:55:00.000Z', - stop: '2025-01-12T00:40:00.000Z', - title: 'Una joven prometedora', - description: - 'Cassie tenía un brillante futuro por delante. Sin embargo, un incidente provocó que no pudiese cumplir sus sueños. Con el paso del tiempo, tendrá la oportunidad de enmendar los errores del pasado.', - category: ['Cine', 'Drama', 'Suspense'], - icon: 'https://pc.orangetv.orange.es/pc/api/rtv/v1/images/epg/COVER/COVER_2247567.jpg' - }) - expect(results[17]).toMatchObject({ - start: '2025-01-12T21:05:00.000Z', - stop: '2025-01-12T23:05:00.000Z', - title: 'Bake Off: Famosos al horno - T2, E01: Bake Off: Famosos al horno', - sub_title: 'Bake Off: Famosos al horno', - description: - 'Nervios y emoción en el debut de los 14 pasteleros de la nueva temporada de Bake off Famosos al horno. En el primer programa hornearán unas galletas dedicadas a sus mascotas y una tradicional tarta de queso.', - category: ['Programa', 'Reality'], - icon: 'https://pc.orangetv.orange.es/pc/api/rtv/v1/images/epg/COVER/COVER_3520028.jpg', - season: 2, - episode: 1 - }) -}) - -it('can handle empty guide', async () => { - const result = await parser({ - date, - channel, - content: '{}' - }) - expect(result).toMatchObject({}) -}) +const { parser, url } = require('./orangetv.orange.es.config.js') +const path = require('path') +const fs = require('fs') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.utc('2025-01-12', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '1010', + xmltv_id: 'La1.es' +} + +axios.get.mockImplementation(url => { + const result = {} + const urls = { + 'https://epg.orangetv.orange.es/epg/Smartphone_Android/1_PRO/20250112_8h_1.json': + 'data1.json', + 'https://epg.orangetv.orange.es/epg/Smartphone_Android/1_PRO/20250112_8h_2.json': + 'data2.json', + 'https://epg.orangetv.orange.es/epg/Smartphone_Android/1_PRO/20250112_8h_3.json': + 'data3.json', + } + if (urls[url] !== undefined) { + result.data = fs.readFileSync(path.join(__dirname, '__data__', urls[url])).toString() + if (!urls[url].startsWith('data1')) { + result.data = JSON.parse(result.data) + } + } + + return Promise.resolve(result) +}) + +it('can generate valid url', () => { + expect(url({ date })).toBe( + 'https://epg.orangetv.orange.es/epg/Smartphone_Android/1_PRO/20250112_8h_1.json' + ) +}) + +it('can parse response', async () => { + const content = fs.readFileSync(path.join(__dirname, '__data__', 'data1.json')).toString() + const results = (await parser({ content, channel, date })).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(18) + expect(results[0]).toMatchObject({ + start: '2025-01-11T22:55:00.000Z', + stop: '2025-01-12T00:40:00.000Z', + title: 'Una joven prometedora', + description: + 'Cassie tenía un brillante futuro por delante. Sin embargo, un incidente provocó que no pudiese cumplir sus sueños. Con el paso del tiempo, tendrá la oportunidad de enmendar los errores del pasado.', + category: ['Cine', 'Drama', 'Suspense'], + icon: 'https://pc.orangetv.orange.es/pc/api/rtv/v1/images/epg/COVER/COVER_2247567.jpg' + }) + expect(results[17]).toMatchObject({ + start: '2025-01-12T21:05:00.000Z', + stop: '2025-01-12T23:05:00.000Z', + title: 'Bake Off: Famosos al horno - T2, E01: Bake Off: Famosos al horno', + sub_title: 'Bake Off: Famosos al horno', + description: + 'Nervios y emoción en el debut de los 14 pasteleros de la nueva temporada de Bake off Famosos al horno. En el primer programa hornearán unas galletas dedicadas a sus mascotas y una tradicional tarta de queso.', + category: ['Programa', 'Reality'], + icon: 'https://pc.orangetv.orange.es/pc/api/rtv/v1/images/epg/COVER/COVER_3520028.jpg', + season: 2, + episode: 1 + }) +}) + +it('can handle empty guide', async () => { + const result = await parser({ + date, + channel, + content: '{}' + }) + expect(result).toMatchObject({}) +}) diff --git a/sites/osn.com/osn.com.config.js b/sites/osn.com/osn.com.config.js index ece2bf72b..c2b6bd77e 100644 --- a/sites/osn.com/osn.com.config.js +++ b/sites/osn.com/osn.com.config.js @@ -1,73 +1,73 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') - -dayjs.extend(utc) -dayjs.extend(timezone) - -const packages = { - 'OSNTV CONNECT': 3720, - 'OSNTV PRIME': 3733, - ALFA: 1281, - 'OSN PINOY PLUS EXTRA': 3519 -} -const country = 'AE' -const tz = 'Asia/Dubai' - -module.exports = { - site: 'osn.com', - days: 2, - url({ channel, date }) { - return `https://www.osn.com/api/TVScheduleWebService.asmx/time?dt=${encodeURIComponent( - date.format('MM/DD/YYYY') - )}&co=${country}&ch=${channel.site_id}&mo=false&hr=0` - }, - request: { - headers({ channel }) { - return { - Referer: `https://www.osn.com/${channel.lang}-${country.toLowerCase()}/watch/tv-schedule` - } - } - }, - parser({ content, channel }) { - const programs = [] - const items = JSON.parse(content) || [] - if (Array.isArray(items)) { - for (const item of items) { - const title = channel.lang === 'ar' ? item.Arab_Title : item.Title - const start = dayjs.tz(item.StartDateTime, 'DD MMM YYYY, HH:mm', tz) - const duration = parseInt(item.TotalDivWidth / 4.8) - const stop = start.add(duration, 'm') - programs.push({ title, start, stop }) - } - } - - return programs - }, - async channels({ lang = 'ar' }) { - const result = {} - const axios = require('axios') - for (const pkg of Object.values(packages)) { - const channels = await axios - .get( - `https://www.osn.com/api/tvchannels.ashx?culture=en-US&packageId=${pkg}&country=${country}` - ) - .then(response => response.data) - .catch(console.error) - - if (Array.isArray(channels)) { - for (const ch of channels) { - if (result[ch.channelCode] === undefined) { - result[ch.channelCode] = { - lang, - site_id: ch.channelCode, - name: ch.channeltitle - } - } - } - } - } - - return Object.values(result) - } -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') + +dayjs.extend(utc) +dayjs.extend(timezone) + +const packages = { + 'OSNTV CONNECT': 3720, + 'OSNTV PRIME': 3733, + ALFA: 1281, + 'OSN PINOY PLUS EXTRA': 3519 +} +const country = 'AE' +const tz = 'Asia/Dubai' + +module.exports = { + site: 'osn.com', + days: 2, + url({ channel, date }) { + return `https://www.osn.com/api/TVScheduleWebService.asmx/time?dt=${encodeURIComponent( + date.format('MM/DD/YYYY') + )}&co=${country}&ch=${channel.site_id}&mo=false&hr=0` + }, + request: { + headers({ channel }) { + return { + Referer: `https://www.osn.com/${channel.lang}-${country.toLowerCase()}/watch/tv-schedule` + } + } + }, + parser({ content, channel }) { + const programs = [] + const items = JSON.parse(content) || [] + if (Array.isArray(items)) { + for (const item of items) { + const title = channel.lang === 'ar' ? item.Arab_Title : item.Title + const start = dayjs.tz(item.StartDateTime, 'DD MMM YYYY, HH:mm', tz) + const duration = parseInt(item.TotalDivWidth / 4.8) + const stop = start.add(duration, 'm') + programs.push({ title, start, stop }) + } + } + + return programs + }, + async channels({ lang = 'ar' }) { + const result = {} + const axios = require('axios') + for (const pkg of Object.values(packages)) { + const channels = await axios + .get( + `https://www.osn.com/api/tvchannels.ashx?culture=en-US&packageId=${pkg}&country=${country}` + ) + .then(response => response.data) + .catch(console.error) + + if (Array.isArray(channels)) { + for (const ch of channels) { + if (result[ch.channelCode] === undefined) { + result[ch.channelCode] = { + lang, + site_id: ch.channelCode, + name: ch.channeltitle + } + } + } + } + } + + return Object.values(result) + } +} diff --git a/sites/osn.com/osn.com.test.js b/sites/osn.com/osn.com.test.js index fb2fcfbcc..c10c9d86c 100644 --- a/sites/osn.com/osn.com.test.js +++ b/sites/osn.com/osn.com.test.js @@ -1,61 +1,61 @@ -const { parser, url, request } = require('./osn.com.config') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2024-11-27', 'YYYY-MM-DD').startOf('d') -const channelAR = { site_id: 'FTF', xmltv_id: 'Fatafeat.ae', lang: 'ar' } -const channelEN = { site_id: 'FTF', xmltv_id: 'Fatafeat.ae', lang: 'en' } -const content = fs.readFileSync(path.join(__dirname, '__data__', 'content.json')) - -it('can generate valid request headers', () => { - const result = request.headers({ channel: channelAR, date }) - expect(result).toMatchObject({ - Referer: 'https://www.osn.com/ar-ae/watch/tv-schedule' - }) -}) - -it('can generate valid url', () => { - const result = url({ channel: channelAR, date }) - expect(result).toBe( - 'https://www.osn.com/api/TVScheduleWebService.asmx/time?dt=11%2F27%2F2024&co=AE&ch=FTF&mo=false&hr=0' - ) -}) - -it('can parse response (ar)', () => { - const result = parser({ date, channel: channelAR, content }).map(a => { - a.start = a.start.toJSON() - a.stop = a.stop.toJSON() - return a - }) - expect(result.length).toBe(29) - expect(result[1]).toMatchObject({ - start: '2024-11-26T20:50:00.000Z', - stop: '2024-11-26T21:45:00.000Z', - title: 'بيت الحلويات: الحلقة 3' - }) -}) - -it('can parse response (en)', () => { - const result = parser({ date, channel: channelEN, content }).map(a => { - a.start = a.start.toJSON() - a.stop = a.stop.toJSON() - return a - }) - expect(result.length).toBe(29) - expect(result[1]).toMatchObject({ - start: '2024-11-26T20:50:00.000Z', - stop: '2024-11-26T21:45:00.000Z', - title: 'House Of Desserts: Episode 3' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ date, channel: channelAR, content: '[]' }) - expect(result).toMatchObject([]) -}) +const { parser, url, request } = require('./osn.com.config') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2024-11-27', 'YYYY-MM-DD').startOf('d') +const channelAR = { site_id: 'FTF', xmltv_id: 'Fatafeat.ae', lang: 'ar' } +const channelEN = { site_id: 'FTF', xmltv_id: 'Fatafeat.ae', lang: 'en' } +const content = fs.readFileSync(path.join(__dirname, '__data__', 'content.json')) + +it('can generate valid request headers', () => { + const result = request.headers({ channel: channelAR, date }) + expect(result).toMatchObject({ + Referer: 'https://www.osn.com/ar-ae/watch/tv-schedule' + }) +}) + +it('can generate valid url', () => { + const result = url({ channel: channelAR, date }) + expect(result).toBe( + 'https://www.osn.com/api/TVScheduleWebService.asmx/time?dt=11%2F27%2F2024&co=AE&ch=FTF&mo=false&hr=0' + ) +}) + +it('can parse response (ar)', () => { + const result = parser({ date, channel: channelAR, content }).map(a => { + a.start = a.start.toJSON() + a.stop = a.stop.toJSON() + return a + }) + expect(result.length).toBe(29) + expect(result[1]).toMatchObject({ + start: '2024-11-26T20:50:00.000Z', + stop: '2024-11-26T21:45:00.000Z', + title: 'بيت الحلويات: الحلقة 3' + }) +}) + +it('can parse response (en)', () => { + const result = parser({ date, channel: channelEN, content }).map(a => { + a.start = a.start.toJSON() + a.stop = a.stop.toJSON() + return a + }) + expect(result.length).toBe(29) + expect(result[1]).toMatchObject({ + start: '2024-11-26T20:50:00.000Z', + stop: '2024-11-26T21:45:00.000Z', + title: 'House Of Desserts: Episode 3' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ date, channel: channelAR, content: '[]' }) + expect(result).toMatchObject([]) +}) diff --git a/sites/pbsguam.org/pbsguam.org.config.js b/sites/pbsguam.org/pbsguam.org.config.js index 504778df5..caa3cf498 100644 --- a/sites/pbsguam.org/pbsguam.org.config.js +++ b/sites/pbsguam.org/pbsguam.org.config.js @@ -1,41 +1,41 @@ -const dayjs = require('dayjs') -const isBetween = require('dayjs/plugin/isBetween') - -dayjs.extend(isBetween) - -module.exports = { - site: 'pbsguam.org', - days: 2, - url: 'https://pbsguam.org/calendar/', - parser: function ({ content, date }) { - let programs = [] - const items = parseItems(content, date) - items.forEach(item => { - programs.push({ - title: item.title, - start: dayjs(item.start), - stop: dayjs(item.end) - }) - }) - - return programs - } -} - -function parseItems(content, date) { - const [, json] = content.match(/EventsSchedule_1 = (.*);/i) || [null, ''] - let data - try { - data = JSON.parse(json) - } catch { - return [] - } - - if (!data || !Array.isArray(data.feed)) return [] - - return data.feed.filter( - i => - dayjs(i.start).isBetween(date, date.add(1, 'd')) || - dayjs(i.end).isBetween(date, date.add(1, 'd')) - ) -} +const dayjs = require('dayjs') +const isBetween = require('dayjs/plugin/isBetween') + +dayjs.extend(isBetween) + +module.exports = { + site: 'pbsguam.org', + days: 2, + url: 'https://pbsguam.org/calendar/', + parser: function ({ content, date }) { + let programs = [] + const items = parseItems(content, date) + items.forEach(item => { + programs.push({ + title: item.title, + start: dayjs(item.start), + stop: dayjs(item.end) + }) + }) + + return programs + } +} + +function parseItems(content, date) { + const [, json] = content.match(/EventsSchedule_1 = (.*);/i) || [null, ''] + let data + try { + data = JSON.parse(json) + } catch { + return [] + } + + if (!data || !Array.isArray(data.feed)) return [] + + return data.feed.filter( + i => + dayjs(i.start).isBetween(date, date.add(1, 'd')) || + dayjs(i.end).isBetween(date, date.add(1, 'd')) + ) +} diff --git a/sites/pickx.be/pickx.be.config.js b/sites/pickx.be/pickx.be.config.js index 0f692f6cb..a4011b330 100644 --- a/sites/pickx.be/pickx.be.config.js +++ b/sites/pickx.be/pickx.be.config.js @@ -1,131 +1,131 @@ -const axios = require('axios') -const dayjs = require('dayjs') - -let apiVersion - -module.exports = { - site: 'pickx.be', - days: 2, - async url({ channel, date }) { - if (!apiVersion) { - await fetchApiVersion() - } - - return `https://px-epg.azureedge.net/airings/${apiVersion}/${date.format( - 'YYYY-MM-DD' - )}/channel/${channel.site_id}?timezone=Europe%2FBrussels` - }, - request: { - headers: { - Origin: 'https://www.pickx.be', - Referer: 'https://www.pickx.be/' - } - }, - parser({ channel, content }) { - const programs = [] - if (content) { - const items = JSON.parse(content) - items.forEach(item => { - programs.push({ - title: item.program.title, - sub_title: item.program.episodeTitle, - description: item.program.description, - category: item.program.translatedCategory?.[channel.lang] - ? item.program.translatedCategory[channel.lang] - : item.program.category.split('.')[1], - image: item.program.posterFileName - ? `https://experience-cache.proximustv.be/posterserver/poster/EPG/w-166_h-110/${item.program.posterFileName}` - : null, - season: item.program.seasonNumber, - episode: item.program.episodeNumber, - actors: item.program.actors, - director: item.program.director ? [item.program.director] : null, - start: dayjs(item.programScheduleStart), - stop: dayjs(item.programScheduleEnd) - }) - }) - } - - return programs - }, - async channels() { - let channels = [] - - const query = { - operationName: 'getChannels', - variables: { - language: 'fr', - queryParams: {}, - id: '0', - params: { - shouldReadFromCache: true - } - }, - query: `query getChannels($language: String!, $queryParams: ChannelQueryParams, $id: String, $params: ChannelParams) { - channels(language: $language, queryParams: $queryParams, id: $id, params: $params) { - id - name - language - radio - } - }` - } - - const data = await axios - .post('https://api.proximusmwc.be/tiams/v3/graphql', query) - .then(r => r.data) - .catch(console.error) - - data.data.channels.forEach(channel => { - let lang = channel.language || 'fr' - if (channel.language === 'ger') lang = 'de' - - channels.push({ - lang, - site_id: channel.id, - name: channel.name - }) - }) - - return channels - } -} - -async function fetchApiVersion() { - const hashUrl = 'https://www.pickx.be/nl/televisie/tv-gids' - const hashData = await axios - .get(hashUrl) - .then(r => { - const re = /"hashes":\["(.*)"\]/ - const match = r.data.match(re) - if (match && match[1]) { - return match[1] - } else { - throw new Error('React app version hash not found') - } - }) - .catch(console.error) - - const versionUrl = `https://www.pickx.be/api/s-${hashData}` - const response = await axios.get(versionUrl, { - headers: { - Origin: 'https://www.pickx.be', - Referer: 'https://www.pickx.be/' - } - }) - - return new Promise((resolve, reject) => { - try { - if (response.status === 200) { - apiVersion = response.data.version - resolve() - } else { - console.error(`Failed to fetch API version. Status: ${response.status}`) - reject(`Failed to fetch API version. Status: ${response.status}`) - } - } catch (error) { - console.error('Error during fetchApiVersion:', error) - reject(error) - } - }) -} +const axios = require('axios') +const dayjs = require('dayjs') + +let apiVersion + +module.exports = { + site: 'pickx.be', + days: 2, + async url({ channel, date }) { + if (!apiVersion) { + await fetchApiVersion() + } + + return `https://px-epg.azureedge.net/airings/${apiVersion}/${date.format( + 'YYYY-MM-DD' + )}/channel/${channel.site_id}?timezone=Europe%2FBrussels` + }, + request: { + headers: { + Origin: 'https://www.pickx.be', + Referer: 'https://www.pickx.be/' + } + }, + parser({ channel, content }) { + const programs = [] + if (content) { + const items = JSON.parse(content) + items.forEach(item => { + programs.push({ + title: item.program.title, + sub_title: item.program.episodeTitle, + description: item.program.description, + category: item.program.translatedCategory?.[channel.lang] + ? item.program.translatedCategory[channel.lang] + : item.program.category.split('.')[1], + image: item.program.posterFileName + ? `https://experience-cache.proximustv.be/posterserver/poster/EPG/w-166_h-110/${item.program.posterFileName}` + : null, + season: item.program.seasonNumber, + episode: item.program.episodeNumber, + actors: item.program.actors, + director: item.program.director ? [item.program.director] : null, + start: dayjs(item.programScheduleStart), + stop: dayjs(item.programScheduleEnd) + }) + }) + } + + return programs + }, + async channels() { + let channels = [] + + const query = { + operationName: 'getChannels', + variables: { + language: 'fr', + queryParams: {}, + id: '0', + params: { + shouldReadFromCache: true + } + }, + query: `query getChannels($language: String!, $queryParams: ChannelQueryParams, $id: String, $params: ChannelParams) { + channels(language: $language, queryParams: $queryParams, id: $id, params: $params) { + id + name + language + radio + } + }` + } + + const data = await axios + .post('https://api.proximusmwc.be/tiams/v3/graphql', query) + .then(r => r.data) + .catch(console.error) + + data.data.channels.forEach(channel => { + let lang = channel.language || 'fr' + if (channel.language === 'ger') lang = 'de' + + channels.push({ + lang, + site_id: channel.id, + name: channel.name + }) + }) + + return channels + } +} + +async function fetchApiVersion() { + const hashUrl = 'https://www.pickx.be/nl/televisie/tv-gids' + const hashData = await axios + .get(hashUrl) + .then(r => { + const re = /"hashes":\["(.*)"\]/ + const match = r.data.match(re) + if (match && match[1]) { + return match[1] + } else { + throw new Error('React app version hash not found') + } + }) + .catch(console.error) + + const versionUrl = `https://www.pickx.be/api/s-${hashData}` + const response = await axios.get(versionUrl, { + headers: { + Origin: 'https://www.pickx.be', + Referer: 'https://www.pickx.be/' + } + }) + + return new Promise((resolve, reject) => { + try { + if (response.status === 200) { + apiVersion = response.data.version + resolve() + } else { + console.error(`Failed to fetch API version. Status: ${response.status}`) + reject(`Failed to fetch API version. Status: ${response.status}`) + } + } catch (error) { + console.error('Error during fetchApiVersion:', error) + reject(error) + } + }) +} diff --git a/sites/pickx.be/pickx.be.test.js b/sites/pickx.be/pickx.be.test.js index ee11e679b..3dd95ee8e 100644 --- a/sites/pickx.be/pickx.be.test.js +++ b/sites/pickx.be/pickx.be.test.js @@ -1,83 +1,83 @@ -const { parser, url, request } = require('./pickx.be.config.js') -const axios = require('axios') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -dayjs.extend(utc) - -jest.mock('axios') - -axios.get.mockImplementation((url, data) => { - if (url === 'https://www.pickx.be/nl/televisie/tv-gids') { - return Promise.resolve({ - data: fs.readFileSync(path.resolve(__dirname, '__data__/hash.html'), 'utf8') - }) - } else if ( - url === - 'https://www.pickx.be/api/s-375ce5e452cf964b4158545d9ddf26cc97d6411f0998a2fa7ed5922c88d5bdc4' && - JSON.stringify(data) === - JSON.stringify({ - headers: { - Origin: 'https://www.pickx.be', - Referer: 'https://www.pickx.be/' - } - }) - ) { - return Promise.resolve({ - status: 200, - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/version.json'))) - }) - } else { - return Promise.resolve({ - data: '' - }) - } -}) - -const date = dayjs.utc('2023-12-13').startOf('d') -const channel = { - lang: 'fr', - site_id: 'UID0118' -} - -it('can generate valid url', async () => { - expect(await url({ channel, date })).toBe( - 'https://px-epg.azureedge.net/airings/21738594888692v.4.2/2023-12-13/channel/UID0118?timezone=Europe%2FBrussels' - ) -}) - -it('can generate valid request headers', () => { - expect(request.headers).toMatchObject({ - Origin: 'https://www.pickx.be', - Referer: 'https://www.pickx.be/' - }) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/data.json')) - const result = parser({ content, channel, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result[0]).toMatchObject({ - start: '2023-12-12T23:55:00.000Z', - stop: '2023-12-13T00:15:00.000Z', - title: 'Le 22h30', - description: 'Le journal de vivre ici.', - category: 'Info', - image: - 'https://experience-cache.proximustv.be/posterserver/poster/EPG/w-166_h-110/250_250_4B990CC58066A7B2A660AFA0BDDE5C41.jpg' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: '' - }) - expect(result).toMatchObject([]) -}) +const { parser, url, request } = require('./pickx.be.config.js') +const axios = require('axios') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +dayjs.extend(utc) + +jest.mock('axios') + +axios.get.mockImplementation((url, data) => { + if (url === 'https://www.pickx.be/nl/televisie/tv-gids') { + return Promise.resolve({ + data: fs.readFileSync(path.resolve(__dirname, '__data__/hash.html'), 'utf8') + }) + } else if ( + url === + 'https://www.pickx.be/api/s-375ce5e452cf964b4158545d9ddf26cc97d6411f0998a2fa7ed5922c88d5bdc4' && + JSON.stringify(data) === + JSON.stringify({ + headers: { + Origin: 'https://www.pickx.be', + Referer: 'https://www.pickx.be/' + } + }) + ) { + return Promise.resolve({ + status: 200, + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/version.json'))) + }) + } else { + return Promise.resolve({ + data: '' + }) + } +}) + +const date = dayjs.utc('2023-12-13').startOf('d') +const channel = { + lang: 'fr', + site_id: 'UID0118' +} + +it('can generate valid url', async () => { + expect(await url({ channel, date })).toBe( + 'https://px-epg.azureedge.net/airings/21738594888692v.4.2/2023-12-13/channel/UID0118?timezone=Europe%2FBrussels' + ) +}) + +it('can generate valid request headers', () => { + expect(request.headers).toMatchObject({ + Origin: 'https://www.pickx.be', + Referer: 'https://www.pickx.be/' + }) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/data.json')) + const result = parser({ content, channel, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result[0]).toMatchObject({ + start: '2023-12-12T23:55:00.000Z', + stop: '2023-12-13T00:15:00.000Z', + title: 'Le 22h30', + description: 'Le journal de vivre ici.', + category: 'Info', + image: + 'https://experience-cache.proximustv.be/posterserver/poster/EPG/w-166_h-110/250_250_4B990CC58066A7B2A660AFA0BDDE5C41.jpg' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: '' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/player.ee.co.uk/player.ee.co.uk.config.js b/sites/player.ee.co.uk/player.ee.co.uk.config.js index 6914b0170..21258e986 100644 --- a/sites/player.ee.co.uk/player.ee.co.uk.config.js +++ b/sites/player.ee.co.uk/player.ee.co.uk.config.js @@ -1,104 +1,104 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') - -dayjs.extend(utc) - -module.exports = { - site: 'player.ee.co.uk', - days: 2, - url({ date, channel, hour = 0 }) { - return `https://api.youview.tv/metadata/linear/v2/schedule/by-servicelocator?serviceLocator=${encodeURIComponent( - channel.site_id - )}&interval=${date.format('YYYY-MM-DD')}T${hour.toString().padStart(2, '0')}Z/PT12H` - }, - request: { - headers: { - Referer: 'https://player.ee.co.uk/' - } - }, - async parser({ content, channel, date }) { - const programs = [] - if (content) { - const schedule = JSON.parse(content) - // fetch next 12 hours schedule - const { url, request } = module.exports - const nextSchedule = await axios - .get(url({ channel, date, hour: 12 }), { headers: request.headers }) - .then(response => response.data) - .catch(console.error) - - if (schedule?.items) { - // merge schedules - if (nextSchedule?.items) { - schedule.items.push(...nextSchedule.items) - } - schedule.items.forEach(item => { - let season, episode - const start = dayjs.utc(item.publishedStartTime) - const stop = start.add(item.publishedDuration, 's') - const description = item.synopsis - if (description) { - const matches = description.trim().match(/\(?S(\d+)[/\s]Ep(\d+)\)?/) - if (matches) { - if (matches[1]) { - season = parseInt(matches[1]) - } - if (matches[2]) { - episode = parseInt(matches[2]) - } - } - } - programs.push({ - title: item.title, - description, - season, - episode, - start, - stop - }) - }) - } - } - - return programs - }, - async channels() { - const token = - 'eyJkaXNjb3ZlcnlVc2VyR3JvdXBzIjpbIkFMTFVTRVJTIiwiYWxsIiwiaHR0cDovL3JlZmRhd' + - 'GEueW91dmlldy5jb20vbXBlZzdjcy9Zb3VWaWV3QXBwbGljYXRpb25QbGF5ZXJDUy8yMDIxLT' + - 'A5LTEwI2FuZHJvaWRfcnVudGltZS1wcm9maWxlMSIsInRhZzpidC5jb20sMjAxOC0wNy0xMTp' + - '1c2VyZ3JvdXAjR0JSLWJ0X25vd1RWX211bHRpY2FzdCIsInRhZzpidC5jb20sMjAyMS0xMC0y' + - 'NTp1c2VyZ3JvdXAjR0JSLWJ0X2V1cm9zcG9ydCJdLCJyZWdpb25zIjpbIkFMTFJFR0lPTlMiL' + - 'CJHQlIiLCJHQlItRU5HIiwiR0JSLUVORy1sb25kb24iLCJhbGwiXSwic3Vic2V0IjoiMy41Lj' + - 'EvYW5kcm9pZF9ydW50aW1lLXByb2ZpbGUxL0JST0FEQ0FTVF9JUC9HQlItYnRfYnJvYWRiYW5' + - 'kIiwic3Vic2V0cyI6WyIvLy8iLCIvL0JST0FEQ0FTVF9JUC8iLCIzLjUvLy8iXX0=' - const extensions = [ - 'LinearCategoriesExtension', - 'LogicalChannelNumberExtension', - 'BTSubscriptionCodesExtension' - ] - const result = await axios - .get('https://api.youview.tv/metadata/linear/v2/linear-services', { - params: { - contentTargetingToken: token, - extensions: extensions.join(',') - }, - headers: module.exports.request.headers - }) - .then(response => response.data) - .catch(console.error) - - return ( - result?.items - .filter(channel => channel.contentTypes.indexOf('tv') >= 0) - .map(channel => { - return { - lang: 'en', - site_id: channel.serviceLocator, - name: channel.fullName - } - }) || [] - ) - } -} +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') + +dayjs.extend(utc) + +module.exports = { + site: 'player.ee.co.uk', + days: 2, + url({ date, channel, hour = 0 }) { + return `https://api.youview.tv/metadata/linear/v2/schedule/by-servicelocator?serviceLocator=${encodeURIComponent( + channel.site_id + )}&interval=${date.format('YYYY-MM-DD')}T${hour.toString().padStart(2, '0')}Z/PT12H` + }, + request: { + headers: { + Referer: 'https://player.ee.co.uk/' + } + }, + async parser({ content, channel, date }) { + const programs = [] + if (content) { + const schedule = JSON.parse(content) + // fetch next 12 hours schedule + const { url, request } = module.exports + const nextSchedule = await axios + .get(url({ channel, date, hour: 12 }), { headers: request.headers }) + .then(response => response.data) + .catch(console.error) + + if (schedule?.items) { + // merge schedules + if (nextSchedule?.items) { + schedule.items.push(...nextSchedule.items) + } + schedule.items.forEach(item => { + let season, episode + const start = dayjs.utc(item.publishedStartTime) + const stop = start.add(item.publishedDuration, 's') + const description = item.synopsis + if (description) { + const matches = description.trim().match(/\(?S(\d+)[/\s]Ep(\d+)\)?/) + if (matches) { + if (matches[1]) { + season = parseInt(matches[1]) + } + if (matches[2]) { + episode = parseInt(matches[2]) + } + } + } + programs.push({ + title: item.title, + description, + season, + episode, + start, + stop + }) + }) + } + } + + return programs + }, + async channels() { + const token = + 'eyJkaXNjb3ZlcnlVc2VyR3JvdXBzIjpbIkFMTFVTRVJTIiwiYWxsIiwiaHR0cDovL3JlZmRhd' + + 'GEueW91dmlldy5jb20vbXBlZzdjcy9Zb3VWaWV3QXBwbGljYXRpb25QbGF5ZXJDUy8yMDIxLT' + + 'A5LTEwI2FuZHJvaWRfcnVudGltZS1wcm9maWxlMSIsInRhZzpidC5jb20sMjAxOC0wNy0xMTp' + + '1c2VyZ3JvdXAjR0JSLWJ0X25vd1RWX211bHRpY2FzdCIsInRhZzpidC5jb20sMjAyMS0xMC0y' + + 'NTp1c2VyZ3JvdXAjR0JSLWJ0X2V1cm9zcG9ydCJdLCJyZWdpb25zIjpbIkFMTFJFR0lPTlMiL' + + 'CJHQlIiLCJHQlItRU5HIiwiR0JSLUVORy1sb25kb24iLCJhbGwiXSwic3Vic2V0IjoiMy41Lj' + + 'EvYW5kcm9pZF9ydW50aW1lLXByb2ZpbGUxL0JST0FEQ0FTVF9JUC9HQlItYnRfYnJvYWRiYW5' + + 'kIiwic3Vic2V0cyI6WyIvLy8iLCIvL0JST0FEQ0FTVF9JUC8iLCIzLjUvLy8iXX0=' + const extensions = [ + 'LinearCategoriesExtension', + 'LogicalChannelNumberExtension', + 'BTSubscriptionCodesExtension' + ] + const result = await axios + .get('https://api.youview.tv/metadata/linear/v2/linear-services', { + params: { + contentTargetingToken: token, + extensions: extensions.join(',') + }, + headers: module.exports.request.headers + }) + .then(response => response.data) + .catch(console.error) + + return ( + result?.items + .filter(channel => channel.contentTypes.indexOf('tv') >= 0) + .map(channel => { + return { + lang: 'en', + site_id: channel.serviceLocator, + name: channel.fullName + } + }) || [] + ) + } +} diff --git a/sites/player.ee.co.uk/player.ee.co.uk.test.js b/sites/player.ee.co.uk/player.ee.co.uk.test.js index 30424e744..99619111a 100644 --- a/sites/player.ee.co.uk/player.ee.co.uk.test.js +++ b/sites/player.ee.co.uk/player.ee.co.uk.test.js @@ -1,74 +1,74 @@ -const { parser, url } = require('./player.ee.co.uk.config.js') -const fs = require('fs') -const path = require('path') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') - -dayjs.extend(utc) - -jest.mock('axios') - -const date = dayjs.utc('2023-12-13').startOf('d') -const channel = { - site_id: 'dvb://233a..6d60', - xmltv_id: 'HGTV.uk' -} - -axios.get.mockImplementation(url => { - if ( - url === - 'https://api.youview.tv/metadata/linear/v2/schedule/by-servicelocator?serviceLocator=dvb%3A%2F%2F233a..6d60&interval=2023-12-13T12Z/PT12H' - ) { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/data1.json'))) - }) - } - - return Promise.resolve({ data: '' }) -}) - -it('can generate valid url', () => { - expect(url({ date, channel })).toBe( - 'https://api.youview.tv/metadata/linear/v2/schedule/by-servicelocator?serviceLocator=dvb%3A%2F%2F233a..6d60&interval=2023-12-13T00Z/PT12H' - ) -}) - -it('can parse response', async () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/data.json')) - const result = (await parser({ content, channel, date })).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - title: 'Bargain Mansions', - description: - 'Tamara and her dad help a recent widow who loves to cook for her family design her dream kitchen, perfect for entertaining and large gatherings. S4/Ep1', - season: 4, - episode: 1, - start: '2023-12-13T13:00:00.000Z', - stop: '2023-12-13T14:00:00.000Z' - }, - { - title: 'Flip Or Flop', - description: - 'Tarek and Christina are contacted by a cash strapped flipper who needs to unload a project house. S2/Ep2', - season: 2, - episode: 2, - start: '2023-12-13T14:00:00.000Z', - stop: '2023-12-13T14:30:00.000Z' - } - ]) -}) - -it('can handle empty guide', async () => { - const result = await parser({ - channel, - date, - content: '' - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./player.ee.co.uk.config.js') +const fs = require('fs') +const path = require('path') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') + +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.utc('2023-12-13').startOf('d') +const channel = { + site_id: 'dvb://233a..6d60', + xmltv_id: 'HGTV.uk' +} + +axios.get.mockImplementation(url => { + if ( + url === + 'https://api.youview.tv/metadata/linear/v2/schedule/by-servicelocator?serviceLocator=dvb%3A%2F%2F233a..6d60&interval=2023-12-13T12Z/PT12H' + ) { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/data1.json'))) + }) + } + + return Promise.resolve({ data: '' }) +}) + +it('can generate valid url', () => { + expect(url({ date, channel })).toBe( + 'https://api.youview.tv/metadata/linear/v2/schedule/by-servicelocator?serviceLocator=dvb%3A%2F%2F233a..6d60&interval=2023-12-13T00Z/PT12H' + ) +}) + +it('can parse response', async () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/data.json')) + const result = (await parser({ content, channel, date })).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + title: 'Bargain Mansions', + description: + 'Tamara and her dad help a recent widow who loves to cook for her family design her dream kitchen, perfect for entertaining and large gatherings. S4/Ep1', + season: 4, + episode: 1, + start: '2023-12-13T13:00:00.000Z', + stop: '2023-12-13T14:00:00.000Z' + }, + { + title: 'Flip Or Flop', + description: + 'Tarek and Christina are contacted by a cash strapped flipper who needs to unload a project house. S2/Ep2', + season: 2, + episode: 2, + start: '2023-12-13T14:00:00.000Z', + stop: '2023-12-13T14:30:00.000Z' + } + ]) +}) + +it('can handle empty guide', async () => { + const result = await parser({ + channel, + date, + content: '' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/playtv.unifi.com.my/playtv.unifi.com.my.config.js b/sites/playtv.unifi.com.my/playtv.unifi.com.my.config.js index d426eeb57..b3e533e2b 100644 --- a/sites/playtv.unifi.com.my/playtv.unifi.com.my.config.js +++ b/sites/playtv.unifi.com.my/playtv.unifi.com.my.config.js @@ -1,85 +1,85 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'playtv.unifi.com.my', - days: 2, - url: 'https://unifi.com.my/tv/api/tv', - request: { - cache: { - ttl: 60 * 60 * 1000 // 1 hour - }, - method: 'POST', - headers: { - 'x-requested-with': 'XMLHttpRequest' - }, - data({ date }) { - const params = new URLSearchParams() - params.append('date', date.format('YYYY-MM-DD')) - return params - } - }, - parser({ content, channel, date }) { - let programs = [] - const items = parseItems(content, channel) - items.forEach(item => { - const start = parseStart(item, date) - const stop = start.add(item.minute, 'minute') - programs.push({ - title: item.name, - start, - stop - }) - }) - return programs - }, - async channels() { - const axios = require('axios') - const data = await axios - .post( - 'https://playtv.unifi.com.my:7053/VSP/V3/QueryAllChannel', - { isReturnAllMedia: '0' }, - { - params: { - userFilter: '-1880777955', - from: 'inMSAAccess' - } - } - ) - .then(r => r.data) - .catch(console.log) - - return data.channelDetails.map(item => { - return { - lang: 'en', - site_id: item.ID, - name: item.name - } - }) - } -} - -function parseItems(content, channel) { - try { - const [, string] = content.match(/initializeClient(.*)$/) - const data = JSON.parse(string) - if (!data) return [] - if (!Array.isArray(data)) return [] - - const channelData = data.find(i => i.id == channel.site_id) - return channelData.items && Array.isArray(channelData.items) ? channelData.items : [] - } catch { - return [] - } -} - -function parseStart(item, date) { - const time = `${date.format('YYYY-MM-DD')} ${item.start_time}` - return dayjs.tz(time, 'YYYY-MM-DD H:mma', 'Asia/Kuala_Lumpur') -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'playtv.unifi.com.my', + days: 2, + url: 'https://unifi.com.my/tv/api/tv', + request: { + cache: { + ttl: 60 * 60 * 1000 // 1 hour + }, + method: 'POST', + headers: { + 'x-requested-with': 'XMLHttpRequest' + }, + data({ date }) { + const params = new URLSearchParams() + params.append('date', date.format('YYYY-MM-DD')) + return params + } + }, + parser({ content, channel, date }) { + let programs = [] + const items = parseItems(content, channel) + items.forEach(item => { + const start = parseStart(item, date) + const stop = start.add(item.minute, 'minute') + programs.push({ + title: item.name, + start, + stop + }) + }) + return programs + }, + async channels() { + const axios = require('axios') + const data = await axios + .post( + 'https://playtv.unifi.com.my:7053/VSP/V3/QueryAllChannel', + { isReturnAllMedia: '0' }, + { + params: { + userFilter: '-1880777955', + from: 'inMSAAccess' + } + } + ) + .then(r => r.data) + .catch(console.log) + + return data.channelDetails.map(item => { + return { + lang: 'en', + site_id: item.ID, + name: item.name + } + }) + } +} + +function parseItems(content, channel) { + try { + const [, string] = content.match(/initializeClient(.*)$/) + const data = JSON.parse(string) + if (!data) return [] + if (!Array.isArray(data)) return [] + + const channelData = data.find(i => i.id == channel.site_id) + return channelData.items && Array.isArray(channelData.items) ? channelData.items : [] + } catch { + return [] + } +} + +function parseStart(item, date) { + const time = `${date.format('YYYY-MM-DD')} ${item.start_time}` + return dayjs.tz(time, 'YYYY-MM-DD H:mma', 'Asia/Kuala_Lumpur') +} diff --git a/sites/playtv.unifi.com.my/playtv.unifi.com.my.test.js b/sites/playtv.unifi.com.my/playtv.unifi.com.my.test.js index 5b1fca9f9..2fc223f05 100644 --- a/sites/playtv.unifi.com.my/playtv.unifi.com.my.test.js +++ b/sites/playtv.unifi.com.my/playtv.unifi.com.my.test.js @@ -1,55 +1,55 @@ -const { parser, url, request } = require('./playtv.unifi.com.my.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-01-13', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '20000009', - xmltv_id: 'TV1.my' -} - -it('can generate valid url', () => { - expect(url).toBe('https://unifi.com.my/tv/api/tv') -}) - -it('can generate valid request method', () => { - expect(request.method).toBe('POST') -}) - -it('can generate valid request headers', () => { - expect(request.headers).toMatchObject({ - 'x-requested-with': 'XMLHttpRequest' - }) -}) - -it('can generate valid request data', () => { - const data = request.data({ date }) - - expect(data.get('date')).toBe('2023-01-13') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') - const results = parser({ content, date, channel }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - title: 'Berita Tengah Malam', - start: '2023-01-12T16:00:00.000Z', - stop: '2023-01-12T16:30:00.000Z' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ content: '', channel }) - - expect(results).toMatchObject([]) -}) +const { parser, url, request } = require('./playtv.unifi.com.my.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-01-13', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '20000009', + xmltv_id: 'TV1.my' +} + +it('can generate valid url', () => { + expect(url).toBe('https://unifi.com.my/tv/api/tv') +}) + +it('can generate valid request method', () => { + expect(request.method).toBe('POST') +}) + +it('can generate valid request headers', () => { + expect(request.headers).toMatchObject({ + 'x-requested-with': 'XMLHttpRequest' + }) +}) + +it('can generate valid request data', () => { + const data = request.data({ date }) + + expect(data.get('date')).toBe('2023-01-13') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') + const results = parser({ content, date, channel }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + title: 'Berita Tengah Malam', + start: '2023-01-12T16:00:00.000Z', + stop: '2023-01-12T16:30:00.000Z' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ content: '', channel }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/plex.tv/plex.tv.config.js b/sites/plex.tv/plex.tv.config.js index bb7eb56a3..c2f33b129 100644 --- a/sites/plex.tv/plex.tv.config.js +++ b/sites/plex.tv/plex.tv.config.js @@ -1,91 +1,91 @@ -const axios = require('axios') -const dayjs = require('dayjs') - -const API_ENDPOINT = 'https://epg.provider.plex.tv' - -module.exports = { - site: 'plex.tv', - days: 2, - request: { - headers: { - 'x-plex-provider-version': '5.1' - } - }, - url: function ({ channel, date }) { - const [, channelGridKey] = channel.site_id.split('-') - - return `${API_ENDPOINT}/grid?channelGridKey=${channelGridKey}&date=${date.format('YYYY-MM-DD')}` - }, - parser({ content }) { - const programs = [] - const items = parseItems(content) - for (let item of items) { - programs.push({ - title: item.title, - description: item.summary, - categories: parseCategories(item), - image: item.art, - start: parseStart(item), - stop: parseStop(item) - }) - } - - return programs - }, - async channels({ token }) { - const data = await axios - .get(`${API_ENDPOINT}/lineups/plex/channels?X-Plex-Token=${token}`) - .then(r => r.data) - .catch(console.error) - - return data.MediaContainer.Channel.map(c => { - return { - lang: 'en', - site_id: c.id, - name: c.title - } - }) - } -} - -function parseCategories(item) { - return Array.isArray(item.Genre) ? item.Genre.map(g => g.tag) : [] -} - -function parseStart(item) { - return item.beginsAt ? dayjs.unix(item.beginsAt) : null -} - -function parseStop(item) { - return item.endsAt ? dayjs.unix(item.endsAt) : null -} - -function parseItems(content) { - const data = JSON.parse(content) - if (!data || !data.MediaContainer || !Array.isArray(data.MediaContainer.Metadata)) return [] - const metadata = data.MediaContainer.Metadata - const items = [] - metadata.forEach(item => { - let segments = [] - item.Media.sort(byTime).forEach(media => { - let prevSegment = segments[segments.length - 1] - if (prevSegment && prevSegment.endsAt === media.beginsAt) { - prevSegment.endsAt = media.endsAt - } else { - segments.push(media) - } - }) - - segments.forEach(segment => { - items.push({ ...item, segments, beginsAt: segment.beginsAt, endsAt: segment.endsAt }) - }) - }) - - return items.sort(byTime) - - function byTime(a, b) { - if (a.beginsAt > b.beginsAt) return 1 - if (a.beginsAt < b.beginsAt) return -1 - return 0 - } -} +const axios = require('axios') +const dayjs = require('dayjs') + +const API_ENDPOINT = 'https://epg.provider.plex.tv' + +module.exports = { + site: 'plex.tv', + days: 2, + request: { + headers: { + 'x-plex-provider-version': '5.1' + } + }, + url: function ({ channel, date }) { + const [, channelGridKey] = channel.site_id.split('-') + + return `${API_ENDPOINT}/grid?channelGridKey=${channelGridKey}&date=${date.format('YYYY-MM-DD')}` + }, + parser({ content }) { + const programs = [] + const items = parseItems(content) + for (let item of items) { + programs.push({ + title: item.title, + description: item.summary, + categories: parseCategories(item), + image: item.art, + start: parseStart(item), + stop: parseStop(item) + }) + } + + return programs + }, + async channels({ token }) { + const data = await axios + .get(`${API_ENDPOINT}/lineups/plex/channels?X-Plex-Token=${token}`) + .then(r => r.data) + .catch(console.error) + + return data.MediaContainer.Channel.map(c => { + return { + lang: 'en', + site_id: c.id, + name: c.title + } + }) + } +} + +function parseCategories(item) { + return Array.isArray(item.Genre) ? item.Genre.map(g => g.tag) : [] +} + +function parseStart(item) { + return item.beginsAt ? dayjs.unix(item.beginsAt) : null +} + +function parseStop(item) { + return item.endsAt ? dayjs.unix(item.endsAt) : null +} + +function parseItems(content) { + const data = JSON.parse(content) + if (!data || !data.MediaContainer || !Array.isArray(data.MediaContainer.Metadata)) return [] + const metadata = data.MediaContainer.Metadata + const items = [] + metadata.forEach(item => { + let segments = [] + item.Media.sort(byTime).forEach(media => { + let prevSegment = segments[segments.length - 1] + if (prevSegment && prevSegment.endsAt === media.beginsAt) { + prevSegment.endsAt = media.endsAt + } else { + segments.push(media) + } + }) + + segments.forEach(segment => { + items.push({ ...item, segments, beginsAt: segment.beginsAt, endsAt: segment.endsAt }) + }) + }) + + return items.sort(byTime) + + function byTime(a, b) { + if (a.beginsAt > b.beginsAt) return 1 + if (a.beginsAt < b.beginsAt) return -1 + return 0 + } +} diff --git a/sites/plex.tv/plex.tv.test.js b/sites/plex.tv/plex.tv.test.js index a189c6916..6cb2e74b4 100644 --- a/sites/plex.tv/plex.tv.test.js +++ b/sites/plex.tv/plex.tv.test.js @@ -1,56 +1,56 @@ -const { parser, url, request } = require('./plex.tv.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -const date = dayjs.utc('2023-02-05', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '5e20b730f2f8d5003d739db7-5eea605674085f0040ddc7a6', - xmltv_id: 'DarkMatterTV.us' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://epg.provider.plex.tv/grid?channelGridKey=5eea605674085f0040ddc7a6&date=2023-02-05' - ) -}) - -it('can generate valid request headers', () => { - expect(request.headers).toMatchObject({ - 'x-plex-provider-version': '5.1' - }) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - let results = parser({ content }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - // expect(results.length).toBe(15) - expect(results[0]).toMatchObject({ - start: '2023-02-04T23:31:14.000Z', - stop: '2023-02-05T01:10:45.000Z', - title: 'Violet & Daisy', - description: - 'Two teenage assassins accept what they think will be a quick-and-easy job, until an unexpected target throws them off their plan.', - image: 'https://provider-static.plex.tv/epg/images/ott_channels/arts/darkmatter-tv-about.jpg', - categories: ['Movies'] - }) -}) - -it('can handle empty guide', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - const results = parser({ content }) - - expect(results).toMatchObject([]) -}) +const { parser, url, request } = require('./plex.tv.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.utc('2023-02-05', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '5e20b730f2f8d5003d739db7-5eea605674085f0040ddc7a6', + xmltv_id: 'DarkMatterTV.us' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://epg.provider.plex.tv/grid?channelGridKey=5eea605674085f0040ddc7a6&date=2023-02-05' + ) +}) + +it('can generate valid request headers', () => { + expect(request.headers).toMatchObject({ + 'x-plex-provider-version': '5.1' + }) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + let results = parser({ content }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + // expect(results.length).toBe(15) + expect(results[0]).toMatchObject({ + start: '2023-02-04T23:31:14.000Z', + stop: '2023-02-05T01:10:45.000Z', + title: 'Violet & Daisy', + description: + 'Two teenage assassins accept what they think will be a quick-and-easy job, until an unexpected target throws them off their plan.', + image: 'https://provider-static.plex.tv/epg/images/ott_channels/arts/darkmatter-tv-about.jpg', + categories: ['Movies'] + }) +}) + +it('can handle empty guide', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + const results = parser({ content }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/pluto.tv/pluto.tv.config.js b/sites/pluto.tv/pluto.tv.config.js index 294e130ec..d4001a47b 100644 --- a/sites/pluto.tv/pluto.tv.config.js +++ b/sites/pluto.tv/pluto.tv.config.js @@ -1,49 +1,49 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') - -dayjs.extend(utc) -dayjs.extend(timezone) - -module.exports = { - site: 'pluto.tv', - days: 3, - - url: function ({ date, channel }) { - const channelId = channel.site_id - - const localTimezone = dayjs.tz.guess() - - const startTime = dayjs(date).tz(localTimezone).startOf('day').toISOString() - const endTime = dayjs(date).tz(localTimezone).add(this.days, 'day').endOf('day').toISOString() - - const generatedUrl = `https://api.pluto.tv/v2/channels/${channelId}?start=${startTime}&stop=${endTime}` - return generatedUrl - }, - - parser: function ({ content }) { - const data = JSON.parse(content) - const programs = [] - - if (data.timelines) { - data.timelines.forEach(item => { - programs.push({ - title: item.title, - subTitle: item.episode?.name || '', - description: item.episode?.description || '', - episode: item.episode?.number || '', - season: item.episode?.season || '', - actors: item.episode?.clip?.actors || [], - categories: [item.episode?.genre, item.episode?.subGenre].filter(Boolean), - rating: item.episode?.rating || '', - date: item.episode?.clip?.originalReleaseDate || '', - icon: item.episode?.series?.tile?.path || '', - start: item.start, - stop: item.stop - }) - }) - } - - return programs - } -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') + +dayjs.extend(utc) +dayjs.extend(timezone) + +module.exports = { + site: 'pluto.tv', + days: 3, + + url: function ({ date, channel }) { + const channelId = channel.site_id + + const localTimezone = dayjs.tz.guess() + + const startTime = dayjs(date).tz(localTimezone).startOf('day').toISOString() + const endTime = dayjs(date).tz(localTimezone).add(this.days, 'day').endOf('day').toISOString() + + const generatedUrl = `https://api.pluto.tv/v2/channels/${channelId}?start=${startTime}&stop=${endTime}` + return generatedUrl + }, + + parser: function ({ content }) { + const data = JSON.parse(content) + const programs = [] + + if (data.timelines) { + data.timelines.forEach(item => { + programs.push({ + title: item.title, + subTitle: item.episode?.name || '', + description: item.episode?.description || '', + episode: item.episode?.number || '', + season: item.episode?.season || '', + actors: item.episode?.clip?.actors || [], + categories: [item.episode?.genre, item.episode?.subGenre].filter(Boolean), + rating: item.episode?.rating || '', + date: item.episode?.clip?.originalReleaseDate || '', + icon: item.episode?.series?.tile?.path || '', + start: item.start, + stop: item.stop + }) + }) + } + + return programs + } +} diff --git a/sites/pluto.tv/pluto.tv.test.js b/sites/pluto.tv/pluto.tv.test.js index 37ec1a81a..9fd9308d9 100644 --- a/sites/pluto.tv/pluto.tv.test.js +++ b/sites/pluto.tv/pluto.tv.test.js @@ -1,59 +1,59 @@ -const config = require('./pluto.tv.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2024-12-28', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '5ee92e72fb286e0007285fec', - xmltv_id: 'Naruto' -} - -it('can generate valid url', () => { - const url = config.url({ date, channel }) - expect(url).toBe( - 'https://api.pluto.tv/v2/channels/5ee92e72fb286e0007285fec?start=2024-12-27T12:00:00.000Z&stop=2024-12-31T11:59:59.999Z' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') - const results = config.parser({ content }).map(p => { - p.start = dayjs(p.start).toJSON() - p.stop = dayjs(p.stop).toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2024-12-28T00:21:00.000Z', - stop: '2024-12-28T00:48:00.000Z', - title: 'Naruto: El Tercer Hokage, Eternamente', - description: - 'Gaara y Naruto continúan combatiendo con todas sus fuerzas. Decidido a proteger a Sakura, Naruto ataca a Gaara una y otra vez.', - subTitle: 'El Tercer Hokage, Eternamente', - episode: 80, - season: 2, - actors: [ - 'Isabel Martion (Naruto Uzumaki)', - 'Christine Byrd (Sakura Haruno)', - 'Victor Ugarte (Sasuke Uchiha)', - 'Alfonso Obreg (Kakashi Hatake)' - ], - categories: ['Anime', 'Anime Action & Adventure'], - rating: 'TV-14', - date: '2004-04-21T00:00:00.000Z', - icon: 'https://images.pluto.tv/series/5e73b850e40c9f001a0a9fb4/tile.jpg?fill=blur&fit=fill&fm=jpg&h=660&q=75&w=660' - }) -}) - -it('can handle empty guide', () => { - const results = config.parser({ - content: '[]' - }) - expect(results).toMatchObject([]) -}) +const config = require('./pluto.tv.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2024-12-28', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '5ee92e72fb286e0007285fec', + xmltv_id: 'Naruto' +} + +it('can generate valid url', () => { + const url = config.url({ date, channel }) + expect(url).toBe( + 'https://api.pluto.tv/v2/channels/5ee92e72fb286e0007285fec?start=2024-12-27T12:00:00.000Z&stop=2024-12-31T11:59:59.999Z' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') + const results = config.parser({ content }).map(p => { + p.start = dayjs(p.start).toJSON() + p.stop = dayjs(p.stop).toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2024-12-28T00:21:00.000Z', + stop: '2024-12-28T00:48:00.000Z', + title: 'Naruto: El Tercer Hokage, Eternamente', + description: + 'Gaara y Naruto continúan combatiendo con todas sus fuerzas. Decidido a proteger a Sakura, Naruto ataca a Gaara una y otra vez.', + subTitle: 'El Tercer Hokage, Eternamente', + episode: 80, + season: 2, + actors: [ + 'Isabel Martion (Naruto Uzumaki)', + 'Christine Byrd (Sakura Haruno)', + 'Victor Ugarte (Sasuke Uchiha)', + 'Alfonso Obreg (Kakashi Hatake)' + ], + categories: ['Anime', 'Anime Action & Adventure'], + rating: 'TV-14', + date: '2004-04-21T00:00:00.000Z', + icon: 'https://images.pluto.tv/series/5e73b850e40c9f001a0a9fb4/tile.jpg?fill=blur&fit=fill&fm=jpg&h=660&q=75&w=660' + }) +}) + +it('can handle empty guide', () => { + const results = config.parser({ + content: '[]' + }) + expect(results).toMatchObject([]) +}) diff --git a/sites/programacion-tv.elpais.com/programacion-tv.elpais.com.config.js b/sites/programacion-tv.elpais.com/programacion-tv.elpais.com.config.js index 2ac993f76..7ff4b728e 100644 --- a/sites/programacion-tv.elpais.com/programacion-tv.elpais.com.config.js +++ b/sites/programacion-tv.elpais.com/programacion-tv.elpais.com.config.js @@ -1,105 +1,105 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') - -dayjs.extend(utc) -dayjs.extend(timezone) - -module.exports = { - site: 'programacion-tv.elpais.com', - days: 2, - request: { - cache: { - ttl: 60 * 60 * 1000 // 1 hour - } - }, - url: function ({ date }) { - return `https://programacion-tv.elpais.com/data/parrilla_${date.format('DDMMYYYY')}.json` - }, - parser: async function ({ content, channel }) { - const programs = [] - const items = parseItems(content, channel) - if (!items.length) return programs - const programsData = await loadProgramsData(channel) - items.forEach(item => { - const start = parseStart(item) - const stop = parseStop(item) - const details = programsData.find(p => p.id_programa === item.id_programa) || {} - programs.push({ - title: item.title, - sub_title: details.episode_title, - description: details.episode_description || item.description, - category: parseCategory(details), - image: parseImage(details), - director: parseList(details.director), - actors: parseList(details.actors), - writer: parseList(details.script), - producer: parseList(details.producer), - presenter: parseList(details.presented_by), - composer: parseList(details.music), - guest: parseList(details.guest_actors), - season: parseNumber(details.season), - episode: parseNumber(details.episode), - start, - stop - }) - }) - - return programs - }, - async channels() { - const data = await axios - .get('https://programacion-tv.elpais.com/data/canales.json') - .then(r => r.data) - .catch(console.log) - - return Object.values(data).map(item => ({ - lang: 'es', - site_id: item.id, - name: item.nombre - })) - } -} - -function parseNumber(str) { - return typeof str === 'string' ? parseInt(str) : null -} - -function parseList(str) { - return typeof str === 'string' ? str.split(', ') : [] -} - -function parseImage(details) { - const url = new URL(details.image, 'https://programacion-tv.elpais.com/') - - return url.href -} - -function parseCategory(details) { - return [details.txt_genre, details.txt_subgenre].filter(Boolean).join('/') -} - -async function loadProgramsData(channel) { - return await axios - .get(`https://programacion-tv.elpais.com/data/programas/${channel.site_id}.json`) - .then(r => r.data) - .catch(console.log) -} - -function parseStart(item) { - return dayjs.tz(item.iniDate, 'YYYY-MM-DD HH:mm:ss', 'Europe/Madrid') -} - -function parseStop(item) { - return dayjs.tz(item.endDate, 'YYYY-MM-DD HH:mm:ss', 'Europe/Madrid') -} - -function parseItems(content, channel) { - if (!content) return [] - const data = JSON.parse(content) - const channelData = data.find(i => i.idCanal === channel.site_id) - if (!channelData || !Array.isArray(channelData.programas)) return [] - - return channelData.programas -} +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') + +dayjs.extend(utc) +dayjs.extend(timezone) + +module.exports = { + site: 'programacion-tv.elpais.com', + days: 2, + request: { + cache: { + ttl: 60 * 60 * 1000 // 1 hour + } + }, + url: function ({ date }) { + return `https://programacion-tv.elpais.com/data/parrilla_${date.format('DDMMYYYY')}.json` + }, + parser: async function ({ content, channel }) { + const programs = [] + const items = parseItems(content, channel) + if (!items.length) return programs + const programsData = await loadProgramsData(channel) + items.forEach(item => { + const start = parseStart(item) + const stop = parseStop(item) + const details = programsData.find(p => p.id_programa === item.id_programa) || {} + programs.push({ + title: item.title, + sub_title: details.episode_title, + description: details.episode_description || item.description, + category: parseCategory(details), + image: parseImage(details), + director: parseList(details.director), + actors: parseList(details.actors), + writer: parseList(details.script), + producer: parseList(details.producer), + presenter: parseList(details.presented_by), + composer: parseList(details.music), + guest: parseList(details.guest_actors), + season: parseNumber(details.season), + episode: parseNumber(details.episode), + start, + stop + }) + }) + + return programs + }, + async channels() { + const data = await axios + .get('https://programacion-tv.elpais.com/data/canales.json') + .then(r => r.data) + .catch(console.log) + + return Object.values(data).map(item => ({ + lang: 'es', + site_id: item.id, + name: item.nombre + })) + } +} + +function parseNumber(str) { + return typeof str === 'string' ? parseInt(str) : null +} + +function parseList(str) { + return typeof str === 'string' ? str.split(', ') : [] +} + +function parseImage(details) { + const url = new URL(details.image, 'https://programacion-tv.elpais.com/') + + return url.href +} + +function parseCategory(details) { + return [details.txt_genre, details.txt_subgenre].filter(Boolean).join('/') +} + +async function loadProgramsData(channel) { + return await axios + .get(`https://programacion-tv.elpais.com/data/programas/${channel.site_id}.json`) + .then(r => r.data) + .catch(console.log) +} + +function parseStart(item) { + return dayjs.tz(item.iniDate, 'YYYY-MM-DD HH:mm:ss', 'Europe/Madrid') +} + +function parseStop(item) { + return dayjs.tz(item.endDate, 'YYYY-MM-DD HH:mm:ss', 'Europe/Madrid') +} + +function parseItems(content, channel) { + if (!content) return [] + const data = JSON.parse(content) + const channelData = data.find(i => i.idCanal === channel.site_id) + if (!channelData || !Array.isArray(channelData.programas)) return [] + + return channelData.programas +} diff --git a/sites/programacion-tv.elpais.com/programacion-tv.elpais.com.test.js b/sites/programacion-tv.elpais.com/programacion-tv.elpais.com.test.js index 1cfff4f55..543df9dc3 100644 --- a/sites/programacion-tv.elpais.com/programacion-tv.elpais.com.test.js +++ b/sites/programacion-tv.elpais.com/programacion-tv.elpais.com.test.js @@ -1,71 +1,71 @@ -const { parser, url } = require('./programacion-tv.elpais.com.config.js') -const axios = require('axios') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -const date = dayjs.utc('2022-10-04', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '3', - xmltv_id: 'La1.es' -} - -it('can generate valid url', () => { - expect(url({ date })).toBe('https://programacion-tv.elpais.com/data/parrilla_04102022.json') -}) - -it('can parse response', async () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - axios.get.mockImplementation(url => { - if (url === 'https://programacion-tv.elpais.com/data/programas/3.json') { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/programs.json'))) - }) - } else { - return Promise.resolve({ data: '' }) - } - }) - - let results = await parser({ content, channel }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results).toMatchObject([ - { - start: '2022-10-03T23:30:00.000Z', - stop: '2022-10-04T00:25:00.000Z', - title: 'Comerse el mundo', - sub_title: 'París', - description: - 'El chef Peña viaja hasta París, una de las capitales mundiales de la alta gastronomía. Allí visitará un viñedo muy especial en pleno corazón de la ciudad, probará los famosos caracoles, hará un queso y conocerá a chefs que llegaron a la capital gala para cumplir sus sueños y los consiguieron.', - director: ['Sergio Martín', 'Victor Arribas'], - presenter: ['Javier Peña'], - writer: ['Filippo Gravino', 'Guido Iuculano', 'Michele Pellegrini'], - actors: ['Pietro Sermonti', 'Maya Sansa', 'Ana Caterina Morariu'], - guest: ['Tobia de Angelis', 'Benedetta Porcaroli', 'Roberto Nocchi'], - producer: ['Javier Redondo'], - composer: ['Paco Musulén'], - category: 'Ocio-Cultura/Cocina', - season: 1, - episode: 23, - image: 'https://programacion-tv.elpais.com/imagenes/programas/2099957.jpg' - } - ]) -}) - -it('can handle empty guide', async () => { - const result = await parser({ - content: '', - channel - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./programacion-tv.elpais.com.config.js') +const axios = require('axios') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.utc('2022-10-04', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '3', + xmltv_id: 'La1.es' +} + +it('can generate valid url', () => { + expect(url({ date })).toBe('https://programacion-tv.elpais.com/data/parrilla_04102022.json') +}) + +it('can parse response', async () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + axios.get.mockImplementation(url => { + if (url === 'https://programacion-tv.elpais.com/data/programas/3.json') { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/programs.json'))) + }) + } else { + return Promise.resolve({ data: '' }) + } + }) + + let results = await parser({ content, channel }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results).toMatchObject([ + { + start: '2022-10-03T23:30:00.000Z', + stop: '2022-10-04T00:25:00.000Z', + title: 'Comerse el mundo', + sub_title: 'París', + description: + 'El chef Peña viaja hasta París, una de las capitales mundiales de la alta gastronomía. Allí visitará un viñedo muy especial en pleno corazón de la ciudad, probará los famosos caracoles, hará un queso y conocerá a chefs que llegaron a la capital gala para cumplir sus sueños y los consiguieron.', + director: ['Sergio Martín', 'Victor Arribas'], + presenter: ['Javier Peña'], + writer: ['Filippo Gravino', 'Guido Iuculano', 'Michele Pellegrini'], + actors: ['Pietro Sermonti', 'Maya Sansa', 'Ana Caterina Morariu'], + guest: ['Tobia de Angelis', 'Benedetta Porcaroli', 'Roberto Nocchi'], + producer: ['Javier Redondo'], + composer: ['Paco Musulén'], + category: 'Ocio-Cultura/Cocina', + season: 1, + episode: 23, + image: 'https://programacion-tv.elpais.com/imagenes/programas/2099957.jpg' + } + ]) +}) + +it('can handle empty guide', async () => { + const result = await parser({ + content: '', + channel + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/programacion.tcc.com.uy/programacion.tcc.com.uy.config.js b/sites/programacion.tcc.com.uy/programacion.tcc.com.uy.config.js index 5a42c02b3..b82be339c 100644 --- a/sites/programacion.tcc.com.uy/programacion.tcc.com.uy.config.js +++ b/sites/programacion.tcc.com.uy/programacion.tcc.com.uy.config.js @@ -1,102 +1,102 @@ -const axios = require('axios') -const dayjs = require('dayjs') - -const API_ENDPOINT = 'https://www.tccvivo.com.uy/api/v1/navigation_filter/1575/filter/' - -module.exports = { - site: 'programacion.tcc.com.uy', - days: 2, - request: { - cache: { - ttl: 60 * 60 * 1000 // 1 hour - }, - maxContentLength: 10 * 1024 * 1024 // 30Mb - }, - url: function ({ date }) { - return `${API_ENDPOINT}?cable_operator=1&emission_start=${date.format( - 'YYYY-MM-DDTHH:mm:ss[Z]' - )}&emission_end=${date.add(1, 'd').format('YYYY-MM-DDTHH:mm:ss[Z]')}&format=json` - }, - parser({ content, channel }) { - let programs = [] - let items = parseItems(content, channel) - items.forEach(item => { - programs.push({ - title: parseTitle(item), - description: parseDescription(item), - categories: parseCategories(item), - date: item.year, - season: item.season_number, - episode: item.episode_number, - image: parseImage(item), - start: parseStart(item), - stop: parseStop(item) - }) - }) - - return programs - }, - async channels() { - const data = await axios - .get( - `${API_ENDPOINT}?cable_operator=1&emission_start=${dayjs().format( - 'YYYY-MM-DDTHH:mm:ss[Z]' - )}&emission_end=${dayjs().format('YYYY-MM-DDTHH:mm:ss[Z]')}&format=json` - ) - .then(r => r.data) - .catch(console.error) - - return data.results.map(c => { - return { - lang: 'es', - site_id: c.id, - name: c.name.replace(/^\[.*\]\s/, '') - } - }) - } -} - -function parseTitle(item) { - const localized = item.localized.find(i => i.language === 'es') - - return localized ? localized.title : item.original_title -} - -function parseDescription(item) { - const localized = item.localized.find(i => i.language === 'es') - - return localized ? localized.description : null -} - -function parseCategories(item) { - return item.genres - .map(g => { - const localized = g.localized.find(i => i.language === 'es') - - return localized ? localized.name : null - }) - .filter(Boolean) -} - -function parseImage(item) { - const uri = item.images[0] ? item.images[0].image_media.file : null - - return uri ? `https:${uri}` : null -} - -function parseStart(item) { - return dayjs(item.emission_start) -} - -function parseStop(item) { - return dayjs(item.emission_end) -} - -function parseItems(content, channel) { - const data = JSON.parse(content) - if (!data || !Array.isArray(data.results)) return [] - const channelData = data.results.find(c => c.id == channel.site_id) - if (!channelData || !Array.isArray(channelData.events)) return [] - - return channelData.events -} +const axios = require('axios') +const dayjs = require('dayjs') + +const API_ENDPOINT = 'https://www.tccvivo.com.uy/api/v1/navigation_filter/1575/filter/' + +module.exports = { + site: 'programacion.tcc.com.uy', + days: 2, + request: { + cache: { + ttl: 60 * 60 * 1000 // 1 hour + }, + maxContentLength: 10 * 1024 * 1024 // 30Mb + }, + url: function ({ date }) { + return `${API_ENDPOINT}?cable_operator=1&emission_start=${date.format( + 'YYYY-MM-DDTHH:mm:ss[Z]' + )}&emission_end=${date.add(1, 'd').format('YYYY-MM-DDTHH:mm:ss[Z]')}&format=json` + }, + parser({ content, channel }) { + let programs = [] + let items = parseItems(content, channel) + items.forEach(item => { + programs.push({ + title: parseTitle(item), + description: parseDescription(item), + categories: parseCategories(item), + date: item.year, + season: item.season_number, + episode: item.episode_number, + image: parseImage(item), + start: parseStart(item), + stop: parseStop(item) + }) + }) + + return programs + }, + async channels() { + const data = await axios + .get( + `${API_ENDPOINT}?cable_operator=1&emission_start=${dayjs().format( + 'YYYY-MM-DDTHH:mm:ss[Z]' + )}&emission_end=${dayjs().format('YYYY-MM-DDTHH:mm:ss[Z]')}&format=json` + ) + .then(r => r.data) + .catch(console.error) + + return data.results.map(c => { + return { + lang: 'es', + site_id: c.id, + name: c.name.replace(/^\[.*\]\s/, '') + } + }) + } +} + +function parseTitle(item) { + const localized = item.localized.find(i => i.language === 'es') + + return localized ? localized.title : item.original_title +} + +function parseDescription(item) { + const localized = item.localized.find(i => i.language === 'es') + + return localized ? localized.description : null +} + +function parseCategories(item) { + return item.genres + .map(g => { + const localized = g.localized.find(i => i.language === 'es') + + return localized ? localized.name : null + }) + .filter(Boolean) +} + +function parseImage(item) { + const uri = item.images[0] ? item.images[0].image_media.file : null + + return uri ? `https:${uri}` : null +} + +function parseStart(item) { + return dayjs(item.emission_start) +} + +function parseStop(item) { + return dayjs(item.emission_end) +} + +function parseItems(content, channel) { + const data = JSON.parse(content) + if (!data || !Array.isArray(data.results)) return [] + const channelData = data.results.find(c => c.id == channel.site_id) + if (!channelData || !Array.isArray(channelData.events)) return [] + + return channelData.events +} diff --git a/sites/programacion.tcc.com.uy/programacion.tcc.com.uy.test.js b/sites/programacion.tcc.com.uy/programacion.tcc.com.uy.test.js index f2600af10..02ce2dc95 100644 --- a/sites/programacion.tcc.com.uy/programacion.tcc.com.uy.test.js +++ b/sites/programacion.tcc.com.uy/programacion.tcc.com.uy.test.js @@ -1,76 +1,76 @@ -const { parser, url } = require('./programacion.tcc.com.uy.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-02-11', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '212', - xmltv_id: 'MultiPremier.mx' -} - -it('can generate valid url', () => { - expect(url({ date })).toBe( - 'https://www.tccvivo.com.uy/api/v1/navigation_filter/1575/filter/?cable_operator=1&emission_start=2023-02-11T00:00:00Z&emission_end=2023-02-12T00:00:00Z&format=json' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - let results = parser({ content, channel }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2023-02-10T22:45:00.000Z', - stop: '2023-02-11T00:30:00.000Z', - title: 'Meurtres à... - Temp. 3 - Episodio 3', - date: 2016, - season: 3, - episode: 3, - categories: [], - image: 'https://zpapi.zetatv.com.uy/media/images/2b45d2675389f2e4f7f6fe0655ccc968.jpg', - description: - 'Cada episodio relata un lugar y una historia diferente pero siguiendo la línea de una investigación basada en una leyenda la cual es guiada por una pareja. Estos dos personajes no son necesariamente ambos policías, pero se ven obligados a colaborar a pesar de los primeros informes difíciles.' - }) - expect(results[1]).toMatchObject({ - start: '2023-02-11T00:30:00.000Z', - stop: '2023-02-11T03:00:00.000Z', - title: 'Grandes esperanzas', - date: 1998, - season: null, - episode: null, - categories: ['Drama'], - image: 'https://zpapi.zetatv.com.uy/media/images/8cab42d88691edaa8a4001b91f809d91.jpg', - description: - 'Basada en la novela de Charles Dickens, cuenta la historia del pintor Finn que persigue obsesionado a su amor de la niñez, la bella y rica Estella. Gracias a un misterioso benefactor, Finn es enviado a Nueva York, donde se reúne con la hermosa y fría joven.' - }) - expect(results[3]).toMatchObject({ - start: '2023-02-11T05:35:00.000Z', - stop: '2023-02-11T07:45:00.000Z', - title: 'Los niños están bien', - date: 2010, - season: null, - episode: null, - categories: ['Comedia', 'Drama'], - image: 'https://zpapi.zetatv.com.uy/media/images/51684d91ed33cb9b0c1863b7a9b097e9.jpg', - description: - 'Una pareja de lesbianas conciben a un niño y una niña por inseminacion artificial. Al paso del tiempo, los chicos deciden conocer a su verdadero padre a espaldas de sus madres. Tras localizarlo intentan integrar toda una familia. Podran lograrlo?.' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')), - channel - }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./programacion.tcc.com.uy.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-02-11', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '212', + xmltv_id: 'MultiPremier.mx' +} + +it('can generate valid url', () => { + expect(url({ date })).toBe( + 'https://www.tccvivo.com.uy/api/v1/navigation_filter/1575/filter/?cable_operator=1&emission_start=2023-02-11T00:00:00Z&emission_end=2023-02-12T00:00:00Z&format=json' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + let results = parser({ content, channel }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2023-02-10T22:45:00.000Z', + stop: '2023-02-11T00:30:00.000Z', + title: 'Meurtres à... - Temp. 3 - Episodio 3', + date: 2016, + season: 3, + episode: 3, + categories: [], + image: 'https://zpapi.zetatv.com.uy/media/images/2b45d2675389f2e4f7f6fe0655ccc968.jpg', + description: + 'Cada episodio relata un lugar y una historia diferente pero siguiendo la línea de una investigación basada en una leyenda la cual es guiada por una pareja. Estos dos personajes no son necesariamente ambos policías, pero se ven obligados a colaborar a pesar de los primeros informes difíciles.' + }) + expect(results[1]).toMatchObject({ + start: '2023-02-11T00:30:00.000Z', + stop: '2023-02-11T03:00:00.000Z', + title: 'Grandes esperanzas', + date: 1998, + season: null, + episode: null, + categories: ['Drama'], + image: 'https://zpapi.zetatv.com.uy/media/images/8cab42d88691edaa8a4001b91f809d91.jpg', + description: + 'Basada en la novela de Charles Dickens, cuenta la historia del pintor Finn que persigue obsesionado a su amor de la niñez, la bella y rica Estella. Gracias a un misterioso benefactor, Finn es enviado a Nueva York, donde se reúne con la hermosa y fría joven.' + }) + expect(results[3]).toMatchObject({ + start: '2023-02-11T05:35:00.000Z', + stop: '2023-02-11T07:45:00.000Z', + title: 'Los niños están bien', + date: 2010, + season: null, + episode: null, + categories: ['Comedia', 'Drama'], + image: 'https://zpapi.zetatv.com.uy/media/images/51684d91ed33cb9b0c1863b7a9b097e9.jpg', + description: + 'Una pareja de lesbianas conciben a un niño y una niña por inseminacion artificial. Al paso del tiempo, los chicos deciden conocer a su verdadero padre a espaldas de sus madres. Tras localizarlo intentan integrar toda una familia. Podran lograrlo?.' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')), + channel + }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/programme-tv.net/programme-tv.net.test.js b/sites/programme-tv.net/programme-tv.net.test.js index 3f59862da..a3c2ddde6 100644 --- a/sites/programme-tv.net/programme-tv.net.test.js +++ b/sites/programme-tv.net/programme-tv.net.test.js @@ -1,63 +1,63 @@ -const { parser, url, request } = require('./programme-tv.net.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-11-27', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'tf1-19', - xmltv_id: 'TF1.fr' -} - -it('can generate valid url', () => { - expect(request.headers).toMatchObject({ - cookie: 'authId=b7154156fe4fb8acdb6f38e1207c6231' - }) -}) - -it('can generate valid url', () => { - expect(url({ date, channel })).toBe( - 'https://www.programme-tv.net/programme/chaine/2023-11-27/programme-tf1-19.html' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - let results = parser({ content, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2023-11-27T00:05:00.000Z', - stop: '2023-11-27T05:30:00.000Z', - title: 'Programmes de la nuit', - category: 'Autre', - image: - 'https://www.programme-tv.net/imgre/fit/~2~program~978eb86d5b99cee0.jpg/960x540/quality/80/programmes-de-la-nuit.jpg' - }) - - expect(results[27]).toMatchObject({ - start: '2023-11-27T22:50:00.000Z', - stop: '2023-11-27T23:45:00.000Z', - title: 'Coup de foudre chez le Père Noël', - category: 'Téléfilm', - image: - 'https://www.programme-tv.net/imgre/fit/~2~program~5a4e78779c4a3fac.jpg/960x540/quality/80/coup-de-foudre-chez-le-pere-noel.jpg' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - content: '', - date - }) - - expect(results).toMatchObject([]) -}) +const { parser, url, request } = require('./programme-tv.net.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-11-27', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'tf1-19', + xmltv_id: 'TF1.fr' +} + +it('can generate valid url', () => { + expect(request.headers).toMatchObject({ + cookie: 'authId=b7154156fe4fb8acdb6f38e1207c6231' + }) +}) + +it('can generate valid url', () => { + expect(url({ date, channel })).toBe( + 'https://www.programme-tv.net/programme/chaine/2023-11-27/programme-tf1-19.html' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + let results = parser({ content, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2023-11-27T00:05:00.000Z', + stop: '2023-11-27T05:30:00.000Z', + title: 'Programmes de la nuit', + category: 'Autre', + image: + 'https://www.programme-tv.net/imgre/fit/~2~program~978eb86d5b99cee0.jpg/960x540/quality/80/programmes-de-la-nuit.jpg' + }) + + expect(results[27]).toMatchObject({ + start: '2023-11-27T22:50:00.000Z', + stop: '2023-11-27T23:45:00.000Z', + title: 'Coup de foudre chez le Père Noël', + category: 'Téléfilm', + image: + 'https://www.programme-tv.net/imgre/fit/~2~program~5a4e78779c4a3fac.jpg/960x540/quality/80/coup-de-foudre-chez-le-pere-noel.jpg' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + content: '', + date + }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/programme-tv.vini.pf/programme-tv.vini.pf.config.js b/sites/programme-tv.vini.pf/programme-tv.vini.pf.config.js index 1e697583d..1d9de3423 100644 --- a/sites/programme-tv.vini.pf/programme-tv.vini.pf.config.js +++ b/sites/programme-tv.vini.pf/programme-tv.vini.pf.config.js @@ -1,90 +1,90 @@ -const dayjs = require('dayjs') -const axios = require('axios') - -const apiUrl = 'https://programme-tv.vini.pf/programmesJSON' - -module.exports = { - site: 'programme-tv.vini.pf', - days: 2, - url: apiUrl, - request: { - method: 'POST', - data({ date }) { - return { - dateDebut: `${date.subtract(10, 'h').format('YYYY-MM-DDTHH:mm:ss')}-10:00` - } - } - }, - parser: async function ({ content, channel, date }) { - const programs = [] - const items = parseItems(content, channel) - if (items.length) { - for (let hours of [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22]) { - const nextContent = await loadNextItems(date, hours) - const nextItems = parseItems(nextContent, channel) - for (let item of nextItems) { - if (!items.find(i => i.nidP === item.nidP)) { - items.push(item) - } - } - } - } - - items.forEach(item => { - programs.push({ - title: item.titreP, - description: item.desc, - category: item.categorieP, - image: item.srcP, - start: dayjs.unix(item.timestampDeb), - stop: dayjs.unix(item.timestampFin) - }) - }) - - return programs - }, - async channels() { - const data = await axios - .post('https://programme-tv.vini.pf/programmesJSON') - .then(r => r.data) - .catch(console.log) - - return data.programmes.map(item => { - const site_id = item.url.replace('/', '') - const name = site_id.replace(/-/gi, ' ') - - return { - lang: 'fr', - site_id, - name - } - }) - } -} - -async function loadNextItems(date, hours) { - date = date.add(hours, 'h') - - return axios - .post( - apiUrl, - { - dateDebut: `${date.subtract(10, 'h').format('YYYY-MM-DDTHH:mm:ss')}-10:00` - }, - { - responseType: 'arraybuffer' - } - ) - .then(res => res.data.toString()) - .catch(console.log) -} - -function parseItems(content, channel) { - if (!content) return [] - const data = JSON.parse(content) - if (!data || !Array.isArray(data.programmes)) return [] - const channelData = data.programmes.find(i => i.url === `/${channel.site_id}`) - if (!channelData) return [] - - return channelData.programmes || [] -} +const dayjs = require('dayjs') +const axios = require('axios') + +const apiUrl = 'https://programme-tv.vini.pf/programmesJSON' + +module.exports = { + site: 'programme-tv.vini.pf', + days: 2, + url: apiUrl, + request: { + method: 'POST', + data({ date }) { + return { + dateDebut: `${date.subtract(10, 'h').format('YYYY-MM-DDTHH:mm:ss')}-10:00` + } + } + }, + parser: async function ({ content, channel, date }) { + const programs = [] + const items = parseItems(content, channel) + if (items.length) { + for (let hours of [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22]) { + const nextContent = await loadNextItems(date, hours) + const nextItems = parseItems(nextContent, channel) + for (let item of nextItems) { + if (!items.find(i => i.nidP === item.nidP)) { + items.push(item) + } + } + } + } + + items.forEach(item => { + programs.push({ + title: item.titreP, + description: item.desc, + category: item.categorieP, + image: item.srcP, + start: dayjs.unix(item.timestampDeb), + stop: dayjs.unix(item.timestampFin) + }) + }) + + return programs + }, + async channels() { + const data = await axios + .post('https://programme-tv.vini.pf/programmesJSON') + .then(r => r.data) + .catch(console.log) + + return data.programmes.map(item => { + const site_id = item.url.replace('/', '') + const name = site_id.replace(/-/gi, ' ') + + return { + lang: 'fr', + site_id, + name + } + }) + } +} + +async function loadNextItems(date, hours) { + date = date.add(hours, 'h') + + return axios + .post( + apiUrl, + { + dateDebut: `${date.subtract(10, 'h').format('YYYY-MM-DDTHH:mm:ss')}-10:00` + }, + { + responseType: 'arraybuffer' + } + ) + .then(res => res.data.toString()) + .catch(console.log) +} + +function parseItems(content, channel) { + if (!content) return [] + const data = JSON.parse(content) + if (!data || !Array.isArray(data.programmes)) return [] + const channelData = data.programmes.find(i => i.url === `/${channel.site_id}`) + if (!channelData) return [] + + return channelData.programmes || [] +} diff --git a/sites/programme.tvb.com/programme.tvb.com.config.js b/sites/programme.tvb.com/programme.tvb.com.config.js index 87c3580e0..2b1e5bc5e 100644 --- a/sites/programme.tvb.com/programme.tvb.com.config.js +++ b/sites/programme.tvb.com/programme.tvb.com.config.js @@ -1,92 +1,92 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -const tz = 'Asia/Hong_Kong' - -module.exports = { - site: 'programme.tvb.com', - days: 2, - url({ channel, date, time = null }) { - return `https://programme.tvb.com/api/schedule?input_date=${date.format( - 'YYYYMMDD' - )}&network_code=${channel.site_id}&_t=${time ? time : parseInt(Date.now() / 1000)}` - }, - parser({ content, channel, date }) { - const programs = [] - const data = content ? JSON.parse(content) : {} - if (Array.isArray(data.data?.list)) { - for (const d of data.data.list) { - if (Array.isArray(d.schedules)) { - const schedules = d.schedules.filter(s => s.network_code === channel.site_id) - schedules.forEach((s, i) => { - const start = dayjs.tz(s.event_datetime, 'YYYY-MM-DD HH:mm:ss', tz) - let stop - if (i < schedules.length - 1) { - stop = dayjs.tz(schedules[i + 1].event_datetime, 'YYYY-MM-DD HH:mm:ss', tz) - } else { - stop = date.add(1, 'd') - } - programs.push({ - title: channel.lang === 'en' ? s.en_programme_title : s.programme_title, - description: channel.lang === 'en' ? s.en_synopsis : s.synopsis, - start, - stop - }) - }) - } - } - } - - return programs - }, - async channels({ lang = 'en' }) { - const channels = [] - const axios = require('axios') - const base = 'https://programme.tvb.com' - const queues = [base] - while (true) { - if (queues.length) { - const url = queues.shift() - const content = await axios - .get(url) - .then(response => response.data) - .catch(console.error) - if (content) { - const assets = content.match(/assets\/index\.([a-z0-9]+)\.js/g) - if (assets) { - queues.push(...assets.map(a => base + '/' + a)) - } else { - const metadata = content.match(/e=(\[(.*?)\])/) - if (metadata) { - const infos = eval(metadata[1]) - if (Array.isArray(infos)) { - infos - .filter(a => a.code.length) - .map(a => { - channels.push({ - lang, - site_id: a.code, - name: lang === 'en' ? a.nameEn : a.name - }) - }) - break - } - } - } - if (queues.length) { - continue - } - } - } - break - } - - return channels - } -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +const tz = 'Asia/Hong_Kong' + +module.exports = { + site: 'programme.tvb.com', + days: 2, + url({ channel, date, time = null }) { + return `https://programme.tvb.com/api/schedule?input_date=${date.format( + 'YYYYMMDD' + )}&network_code=${channel.site_id}&_t=${time ? time : parseInt(Date.now() / 1000)}` + }, + parser({ content, channel, date }) { + const programs = [] + const data = content ? JSON.parse(content) : {} + if (Array.isArray(data.data?.list)) { + for (const d of data.data.list) { + if (Array.isArray(d.schedules)) { + const schedules = d.schedules.filter(s => s.network_code === channel.site_id) + schedules.forEach((s, i) => { + const start = dayjs.tz(s.event_datetime, 'YYYY-MM-DD HH:mm:ss', tz) + let stop + if (i < schedules.length - 1) { + stop = dayjs.tz(schedules[i + 1].event_datetime, 'YYYY-MM-DD HH:mm:ss', tz) + } else { + stop = date.add(1, 'd') + } + programs.push({ + title: channel.lang === 'en' ? s.en_programme_title : s.programme_title, + description: channel.lang === 'en' ? s.en_synopsis : s.synopsis, + start, + stop + }) + }) + } + } + } + + return programs + }, + async channels({ lang = 'en' }) { + const channels = [] + const axios = require('axios') + const base = 'https://programme.tvb.com' + const queues = [base] + while (true) { + if (queues.length) { + const url = queues.shift() + const content = await axios + .get(url) + .then(response => response.data) + .catch(console.error) + if (content) { + const assets = content.match(/assets\/index\.([a-z0-9]+)\.js/g) + if (assets) { + queues.push(...assets.map(a => base + '/' + a)) + } else { + const metadata = content.match(/e=(\[(.*?)\])/) + if (metadata) { + const infos = eval(metadata[1]) + if (Array.isArray(infos)) { + infos + .filter(a => a.code.length) + .map(a => { + channels.push({ + lang, + site_id: a.code, + name: lang === 'en' ? a.nameEn : a.name + }) + }) + break + } + } + } + if (queues.length) { + continue + } + } + } + break + } + + return channels + } +} diff --git a/sites/programme.tvb.com/programme.tvb.com.test.js b/sites/programme.tvb.com/programme.tvb.com.test.js index 558f46002..ce8fda7e6 100644 --- a/sites/programme.tvb.com/programme.tvb.com.test.js +++ b/sites/programme.tvb.com/programme.tvb.com.test.js @@ -1,64 +1,64 @@ -const { parser, url } = require('./programme.tvb.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) -const date = dayjs.utc('2024-12-06', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'J', - xmltv_id: 'Jade.hk', - lang: 'en' -} - -it('can generate valid url', () => { - const time = 1733491000 - expect(url({ channel, date, time })).toBe( - 'https://programme.tvb.com/api/schedule?input_date=20241206&network_code=J&_t=1733491000' - ) -}) - -it('can parse response (en)', () => { - const results = parser({ content, channel, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(3) - expect(results[1]).toMatchObject({ - start: '2024-12-06T15:55:00.000Z', - stop: '2024-12-06T16:55:00.000Z', - title: 'Line Walker: Bull Fight#16[Can][PG]' - }) -}) - -it('can parse response (zh)', () => { - const results = parser({ content, channel: { ...channel, lang: 'zh' }, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(3) - expect(results[1]).toMatchObject({ - start: '2024-12-06T15:55:00.000Z', - stop: '2024-12-06T16:55:00.000Z', - title: '使徒行者3#16[粵][PG]', - description: - '文鼎從淑梅手上救走大聖爺兒子,大聖爺還恩於歡喜,答允支持九指強。崇聯社定下選舉日子,恰巧是韋傑出獄之日,頭目們顧念舊日恩義,紛紛轉投浩洋。浩洋帶亞希逛傢俬店,憧憬二人未來。亞希向家強承認愛上浩洋,要求退出臥底任務。作榮與歡喜暗中會面,將國際犯罪組織「永恆幫」情報交給他。阿火遭家強出賣,到沐足店搶錢。家強逮住阿火,惟被合星誤會而受拘捕。家強把正植遺下的頸鏈和學生證交還,合星意識到家強已知悉正植身世。' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: '', - date - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./programme.tvb.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) +const date = dayjs.utc('2024-12-06', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'J', + xmltv_id: 'Jade.hk', + lang: 'en' +} + +it('can generate valid url', () => { + const time = 1733491000 + expect(url({ channel, date, time })).toBe( + 'https://programme.tvb.com/api/schedule?input_date=20241206&network_code=J&_t=1733491000' + ) +}) + +it('can parse response (en)', () => { + const results = parser({ content, channel, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(3) + expect(results[1]).toMatchObject({ + start: '2024-12-06T15:55:00.000Z', + stop: '2024-12-06T16:55:00.000Z', + title: 'Line Walker: Bull Fight#16[Can][PG]' + }) +}) + +it('can parse response (zh)', () => { + const results = parser({ content, channel: { ...channel, lang: 'zh' }, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(3) + expect(results[1]).toMatchObject({ + start: '2024-12-06T15:55:00.000Z', + stop: '2024-12-06T16:55:00.000Z', + title: '使徒行者3#16[粵][PG]', + description: + '文鼎從淑梅手上救走大聖爺兒子,大聖爺還恩於歡喜,答允支持九指強。崇聯社定下選舉日子,恰巧是韋傑出獄之日,頭目們顧念舊日恩義,紛紛轉投浩洋。浩洋帶亞希逛傢俬店,憧憬二人未來。亞希向家強承認愛上浩洋,要求退出臥底任務。作榮與歡喜暗中會面,將國際犯罪組織「永恆幫」情報交給他。阿火遭家強出賣,到沐足店搶錢。家強逮住阿火,惟被合星誤會而受拘捕。家強把正植遺下的頸鏈和學生證交還,合星意識到家強已知悉正植身世。' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: '', + date + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/programtv.onet.pl/programtv.onet.pl.config.js b/sites/programtv.onet.pl/programtv.onet.pl.config.js index 64ff12df0..ecb95773f 100644 --- a/sites/programtv.onet.pl/programtv.onet.pl.config.js +++ b/sites/programtv.onet.pl/programtv.onet.pl.config.js @@ -1,89 +1,89 @@ -const cheerio = require('cheerio') -const { DateTime } = require('luxon') - -module.exports = { - delay: 5000, - site: 'programtv.onet.pl', - days: 2, - url: function ({ date, channel }) { - const currDate = DateTime.now().toUTC().startOf('day') - const day = date.diff(currDate, 'd') - - return `https://programtv.onet.pl/program-tv/${channel.site_id}?dzien=${day}` - }, - parser: function ({ content, date }) { - const programs = [] - const items = parseItems(content) - items.forEach(item => { - const prev = programs[programs.length - 1] - const $item = cheerio.load(item) - let start = parseStart($item, date) - if (prev) { - if (start < prev.start) { - start = start.plus({ days: 1 }) - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.plus({ hours: 1 }) - programs.push({ - title: parseTitle($item), - description: parseDescription($item), - category: parseCategory($item), - start, - stop - }) - }) - - return programs - }, - async channels() { - const axios = require('axios') - const data = await axios - .get('https://programtv.onet.pl/stacje') - .then(r => r.data) - .catch(console.log) - - let channels = [] - - const $ = cheerio.load(data) - $('ul.channelList a').each((i, el) => { - const name = $(el).text() - const url = $(el).attr('href') - const [, site_id] = url.match(/^\/program-tv\/(.*)$/i) - - channels.push({ - lang: 'pl', - site_id, - name - }) - }) - - return channels - } -} - -function parseStart($item, date) { - const timeString = $item('.hours > .hour').text() - const dateString = `${date.format('MM/DD/YYYY')} ${timeString}` - - return DateTime.fromFormat(dateString, 'MM/dd/yyyy HH:mm', { zone: 'Europe/Warsaw' }).toUTC() -} - -function parseCategory($item) { - return $item('.titles > .type').text() -} - -function parseDescription($item) { - return $item('.titles > p').text().trim() -} - -function parseTitle($item) { - return $item('.titles > a').text().trim() -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('#channelTV > section > div.emissions > ul > li').toArray() -} +const cheerio = require('cheerio') +const { DateTime } = require('luxon') + +module.exports = { + delay: 5000, + site: 'programtv.onet.pl', + days: 2, + url: function ({ date, channel }) { + const currDate = DateTime.now().toUTC().startOf('day') + const day = date.diff(currDate, 'd') + + return `https://programtv.onet.pl/program-tv/${channel.site_id}?dzien=${day}` + }, + parser: function ({ content, date }) { + const programs = [] + const items = parseItems(content) + items.forEach(item => { + const prev = programs[programs.length - 1] + const $item = cheerio.load(item) + let start = parseStart($item, date) + if (prev) { + if (start < prev.start) { + start = start.plus({ days: 1 }) + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.plus({ hours: 1 }) + programs.push({ + title: parseTitle($item), + description: parseDescription($item), + category: parseCategory($item), + start, + stop + }) + }) + + return programs + }, + async channels() { + const axios = require('axios') + const data = await axios + .get('https://programtv.onet.pl/stacje') + .then(r => r.data) + .catch(console.log) + + let channels = [] + + const $ = cheerio.load(data) + $('ul.channelList a').each((i, el) => { + const name = $(el).text() + const url = $(el).attr('href') + const [, site_id] = url.match(/^\/program-tv\/(.*)$/i) + + channels.push({ + lang: 'pl', + site_id, + name + }) + }) + + return channels + } +} + +function parseStart($item, date) { + const timeString = $item('.hours > .hour').text() + const dateString = `${date.format('MM/DD/YYYY')} ${timeString}` + + return DateTime.fromFormat(dateString, 'MM/dd/yyyy HH:mm', { zone: 'Europe/Warsaw' }).toUTC() +} + +function parseCategory($item) { + return $item('.titles > .type').text() +} + +function parseDescription($item) { + return $item('.titles > p').text().trim() +} + +function parseTitle($item) { + return $item('.titles > a').text().trim() +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('#channelTV > section > div.emissions > ul > li').toArray() +} diff --git a/sites/raiplay.it/raiplay.it.config.js b/sites/raiplay.it/raiplay.it.config.js index 4030d21d5..b910d8c72 100644 --- a/sites/raiplay.it/raiplay.it.config.js +++ b/sites/raiplay.it/raiplay.it.config.js @@ -1,79 +1,79 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(customParseFormat) -dayjs.extend(timezone) - -module.exports = { - site: 'raiplay.it', - days: 2, - url: function ({ date, channel }) { - return `https://www.raiplay.it/palinsesto/app/${channel.site_id}/${date.format( - 'DD-MM-YYYY' - )}.json` - }, - parser: function ({ content, date }) { - const programs = [] - const data = JSON.parse(content) - if (!data.events) return programs - - data.events.forEach(item => { - if (!item.name || !item.hour || !item.duration_in_minutes) return - const start = parseStart(item, date) - const duration = parseInt(item.duration_in_minutes) - const stop = start.add(duration, 'm') - - programs.push({ - title: item.name || item.program.name, - description: item.description, - season: parseSeason(item), - episode: parseEpisode(item), - sub_title: item['episode_title'] || null, - url: parseURL(item), - start, - stop, - image: parseImage(item) - }) - }) - - return programs - } -} - -function parseStart(item, date) { - return dayjs.tz(`${date.format('YYYY-MM-DD')} ${item.hour}`, 'YYYY-MM-DD HH:mm', 'Europe/Rome') -} - -function parseImage(item) { - let cover = null - if (item.image) { - cover = `https://www.raiplay.it${item.image}` - } - return cover -} - -function parseURL(item) { - let url = null - if (item.weblink) { - url = `https://www.raiplay.it${item.weblink}` - } - if (item.event_weblink) { - url = `https://www.raiplay.it${item.event_weblink}` - } - return url -} - -function parseSeason(item) { - if (!item.season) return null - if (String(item.season).length > 2) return null - return item.season -} - -function parseEpisode(item) { - if (!item.episode) return null - if (String(item.episode).length > 3) return null - return item.episode -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(customParseFormat) +dayjs.extend(timezone) + +module.exports = { + site: 'raiplay.it', + days: 2, + url: function ({ date, channel }) { + return `https://www.raiplay.it/palinsesto/app/${channel.site_id}/${date.format( + 'DD-MM-YYYY' + )}.json` + }, + parser: function ({ content, date }) { + const programs = [] + const data = JSON.parse(content) + if (!data.events) return programs + + data.events.forEach(item => { + if (!item.name || !item.hour || !item.duration_in_minutes) return + const start = parseStart(item, date) + const duration = parseInt(item.duration_in_minutes) + const stop = start.add(duration, 'm') + + programs.push({ + title: item.name || item.program.name, + description: item.description, + season: parseSeason(item), + episode: parseEpisode(item), + sub_title: item['episode_title'] || null, + url: parseURL(item), + start, + stop, + image: parseImage(item) + }) + }) + + return programs + } +} + +function parseStart(item, date) { + return dayjs.tz(`${date.format('YYYY-MM-DD')} ${item.hour}`, 'YYYY-MM-DD HH:mm', 'Europe/Rome') +} + +function parseImage(item) { + let cover = null + if (item.image) { + cover = `https://www.raiplay.it${item.image}` + } + return cover +} + +function parseURL(item) { + let url = null + if (item.weblink) { + url = `https://www.raiplay.it${item.weblink}` + } + if (item.event_weblink) { + url = `https://www.raiplay.it${item.event_weblink}` + } + return url +} + +function parseSeason(item) { + if (!item.season) return null + if (String(item.season).length > 2) return null + return item.season +} + +function parseEpisode(item) { + if (!item.episode) return null + if (String(item.episode).length > 3) return null + return item.episode +} diff --git a/sites/reportv.com.ar/reportv.com.ar.test.js b/sites/reportv.com.ar/reportv.com.ar.test.js index 19fa99bb8..2b20f79d3 100644 --- a/sites/reportv.com.ar/reportv.com.ar.test.js +++ b/sites/reportv.com.ar/reportv.com.ar.test.js @@ -1,111 +1,111 @@ -const { parser, url, request } = require('./reportv.com.ar.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) -const axios = require('axios') -jest.mock('axios') - -const date = dayjs.utc('2022-10-03', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '914', - xmltv_id: 'VePlusVenezuela.ve' -} - -it('can generate valid url', () => { - expect(url).toBe('https://www.reportv.com.ar/buscador/ProgXSenial.php') -}) - -it('can generate valid request method', () => { - expect(request.method).toBe('POST') -}) - -it('can generate valid request headers', () => { - expect(request.headers).toMatchObject({ - 'Content-Type': 'application/x-www-form-urlencoded' - }) -}) - -it('can generate valid request data', () => { - const result = request.data({ channel, date }) - expect(result.get('idSenial')).toBe('914') - expect(result.get('Alineacion')).toBe('2694') - expect(result.get('DiaDesde')).toBe('2022/10/03') - expect(result.get('HoraDesde')).toBe('00:00:00') -}) - -it('can parse response', async () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - - axios.post.mockImplementation((url, data) => { - if ( - url === 'https://www.reportv.com.ar/buscador/DetallePrograma.php' && - data.get('id') == '286096' - ) { - return Promise.resolve({ - data: fs.readFileSync(path.resolve(__dirname, '__data__/program1.html')) - }) - } else if ( - url === 'https://www.reportv.com.ar/buscador/DetallePrograma.php' && - data.get('id') == '392803' - ) { - return Promise.resolve({ - data: fs.readFileSync(path.resolve(__dirname, '__data__/program2.html')) - }) - } else { - return Promise.resolve({ - data: fs.readFileSync(path.resolve(__dirname, '__data__/no_program.html')) - }) - } - }) - - let results = await parser({ content, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2022-10-03T04:00:00.000Z', - stop: '2022-10-03T05:00:00.000Z', - title: '¿Quién tiene la razón?', - category: 'Talk Show', - image: 'https://www.reportv.com.ar/buscador/img/Programas/4401882.jpg', - actors: ['Nancy Álvarez'], - description: - 'Espacio que dará de qué hablar cuando la doctora Nancy Álvarez y Carmen Jara, acompañadas de un jurado implacable, lleguen a escuchar y a resolver los problemas de las partes en conflicto para luego decidir quién tiene la razón.' - }) - - expect(results[21]).toMatchObject({ - start: '2022-10-04T03:00:00.000Z', - stop: '2022-10-04T04:00:00.000Z', - title: 'Valeria', - category: 'Comedia', - image: 'https://www.reportv.com.ar/buscador/img/Programas/18788047.jpg', - directors: ['Inma Torrente'], - actors: [ - 'Diana Gómez', - 'Silma López', - 'Paula Malia', - 'Teresa Riott', - 'Maxi Iglesias', - 'Juanlu González', - 'Aitor Luna', - 'Lauren McFall', - 'Éva Martin', - 'Raquel Ventosa' - ], - description: - 'Valeria es una escritora que no está pasando por su mejor momento a nivel profesional y sentimental. La distancia emocional que la separa de su marido la lleva a refugiarse en sus tres mejores amigas: Carmen, Lola y Nerea. Valeria y sus amigas están inmersas en un torbellino de emociones de amor, amistad, celos, infidelidad, dudas, desamor, secretos, trabajo, preocupaciones, alegrías y sueños sobre el futuro.' - }) -}) - -it('can handle empty guide', async () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) - const result = await parser({ content, date }) - expect(result).toMatchObject([]) -}) +const { parser, url, request } = require('./reportv.com.ar.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) +const axios = require('axios') +jest.mock('axios') + +const date = dayjs.utc('2022-10-03', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '914', + xmltv_id: 'VePlusVenezuela.ve' +} + +it('can generate valid url', () => { + expect(url).toBe('https://www.reportv.com.ar/buscador/ProgXSenial.php') +}) + +it('can generate valid request method', () => { + expect(request.method).toBe('POST') +}) + +it('can generate valid request headers', () => { + expect(request.headers).toMatchObject({ + 'Content-Type': 'application/x-www-form-urlencoded' + }) +}) + +it('can generate valid request data', () => { + const result = request.data({ channel, date }) + expect(result.get('idSenial')).toBe('914') + expect(result.get('Alineacion')).toBe('2694') + expect(result.get('DiaDesde')).toBe('2022/10/03') + expect(result.get('HoraDesde')).toBe('00:00:00') +}) + +it('can parse response', async () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + + axios.post.mockImplementation((url, data) => { + if ( + url === 'https://www.reportv.com.ar/buscador/DetallePrograma.php' && + data.get('id') == '286096' + ) { + return Promise.resolve({ + data: fs.readFileSync(path.resolve(__dirname, '__data__/program1.html')) + }) + } else if ( + url === 'https://www.reportv.com.ar/buscador/DetallePrograma.php' && + data.get('id') == '392803' + ) { + return Promise.resolve({ + data: fs.readFileSync(path.resolve(__dirname, '__data__/program2.html')) + }) + } else { + return Promise.resolve({ + data: fs.readFileSync(path.resolve(__dirname, '__data__/no_program.html')) + }) + } + }) + + let results = await parser({ content, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2022-10-03T04:00:00.000Z', + stop: '2022-10-03T05:00:00.000Z', + title: '¿Quién tiene la razón?', + category: 'Talk Show', + image: 'https://www.reportv.com.ar/buscador/img/Programas/4401882.jpg', + actors: ['Nancy Álvarez'], + description: + 'Espacio que dará de qué hablar cuando la doctora Nancy Álvarez y Carmen Jara, acompañadas de un jurado implacable, lleguen a escuchar y a resolver los problemas de las partes en conflicto para luego decidir quién tiene la razón.' + }) + + expect(results[21]).toMatchObject({ + start: '2022-10-04T03:00:00.000Z', + stop: '2022-10-04T04:00:00.000Z', + title: 'Valeria', + category: 'Comedia', + image: 'https://www.reportv.com.ar/buscador/img/Programas/18788047.jpg', + directors: ['Inma Torrente'], + actors: [ + 'Diana Gómez', + 'Silma López', + 'Paula Malia', + 'Teresa Riott', + 'Maxi Iglesias', + 'Juanlu González', + 'Aitor Luna', + 'Lauren McFall', + 'Éva Martin', + 'Raquel Ventosa' + ], + description: + 'Valeria es una escritora que no está pasando por su mejor momento a nivel profesional y sentimental. La distancia emocional que la separa de su marido la lleva a refugiarse en sus tres mejores amigas: Carmen, Lola y Nerea. Valeria y sus amigas están inmersas en un torbellino de emociones de amor, amistad, celos, infidelidad, dudas, desamor, secretos, trabajo, preocupaciones, alegrías y sueños sobre el futuro.' + }) +}) + +it('can handle empty guide', async () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) + const result = await parser({ content, date }) + expect(result).toMatchObject([]) +}) diff --git a/sites/rikstv.no/rikstv.no.config.js b/sites/rikstv.no/rikstv.no.config.js index b897473b4..7204ffe30 100644 --- a/sites/rikstv.no/rikstv.no.config.js +++ b/sites/rikstv.no/rikstv.no.config.js @@ -1,76 +1,76 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const axios = require('axios') - -dayjs.extend(utc) - -module.exports = { - site: 'rikstv.no', - days: 3, - request: { - cache: { - ttl: 60 * 60 * 1000 // 1 hour - } - }, - url({ channel, date }) { - return `https://play.rikstv.no/api/content-search/1/channel/${ - channel.site_id - }/epg/${date.format('YYYY-MM-DD')}` - }, - parser: function ({ content }) { - let data - try { - data = JSON.parse(content) - } catch (error) { - console.error('Error parsing JSON:', error) - return [] - } - - const programs = [] - - if (data && Array.isArray(data)) { - data.forEach(item => { - if (!item) return - //const start = dayjs.utc(item.broadcastedTime) - //const stop = dayjs.utc(item.broadcastedTimeEnd) - - programs.push({ - title: item.seriesName, - sub_title: item.name, - description: item.description || item.synopsis, - season: item.season || null, - episode: item.episode || null, - category: item.genres, - actors: item.actors, - directors: item.director || item.directors, - icon: item.imagePackUri, - start: item.broadcastedTime, - stop: item.broadcastedTimeEnd - }) - }) - } - - return programs - }, - async channels() { - try { - const response = await axios.get( - 'https://play.rikstv.no/api/content-search/1/channel?includePrograms=false' - ) - if (!response.data || !Array.isArray(response.data)) { - console.error('Error: No channels data found') - return [] - } - return response.data.map(item => { - return { - lang: 'no', - site_id: item.channelId, - name: item.serviceName - } - }) - } catch (error) { - console.error('Error fetching channels:', error) - return [] - } - } -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const axios = require('axios') + +dayjs.extend(utc) + +module.exports = { + site: 'rikstv.no', + days: 3, + request: { + cache: { + ttl: 60 * 60 * 1000 // 1 hour + } + }, + url({ channel, date }) { + return `https://play.rikstv.no/api/content-search/1/channel/${ + channel.site_id + }/epg/${date.format('YYYY-MM-DD')}` + }, + parser: function ({ content }) { + let data + try { + data = JSON.parse(content) + } catch (error) { + console.error('Error parsing JSON:', error) + return [] + } + + const programs = [] + + if (data && Array.isArray(data)) { + data.forEach(item => { + if (!item) return + //const start = dayjs.utc(item.broadcastedTime) + //const stop = dayjs.utc(item.broadcastedTimeEnd) + + programs.push({ + title: item.seriesName, + sub_title: item.name, + description: item.description || item.synopsis, + season: item.season || null, + episode: item.episode || null, + category: item.genres, + actors: item.actors, + directors: item.director || item.directors, + icon: item.imagePackUri, + start: item.broadcastedTime, + stop: item.broadcastedTimeEnd + }) + }) + } + + return programs + }, + async channels() { + try { + const response = await axios.get( + 'https://play.rikstv.no/api/content-search/1/channel?includePrograms=false' + ) + if (!response.data || !Array.isArray(response.data)) { + console.error('Error: No channels data found') + return [] + } + return response.data.map(item => { + return { + lang: 'no', + site_id: item.channelId, + name: item.serviceName + } + }) + } catch (error) { + console.error('Error fetching channels:', error) + return [] + } + } +} diff --git a/sites/rotana.net/rotana.net.config.js b/sites/rotana.net/rotana.net.config.js index f38abbffe..d8ab85b6f 100644 --- a/sites/rotana.net/rotana.net.config.js +++ b/sites/rotana.net/rotana.net.config.js @@ -1,189 +1,189 @@ -const axios = require('axios') -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const timezone = require('dayjs/plugin/timezone') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -const doFetch = require('@ntlab/sfetch') -const debug = require('debug')('site:rotana.net') - -dayjs.extend(timezone) -dayjs.extend(utc) -dayjs.extend(customParseFormat) - -doFetch.setCheckResult(false).setDebugger(debug) - -const tz = 'Asia/Riyadh' -const defaultHeaders = { - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 OPR/104.0.0.0' -} -const cookies = {} - -module.exports = { - site: 'rotana.net', - days: 2, - url({ channel }) { - return `https://rotana.net/${channel.lang}/streams?channel=${channel.site_id}&tz=` - }, - request: { - headers: defaultHeaders, - timeout: 15000 - }, - async parser({ content, headers, channel, date }) { - const programs = [] - if (!cookies[channel.lang]) { - cookies[channel.lang] = parseCookies(headers) - } - - const items = parseItems(content, date) - if (items.length) { - const queues = [] - for (const item of items) { - const url = `https://rotana.net/${channel.lang}/streams?channel=${channel.site_id}&itemId=${item.program}` - const params = { - headers: { - ...defaultHeaders, - 'X-Requested-With': 'XMLHttpRequest', - cookie: cookies[channel.lang] - } - } - queues.push({ i: item, url, params }) - } - await doFetch(queues, (queue, res) => { - programs.push(parseProgram(queue.i, res)) - }) - } - - return programs - }, - async channels({ lang = 'en' }) { - const result = await axios - .get('https://rotana.net/api/channels') - .then(response => response.data) - .catch(console.error) - - return result.data.map(item => { - return { - lang, - site_id: item.id, - name: item.name[lang] - } - }) - } -} - -function parseProgram(item, result) { - const $ = cheerio.load(result) - const details = $('.trending-info .row div > span') - if (details.length) { - for (const el of details[0].children) { - switch (el.constructor.name) { - case 'Text': - if (item.description === undefined) { - const desc = $(el).text().trim() - if (desc) { - item.description = desc - } - } - break - case 'Element': - if (el.name === 'span') { - const [k, v] = $(el) - .text() - .split(':') - .map(a => a.trim()) - switch (k) { - case 'Category': - case 'التصنيف': - item.category = v - break - case 'Country': - case 'البلد': - item.country = v - break - case 'Director': - case 'المخرج': - item.director = v - break - case 'Language': - case 'اللغة': - item.language = v - break - case 'Release Year': - case 'سنة الإصدار': - item.date = v - break - } - } - break - } - } - } - const img = $('.row > div > img') - if (img.length) { - item.image = img.attr('src') - } - delete item.program - return item -} - -function parseItems(content, date) { - const $ = cheerio.load(content) - - const items = [] - let curDate - $('.hour > div').each((_, item) => { - const $item = $(item) - if ($item.hasClass('bg')) { - curDate = $item.attr('id') - curDate = curDate.substr(curDate.indexOf('-') + 1).split('-') - } else if ($item.hasClass('iq-accordion')) { - const top = $item.find('.iq-accordion-block') - const heading = top.find('.iq-accordion-title .big-title') - if (heading.length) { - const progId = top.attr('id') - const title = heading - .find('span:eq(1)') - .text() - .split('\n') - .map(a => a.trim()) - .join(' ') - const time = heading.find('span:eq(0)').text() - const [d, m, y] = curDate - items.push({ - program: progId.substr(progId.indexOf('-') + 1), - title: title ? title.trim() : title, - start: `${y}-${m}-${d} ${time.trim()}` - }) - } - } - }) - items.sort((a, b) => a.start.localeCompare(b.start)) - for (let i = 0; i < items.length; i++) { - if (i < items.length - 2) { - items[i].stop = items[i + 1].start - } else { - const dt = dayjs.tz(items[i].start).add(1, 'd') - items[i].stop = `${dt.format('YYYY-MM-DD')} 00:00` - } - } - const expectedDate = `${date.format('YYYY-MM-DD')}` - return items - .filter(a => a.start.startsWith(expectedDate) || a.stop.startsWith(expectedDate)) - .map(a => { - a.start = dayjs.tz(a.start, tz) - a.stop = dayjs.tz(a.stop, tz) - return a - }) -} - -function parseCookies(headers) { - const cookies = [] - if (headers && Array.isArray(headers['set-cookie'])) { - headers['set-cookie'].forEach(cookie => { - cookies.push(cookie.split('; ')[0]) - }) - } - return cookies.length ? cookies.join('; ') : null -} +const axios = require('axios') +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const timezone = require('dayjs/plugin/timezone') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const doFetch = require('@ntlab/sfetch') +const debug = require('debug')('site:rotana.net') + +dayjs.extend(timezone) +dayjs.extend(utc) +dayjs.extend(customParseFormat) + +doFetch.setCheckResult(false).setDebugger(debug) + +const tz = 'Asia/Riyadh' +const defaultHeaders = { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 OPR/104.0.0.0' +} +const cookies = {} + +module.exports = { + site: 'rotana.net', + days: 2, + url({ channel }) { + return `https://rotana.net/${channel.lang}/streams?channel=${channel.site_id}&tz=` + }, + request: { + headers: defaultHeaders, + timeout: 15000 + }, + async parser({ content, headers, channel, date }) { + const programs = [] + if (!cookies[channel.lang]) { + cookies[channel.lang] = parseCookies(headers) + } + + const items = parseItems(content, date) + if (items.length) { + const queues = [] + for (const item of items) { + const url = `https://rotana.net/${channel.lang}/streams?channel=${channel.site_id}&itemId=${item.program}` + const params = { + headers: { + ...defaultHeaders, + 'X-Requested-With': 'XMLHttpRequest', + cookie: cookies[channel.lang] + } + } + queues.push({ i: item, url, params }) + } + await doFetch(queues, (queue, res) => { + programs.push(parseProgram(queue.i, res)) + }) + } + + return programs + }, + async channels({ lang = 'en' }) { + const result = await axios + .get('https://rotana.net/api/channels') + .then(response => response.data) + .catch(console.error) + + return result.data.map(item => { + return { + lang, + site_id: item.id, + name: item.name[lang] + } + }) + } +} + +function parseProgram(item, result) { + const $ = cheerio.load(result) + const details = $('.trending-info .row div > span') + if (details.length) { + for (const el of details[0].children) { + switch (el.constructor.name) { + case 'Text': + if (item.description === undefined) { + const desc = $(el).text().trim() + if (desc) { + item.description = desc + } + } + break + case 'Element': + if (el.name === 'span') { + const [k, v] = $(el) + .text() + .split(':') + .map(a => a.trim()) + switch (k) { + case 'Category': + case 'التصنيف': + item.category = v + break + case 'Country': + case 'البلد': + item.country = v + break + case 'Director': + case 'المخرج': + item.director = v + break + case 'Language': + case 'اللغة': + item.language = v + break + case 'Release Year': + case 'سنة الإصدار': + item.date = v + break + } + } + break + } + } + } + const img = $('.row > div > img') + if (img.length) { + item.image = img.attr('src') + } + delete item.program + return item +} + +function parseItems(content, date) { + const $ = cheerio.load(content) + + const items = [] + let curDate + $('.hour > div').each((_, item) => { + const $item = $(item) + if ($item.hasClass('bg')) { + curDate = $item.attr('id') + curDate = curDate.substr(curDate.indexOf('-') + 1).split('-') + } else if ($item.hasClass('iq-accordion')) { + const top = $item.find('.iq-accordion-block') + const heading = top.find('.iq-accordion-title .big-title') + if (heading.length) { + const progId = top.attr('id') + const title = heading + .find('span:eq(1)') + .text() + .split('\n') + .map(a => a.trim()) + .join(' ') + const time = heading.find('span:eq(0)').text() + const [d, m, y] = curDate + items.push({ + program: progId.substr(progId.indexOf('-') + 1), + title: title ? title.trim() : title, + start: `${y}-${m}-${d} ${time.trim()}` + }) + } + } + }) + items.sort((a, b) => a.start.localeCompare(b.start)) + for (let i = 0; i < items.length; i++) { + if (i < items.length - 2) { + items[i].stop = items[i + 1].start + } else { + const dt = dayjs.tz(items[i].start).add(1, 'd') + items[i].stop = `${dt.format('YYYY-MM-DD')} 00:00` + } + } + const expectedDate = `${date.format('YYYY-MM-DD')}` + return items + .filter(a => a.start.startsWith(expectedDate) || a.stop.startsWith(expectedDate)) + .map(a => { + a.start = dayjs.tz(a.start, tz) + a.stop = dayjs.tz(a.stop, tz) + return a + }) +} + +function parseCookies(headers) { + const cookies = [] + if (headers && Array.isArray(headers['set-cookie'])) { + headers['set-cookie'].forEach(cookie => { + cookies.push(cookie.split('; ')[0]) + }) + } + return cookies.length ? cookies.join('; ') : null +} diff --git a/sites/rotana.net/rotana.net.test.js b/sites/rotana.net/rotana.net.test.js index ef49b147a..cc04274a6 100644 --- a/sites/rotana.net/rotana.net.test.js +++ b/sites/rotana.net/rotana.net.test.js @@ -1,113 +1,113 @@ -const { parser, url, request } = require('./rotana.net.config.js') -const fs = require('fs') -const path = require('path') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -const date = dayjs.utc('2024-11-26').startOf('d') -const channel = { - lang: 'en', - site_id: '439', - xmltv_id: 'RotanaCinemaMasr.sa' -} -const channelAr = Object.assign({}, channel, { lang: 'ar' }) - -axios.get.mockImplementation(url => { - if (url === 'https://rotana.net/en/streams?channel=439&itemId=736970') { - return Promise.resolve({ - data: fs.readFileSync(path.resolve(__dirname, '__data__/program_en.html')) - }) - } - if (url === 'https://rotana.net/ar/streams?channel=439&itemId=736970') { - return Promise.resolve({ - data: fs.readFileSync(path.resolve(__dirname, '__data__/program_ar.html')) - }) - } - - return Promise.resolve({ data: '' }) -}) - -it('can use defined user agent', () => { - const result = request.headers['User-Agent'] - expect(result).toBe( - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 OPR/104.0.0.0' - ) -}) - -it('can generate valid english url', () => { - const result = url({ channel, date }) - expect(result).toBe('https://rotana.net/en/streams?channel=439&tz=') -}) - -it('can generate valid arabic url', () => { - const result = url({ channel: channelAr, date }) - expect(result).toBe('https://rotana.net/ar/streams?channel=439&tz=') -}) - -it('can parse english response', async () => { - const result = ( - await parser({ - channel, - date, - content: fs.readFileSync(path.join(__dirname, '/__data__/content_en.html')) - }) - ).map(a => { - a.start = a.start.toJSON() - a.stop = a.stop.toJSON() - return a - }) - - expect(result.length).toBe(12) - expect(result[11]).toMatchObject({ - start: '2024-11-26T20:00:00.000Z', - stop: '2024-11-26T22:00:00.000Z', - title: 'Khiyana Mashroua', - description: - 'Hisham knows that his father has given all his wealth to his elder brother. This leads him to plan to kill his brother to make it look like a defense of honor, which he does by killing his wife along...', - image: - 'https://s3.eu-central-1.amazonaws.com/rotana.website/spider_storage/1398X1000/1687084565', - category: 'Movie' - }) -}) - -it('can parse arabic response', async () => { - const result = ( - await parser({ - channel: channelAr, - date, - content: fs.readFileSync(path.join(__dirname, '/__data__/content_ar.html')) - }) - ).map(a => { - a.start = a.start.toJSON() - a.stop = a.stop.toJSON() - return a - }) - - expect(result.length).toBe(12) - expect(result[11]).toMatchObject({ - start: '2024-11-26T20:00:00.000Z', - stop: '2024-11-26T22:00:00.000Z', - title: 'خيانة مشروعة', - description: - 'يعلم هشام البحيري أن والده قد حرمه من الميراث، ووهب كل ثروته لشقيقه اﻷكبر، وهو ما يدفعه لتدبير جريمة قتل شقيقه لتبدو وكأنها دفاع عن الشرف، وذلك حين يقتل هشام زوجته مع شقيقه.', - image: - 'https://s3.eu-central-1.amazonaws.com/rotana.website/spider_storage/1398X1000/1687084565', - category: 'فيلم' - }) -}) - -it('can handle empty guide', async () => { - const result = await parser({ - content: '', - date, - channel - }) - expect(result).toMatchObject([]) -}) +const { parser, url, request } = require('./rotana.net.config.js') +const fs = require('fs') +const path = require('path') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.utc('2024-11-26').startOf('d') +const channel = { + lang: 'en', + site_id: '439', + xmltv_id: 'RotanaCinemaMasr.sa' +} +const channelAr = Object.assign({}, channel, { lang: 'ar' }) + +axios.get.mockImplementation(url => { + if (url === 'https://rotana.net/en/streams?channel=439&itemId=736970') { + return Promise.resolve({ + data: fs.readFileSync(path.resolve(__dirname, '__data__/program_en.html')) + }) + } + if (url === 'https://rotana.net/ar/streams?channel=439&itemId=736970') { + return Promise.resolve({ + data: fs.readFileSync(path.resolve(__dirname, '__data__/program_ar.html')) + }) + } + + return Promise.resolve({ data: '' }) +}) + +it('can use defined user agent', () => { + const result = request.headers['User-Agent'] + expect(result).toBe( + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 OPR/104.0.0.0' + ) +}) + +it('can generate valid english url', () => { + const result = url({ channel, date }) + expect(result).toBe('https://rotana.net/en/streams?channel=439&tz=') +}) + +it('can generate valid arabic url', () => { + const result = url({ channel: channelAr, date }) + expect(result).toBe('https://rotana.net/ar/streams?channel=439&tz=') +}) + +it('can parse english response', async () => { + const result = ( + await parser({ + channel, + date, + content: fs.readFileSync(path.join(__dirname, '/__data__/content_en.html')) + }) + ).map(a => { + a.start = a.start.toJSON() + a.stop = a.stop.toJSON() + return a + }) + + expect(result.length).toBe(12) + expect(result[11]).toMatchObject({ + start: '2024-11-26T20:00:00.000Z', + stop: '2024-11-26T22:00:00.000Z', + title: 'Khiyana Mashroua', + description: + 'Hisham knows that his father has given all his wealth to his elder brother. This leads him to plan to kill his brother to make it look like a defense of honor, which he does by killing his wife along...', + image: + 'https://s3.eu-central-1.amazonaws.com/rotana.website/spider_storage/1398X1000/1687084565', + category: 'Movie' + }) +}) + +it('can parse arabic response', async () => { + const result = ( + await parser({ + channel: channelAr, + date, + content: fs.readFileSync(path.join(__dirname, '/__data__/content_ar.html')) + }) + ).map(a => { + a.start = a.start.toJSON() + a.stop = a.stop.toJSON() + return a + }) + + expect(result.length).toBe(12) + expect(result[11]).toMatchObject({ + start: '2024-11-26T20:00:00.000Z', + stop: '2024-11-26T22:00:00.000Z', + title: 'خيانة مشروعة', + description: + 'يعلم هشام البحيري أن والده قد حرمه من الميراث، ووهب كل ثروته لشقيقه اﻷكبر، وهو ما يدفعه لتدبير جريمة قتل شقيقه لتبدو وكأنها دفاع عن الشرف، وذلك حين يقتل هشام زوجته مع شقيقه.', + image: + 'https://s3.eu-central-1.amazonaws.com/rotana.website/spider_storage/1398X1000/1687084565', + category: 'فيلم' + }) +}) + +it('can handle empty guide', async () => { + const result = await parser({ + content: '', + date, + channel + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/rtb.gov.bn/rtb.gov.bn.config.js b/sites/rtb.gov.bn/rtb.gov.bn.config.js index c560f224a..5a4239eb4 100644 --- a/sites/rtb.gov.bn/rtb.gov.bn.config.js +++ b/sites/rtb.gov.bn/rtb.gov.bn.config.js @@ -1,74 +1,74 @@ -const pdf = require('pdf-parse') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'rtb.gov.bn', - days: 2, - url: function ({ channel, date }) { - return encodeURI( - `http://www.rtb.gov.bn/PublishingImages/SitePages/Programme Guide/${ - channel.site_id - } ${date.format('DD MMMM YYYY')}.pdf` - ) - }, - parser: async function ({ buffer, date }) { - let programs = [] - const items = await parseItems(buffer) - items.forEach(item => { - const prev = programs[programs.length - 1] - let start = parseStart(item, date) - if (prev) { - if (start.isBefore(prev.start)) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.add(1, 'h') - programs.push({ - title: item.title, - start, - stop - }) - }) - - return programs - } -} - -function parseStart(item, date) { - const dateString = `${date.format('YYYY-MM-DD')} ${item.time}` - - return dayjs.tz(dateString, 'YYYY-MM-DD HH:mm', 'Asia/Brunei') -} - -async function parseItems(buffer) { - let data - try { - data = await pdf(buffer) - } catch { - return [] - } - - if (!data) return [] - - return data.text - .split('\n') - .filter(s => { - const string = s.trim() - - return string && /^\d{2}:\d{2}/.test(string) - }) - .map(s => { - const [, time, title] = s.trim().match(/^(\d{2}:\d{2}) (.*)/) || [null, null, null] - - return { time, title } - }) -} +const pdf = require('pdf-parse') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'rtb.gov.bn', + days: 2, + url: function ({ channel, date }) { + return encodeURI( + `http://www.rtb.gov.bn/PublishingImages/SitePages/Programme Guide/${ + channel.site_id + } ${date.format('DD MMMM YYYY')}.pdf` + ) + }, + parser: async function ({ buffer, date }) { + let programs = [] + const items = await parseItems(buffer) + items.forEach(item => { + const prev = programs[programs.length - 1] + let start = parseStart(item, date) + if (prev) { + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.add(1, 'h') + programs.push({ + title: item.title, + start, + stop + }) + }) + + return programs + } +} + +function parseStart(item, date) { + const dateString = `${date.format('YYYY-MM-DD')} ${item.time}` + + return dayjs.tz(dateString, 'YYYY-MM-DD HH:mm', 'Asia/Brunei') +} + +async function parseItems(buffer) { + let data + try { + data = await pdf(buffer) + } catch { + return [] + } + + if (!data) return [] + + return data.text + .split('\n') + .filter(s => { + const string = s.trim() + + return string && /^\d{2}:\d{2}/.test(string) + }) + .map(s => { + const [, time, title] = s.trim().match(/^(\d{2}:\d{2}) (.*)/) || [null, null, null] + + return { time, title } + }) +} diff --git a/sites/rtb.gov.bn/rtb.gov.bn.test.js b/sites/rtb.gov.bn/rtb.gov.bn.test.js index 5ce7f0ee5..544b483b6 100644 --- a/sites/rtb.gov.bn/rtb.gov.bn.test.js +++ b/sites/rtb.gov.bn/rtb.gov.bn.test.js @@ -1,94 +1,94 @@ -const { parser, url } = require('./rtb.gov.bn.config.js') -const path = require('path') -const fs = require('fs') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2021-11-11', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'Sukmaindera', - xmltv_id: 'RTBSukmaindera.bn' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'http://www.rtb.gov.bn/PublishingImages/SitePages/Programme%20Guide/Sukmaindera%2011%20November%202021.pdf' - ) -}) - -it('can parse Sukmaindera 11 November 2021.pdf', done => { - const buffer = fs.readFileSync( - path.resolve(__dirname, '__data__/Sukmaindera 11 November 2021.pdf'), - { - charset: 'utf8' - } - ) - parser({ buffer, date }) - .then(results => { - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - expect(results.length).toBe(47) - expect(results[0]).toMatchObject({ - start: '2021-11-10T22:00:00.000Z', - stop: '2021-11-10T22:05:00.000Z', - title: 'NATIONAL ANTHEM' - }) - expect(results[46]).toMatchObject({ - start: '2021-11-11T21:30:00.000Z', - stop: '2021-11-11T22:30:00.000Z', - title: 'BACAAN SURAH YASSIN' - }) - done() - }) - .catch(error => { - done(error) - }) -}) - -it('can parse Aneka 11 November 2021.pdf', done => { - const buffer = fs.readFileSync(path.resolve(__dirname, '__data__/Aneka 11 November 2021.pdf'), { - charset: 'utf8' - }) - parser({ buffer, date }) - .then(results => { - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - expect(results.length).toBe(26) - expect(results[4]).toMatchObject({ - start: '2021-11-11T03:00:00.000Z', - stop: '2021-11-11T04:05:00.000Z', - title: 'DRAMA TURKI:' - }) - done() - }) - .catch(error => { - done(error) - }) -}) - -it('can handle empty guide', done => { - parser({ - date, - channel, - content: `Object moved -

    Object moved to here.

    - -` - }) - .then(result => { - expect(result).toMatchObject([]) - done() - }) - .catch(error => { - done(error) - }) -}) +const { parser, url } = require('./rtb.gov.bn.config.js') +const path = require('path') +const fs = require('fs') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2021-11-11', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'Sukmaindera', + xmltv_id: 'RTBSukmaindera.bn' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'http://www.rtb.gov.bn/PublishingImages/SitePages/Programme%20Guide/Sukmaindera%2011%20November%202021.pdf' + ) +}) + +it('can parse Sukmaindera 11 November 2021.pdf', done => { + const buffer = fs.readFileSync( + path.resolve(__dirname, '__data__/Sukmaindera 11 November 2021.pdf'), + { + charset: 'utf8' + } + ) + parser({ buffer, date }) + .then(results => { + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + expect(results.length).toBe(47) + expect(results[0]).toMatchObject({ + start: '2021-11-10T22:00:00.000Z', + stop: '2021-11-10T22:05:00.000Z', + title: 'NATIONAL ANTHEM' + }) + expect(results[46]).toMatchObject({ + start: '2021-11-11T21:30:00.000Z', + stop: '2021-11-11T22:30:00.000Z', + title: 'BACAAN SURAH YASSIN' + }) + done() + }) + .catch(error => { + done(error) + }) +}) + +it('can parse Aneka 11 November 2021.pdf', done => { + const buffer = fs.readFileSync(path.resolve(__dirname, '__data__/Aneka 11 November 2021.pdf'), { + charset: 'utf8' + }) + parser({ buffer, date }) + .then(results => { + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + expect(results.length).toBe(26) + expect(results[4]).toMatchObject({ + start: '2021-11-11T03:00:00.000Z', + stop: '2021-11-11T04:05:00.000Z', + title: 'DRAMA TURKI:' + }) + done() + }) + .catch(error => { + done(error) + }) +}) + +it('can handle empty guide', done => { + parser({ + date, + channel, + content: `Object moved +

    Object moved to here.

    + +` + }) + .then(result => { + expect(result).toMatchObject([]) + done() + }) + .catch(error => { + done(error) + }) +}) diff --git a/sites/rthk.hk/rthk.hk.config.js b/sites/rthk.hk/rthk.hk.config.js index f9efcc698..babdb9605 100644 --- a/sites/rthk.hk/rthk.hk.config.js +++ b/sites/rthk.hk/rthk.hk.config.js @@ -1,89 +1,89 @@ -const dayjs = require('dayjs') -const cheerio = require('cheerio') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'rthk.hk', - days: 2, - request: { - headers({ channel }) { - return { - Cookie: `lang=${channel.lang}` - } - }, - cache: { - ttl: 60 * 60 * 1000 // 1h - } - }, - url: function ({ date }) { - return `https://www.rthk.hk/timetable/main_timetable/${date.format('YYYYMMDD')}` - }, - parser({ content, channel, date }) { - const programs = [] - const items = parseItems(content, channel) - for (let item of items) { - const $item = cheerio.load(item) - programs.push({ - title: parseTitle($item), - sub_title: parseSubTitle($item), - categories: parseCategories($item), - image: parseImage($item), - start: parseStart($item, date), - stop: parseStop($item, date) - }) - } - - return programs - } -} - -function parseImage($item) { - return $item('.single-wrap').data('p') -} - -function parseCategories($item) { - let cate = $item('.single-wrap').data('cate') || '' - let [, categories] = cate.match(/^\|(.*)\|$/) || [null, ''] - - return categories.split('||').filter(Boolean) -} - -function parseTitle($item) { - return $item('.showTit').attr('title') -} - -function parseSubTitle($item) { - return $item('.showEpi').attr('title') -} - -function parseStart($item, date) { - const timeRow = $item('.timeRow').text().trim() - const [, HH, mm] = timeRow.match(/^(\d+):(\d+)-/) || [null, null, null] - if (!HH || !mm) return null - - return dayjs.tz(`${date.format('YYYY-MM-DD')} ${HH}:${mm}`, 'YYYY-MM-DD HH:mm', 'Asia/Hong_Kong') -} - -function parseStop($item, date) { - const timeRow = $item('.timeRow').text().trim() - const [, HH, mm] = timeRow.match(/-(\d+):(\d+)$/) || [null, null, null] - if (!HH || !mm) return null - - return dayjs.tz(`${date.format('YYYY-MM-DD')} ${HH}:${mm}`, 'YYYY-MM-DD HH:mm', 'Asia/Hong_Kong') -} - -function parseItems(content, channel) { - const data = JSON.parse(content) - if (!data || !Array.isArray(data.result)) return [] - const channelData = data.result.find(i => i.key == channel.site_id) - if (!channelData || !channelData.data) return [] - const $ = cheerio.load(channelData.data) - - return $('.showWrap').toArray() -} +const dayjs = require('dayjs') +const cheerio = require('cheerio') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'rthk.hk', + days: 2, + request: { + headers({ channel }) { + return { + Cookie: `lang=${channel.lang}` + } + }, + cache: { + ttl: 60 * 60 * 1000 // 1h + } + }, + url: function ({ date }) { + return `https://www.rthk.hk/timetable/main_timetable/${date.format('YYYYMMDD')}` + }, + parser({ content, channel, date }) { + const programs = [] + const items = parseItems(content, channel) + for (let item of items) { + const $item = cheerio.load(item) + programs.push({ + title: parseTitle($item), + sub_title: parseSubTitle($item), + categories: parseCategories($item), + image: parseImage($item), + start: parseStart($item, date), + stop: parseStop($item, date) + }) + } + + return programs + } +} + +function parseImage($item) { + return $item('.single-wrap').data('p') +} + +function parseCategories($item) { + let cate = $item('.single-wrap').data('cate') || '' + let [, categories] = cate.match(/^\|(.*)\|$/) || [null, ''] + + return categories.split('||').filter(Boolean) +} + +function parseTitle($item) { + return $item('.showTit').attr('title') +} + +function parseSubTitle($item) { + return $item('.showEpi').attr('title') +} + +function parseStart($item, date) { + const timeRow = $item('.timeRow').text().trim() + const [, HH, mm] = timeRow.match(/^(\d+):(\d+)-/) || [null, null, null] + if (!HH || !mm) return null + + return dayjs.tz(`${date.format('YYYY-MM-DD')} ${HH}:${mm}`, 'YYYY-MM-DD HH:mm', 'Asia/Hong_Kong') +} + +function parseStop($item, date) { + const timeRow = $item('.timeRow').text().trim() + const [, HH, mm] = timeRow.match(/-(\d+):(\d+)$/) || [null, null, null] + if (!HH || !mm) return null + + return dayjs.tz(`${date.format('YYYY-MM-DD')} ${HH}:${mm}`, 'YYYY-MM-DD HH:mm', 'Asia/Hong_Kong') +} + +function parseItems(content, channel) { + const data = JSON.parse(content) + if (!data || !Array.isArray(data.result)) return [] + const channelData = data.result.find(i => i.key == channel.site_id) + if (!channelData || !channelData.data) return [] + const $ = cheerio.load(channelData.data) + + return $('.showWrap').toArray() +} diff --git a/sites/rthk.hk/rthk.hk.test.js b/sites/rthk.hk/rthk.hk.test.js index 19541dfc6..40da15192 100644 --- a/sites/rthk.hk/rthk.hk.test.js +++ b/sites/rthk.hk/rthk.hk.test.js @@ -1,80 +1,80 @@ -const { parser, url, request } = require('./rthk.hk.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -const date = dayjs.utc('2022-12-02', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '31', - xmltv_id: 'RTHKTV31.hk', - lang: 'zh' -} - -it('can generate valid url', () => { - expect(url({ date })).toBe('https://www.rthk.hk/timetable/main_timetable/20221202') -}) - -it('can generate valid request headers', () => { - expect(request.headers({ channel })).toMatchObject({ - Cookie: 'lang=zh' - }) -}) - -it('can generate valid request headers for English version', () => { - const channelEN = { ...channel, lang: 'en' } - - expect(request.headers({ channel: channelEN })).toMatchObject({ - Cookie: 'lang=en' - }) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_zh.json')) - let results = parser({ content, channel, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2022-12-01T16:00:00.000Z', - stop: '2022-12-01T17:00:00.000Z', - title: '問天', - sub_title: '第十四集', - categories: ['戲劇'], - image: 'https://www.rthk.hk/assets/images/rthk/dtt31/thegreataerospace/10239_1920_s.jpg' - }) -}) - -it('can parse response in English', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_en.json')) - let results = parser({ content, channel, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2022-12-01T16:00:00.000Z', - stop: '2022-12-01T17:00:00.000Z', - title: 'The Great Aerospace', - sub_title: 'Episode 14', - categories: ['戲劇'], - image: 'https://www.rthk.hk/assets/images/rthk/dtt31/thegreataerospace/10239_1920_s.jpg' - }) -}) - -it('can handle empty guide', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - const results = parser({ date, channel, content }) - - expect(results).toMatchObject([]) -}) +const { parser, url, request } = require('./rthk.hk.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.utc('2022-12-02', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '31', + xmltv_id: 'RTHKTV31.hk', + lang: 'zh' +} + +it('can generate valid url', () => { + expect(url({ date })).toBe('https://www.rthk.hk/timetable/main_timetable/20221202') +}) + +it('can generate valid request headers', () => { + expect(request.headers({ channel })).toMatchObject({ + Cookie: 'lang=zh' + }) +}) + +it('can generate valid request headers for English version', () => { + const channelEN = { ...channel, lang: 'en' } + + expect(request.headers({ channel: channelEN })).toMatchObject({ + Cookie: 'lang=en' + }) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_zh.json')) + let results = parser({ content, channel, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2022-12-01T16:00:00.000Z', + stop: '2022-12-01T17:00:00.000Z', + title: '問天', + sub_title: '第十四集', + categories: ['戲劇'], + image: 'https://www.rthk.hk/assets/images/rthk/dtt31/thegreataerospace/10239_1920_s.jpg' + }) +}) + +it('can parse response in English', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_en.json')) + let results = parser({ content, channel, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2022-12-01T16:00:00.000Z', + stop: '2022-12-01T17:00:00.000Z', + title: 'The Great Aerospace', + sub_title: 'Episode 14', + categories: ['戲劇'], + image: 'https://www.rthk.hk/assets/images/rthk/dtt31/thegreataerospace/10239_1920_s.jpg' + }) +}) + +it('can handle empty guide', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + const results = parser({ date, channel, content }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/rtmklik.rtm.gov.my/rtmklik.rtm.gov.my.config.js b/sites/rtmklik.rtm.gov.my/rtmklik.rtm.gov.my.config.js index 623a7c834..f42a408bf 100644 --- a/sites/rtmklik.rtm.gov.my/rtmklik.rtm.gov.my.config.js +++ b/sites/rtmklik.rtm.gov.my/rtmklik.rtm.gov.my.config.js @@ -1,44 +1,44 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'rtmklik.rtm.gov.my', - days: 2, - url: function ({ date, channel }) { - return `https://rtm.glueapi.io/v3/epg/${ - channel.site_id - }/ChannelSchedule?dateStart=${date.format('YYYY-MM-DD')}&dateEnd=${date.format( - 'YYYY-MM-DD' - )}&timezone=0` - }, - parser: function ({ content }) { - const programs = [] - const items = parseItems(content) - if (!items.length) return programs - items.forEach(item => { - programs.push({ - title: item.programTitle, - description: item.description, - start: parseTime(item.dateTimeStart), - stop: parseTime(item.dateTimeEnd) - }) - }) - - return programs - } -} - -function parseItems(content) { - const data = JSON.parse(content) - return data.schedule ? data.schedule : [] -} - -function parseTime(time) { - return dayjs.utc(time, 'YYYY-MM-DDTHH:mm:ss') -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'rtmklik.rtm.gov.my', + days: 2, + url: function ({ date, channel }) { + return `https://rtm.glueapi.io/v3/epg/${ + channel.site_id + }/ChannelSchedule?dateStart=${date.format('YYYY-MM-DD')}&dateEnd=${date.format( + 'YYYY-MM-DD' + )}&timezone=0` + }, + parser: function ({ content }) { + const programs = [] + const items = parseItems(content) + if (!items.length) return programs + items.forEach(item => { + programs.push({ + title: item.programTitle, + description: item.description, + start: parseTime(item.dateTimeStart), + stop: parseTime(item.dateTimeEnd) + }) + }) + + return programs + } +} + +function parseItems(content) { + const data = JSON.parse(content) + return data.schedule ? data.schedule : [] +} + +function parseTime(time) { + return dayjs.utc(time, 'YYYY-MM-DDTHH:mm:ss') +} diff --git a/sites/rtp.pt/rtp.pt.test.js b/sites/rtp.pt/rtp.pt.test.js index 7feeb1f68..97cf797a3 100644 --- a/sites/rtp.pt/rtp.pt.test.js +++ b/sites/rtp.pt/rtp.pt.test.js @@ -1,42 +1,42 @@ -const { parser, url } = require('./rtp.pt.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-12-02', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'lis#4', - xmltv_id: 'RTPMadeira.pt' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://www.rtp.pt/EPG/json/rtp-channels-page/list-grid/tv/4/2-12-2022/lis' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - let results = parser({ content, channel }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[9]).toMatchObject({ - start: '2022-12-02T23:30:00.000Z', - stop: '2022-12-03T00:00:00.000Z', - title: 'Telejornal Madeira', - description: 'Informação de proximidade. De confiança!', - image: 'https://cdn-images.rtp.pt/EPG/imagens/15790_43438_8820.png?w=384&h=216' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ content: '', channel, date }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./rtp.pt.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-12-02', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'lis#4', + xmltv_id: 'RTPMadeira.pt' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://www.rtp.pt/EPG/json/rtp-channels-page/list-grid/tv/4/2-12-2022/lis' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + let results = parser({ content, channel }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[9]).toMatchObject({ + start: '2022-12-02T23:30:00.000Z', + stop: '2022-12-03T00:00:00.000Z', + title: 'Telejornal Madeira', + description: 'Informação de proximidade. De confiança!', + image: 'https://cdn-images.rtp.pt/EPG/imagens/15790_43438_8820.png?w=384&h=216' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ content: '', channel, date }) + expect(result).toMatchObject([]) +}) diff --git a/sites/ruv.is/ruv.is.config.js b/sites/ruv.is/ruv.is.config.js index 7bc6024a2..5612640ce 100644 --- a/sites/ruv.is/ruv.is.config.js +++ b/sites/ruv.is/ruv.is.config.js @@ -1,79 +1,79 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'ruv.is', - days: 2, - url({ channel, date }) { - let params = new URLSearchParams() - params.append('operationName', 'getSchedule') - params.append( - 'variables', - JSON.stringify({ channel: channel.site_id, date: date.format('YYYY-MM-DD') }) - ) - params.append( - 'extensions', - JSON.stringify({ - persistedQuery: { - version: 1, - sha256Hash: '7d133b9bd9e50127e90f2b3af1b41eb5e89cd386ed9b100b55169f395af350e6' - } - }) - ) - - return `https://www.ruv.is/gql/?${params.toString()}` - }, - parser({ content, date }) { - let programs = [] - const items = parseItems(content, date) - items.forEach(item => { - let start = parseStart(item, date) - let stop = parseStop(item, date) - if (stop.isBefore(start)) { - stop = stop.add(1, 'd') - } - programs.push({ - title: item.title, - description: item.description, - image: parseImage(item), - start, - stop - }) - }) - - return programs - } -} - -function parseImage(item) { - return item.image.replace('$$IMAGESIZE$$', '480') -} - -function parseStart(item, date) { - return dayjs.tz( - `${date.format('YYYY-MM-DD')} ${item.start_time_friendly}`, - 'YYYY-MM-DD HH:mm', - 'Atlantic/Reykjavik' - ) -} - -function parseStop(item, date) { - return dayjs.tz( - `${date.format('YYYY-MM-DD')} ${item.end_time_friendly}`, - 'YYYY-MM-DD HH:mm', - 'Atlantic/Reykjavik' - ) -} - -function parseItems(content) { - const data = JSON.parse(content) - if (!data || !Array.isArray(data.data.Schedule.events)) return [] - - return data.data.Schedule.events -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'ruv.is', + days: 2, + url({ channel, date }) { + let params = new URLSearchParams() + params.append('operationName', 'getSchedule') + params.append( + 'variables', + JSON.stringify({ channel: channel.site_id, date: date.format('YYYY-MM-DD') }) + ) + params.append( + 'extensions', + JSON.stringify({ + persistedQuery: { + version: 1, + sha256Hash: '7d133b9bd9e50127e90f2b3af1b41eb5e89cd386ed9b100b55169f395af350e6' + } + }) + ) + + return `https://www.ruv.is/gql/?${params.toString()}` + }, + parser({ content, date }) { + let programs = [] + const items = parseItems(content, date) + items.forEach(item => { + let start = parseStart(item, date) + let stop = parseStop(item, date) + if (stop.isBefore(start)) { + stop = stop.add(1, 'd') + } + programs.push({ + title: item.title, + description: item.description, + image: parseImage(item), + start, + stop + }) + }) + + return programs + } +} + +function parseImage(item) { + return item.image.replace('$$IMAGESIZE$$', '480') +} + +function parseStart(item, date) { + return dayjs.tz( + `${date.format('YYYY-MM-DD')} ${item.start_time_friendly}`, + 'YYYY-MM-DD HH:mm', + 'Atlantic/Reykjavik' + ) +} + +function parseStop(item, date) { + return dayjs.tz( + `${date.format('YYYY-MM-DD')} ${item.end_time_friendly}`, + 'YYYY-MM-DD HH:mm', + 'Atlantic/Reykjavik' + ) +} + +function parseItems(content) { + const data = JSON.parse(content) + if (!data || !Array.isArray(data.data.Schedule.events)) return [] + + return data.data.Schedule.events +} diff --git a/sites/ruv.is/ruv.is.test.js b/sites/ruv.is/ruv.is.test.js index ed52d2874..0deab56aa 100644 --- a/sites/ruv.is/ruv.is.test.js +++ b/sites/ruv.is/ruv.is.test.js @@ -1,47 +1,47 @@ -const { parser, url } = require('./ruv.is.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-01-17', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'ruv', - xmltv_id: 'RUV.is' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://www.ruv.is/gql/?operationName=getSchedule&variables=%7B%22channel%22%3A%22ruv%22%2C%22date%22%3A%222023-01-17%22%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%227d133b9bd9e50127e90f2b3af1b41eb5e89cd386ed9b100b55169f395af350e6%22%7D%7D' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - let results = parser({ content, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2023-01-17T13:00:00.000Z', - stop: '2023-01-17T13:10:00.000Z', - title: 'Heimaleikfimi', - description: - 'Góð ráð og æfingar sem tilvalið er að gera heima. Íris Rut Garðarsdóttir sjúkraþjálfari hefur umsjón með leikfiminni. e.', - image: - 'https://d38kdhuogyllre.cloudfront.net/fit-in/480x/filters:quality(65)/hd_posters/91pvig-3p3hig.jpg' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./ruv.is.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-01-17', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'ruv', + xmltv_id: 'RUV.is' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://www.ruv.is/gql/?operationName=getSchedule&variables=%7B%22channel%22%3A%22ruv%22%2C%22date%22%3A%222023-01-17%22%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%227d133b9bd9e50127e90f2b3af1b41eb5e89cd386ed9b100b55169f395af350e6%22%7D%7D' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + let results = parser({ content, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2023-01-17T13:00:00.000Z', + stop: '2023-01-17T13:10:00.000Z', + title: 'Heimaleikfimi', + description: + 'Góð ráð og æfingar sem tilvalið er að gera heima. Íris Rut Garðarsdóttir sjúkraþjálfari hefur umsjón með leikfiminni. e.', + image: + 'https://d38kdhuogyllre.cloudfront.net/fit-in/480x/filters:quality(65)/hd_posters/91pvig-3p3hig.jpg' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/s.mxtv.jp/s.mxtv.jp.config.js b/sites/s.mxtv.jp/s.mxtv.jp.config.js index d85d3c06a..0becd41f6 100644 --- a/sites/s.mxtv.jp/s.mxtv.jp.config.js +++ b/sites/s.mxtv.jp/s.mxtv.jp.config.js @@ -1,83 +1,83 @@ -const dayjs = require('dayjs') -const duration = require('dayjs/plugin/duration') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) -dayjs.extend(duration) - -module.exports = { - site: 's.mxtv.jp', - days: 1, - lang: 'ja', - url: function ({ date, channel }) { - const id = `SV${channel.site_id}EPG${date.format('YYYYMMDD')}` - return `https://s.mxtv.jp/bangumi_file/json01/${id}.json` - }, - parser: function ({ content }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - programs.push({ - title: item.Event_name, - description: item.Event_text, - category: parseCategory(item), - image: parseImage(item), - start: parseStart(item), - stop: parseStop(item) - }) - }) - return programs - }, - channels() { - return [ - { - lang: 'ja', - site_id: '1', - name: 'Tokyo MX1', - xmltv_id: 'TokyoMX1.jp' - }, - { - lang: 'ja', - site_id: '2', - name: 'Tokyo MX2', - xmltv_id: 'TokyoMX2.jp' - } - ] - } -} - -function parseImage() { - // Should return a string if we can output an image URL - // Might be done with `https://s.mxtv.jp/bangumi/link/weblinkU.csv?1722421896752` ? - return null -} - -function parseCategory() { - // Should return a string if we can determine the category - // Might be done with `https://s.mxtv.jp/index_set/csv/ranking_bangumi_allU.csv` ? - return null -} - -function parseStart(item) { - return dayjs.tz(item.Start_time.toString(), 'YYYY年MM月DD日HH時mm分ss秒', 'Asia/Tokyo') -} - -function parseStop(item) { - // Add the duration to the start time - const durationDate = dayjs(item.Duration, 'HH:mm:ss') - return parseStart(item).add( - dayjs.duration({ - hours: durationDate.hour(), - minutes: durationDate.minute(), - seconds: durationDate.second() - }) - ) -} - -function parseItems(content) { - return JSON.parse(content) || [] -} +const dayjs = require('dayjs') +const duration = require('dayjs/plugin/duration') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) +dayjs.extend(duration) + +module.exports = { + site: 's.mxtv.jp', + days: 1, + lang: 'ja', + url: function ({ date, channel }) { + const id = `SV${channel.site_id}EPG${date.format('YYYYMMDD')}` + return `https://s.mxtv.jp/bangumi_file/json01/${id}.json` + }, + parser: function ({ content }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + programs.push({ + title: item.Event_name, + description: item.Event_text, + category: parseCategory(item), + image: parseImage(item), + start: parseStart(item), + stop: parseStop(item) + }) + }) + return programs + }, + channels() { + return [ + { + lang: 'ja', + site_id: '1', + name: 'Tokyo MX1', + xmltv_id: 'TokyoMX1.jp' + }, + { + lang: 'ja', + site_id: '2', + name: 'Tokyo MX2', + xmltv_id: 'TokyoMX2.jp' + } + ] + } +} + +function parseImage() { + // Should return a string if we can output an image URL + // Might be done with `https://s.mxtv.jp/bangumi/link/weblinkU.csv?1722421896752` ? + return null +} + +function parseCategory() { + // Should return a string if we can determine the category + // Might be done with `https://s.mxtv.jp/index_set/csv/ranking_bangumi_allU.csv` ? + return null +} + +function parseStart(item) { + return dayjs.tz(item.Start_time.toString(), 'YYYY年MM月DD日HH時mm分ss秒', 'Asia/Tokyo') +} + +function parseStop(item) { + // Add the duration to the start time + const durationDate = dayjs(item.Duration, 'HH:mm:ss') + return parseStart(item).add( + dayjs.duration({ + hours: durationDate.hour(), + minutes: durationDate.minute(), + seconds: durationDate.second() + }) + ) +} + +function parseItems(content) { + return JSON.parse(content) || [] +} diff --git a/sites/sat.tv/sat.tv.config.js b/sites/sat.tv/sat.tv.config.js index 204ab99a1..e342db6ae 100644 --- a/sites/sat.tv/sat.tv.config.js +++ b/sites/sat.tv/sat.tv.config.js @@ -1,173 +1,173 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const cheerio = require('cheerio') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(customParseFormat) - -const API_ENDPOINT = 'https://www.sat.tv/wp-content/themes/twentytwenty-child/ajax_chaines.php' - -module.exports = { - site: 'sat.tv', - days: 2, - url: API_ENDPOINT, - request: { - method: 'POST', - headers({ channel }) { - return { - 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', - Cookie: `pll_language=${channel.lang}` - } - }, - data({ channel, date }) { - const [satSatellite, satLineup] = channel.site_id.split('#') - const params = new URLSearchParams() - params.append('dateFiltre', date.format('YYYY-MM-DD')) - params.append('hoursFiltre', '0') - params.append('satLineup', satLineup) - params.append('satSatellite', satSatellite) - params.append('userDateTime', date.valueOf()) - params.append('userTimezone', 'Europe/London') - - return params - }, - cache: { - ttl: 60 * 60 * 1000 // 1h - } - }, - parser: function ({ content, date, channel }) { - let programs = [] - const items = parseItems(content, channel) - items.forEach(item => { - let $item = cheerio.load(item) - let start = parseStart($item, date) - let duration = parseDuration($item) - let stop = start.add(duration, 'm') - - programs.push({ - title: parseTitle($item), - description: parseDescription($item), - image: parseImage($item), - start, - stop - }) - }) - - return programs - }, - async channels({ lang }) { - const satellites = [ - { satellite: 2, lineup: 55 }, - { satellite: 2, lineup: 58 }, - { satellite: 2, lineup: 53 }, - { satellite: 2, lineup: 57 }, - { satellite: 2, lineup: 54 }, - { satellite: 2, lineup: 56 }, - { satellite: 1, lineup: 48 }, - { satellite: 1, lineup: 44 }, - { satellite: 1, lineup: 42 }, - { satellite: 1, lineup: 39 }, - { satellite: 1, lineup: 37 }, - { satellite: 1, lineup: 38 }, - { satellite: 1, lineup: 68 }, - { satellite: 1, lineup: 47 }, - { satellite: 1, lineup: 41 }, - { satellite: 1, lineup: 49 }, - { satellite: 1, lineup: 46 }, - { satellite: 1, lineup: 35 }, - { satellite: 1, lineup: 43 }, - { satellite: 1, lineup: 45 }, - { satellite: 1, lineup: 50 }, - { satellite: 1, lineup: 71 }, - { satellite: 1, lineup: 40 }, - { satellite: 1, lineup: 72 }, - { satellite: 1, lineup: 33 }, - { satellite: 8, lineup: 62 }, - { satellite: 8, lineup: 63 }, - { satellite: 8, lineup: 64 }, - { satellite: 8, lineup: 65 }, - { satellite: 8, lineup: 66 }, - { satellite: 8, lineup: 67 } - ] - - let channels = [] - for (let sat of satellites) { - const params = new URLSearchParams() - params.append('dateFiltre', dayjs().format('YYYY-MM-DD')) - params.append('hoursFiltre', '0') - params.append('satLineup', sat.lineup) - params.append('satSatellite', sat.satellite) - params.append('userDateTime', dayjs().valueOf()) - params.append('userTimezone', 'Europe/London') - const data = await axios - .post(API_ENDPOINT, params, { - headers: { - 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', - Cookie: `pll_language=${lang}` - } - }) - .then(r => r.data) - .catch(console.log) - - const $ = cheerio.load(data) - $('.main-container-channels-events > .container-channel-events').each((i, el) => { - const name = $(el).find('.channel-title').text().trim() - const channelId = name.replace(/\s&\s/gi, ' & ') - - if (!name) return - - channels.push({ - lang, - site_id: `${sat.satellite}#${sat.lineup}#${channelId}`, - name - }) - }) - } - - return channels - } -} - -function parseImage($item) { - const src = $item('.event-logo img:not(.no-img)').attr('src') - - return src ? `https://sat.tv${src}` : null -} - -function parseTitle($item) { - return $item('.event-data-title').text() -} - -function parseDescription($item) { - return $item('.event-data-desc').text() -} - -function parseStart($item, date) { - let eventDataDate = $item('.event-data-date').text().trim() - let [, time] = eventDataDate.match(/(\d{2}:\d{2})/) || [null, null] - if (!time) return null - - return dayjs.utc(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm') -} - -function parseDuration($item) { - let eventDataInfo = $item('.event-data-info').text().trim() - let [, h, m] = eventDataInfo.match(/(\d{2})h(\d{2})/) || [null, 0, 0] - - return parseInt(h) * 60 + parseInt(m) -} - -function parseItems(content, channel) { - const [, , site_id] = channel.site_id.split('#') - const $ = cheerio.load(content) - const channelData = $('.main-container-channels-events > .container-channel-events') - .filter((index, el) => { - return $(el).find('.channel-title').text().trim() === site_id - }) - .first() - if (!channelData) return [] - - return $(channelData).find('.container-event').toArray() -} +const axios = require('axios') +const dayjs = require('dayjs') +const cheerio = require('cheerio') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(customParseFormat) + +const API_ENDPOINT = 'https://www.sat.tv/wp-content/themes/twentytwenty-child/ajax_chaines.php' + +module.exports = { + site: 'sat.tv', + days: 2, + url: API_ENDPOINT, + request: { + method: 'POST', + headers({ channel }) { + return { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + Cookie: `pll_language=${channel.lang}` + } + }, + data({ channel, date }) { + const [satSatellite, satLineup] = channel.site_id.split('#') + const params = new URLSearchParams() + params.append('dateFiltre', date.format('YYYY-MM-DD')) + params.append('hoursFiltre', '0') + params.append('satLineup', satLineup) + params.append('satSatellite', satSatellite) + params.append('userDateTime', date.valueOf()) + params.append('userTimezone', 'Europe/London') + + return params + }, + cache: { + ttl: 60 * 60 * 1000 // 1h + } + }, + parser: function ({ content, date, channel }) { + let programs = [] + const items = parseItems(content, channel) + items.forEach(item => { + let $item = cheerio.load(item) + let start = parseStart($item, date) + let duration = parseDuration($item) + let stop = start.add(duration, 'm') + + programs.push({ + title: parseTitle($item), + description: parseDescription($item), + image: parseImage($item), + start, + stop + }) + }) + + return programs + }, + async channels({ lang }) { + const satellites = [ + { satellite: 2, lineup: 55 }, + { satellite: 2, lineup: 58 }, + { satellite: 2, lineup: 53 }, + { satellite: 2, lineup: 57 }, + { satellite: 2, lineup: 54 }, + { satellite: 2, lineup: 56 }, + { satellite: 1, lineup: 48 }, + { satellite: 1, lineup: 44 }, + { satellite: 1, lineup: 42 }, + { satellite: 1, lineup: 39 }, + { satellite: 1, lineup: 37 }, + { satellite: 1, lineup: 38 }, + { satellite: 1, lineup: 68 }, + { satellite: 1, lineup: 47 }, + { satellite: 1, lineup: 41 }, + { satellite: 1, lineup: 49 }, + { satellite: 1, lineup: 46 }, + { satellite: 1, lineup: 35 }, + { satellite: 1, lineup: 43 }, + { satellite: 1, lineup: 45 }, + { satellite: 1, lineup: 50 }, + { satellite: 1, lineup: 71 }, + { satellite: 1, lineup: 40 }, + { satellite: 1, lineup: 72 }, + { satellite: 1, lineup: 33 }, + { satellite: 8, lineup: 62 }, + { satellite: 8, lineup: 63 }, + { satellite: 8, lineup: 64 }, + { satellite: 8, lineup: 65 }, + { satellite: 8, lineup: 66 }, + { satellite: 8, lineup: 67 } + ] + + let channels = [] + for (let sat of satellites) { + const params = new URLSearchParams() + params.append('dateFiltre', dayjs().format('YYYY-MM-DD')) + params.append('hoursFiltre', '0') + params.append('satLineup', sat.lineup) + params.append('satSatellite', sat.satellite) + params.append('userDateTime', dayjs().valueOf()) + params.append('userTimezone', 'Europe/London') + const data = await axios + .post(API_ENDPOINT, params, { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + Cookie: `pll_language=${lang}` + } + }) + .then(r => r.data) + .catch(console.log) + + const $ = cheerio.load(data) + $('.main-container-channels-events > .container-channel-events').each((i, el) => { + const name = $(el).find('.channel-title').text().trim() + const channelId = name.replace(/\s&\s/gi, ' & ') + + if (!name) return + + channels.push({ + lang, + site_id: `${sat.satellite}#${sat.lineup}#${channelId}`, + name + }) + }) + } + + return channels + } +} + +function parseImage($item) { + const src = $item('.event-logo img:not(.no-img)').attr('src') + + return src ? `https://sat.tv${src}` : null +} + +function parseTitle($item) { + return $item('.event-data-title').text() +} + +function parseDescription($item) { + return $item('.event-data-desc').text() +} + +function parseStart($item, date) { + let eventDataDate = $item('.event-data-date').text().trim() + let [, time] = eventDataDate.match(/(\d{2}:\d{2})/) || [null, null] + if (!time) return null + + return dayjs.utc(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm') +} + +function parseDuration($item) { + let eventDataInfo = $item('.event-data-info').text().trim() + let [, h, m] = eventDataInfo.match(/(\d{2})h(\d{2})/) || [null, 0, 0] + + return parseInt(h) * 60 + parseInt(m) +} + +function parseItems(content, channel) { + const [, , site_id] = channel.site_id.split('#') + const $ = cheerio.load(content) + const channelData = $('.main-container-channels-events > .container-channel-events') + .filter((index, el) => { + return $(el).find('.channel-title').text().trim() === site_id + }) + .first() + if (!channelData) return [] + + return $(channelData).find('.container-event').toArray() +} diff --git a/sites/sat.tv/sat.tv.test.js b/sites/sat.tv/sat.tv.test.js index e7e957807..7c8ef56c5 100644 --- a/sites/sat.tv/sat.tv.test.js +++ b/sites/sat.tv/sat.tv.test.js @@ -1,112 +1,112 @@ -const { parser, url, request } = require('./sat.tv.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-06-26', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '1#38#السعودية', - xmltv_id: 'AlSaudiya.sa', - lang: 'ar' -} - -it('can generate valid url', () => { - expect(url).toBe('https://www.sat.tv/wp-content/themes/twentytwenty-child/ajax_chaines.php') -}) - -it('can generate valid request method', () => { - expect(request.method).toBe('POST') -}) - -it('can generate valid request headers', () => { - expect(request.headers({ channel })).toMatchObject({ - 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', - Cookie: 'pll_language=ar' - }) -}) - -it('can generate valid request data', () => { - const data = request.data({ channel, date }) - expect(data.get('dateFiltre')).toBe('2023-06-26') - expect(data.get('hoursFiltre')).toBe('0') - expect(data.get('satLineup')).toBe('38') - expect(data.get('satSatellite')).toBe('1') - expect(data.get('userDateTime')).toBe('1687737600000') - expect(data.get('userTimezone')).toBe('Europe/London') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_ar.html')) - const results = parser({ content, date, channel }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(35) - expect(results[0]).toMatchObject({ - start: '2023-06-26T06:30:00.000Z', - stop: '2023-06-26T07:00:00.000Z', - title: 'تعظيم البلد الحرام', - description: `الناس, دين, ثقافة -يلقي صانع الفيلم الضوء على مشروع تعظيم البلد الحرام في مكة من العائلة الملكية في المملكة العربية السعودية، والذي يهدف لإبراز حرمته لدى المسلمين حول العالم.`, - image: null - }) - - expect(results[34]).toMatchObject({ - start: '2023-06-26T22:30:00.000Z', - stop: '2023-06-27T01:00:00.000Z', - title: 'الأخبار', - description: `نشرة -.يطرح أهم القضايا والأحداث على الساحة السعودية والعالمية`, - image: - 'https://sat.tv/wp-content/themes/twentytwenty-child/data_lineups/nilesat/images3/epg-3077892.jpg' - }) -}) - -it('can parse response in english', () => { - const channel = { - site_id: '1#38#Saudi HD', - xmltv_id: 'AlSaudiya.sa', - lang: 'en' - } - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_en.html')) - const results = parser({ content, date, channel }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(32) - expect(results[0]).toMatchObject({ - start: '2023-06-26T09:00:00.000Z', - stop: '2023-06-26T10:00:00.000Z', - title: 'News', - description: `Newscast -The most important issues and events on the Saudi and the world.`, - image: - 'https://sat.tv/wp-content/themes/twentytwenty-child/data_lineups/nilesat/images3/epg-3077892.jpg' - }) - - expect(results[31]).toMatchObject({ - start: '2023-06-26T23:15:00.000Z', - stop: '2023-06-27T00:00:00.000Z', - title: "Bride's Father", - description: `Romance, Drama, Family -2022 -Abdelhamid's family struggles to deal with the challenges of life that keep flowing one by one. they manage to stay strong-armed with their love and trust for each other. -Sayed Ragab, Sawsan Badr, Medhat Saleh, Nermine Al Feqy, Mohamed Adel, Khaled Kamal, Rania Farid, Hani Kamal, Hani Kamal`, - image: - 'https://sat.tv/wp-content/themes/twentytwenty-child/data_lineups/nilesat/images3/epg-3157177.jpg' - }) -}) - -it('can handle empty guide', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) - const result = parser({ content, date, channel }) - expect(result).toMatchObject([]) -}) +const { parser, url, request } = require('./sat.tv.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-06-26', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '1#38#السعودية', + xmltv_id: 'AlSaudiya.sa', + lang: 'ar' +} + +it('can generate valid url', () => { + expect(url).toBe('https://www.sat.tv/wp-content/themes/twentytwenty-child/ajax_chaines.php') +}) + +it('can generate valid request method', () => { + expect(request.method).toBe('POST') +}) + +it('can generate valid request headers', () => { + expect(request.headers({ channel })).toMatchObject({ + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + Cookie: 'pll_language=ar' + }) +}) + +it('can generate valid request data', () => { + const data = request.data({ channel, date }) + expect(data.get('dateFiltre')).toBe('2023-06-26') + expect(data.get('hoursFiltre')).toBe('0') + expect(data.get('satLineup')).toBe('38') + expect(data.get('satSatellite')).toBe('1') + expect(data.get('userDateTime')).toBe('1687737600000') + expect(data.get('userTimezone')).toBe('Europe/London') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_ar.html')) + const results = parser({ content, date, channel }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(35) + expect(results[0]).toMatchObject({ + start: '2023-06-26T06:30:00.000Z', + stop: '2023-06-26T07:00:00.000Z', + title: 'تعظيم البلد الحرام', + description: `الناس, دين, ثقافة +يلقي صانع الفيلم الضوء على مشروع تعظيم البلد الحرام في مكة من العائلة الملكية في المملكة العربية السعودية، والذي يهدف لإبراز حرمته لدى المسلمين حول العالم.`, + image: null + }) + + expect(results[34]).toMatchObject({ + start: '2023-06-26T22:30:00.000Z', + stop: '2023-06-27T01:00:00.000Z', + title: 'الأخبار', + description: `نشرة +.يطرح أهم القضايا والأحداث على الساحة السعودية والعالمية`, + image: + 'https://sat.tv/wp-content/themes/twentytwenty-child/data_lineups/nilesat/images3/epg-3077892.jpg' + }) +}) + +it('can parse response in english', () => { + const channel = { + site_id: '1#38#Saudi HD', + xmltv_id: 'AlSaudiya.sa', + lang: 'en' + } + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_en.html')) + const results = parser({ content, date, channel }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(32) + expect(results[0]).toMatchObject({ + start: '2023-06-26T09:00:00.000Z', + stop: '2023-06-26T10:00:00.000Z', + title: 'News', + description: `Newscast +The most important issues and events on the Saudi and the world.`, + image: + 'https://sat.tv/wp-content/themes/twentytwenty-child/data_lineups/nilesat/images3/epg-3077892.jpg' + }) + + expect(results[31]).toMatchObject({ + start: '2023-06-26T23:15:00.000Z', + stop: '2023-06-27T00:00:00.000Z', + title: "Bride's Father", + description: `Romance, Drama, Family +2022 +Abdelhamid's family struggles to deal with the challenges of life that keep flowing one by one. they manage to stay strong-armed with their love and trust for each other. +Sayed Ragab, Sawsan Badr, Medhat Saleh, Nermine Al Feqy, Mohamed Adel, Khaled Kamal, Rania Farid, Hani Kamal, Hani Kamal`, + image: + 'https://sat.tv/wp-content/themes/twentytwenty-child/data_lineups/nilesat/images3/epg-3157177.jpg' + }) +}) + +it('can handle empty guide', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) + const result = parser({ content, date, channel }) + expect(result).toMatchObject([]) +}) diff --git a/sites/shahid.mbc.net/shahid.mbc.net.config.js b/sites/shahid.mbc.net/shahid.mbc.net.config.js index a847c7696..f3ac90872 100644 --- a/sites/shahid.mbc.net/shahid.mbc.net.config.js +++ b/sites/shahid.mbc.net/shahid.mbc.net.config.js @@ -1,79 +1,79 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'shahid.mbc.net', - days: 2, - url({ channel, date }) { - return `https://api2.shahid.net/proxy/v2.1/shahid-epg-api/?csvChannelIds=${ - channel.site_id - }&from=${date.format('YYYY-MM-DD')}T00:00:00.000Z&to=${date.format( - 'YYYY-MM-DD' - )}T23:59:59.999Z&country=SA&language=${channel.lang}&Accept-Language=${channel.lang}` - }, - parser({ content, channel }) { - const programs = parseItems(content, channel).map(item => { - return { - title: item.title, - description: item.description, - session: item.seasonNumber, - episode: item.episodeNumber, - start: dayjs.tz(item.actualFrom, 'Asia/Riyadh').toISOString(), - stop: dayjs.tz(item.actualTo, 'Asia/Riyadh').toISOString() - } - }) - - return programs - }, - async channels({ lang = 'en' }) { - const axios = require('axios') - const items = [] - let page = 0 - while (true) { - const result = await axios - .get( - `https://api2.shahid.net/proxy/v2.1/product/filter?filter=%7B"pageNumber":${page},"pageSize":100,"productType":"LIVESTREAM","productSubType":"LIVE_CHANNEL"%7D&country=SA&language=${lang}&Accept-Language=${lang}` - ) - .then(response => response.data) - .catch(console.error) - if (result.productList) { - items.push(...result.productList.products) - if (result.productList.hasMore) { - page++ - continue - } - } - break - } - const channels = items.map(channel => { - return { - lang, - site_id: channel.id, - name: channel.title - } - }) - - return channels - } -} - -function parseItems(content, channel) { - const items = [] - content = content ? JSON.parse(content) : [] - if (content.items) { - content.items.forEach(schedules => { - if (schedules.channelId == channel.site_id) { - items.push(...schedules.items) - return true - } - }) - } - - return items -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'shahid.mbc.net', + days: 2, + url({ channel, date }) { + return `https://api2.shahid.net/proxy/v2.1/shahid-epg-api/?csvChannelIds=${ + channel.site_id + }&from=${date.format('YYYY-MM-DD')}T00:00:00.000Z&to=${date.format( + 'YYYY-MM-DD' + )}T23:59:59.999Z&country=SA&language=${channel.lang}&Accept-Language=${channel.lang}` + }, + parser({ content, channel }) { + const programs = parseItems(content, channel).map(item => { + return { + title: item.title, + description: item.description, + session: item.seasonNumber, + episode: item.episodeNumber, + start: dayjs.tz(item.actualFrom, 'Asia/Riyadh').toISOString(), + stop: dayjs.tz(item.actualTo, 'Asia/Riyadh').toISOString() + } + }) + + return programs + }, + async channels({ lang = 'en' }) { + const axios = require('axios') + const items = [] + let page = 0 + while (true) { + const result = await axios + .get( + `https://api2.shahid.net/proxy/v2.1/product/filter?filter=%7B"pageNumber":${page},"pageSize":100,"productType":"LIVESTREAM","productSubType":"LIVE_CHANNEL"%7D&country=SA&language=${lang}&Accept-Language=${lang}` + ) + .then(response => response.data) + .catch(console.error) + if (result.productList) { + items.push(...result.productList.products) + if (result.productList.hasMore) { + page++ + continue + } + } + break + } + const channels = items.map(channel => { + return { + lang, + site_id: channel.id, + name: channel.title + } + }) + + return channels + } +} + +function parseItems(content, channel) { + const items = [] + content = content ? JSON.parse(content) : [] + if (content.items) { + content.items.forEach(schedules => { + if (schedules.channelId == channel.site_id) { + items.push(...schedules.items) + return true + } + }) + } + + return items +} diff --git a/sites/siba.com.co/siba.com.co.config.js b/sites/siba.com.co/siba.com.co.config.js index 970815bee..d1ef37d20 100644 --- a/sites/siba.com.co/siba.com.co.config.js +++ b/sites/siba.com.co/siba.com.co.config.js @@ -1,56 +1,56 @@ -const dayjs = require('dayjs') - -module.exports = { - site: 'siba.com.co', - days: 2, - url: 'http://devportal.siba.com.co/index.php?action=grilla', - request: { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' - }, - data({ channel, date }) { - const params = new URLSearchParams() - params.append('servicio', '10') - params.append('ini', date.unix()) - params.append('end', date.add(1, 'd').unix()) - params.append('chn', channel.site_id) - - return params - } - }, - parser: function ({ content, channel }) { - let programs = [] - const items = parseItems(content, channel) - items.forEach(item => { - programs.push({ - title: item.nom, - start: parseStart(item).toJSON(), - stop: parseStop(item).toJSON() - }) - }) - - return programs - } -} - -function parseStart(item) { - return dayjs.unix(item.ini) -} - -function parseStop(item) { - return dayjs.unix(item.fin) -} - -function parseContent(content, channel) { - const data = JSON.parse(content) - if (!data || !Array.isArray(data.list)) return null - - return data.list.find(i => i.id === channel.site_id) -} - -function parseItems(content, channel) { - const data = parseContent(content, channel) - - return data ? data.prog : [] -} +const dayjs = require('dayjs') + +module.exports = { + site: 'siba.com.co', + days: 2, + url: 'http://devportal.siba.com.co/index.php?action=grilla', + request: { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' + }, + data({ channel, date }) { + const params = new URLSearchParams() + params.append('servicio', '10') + params.append('ini', date.unix()) + params.append('end', date.add(1, 'd').unix()) + params.append('chn', channel.site_id) + + return params + } + }, + parser: function ({ content, channel }) { + let programs = [] + const items = parseItems(content, channel) + items.forEach(item => { + programs.push({ + title: item.nom, + start: parseStart(item).toJSON(), + stop: parseStop(item).toJSON() + }) + }) + + return programs + } +} + +function parseStart(item) { + return dayjs.unix(item.ini) +} + +function parseStop(item) { + return dayjs.unix(item.fin) +} + +function parseContent(content, channel) { + const data = JSON.parse(content) + if (!data || !Array.isArray(data.list)) return null + + return data.list.find(i => i.id === channel.site_id) +} + +function parseItems(content, channel) { + const data = parseContent(content, channel) + + return data ? data.prog : [] +} diff --git a/sites/singtel.com/singtel.com.config.js b/sites/singtel.com/singtel.com.config.js index 07d3a33e0..26d6301c3 100644 --- a/sites/singtel.com/singtel.com.config.js +++ b/sites/singtel.com/singtel.com.config.js @@ -1,68 +1,68 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') - -dayjs.extend(utc) -dayjs.extend(timezone) - -module.exports = { - site: 'singtel.com', - days: 3, - request: { - cache: { - ttl: 60 * 60 * 1000 // 1 hour - } - }, - url({ date }) { - return `https://www.singtel.com/etc/singtel/public/tv/epg-parsed-data/${date.format( - 'DDMMYYYY' - )}.json` - }, - parser({ content, channel }) { - let programs = [] - const items = parseItems(content, channel) - items.forEach(item => { - const start = dayjs.tz(item.startDateTime, 'Asia/Singapore') - const stop = start.add(item.duration, 's') - programs.push({ - title: item.program.title, - category: item.program.subCategory, - description: item.program.description, - start, - stop - }) - }) - - return programs - }, - async channels() { - const axios = require('axios') - const cheerio = require('cheerio') - - const data = await axios - .get('https://www.singtel.com/personal/products-services/tv/tv-programme-guide') - .then(r => r.data) - .catch(console.log) - - const $ = cheerio.load(data) - let datamodel = $('ux-tv-channel-epg').attr('datamodel') - datamodel = JSON.parse(datamodel) - - return datamodel.tvChannelLists.map(item => { - return { - lang: 'en', - site_id: item.epgChannelId, - name: item.title.trim() - } - }) - } -} - -function parseItems(content, channel) { - try { - const data = JSON.parse(content) - return data && data[channel.site_id] ? data[channel.site_id] : [] - } catch { - return [] - } -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') + +dayjs.extend(utc) +dayjs.extend(timezone) + +module.exports = { + site: 'singtel.com', + days: 3, + request: { + cache: { + ttl: 60 * 60 * 1000 // 1 hour + } + }, + url({ date }) { + return `https://www.singtel.com/etc/singtel/public/tv/epg-parsed-data/${date.format( + 'DDMMYYYY' + )}.json` + }, + parser({ content, channel }) { + let programs = [] + const items = parseItems(content, channel) + items.forEach(item => { + const start = dayjs.tz(item.startDateTime, 'Asia/Singapore') + const stop = start.add(item.duration, 's') + programs.push({ + title: item.program.title, + category: item.program.subCategory, + description: item.program.description, + start, + stop + }) + }) + + return programs + }, + async channels() { + const axios = require('axios') + const cheerio = require('cheerio') + + const data = await axios + .get('https://www.singtel.com/personal/products-services/tv/tv-programme-guide') + .then(r => r.data) + .catch(console.log) + + const $ = cheerio.load(data) + let datamodel = $('ux-tv-channel-epg').attr('datamodel') + datamodel = JSON.parse(datamodel) + + return datamodel.tvChannelLists.map(item => { + return { + lang: 'en', + site_id: item.epgChannelId, + name: item.title.trim() + } + }) + } +} + +function parseItems(content, channel) { + try { + const data = JSON.parse(content) + return data && data[channel.site_id] ? data[channel.site_id] : [] + } catch { + return [] + } +} diff --git a/sites/singtel.com/singtel.com.test.js b/sites/singtel.com/singtel.com.test.js index 2d6b0bbd9..82689ea0c 100644 --- a/sites/singtel.com/singtel.com.test.js +++ b/sites/singtel.com/singtel.com.test.js @@ -1,59 +1,59 @@ -const { parser, url } = require('./singtel.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -const date = dayjs.utc('2023-01-29', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '5418', - xmltv_id: 'ParamountNetworkSingapore.sg' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://www.singtel.com/etc/singtel/public/tv/epg-parsed-data/29012023.json' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - let results = parser({ content, channel }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(23) - expect(results[0]).toMatchObject({ - start: '2023-01-28T16:00:00.000Z', - stop: '2023-01-28T17:30:00.000Z', - title: 'Hip Hop Family Christmas Wedding', - description: - 'Hip Hop\'s most famous family is back, and this time Christmas wedding bells are ringing! Jessica and Jayson are getting ready to say their "I do\'s".', - category: 'Specials' - }) - - expect(results[10]).toMatchObject({ - start: '2023-01-29T01:00:00.000Z', - stop: '2023-01-29T01:30:00.000Z', - title: 'The Daily Show', - description: - 'The Daily Show correspondents tackle the biggest stories in news, politics and pop culture.', - category: 'English Entertainment' - }) -}) - -it('can handle empty guide', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) - const results = parser({ content, channel }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./singtel.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.utc('2023-01-29', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '5418', + xmltv_id: 'ParamountNetworkSingapore.sg' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://www.singtel.com/etc/singtel/public/tv/epg-parsed-data/29012023.json' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + let results = parser({ content, channel }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(23) + expect(results[0]).toMatchObject({ + start: '2023-01-28T16:00:00.000Z', + stop: '2023-01-28T17:30:00.000Z', + title: 'Hip Hop Family Christmas Wedding', + description: + 'Hip Hop\'s most famous family is back, and this time Christmas wedding bells are ringing! Jessica and Jayson are getting ready to say their "I do\'s".', + category: 'Specials' + }) + + expect(results[10]).toMatchObject({ + start: '2023-01-29T01:00:00.000Z', + stop: '2023-01-29T01:30:00.000Z', + title: 'The Daily Show', + description: + 'The Daily Show correspondents tackle the biggest stories in news, politics and pop culture.', + category: 'English Entertainment' + }) +}) + +it('can handle empty guide', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) + const results = parser({ content, channel }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/sjonvarp.is/sjonvarp.is.config.js b/sites/sjonvarp.is/sjonvarp.is.config.js index 7fd6c33f7..d2f656b5d 100644 --- a/sites/sjonvarp.is/sjonvarp.is.config.js +++ b/sites/sjonvarp.is/sjonvarp.is.config.js @@ -1,90 +1,90 @@ -const dayjs = require('dayjs') -const cheerio = require('cheerio') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'sjonvarp.is', - days: 2, - url: function ({ channel, date }) { - return `http://www.sjonvarp.is/index.php?Tm=%3F&p=idag&c=${channel.site_id}&y=${date.format( - 'YYYY' - )}&m=${date.format('MM')}&d=${date.format('DD')}` - }, - parser: function ({ content, date }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - const prev = programs[programs.length - 1] - const $item = cheerio.load(item) - let start = parseStart($item, date) - if (prev) { - if (start.isBefore(prev.start)) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.add(30, 'm') - programs.push({ - title: parseTitle($item), - description: parseDescription($item), - start, - stop - }) - }) - - return programs - }, - async channels() { - const axios = require('axios') - const cheerio = require('cheerio') - - const data = await axios - .get('https://sjonvarp.is/') - .then(r => r.data) - .catch(console.log) - - let channels = [] - - const $ = cheerio.load(data) - $('.listing-row').each((i, el) => { - const site_id = $(el).attr('id') - const title = $(el).find('a.channel').first().attr('title') - const [, name] = title.match(/^Skoða dagskránna á (.*) í dag$/) - - channels.push({ - lang: 'is', - site_id, - name - }) - }) - - return channels - } -} - -function parseTitle($item) { - return $item('.day-listing-title').text() -} - -function parseDescription($item) { - return $item('.day-listing-description').text() -} - -function parseStart($item, date) { - const time = $item('.day-listing-time') - - return dayjs.utc(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm') -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $( - 'body > div.container.nano-container > div > ul > div.day-listing > div:not(.day-listing-channel)' - ).toArray() -} +const dayjs = require('dayjs') +const cheerio = require('cheerio') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'sjonvarp.is', + days: 2, + url: function ({ channel, date }) { + return `http://www.sjonvarp.is/index.php?Tm=%3F&p=idag&c=${channel.site_id}&y=${date.format( + 'YYYY' + )}&m=${date.format('MM')}&d=${date.format('DD')}` + }, + parser: function ({ content, date }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + const prev = programs[programs.length - 1] + const $item = cheerio.load(item) + let start = parseStart($item, date) + if (prev) { + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.add(30, 'm') + programs.push({ + title: parseTitle($item), + description: parseDescription($item), + start, + stop + }) + }) + + return programs + }, + async channels() { + const axios = require('axios') + const cheerio = require('cheerio') + + const data = await axios + .get('https://sjonvarp.is/') + .then(r => r.data) + .catch(console.log) + + let channels = [] + + const $ = cheerio.load(data) + $('.listing-row').each((i, el) => { + const site_id = $(el).attr('id') + const title = $(el).find('a.channel').first().attr('title') + const [, name] = title.match(/^Skoða dagskránna á (.*) í dag$/) + + channels.push({ + lang: 'is', + site_id, + name + }) + }) + + return channels + } +} + +function parseTitle($item) { + return $item('.day-listing-title').text() +} + +function parseDescription($item) { + return $item('.day-listing-description').text() +} + +function parseStart($item, date) { + const time = $item('.day-listing-time') + + return dayjs.utc(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm') +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $( + 'body > div.container.nano-container > div > ul > div.day-listing > div:not(.day-listing-channel)' + ).toArray() +} diff --git a/sites/sjonvarp.is/sjonvarp.is.test.js b/sites/sjonvarp.is/sjonvarp.is.test.js index 609b210cf..3b96a52d3 100644 --- a/sites/sjonvarp.is/sjonvarp.is.test.js +++ b/sites/sjonvarp.is/sjonvarp.is.test.js @@ -1,48 +1,48 @@ -const { parser, url } = require('./sjonvarp.is.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-08-28', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'RUV', - xmltv_id: 'RUV.is' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'http://www.sjonvarp.is/index.php?Tm=%3F&p=idag&c=RUV&y=2022&m=08&d=28' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - const results = parser({ content, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2022-08-28T07:15:00.000Z', - stop: '2022-08-28T07:16:00.000Z', - title: 'KrakkaRÚV' - }) - - expect(results[1]).toMatchObject({ - start: '2022-08-28T07:16:00.000Z', - stop: '2022-08-28T07:21:00.000Z', - title: 'Tölukubbar', - description: 'Lærið um tölustafina með Tölukubbunum! e.' - }) -}) - -it('can handle empty guide', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) - const result = parser({ content, date }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./sjonvarp.is.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-08-28', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'RUV', + xmltv_id: 'RUV.is' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'http://www.sjonvarp.is/index.php?Tm=%3F&p=idag&c=RUV&y=2022&m=08&d=28' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + const results = parser({ content, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2022-08-28T07:15:00.000Z', + stop: '2022-08-28T07:16:00.000Z', + title: 'KrakkaRÚV' + }) + + expect(results[1]).toMatchObject({ + start: '2022-08-28T07:16:00.000Z', + stop: '2022-08-28T07:21:00.000Z', + title: 'Tölukubbar', + description: 'Lærið um tölustafina með Tölukubbunum! e.' + }) +}) + +it('can handle empty guide', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) + const result = parser({ content, date }) + expect(result).toMatchObject([]) +}) diff --git a/sites/sky.co.nz/sky.co.nz.config.js b/sites/sky.co.nz/sky.co.nz.config.js index 117fbd9ff..c095e5f3e 100644 --- a/sites/sky.co.nz/sky.co.nz.config.js +++ b/sites/sky.co.nz/sky.co.nz.config.js @@ -1,55 +1,55 @@ -const axios = require('axios') -const dayjs = require('dayjs') - -module.exports = { - site: 'sky.co.nz', - days: 2, - url({ date, channel }) { - return `https://web-epg.sky.co.nz/prod/epgs/v1?channelNumber=${ - channel.site_id - }&start=${date.valueOf()}&end=${date.add(1, 'day').valueOf()}&limit=20000` - }, - parser({ content }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - programs.push({ - title: item.title, - description: item.synopsis, - category: item.genres, - rating: parseRating(item), - start: dayjs(parseInt(item.start)), - stop: dayjs(parseInt(item.end)) - }) - }) - - return programs - }, - async channels() { - const data = await axios - .get('https://skywebconfig.msl-prod.skycloud.co.nz/sky/json/channels.prod.json') - .then(r => r.data) - .catch(console.log) - - return data.channels.map(item => { - return { - lang: 'en', - site_id: parseInt(item.number).toString(), - name: item.name - } - }) - } -} - -function parseItems(content) { - const data = JSON.parse(content) - return data && data.events && Array.isArray(data.events) ? data.events : [] -} - -function parseRating(item) { - if (!item.rating) return null - return { - system: 'OFLC', - value: item.rating - } -} +const axios = require('axios') +const dayjs = require('dayjs') + +module.exports = { + site: 'sky.co.nz', + days: 2, + url({ date, channel }) { + return `https://web-epg.sky.co.nz/prod/epgs/v1?channelNumber=${ + channel.site_id + }&start=${date.valueOf()}&end=${date.add(1, 'day').valueOf()}&limit=20000` + }, + parser({ content }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + programs.push({ + title: item.title, + description: item.synopsis, + category: item.genres, + rating: parseRating(item), + start: dayjs(parseInt(item.start)), + stop: dayjs(parseInt(item.end)) + }) + }) + + return programs + }, + async channels() { + const data = await axios + .get('https://skywebconfig.msl-prod.skycloud.co.nz/sky/json/channels.prod.json') + .then(r => r.data) + .catch(console.log) + + return data.channels.map(item => { + return { + lang: 'en', + site_id: parseInt(item.number).toString(), + name: item.name + } + }) + } +} + +function parseItems(content) { + const data = JSON.parse(content) + return data && data.events && Array.isArray(data.events) ? data.events : [] +} + +function parseRating(item) { + if (!item.rating) return null + return { + system: 'OFLC', + value: item.rating + } +} diff --git a/sites/sky.co.nz/sky.co.nz.test.js b/sites/sky.co.nz/sky.co.nz.test.js index 25b35dea0..4840ee627 100644 --- a/sites/sky.co.nz/sky.co.nz.test.js +++ b/sites/sky.co.nz/sky.co.nz.test.js @@ -1,60 +1,60 @@ -const { parser, url } = require('./sky.co.nz.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -dayjs.extend(utc) - -const date = dayjs.utc('2023-01-21', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '36', - xmltv_id: 'SkyMoviesFamily.nz' -} -it('can generate valid url', () => { - expect(url({ date, channel })).toBe( - 'https://web-epg.sky.co.nz/prod/epgs/v1?channelNumber=36&start=1674259200000&end=1674345600000&limit=20000' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const result = parser({ content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result[0]).toMatchObject({ - title: 'Sing 2', - description: - "Animated: Buster Moon and his friends must persuade the world's most reclusive rock star to help launch their most dazzling extravaganza yet. Voices Of: Matthew McConaughey, Reese Witherspoon (2021)", - category: ['Animated'], - rating: { system: 'OFLC', value: 'PG' }, - start: '2023-01-20T23:41:00.000Z', - stop: '2023-01-21T01:28:00.000Z' - }) - - expect(result[5]).toMatchObject({ - title: 'Harry Potter and the Goblet of Fire', - description: - 'Adventure: Harry is selected to represent Hogwarts at a legendary and dangerous wizardry competition between three schools of magic. Stars: Daniel Radcliffe, Rupert Grint (2005)', - category: ['Action/Adventure'], - rating: { system: 'OFLC', value: 'M-V' }, - start: '2023-01-21T07:42:00.000Z', - stop: '2023-01-21T10:13:00.000Z' - }) -}) - -it('can handle empty guide', () => { - const result = parser( - { - content: `{ - "code": "DATE_FORMAT_ERROR", - "description": "DateFormat error", - "message": "Unparseable date: x" - }` - }, - channel - ) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./sky.co.nz.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +dayjs.extend(utc) + +const date = dayjs.utc('2023-01-21', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '36', + xmltv_id: 'SkyMoviesFamily.nz' +} +it('can generate valid url', () => { + expect(url({ date, channel })).toBe( + 'https://web-epg.sky.co.nz/prod/epgs/v1?channelNumber=36&start=1674259200000&end=1674345600000&limit=20000' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const result = parser({ content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result[0]).toMatchObject({ + title: 'Sing 2', + description: + "Animated: Buster Moon and his friends must persuade the world's most reclusive rock star to help launch their most dazzling extravaganza yet. Voices Of: Matthew McConaughey, Reese Witherspoon (2021)", + category: ['Animated'], + rating: { system: 'OFLC', value: 'PG' }, + start: '2023-01-20T23:41:00.000Z', + stop: '2023-01-21T01:28:00.000Z' + }) + + expect(result[5]).toMatchObject({ + title: 'Harry Potter and the Goblet of Fire', + description: + 'Adventure: Harry is selected to represent Hogwarts at a legendary and dangerous wizardry competition between three schools of magic. Stars: Daniel Radcliffe, Rupert Grint (2005)', + category: ['Action/Adventure'], + rating: { system: 'OFLC', value: 'M-V' }, + start: '2023-01-21T07:42:00.000Z', + stop: '2023-01-21T10:13:00.000Z' + }) +}) + +it('can handle empty guide', () => { + const result = parser( + { + content: `{ + "code": "DATE_FORMAT_ERROR", + "description": "DateFormat error", + "message": "Unparseable date: x" + }` + }, + channel + ) + expect(result).toMatchObject([]) +}) diff --git a/sites/sky.com/sky.com.test.js b/sites/sky.com/sky.com.test.js index e58a818d8..141a5f791 100644 --- a/sites/sky.com/sky.com.test.js +++ b/sites/sky.com/sky.com.test.js @@ -1,61 +1,61 @@ -const { parser, url } = require('./sky.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2024-12-14', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '4086', - xmltv_id: 'SkyHistoryHD.uk' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe('https://awk.epgsky.com/hawk/linear/schedule/20241214/4086') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.join(__dirname, '__data__', 'content.json')) - const result = parser({ content, channel, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result.length).toBe(31) - expect(result[0]).toMatchObject({ - start: '2024-12-14T00:00:00.000Z', - stop: '2024-12-14T00:30:00.000Z', - title: 'Storage Wars', - description: - 'A Sale Of Two Cities: Emily brings her mother along with her to Walnut, and Darrell wastes no time finding an advantage. Ivy and Ivy jr clean up with their locker. (S12, ep 4)', - season: 12, - episode: 4, - icon: 'https://images.metadata.sky.com/pd-image/b9572a38-8db7-471e-a2d7-462e1dd26af2/16-9/640', - image: 'https://images.metadata.sky.com/pd-image/b9572a38-8db7-471e-a2d7-462e1dd26af2/16-9/640' - }) - expect(result[2]).toMatchObject({ - start: '2024-12-14T01:00:00.000Z', - stop: '2024-12-14T01:30:00.000Z', - title: 'Storage Wars', - description: - 'Not All That Glitters Is Gourd: Back in the city of Orange, the Vegas Ladies arrive in vintage style - though not everyone agrees. (S12, ep 6)', - season: 12, - episode: 6, - icon: 'https://images.metadata.sky.com/pd-image/e9521ccc-bdcc-4075-9c2e-bc835247148b/16-9/640', - image: 'https://images.metadata.sky.com/pd-image/e9521ccc-bdcc-4075-9c2e-bc835247148b/16-9/640' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: '' - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./sky.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2024-12-14', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '4086', + xmltv_id: 'SkyHistoryHD.uk' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe('https://awk.epgsky.com/hawk/linear/schedule/20241214/4086') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.join(__dirname, '__data__', 'content.json')) + const result = parser({ content, channel, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result.length).toBe(31) + expect(result[0]).toMatchObject({ + start: '2024-12-14T00:00:00.000Z', + stop: '2024-12-14T00:30:00.000Z', + title: 'Storage Wars', + description: + 'A Sale Of Two Cities: Emily brings her mother along with her to Walnut, and Darrell wastes no time finding an advantage. Ivy and Ivy jr clean up with their locker. (S12, ep 4)', + season: 12, + episode: 4, + icon: 'https://images.metadata.sky.com/pd-image/b9572a38-8db7-471e-a2d7-462e1dd26af2/16-9/640', + image: 'https://images.metadata.sky.com/pd-image/b9572a38-8db7-471e-a2d7-462e1dd26af2/16-9/640' + }) + expect(result[2]).toMatchObject({ + start: '2024-12-14T01:00:00.000Z', + stop: '2024-12-14T01:30:00.000Z', + title: 'Storage Wars', + description: + 'Not All That Glitters Is Gourd: Back in the city of Orange, the Vegas Ladies arrive in vintage style - though not everyone agrees. (S12, ep 6)', + season: 12, + episode: 6, + icon: 'https://images.metadata.sky.com/pd-image/e9521ccc-bdcc-4075-9c2e-bc835247148b/16-9/640', + image: 'https://images.metadata.sky.com/pd-image/e9521ccc-bdcc-4075-9c2e-bc835247148b/16-9/640' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: '' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/sky.de/sky.de.config.js b/sites/sky.de/sky.de.config.js index e91451bfa..19a245801 100644 --- a/sites/sky.de/sky.de.config.js +++ b/sites/sky.de/sky.de.config.js @@ -1,78 +1,78 @@ -const dayjs = require('dayjs') - -module.exports = { - site: 'sky.de', - days: 2, - url: 'https://www.sky.de/sgtvg/service/getBroadcastsForGrid', - request: { - method: 'POST', - headers: { - 'accept-language': 'en-GB', - 'accept-encoding': 'gzip, deflate, br', - accept: 'application/json' - }, - data: function ({ channel, date }) { - return { - cil: [channel.site_id], - d: date.valueOf() - } - } - }, - parser: function ({ content, channel }) { - const programs = [] - const items = parseItems(content, channel) - items.forEach(item => { - programs.push({ - title: item.et, - description: item.epit, - category: item.ec, - start: dayjs(item.bsdt), - stop: dayjs(item.bedt), - season: item.sn, - episode: item.en, - image: item.pu ? `http://sky.de${item.pu}` : null - }) - }) - - return programs - }, - async channels() { - const axios = require('axios') - const data = await axios - .post( - 'https://www.sky.de/sgtvg/service/getChannelList', - { dom: 'de', s: 0, feed: 1 }, - { - headers: { - 'Content-Type': 'application/json', - Referer: 'https://www.sky.de/tvguide-7599', - 'X-Requested-With': 'XMLHttpRequest' - } - } - ) - .then(r => r.data) - .catch(console.log) - - let channels = [] - data.cl.forEach(item => { - channels.push({ - lang: 'de', - name: item.cn, - site_id: item.ci - }) - }) - - return channels - } -} - -function parseContent(content, channel) { - const json = JSON.parse(content) - if (!Array.isArray(json.cl)) return null - return json.cl.find(i => i.ci == channel.site_id) -} - -function parseItems(content, channel) { - const data = parseContent(content, channel) - return data && Array.isArray(data.el) ? data.el : [] -} +const dayjs = require('dayjs') + +module.exports = { + site: 'sky.de', + days: 2, + url: 'https://www.sky.de/sgtvg/service/getBroadcastsForGrid', + request: { + method: 'POST', + headers: { + 'accept-language': 'en-GB', + 'accept-encoding': 'gzip, deflate, br', + accept: 'application/json' + }, + data: function ({ channel, date }) { + return { + cil: [channel.site_id], + d: date.valueOf() + } + } + }, + parser: function ({ content, channel }) { + const programs = [] + const items = parseItems(content, channel) + items.forEach(item => { + programs.push({ + title: item.et, + description: item.epit, + category: item.ec, + start: dayjs(item.bsdt), + stop: dayjs(item.bedt), + season: item.sn, + episode: item.en, + image: item.pu ? `http://sky.de${item.pu}` : null + }) + }) + + return programs + }, + async channels() { + const axios = require('axios') + const data = await axios + .post( + 'https://www.sky.de/sgtvg/service/getChannelList', + { dom: 'de', s: 0, feed: 1 }, + { + headers: { + 'Content-Type': 'application/json', + Referer: 'https://www.sky.de/tvguide-7599', + 'X-Requested-With': 'XMLHttpRequest' + } + } + ) + .then(r => r.data) + .catch(console.log) + + let channels = [] + data.cl.forEach(item => { + channels.push({ + lang: 'de', + name: item.cn, + site_id: item.ci + }) + }) + + return channels + } +} + +function parseContent(content, channel) { + const json = JSON.parse(content) + if (!Array.isArray(json.cl)) return null + return json.cl.find(i => i.ci == channel.site_id) +} + +function parseItems(content, channel) { + const data = parseContent(content, channel) + return data && Array.isArray(data.el) ? data.el : [] +} diff --git a/sites/skylife.co.kr/skylife.co.kr.config.js b/sites/skylife.co.kr/skylife.co.kr.config.js index 617be99aa..0dc4d138b 100644 --- a/sites/skylife.co.kr/skylife.co.kr.config.js +++ b/sites/skylife.co.kr/skylife.co.kr.config.js @@ -1,81 +1,81 @@ -const dayjs = require('dayjs') -const axios = require('axios') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'skylife.co.kr', - days: 2, - request: { - cache: { - ttl: 60 * 60 * 1000 // 1 hour - } - }, - url({ date }) { - return `https://www.skylife.co.kr/api/api/public/tv/schedule/${date.format('YYYYMMDD')}` - }, - parser: function ({ content, channel }) { - let programs = [] - const items = parseItems(content, channel) - items.forEach(item => { - programs.push({ - title: item.name, - description: item.summary, - category: item.mainCategory, - actors: parseCast(item.cast), - start: parseTime(item.startTime), - stop: parseTime(item.endTime) - }) - }) - - return programs - }, - async channels() { - let channels = [] - - const url = `https://www.skylife.co.kr/api/api/public/tv/schedule/${dayjs().format('YYYYMMDD')}` - const data = await axios - .get(url) - .then(r => r.data) - .catch(console.log) - - for (let category of data) { - for (let channel of category.channels) { - channels.push({ - name: channel.name, - site_id: `${category.code}#${channel.id}`, - lang: 'ko' - }) - } - } - - return channels - } -} - -function parseCast(cast) { - if (!cast) return [] - - return cast.split(',') -} - -function parseTime(time) { - return dayjs.tz(time, 'YYYYMMDDHHmmss', 'Asia/Seoul') -} - -function parseItems(content, channel) { - const [categoryCode, channelId] = channel.site_id.split('#') - const data = JSON.parse(content) - if (!Array.isArray(data)) return [] - const category = data.find(_category => _category.code === categoryCode) - if (!category || !Array.isArray(category.channels)) return [] - const channelData = category.channels.find(_channel => _channel.id === channelId) - if (!channelData || !Array.isArray(channelData.programs)) return [] - - return channelData.programs -} +const dayjs = require('dayjs') +const axios = require('axios') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'skylife.co.kr', + days: 2, + request: { + cache: { + ttl: 60 * 60 * 1000 // 1 hour + } + }, + url({ date }) { + return `https://www.skylife.co.kr/api/api/public/tv/schedule/${date.format('YYYYMMDD')}` + }, + parser: function ({ content, channel }) { + let programs = [] + const items = parseItems(content, channel) + items.forEach(item => { + programs.push({ + title: item.name, + description: item.summary, + category: item.mainCategory, + actors: parseCast(item.cast), + start: parseTime(item.startTime), + stop: parseTime(item.endTime) + }) + }) + + return programs + }, + async channels() { + let channels = [] + + const url = `https://www.skylife.co.kr/api/api/public/tv/schedule/${dayjs().format('YYYYMMDD')}` + const data = await axios + .get(url) + .then(r => r.data) + .catch(console.log) + + for (let category of data) { + for (let channel of category.channels) { + channels.push({ + name: channel.name, + site_id: `${category.code}#${channel.id}`, + lang: 'ko' + }) + } + } + + return channels + } +} + +function parseCast(cast) { + if (!cast) return [] + + return cast.split(',') +} + +function parseTime(time) { + return dayjs.tz(time, 'YYYYMMDDHHmmss', 'Asia/Seoul') +} + +function parseItems(content, channel) { + const [categoryCode, channelId] = channel.site_id.split('#') + const data = JSON.parse(content) + if (!Array.isArray(data)) return [] + const category = data.find(_category => _category.code === categoryCode) + if (!category || !Array.isArray(category.channels)) return [] + const channelData = category.channels.find(_channel => _channel.id === channelId) + if (!channelData || !Array.isArray(channelData.programs)) return [] + + return channelData.programs +} diff --git a/sites/skylife.co.kr/skylife.co.kr.test.js b/sites/skylife.co.kr/skylife.co.kr.test.js index c35e85d46..0ba7d4a96 100644 --- a/sites/skylife.co.kr/skylife.co.kr.test.js +++ b/sites/skylife.co.kr/skylife.co.kr.test.js @@ -1,42 +1,42 @@ -const { parser, url } = require('./skylife.co.kr.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2024-06-26', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '4003#798', - xmltv_id: 'EBS.kr' -} - -it('can generate valid url', () => { - expect(url({ date })).toBe('https://www.skylife.co.kr/api/api/public/tv/schedule/20240626') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - let results = parser({ content, channel }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[20]).toMatchObject({ - start: '2024-06-26T00:40:00.000Z', // 20240626094000 - stop: '2024-06-26T01:30:00.000Z', // 20240626103000 - title: '세상에 나쁜 개는 없다', - description: '문제 있는 반려견들의 행동을 알아 보고 원인을 찾아나가는 프로그램', - category: '교양/정보', - actors: ['박영진', '강형욱'] - }) -}) - -it('can handle empty guide', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - const result = parser({ content, channel }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./skylife.co.kr.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2024-06-26', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '4003#798', + xmltv_id: 'EBS.kr' +} + +it('can generate valid url', () => { + expect(url({ date })).toBe('https://www.skylife.co.kr/api/api/public/tv/schedule/20240626') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + let results = parser({ content, channel }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[20]).toMatchObject({ + start: '2024-06-26T00:40:00.000Z', // 20240626094000 + stop: '2024-06-26T01:30:00.000Z', // 20240626103000 + title: '세상에 나쁜 개는 없다', + description: '문제 있는 반려견들의 행동을 알아 보고 원인을 찾아나가는 프로그램', + category: '교양/정보', + actors: ['박영진', '강형욱'] + }) +}) + +it('can handle empty guide', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + const result = parser({ content, channel }) + expect(result).toMatchObject([]) +}) diff --git a/sites/skyperfectv.co.jp/skyperfectv.co.jp.config.js b/sites/skyperfectv.co.jp/skyperfectv.co.jp.config.js index 1963124d0..9b2508cd0 100644 --- a/sites/skyperfectv.co.jp/skyperfectv.co.jp.config.js +++ b/sites/skyperfectv.co.jp/skyperfectv.co.jp.config.js @@ -1,121 +1,121 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const cheerio = require('cheerio') -const duration = require('dayjs/plugin/duration') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) -dayjs.extend(duration) - -const exported = { - site: 'skyperfectv.co.jp', - days: 1, - lang: 'ja', - url: function ({ date, channel }) { - let [type, ...code] = channel.site_id.split('_') - code = code.join('_') - return `https://www.skyperfectv.co.jp/program/schedule/${type}/channel:${code}/date:${date.format( - 'YYMMDD' - )}` - }, - logo: function ({ channel }) { - return `https://www.skyperfectv.co.jp/library/common/img/channel/icon/basic/m_${channel.site_id.toLowerCase()}.gif` - }, - // Specific function that permits to gather NSFW channels (needs confirmation) - async fetchSchedule({ date, channel }) { - const url = exported.url({ date, channel }) - const response = await axios.get(url, { - headers: { - Cookie: 'adult_auth=true' - } - }) - return response.data - }, - parser({ content, date }) { - const $ = cheerio.load(content) - const programs = [] - - const sections = [ - { id: 'js-am', addition: 0 }, - { id: 'js-pm', addition: 0 }, - { id: 'js-md', addition: 1 } - ] - - sections.forEach(({ id, addition }) => { - $(`#${id} > td`).each((index, element) => { - // `td` is a column for a day - // the next `td` will be the next day - const today = date.add(index + addition, 'd').tz('Asia/Tokyo') - - const parseTime = timeString => { - // timeString is in the format "HH:mm" - // replace `today` with the time from timeString - const [hour, minute] = timeString.split(':').map(Number) - return today.hour(hour).minute(minute) - } - - const $element = $(element) // Wrap element with Cheerio - $element.find('.p-program__item').each((itemIndex, itemElement) => { - const $itemElement = $(itemElement) // Wrap itemElement with Cheerio - const [start, stop] = $itemElement - .find('.p-program__range') - .first() - .text() - .split('〜') - .map(parseTime) - const title = $itemElement.find('.p-program__name').first().text() - const image = $itemElement.find('.js-program_thumbnail').first().attr('data-lazysrc') - programs.push({ - title, - start, - stop, - image - }) - }) - }) - }) - - return programs - }, - async channels() { - const pageParser = (content, type) => { - // type: "basic" | "premium" - // Returns an array of channel objects - - const $ = cheerio.load(content) - const channels = [] - - $('.p-channel').each((index, element) => { - const site_id = `${type}_${$(element).find('.p-channel__id').text()}` - const name = $(element).find('.p-channel__name').text() - channels.push({ site_id, name, lang: 'ja' }) - }) - - return channels - } - - const getChannels = async type => { - const response = await axios.get(`https://www.skyperfectv.co.jp/program/schedule/${type}/`, { - headers: { - Cookie: 'adult_auth=true;' - } - }) - return pageParser(response.data, type) - } - - const fetchAllChannels = async () => { - const basicChannels = await getChannels('basic') - const premiumChannels = await getChannels('premium') - const results = [...basicChannels, ...premiumChannels] - return results - } - - return await fetchAllChannels() - } -} - -module.exports = exported +const axios = require('axios') +const dayjs = require('dayjs') +const cheerio = require('cheerio') +const duration = require('dayjs/plugin/duration') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) +dayjs.extend(duration) + +const exported = { + site: 'skyperfectv.co.jp', + days: 1, + lang: 'ja', + url: function ({ date, channel }) { + let [type, ...code] = channel.site_id.split('_') + code = code.join('_') + return `https://www.skyperfectv.co.jp/program/schedule/${type}/channel:${code}/date:${date.format( + 'YYMMDD' + )}` + }, + logo: function ({ channel }) { + return `https://www.skyperfectv.co.jp/library/common/img/channel/icon/basic/m_${channel.site_id.toLowerCase()}.gif` + }, + // Specific function that permits to gather NSFW channels (needs confirmation) + async fetchSchedule({ date, channel }) { + const url = exported.url({ date, channel }) + const response = await axios.get(url, { + headers: { + Cookie: 'adult_auth=true' + } + }) + return response.data + }, + parser({ content, date }) { + const $ = cheerio.load(content) + const programs = [] + + const sections = [ + { id: 'js-am', addition: 0 }, + { id: 'js-pm', addition: 0 }, + { id: 'js-md', addition: 1 } + ] + + sections.forEach(({ id, addition }) => { + $(`#${id} > td`).each((index, element) => { + // `td` is a column for a day + // the next `td` will be the next day + const today = date.add(index + addition, 'd').tz('Asia/Tokyo') + + const parseTime = timeString => { + // timeString is in the format "HH:mm" + // replace `today` with the time from timeString + const [hour, minute] = timeString.split(':').map(Number) + return today.hour(hour).minute(minute) + } + + const $element = $(element) // Wrap element with Cheerio + $element.find('.p-program__item').each((itemIndex, itemElement) => { + const $itemElement = $(itemElement) // Wrap itemElement with Cheerio + const [start, stop] = $itemElement + .find('.p-program__range') + .first() + .text() + .split('〜') + .map(parseTime) + const title = $itemElement.find('.p-program__name').first().text() + const image = $itemElement.find('.js-program_thumbnail').first().attr('data-lazysrc') + programs.push({ + title, + start, + stop, + image + }) + }) + }) + }) + + return programs + }, + async channels() { + const pageParser = (content, type) => { + // type: "basic" | "premium" + // Returns an array of channel objects + + const $ = cheerio.load(content) + const channels = [] + + $('.p-channel').each((index, element) => { + const site_id = `${type}_${$(element).find('.p-channel__id').text()}` + const name = $(element).find('.p-channel__name').text() + channels.push({ site_id, name, lang: 'ja' }) + }) + + return channels + } + + const getChannels = async type => { + const response = await axios.get(`https://www.skyperfectv.co.jp/program/schedule/${type}/`, { + headers: { + Cookie: 'adult_auth=true;' + } + }) + return pageParser(response.data, type) + } + + const fetchAllChannels = async () => { + const basicChannels = await getChannels('basic') + const premiumChannels = await getChannels('premium') + const results = [...basicChannels, ...premiumChannels] + return results + } + + return await fetchAllChannels() + } +} + +module.exports = exported diff --git a/sites/skyperfectv.co.jp/skyperfectv.co.jp.test.js b/sites/skyperfectv.co.jp/skyperfectv.co.jp.test.js index c70ae27c8..f96a9b9b0 100644 --- a/sites/skyperfectv.co.jp/skyperfectv.co.jp.test.js +++ b/sites/skyperfectv.co.jp/skyperfectv.co.jp.test.js @@ -1,53 +1,53 @@ -const { parser, url } = require('./skyperfectv.co.jp.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2024-08-01', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'basic_BS193', - name: 'WOWOWシネマ', - xmltv_id: 'WOWOWCinema.jp' -} - -const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - -it('can generate valid url', () => { - const result = url({ date, channel }) - expect(result).toBe( - 'https://www.skyperfectv.co.jp/program/schedule/basic/channel:BS193/date:240801' - ) -}) - -it('can parse response', async () => { - const result = (await parser({ date, channel, content })).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result.filter(p => p.title == 'ヴァルキリードライヴマーメイド #06')).toMatchObject([ - { - start: '2024-07-31T19:00:00.000Z', // UTC time - stop: '2024-07-31T19:30:00.000Z', // UTC - title: 'ヴァルキリードライヴマーメイド #06', - image: - 'https://pm-img-ap.skyperfectv.co.jp/uploads/thumbnail/image/11301805/S_BC929697780313_be7975d4e26a4cad9b89fc6c94807e38_20240613144158569.jpg' - } - ]) -}) - -const empty = fs.readFileSync(path.resolve(__dirname, '__data__/empty.html')) - -it('can handle empty guide', async () => { - const result = parser({ - date, - channel, - content: empty - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./skyperfectv.co.jp.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2024-08-01', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'basic_BS193', + name: 'WOWOWシネマ', + xmltv_id: 'WOWOWCinema.jp' +} + +const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + +it('can generate valid url', () => { + const result = url({ date, channel }) + expect(result).toBe( + 'https://www.skyperfectv.co.jp/program/schedule/basic/channel:BS193/date:240801' + ) +}) + +it('can parse response', async () => { + const result = (await parser({ date, channel, content })).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result.filter(p => p.title == 'ヴァルキリードライヴマーメイド #06')).toMatchObject([ + { + start: '2024-07-31T19:00:00.000Z', // UTC time + stop: '2024-07-31T19:30:00.000Z', // UTC + title: 'ヴァルキリードライヴマーメイド #06', + image: + 'https://pm-img-ap.skyperfectv.co.jp/uploads/thumbnail/image/11301805/S_BC929697780313_be7975d4e26a4cad9b89fc6c94807e38_20240613144158569.jpg' + } + ]) +}) + +const empty = fs.readFileSync(path.resolve(__dirname, '__data__/empty.html')) + +it('can handle empty guide', async () => { + const result = parser({ + date, + channel, + content: empty + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/snrt.ma/snrt.ma.config.js b/sites/snrt.ma/snrt.ma.config.js index 84d5ede0a..4448c7f3a 100644 --- a/sites/snrt.ma/snrt.ma.config.js +++ b/sites/snrt.ma/snrt.ma.config.js @@ -1,98 +1,98 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -const tz = 'Africa/Casablanca' - -module.exports = { - site: 'snrt.ma', - days: 2, - url({ channel }) { - return `https://www.snrt.ma/ar/node/${channel.site_id}` - }, - request: { - cache: { - ttl: 24 * 60 * 60 * 1000 // 1 day - } - }, - parser({ content, date }) { - const [$, items] = parseItems(content) - const programs = items.map(item => { - const $item = $(item) - const start = parseStart($item) - return { - title: parseTitle($item), - description: parseDescription($item), - category: parseCategory($item), - start - } - }).filter(item => item.start).sort((a, b) => a.start - b.start) - // fill start-stop - for (let i = 0; i < programs.length; i++) { - if (i < programs.length - 1) { - programs[i].stop = programs[i + 1].start - } else { - programs[i].stop = dayjs.tz( - `${date.add(1, 'd').format('YYYY-MM-DD')} 00:00`, - 'YYYY-MM-DD HH:mm', - tz - ) - } - } - - return programs.filter(p => p.start.isSame(date, 'd')) - }, - async channels({ lang = 'ar' }) { - const axios = require('axios') - const result = await axios - .get('https://www.snrt.ma/ar/node/1208') - .then(response => response.data) - .catch(console.error) - - const $ = cheerio.load(result) - const items = $('.channels-row h4').toArray() - const channels = items.map(item => { - const $item = $(item) - const url = $item.find('a').attr('href') - return { - lang, - site_id: url.substr(url.lastIndexOf('/') + 1), - name: $item.find('img').attr('alt') - } - }) - - return channels - } -} - -function parseStart($item) { - const date = $item.attr('class').match(/\d{8}/)[0] - const time = $item.find('.grille-time').text().trim() - if (time) { - return dayjs.tz(`${date} ${time.replace('H', ':')}`, 'YYYYMMDD HH:mm', tz) - } -} - -function parseTitle($item) { - return $item.find('.program-title-sm').text().trim() -} - -function parseDescription($item) { - return $item.find('.program-description-sm').text().trim() -} - -function parseCategory($item) { - return $item.find('.genre-first').text().trim() -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return [$, $('.grille-line').toArray()] -} +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +const tz = 'Africa/Casablanca' + +module.exports = { + site: 'snrt.ma', + days: 2, + url({ channel }) { + return `https://www.snrt.ma/ar/node/${channel.site_id}` + }, + request: { + cache: { + ttl: 24 * 60 * 60 * 1000 // 1 day + } + }, + parser({ content, date }) { + const [$, items] = parseItems(content) + const programs = items.map(item => { + const $item = $(item) + const start = parseStart($item) + return { + title: parseTitle($item), + description: parseDescription($item), + category: parseCategory($item), + start + } + }).filter(item => item.start).sort((a, b) => a.start - b.start) + // fill start-stop + for (let i = 0; i < programs.length; i++) { + if (i < programs.length - 1) { + programs[i].stop = programs[i + 1].start + } else { + programs[i].stop = dayjs.tz( + `${date.add(1, 'd').format('YYYY-MM-DD')} 00:00`, + 'YYYY-MM-DD HH:mm', + tz + ) + } + } + + return programs.filter(p => p.start.isSame(date, 'd')) + }, + async channels({ lang = 'ar' }) { + const axios = require('axios') + const result = await axios + .get('https://www.snrt.ma/ar/node/1208') + .then(response => response.data) + .catch(console.error) + + const $ = cheerio.load(result) + const items = $('.channels-row h4').toArray() + const channels = items.map(item => { + const $item = $(item) + const url = $item.find('a').attr('href') + return { + lang, + site_id: url.substr(url.lastIndexOf('/') + 1), + name: $item.find('img').attr('alt') + } + }) + + return channels + } +} + +function parseStart($item) { + const date = $item.attr('class').match(/\d{8}/)[0] + const time = $item.find('.grille-time').text().trim() + if (time) { + return dayjs.tz(`${date} ${time.replace('H', ':')}`, 'YYYYMMDD HH:mm', tz) + } +} + +function parseTitle($item) { + return $item.find('.program-title-sm').text().trim() +} + +function parseDescription($item) { + return $item.find('.program-description-sm').text().trim() +} + +function parseCategory($item) { + return $item.find('.genre-first').text().trim() +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return [$, $('.grille-line').toArray()] +} diff --git a/sites/snrt.ma/snrt.ma.test.js b/sites/snrt.ma/snrt.ma.test.js index d20856fa1..dbb544cca 100644 --- a/sites/snrt.ma/snrt.ma.test.js +++ b/sites/snrt.ma/snrt.ma.test.js @@ -1,47 +1,47 @@ -const { parser, url } = require('./snrt.ma.config.js') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const fs = require('fs') -const path = require('path') - -dayjs.extend(utc) -dayjs.extend(timezone) - -const date = dayjs.utc('2025-01-13', 'YYYY-MM-DD').startOf('d') -const channel = { site_id: '4075', xmltv_id: 'Tamazight.ma', lang: 'ar' } - -it('can generate valid url', () => { - expect(url({ channel })).toBe('https://www.snrt.ma/ar/node/4075') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - const results = parser({ date, content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(27) - expect(results[0]).toMatchObject({ - start: '2025-01-12T23:15:00.000Z', - stop: '2025-01-12T23:30:00.000Z', - title: 'الموعد الرياضي' - }) - expect(results[26]).toMatchObject({ - start: '2025-01-13T21:30:00.000Z', - stop: '2025-01-13T23:00:00.000Z', - title: 'سهرة خاصة براس السنة الامازيغية', - category: 'ترفيه' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel: channel, - content: '' - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./snrt.ma.config.js') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const fs = require('fs') +const path = require('path') + +dayjs.extend(utc) +dayjs.extend(timezone) + +const date = dayjs.utc('2025-01-13', 'YYYY-MM-DD').startOf('d') +const channel = { site_id: '4075', xmltv_id: 'Tamazight.ma', lang: 'ar' } + +it('can generate valid url', () => { + expect(url({ channel })).toBe('https://www.snrt.ma/ar/node/4075') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + const results = parser({ date, content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(27) + expect(results[0]).toMatchObject({ + start: '2025-01-12T23:15:00.000Z', + stop: '2025-01-12T23:30:00.000Z', + title: 'الموعد الرياضي' + }) + expect(results[26]).toMatchObject({ + start: '2025-01-13T21:30:00.000Z', + stop: '2025-01-13T23:00:00.000Z', + title: 'سهرة خاصة براس السنة الامازيغية', + category: 'ترفيه' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel: channel, + content: '' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/sporttv.pt/sporttv.pt.config.js b/sites/sporttv.pt/sporttv.pt.config.js index 201b98ede..0709918e0 100644 --- a/sites/sporttv.pt/sporttv.pt.config.js +++ b/sites/sporttv.pt/sporttv.pt.config.js @@ -1,63 +1,63 @@ -const dayjs = require('dayjs') -const cheerio = require('cheerio') - -module.exports = { - site: 'sporttv.pt', - days: 2, - url: 'https://www.sporttv.pt/guia', - parser({ content, date, channel }) { - let programs = [] - const items = parseItems(content, channel, date) - items.forEach(item => { - const start = dayjs(item.data) - const stop = start.add(item.duracao, 'ms') - - programs.push({ - title: item.descricao, - description: item?.evento?.nome, - image: item.imagem, - category: item?.modalidade?.nomeModalidade, - start, - stop - }) - }) - - return programs - } -} - -function parseItems(content, channel, date) { - const $ = cheerio.load(content) - const nuxtData = $('#__NUXT_DATA__').html() - if (!nuxtData) return [] - const parsed = JSON.parse(nuxtData) - const dataIndex = parsed[1].data - const epgIndex = Object.values(parsed[dataIndex])[3] // 1611 - const epg = parsed[epgIndex].map(i => parsed[i]).map(obj => dataMapper(obj, parsed)) - if (!Array.isArray(epg)) return [] - - return epg - .filter( - item => item.canal.id === parseInt(channel.site_id) && date.isSame(dayjs(item.data), 'd') - ) - .sort((a, b) => { - if (a < b) return -1 - if (a > b) return 1 - return 0 - }) -} - -function dataMapper(object, parsed) { - let output = {} - - for (let key in object) { - const value = parsed[object[key]] - if (typeof value === 'object') { - output[key] = dataMapper(value, parsed) - } else { - output[key] = value - } - } - - return output -} +const dayjs = require('dayjs') +const cheerio = require('cheerio') + +module.exports = { + site: 'sporttv.pt', + days: 2, + url: 'https://www.sporttv.pt/guia', + parser({ content, date, channel }) { + let programs = [] + const items = parseItems(content, channel, date) + items.forEach(item => { + const start = dayjs(item.data) + const stop = start.add(item.duracao, 'ms') + + programs.push({ + title: item.descricao, + description: item?.evento?.nome, + image: item.imagem, + category: item?.modalidade?.nomeModalidade, + start, + stop + }) + }) + + return programs + } +} + +function parseItems(content, channel, date) { + const $ = cheerio.load(content) + const nuxtData = $('#__NUXT_DATA__').html() + if (!nuxtData) return [] + const parsed = JSON.parse(nuxtData) + const dataIndex = parsed[1].data + const epgIndex = Object.values(parsed[dataIndex])[3] // 1611 + const epg = parsed[epgIndex].map(i => parsed[i]).map(obj => dataMapper(obj, parsed)) + if (!Array.isArray(epg)) return [] + + return epg + .filter( + item => item.canal.id === parseInt(channel.site_id) && date.isSame(dayjs(item.data), 'd') + ) + .sort((a, b) => { + if (a < b) return -1 + if (a > b) return 1 + return 0 + }) +} + +function dataMapper(object, parsed) { + let output = {} + + for (let key in object) { + const value = parsed[object[key]] + if (typeof value === 'object') { + output[key] = dataMapper(value, parsed) + } else { + output[key] = value + } + } + + return output +} diff --git a/sites/sporttv.pt/sporttv.pt.test.js b/sites/sporttv.pt/sporttv.pt.test.js index a0836afe3..b33128d3b 100644 --- a/sites/sporttv.pt/sporttv.pt.test.js +++ b/sites/sporttv.pt/sporttv.pt.test.js @@ -1,54 +1,54 @@ -const { parser, url } = require('./sporttv.pt.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2024-12-23', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 727, - xmltv_id: 'SportTV1.pt' -} - -it('can generate valid url', () => { - expect(url).toBe('https://www.sporttv.pt/guia') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - const results = parser({ content, date, channel }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(19) - - expect(results[0]).toMatchObject({ - start: '2024-12-23T01:00:00.000Z', - stop: '2024-12-23T01:30:00.000Z', - description: 'LIGA PORTUGAL BETCLIC', - category: 'FUTEBOL', - title: 'RESUMOS DA JORNADA 15', - image: 'https://www.sporttv.pt/default/0001/11/08cb25f0b9b427e0bb83179309074632410f536b.jpg' - }) - - expect(results[1]).toMatchObject({ - start: '2024-12-23T01:30:00.000Z', - stop: '2024-12-23T02:00:00.000Z', - description: 'LIGA ITALIANA', - category: 'FUTEBOL', - title: 'RESUMOS DA JORNADA 17', - image: - 'https://www.sporttv.pt/cms_media/default/0001/11/56ab6bb72a00c8a9543eff35f90f57c07fb0ff87.jpg' - }) -}) - -it('can handle empty guide', () => { - const content = '' - const result = parser({ content, date }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./sporttv.pt.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2024-12-23', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 727, + xmltv_id: 'SportTV1.pt' +} + +it('can generate valid url', () => { + expect(url).toBe('https://www.sporttv.pt/guia') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + const results = parser({ content, date, channel }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(19) + + expect(results[0]).toMatchObject({ + start: '2024-12-23T01:00:00.000Z', + stop: '2024-12-23T01:30:00.000Z', + description: 'LIGA PORTUGAL BETCLIC', + category: 'FUTEBOL', + title: 'RESUMOS DA JORNADA 15', + image: 'https://www.sporttv.pt/default/0001/11/08cb25f0b9b427e0bb83179309074632410f536b.jpg' + }) + + expect(results[1]).toMatchObject({ + start: '2024-12-23T01:30:00.000Z', + stop: '2024-12-23T02:00:00.000Z', + description: 'LIGA ITALIANA', + category: 'FUTEBOL', + title: 'RESUMOS DA JORNADA 17', + image: + 'https://www.sporttv.pt/cms_media/default/0001/11/56ab6bb72a00c8a9543eff35f90f57c07fb0ff87.jpg' + }) +}) + +it('can handle empty guide', () => { + const content = '' + const result = parser({ content, date }) + expect(result).toMatchObject([]) +}) diff --git a/sites/starhubtvplus.com/starhubtvplus.com.config.js b/sites/starhubtvplus.com/starhubtvplus.com.config.js index c9710bae1..5fc26feb2 100644 --- a/sites/starhubtvplus.com/starhubtvplus.com.config.js +++ b/sites/starhubtvplus.com/starhubtvplus.com.config.js @@ -1,88 +1,88 @@ -const axios = require('axios') -const dayjs = require('dayjs') - -const languages = { en: 'en_US', zh: 'zh' } - -module.exports = { - site: 'starhubtvplus.com', - days: 2, - url({ date, channel }) { - return `https://waf-starhub-metadata-api-p001.ifs.vubiquity.com/v3.1/epg/schedules?locale=${ - languages[channel.lang] - }&locale_default=${languages[channel.lang]}&device=1&in_channel_id=${ - channel.site_id - }>_end=${date.unix()}<_start=${date.add(1, 'd').unix()}&limit=100&page=1` - }, - async parser({ content, date, channel }) { - const programs = [] - if (content) { - let res = JSON.parse(content) - while (res) { - if (res.resources) { - programs.push(...res.resources) - } - if (res.page && res.page.current < res.page.total) { - res = await axios - .get( - module.exports - .url({ date, channel }) - .replace(/page=(\d+)/, `page=${res.page.current + 1}`) - ) - .then(r => r.data) - .catch(console.error) - } else { - res = null - } - } - } - const season = s => { - if (s) { - const [, , n] = s.match(/(S|Season )(\d+)/) || [null, null, null] - if (n) { - return parseInt(n) - } - } - } - - return programs.map(item => { - return { - title: item.title, - subTitle: item.serie_title, - description: item.description, - category: item.genres, - image: item.pictures?.map(img => img.url), - season: season(item.serie_title), - episode: item.episode_number, - rating: item.rating, - start: dayjs(item.start * 1000), - stop: dayjs(item.end * 1000) - } - }) - }, - async channels({ lang = 'en' }) { - const resources = [] - let page = 1 - while (true) { - const items = await axios - .get( - `https://waf-starhub-metadata-api-p001.ifs.vubiquity.com/v3.1/epg/channels?locale=${languages[lang]}&locale_default=${languages[lang]}&device=1&limit=50&page=${page}` - ) - .then(r => r.data) - .catch(console.error) - if (items.resources) { - resources.push(...items.resources) - } - if (items.page && page < items.page.total) { - page++ - } else { - break - } - } - - return resources.map(ch => ({ - lang, - site_id: ch.id, - name: ch.title - })) - } -} +const axios = require('axios') +const dayjs = require('dayjs') + +const languages = { en: 'en_US', zh: 'zh' } + +module.exports = { + site: 'starhubtvplus.com', + days: 2, + url({ date, channel }) { + return `https://waf-starhub-metadata-api-p001.ifs.vubiquity.com/v3.1/epg/schedules?locale=${ + languages[channel.lang] + }&locale_default=${languages[channel.lang]}&device=1&in_channel_id=${ + channel.site_id + }>_end=${date.unix()}<_start=${date.add(1, 'd').unix()}&limit=100&page=1` + }, + async parser({ content, date, channel }) { + const programs = [] + if (content) { + let res = JSON.parse(content) + while (res) { + if (res.resources) { + programs.push(...res.resources) + } + if (res.page && res.page.current < res.page.total) { + res = await axios + .get( + module.exports + .url({ date, channel }) + .replace(/page=(\d+)/, `page=${res.page.current + 1}`) + ) + .then(r => r.data) + .catch(console.error) + } else { + res = null + } + } + } + const season = s => { + if (s) { + const [, , n] = s.match(/(S|Season )(\d+)/) || [null, null, null] + if (n) { + return parseInt(n) + } + } + } + + return programs.map(item => { + return { + title: item.title, + subTitle: item.serie_title, + description: item.description, + category: item.genres, + image: item.pictures?.map(img => img.url), + season: season(item.serie_title), + episode: item.episode_number, + rating: item.rating, + start: dayjs(item.start * 1000), + stop: dayjs(item.end * 1000) + } + }) + }, + async channels({ lang = 'en' }) { + const resources = [] + let page = 1 + while (true) { + const items = await axios + .get( + `https://waf-starhub-metadata-api-p001.ifs.vubiquity.com/v3.1/epg/channels?locale=${languages[lang]}&locale_default=${languages[lang]}&device=1&limit=50&page=${page}` + ) + .then(r => r.data) + .catch(console.error) + if (items.resources) { + resources.push(...items.resources) + } + if (items.page && page < items.page.total) { + page++ + } else { + break + } + } + + return resources.map(ch => ({ + lang, + site_id: ch.id, + name: ch.title + })) + } +} diff --git a/sites/starhubtvplus.com/starhubtvplus.com.test.js b/sites/starhubtvplus.com/starhubtvplus.com.test.js index 78d97c5bf..97216eace 100644 --- a/sites/starhubtvplus.com/starhubtvplus.com.test.js +++ b/sites/starhubtvplus.com/starhubtvplus.com.test.js @@ -1,55 +1,55 @@ -const { parser, url } = require('./starhubtvplus.com.config.js') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2024-12-04', 'YYYY-MM-DD').startOf('d') -const channel = { - lang: 'en', - site_id: 'd258444e-b66b-4cbe-88db-e09f31ab8a1f', - xmltv_id: 'AXN.sg' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://waf-starhub-metadata-api-p001.ifs.vubiquity.com/v3.1/epg/schedules?locale=en_US&locale_default=en_US&device=1&in_channel_id=d258444e-b66b-4cbe-88db-e09f31ab8a1f>_end=1733270400<_start=1733356800&limit=100&page=1' - ) -}) - -it('can parse response', async () => { - const fs = require('fs') - const path = require('path') - const content = fs.readFileSync(path.join(__dirname, '__data__', 'content.json')) - const result = (await parser({ content, date, channel })).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2024-12-03T17:25:00.000Z', - stop: '2024-12-03T18:20:00.000Z', - title: 'Northern Rexposure', - subTitle: 'Hudson & Rex (Season 5)', - description: - "When Jesse's sister contacts him for help, he, Sarah and Rex head to Northern Ontario and find themselves in the middle of a deadly situation.", - category: ['Drama'], - image: [ - 'https://poster.starhubgo.com/poster/ch511_hudson_rex5.jpg?w=960&h=540', - 'https://poster.starhubgo.com/poster/ch511_hudson_rex5.jpg?w=341&h=192' - ], - season: 5, - episode: 15, - rating: 'PG13' - } - ]) -}) - -it('can handle empty guide', async () => { - const result = await parser({ content: '' }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./starhubtvplus.com.config.js') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2024-12-04', 'YYYY-MM-DD').startOf('d') +const channel = { + lang: 'en', + site_id: 'd258444e-b66b-4cbe-88db-e09f31ab8a1f', + xmltv_id: 'AXN.sg' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://waf-starhub-metadata-api-p001.ifs.vubiquity.com/v3.1/epg/schedules?locale=en_US&locale_default=en_US&device=1&in_channel_id=d258444e-b66b-4cbe-88db-e09f31ab8a1f>_end=1733270400<_start=1733356800&limit=100&page=1' + ) +}) + +it('can parse response', async () => { + const fs = require('fs') + const path = require('path') + const content = fs.readFileSync(path.join(__dirname, '__data__', 'content.json')) + const result = (await parser({ content, date, channel })).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2024-12-03T17:25:00.000Z', + stop: '2024-12-03T18:20:00.000Z', + title: 'Northern Rexposure', + subTitle: 'Hudson & Rex (Season 5)', + description: + "When Jesse's sister contacts him for help, he, Sarah and Rex head to Northern Ontario and find themselves in the middle of a deadly situation.", + category: ['Drama'], + image: [ + 'https://poster.starhubgo.com/poster/ch511_hudson_rex5.jpg?w=960&h=540', + 'https://poster.starhubgo.com/poster/ch511_hudson_rex5.jpg?w=341&h=192' + ], + season: 5, + episode: 15, + rating: 'PG13' + } + ]) +}) + +it('can handle empty guide', async () => { + const result = await parser({ content: '' }) + expect(result).toMatchObject([]) +}) diff --git a/sites/startimestv.com/startimestv.com.config.js b/sites/startimestv.com/startimestv.com.config.js index 214e28b11..cf61c7d0f 100644 --- a/sites/startimestv.com/startimestv.com.config.js +++ b/sites/startimestv.com/startimestv.com.config.js @@ -1,112 +1,112 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -const doFetch = require('@ntlab/sfetch') -const debug = require('debug')('site:startimestv.com') - -dayjs.extend(utc) -dayjs.extend(customParseFormat) - -doFetch.setDebugger(debug).setMaxWorker(5) - -module.exports = { - site: 'startimestv.com', - days: 2, - url({ channel, date }) { - return `https://www.startimestv.com/channeldetail/${channel.site_id}/${date.format( - 'YYYY-MM-DD' - )}.html` - }, - parser({ content, date }) { - const programs = [] - if (content) { - const $ = cheerio.load(content) - $('.box .mask') - .toArray() - .forEach(el => { - let title = parseText($(el).find('h4')) - const [s, e] = title.substr(0, title.indexOf(' ')).split('-') || [null, null] - const start = dayjs.utc(`${date.format('YYYY-MM-DD')} ${s}`, 'YYYY-MM-DD HH:nn') - const stop = dayjs.utc(`${date.format('YYYY-MM-DD')} ${e}`, 'YYYY-MM-DD HH:nn') - title = title.substr(title.indexOf(' ') + 1) - const [, season, episode] = title.match(/ S(\d+) E(\d+)/) || [null, null, null] - const description = parseText($(el).find('p')) - programs.push({ - title, - description: description !== 'NA' ? description : null, - season: season ? parseInt(season) : season, - episode: episode ? parseInt(episode) : episode, - start, - stop - }) - }) - } - - return programs - }, - async channels() { - const channels = {} - const queues = [{ t: 'a', url: 'https://www.startimestv.com/tv_guide.html' }] - await doFetch(queues, (queue, res) => { - // process area-id - if (queue.t === 'a') { - const $ = cheerio.load(res) - $('dd.update-areaID') - .toArray() - .forEach(el => { - const dd = $(el) - const areaId = dd.attr('area-id') - queues.push({ - t: 's', - url: 'https://www.startimestv.com/tv_guide.html', - params: { - headers: { - cookie: `default_areaID=${areaId}` - } - } - }) - }) - } - // process channel - if (queue.t === 's') { - if (res) { - const $ = cheerio.load(res) - $('.channl .c') - .toArray() - .forEach(el => { - // only process channel with schedule only - const clazz = $(el).attr('class') - const [idx] = clazz.match(/\d+/) || [null] - if (idx && $(`.item.item-${idx} .mask`).length) { - const ch = $(el).find('.pic a[title]') - const [site_id] = ch.attr('href').match(/\d+/) || [null] - if (channels[site_id] === undefined) { - channels[site_id] = { - lang: 'en', - name: ch.attr('title'), - site_id - } - } - } - }) - } - } - }) - - return Object.values(channels) - } -} - -function parseText($item) { - let text = $item.text().replace(/\t/g, '').replace(/\n/g, ' ').trim() - while (true) { - if (text.match(/\s\s/)) { - text = text.replace(/\s\s/g, ' ') - continue - } - break - } - - return text -} +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const doFetch = require('@ntlab/sfetch') +const debug = require('debug')('site:startimestv.com') + +dayjs.extend(utc) +dayjs.extend(customParseFormat) + +doFetch.setDebugger(debug).setMaxWorker(5) + +module.exports = { + site: 'startimestv.com', + days: 2, + url({ channel, date }) { + return `https://www.startimestv.com/channeldetail/${channel.site_id}/${date.format( + 'YYYY-MM-DD' + )}.html` + }, + parser({ content, date }) { + const programs = [] + if (content) { + const $ = cheerio.load(content) + $('.box .mask') + .toArray() + .forEach(el => { + let title = parseText($(el).find('h4')) + const [s, e] = title.substr(0, title.indexOf(' ')).split('-') || [null, null] + const start = dayjs.utc(`${date.format('YYYY-MM-DD')} ${s}`, 'YYYY-MM-DD HH:nn') + const stop = dayjs.utc(`${date.format('YYYY-MM-DD')} ${e}`, 'YYYY-MM-DD HH:nn') + title = title.substr(title.indexOf(' ') + 1) + const [, season, episode] = title.match(/ S(\d+) E(\d+)/) || [null, null, null] + const description = parseText($(el).find('p')) + programs.push({ + title, + description: description !== 'NA' ? description : null, + season: season ? parseInt(season) : season, + episode: episode ? parseInt(episode) : episode, + start, + stop + }) + }) + } + + return programs + }, + async channels() { + const channels = {} + const queues = [{ t: 'a', url: 'https://www.startimestv.com/tv_guide.html' }] + await doFetch(queues, (queue, res) => { + // process area-id + if (queue.t === 'a') { + const $ = cheerio.load(res) + $('dd.update-areaID') + .toArray() + .forEach(el => { + const dd = $(el) + const areaId = dd.attr('area-id') + queues.push({ + t: 's', + url: 'https://www.startimestv.com/tv_guide.html', + params: { + headers: { + cookie: `default_areaID=${areaId}` + } + } + }) + }) + } + // process channel + if (queue.t === 's') { + if (res) { + const $ = cheerio.load(res) + $('.channl .c') + .toArray() + .forEach(el => { + // only process channel with schedule only + const clazz = $(el).attr('class') + const [idx] = clazz.match(/\d+/) || [null] + if (idx && $(`.item.item-${idx} .mask`).length) { + const ch = $(el).find('.pic a[title]') + const [site_id] = ch.attr('href').match(/\d+/) || [null] + if (channels[site_id] === undefined) { + channels[site_id] = { + lang: 'en', + name: ch.attr('title'), + site_id + } + } + } + }) + } + } + }) + + return Object.values(channels) + } +} + +function parseText($item) { + let text = $item.text().replace(/\t/g, '').replace(/\n/g, ' ').trim() + while (true) { + if (text.match(/\s\s/)) { + text = text.replace(/\s\s/g, ' ') + continue + } + break + } + + return text +} diff --git a/sites/startimestv.com/startimestv.com.test.js b/sites/startimestv.com/startimestv.com.test.js index 836537e6e..4c1384756 100644 --- a/sites/startimestv.com/startimestv.com.test.js +++ b/sites/startimestv.com/startimestv.com.test.js @@ -1,48 +1,48 @@ -const { parser, url } = require('./startimestv.com.config.js') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2024-12-10', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '1023102509', - xmltv_id: 'ZeeOneAfrica.za' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://www.startimestv.com/channeldetail/1023102509/2024-12-10.html' - ) -}) - -it('can parse response', () => { - const fs = require('fs') - const path = require('path') - const content = fs.readFileSync(path.join(__dirname, '__data__', 'content.html')) - const result = parser({ content, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result.length).toBe(22) - expect(result[0]).toMatchObject({ - start: '2024-12-10T00:00:00.000Z', - stop: '2024-12-10T01:00:00.000Z', - title: 'Deserted S1 E37', - description: - 'Tora approaches Tubri for help, but she expresses her helplessness in seeking assistance from Arjun. Meanwhile, other family members are caught in the crossfire, trying to navigate their own positions within the household.', - season: 1, - episode: 37 - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: - '

    Rate:

    Category:


    ' - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./startimestv.com.config.js') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2024-12-10', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '1023102509', + xmltv_id: 'ZeeOneAfrica.za' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://www.startimestv.com/channeldetail/1023102509/2024-12-10.html' + ) +}) + +it('can parse response', () => { + const fs = require('fs') + const path = require('path') + const content = fs.readFileSync(path.join(__dirname, '__data__', 'content.html')) + const result = parser({ content, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result.length).toBe(22) + expect(result[0]).toMatchObject({ + start: '2024-12-10T00:00:00.000Z', + stop: '2024-12-10T01:00:00.000Z', + title: 'Deserted S1 E37', + description: + 'Tora approaches Tubri for help, but she expresses her helplessness in seeking assistance from Arjun. Meanwhile, other family members are caught in the crossfire, trying to navigate their own positions within the household.', + season: 1, + episode: 37 + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: + '

    Rate:

    Category:


    ' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/stod2.is/stod2.is.config.js b/sites/stod2.is/stod2.is.config.js index fc91355d3..c5cffee89 100644 --- a/sites/stod2.is/stod2.is.config.js +++ b/sites/stod2.is/stod2.is.config.js @@ -1,67 +1,67 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const axios = require('axios') - -dayjs.extend(utc) - -module.exports = { - site: 'stod2.is', - days: 7, - request: { - cache: { - ttl: 60 * 60 * 1000 // 1 hour - } - }, - url({ channel, date }) { - return `https://api.stod2.is/dagskra/api/${channel.site_id}/${date.format('YYYY-MM-DD')}` - }, - parser: function ({ content }) { - let data - try { - data = JSON.parse(content) - } catch (error) { - console.error('Error parsing JSON:', error) - return [] - } - - const programs = [] - - if (data && Array.isArray(data)) { - data.forEach(item => { - if (!item) return - const start = dayjs.utc(item.upphaf) - const stop = start.add(item.slott, 'm') - - programs.push({ - title: item.isltitill, - sub_title: item.undirtitill, - description: item.lysing, - actors: item.adalhlutverk, - directors: item.leikstjori, - start, - stop - }) - }) - } - - return programs - }, - async channels() { - try { - const response = await axios.get('https://api.stod2.is/dagskra/api') - if (!response.data || !Array.isArray(response.data)) { - console.error('Error: No channels data found') - return [] - } - return response.data.map(item => { - return { - lang: 'is', - site_id: item - } - }) - } catch (error) { - console.error('Error fetching channels:', error) - return [] - } - } -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const axios = require('axios') + +dayjs.extend(utc) + +module.exports = { + site: 'stod2.is', + days: 7, + request: { + cache: { + ttl: 60 * 60 * 1000 // 1 hour + } + }, + url({ channel, date }) { + return `https://api.stod2.is/dagskra/api/${channel.site_id}/${date.format('YYYY-MM-DD')}` + }, + parser: function ({ content }) { + let data + try { + data = JSON.parse(content) + } catch (error) { + console.error('Error parsing JSON:', error) + return [] + } + + const programs = [] + + if (data && Array.isArray(data)) { + data.forEach(item => { + if (!item) return + const start = dayjs.utc(item.upphaf) + const stop = start.add(item.slott, 'm') + + programs.push({ + title: item.isltitill, + sub_title: item.undirtitill, + description: item.lysing, + actors: item.adalhlutverk, + directors: item.leikstjori, + start, + stop + }) + }) + } + + return programs + }, + async channels() { + try { + const response = await axios.get('https://api.stod2.is/dagskra/api') + if (!response.data || !Array.isArray(response.data)) { + console.error('Error: No channels data found') + return [] + } + return response.data.map(item => { + return { + lang: 'is', + site_id: item + } + }) + } catch (error) { + console.error('Error fetching channels:', error) + return [] + } + } +} diff --git a/sites/streamingtvguides.com/streamingtvguides.com.test.js b/sites/streamingtvguides.com/streamingtvguides.com.test.js index 1539f5757..57240ba2a 100644 --- a/sites/streamingtvguides.com/streamingtvguides.com.test.js +++ b/sites/streamingtvguides.com/streamingtvguides.com.test.js @@ -1,53 +1,53 @@ -const { parser, url } = require('./streamingtvguides.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-06-27', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'GMAPNY', - xmltv_id: 'GMAPinoyTVUSACanada.ph' -} - -it('can generate valid url', () => { - expect(url({ channel })).toBe('https://streamingtvguides.com/Channel/GMAPNY') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') - let results = parser({ content, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(38) - - expect(results[0]).toMatchObject({ - start: '2023-06-27T00:40:00.000Z', - stop: '2023-06-27T02:00:00.000Z', - title: '24 Oras', - description: 'Up to the minute news around the world.' - }) - - expect(results[37]).toMatchObject({ - start: '2023-06-27T21:50:00.000Z', - stop: '2023-06-28T00:00:00.000Z', - title: 'Eat Bulaga', - description: 'Rousing and engrossing segments with engaging hosts.' - }) -}) - -it('can handle empty guide', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') - const result = parser({ - date, - content - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./streamingtvguides.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-06-27', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'GMAPNY', + xmltv_id: 'GMAPinoyTVUSACanada.ph' +} + +it('can generate valid url', () => { + expect(url({ channel })).toBe('https://streamingtvguides.com/Channel/GMAPNY') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') + let results = parser({ content, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(38) + + expect(results[0]).toMatchObject({ + start: '2023-06-27T00:40:00.000Z', + stop: '2023-06-27T02:00:00.000Z', + title: '24 Oras', + description: 'Up to the minute news around the world.' + }) + + expect(results[37]).toMatchObject({ + start: '2023-06-27T21:50:00.000Z', + stop: '2023-06-28T00:00:00.000Z', + title: 'Eat Bulaga', + description: 'Rousing and engrossing segments with engaging hosts.' + }) +}) + +it('can handle empty guide', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') + const result = parser({ + date, + content + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/superguidatv.it/superguidatv.it.config.js b/sites/superguidatv.it/superguidatv.it.config.js index 6a89e9fe5..b8d971423 100644 --- a/sites/superguidatv.it/superguidatv.it.config.js +++ b/sites/superguidatv.it/superguidatv.it.config.js @@ -1,117 +1,117 @@ -const cheerio = require('cheerio') -const axios = require('axios') -const { DateTime } = require('luxon') - -module.exports = { - site: 'superguidatv.it', - days: 3, - url({ channel, date }) { - let diff = date.diff(DateTime.now().toUTC().startOf('day'), 'd') - let day = { - 0: 'oggi', - 1: 'domani', - 2: 'dopodomani' - } - - return `https://www.superguidatv.it/programmazione-canale/${day[diff]}/guida-programmi-tv-${channel.site_id}/` - }, - parser({ content, date }) { - const programs = [] - const items = parseItems(content) - items.forEach(item => { - const $item = cheerio.load(item) - const prev = programs[programs.length - 1] - let start = parseStart($item, date) - if (prev) { - if (start < prev.start) { - start = start.plus({ days: 1 }) - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.plus({ minutes: 30 }) - programs.push({ - title: parseTitle($item), - category: parseCategory($item), - start, - stop - }) - }) - - return programs - }, - async channels() { - const providers = [ - '', - 'premium/', - 'sky-intrattenimento/', - 'sky-sport/', - 'sky-cinema/', - 'sky-doc-e-lifestyle/', - 'sky-news/', - 'sky-bambini/', - 'sky-musica/', - 'sky-primafila/', - 'dazn/', - 'rsi/' - ] - const promises = providers.map(p => axios.get(`https://www.superguidatv.it/canali/${p}`)) - - const channels = [] - await Promise.all(promises) - .then(responses => { - responses.forEach(r => { - const $ = cheerio.load(r.data) - - $('.sgtvchannellist_mainContainer .sgtvchannel_divCell a').each((i, link) => { - let [, site_id] = $(link) - .attr('href') - .match(/guida-programmi-tv-(.*)\/$/) || [null, null] - let name = $(link).find('.pchannel').text().trim() - - channels.push({ - lang: 'it', - site_id, - name - }) - }) - }) - }) - .catch(console.log) - - return channels - } -} - -function parseStart($item, date) { - const hours = $item('.sgtvchannelplan_hoursCell') - .clone() - .children('.sgtvOnairSpan') - .remove() - .end() - .text() - .trim() - - return DateTime.fromFormat(`${date.format('YYYY-MM-DD')} ${hours}`, 'yyyy-MM-dd HH:mm', { - zone: 'Europe/Rome' - }).toUTC() -} - -function parseTitle($item) { - return $item('.sgtvchannelplan_spanInfoNextSteps').text().trim() -} - -function parseCategory($item) { - const eventType = $item('.sgtvchannelplan_spanEventType').text().trim() - const [, category] = eventType.match(/(^[^(]+)/) || [null, ''] - - return category.trim() -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('.sgtvchannelplan_divContainer > .sgtvchannelplan_divTableRow') - .has('#containerInfoEvent') - .toArray() -} +const cheerio = require('cheerio') +const axios = require('axios') +const { DateTime } = require('luxon') + +module.exports = { + site: 'superguidatv.it', + days: 3, + url({ channel, date }) { + let diff = date.diff(DateTime.now().toUTC().startOf('day'), 'd') + let day = { + 0: 'oggi', + 1: 'domani', + 2: 'dopodomani' + } + + return `https://www.superguidatv.it/programmazione-canale/${day[diff]}/guida-programmi-tv-${channel.site_id}/` + }, + parser({ content, date }) { + const programs = [] + const items = parseItems(content) + items.forEach(item => { + const $item = cheerio.load(item) + const prev = programs[programs.length - 1] + let start = parseStart($item, date) + if (prev) { + if (start < prev.start) { + start = start.plus({ days: 1 }) + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.plus({ minutes: 30 }) + programs.push({ + title: parseTitle($item), + category: parseCategory($item), + start, + stop + }) + }) + + return programs + }, + async channels() { + const providers = [ + '', + 'premium/', + 'sky-intrattenimento/', + 'sky-sport/', + 'sky-cinema/', + 'sky-doc-e-lifestyle/', + 'sky-news/', + 'sky-bambini/', + 'sky-musica/', + 'sky-primafila/', + 'dazn/', + 'rsi/' + ] + const promises = providers.map(p => axios.get(`https://www.superguidatv.it/canali/${p}`)) + + const channels = [] + await Promise.all(promises) + .then(responses => { + responses.forEach(r => { + const $ = cheerio.load(r.data) + + $('.sgtvchannellist_mainContainer .sgtvchannel_divCell a').each((i, link) => { + let [, site_id] = $(link) + .attr('href') + .match(/guida-programmi-tv-(.*)\/$/) || [null, null] + let name = $(link).find('.pchannel').text().trim() + + channels.push({ + lang: 'it', + site_id, + name + }) + }) + }) + }) + .catch(console.log) + + return channels + } +} + +function parseStart($item, date) { + const hours = $item('.sgtvchannelplan_hoursCell') + .clone() + .children('.sgtvOnairSpan') + .remove() + .end() + .text() + .trim() + + return DateTime.fromFormat(`${date.format('YYYY-MM-DD')} ${hours}`, 'yyyy-MM-dd HH:mm', { + zone: 'Europe/Rome' + }).toUTC() +} + +function parseTitle($item) { + return $item('.sgtvchannelplan_spanInfoNextSteps').text().trim() +} + +function parseCategory($item) { + const eventType = $item('.sgtvchannelplan_spanEventType').text().trim() + const [, category] = eventType.match(/(^[^(]+)/) || [null, ''] + + return category.trim() +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('.sgtvchannelplan_divContainer > .sgtvchannelplan_divTableRow') + .has('#containerInfoEvent') + .toArray() +} diff --git a/sites/superguidatv.it/superguidatv.it.test.js b/sites/superguidatv.it/superguidatv.it.test.js index 90b40564c..9ef520151 100644 --- a/sites/superguidatv.it/superguidatv.it.test.js +++ b/sites/superguidatv.it/superguidatv.it.test.js @@ -1,64 +1,64 @@ -const { parser, url } = require('./superguidatv.it.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-01-11', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'virgin-radio/461', - xmltv_id: 'VirginRadioTV.it' -} - -it('can generate valid url', () => { - expect(url({ channel, date: dayjs.utc().startOf('d') })).toBe( - 'https://www.superguidatv.it/programmazione-canale/oggi/guida-programmi-tv-virgin-radio/461/' - ) -}) - -it('can generate valid url for tomorrow', () => { - expect(url({ channel, date: dayjs.utc().startOf('d').add(1, 'd') })).toBe( - 'https://www.superguidatv.it/programmazione-canale/domani/guida-programmi-tv-virgin-radio/461/' - ) -}) - -it('can generate valid url for after tomorrow', () => { - expect(url({ channel, date: dayjs.utc().startOf('d').add(2, 'd') })).toBe( - 'https://www.superguidatv.it/programmazione-canale/dopodomani/guida-programmi-tv-virgin-radio/461/' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') - let results = parser({ content, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2023-01-11T01:00:00.000Z', - stop: '2023-01-11T05:00:00.000Z', - title: 'All Nite Rock', - category: 'Musica' - }) - - expect(results[13]).toMatchObject({ - start: '2023-01-12T05:00:00.000Z', - stop: '2023-01-12T05:30:00.000Z', - title: 'Free Rock', - category: 'Musica' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./superguidatv.it.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-01-11', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'virgin-radio/461', + xmltv_id: 'VirginRadioTV.it' +} + +it('can generate valid url', () => { + expect(url({ channel, date: dayjs.utc().startOf('d') })).toBe( + 'https://www.superguidatv.it/programmazione-canale/oggi/guida-programmi-tv-virgin-radio/461/' + ) +}) + +it('can generate valid url for tomorrow', () => { + expect(url({ channel, date: dayjs.utc().startOf('d').add(1, 'd') })).toBe( + 'https://www.superguidatv.it/programmazione-canale/domani/guida-programmi-tv-virgin-radio/461/' + ) +}) + +it('can generate valid url for after tomorrow', () => { + expect(url({ channel, date: dayjs.utc().startOf('d').add(2, 'd') })).toBe( + 'https://www.superguidatv.it/programmazione-canale/dopodomani/guida-programmi-tv-virgin-radio/461/' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') + let results = parser({ content, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2023-01-11T01:00:00.000Z', + stop: '2023-01-11T05:00:00.000Z', + title: 'All Nite Rock', + category: 'Musica' + }) + + expect(results[13]).toMatchObject({ + start: '2023-01-12T05:00:00.000Z', + stop: '2023-01-12T05:30:00.000Z', + title: 'Free Rock', + category: 'Musica' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/taiwanplus.com/taiwanplus.com.config.js b/sites/taiwanplus.com/taiwanplus.com.config.js index d4ec59135..54bccc0cd 100644 --- a/sites/taiwanplus.com/taiwanplus.com.config.js +++ b/sites/taiwanplus.com/taiwanplus.com.config.js @@ -1,68 +1,68 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const isSameOrAfter = require('dayjs/plugin/isSameOrAfter') -const isSameOrBefore = require('dayjs/plugin/isSameOrBefore') - -dayjs.extend(utc) -dayjs.extend(isSameOrAfter) -dayjs.extend(isSameOrBefore) - -module.exports = { - site: 'taiwanplus.com', - days: 7, - output: 'taiwanplus.com.guide.xml', - channels: 'taiwanplus.com.channels.xml', - lang: 'en', - - url: function () { - return 'https://www.taiwanplus.com/api/video/live/schedule/0' - }, - - request: { - method: 'GET', - timeout: 5000, - cache: { ttl: 60 * 60 * 1000 } // 60 * 60 seconds = 1 hour - }, - - logo: function (context) { - return context.channel.logo - }, - - parser: function (context) { - const programs = [] - const scheduleDates = parseItems(context.content) - const today = dayjs.utc(context.date).startOf('day') - - for (let scheduleDate of scheduleDates) { - const currentScheduleDate = new dayjs.utc(scheduleDate.date, 'YYYY/MM/DD') - - if (currentScheduleDate.isSame(today)) { - scheduleDate.schedule.forEach(function (program, i) { - programs.push({ - title: program.title, - start: dayjs.utc(program.dateTime, 'YYYY/MM/DD HH:mm'), - stop: - i != scheduleDate.schedule.length - 1 - ? dayjs.utc(scheduleDate.schedule[i + 1].dateTime, 'YYYY/MM/DD HH:mm') - : dayjs.utc(program.dateTime, 'YYYY/MM/DD HH:mm').add(1, 'day').startOf('day'), - description: program.description, - image: program.image, - category: program.categoryName, - rating: program.ageRating - }) - }) - } - } - - return programs - } -} - -function parseItems(content) { - if (content != '') { - const data = JSON.parse(content) - return !data || !data.data || !Array.isArray(data.data) ? [] : data.data - } else { - return [] - } -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const isSameOrAfter = require('dayjs/plugin/isSameOrAfter') +const isSameOrBefore = require('dayjs/plugin/isSameOrBefore') + +dayjs.extend(utc) +dayjs.extend(isSameOrAfter) +dayjs.extend(isSameOrBefore) + +module.exports = { + site: 'taiwanplus.com', + days: 7, + output: 'taiwanplus.com.guide.xml', + channels: 'taiwanplus.com.channels.xml', + lang: 'en', + + url: function () { + return 'https://www.taiwanplus.com/api/video/live/schedule/0' + }, + + request: { + method: 'GET', + timeout: 5000, + cache: { ttl: 60 * 60 * 1000 } // 60 * 60 seconds = 1 hour + }, + + logo: function (context) { + return context.channel.logo + }, + + parser: function (context) { + const programs = [] + const scheduleDates = parseItems(context.content) + const today = dayjs.utc(context.date).startOf('day') + + for (let scheduleDate of scheduleDates) { + const currentScheduleDate = new dayjs.utc(scheduleDate.date, 'YYYY/MM/DD') + + if (currentScheduleDate.isSame(today)) { + scheduleDate.schedule.forEach(function (program, i) { + programs.push({ + title: program.title, + start: dayjs.utc(program.dateTime, 'YYYY/MM/DD HH:mm'), + stop: + i != scheduleDate.schedule.length - 1 + ? dayjs.utc(scheduleDate.schedule[i + 1].dateTime, 'YYYY/MM/DD HH:mm') + : dayjs.utc(program.dateTime, 'YYYY/MM/DD HH:mm').add(1, 'day').startOf('day'), + description: program.description, + image: program.image, + category: program.categoryName, + rating: program.ageRating + }) + }) + } + } + + return programs + } +} + +function parseItems(content) { + if (content != '') { + const data = JSON.parse(content) + return !data || !data.data || !Array.isArray(data.data) ? [] : data.data + } else { + return [] + } +} diff --git a/sites/tapdmv.com/tapdmv.com.config.js b/sites/tapdmv.com/tapdmv.com.config.js index 015816319..15d0255e9 100644 --- a/sites/tapdmv.com/tapdmv.com.config.js +++ b/sites/tapdmv.com/tapdmv.com.config.js @@ -1,65 +1,65 @@ -const axios = require('axios') -const dayjs = require('dayjs') - -module.exports = { - site: 'tapdmv.com', - days: 2, - request: { - maxContentLength: 10485760 // 10 Mb - }, - url({ channel, date }) { - return `https://epg.tapdmv.com/calendar/${ - channel.site_id - }?%24limit=10000&%24sort%5BcreatedAt%5D=-1&start=${date.toJSON()}&end=${date - .add(1, 'd') - .toJSON()}` - }, - parser: function ({ content, date }) { - let programs = [] - const items = parseItems(content, date) - items.forEach(item => { - programs.push({ - title: item.program.trim(), - description: item.description, - category: item.genre, - image: item.thumbnailImage, - start: parseStart(item), - stop: parseStop(item) - }) - }) - - return programs - }, - async channels() { - const items = await axios - .get('https://epg.tapdmv.com/calendar?$limit=10000&$sort[createdAt]=-1') - .then(r => r.data.data) - .catch(console.log) - - return items.map(item => { - const [, name] = item.name.match(/epg-tapgo-([^.]+).json/) - return { - lang: 'en', - site_id: item.id, - name - } - }) - } -} - -function parseStart(item) { - return dayjs(item.startTime) -} - -function parseStop(item) { - return dayjs(item.endTime) -} - -function parseItems(content, date) { - if (!content) return [] - const data = JSON.parse(content) - if (!Array.isArray(data)) return [] - const d = date.format('YYYY-MM-DD') - - return data.filter(i => i.startTime.includes(d)) -} +const axios = require('axios') +const dayjs = require('dayjs') + +module.exports = { + site: 'tapdmv.com', + days: 2, + request: { + maxContentLength: 10485760 // 10 Mb + }, + url({ channel, date }) { + return `https://epg.tapdmv.com/calendar/${ + channel.site_id + }?%24limit=10000&%24sort%5BcreatedAt%5D=-1&start=${date.toJSON()}&end=${date + .add(1, 'd') + .toJSON()}` + }, + parser: function ({ content, date }) { + let programs = [] + const items = parseItems(content, date) + items.forEach(item => { + programs.push({ + title: item.program.trim(), + description: item.description, + category: item.genre, + image: item.thumbnailImage, + start: parseStart(item), + stop: parseStop(item) + }) + }) + + return programs + }, + async channels() { + const items = await axios + .get('https://epg.tapdmv.com/calendar?$limit=10000&$sort[createdAt]=-1') + .then(r => r.data.data) + .catch(console.log) + + return items.map(item => { + const [, name] = item.name.match(/epg-tapgo-([^.]+).json/) + return { + lang: 'en', + site_id: item.id, + name + } + }) + } +} + +function parseStart(item) { + return dayjs(item.startTime) +} + +function parseStop(item) { + return dayjs(item.endTime) +} + +function parseItems(content, date) { + if (!content) return [] + const data = JSON.parse(content) + if (!Array.isArray(data)) return [] + const d = date.format('YYYY-MM-DD') + + return data.filter(i => i.startTime.includes(d)) +} diff --git a/sites/telebilbao.es/telebilbao.es.config.js b/sites/telebilbao.es/telebilbao.es.config.js index 8355ce3fd..81d8b9e47 100644 --- a/sites/telebilbao.es/telebilbao.es.config.js +++ b/sites/telebilbao.es/telebilbao.es.config.js @@ -1,70 +1,70 @@ -const dayjs = require('dayjs') -const cheerio = require('cheerio') -const table2array = require('table2array') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -require('dayjs/locale/es') - -module.exports = { - site: 'telebilbao.es', - days: 1, - url: 'https://www.telebilbao.es/programacion-2/', - request: { - cache: { - ttl: 24 * 60 * 60 * 1000 // 1 day - } - }, - parser({ content, date }) { - let programs = [] - const items = parseItems(content, date) - items.forEach(item => { - const prev = programs[programs.length - 1] - let start = parseStart(item, date) - if (prev) { - if (start.isBefore(prev.start)) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.add(30, 'm') - - programs.push({ - title: item.title, - start, - stop - }) - }) - - return programs - } -} - -function parseStart(item, date) { - return dayjs.tz(`${date.format('YYYY-MM-DD')} ${item.time}`, 'YYYY-MM-DD HH:mm', 'Europe/Madrid') -} - -function parseItems(content, date) { - const $ = cheerio.load(content) - const tableHtml = $('table.programacion').html() - let tableArray = table2array(`${tableHtml}
    `) - const day = date.locale('es').format('dddd\nD MMMM').toUpperCase() - if (!tableArray[0]) return [] - const indexOfColumn = tableArray[0].indexOf(day) - tableArray.pop() - const items = [] - tableArray.forEach(row => { - items.push({ - time: row[0], - title: row[indexOfColumn] - }) - }) - - return items.filter(i => Boolean(i.time)) -} +const dayjs = require('dayjs') +const cheerio = require('cheerio') +const table2array = require('table2array') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +require('dayjs/locale/es') + +module.exports = { + site: 'telebilbao.es', + days: 1, + url: 'https://www.telebilbao.es/programacion-2/', + request: { + cache: { + ttl: 24 * 60 * 60 * 1000 // 1 day + } + }, + parser({ content, date }) { + let programs = [] + const items = parseItems(content, date) + items.forEach(item => { + const prev = programs[programs.length - 1] + let start = parseStart(item, date) + if (prev) { + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.add(30, 'm') + + programs.push({ + title: item.title, + start, + stop + }) + }) + + return programs + } +} + +function parseStart(item, date) { + return dayjs.tz(`${date.format('YYYY-MM-DD')} ${item.time}`, 'YYYY-MM-DD HH:mm', 'Europe/Madrid') +} + +function parseItems(content, date) { + const $ = cheerio.load(content) + const tableHtml = $('table.programacion').html() + let tableArray = table2array(`${tableHtml}
    `) + const day = date.locale('es').format('dddd\nD MMMM').toUpperCase() + if (!tableArray[0]) return [] + const indexOfColumn = tableArray[0].indexOf(day) + tableArray.pop() + const items = [] + tableArray.forEach(row => { + items.push({ + time: row[0], + title: row[indexOfColumn] + }) + }) + + return items.filter(i => Boolean(i.time)) +} diff --git a/sites/telebilbao.es/telebilbao.es.test.js b/sites/telebilbao.es/telebilbao.es.test.js index 6450fb7e1..e00f4abb3 100644 --- a/sites/telebilbao.es/telebilbao.es.test.js +++ b/sites/telebilbao.es/telebilbao.es.test.js @@ -1,44 +1,44 @@ -const { parser, url } = require('./telebilbao.es.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-01-16', 'YYYY-MM-DD').startOf('d') - -it('can generate valid url', () => { - expect(url).toBe('https://www.telebilbao.es/programacion-2/') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - let results = parser({ content, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(50) - expect(results[0]).toMatchObject({ - start: '2025-01-16T06:00:00.000Z', - stop: '2025-01-16T06:30:00.000Z', - title: 'BAI HORIXE' - }) - expect(results[49]).toMatchObject({ - start: '2025-01-17T07:30:00.000Z', - stop: '2025-01-17T08:00:00.000Z', - title: 'LA KAPITAL' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - date, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) - }) - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./telebilbao.es.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-01-16', 'YYYY-MM-DD').startOf('d') + +it('can generate valid url', () => { + expect(url).toBe('https://www.telebilbao.es/programacion-2/') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + let results = parser({ content, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(50) + expect(results[0]).toMatchObject({ + start: '2025-01-16T06:00:00.000Z', + stop: '2025-01-16T06:30:00.000Z', + title: 'BAI HORIXE' + }) + expect(results[49]).toMatchObject({ + start: '2025-01-17T07:30:00.000Z', + stop: '2025-01-17T08:00:00.000Z', + title: 'LA KAPITAL' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + date, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) + }) + expect(results).toMatchObject([]) +}) diff --git a/sites/teleboy.ch/teleboy.ch.config.js b/sites/teleboy.ch/teleboy.ch.config.js index 1e96375b9..2c52dfd0c 100644 --- a/sites/teleboy.ch/teleboy.ch.config.js +++ b/sites/teleboy.ch/teleboy.ch.config.js @@ -1,75 +1,75 @@ -const axios = require('axios') -const dayjs = require('dayjs') - -const API_KEY = 'e899f715940a209148f834702fc7f340b6b0496b62120b3ed9c9b3ec4d7dca00' - -module.exports = { - site: 'teleboy.ch', - url({ channel, date }) { - const begin = date.format('YYYY-MM-DD HH:mm:ss') - const end = date.add(1, 'd').format('YYYY-MM-DD HH:mm:ss') - - return `https://api.teleboy.ch/epg/broadcasts?begin=${begin}&end=${end}&expand=flags,primary_image&station=${channel.site_id}` - }, - request: { - headers: { - 'x-teleboy-apikey': API_KEY - } - }, - parser({ content }) { - const items = parseItems(content) - - return items.map(item => { - return { - start: dayjs(item.begin), - stop: dayjs(item.end), - title: item.title, - subtitle: item.subtitle || null, - description: item.short_description || null, - date: item.year ? item.year.toString() : null, - season: item.serie_season || null, - episode: item.serie_episode || null, - starRatings: parseRating(item), - image: parseImage(item) - } - }) - }, - async channels() { - const data = await axios - .get('https://api.teleboy.ch/epg/stations', module.exports.request) - .then(r => r.data) - .catch(console.error) - - return data.data.items.map(channel => ({ - lang: channel.language, - name: channel.name, - site_id: channel.id - })) - } -} - -function parseImage(item) { - if (!item?.primary_image?.base_path || !item?.primary_image?.hash) return null - - return `${item.primary_image.base_path}teleboyteaser6/${item.primary_image.hash}.jpg` -} - -function parseRating(item) { - if (!item.imdb_rating) return null - - return { - system: 'IMDb', - value: item.imdb_rating - } -} - -function parseItems(content) { - try { - const data = JSON.parse(content) - if (!data?.data?.items || !Array.isArray(data.data.items)) return [] - - return data.data.items - } catch { - return [] - } -} +const axios = require('axios') +const dayjs = require('dayjs') + +const API_KEY = 'e899f715940a209148f834702fc7f340b6b0496b62120b3ed9c9b3ec4d7dca00' + +module.exports = { + site: 'teleboy.ch', + url({ channel, date }) { + const begin = date.format('YYYY-MM-DD HH:mm:ss') + const end = date.add(1, 'd').format('YYYY-MM-DD HH:mm:ss') + + return `https://api.teleboy.ch/epg/broadcasts?begin=${begin}&end=${end}&expand=flags,primary_image&station=${channel.site_id}` + }, + request: { + headers: { + 'x-teleboy-apikey': API_KEY + } + }, + parser({ content }) { + const items = parseItems(content) + + return items.map(item => { + return { + start: dayjs(item.begin), + stop: dayjs(item.end), + title: item.title, + subtitle: item.subtitle || null, + description: item.short_description || null, + date: item.year ? item.year.toString() : null, + season: item.serie_season || null, + episode: item.serie_episode || null, + starRatings: parseRating(item), + image: parseImage(item) + } + }) + }, + async channels() { + const data = await axios + .get('https://api.teleboy.ch/epg/stations', module.exports.request) + .then(r => r.data) + .catch(console.error) + + return data.data.items.map(channel => ({ + lang: channel.language, + name: channel.name, + site_id: channel.id + })) + } +} + +function parseImage(item) { + if (!item?.primary_image?.base_path || !item?.primary_image?.hash) return null + + return `${item.primary_image.base_path}teleboyteaser6/${item.primary_image.hash}.jpg` +} + +function parseRating(item) { + if (!item.imdb_rating) return null + + return { + system: 'IMDb', + value: item.imdb_rating + } +} + +function parseItems(content) { + try { + const data = JSON.parse(content) + if (!data?.data?.items || !Array.isArray(data.data.items)) return [] + + return data.data.items + } catch { + return [] + } +} diff --git a/sites/teleboy.ch/teleboy.ch.test.js b/sites/teleboy.ch/teleboy.ch.test.js index cfa555180..c6fb93db8 100644 --- a/sites/teleboy.ch/teleboy.ch.test.js +++ b/sites/teleboy.ch/teleboy.ch.test.js @@ -1,62 +1,62 @@ -const { parser, url, request } = require('./teleboy.ch.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-01-26', 'YYYY-MM-DD').startOf('d') -const channel = { site_id: '303', xmltv_id: 'SRF1.ch' } - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://api.teleboy.ch/epg/broadcasts?begin=2025-01-26 00:00:00&end=2025-01-27 00:00:00&expand=flags,primary_image&station=303' - ) -}) - -it('can generate valid request headers', () => { - expect(request.headers).toMatchObject({ - 'x-teleboy-apikey': 'e899f715940a209148f834702fc7f340b6b0496b62120b3ed9c9b3ec4d7dca00' - }) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - - let results = parser({ content }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - - return p - }) - - expect(results.length).toBe(35) - expect(results[0]).toMatchObject({ - title: 'Der Staatsanwalt', - description: - 'Der Tod eines beliebten Wasserretters konfrontiert Oberstaatsanwalt Bernd Reuther, Hauptkommissarin Kerstin Klar und Oberkommissar Max Fischer mit einem undurchsichtigen Geflecht aus Lügen.', - subtitle: 'Tod eines Helden', - episode: 6, - season: 16, - date: '2021', - image: - 'https://media.teleboy.ch/media/teleboyteaser6/bd01aed53c7a37399ae034c2a1a2cc8aa31943f2.jpg', - starRatings: { - system: 'IMDb', - value: 6 - }, - start: '2025-01-25T22:45:00.000Z', - stop: '2025-01-25T23:50:00.000Z' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - }) - - expect(results).toMatchObject([]) -}) +const { parser, url, request } = require('./teleboy.ch.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-01-26', 'YYYY-MM-DD').startOf('d') +const channel = { site_id: '303', xmltv_id: 'SRF1.ch' } + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://api.teleboy.ch/epg/broadcasts?begin=2025-01-26 00:00:00&end=2025-01-27 00:00:00&expand=flags,primary_image&station=303' + ) +}) + +it('can generate valid request headers', () => { + expect(request.headers).toMatchObject({ + 'x-teleboy-apikey': 'e899f715940a209148f834702fc7f340b6b0496b62120b3ed9c9b3ec4d7dca00' + }) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + + let results = parser({ content }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + + return p + }) + + expect(results.length).toBe(35) + expect(results[0]).toMatchObject({ + title: 'Der Staatsanwalt', + description: + 'Der Tod eines beliebten Wasserretters konfrontiert Oberstaatsanwalt Bernd Reuther, Hauptkommissarin Kerstin Klar und Oberkommissar Max Fischer mit einem undurchsichtigen Geflecht aus Lügen.', + subtitle: 'Tod eines Helden', + episode: 6, + season: 16, + date: '2021', + image: + 'https://media.teleboy.ch/media/teleboyteaser6/bd01aed53c7a37399ae034c2a1a2cc8aa31943f2.jpg', + starRatings: { + system: 'IMDb', + value: 6 + }, + start: '2025-01-25T22:45:00.000Z', + stop: '2025-01-25T23:50:00.000Z' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/telenet.tv/telenet.tv.config.js b/sites/telenet.tv/telenet.tv.config.js index 7f286001a..b5329d4b4 100644 --- a/sites/telenet.tv/telenet.tv.config.js +++ b/sites/telenet.tv/telenet.tv.config.js @@ -1,138 +1,138 @@ -const axios = require('axios') -const dayjs = require('dayjs') - -const API_STATIC_ENDPOINT = 'https://static.spark.telenet.tv/eng/web/epg-service-lite/be' -const API_PROD_ENDPOINT = 'https://spark-prod-be.gnp.cloud.telenet.tv/eng/web/linear-service/v2' -const API_IMAGE_ENDPOINT = 'https://staticqbr-prod-be.gnp.cloud.telenet.tv/image-service' - -module.exports = { - site: 'telenet.tv', - days: 2, - request: { - cache: { - ttl: 60 * 60 * 1000 // 1 hour - } - }, - url: function ({ date, channel }) { - return `${API_STATIC_ENDPOINT}/${channel.lang}/events/segments/${date.format('YYYYMMDDHHmmss')}` - }, - async parser({ content, channel, date }) { - let programs = [] - let items = parseItems(content, channel) - if (!items.length) return programs - const promises = [ - axios.get( - `${API_STATIC_ENDPOINT}/${channel.lang}/events/segments/${date - .add(6, 'h') - .format('YYYYMMDDHHmmss')}`, - { - responseType: 'arraybuffer' - } - ), - axios.get( - `${API_STATIC_ENDPOINT}/${channel.lang}/events/segments/${date - .add(12, 'h') - .format('YYYYMMDDHHmmss')}`, - { - responseType: 'arraybuffer' - } - ), - axios.get( - `${API_STATIC_ENDPOINT}/${channel.lang}/events/segments/${date - .add(18, 'h') - .format('YYYYMMDDHHmmss')}`, - { - responseType: 'arraybuffer' - } - ) - ] - - await Promise.allSettled(promises) - .then(results => { - results.forEach(r => { - if (r.status === 'fulfilled') { - const parsed = parseItems(r.value.data, channel) - - items = items.concat(parsed) - } - }) - }) - .catch(console.error) - - for (let item of items) { - const detail = await loadProgramDetails(item, channel) - programs.push({ - title: item.title, - icon: parseIcon(item), - description: detail.longDescription, - category: detail.genres, - actors: detail.actors, - season: parseSeason(detail), - episode: parseEpisode(detail), - start: parseStart(item), - stop: parseStop(item) - }) - } - - return programs - }, - async channels() { - const data = await axios - .get(`${API_PROD_ENDPOINT}/channels?cityId=28001&language=en&productClass=Orion-DASH`) - .then(r => r.data) - .catch(console.log) - - return data.map(item => { - return { - lang: 'nl', - site_id: item.id, - name: item.name - } - }) - } -} - -async function loadProgramDetails(item, channel) { - if (!item.id) return {} - const url = `${API_PROD_ENDPOINT}/replayEvent/${item.id}?returnLinearContent=true&language=${channel.lang}` - const data = await axios - .get(url) - .then(r => r.data) - .catch(console.log) - - return data || {} -} - -function parseStart(item) { - return dayjs.unix(item.startTime) -} - -function parseStop(item) { - return dayjs.unix(item.endTime) -} - -function parseItems(content, channel) { - if (!content) return [] - const data = JSON.parse(content) - if (!data || !Array.isArray(data.entries)) return [] - const channelData = data.entries.find(e => e.channelId === channel.site_id) - if (!channelData) return [] - - return Array.isArray(channelData.events) ? channelData.events : [] -} - -function parseSeason(detail) { - if (!detail.seasonNumber) return null - if (String(detail.seasonNumber).length > 2) return null - return detail.seasonNumber -} - -function parseEpisode(detail) { - if (!detail.episodeNumber) return null - if (String(detail.episodeNumber).length > 3) return null - return detail.episodeNumber -} - -function parseIcon(item) { - return `${API_IMAGE_ENDPOINT}/intent/${item.id}/posterTile` -} +const axios = require('axios') +const dayjs = require('dayjs') + +const API_STATIC_ENDPOINT = 'https://static.spark.telenet.tv/eng/web/epg-service-lite/be' +const API_PROD_ENDPOINT = 'https://spark-prod-be.gnp.cloud.telenet.tv/eng/web/linear-service/v2' +const API_IMAGE_ENDPOINT = 'https://staticqbr-prod-be.gnp.cloud.telenet.tv/image-service' + +module.exports = { + site: 'telenet.tv', + days: 2, + request: { + cache: { + ttl: 60 * 60 * 1000 // 1 hour + } + }, + url: function ({ date, channel }) { + return `${API_STATIC_ENDPOINT}/${channel.lang}/events/segments/${date.format('YYYYMMDDHHmmss')}` + }, + async parser({ content, channel, date }) { + let programs = [] + let items = parseItems(content, channel) + if (!items.length) return programs + const promises = [ + axios.get( + `${API_STATIC_ENDPOINT}/${channel.lang}/events/segments/${date + .add(6, 'h') + .format('YYYYMMDDHHmmss')}`, + { + responseType: 'arraybuffer' + } + ), + axios.get( + `${API_STATIC_ENDPOINT}/${channel.lang}/events/segments/${date + .add(12, 'h') + .format('YYYYMMDDHHmmss')}`, + { + responseType: 'arraybuffer' + } + ), + axios.get( + `${API_STATIC_ENDPOINT}/${channel.lang}/events/segments/${date + .add(18, 'h') + .format('YYYYMMDDHHmmss')}`, + { + responseType: 'arraybuffer' + } + ) + ] + + await Promise.allSettled(promises) + .then(results => { + results.forEach(r => { + if (r.status === 'fulfilled') { + const parsed = parseItems(r.value.data, channel) + + items = items.concat(parsed) + } + }) + }) + .catch(console.error) + + for (let item of items) { + const detail = await loadProgramDetails(item, channel) + programs.push({ + title: item.title, + icon: parseIcon(item), + description: detail.longDescription, + category: detail.genres, + actors: detail.actors, + season: parseSeason(detail), + episode: parseEpisode(detail), + start: parseStart(item), + stop: parseStop(item) + }) + } + + return programs + }, + async channels() { + const data = await axios + .get(`${API_PROD_ENDPOINT}/channels?cityId=28001&language=en&productClass=Orion-DASH`) + .then(r => r.data) + .catch(console.log) + + return data.map(item => { + return { + lang: 'nl', + site_id: item.id, + name: item.name + } + }) + } +} + +async function loadProgramDetails(item, channel) { + if (!item.id) return {} + const url = `${API_PROD_ENDPOINT}/replayEvent/${item.id}?returnLinearContent=true&language=${channel.lang}` + const data = await axios + .get(url) + .then(r => r.data) + .catch(console.log) + + return data || {} +} + +function parseStart(item) { + return dayjs.unix(item.startTime) +} + +function parseStop(item) { + return dayjs.unix(item.endTime) +} + +function parseItems(content, channel) { + if (!content) return [] + const data = JSON.parse(content) + if (!data || !Array.isArray(data.entries)) return [] + const channelData = data.entries.find(e => e.channelId === channel.site_id) + if (!channelData) return [] + + return Array.isArray(channelData.events) ? channelData.events : [] +} + +function parseSeason(detail) { + if (!detail.seasonNumber) return null + if (String(detail.seasonNumber).length > 2) return null + return detail.seasonNumber +} + +function parseEpisode(detail) { + if (!detail.episodeNumber) return null + if (String(detail.episodeNumber).length > 3) return null + return detail.episodeNumber +} + +function parseIcon(item) { + return `${API_IMAGE_ENDPOINT}/intent/${item.id}/posterTile` +} diff --git a/sites/telenet.tv/telenet.tv.test.js b/sites/telenet.tv/telenet.tv.test.js index 28baeff82..bf7fd0c33 100644 --- a/sites/telenet.tv/telenet.tv.test.js +++ b/sites/telenet.tv/telenet.tv.test.js @@ -1,89 +1,89 @@ -const { parser, url } = require('./telenet.tv.config.js') -const fs = require('fs') -const path = require('path') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const API_STATIC_ENDPOINT = 'https://static.spark.telenet.tv/eng/web/epg-service-lite/be' -const API_PROD_ENDPOINT = 'https://spark-prod-be.gnp.cloud.telenet.tv/eng/web/linear-service/v2' - -jest.mock('axios') - -const date = dayjs.utc('2022-10-30', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'outtv', - xmltv_id: 'OutTV.nl', - lang: 'nl' -} - -it('can generate valid url', () => { - expect(url({ date, channel })).toBe(`${API_STATIC_ENDPOINT}/nl/events/segments/20221030000000`) -}) - -it('can parse response', async () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_0000.json')) - - axios.get.mockImplementation(url => { - if (url === `${API_STATIC_ENDPOINT}/nl/events/segments/20221030060000`) { - return Promise.resolve({ - data: fs.readFileSync(path.resolve(__dirname, '__data__/content_0600.json')) - }) - } else if (url === `${API_STATIC_ENDPOINT}/nl/events/segments/20221030120000`) { - return Promise.resolve({ - data: fs.readFileSync(path.resolve(__dirname, '__data__/content_1200.json')) - }) - } else if (url === `${API_STATIC_ENDPOINT}/nl/events/segments/20221030180000`) { - return Promise.resolve({ - data: fs.readFileSync(path.resolve(__dirname, '__data__/content_1800.json')) - }) - } else if ( - url === - `${API_PROD_ENDPOINT}/replayEvent/crid:~~2F~~2Fgn.tv~~2F2459095~~2FEP036477800004,imi:0a2f4207b03c16c70b7fb3be8e07881aafe44106?returnLinearContent=true&language=nl` - ) { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program.json'))) - }) - } else { - return Promise.resolve({ data: '' }) - } - }) - - let results = await parser({ content, channel, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2022-10-29T23:56:00.000Z', - stop: '2022-10-30T01:44:00.000Z', - title: 'Queer as Folk USA', - icon: 'https://staticqbr-prod-be.gnp.cloud.telenet.tv/image-service/intent/crid:~~2F~~2Fgn.tv~~2F2459095~~2FEP036477800004,imi:0a2f4207b03c16c70b7fb3be8e07881aafe44106/posterTile', - description: - "Justin belandt in de gevangenis, Brian en Brandon banen zich een weg door de lijst, Ben treurt, Melanie en Lindsay proberen een interne scheiding en Emmett's stalker onthult zichzelf.", - category: ['Dramaserie', 'LHBTI'], - actors: [ - 'Gale Harold', - 'Hal Sparks', - 'Randy Harrison', - 'Peter Paige', - 'Scott Lowell', - 'Thea Gill', - 'Michelle Clunie', - 'Sharon Gless' - ], - season: 5, - episode: 8 - }) -}) - -it('can handle empty guide', async () => { - let results = await parser({ content: '', channel, date }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./telenet.tv.config.js') +const fs = require('fs') +const path = require('path') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const API_STATIC_ENDPOINT = 'https://static.spark.telenet.tv/eng/web/epg-service-lite/be' +const API_PROD_ENDPOINT = 'https://spark-prod-be.gnp.cloud.telenet.tv/eng/web/linear-service/v2' + +jest.mock('axios') + +const date = dayjs.utc('2022-10-30', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'outtv', + xmltv_id: 'OutTV.nl', + lang: 'nl' +} + +it('can generate valid url', () => { + expect(url({ date, channel })).toBe(`${API_STATIC_ENDPOINT}/nl/events/segments/20221030000000`) +}) + +it('can parse response', async () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_0000.json')) + + axios.get.mockImplementation(url => { + if (url === `${API_STATIC_ENDPOINT}/nl/events/segments/20221030060000`) { + return Promise.resolve({ + data: fs.readFileSync(path.resolve(__dirname, '__data__/content_0600.json')) + }) + } else if (url === `${API_STATIC_ENDPOINT}/nl/events/segments/20221030120000`) { + return Promise.resolve({ + data: fs.readFileSync(path.resolve(__dirname, '__data__/content_1200.json')) + }) + } else if (url === `${API_STATIC_ENDPOINT}/nl/events/segments/20221030180000`) { + return Promise.resolve({ + data: fs.readFileSync(path.resolve(__dirname, '__data__/content_1800.json')) + }) + } else if ( + url === + `${API_PROD_ENDPOINT}/replayEvent/crid:~~2F~~2Fgn.tv~~2F2459095~~2FEP036477800004,imi:0a2f4207b03c16c70b7fb3be8e07881aafe44106?returnLinearContent=true&language=nl` + ) { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program.json'))) + }) + } else { + return Promise.resolve({ data: '' }) + } + }) + + let results = await parser({ content, channel, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2022-10-29T23:56:00.000Z', + stop: '2022-10-30T01:44:00.000Z', + title: 'Queer as Folk USA', + icon: 'https://staticqbr-prod-be.gnp.cloud.telenet.tv/image-service/intent/crid:~~2F~~2Fgn.tv~~2F2459095~~2FEP036477800004,imi:0a2f4207b03c16c70b7fb3be8e07881aafe44106/posterTile', + description: + "Justin belandt in de gevangenis, Brian en Brandon banen zich een weg door de lijst, Ben treurt, Melanie en Lindsay proberen een interne scheiding en Emmett's stalker onthult zichzelf.", + category: ['Dramaserie', 'LHBTI'], + actors: [ + 'Gale Harold', + 'Hal Sparks', + 'Randy Harrison', + 'Peter Paige', + 'Scott Lowell', + 'Thea Gill', + 'Michelle Clunie', + 'Sharon Gless' + ], + season: 5, + episode: 8 + }) +}) + +it('can handle empty guide', async () => { + let results = await parser({ content: '', channel, date }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/teliatv.ee/teliatv.ee.config.js b/sites/teliatv.ee/teliatv.ee.config.js index b6393c9b2..2d7253dba 100644 --- a/sites/teliatv.ee/teliatv.ee.config.js +++ b/sites/teliatv.ee/teliatv.ee.config.js @@ -1,67 +1,67 @@ -const axios = require('axios') -const dayjs = require('dayjs') - -module.exports = { - site: 'teliatv.ee', - days: 2, - url({ date, channel }) { - const [lang, channelId] = channel.site_id.split('#') - return `https://api.teliatv.ee/dtv-api/3.2/${lang}/epg/guide?channelIds=${channelId}&relations=programmes&images=webGuideItemLarge&startAt=${date - .add(1, 'd') - .format('YYYY-MM-DDTHH:mm')}&startAtOp=lte&endAt=${date.format( - 'YYYY-MM-DDTHH:mm' - )}&endAtOp=gt` - }, - parser({ content, channel }) { - let programs = [] - const items = parseItems(content, channel) - items.forEach(item => { - programs.push({ - title: item.name, - image: parseImage(item), - start: dayjs(item.startAt), - stop: dayjs(item.endAt) - }) - }) - - return programs - }, - async channels({ lang }) { - const data = await axios - .get(`https://api.teliatv.ee/dtv-api/3.0/${lang}/channel-lists?listClass=tv&ui=tv-web`) - .then(r => r.data) - .catch(console.log) - - return Object.values(data.channels).map(item => { - return { - lang, - site_id: `${lang}#${item.id}`, - name: item.title - } - }) - } -} - -function parseImage(item) { - return item.images.webGuideItemLarge - ? `https://inet-static.mw.elion.ee${item.images.webGuideItemLarge}` - : null -} - -function parseItems(content, channel) { - const data = JSON.parse(content) - if (!data || !data.relations || !data.categoryItems) return [] - const [, channelId] = channel.site_id.split('#') - const items = data.categoryItems[channelId] || [] - - return items - .map(i => { - const programmeId = i.related.programmeIds[0] - if (!programmeId) return null - const progData = data.relations.programmes[programmeId] - if (!progData) return null - - return { ...i, ...progData } - }) - .filter(i => i) -} +const axios = require('axios') +const dayjs = require('dayjs') + +module.exports = { + site: 'teliatv.ee', + days: 2, + url({ date, channel }) { + const [lang, channelId] = channel.site_id.split('#') + return `https://api.teliatv.ee/dtv-api/3.2/${lang}/epg/guide?channelIds=${channelId}&relations=programmes&images=webGuideItemLarge&startAt=${date + .add(1, 'd') + .format('YYYY-MM-DDTHH:mm')}&startAtOp=lte&endAt=${date.format( + 'YYYY-MM-DDTHH:mm' + )}&endAtOp=gt` + }, + parser({ content, channel }) { + let programs = [] + const items = parseItems(content, channel) + items.forEach(item => { + programs.push({ + title: item.name, + image: parseImage(item), + start: dayjs(item.startAt), + stop: dayjs(item.endAt) + }) + }) + + return programs + }, + async channels({ lang }) { + const data = await axios + .get(`https://api.teliatv.ee/dtv-api/3.0/${lang}/channel-lists?listClass=tv&ui=tv-web`) + .then(r => r.data) + .catch(console.log) + + return Object.values(data.channels).map(item => { + return { + lang, + site_id: `${lang}#${item.id}`, + name: item.title + } + }) + } +} + +function parseImage(item) { + return item.images.webGuideItemLarge + ? `https://inet-static.mw.elion.ee${item.images.webGuideItemLarge}` + : null +} + +function parseItems(content, channel) { + const data = JSON.parse(content) + if (!data || !data.relations || !data.categoryItems) return [] + const [, channelId] = channel.site_id.split('#') + const items = data.categoryItems[channelId] || [] + + return items + .map(i => { + const programmeId = i.related.programmeIds[0] + if (!programmeId) return null + const progData = data.relations.programmes[programmeId] + if (!progData) return null + + return { ...i, ...progData } + }) + .filter(i => i) +} diff --git a/sites/telkussa.fi/telkussa.fi.config.js b/sites/telkussa.fi/telkussa.fi.config.js index d9eeb6c23..f50d043ac 100644 --- a/sites/telkussa.fi/telkussa.fi.config.js +++ b/sites/telkussa.fi/telkussa.fi.config.js @@ -1,45 +1,45 @@ -const dayjs = require('dayjs') - -module.exports = { - site: 'telkussa.fi', - days: 2, - url: function ({ date, channel }) { - return `https://telkussa.fi/API/Channel/${channel.site_id}/${date.format('YYYYMMDD')}` - }, - parser: function ({ content }) { - const programs = [] - const items = JSON.parse(content) - if (!items.length) return programs - - items.forEach(item => { - if (item.name && item.start && item.stop) { - const start = dayjs.unix(parseInt(item.start) * 60) - const stop = dayjs.unix(parseInt(item.stop) * 60) - - programs.push({ - title: item.name, - description: item.description, - start, - stop - }) - } - }) - - return programs - }, - async channels() { - const axios = require('axios') - const data = await axios - .get('https://telkussa.fi/API/Channels') - .then(r => r.data) - .catch(console.log) - - return data.map(item => { - return { - lang: 'fi', - site_id: item.id, - name: item.name - } - }) - } -} +const dayjs = require('dayjs') + +module.exports = { + site: 'telkussa.fi', + days: 2, + url: function ({ date, channel }) { + return `https://telkussa.fi/API/Channel/${channel.site_id}/${date.format('YYYYMMDD')}` + }, + parser: function ({ content }) { + const programs = [] + const items = JSON.parse(content) + if (!items.length) return programs + + items.forEach(item => { + if (item.name && item.start && item.stop) { + const start = dayjs.unix(parseInt(item.start) * 60) + const stop = dayjs.unix(parseInt(item.stop) * 60) + + programs.push({ + title: item.name, + description: item.description, + start, + stop + }) + } + }) + + return programs + }, + async channels() { + const axios = require('axios') + const data = await axios + .get('https://telkussa.fi/API/Channels') + .then(r => r.data) + .catch(console.log) + + return data.map(item => { + return { + lang: 'fi', + site_id: item.id, + name: item.name + } + }) + } +} diff --git a/sites/telkussa.fi/telkussa.fi.test.js b/sites/telkussa.fi/telkussa.fi.test.js index c4dfd77e2..48748b0c1 100644 --- a/sites/telkussa.fi/telkussa.fi.test.js +++ b/sites/telkussa.fi/telkussa.fi.test.js @@ -1,50 +1,50 @@ -const { parser, url } = require('./telkussa.fi.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-11-30', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '88', - xmltv_id: 'TV5.fi' -} - -it('can generate valid url', () => { - expect(url({ date, channel })).toBe('https://telkussa.fi/API/Channel/88/20231130') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const results = parser({ content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2023-11-29T20:40:00.000Z', - stop: '2023-11-29T23:20:00.000Z', - title: 'The Suicide Squad: Suicide Mission', - description: - 'SUOMEN TV-ENSI-ILTA Tervetuloa helvettiin - toisin sanoen Belle Reven vankilaan, missä henki on höllemmessä kuin missään muualla koko Amerikanmaalla. Missä pidetään pahimpia superroistoja ja missä ollaan valmiita tekemään mitä vain, jotta pääsisi pois - jopa liittymään supersalaiseen, superhämärään ryhmään nimeltä Task Force X. Ja mikä on päivän itsetuhoinen tehtävä? Kerää kokoon joukko vankeja, mukaan lukien Bloodsport, Peacemaker, Captain Boomerang, Ratcatcher 2, Savant, King Shark, Blackguard, Javelin ja kaikkien lempisekopää Harley Quinn. Anna heille raskas aseistus ja pudota heidät (kirjaimellisesti) Corto Maltesen syrjäiselle, vihollisia kuhisevalle saarelle. Halki viidakon, joka vilisee sotaisia vastustajia ja sissijoukkoja, ryhmä taivaltaa kohti tuhoamistehtäväänsä. Matkalla heitä kurissa yrittää pitää vain eversti Rick Flag… sekä Amanda Wallerin tekniikkavelhot, jotka antavat jatkuvasti ohjeita korvanappeihin. Ja kuten aina, yksikin väärä liike tietää kuolemaa (tuli se sitten vastustajan, toverin tai Wallerin itsensä toimesta). Jos joku haluaa lyödä vetoa, fiksuinta lienee veikata heitä vastaan - kaikkia heitä. 132 min. Ohjaus: James Gunn. Pääosissa: Margot Robbie, Idris Elba, John Cena, Joel Kinnaman ja Jai Courtney. (The Suicide Squad, Toiminta, Yhdysvallat, 2021)' - }) - - expect(results[31]).toMatchObject({ - start: '2023-12-01T03:25:00.000Z', - stop: '2023-12-01T03:55:00.000Z', - title: 'Asunnon metsästäjät', - description: - 'Sarjassa sinkut, pariskunnat ja perheet etsivät uutta kotia asunnonvälittäjän avustuksella Yhdysvalloissa. .(House Hunters, Tosi-tv, Yhdysvallat, 2018) S148E02' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: '[]' - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./telkussa.fi.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-11-30', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '88', + xmltv_id: 'TV5.fi' +} + +it('can generate valid url', () => { + expect(url({ date, channel })).toBe('https://telkussa.fi/API/Channel/88/20231130') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const results = parser({ content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2023-11-29T20:40:00.000Z', + stop: '2023-11-29T23:20:00.000Z', + title: 'The Suicide Squad: Suicide Mission', + description: + 'SUOMEN TV-ENSI-ILTA Tervetuloa helvettiin - toisin sanoen Belle Reven vankilaan, missä henki on höllemmessä kuin missään muualla koko Amerikanmaalla. Missä pidetään pahimpia superroistoja ja missä ollaan valmiita tekemään mitä vain, jotta pääsisi pois - jopa liittymään supersalaiseen, superhämärään ryhmään nimeltä Task Force X. Ja mikä on päivän itsetuhoinen tehtävä? Kerää kokoon joukko vankeja, mukaan lukien Bloodsport, Peacemaker, Captain Boomerang, Ratcatcher 2, Savant, King Shark, Blackguard, Javelin ja kaikkien lempisekopää Harley Quinn. Anna heille raskas aseistus ja pudota heidät (kirjaimellisesti) Corto Maltesen syrjäiselle, vihollisia kuhisevalle saarelle. Halki viidakon, joka vilisee sotaisia vastustajia ja sissijoukkoja, ryhmä taivaltaa kohti tuhoamistehtäväänsä. Matkalla heitä kurissa yrittää pitää vain eversti Rick Flag… sekä Amanda Wallerin tekniikkavelhot, jotka antavat jatkuvasti ohjeita korvanappeihin. Ja kuten aina, yksikin väärä liike tietää kuolemaa (tuli se sitten vastustajan, toverin tai Wallerin itsensä toimesta). Jos joku haluaa lyödä vetoa, fiksuinta lienee veikata heitä vastaan - kaikkia heitä. 132 min. Ohjaus: James Gunn. Pääosissa: Margot Robbie, Idris Elba, John Cena, Joel Kinnaman ja Jai Courtney. (The Suicide Squad, Toiminta, Yhdysvallat, 2021)' + }) + + expect(results[31]).toMatchObject({ + start: '2023-12-01T03:25:00.000Z', + stop: '2023-12-01T03:55:00.000Z', + title: 'Asunnon metsästäjät', + description: + 'Sarjassa sinkut, pariskunnat ja perheet etsivät uutta kotia asunnonvälittäjän avustuksella Yhdysvalloissa. .(House Hunters, Tosi-tv, Yhdysvallat, 2018) S148E02' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: '[]' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/telsu.fi/telsu.fi.config.js b/sites/telsu.fi/telsu.fi.config.js index 057cccd1b..73f50928a 100644 --- a/sites/telsu.fi/telsu.fi.config.js +++ b/sites/telsu.fi/telsu.fi.config.js @@ -1,98 +1,98 @@ -const axios = require('axios') -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') - -dayjs.extend(utc) -dayjs.extend(timezone) - -module.exports = { - site: 'telsu.fi', - days: 2, - url: function ({ date, channel }) { - return `https://www.telsu.fi/${date.format('YYYYMMDD')}/${channel.site_id}` - }, - parser: function ({ content, date }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - const $item = cheerio.load(item) - const prev = programs[programs.length - 1] - let start = parseStart($item, date) - let stop = parseStop($item, date) - if (prev) { - if (start.isBefore(prev.start)) { - start = start.add(1, 'd') - } - prev.stop = start - if (stop.isBefore(prev.stop)) { - stop = stop.add(1, 'd') - } - } - programs.push({ - title: parseTitle($item), - description: parseDescription($item), - image: parseImage($item), - start, - stop - }) - }) - - return programs - }, - async channels() { - const html = await axios - .get('https://www.telsu.fi/') - .then(r => r.data) - .catch(console.log) - const $ = cheerio.load(html) - const items = $('.ch').toArray() - return items.map(item => { - const name = $(item).find('a').attr('title') - const site_id = $(item).attr('rel') - - return { - lang: 'fi', - site_id, - name - } - }) - } -} - -function parseTitle($item) { - return $item('h1 > b').text().trim() -} - -function parseDescription($item) { - return $item('.t > div').clone().children().remove().end().text().trim() -} - -function parseImage($item) { - const imgSrc = $item('.t > div > div.ps > a > img').attr('src') - - return imgSrc ? `https://www.telsu.fi${imgSrc}` : null -} - -function parseStart($item, date) { - const subtitle = $item('.h > h2').clone().children().remove().end().text().trim() - const [, HH, mm] = subtitle.match(/(\d{2})\.(\d{2}) - (\d{2})\.(\d{2})$/) || [null, null, null] - if (!HH || !mm) return null - - return dayjs.tz(`${date.format('YYYY-MM-DD')} ${HH}:${mm}`, 'YYYY-MM-DD HH:mm', 'Europe/Helsinki') -} - -function parseStop($item, date) { - const subtitle = $item('.h > h2').clone().children().remove().end().text().trim() - const [, HH, mm] = subtitle.match(/ - (\d{2})\.(\d{2})$/) || [null, null, null] - if (!HH || !mm) return null - - return dayjs.tz(`${date.format('YYYY-MM-DD')} ${HH}:${mm}`, 'YYYY-MM-DD HH:mm', 'Europe/Helsinki') -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('#res > div.dets').toArray() -} +const axios = require('axios') +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') + +dayjs.extend(utc) +dayjs.extend(timezone) + +module.exports = { + site: 'telsu.fi', + days: 2, + url: function ({ date, channel }) { + return `https://www.telsu.fi/${date.format('YYYYMMDD')}/${channel.site_id}` + }, + parser: function ({ content, date }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + const $item = cheerio.load(item) + const prev = programs[programs.length - 1] + let start = parseStart($item, date) + let stop = parseStop($item, date) + if (prev) { + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') + } + prev.stop = start + if (stop.isBefore(prev.stop)) { + stop = stop.add(1, 'd') + } + } + programs.push({ + title: parseTitle($item), + description: parseDescription($item), + image: parseImage($item), + start, + stop + }) + }) + + return programs + }, + async channels() { + const html = await axios + .get('https://www.telsu.fi/') + .then(r => r.data) + .catch(console.log) + const $ = cheerio.load(html) + const items = $('.ch').toArray() + return items.map(item => { + const name = $(item).find('a').attr('title') + const site_id = $(item).attr('rel') + + return { + lang: 'fi', + site_id, + name + } + }) + } +} + +function parseTitle($item) { + return $item('h1 > b').text().trim() +} + +function parseDescription($item) { + return $item('.t > div').clone().children().remove().end().text().trim() +} + +function parseImage($item) { + const imgSrc = $item('.t > div > div.ps > a > img').attr('src') + + return imgSrc ? `https://www.telsu.fi${imgSrc}` : null +} + +function parseStart($item, date) { + const subtitle = $item('.h > h2').clone().children().remove().end().text().trim() + const [, HH, mm] = subtitle.match(/(\d{2})\.(\d{2}) - (\d{2})\.(\d{2})$/) || [null, null, null] + if (!HH || !mm) return null + + return dayjs.tz(`${date.format('YYYY-MM-DD')} ${HH}:${mm}`, 'YYYY-MM-DD HH:mm', 'Europe/Helsinki') +} + +function parseStop($item, date) { + const subtitle = $item('.h > h2').clone().children().remove().end().text().trim() + const [, HH, mm] = subtitle.match(/ - (\d{2})\.(\d{2})$/) || [null, null, null] + if (!HH || !mm) return null + + return dayjs.tz(`${date.format('YYYY-MM-DD')} ${HH}:${mm}`, 'YYYY-MM-DD HH:mm', 'Europe/Helsinki') +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('#res > div.dets').toArray() +} diff --git a/sites/telsu.fi/telsu.fi.test.js b/sites/telsu.fi/telsu.fi.test.js index 991198990..dc83e74cc 100644 --- a/sites/telsu.fi/telsu.fi.test.js +++ b/sites/telsu.fi/telsu.fi.test.js @@ -1,44 +1,44 @@ -const { parser, url } = require('./telsu.fi.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-10-29', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'yle1', - xmltv_id: 'YleTV1.fi' -} - -it('can generate valid url', () => { - expect(url({ date, channel })).toBe('https://www.telsu.fi/20221029/yle1') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - const results = parser({ content, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2022-10-29T04:00:00.000Z', - stop: '2022-10-29T04:28:00.000Z', - title: 'Antiikkikaksintaistelu', - description: - 'Kausi 6, osa 5/12. Antiikkikaksintaistelu jatkuu Løkkenissä. Uusi taistelupari Rikke Fog ja Lasse Franck saavat kumpikin 10 000 kruunua ja viisi tuntia aikaa ostaa alueelta hyvää tavaraa halvalla.', - image: 'https://www.telsu.fi/s/antiikkikaksintaistelu_11713730.jpg' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')), - date - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./telsu.fi.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-10-29', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'yle1', + xmltv_id: 'YleTV1.fi' +} + +it('can generate valid url', () => { + expect(url({ date, channel })).toBe('https://www.telsu.fi/20221029/yle1') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + const results = parser({ content, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2022-10-29T04:00:00.000Z', + stop: '2022-10-29T04:28:00.000Z', + title: 'Antiikkikaksintaistelu', + description: + 'Kausi 6, osa 5/12. Antiikkikaksintaistelu jatkuu Løkkenissä. Uusi taistelupari Rikke Fog ja Lasse Franck saavat kumpikin 10 000 kruunua ja viisi tuntia aikaa ostaa alueelta hyvää tavaraa halvalla.', + image: 'https://www.telsu.fi/s/antiikkikaksintaistelu_11713730.jpg' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')), + date + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/thesportplus.com/thesportplus.com.config.js b/sites/thesportplus.com/thesportplus.com.config.js index dd48e5e2b..c5f0a4326 100644 --- a/sites/thesportplus.com/thesportplus.com.config.js +++ b/sites/thesportplus.com/thesportplus.com.config.js @@ -1,73 +1,73 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -const timezones = { - usa: 'America/New_York', - aus: 'Australia/Sydney', - euro: 'UTC' -} - -module.exports = { - site: 'thesportplus.com', - days: 2, - url({ channel, date }) { - return `https://www.thesportplus.com/schedule_${channel.site_id}.php?d=${date.format( - 'YYYY-MM-DD' - )}` - }, - parser({ content, date, channel }) { - const programs = [] - const items = parseItems(content) - items.forEach(item => { - const $item = cheerio.load(item) - const prev = programs[programs.length - 1] - let start = parseStart($item, date, channel) - if (!start) return - if (prev) { - if (start.isBefore(prev.start) && start.hour() < 12) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.add(1, 'h') - programs.push({ - title: parseTitle($item), - description: parseDescription($item), - start, - stop - }) - }) - - return programs - } -} - -function parseTitle($item) { - return $item('h5:last').text().trim() -} - -function parseDescription($item) { - return $item('p').text().trim() -} - -function parseStart($item, date, channel) { - const timezone = timezones[channel.site_id] - const time = $item('h4').text().trim() - const dateString = `${date.format('YYYY-MM-DD')} ${time}` - - return dayjs.tz(dateString, 'YYYY-MM-DD HH:mm', timezone) -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('.resume-item').toArray() -} +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +const timezones = { + usa: 'America/New_York', + aus: 'Australia/Sydney', + euro: 'UTC' +} + +module.exports = { + site: 'thesportplus.com', + days: 2, + url({ channel, date }) { + return `https://www.thesportplus.com/schedule_${channel.site_id}.php?d=${date.format( + 'YYYY-MM-DD' + )}` + }, + parser({ content, date, channel }) { + const programs = [] + const items = parseItems(content) + items.forEach(item => { + const $item = cheerio.load(item) + const prev = programs[programs.length - 1] + let start = parseStart($item, date, channel) + if (!start) return + if (prev) { + if (start.isBefore(prev.start) && start.hour() < 12) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.add(1, 'h') + programs.push({ + title: parseTitle($item), + description: parseDescription($item), + start, + stop + }) + }) + + return programs + } +} + +function parseTitle($item) { + return $item('h5:last').text().trim() +} + +function parseDescription($item) { + return $item('p').text().trim() +} + +function parseStart($item, date, channel) { + const timezone = timezones[channel.site_id] + const time = $item('h4').text().trim() + const dateString = `${date.format('YYYY-MM-DD')} ${time}` + + return dayjs.tz(dateString, 'YYYY-MM-DD HH:mm', timezone) +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('.resume-item').toArray() +} diff --git a/sites/thesportplus.com/thesportplus.com.test.js b/sites/thesportplus.com/thesportplus.com.test.js index ed431fda5..96bae2a57 100644 --- a/sites/thesportplus.com/thesportplus.com.test.js +++ b/sites/thesportplus.com/thesportplus.com.test.js @@ -1,50 +1,50 @@ -const { parser, url } = require('./thesportplus.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-01-19', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'usa', - xmltv_id: 'SportPlusUSA.us' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe('https://www.thesportplus.com/schedule_usa.php?d=2025-01-19') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - const results = parser({ content, date, channel }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(13) - expect(results[0]).toMatchObject({ - start: '2025-01-19T06:00:00.000Z', - stop: '2025-01-19T08:00:00.000Z', - title: 'ASTERAS vs ATROMITOS', - description: 'Super League Season 24-25 MD 4' - }) - expect(results[12]).toMatchObject({ - start: '2025-01-20T04:00:00.000Z', - stop: '2025-01-20T05:00:00.000Z', - title: 'SPORTSHOW', - description: 'Super League' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - date, - channel, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) - }) - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./thesportplus.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-01-19', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'usa', + xmltv_id: 'SportPlusUSA.us' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe('https://www.thesportplus.com/schedule_usa.php?d=2025-01-19') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + const results = parser({ content, date, channel }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(13) + expect(results[0]).toMatchObject({ + start: '2025-01-19T06:00:00.000Z', + stop: '2025-01-19T08:00:00.000Z', + title: 'ASTERAS vs ATROMITOS', + description: 'Super League Season 24-25 MD 4' + }) + expect(results[12]).toMatchObject({ + start: '2025-01-20T04:00:00.000Z', + stop: '2025-01-20T05:00:00.000Z', + title: 'SPORTSHOW', + description: 'Super League' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + date, + channel, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) + }) + expect(results).toMatchObject([]) +}) diff --git a/sites/tivie.id/tivie.id.test.js b/sites/tivie.id/tivie.id.test.js index dcc4e0ee5..11e47c056 100644 --- a/sites/tivie.id/tivie.id.test.js +++ b/sites/tivie.id/tivie.id.test.js @@ -1,75 +1,75 @@ -const { parser, url } = require('./tivie.id.config') -const fs = require('fs') -const path = require('path') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -const date = dayjs.utc('2024-12-31').startOf('d') -const channel = { - site_id: 'axn', - xmltv_id: 'AXN.id', - lang: 'id' -} - -axios.get.mockImplementation(url => { - const urls = { - 'https://tivie.id/film/white-house-down-nwzDnwz9nAv6': 'program01.html', - 'https://tivie.id/program/hudson-rex-s6-e14-nwzDnwvBmQr9': 'program02.html' - } - let data = '' - if (urls[url] !== undefined) { - data = fs.readFileSync(path.join(__dirname, '__data__', urls[url])).toString() - } - return Promise.resolve({ data }) -}) - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe('https://tivie.id/channel/axn/20241231') -}) - -it('can parse response', async () => { - const content = fs.readFileSync(path.join(__dirname, '__data__', 'content.html')) - const results = (await parser({ date, content, channel })).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(27) - expect(results[0]).toMatchObject({ - start: '2024-12-30T17:00:00.000Z', - stop: '2024-12-30T17:05:00.000Z', - title: 'White House Down', - description: - 'Saat melakukan tur di Gedung Putih bersama putrinya yang masih kecil, seorang perwira polisi beraksi untuk melindungi anaknya dan presiden dari sekelompok penjajah paramiliter bersenjata lengkap.', - image: - 'https://i0.wp.com/is3.cloudhost.id/tivie/poster/2023/09/65116c78791c2-1695640694.jpg?resize=480,270' - }) - expect(results[2]).toMatchObject({ - start: '2024-12-30T18:00:00.000Z', - stop: '2024-12-30T18:55:00.000Z', - title: 'Hudson & Rex S6, Ep. 14', - description: - 'Saat guru musik Jesse terbunuh di studio rekamannya, Charlie dan Rex menghubungkan kejahatan tersebut dengan pembunuhan yang tampaknya tak ada hubungannya.', - image: - 'https://i0.wp.com/is3.cloudhost.id/tivie/poster/2024/07/668b7ced47b25-1720417517.jpg?resize=480,270', - season: 6, - episode: 14 - }) -}) - -it('can handle empty guide', async () => { - const results = await parser({ - date, - channel, - content: '' - }) - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./tivie.id.config') +const fs = require('fs') +const path = require('path') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.utc('2024-12-31').startOf('d') +const channel = { + site_id: 'axn', + xmltv_id: 'AXN.id', + lang: 'id' +} + +axios.get.mockImplementation(url => { + const urls = { + 'https://tivie.id/film/white-house-down-nwzDnwz9nAv6': 'program01.html', + 'https://tivie.id/program/hudson-rex-s6-e14-nwzDnwvBmQr9': 'program02.html' + } + let data = '' + if (urls[url] !== undefined) { + data = fs.readFileSync(path.join(__dirname, '__data__', urls[url])).toString() + } + return Promise.resolve({ data }) +}) + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe('https://tivie.id/channel/axn/20241231') +}) + +it('can parse response', async () => { + const content = fs.readFileSync(path.join(__dirname, '__data__', 'content.html')) + const results = (await parser({ date, content, channel })).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(27) + expect(results[0]).toMatchObject({ + start: '2024-12-30T17:00:00.000Z', + stop: '2024-12-30T17:05:00.000Z', + title: 'White House Down', + description: + 'Saat melakukan tur di Gedung Putih bersama putrinya yang masih kecil, seorang perwira polisi beraksi untuk melindungi anaknya dan presiden dari sekelompok penjajah paramiliter bersenjata lengkap.', + image: + 'https://i0.wp.com/is3.cloudhost.id/tivie/poster/2023/09/65116c78791c2-1695640694.jpg?resize=480,270' + }) + expect(results[2]).toMatchObject({ + start: '2024-12-30T18:00:00.000Z', + stop: '2024-12-30T18:55:00.000Z', + title: 'Hudson & Rex S6, Ep. 14', + description: + 'Saat guru musik Jesse terbunuh di studio rekamannya, Charlie dan Rex menghubungkan kejahatan tersebut dengan pembunuhan yang tampaknya tak ada hubungannya.', + image: + 'https://i0.wp.com/is3.cloudhost.id/tivie/poster/2024/07/668b7ced47b25-1720417517.jpg?resize=480,270', + season: 6, + episode: 14 + }) +}) + +it('can handle empty guide', async () => { + const results = await parser({ + date, + channel, + content: '' + }) + expect(results).toMatchObject([]) +}) diff --git a/sites/tivu.tv/tivu.tv.config.js b/sites/tivu.tv/tivu.tv.config.js index 7849be39a..dfec635dc 100644 --- a/sites/tivu.tv/tivu.tv.config.js +++ b/sites/tivu.tv/tivu.tv.config.js @@ -1,89 +1,89 @@ -const cheerio = require('cheerio') -const { DateTime } = require('luxon') - -module.exports = { - site: 'tivu.tv', - days: 2, - request: { - cache: { - ttl: 60 * 60 * 1000 // 1 hour - } - }, - url({ date }) { - const diff = date.diff(DateTime.now().toUTC().startOf('day'), 'd') - - return `https://www.tivu.tv/epg_ajax_sat.aspx?d=${diff}` - }, - parser: function ({ content, channel, date }) { - let programs = [] - const items = parseItems(content, channel, date) - items.forEach(item => { - const $item = cheerio.load(item) - const prev = programs[programs.length - 1] - let start = parseStart($item, date) - if (!start) return - if (prev) { - if (start < prev.start) { - start = start.plus({ days: 1 }) - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.plus({ minutes: 30 }) - programs.push({ - title: parseTitle($item), - start, - stop - }) - }) - - return programs - }, - async channels() { - const axios = require('axios') - const html = await axios - .get('https://www.tivu.tv/epg_ajax_sat.aspx?d=0') - .then(r => r.data) - .catch(console.log) - - let channels = [] - - const $ = cheerio.load(html) - $('.q').each((i, el) => { - const site_id = $(el).attr('id') - const name = $(el).find('a').first().data('channel') - - if (!name) return - - channels.push({ - lang: 'it', - site_id, - name - }) - }) - - return channels - } -} - -function parseTitle($item) { - const [title] = $item('a').html().split('
    ') - - return title -} - -function parseStart($item, date) { - const [, , time] = $item('a').html().split('
    ') - if (!time) return null - - return DateTime.fromFormat(`${date.format('YYYY-MM-DD')} ${time}`, 'yyyy-MM-dd HH:mm', { - zone: 'Europe/Rome' - }).toUTC() -} - -function parseItems(content, channel) { - if (!content) return [] - const $ = cheerio.load(content) - - return $(`.q[id="${channel.site_id}"] > .p`).toArray() -} +const cheerio = require('cheerio') +const { DateTime } = require('luxon') + +module.exports = { + site: 'tivu.tv', + days: 2, + request: { + cache: { + ttl: 60 * 60 * 1000 // 1 hour + } + }, + url({ date }) { + const diff = date.diff(DateTime.now().toUTC().startOf('day'), 'd') + + return `https://www.tivu.tv/epg_ajax_sat.aspx?d=${diff}` + }, + parser: function ({ content, channel, date }) { + let programs = [] + const items = parseItems(content, channel, date) + items.forEach(item => { + const $item = cheerio.load(item) + const prev = programs[programs.length - 1] + let start = parseStart($item, date) + if (!start) return + if (prev) { + if (start < prev.start) { + start = start.plus({ days: 1 }) + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.plus({ minutes: 30 }) + programs.push({ + title: parseTitle($item), + start, + stop + }) + }) + + return programs + }, + async channels() { + const axios = require('axios') + const html = await axios + .get('https://www.tivu.tv/epg_ajax_sat.aspx?d=0') + .then(r => r.data) + .catch(console.log) + + let channels = [] + + const $ = cheerio.load(html) + $('.q').each((i, el) => { + const site_id = $(el).attr('id') + const name = $(el).find('a').first().data('channel') + + if (!name) return + + channels.push({ + lang: 'it', + site_id, + name + }) + }) + + return channels + } +} + +function parseTitle($item) { + const [title] = $item('a').html().split('
    ') + + return title +} + +function parseStart($item, date) { + const [, , time] = $item('a').html().split('
    ') + if (!time) return null + + return DateTime.fromFormat(`${date.format('YYYY-MM-DD')} ${time}`, 'yyyy-MM-dd HH:mm', { + zone: 'Europe/Rome' + }).toUTC() +} + +function parseItems(content, channel) { + if (!content) return [] + const $ = cheerio.load(content) + + return $(`.q[id="${channel.site_id}"] > .p`).toArray() +} diff --git a/sites/tivu.tv/tivu.tv.test.js b/sites/tivu.tv/tivu.tv.test.js index b243072a1..2bc57af13 100644 --- a/sites/tivu.tv/tivu.tv.test.js +++ b/sites/tivu.tv/tivu.tv.test.js @@ -1,56 +1,56 @@ -const { parser, url } = require('./tivu.tv.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -const channel = { - site_id: '62', - xmltv_id: 'Rai1HD.it' -} - -it('can generate valid url for today', () => { - const date = dayjs.utc().startOf('d') - expect(url({ date })).toBe('https://www.tivu.tv/epg_ajax_sat.aspx?d=0') -}) - -it('can generate valid url for tomorrow', () => { - const date = dayjs.utc().startOf('d').add(1, 'd') - expect(url({ date })).toBe('https://www.tivu.tv/epg_ajax_sat.aspx?d=1') -}) - -it('can parse response', () => { - const date = dayjs.utc('2022-10-04', 'YYYY-MM-DD').startOf('d') - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - - let results = parser({ content, channel, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2022-10-03T22:02:00.000Z', - stop: '2022-10-03T22:45:00.000Z', - title: 'Cose Nostre - La figlia del boss' - }) - - expect(results[43]).toMatchObject({ - start: '2022-10-05T04:58:00.000Z', - stop: '2022-10-05T05:28:00.000Z', - title: 'Tgunomattina - in collaborazione con day' - }) -}) - -it('can handle empty guide', () => { - const date = dayjs.utc('2022-10-04', 'YYYY-MM-DD').startOf('d') - const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) - const result = parser({ content, channel, date }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./tivu.tv.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const channel = { + site_id: '62', + xmltv_id: 'Rai1HD.it' +} + +it('can generate valid url for today', () => { + const date = dayjs.utc().startOf('d') + expect(url({ date })).toBe('https://www.tivu.tv/epg_ajax_sat.aspx?d=0') +}) + +it('can generate valid url for tomorrow', () => { + const date = dayjs.utc().startOf('d').add(1, 'd') + expect(url({ date })).toBe('https://www.tivu.tv/epg_ajax_sat.aspx?d=1') +}) + +it('can parse response', () => { + const date = dayjs.utc('2022-10-04', 'YYYY-MM-DD').startOf('d') + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + + let results = parser({ content, channel, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2022-10-03T22:02:00.000Z', + stop: '2022-10-03T22:45:00.000Z', + title: 'Cose Nostre - La figlia del boss' + }) + + expect(results[43]).toMatchObject({ + start: '2022-10-05T04:58:00.000Z', + stop: '2022-10-05T05:28:00.000Z', + title: 'Tgunomattina - in collaborazione con day' + }) +}) + +it('can handle empty guide', () => { + const date = dayjs.utc('2022-10-04', 'YYYY-MM-DD').startOf('d') + const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) + const result = parser({ content, channel, date }) + expect(result).toMatchObject([]) +}) diff --git a/sites/toonamiaftermath.com/toonamiaftermath.com.config.js b/sites/toonamiaftermath.com/toonamiaftermath.com.config.js index 4be2308d8..a19357332 100644 --- a/sites/toonamiaftermath.com/toonamiaftermath.com.config.js +++ b/sites/toonamiaftermath.com/toonamiaftermath.com.config.js @@ -1,60 +1,60 @@ -process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0' - -const dayjs = require('dayjs') -const axios = require('axios') - -const API_ENDPOINT = 'https://api.toonamiaftermath.com' - -module.exports = { - site: 'toonamiaftermath.com', - days: 3, - async url({ channel, date }) { - const playlists = await axios - .get( - `${API_ENDPOINT}/playlists?scheduleName=${channel.site_id}&startDate=${date - .add(1, 'd') - .toJSON()}&thisWeek=true&weekStartDay=monday` - ) - .then(r => r.data) - .catch(console.error) - - const playlist = playlists.find(p => date.isSame(p.startDate, 'day')) - - return `${API_ENDPOINT}/playlist?id=${playlist._id}&addInfo=true` - }, - parser({ content }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - programs.push({ - title: item.name, - sub_title: parseEpisode(item), - image: parseImage(item), - start: dayjs(item.startDate), - stop: dayjs(item.endDate) - }) - }) - - return programs - } -} - -function parseItems(content) { - if (!content) return [] - const data = JSON.parse(content) - if (!data || !data.playlist) return [] - - return data.playlist.blocks.reduce((acc, curr) => { - acc = acc.concat(curr.mediaList) - - return acc - }, []) -} - -function parseEpisode(item) { - return item && item.info && item.info.episode ? item.info.episode : null -} - -function parseImage(item) { - return item && item.info && item.info.image ? item.info.image : null -} +process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0' + +const dayjs = require('dayjs') +const axios = require('axios') + +const API_ENDPOINT = 'https://api.toonamiaftermath.com' + +module.exports = { + site: 'toonamiaftermath.com', + days: 3, + async url({ channel, date }) { + const playlists = await axios + .get( + `${API_ENDPOINT}/playlists?scheduleName=${channel.site_id}&startDate=${date + .add(1, 'd') + .toJSON()}&thisWeek=true&weekStartDay=monday` + ) + .then(r => r.data) + .catch(console.error) + + const playlist = playlists.find(p => date.isSame(p.startDate, 'day')) + + return `${API_ENDPOINT}/playlist?id=${playlist._id}&addInfo=true` + }, + parser({ content }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + programs.push({ + title: item.name, + sub_title: parseEpisode(item), + image: parseImage(item), + start: dayjs(item.startDate), + stop: dayjs(item.endDate) + }) + }) + + return programs + } +} + +function parseItems(content) { + if (!content) return [] + const data = JSON.parse(content) + if (!data || !data.playlist) return [] + + return data.playlist.blocks.reduce((acc, curr) => { + acc = acc.concat(curr.mediaList) + + return acc + }, []) +} + +function parseEpisode(item) { + return item && item.info && item.info.episode ? item.info.episode : null +} + +function parseImage(item) { + return item && item.info && item.info.image ? item.info.image : null +} diff --git a/sites/toonamiaftermath.com/toonamiaftermath.com.test.js b/sites/toonamiaftermath.com/toonamiaftermath.com.test.js index 139a557bc..cc252b7c1 100644 --- a/sites/toonamiaftermath.com/toonamiaftermath.com.test.js +++ b/sites/toonamiaftermath.com/toonamiaftermath.com.test.js @@ -1,61 +1,61 @@ -const { parser, url } = require('./toonamiaftermath.com.config.js') -const fs = require('fs') -const path = require('path') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -const API_ENDPOINT = 'https://api.toonamiaftermath.com' - -const date = dayjs.utc('2022-11-29', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'Toonami Aftermath EST', - xmltv_id: 'ToonamiAftermathEast.us' -} - -it('can generate valid url', async () => { - axios.get.mockImplementation(url => { - if ( - url === - `${API_ENDPOINT}/playlists?scheduleName=Toonami Aftermath EST&startDate=2022-11-30T00:00:00.000Z&thisWeek=true&weekStartDay=monday` - ) { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/playlists.json'))) - }) - } else { - return Promise.resolve({ data: '' }) - } - }) - - const result = await url({ channel, date }) - - expect(result).toBe(`${API_ENDPOINT}/playlist?id=635fbd8117f6824d953a216e&addInfo=true`) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - let results = parser({ content, channel, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(62) - expect(results[0]).toMatchObject({ - start: '2022-11-29T17:00:30.231Z', - stop: '2022-11-29T17:20:54.031Z', - title: 'X-Men', - sub_title: 'Reunion (Part 1)', - image: 'https://i.imgur.com/ZSZ0x1m.gif' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ content: '', date }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./toonamiaftermath.com.config.js') +const fs = require('fs') +const path = require('path') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const API_ENDPOINT = 'https://api.toonamiaftermath.com' + +const date = dayjs.utc('2022-11-29', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'Toonami Aftermath EST', + xmltv_id: 'ToonamiAftermathEast.us' +} + +it('can generate valid url', async () => { + axios.get.mockImplementation(url => { + if ( + url === + `${API_ENDPOINT}/playlists?scheduleName=Toonami Aftermath EST&startDate=2022-11-30T00:00:00.000Z&thisWeek=true&weekStartDay=monday` + ) { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/playlists.json'))) + }) + } else { + return Promise.resolve({ data: '' }) + } + }) + + const result = await url({ channel, date }) + + expect(result).toBe(`${API_ENDPOINT}/playlist?id=635fbd8117f6824d953a216e&addInfo=true`) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + let results = parser({ content, channel, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(62) + expect(results[0]).toMatchObject({ + start: '2022-11-29T17:00:30.231Z', + stop: '2022-11-29T17:20:54.031Z', + title: 'X-Men', + sub_title: 'Reunion (Part 1)', + image: 'https://i.imgur.com/ZSZ0x1m.gif' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ content: '', date }) + expect(result).toMatchObject([]) +}) diff --git a/sites/turksatkablo.com.tr/turksatkablo.com.tr.config.js b/sites/turksatkablo.com.tr/turksatkablo.com.tr.config.js index 12edc6239..8938beeeb 100644 --- a/sites/turksatkablo.com.tr/turksatkablo.com.tr.config.js +++ b/sites/turksatkablo.com.tr/turksatkablo.com.tr.config.js @@ -1,88 +1,88 @@ -const { DateTime } = require('luxon') - -module.exports = { - site: 'turksatkablo.com.tr', - days: 2, - url({ date }) { - const dayOfMonth = date.format('DD') // Get the current day of the month (01-31) - - return `https://www.turksatkablo.com.tr/userUpload/EPG/${dayOfMonth}.json?_=${date.valueOf()}` - }, - request: { - timeout: 60000, - cache: { - ttl: 60 * 60 * 1000 // 1 hour - } - }, - parser: function ({ content, channel, date }) { - let programs = [] - const items = parseItems(content, channel) - items.forEach(item => { - const prev = programs[programs.length - 1] - let start = parseStart(item, date) - if (prev && start < prev.start) { - start = start.plus({ days: 1 }) - date = date.add(1, 'd') - } - let stop = parseStop(item, date) - if (prev && stop < start) { - stop = stop.plus({ days: 1 }) - date = date.add(1, 'd') - } - programs.push({ - title: item.b, - start, - stop - }) - }) - - return programs - }, - async channels() { - const axios = require('axios') - const dayjs = require('dayjs') - const dayOfMonth = dayjs().format('DD') - - const data = await axios - .get(`https://www.turksatkablo.com.tr/userUpload/EPG/${dayOfMonth}.json`) - .then(r => r.data) - .catch(console.log) - - let channels = [] - - data.k.forEach(item => { - channels.push({ - lang: 'tr', - site_id: item.x, - name: item.n - }) - }) - - return channels - } -} - -function parseStart(item, date) { - const time = `${date.format('YYYY-MM-DD')} ${item.c}` - - return DateTime.fromFormat(time, 'yyyy-MM-dd HH:mm', { zone: 'Europe/Istanbul' }).toUTC() -} - -function parseStop(item, date) { - const time = `${date.format('YYYY-MM-DD')} ${item.d}` - - return DateTime.fromFormat(time, 'yyyy-MM-dd HH:mm', { zone: 'Europe/Istanbul' }).toUTC() -} - -function parseItems(content, channel) { - let parsed - try { - parsed = JSON.parse(content) - } catch { - return [] - } - if (!parsed || !parsed.k) return [] - const data = parsed.k.find(c => c.x == channel.site_id) - - return data ? data.p : [] -} +const { DateTime } = require('luxon') + +module.exports = { + site: 'turksatkablo.com.tr', + days: 2, + url({ date }) { + const dayOfMonth = date.format('DD') // Get the current day of the month (01-31) + + return `https://www.turksatkablo.com.tr/userUpload/EPG/${dayOfMonth}.json?_=${date.valueOf()}` + }, + request: { + timeout: 60000, + cache: { + ttl: 60 * 60 * 1000 // 1 hour + } + }, + parser: function ({ content, channel, date }) { + let programs = [] + const items = parseItems(content, channel) + items.forEach(item => { + const prev = programs[programs.length - 1] + let start = parseStart(item, date) + if (prev && start < prev.start) { + start = start.plus({ days: 1 }) + date = date.add(1, 'd') + } + let stop = parseStop(item, date) + if (prev && stop < start) { + stop = stop.plus({ days: 1 }) + date = date.add(1, 'd') + } + programs.push({ + title: item.b, + start, + stop + }) + }) + + return programs + }, + async channels() { + const axios = require('axios') + const dayjs = require('dayjs') + const dayOfMonth = dayjs().format('DD') + + const data = await axios + .get(`https://www.turksatkablo.com.tr/userUpload/EPG/${dayOfMonth}.json`) + .then(r => r.data) + .catch(console.log) + + let channels = [] + + data.k.forEach(item => { + channels.push({ + lang: 'tr', + site_id: item.x, + name: item.n + }) + }) + + return channels + } +} + +function parseStart(item, date) { + const time = `${date.format('YYYY-MM-DD')} ${item.c}` + + return DateTime.fromFormat(time, 'yyyy-MM-dd HH:mm', { zone: 'Europe/Istanbul' }).toUTC() +} + +function parseStop(item, date) { + const time = `${date.format('YYYY-MM-DD')} ${item.d}` + + return DateTime.fromFormat(time, 'yyyy-MM-dd HH:mm', { zone: 'Europe/Istanbul' }).toUTC() +} + +function parseItems(content, channel) { + let parsed + try { + parsed = JSON.parse(content) + } catch { + return [] + } + if (!parsed || !parsed.k) return [] + const data = parsed.k.find(c => c.x == channel.site_id) + + return data ? data.p : [] +} diff --git a/sites/turksatkablo.com.tr/turksatkablo.com.tr.test.js b/sites/turksatkablo.com.tr/turksatkablo.com.tr.test.js index 042efebf8..0342a06f8 100644 --- a/sites/turksatkablo.com.tr/turksatkablo.com.tr.test.js +++ b/sites/turksatkablo.com.tr/turksatkablo.com.tr.test.js @@ -1,48 +1,48 @@ -const { parser, url } = require('./turksatkablo.com.tr.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-05-30', 'YYYY-MM-DD').startOf('d') -const channel = { site_id: '1908' } - -it('can generate valid url', () => { - const result = url({ date }) - - expect(result).toBe('https://www.turksatkablo.com.tr/userUpload/EPG/30.json?_=1748563200000') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const results = parser({ date, channel, content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(14) - expect(results[0]).toMatchObject({ - title: '-', - start: '2025-05-29T21:00:00.000Z', - stop: '2025-05-29T22:30:00.000Z' - }) - expect(results[1]).toMatchObject({ - title: 'Şeytanın Evi', - start: '2025-05-29T22:30:00.000Z', - stop: '2025-05-30T00:00:00.000Z' - }) -}) - -it('can handle empty guide', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) - const result = parser({ - date, - channel, - content - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./turksatkablo.com.tr.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-05-30', 'YYYY-MM-DD').startOf('d') +const channel = { site_id: '1908' } + +it('can generate valid url', () => { + const result = url({ date }) + + expect(result).toBe('https://www.turksatkablo.com.tr/userUpload/EPG/30.json?_=1748563200000') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const results = parser({ date, channel, content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(14) + expect(results[0]).toMatchObject({ + title: '-', + start: '2025-05-29T21:00:00.000Z', + stop: '2025-05-29T22:30:00.000Z' + }) + expect(results[1]).toMatchObject({ + title: 'Şeytanın Evi', + start: '2025-05-29T22:30:00.000Z', + stop: '2025-05-30T00:00:00.000Z' + }) +}) + +it('can handle empty guide', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) + const result = parser({ + date, + channel, + content + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/tv-programme.telecablesat.fr/tv-programme.telecablesat.fr.config.js b/sites/tv-programme.telecablesat.fr/tv-programme.telecablesat.fr.config.js index 87f976933..041caab33 100644 --- a/sites/tv-programme.telecablesat.fr/tv-programme.telecablesat.fr.config.js +++ b/sites/tv-programme.telecablesat.fr/tv-programme.telecablesat.fr.config.js @@ -1,115 +1,115 @@ -const cheerio = require('cheerio') -const axios = require('axios') -const { DateTime } = require('luxon') - -const API_ENDPOINT = 'https://tv-programme.telecablesat.fr/chaine' -const headers = { - 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0' -} - -module.exports = { - site: 'tv-programme.telecablesat.fr', - days: 2, - delay: 5000, - request: { - headers - }, - url: function ({ channel, date }) { - return `${API_ENDPOINT}/${channel.site_id}/index.html?date=${date.format( - 'YYYY-MM-DD' - )}&period=morning` - }, - async parser({ content, date, channel }) { - let programs = [] - let items = parseItems(content) - if (!items.length) return programs - const url = `${API_ENDPOINT}/${channel.site_id}/index.html` - const promises = [ - axios.get(`${url}?date=${date.format('YYYY-MM-DD')}&period=noon`, { headers }), - axios.get(`${url}?date=${date.format('YYYY-MM-DD')}&period=afternoon`, { headers }) - ] - await Promise.allSettled(promises).then(results => { - results.forEach(r => { - if (r.status === 'fulfilled') { - items = items.concat(parseItems(r.value.data)) - } - }) - }) - for (let item of items) { - const prev = programs[programs.length - 1] - const $item = cheerio.load(item) - let start = parseStart($item, date) - if (prev) { - if (start < prev.start) { - start = start.plus({ days: 1 }) - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.plus({ hours: 1 }) - programs.push({ - title: parseTitle($item), - description: parseDescription($item), - image: parseImage($item), - start, - stop - }) - } - - return programs - }, - async channels() { - const data = await axios - .get('https://tv-programme.telecablesat.fr/', { headers }) - .then(r => r.data) - .catch(console.log) - - const $ = cheerio.load(data) - const items = $( - '#ptgv_left > section.main > div > div > div:nth-child(1) > div > div > div.linker.with_search > div.inside > div.scroller > a' - ).toArray() - - return items.map(item => { - const $item = cheerio.load(item) - const link = $item('*').attr('href') - const [, site_id] = link.match(/\/chaine\/(\d+)\//) || [null, null] - const name = $item('*').text().trim() - return { - lang: 'fr', - site_id, - name - } - }) - } -} - -function parseStart($item, date) { - const timeString = $item('.schedule-hour').text() - if (!timeString) return null - - return DateTime.fromFormat(`${date.format('YYYY-MM-DD')} ${timeString}`, 'yyyy-MM-dd HH:mm', { - zone: 'Europe/Paris' - }).toUTC() -} - -function parseImage($item) { - const imgSrc = $item('img').attr('src') - - return imgSrc ? `https:${imgSrc}` : null -} - -function parseTitle($item) { - return $item('div.item-content > div.title-left').text().trim() -} - -function parseDescription($item) { - return $item('div.item-content > p').text() -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $( - '#ptgv_left > div.container > div.row.no-gutter > div.col-md-8 > div > div > div > div > div > div > div.news' - ).toArray() -} +const cheerio = require('cheerio') +const axios = require('axios') +const { DateTime } = require('luxon') + +const API_ENDPOINT = 'https://tv-programme.telecablesat.fr/chaine' +const headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0' +} + +module.exports = { + site: 'tv-programme.telecablesat.fr', + days: 2, + delay: 5000, + request: { + headers + }, + url: function ({ channel, date }) { + return `${API_ENDPOINT}/${channel.site_id}/index.html?date=${date.format( + 'YYYY-MM-DD' + )}&period=morning` + }, + async parser({ content, date, channel }) { + let programs = [] + let items = parseItems(content) + if (!items.length) return programs + const url = `${API_ENDPOINT}/${channel.site_id}/index.html` + const promises = [ + axios.get(`${url}?date=${date.format('YYYY-MM-DD')}&period=noon`, { headers }), + axios.get(`${url}?date=${date.format('YYYY-MM-DD')}&period=afternoon`, { headers }) + ] + await Promise.allSettled(promises).then(results => { + results.forEach(r => { + if (r.status === 'fulfilled') { + items = items.concat(parseItems(r.value.data)) + } + }) + }) + for (let item of items) { + const prev = programs[programs.length - 1] + const $item = cheerio.load(item) + let start = parseStart($item, date) + if (prev) { + if (start < prev.start) { + start = start.plus({ days: 1 }) + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.plus({ hours: 1 }) + programs.push({ + title: parseTitle($item), + description: parseDescription($item), + image: parseImage($item), + start, + stop + }) + } + + return programs + }, + async channels() { + const data = await axios + .get('https://tv-programme.telecablesat.fr/', { headers }) + .then(r => r.data) + .catch(console.log) + + const $ = cheerio.load(data) + const items = $( + '#ptgv_left > section.main > div > div > div:nth-child(1) > div > div > div.linker.with_search > div.inside > div.scroller > a' + ).toArray() + + return items.map(item => { + const $item = cheerio.load(item) + const link = $item('*').attr('href') + const [, site_id] = link.match(/\/chaine\/(\d+)\//) || [null, null] + const name = $item('*').text().trim() + return { + lang: 'fr', + site_id, + name + } + }) + } +} + +function parseStart($item, date) { + const timeString = $item('.schedule-hour').text() + if (!timeString) return null + + return DateTime.fromFormat(`${date.format('YYYY-MM-DD')} ${timeString}`, 'yyyy-MM-dd HH:mm', { + zone: 'Europe/Paris' + }).toUTC() +} + +function parseImage($item) { + const imgSrc = $item('img').attr('src') + + return imgSrc ? `https:${imgSrc}` : null +} + +function parseTitle($item) { + return $item('div.item-content > div.title-left').text().trim() +} + +function parseDescription($item) { + return $item('div.item-content > p').text() +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $( + '#ptgv_left > div.container > div.row.no-gutter > div.col-md-8 > div > div > div > div > div > div > div.news' + ).toArray() +} diff --git a/sites/tv-programme.telecablesat.fr/tv-programme.telecablesat.fr.test.js b/sites/tv-programme.telecablesat.fr/tv-programme.telecablesat.fr.test.js index 7a0af7928..2cf57d250 100644 --- a/sites/tv-programme.telecablesat.fr/tv-programme.telecablesat.fr.test.js +++ b/sites/tv-programme.telecablesat.fr/tv-programme.telecablesat.fr.test.js @@ -1,96 +1,96 @@ -const { parser, url, request } = require('./tv-programme.telecablesat.fr.config.js') -const axios = require('axios') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-11-30', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '13', - xmltv_id: 'DasErste.de' -} -const headers = { - 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0' -} - -jest.mock('axios') - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://tv-programme.telecablesat.fr/chaine/13/index.html?date=2023-11-30&period=morning' - ) -}) - -it('can generate valid request headers', () => { - expect(request.headers).toMatchObject(headers) -}) - -it('can parse response', async () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_morning.html')) - - axios.get.mockImplementation((url, config) => { - if ( - url === - 'https://tv-programme.telecablesat.fr/chaine/13/index.html?date=2023-11-30&period=noon' && - JSON.stringify(config.headers) === JSON.stringify(headers) - ) { - return Promise.resolve({ - data: fs.readFileSync(path.resolve(__dirname, '__data__/content_noon.html')) - }) - } else if ( - url === - 'https://tv-programme.telecablesat.fr/chaine/13/index.html?date=2023-11-30&period=afternoon' && - JSON.stringify(config.headers) === JSON.stringify(headers) - ) { - return Promise.resolve({ - data: fs.readFileSync(path.resolve(__dirname, '__data__/content_afternoon.html')) - }) - } else { - return Promise.resolve({ data: '' }) - } - }) - - let results = await parser({ content, channel, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2023-11-30T08:00:00.000Z', - stop: '2023-11-30T08:05:00.000Z', - title: 'Tagesschau', - description: - 'Die Tagesschau ist eine Institution in der deutschen Fernsehlandschaft. Seit 1952 wird kurz und bündig von aktuellen Geschehnissen in Deutschland und der Welt berichtet. Bis heute ist die Redaktion der sachlichen Berichterstattung treu geblieben und...', - image: - 'https://tv.cdnartwhere.eu/cache/i2/Dc5BDoMgEADAv3Cuwoqy4Fu4LLho24hEaNK06d_rcW7zFYEqi1lsrZU6e-nlXrqyHe2oXVxyT5_Xybys3GduXsYjN7pnPqdkI0CkJbk4gnKWMQFAQLQUtHZesuEwOgWa7DCkKV4cGFEBG0eQrCJSwY3YP8oqbmKn-rwexuBb20n8_g.jpg' - }) - - expect(results[36]).toMatchObject({ - start: '2023-12-01T04:30:00.000Z', - stop: '2023-12-01T05:30:00.000Z', - title: 'ZDF-Morgenmagazin', - description: 'Für einen guten Start in den Tag', - image: - 'https://tv.cdnartwhere.eu/cache/i2/Dc5BDoMgEADAv3CuIiIu-BYuu7Bo24hEaNK06d_rbY7zFYSVxSK21kpdvPRyL13ZjnbULsTc4-d1MseV-8zNy3DkhvfMp0k2KBUwJhcmNTjLkJRSBGCRtHZe2gQTYXRhJNCoL1NyyiTiSI6IhtHADOT6R1nFTexYn9djnuGtrRG_Pw.jpg' - }) -}) - -it('can handle empty guide', done => { - parser({ - content: - ' ', - date, - channel - }) - .then(result => { - expect(result).toMatchObject([]) - done() - }) - .catch(done) -}) +const { parser, url, request } = require('./tv-programme.telecablesat.fr.config.js') +const axios = require('axios') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-11-30', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '13', + xmltv_id: 'DasErste.de' +} +const headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0' +} + +jest.mock('axios') + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://tv-programme.telecablesat.fr/chaine/13/index.html?date=2023-11-30&period=morning' + ) +}) + +it('can generate valid request headers', () => { + expect(request.headers).toMatchObject(headers) +}) + +it('can parse response', async () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_morning.html')) + + axios.get.mockImplementation((url, config) => { + if ( + url === + 'https://tv-programme.telecablesat.fr/chaine/13/index.html?date=2023-11-30&period=noon' && + JSON.stringify(config.headers) === JSON.stringify(headers) + ) { + return Promise.resolve({ + data: fs.readFileSync(path.resolve(__dirname, '__data__/content_noon.html')) + }) + } else if ( + url === + 'https://tv-programme.telecablesat.fr/chaine/13/index.html?date=2023-11-30&period=afternoon' && + JSON.stringify(config.headers) === JSON.stringify(headers) + ) { + return Promise.resolve({ + data: fs.readFileSync(path.resolve(__dirname, '__data__/content_afternoon.html')) + }) + } else { + return Promise.resolve({ data: '' }) + } + }) + + let results = await parser({ content, channel, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2023-11-30T08:00:00.000Z', + stop: '2023-11-30T08:05:00.000Z', + title: 'Tagesschau', + description: + 'Die Tagesschau ist eine Institution in der deutschen Fernsehlandschaft. Seit 1952 wird kurz und bündig von aktuellen Geschehnissen in Deutschland und der Welt berichtet. Bis heute ist die Redaktion der sachlichen Berichterstattung treu geblieben und...', + image: + 'https://tv.cdnartwhere.eu/cache/i2/Dc5BDoMgEADAv3Cuwoqy4Fu4LLho24hEaNK06d_rcW7zFYEqi1lsrZU6e-nlXrqyHe2oXVxyT5_Xybys3GduXsYjN7pnPqdkI0CkJbk4gnKWMQFAQLQUtHZesuEwOgWa7DCkKV4cGFEBG0eQrCJSwY3YP8oqbmKn-rwexuBb20n8_g.jpg' + }) + + expect(results[36]).toMatchObject({ + start: '2023-12-01T04:30:00.000Z', + stop: '2023-12-01T05:30:00.000Z', + title: 'ZDF-Morgenmagazin', + description: 'Für einen guten Start in den Tag', + image: + 'https://tv.cdnartwhere.eu/cache/i2/Dc5BDoMgEADAv3CuIiIu-BYuu7Bo24hEaNK06d_rbY7zFYSVxSK21kpdvPRyL13ZjnbULsTc4-d1MseV-8zNy3DkhvfMp0k2KBUwJhcmNTjLkJRSBGCRtHZe2gQTYXRhJNCoL1NyyiTiSI6IhtHADOT6R1nFTexYn9djnuGtrRG_Pw.jpg' + }) +}) + +it('can handle empty guide', done => { + parser({ + content: + ' ', + date, + channel + }) + .then(result => { + expect(result).toMatchObject([]) + done() + }) + .catch(done) +}) diff --git a/sites/tv-spored.siol.net/tv-spored.siol.net.config.js b/sites/tv-spored.siol.net/tv-spored.siol.net.config.js index 45b8031ff..53dbdc891 100644 --- a/sites/tv-spored.siol.net/tv-spored.siol.net.config.js +++ b/sites/tv-spored.siol.net/tv-spored.siol.net.config.js @@ -1,81 +1,81 @@ -const axios = require('axios') -const cheerio = require('cheerio') - -module.exports = { - site: 'tv-spored.siol.net', - days: 2, - url({ channel, date }) { - return `https://tv-spored.siol.net/kanal/${channel.site_id}/datum/${date.format('YYYYMMDD')}` - }, - request: { - headers: { - Accept: 'text/html' - } - }, - parser({ content, date }) { - const items = parseItems(content, date) - - return items.map(item => ({ - title: item.title, - category: item.category, - season: item.season, - episode: item.episode, - start: item.startDateTime, - stop: item.stopDateTime - })) - }, - async channels() { - const content = await axios - .get('https://tv-spored.siol.net/', { - headers: { - Accept: 'text/html' - } - }) - .then(r => r.data) - .catch(console.log) - - const $ = cheerio.load(content) - const script = $('script:contains(tvChannelsAsJson)').text() - const func = new Function(`const self = { __next_f: [] };${script};return self.__next_f`) - const __next_f = func() - if (!__next_f[0] || !__next_f[0][1]) return [] - const [, dataString] = __next_f[0][1].split(/:(.*)/s) - const data = JSON.parse(dataString) - const tvChannelsAsJson = findByKey(data, 'tvChannelsAsJson') - - return tvChannelsAsJson.map(item => ({ - name: item.name, - site_id: item.externalId.toLowerCase(), - lang: 'sl' - })) - } -} - -function parseItems(content, date) { - try { - const $ = cheerio.load(content) - const script = $('script:contains(channelsAsJson)').text() - const func = new Function(`const self = { __next_f: [] };${script};return self.__next_f`) - const __next_f = func() - if (!__next_f[0] || !__next_f[0][1]) return [] - const [, dataString] = __next_f[0][1].split(/:(.*)/s) - const data = JSON.parse(dataString) - const channelsAsJson = findByKey(data, 'channelsAsJson') - - if (!channelsAsJson[0] || !Array.isArray(channelsAsJson[0].events)) return [] - - return channelsAsJson[0].events.filter(p => date.isSame(p.startDateTime, 'day')) - } catch { - return [] - } -} - -function findByKey(arr, key) { - if (!Array.isArray(arr)) return - return arr.reduce((a, item) => { - if (a) return a - if (item && item[key]) return item[key] - if (item && item.children) return findByKey(item.children, key) - if (Array.isArray(item)) return findByKey(item, key) - }, null) -} +const axios = require('axios') +const cheerio = require('cheerio') + +module.exports = { + site: 'tv-spored.siol.net', + days: 2, + url({ channel, date }) { + return `https://tv-spored.siol.net/kanal/${channel.site_id}/datum/${date.format('YYYYMMDD')}` + }, + request: { + headers: { + Accept: 'text/html' + } + }, + parser({ content, date }) { + const items = parseItems(content, date) + + return items.map(item => ({ + title: item.title, + category: item.category, + season: item.season, + episode: item.episode, + start: item.startDateTime, + stop: item.stopDateTime + })) + }, + async channels() { + const content = await axios + .get('https://tv-spored.siol.net/', { + headers: { + Accept: 'text/html' + } + }) + .then(r => r.data) + .catch(console.log) + + const $ = cheerio.load(content) + const script = $('script:contains(tvChannelsAsJson)').text() + const func = new Function(`const self = { __next_f: [] };${script};return self.__next_f`) + const __next_f = func() + if (!__next_f[0] || !__next_f[0][1]) return [] + const [, dataString] = __next_f[0][1].split(/:(.*)/s) + const data = JSON.parse(dataString) + const tvChannelsAsJson = findByKey(data, 'tvChannelsAsJson') + + return tvChannelsAsJson.map(item => ({ + name: item.name, + site_id: item.externalId.toLowerCase(), + lang: 'sl' + })) + } +} + +function parseItems(content, date) { + try { + const $ = cheerio.load(content) + const script = $('script:contains(channelsAsJson)').text() + const func = new Function(`const self = { __next_f: [] };${script};return self.__next_f`) + const __next_f = func() + if (!__next_f[0] || !__next_f[0][1]) return [] + const [, dataString] = __next_f[0][1].split(/:(.*)/s) + const data = JSON.parse(dataString) + const channelsAsJson = findByKey(data, 'channelsAsJson') + + if (!channelsAsJson[0] || !Array.isArray(channelsAsJson[0].events)) return [] + + return channelsAsJson[0].events.filter(p => date.isSame(p.startDateTime, 'day')) + } catch { + return [] + } +} + +function findByKey(arr, key) { + if (!Array.isArray(arr)) return + return arr.reduce((a, item) => { + if (a) return a + if (item && item[key]) return item[key] + if (item && item.children) return findByKey(item.children, key) + if (Array.isArray(item)) return findByKey(item, key) + }, null) +} diff --git a/sites/tv-spored.siol.net/tv-spored.siol.net.test.js b/sites/tv-spored.siol.net/tv-spored.siol.net.test.js index 846efbcf4..431e152c7 100644 --- a/sites/tv-spored.siol.net/tv-spored.siol.net.test.js +++ b/sites/tv-spored.siol.net/tv-spored.siol.net.test.js @@ -1,56 +1,56 @@ -const { parser, url, request } = require('./tv-spored.siol.net.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-01-15', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'exodustv', - xmltv_id: 'ExodusTV.si' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe('https://tv-spored.siol.net/kanal/exodustv/datum/20250115') -}) - -it('can generate request headers', () => { - expect(request.headers).toMatchObject({ - Accept: 'text/html' - }) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - const results = parser({ content, date }) - - expect(results.length).toBe(41) - expect(results[0]).toMatchObject({ - start: '2025-01-15T00:00:00.000Z', - stop: '2025-01-15T00:30:00.000Z', - title: 'Novice iz Svete dežele', - category: 'informativni', - season: null, - episode: null - }) - - expect(results[40]).toMatchObject({ - start: '2025-01-15T23:00:00.000Z', - stop: '2025-01-15T23:45:00.000Z', - title: 'Sveta maša', - category: 'ostalo', - season: null, - episode: null - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - content: '' - }) - expect(result).toMatchObject([]) -}) +const { parser, url, request } = require('./tv-spored.siol.net.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-01-15', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'exodustv', + xmltv_id: 'ExodusTV.si' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe('https://tv-spored.siol.net/kanal/exodustv/datum/20250115') +}) + +it('can generate request headers', () => { + expect(request.headers).toMatchObject({ + Accept: 'text/html' + }) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + const results = parser({ content, date }) + + expect(results.length).toBe(41) + expect(results[0]).toMatchObject({ + start: '2025-01-15T00:00:00.000Z', + stop: '2025-01-15T00:30:00.000Z', + title: 'Novice iz Svete dežele', + category: 'informativni', + season: null, + episode: null + }) + + expect(results[40]).toMatchObject({ + start: '2025-01-15T23:00:00.000Z', + stop: '2025-01-15T23:45:00.000Z', + title: 'Sveta maša', + category: 'ostalo', + season: null, + episode: null + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + content: '' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/tv.blue.ch/tv.blue.ch.config.js b/sites/tv.blue.ch/tv.blue.ch.config.js index 0ae7acba6..e1a09589f 100644 --- a/sites/tv.blue.ch/tv.blue.ch.config.js +++ b/sites/tv.blue.ch/tv.blue.ch.config.js @@ -1,85 +1,85 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') - -dayjs.extend(utc) - -module.exports = { - site: 'tv.blue.ch', - days: 2, - url: function ({ channel, date }) { - return `https://services.sg101.prd.sctv.ch/catalog/tv/channels/list/(ids=${ - channel.site_id - };start=${date.format('YYYYMMDDHHss')};end=${date - .add(1, 'd') - .format('YYYYMMDDHHss')};level=normal)` - }, - parser: function ({ content }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - const title = parseTitle(item) - if (title === 'Sendepause') return - programs.push({ - title, - description: parseDescription(item), - image: parseImage(item), - start: parseStart(item), - stop: parseStop(item) - }) - }) - - return programs - }, - async channels() { - const items = await axios - .get('https://services.sg101.prd.sctv.ch/portfolio/tv/channels') - .then(r => r.data) - .catch(console.log) - - return items.map(item => { - return { - lang: item.Languages[0] || 'de', - site_id: item.Identifier, - name: item.Title - } - }) - } -} - -function parseTitle(item) { - return item.Content.Description.Title -} - -function parseDescription(item) { - return item.Content.Description.Summary -} - -function parseImage(item) { - const image = item.Content.Nodes ? item.Content.Nodes.Items.find(i => i.Kind === 'Image') : null - const path = image ? image.ContentPath : null - - return path ? `https://services.sg101.prd.sctv.ch/content/images${path}_w1920.webp` : null -} - -function parseStart(item) { - const available = item.Availabilities.length ? item.Availabilities[0] : null - - return dayjs(available.AvailabilityStart) -} - -function parseStop(item) { - const available = item.Availabilities.length ? item.Availabilities[0] : null - - return dayjs(available.AvailabilityEnd) -} - -function parseItems(content) { - const data = JSON.parse(content) - const nodes = data.Nodes.Items.filter(i => i.Kind === 'Channel') - if (!nodes.length) return [] - - return nodes[0].Content.Nodes && Array.isArray(nodes[0].Content.Nodes.Items) - ? nodes[0].Content.Nodes.Items.filter(i => i.Kind === 'Broadcast') - : [] -} +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') + +dayjs.extend(utc) + +module.exports = { + site: 'tv.blue.ch', + days: 2, + url: function ({ channel, date }) { + return `https://services.sg101.prd.sctv.ch/catalog/tv/channels/list/(ids=${ + channel.site_id + };start=${date.format('YYYYMMDDHHss')};end=${date + .add(1, 'd') + .format('YYYYMMDDHHss')};level=normal)` + }, + parser: function ({ content }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + const title = parseTitle(item) + if (title === 'Sendepause') return + programs.push({ + title, + description: parseDescription(item), + image: parseImage(item), + start: parseStart(item), + stop: parseStop(item) + }) + }) + + return programs + }, + async channels() { + const items = await axios + .get('https://services.sg101.prd.sctv.ch/portfolio/tv/channels') + .then(r => r.data) + .catch(console.log) + + return items.map(item => { + return { + lang: item.Languages[0] || 'de', + site_id: item.Identifier, + name: item.Title + } + }) + } +} + +function parseTitle(item) { + return item.Content.Description.Title +} + +function parseDescription(item) { + return item.Content.Description.Summary +} + +function parseImage(item) { + const image = item.Content.Nodes ? item.Content.Nodes.Items.find(i => i.Kind === 'Image') : null + const path = image ? image.ContentPath : null + + return path ? `https://services.sg101.prd.sctv.ch/content/images${path}_w1920.webp` : null +} + +function parseStart(item) { + const available = item.Availabilities.length ? item.Availabilities[0] : null + + return dayjs(available.AvailabilityStart) +} + +function parseStop(item) { + const available = item.Availabilities.length ? item.Availabilities[0] : null + + return dayjs(available.AvailabilityEnd) +} + +function parseItems(content) { + const data = JSON.parse(content) + const nodes = data.Nodes.Items.filter(i => i.Kind === 'Channel') + if (!nodes.length) return [] + + return nodes[0].Content.Nodes && Array.isArray(nodes[0].Content.Nodes.Items) + ? nodes[0].Content.Nodes.Items.filter(i => i.Kind === 'Broadcast') + : [] +} diff --git a/sites/tv.cctv.com/tv.cctv.com.config.js b/sites/tv.cctv.com/tv.cctv.com.config.js index 12cf73358..c455a6d0d 100644 --- a/sites/tv.cctv.com/tv.cctv.com.config.js +++ b/sites/tv.cctv.com/tv.cctv.com.config.js @@ -1,42 +1,42 @@ -const dayjs = require('dayjs') - -module.exports = { - site: 'tv.cctv.com', - days: 2, - url({ channel, date }) { - return `https://api.cntv.cn/epg/getEpgInfoByChannelNew?serviceId=tvcctv&c=${ - channel.site_id - }&d=${date.format('YYYYMMDD')}` - }, - parser({ content, channel }) { - const programs = [] - const items = parseItems(content, channel) - items.forEach(item => { - const title = item.title - const start = parseStart(item) - const stop = parseStop(item) - programs.push({ - title, - start, - stop - }) - }) - - return programs - } -} - -function parseStop(item) { - return dayjs.unix(item.endTime) -} - -function parseStart(item) { - return dayjs.unix(item.startTime) -} - -function parseItems(content, channel) { - const data = JSON.parse(content) - if (!data || !data.data) return [] - - return data.data[channel.site_id].list || [] -} +const dayjs = require('dayjs') + +module.exports = { + site: 'tv.cctv.com', + days: 2, + url({ channel, date }) { + return `https://api.cntv.cn/epg/getEpgInfoByChannelNew?serviceId=tvcctv&c=${ + channel.site_id + }&d=${date.format('YYYYMMDD')}` + }, + parser({ content, channel }) { + const programs = [] + const items = parseItems(content, channel) + items.forEach(item => { + const title = item.title + const start = parseStart(item) + const stop = parseStop(item) + programs.push({ + title, + start, + stop + }) + }) + + return programs + } +} + +function parseStop(item) { + return dayjs.unix(item.endTime) +} + +function parseStart(item) { + return dayjs.unix(item.startTime) +} + +function parseItems(content, channel) { + const data = JSON.parse(content) + if (!data || !data.data) return [] + + return data.data[channel.site_id].list || [] +} diff --git a/sites/tv.cctv.com/tv.cctv.com.test.js b/sites/tv.cctv.com/tv.cctv.com.test.js index 0189f648b..35b41e439 100644 --- a/sites/tv.cctv.com/tv.cctv.com.test.js +++ b/sites/tv.cctv.com/tv.cctv.com.test.js @@ -1,51 +1,51 @@ -const { parser, url } = require('./tv.cctv.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-11-30', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'cctv1', - xmltv_id: 'CCTV1.cn' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://api.cntv.cn/epg/getEpgInfoByChannelNew?serviceId=tvcctv&c=cctv1&d=20231130' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const results = parser({ channel, content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(37) - - expect(results[0]).toMatchObject({ - start: '2023-11-29T17:13:00.000Z', - stop: '2023-11-29T17:41:15.000Z', - title: '今日说法-2023-302' - }) - - expect(results[36]).toMatchObject({ - start: '2023-11-30T15:30:15.000Z', - stop: '2023-11-30T15:59:00.000Z', - title: '非遗里的中国-4' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - channel, - content: '{"errcode":"1001","msg":"params error"}' - }) - expect(results.length).toBe(0) -}) +const { parser, url } = require('./tv.cctv.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-11-30', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'cctv1', + xmltv_id: 'CCTV1.cn' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://api.cntv.cn/epg/getEpgInfoByChannelNew?serviceId=tvcctv&c=cctv1&d=20231130' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const results = parser({ channel, content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(37) + + expect(results[0]).toMatchObject({ + start: '2023-11-29T17:13:00.000Z', + stop: '2023-11-29T17:41:15.000Z', + title: '今日说法-2023-302' + }) + + expect(results[36]).toMatchObject({ + start: '2023-11-30T15:30:15.000Z', + stop: '2023-11-30T15:59:00.000Z', + title: '非遗里的中国-4' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + channel, + content: '{"errcode":"1001","msg":"params error"}' + }) + expect(results.length).toBe(0) +}) diff --git a/sites/tv.lv/tv.lv.config.js b/sites/tv.lv/tv.lv.config.js index c7989235b..91677cb9a 100644 --- a/sites/tv.lv/tv.lv.config.js +++ b/sites/tv.lv/tv.lv.config.js @@ -1,64 +1,64 @@ -const dayjs = require('dayjs') - -module.exports = { - site: 'tv.lv', - days: 2, - url: function ({ date, channel }) { - return `https://www.tv.lv/programme/listing/none/${date.format( - 'DD-MM-YYYY' - )}?filter=channel&subslug=${channel.site_id}` - }, - parser: function ({ content }) { - const programs = [] - const items = parseItems(content) - items.forEach(item => { - const start = parseStart(item) - const stop = parseStop(item) - programs.push({ - title: item.title, - description: item.description_long, - category: item.categorystring, - image: item.image, - start, - stop - }) - }) - - return programs - }, - async channels() { - const axios = require('axios') - const groups = await axios - .get('https://www.tv.lv/data/channels/lvall') - .then(r => r.data) - .catch(console.log) - - let channels = [] - - groups.forEach(group => { - group.channels.forEach(item => { - channels.push({ - lang: 'lv', - site_id: item.slug, - name: item.name - }) - }) - }) - - return channels - } -} - -function parseStart(item) { - return item.start_unix ? dayjs.unix(item.start_unix) : null -} - -function parseStop(item) { - return item.stop_unix ? dayjs.unix(item.stop_unix) : null -} - -function parseItems(content) { - const data = JSON.parse(content) - - return data.schedule.programme || [] -} +const dayjs = require('dayjs') + +module.exports = { + site: 'tv.lv', + days: 2, + url: function ({ date, channel }) { + return `https://www.tv.lv/programme/listing/none/${date.format( + 'DD-MM-YYYY' + )}?filter=channel&subslug=${channel.site_id}` + }, + parser: function ({ content }) { + const programs = [] + const items = parseItems(content) + items.forEach(item => { + const start = parseStart(item) + const stop = parseStop(item) + programs.push({ + title: item.title, + description: item.description_long, + category: item.categorystring, + image: item.image, + start, + stop + }) + }) + + return programs + }, + async channels() { + const axios = require('axios') + const groups = await axios + .get('https://www.tv.lv/data/channels/lvall') + .then(r => r.data) + .catch(console.log) + + let channels = [] + + groups.forEach(group => { + group.channels.forEach(item => { + channels.push({ + lang: 'lv', + site_id: item.slug, + name: item.name + }) + }) + }) + + return channels + } +} + +function parseStart(item) { + return item.start_unix ? dayjs.unix(item.start_unix) : null +} + +function parseStop(item) { + return item.stop_unix ? dayjs.unix(item.stop_unix) : null +} + +function parseItems(content) { + const data = JSON.parse(content) + + return data.schedule.programme || [] +} diff --git a/sites/tv.magenta.at/tv.magenta.at.test.js b/sites/tv.magenta.at/tv.magenta.at.test.js index 66f858ba4..438240840 100644 --- a/sites/tv.magenta.at/tv.magenta.at.test.js +++ b/sites/tv.magenta.at/tv.magenta.at.test.js @@ -1,135 +1,135 @@ -const { parser, url } = require('./tv.magenta.at.config.js') -const fs = require('fs') -const path = require('path') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const API_ENDPOINT = 'https://tv-at-prod.yo-digital.com/at-bifrost' - -jest.mock('axios') - -const date = dayjs.utc('2022-10-30', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '206969383991', - xmltv_id: '13thStreet.de', - lang: 'de' -} - -it('can generate valid url', () => { - expect(url({ date, channel })).toBe( - `${API_ENDPOINT}/epg/channel/schedules/v2?station_ids=206969383991&date=2022-10-30&hour_offset=0&hour_range=3&natco_code=at` - ) -}) - -it('can parse response', async () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_0000.json')) - - axios.get.mockImplementation(url => { - if ( - url === - `${API_ENDPOINT}/epg/channel/schedules/v2?date=2023-11-15&hour_offset=0&hour_range=3&station_ids=206969383991&natco_code=at` - ) { - return Promise.resolve({ - data: fs.readFileSync(path.resolve(__dirname, '__data__/content_0300.json')) - }) - } else if ( - url === - `${API_ENDPOINT}/epg/channel/schedules/v2?date=2023-11-15&hour_offset=3&hour_range=3&station_ids=206969383991&natco_code=at` - ) { - return Promise.resolve({ - data: fs.readFileSync(path.resolve(__dirname, '__data__/content_0600.json')) - }) - } else if ( - url === - `${API_ENDPOINT}/epg/channel/schedules/v2?date=2023-11-15&hour_offset=6&hour_range=3&station_ids=206969383991&natco_code=at` - ) { - return Promise.resolve({ - data: fs.readFileSync(path.resolve(__dirname, '__data__/content_0900.json')) - }) - } else if ( - url === - `${API_ENDPOINT}/epg/channel/schedules/v2?date=2023-11-15&hour_offset=9&hour_range=3&station_ids=206969383991&natco_code=at` - ) { - return Promise.resolve({ - data: fs.readFileSync(path.resolve(__dirname, '__data__/content_1200.json')) - }) - } else if ( - url === - `${API_ENDPOINT}/epg/channel/schedules/v2?date=2023-11-15&hour_offset=12&hour_range=3&station_ids=206969383991&natco_code=at` - ) { - return Promise.resolve({ - data: fs.readFileSync(path.resolve(__dirname, '__data__/content_1500.json')) - }) - } else if ( - url === - `${API_ENDPOINT}/epg/channel/schedules/v2?date=2023-11-15&hour_offset=15&hour_range=3&station_ids=206969383991&natco_code=at` - ) { - return Promise.resolve({ - data: fs.readFileSync(path.resolve(__dirname, '__data__/content_1800.json')) - }) - } else if ( - url === - `${API_ENDPOINT}/epg/channel/schedules/v2?date=2023-11-15&hour_offset=18&hour_range=3&station_ids=206969383991&natco_code=at` - ) { - return Promise.resolve({ - data: fs.readFileSync(path.resolve(__dirname, '__data__/content_2100.json')) - }) - } else if ( - url === `${API_ENDPOINT}/details/series/gn.tv-24101298-EP048489190016?natco_code=at` - ) { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program.json'))) - }) - } else { - return Promise.resolve({ data: '' }) - } - }) - - let results = await parser({ content, channel, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2023-11-14T23:20:00.000Z', - stop: '2023-11-15T00:05:00.000Z', - title: 'So Help Me Todd', - description: - 'Ava ist 17 und eine geniale Hackerin. Jetzt steht die Teenagerin vor Gericht, weil sie sich illegal Zugang zum Verteidigungsministerium verschafft hat. Todd soll das IT-Genie überwachen.', - date: '2023', - category: ['Kriminaldrama'], - actors: [ - 'Marcia Gay Harden', - 'Skylar Astin', - 'Madeline Wise', - 'Tristen J. Winger', - 'Inga Schlingmann', - 'Rosa Evangelina Arredondo', - 'Laila Robins' - ], - directors: ['Jay Karas'], - producers: [ - 'Scott Prendergast', - 'Liz Kruger', - 'Elizabeth Klaviter', - 'Dr. Phil McGraw', - 'Jay McGraw', - 'Julia Eisenman', - 'Amy York Rubin' - ], - season: 1, - episode: 15 - }) -}) - -it('can handle empty guide', async () => { - let results = await parser({ content: '', channel, date }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./tv.magenta.at.config.js') +const fs = require('fs') +const path = require('path') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const API_ENDPOINT = 'https://tv-at-prod.yo-digital.com/at-bifrost' + +jest.mock('axios') + +const date = dayjs.utc('2022-10-30', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '206969383991', + xmltv_id: '13thStreet.de', + lang: 'de' +} + +it('can generate valid url', () => { + expect(url({ date, channel })).toBe( + `${API_ENDPOINT}/epg/channel/schedules/v2?station_ids=206969383991&date=2022-10-30&hour_offset=0&hour_range=3&natco_code=at` + ) +}) + +it('can parse response', async () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_0000.json')) + + axios.get.mockImplementation(url => { + if ( + url === + `${API_ENDPOINT}/epg/channel/schedules/v2?date=2023-11-15&hour_offset=0&hour_range=3&station_ids=206969383991&natco_code=at` + ) { + return Promise.resolve({ + data: fs.readFileSync(path.resolve(__dirname, '__data__/content_0300.json')) + }) + } else if ( + url === + `${API_ENDPOINT}/epg/channel/schedules/v2?date=2023-11-15&hour_offset=3&hour_range=3&station_ids=206969383991&natco_code=at` + ) { + return Promise.resolve({ + data: fs.readFileSync(path.resolve(__dirname, '__data__/content_0600.json')) + }) + } else if ( + url === + `${API_ENDPOINT}/epg/channel/schedules/v2?date=2023-11-15&hour_offset=6&hour_range=3&station_ids=206969383991&natco_code=at` + ) { + return Promise.resolve({ + data: fs.readFileSync(path.resolve(__dirname, '__data__/content_0900.json')) + }) + } else if ( + url === + `${API_ENDPOINT}/epg/channel/schedules/v2?date=2023-11-15&hour_offset=9&hour_range=3&station_ids=206969383991&natco_code=at` + ) { + return Promise.resolve({ + data: fs.readFileSync(path.resolve(__dirname, '__data__/content_1200.json')) + }) + } else if ( + url === + `${API_ENDPOINT}/epg/channel/schedules/v2?date=2023-11-15&hour_offset=12&hour_range=3&station_ids=206969383991&natco_code=at` + ) { + return Promise.resolve({ + data: fs.readFileSync(path.resolve(__dirname, '__data__/content_1500.json')) + }) + } else if ( + url === + `${API_ENDPOINT}/epg/channel/schedules/v2?date=2023-11-15&hour_offset=15&hour_range=3&station_ids=206969383991&natco_code=at` + ) { + return Promise.resolve({ + data: fs.readFileSync(path.resolve(__dirname, '__data__/content_1800.json')) + }) + } else if ( + url === + `${API_ENDPOINT}/epg/channel/schedules/v2?date=2023-11-15&hour_offset=18&hour_range=3&station_ids=206969383991&natco_code=at` + ) { + return Promise.resolve({ + data: fs.readFileSync(path.resolve(__dirname, '__data__/content_2100.json')) + }) + } else if ( + url === `${API_ENDPOINT}/details/series/gn.tv-24101298-EP048489190016?natco_code=at` + ) { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program.json'))) + }) + } else { + return Promise.resolve({ data: '' }) + } + }) + + let results = await parser({ content, channel, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2023-11-14T23:20:00.000Z', + stop: '2023-11-15T00:05:00.000Z', + title: 'So Help Me Todd', + description: + 'Ava ist 17 und eine geniale Hackerin. Jetzt steht die Teenagerin vor Gericht, weil sie sich illegal Zugang zum Verteidigungsministerium verschafft hat. Todd soll das IT-Genie überwachen.', + date: '2023', + category: ['Kriminaldrama'], + actors: [ + 'Marcia Gay Harden', + 'Skylar Astin', + 'Madeline Wise', + 'Tristen J. Winger', + 'Inga Schlingmann', + 'Rosa Evangelina Arredondo', + 'Laila Robins' + ], + directors: ['Jay Karas'], + producers: [ + 'Scott Prendergast', + 'Liz Kruger', + 'Elizabeth Klaviter', + 'Dr. Phil McGraw', + 'Jay McGraw', + 'Julia Eisenman', + 'Amy York Rubin' + ], + season: 1, + episode: 15 + }) +}) + +it('can handle empty guide', async () => { + let results = await parser({ content: '', channel, date }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/tv.movistar.com.pe/tv.movistar.com.pe.config.js b/sites/tv.movistar.com.pe/tv.movistar.com.pe.config.js index 47aee3870..78f3931e9 100644 --- a/sites/tv.movistar.com.pe/tv.movistar.com.pe.config.js +++ b/sites/tv.movistar.com.pe/tv.movistar.com.pe.config.js @@ -1,57 +1,57 @@ -const dayjs = require('dayjs') -const axios = require('axios') - -module.exports = { - site: 'tv.movistar.com.pe', - days: 2, - url({ channel, date }) { - return `https://contentapi-pe.cdn.telefonica.com/28/default/es-PE/schedules?fields=Pid,Title,Description,ChannelName,LiveChannelPid,Start,End,images.videoFrame,AgeRatingPid&orderBy=START_TIME%3Aa&filteravailability=false&starttime=${date.unix()}&endtime=${date - .add(1, 'd') - .unix()}&livechannelpids=${channel.site_id}` - }, - parser({ content, channel }) { - let programs = [] - const items = parseItems(content, channel) - items.forEach(item => { - programs.push({ - title: item.Title, - description: item.Description, - image: parseImage(item), - start: parseTime(item.Start), - stop: parseTime(item.End) - }) - }) - - return programs - }, - async channels() { - const items = await axios - .get( - 'https://contentapi-pe.cdn.telefonica.com/28/default/es-PE/contents/all?contentTypes=LCH&fields=Pid,Name&orderBy=contentOrder&limit=1000' - ) - .then(r => r.data.Content.List) - .catch(console.error) - - return items.map(i => { - return { - lang: 'es', - name: i.Name, - site_id: i.Pid.toLowerCase() - } - }) - } -} - -function parseImage(item) { - return item.Images?.VideoFrame?.[0]?.Url -} - -function parseTime(timestamp) { - return dayjs.unix(timestamp) -} - -function parseItems(content) { - const data = JSON.parse(content) - - return data.Content || [] -} +const dayjs = require('dayjs') +const axios = require('axios') + +module.exports = { + site: 'tv.movistar.com.pe', + days: 2, + url({ channel, date }) { + return `https://contentapi-pe.cdn.telefonica.com/28/default/es-PE/schedules?fields=Pid,Title,Description,ChannelName,LiveChannelPid,Start,End,images.videoFrame,AgeRatingPid&orderBy=START_TIME%3Aa&filteravailability=false&starttime=${date.unix()}&endtime=${date + .add(1, 'd') + .unix()}&livechannelpids=${channel.site_id}` + }, + parser({ content, channel }) { + let programs = [] + const items = parseItems(content, channel) + items.forEach(item => { + programs.push({ + title: item.Title, + description: item.Description, + image: parseImage(item), + start: parseTime(item.Start), + stop: parseTime(item.End) + }) + }) + + return programs + }, + async channels() { + const items = await axios + .get( + 'https://contentapi-pe.cdn.telefonica.com/28/default/es-PE/contents/all?contentTypes=LCH&fields=Pid,Name&orderBy=contentOrder&limit=1000' + ) + .then(r => r.data.Content.List) + .catch(console.error) + + return items.map(i => { + return { + lang: 'es', + name: i.Name, + site_id: i.Pid.toLowerCase() + } + }) + } +} + +function parseImage(item) { + return item.Images?.VideoFrame?.[0]?.Url +} + +function parseTime(timestamp) { + return dayjs.unix(timestamp) +} + +function parseItems(content) { + const data = JSON.parse(content) + + return data.Content || [] +} diff --git a/sites/tv.movistar.com.pe/tv.movistar.com.pe.test.js b/sites/tv.movistar.com.pe/tv.movistar.com.pe.test.js index 06f2aa9ae..426eda97d 100644 --- a/sites/tv.movistar.com.pe/tv.movistar.com.pe.test.js +++ b/sites/tv.movistar.com.pe/tv.movistar.com.pe.test.js @@ -1,45 +1,45 @@ -const { parser, url } = require('./tv.movistar.com.pe.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-11-29', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'lch2219', - xmltv_id: 'WillaxTV.pe' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://contentapi-pe.cdn.telefonica.com/28/default/es-PE/schedules?fields=Pid,Title,Description,ChannelName,LiveChannelPid,Start,End,images.videoFrame,AgeRatingPid&orderBy=START_TIME%3Aa&filteravailability=false&starttime=1669680000&endtime=1669766400&livechannelpids=lch2219' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - let results = parser({ content, channel, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2022-11-28T23:50:00.000Z', - stop: '2022-11-29T00:50:00.000Z', - title: 'Willax noticias edición central', - description: - 'Edición central con el desarrollo y cobertura noticiosa de todos los acontecimientos nacionales e internacionales.', - image: - 'http://media.gvp.telefonica.com/storagearea0/IMAGES/00/13/00/13003906_281B2DAB18B01955.jpg' - }) -}) - -it('can handle empty guide', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - const result = parser({ content, channel, date }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./tv.movistar.com.pe.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-11-29', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'lch2219', + xmltv_id: 'WillaxTV.pe' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://contentapi-pe.cdn.telefonica.com/28/default/es-PE/schedules?fields=Pid,Title,Description,ChannelName,LiveChannelPid,Start,End,images.videoFrame,AgeRatingPid&orderBy=START_TIME%3Aa&filteravailability=false&starttime=1669680000&endtime=1669766400&livechannelpids=lch2219' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + let results = parser({ content, channel, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2022-11-28T23:50:00.000Z', + stop: '2022-11-29T00:50:00.000Z', + title: 'Willax noticias edición central', + description: + 'Edición central con el desarrollo y cobertura noticiosa de todos los acontecimientos nacionales e internacionales.', + image: + 'http://media.gvp.telefonica.com/storagearea0/IMAGES/00/13/00/13003906_281B2DAB18B01955.jpg' + }) +}) + +it('can handle empty guide', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + const result = parser({ content, channel, date }) + expect(result).toMatchObject([]) +}) diff --git a/sites/tv.nu/tv.nu.config.js b/sites/tv.nu/tv.nu.config.js index 5dd28959e..b514a1dc0 100644 --- a/sites/tv.nu/tv.nu.config.js +++ b/sites/tv.nu/tv.nu.config.js @@ -1,87 +1,87 @@ -const dayjs = require('dayjs') - -module.exports = { - site: 'tv.nu', - days: 2, - url: function ({ channel, date }) { - return `https://web-api.tv.nu/channels/${channel.site_id}/schedule?date=${date.format( - 'YYYY-MM-DD' - )}&fullDay=true` - }, - parser: function ({ content }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - programs.push({ - title: item.title, - description: item.description, - category: Array.isArray(item.genres) ? item.genres.map(genre => genre.name) : null, - season: item.seasonNumber || null, - episode: item.episodeNumber || null, - start: parseStart(item), - stop: parseStop(item) - }) - }) - - return programs - }, - async channels() { - const channels = [] - const axios = require('axios') - const result = await axios - .get('https://www.tv.nu/alla-kanaler') - .then(response => response.data) - .catch(console.error) - - if (result) { - const [, data] = result.match(/\\"allModules\\":(\[(.*?)\])/i) || [null, null] - const modules = JSON.parse(data.replace(/\\/g, '')) - if (Array.isArray(modules) && modules.length) { - let offset = 0 - while (offset !== undefined) { - const data = await axios - .get('https://web-api.tv.nu/tableauLinearChannels', { - params: { - modules, - date: dayjs().format('YYYY-MM-DD'), - limit: 12, - offset - } - }) - .then(r => r.data) - .catch(console.error) - - data.data.modules.forEach(item => { - channels.push({ - lang: 'sv', - name: item.content.name, - site_id: item.content.slug - }) - }) - offset = data.data.nextOffset - } - } - } - - return channels - } -} - -function parseStart(item) { - if (!item.broadcast || !item.broadcast.startTime) return null - - return dayjs(item.broadcast.startTime) -} - -function parseStop(item) { - if (!item.broadcast || !item.broadcast.endTime) return null - - return dayjs(item.broadcast.endTime) -} - -function parseItems(content) { - const data = JSON.parse(content) - if (!data || !data.data || !Array.isArray(data.data.broadcasts)) return [] - - return data.data.broadcasts -} +const dayjs = require('dayjs') + +module.exports = { + site: 'tv.nu', + days: 2, + url: function ({ channel, date }) { + return `https://web-api.tv.nu/channels/${channel.site_id}/schedule?date=${date.format( + 'YYYY-MM-DD' + )}&fullDay=true` + }, + parser: function ({ content }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + programs.push({ + title: item.title, + description: item.description, + category: Array.isArray(item.genres) ? item.genres.map(genre => genre.name) : null, + season: item.seasonNumber || null, + episode: item.episodeNumber || null, + start: parseStart(item), + stop: parseStop(item) + }) + }) + + return programs + }, + async channels() { + const channels = [] + const axios = require('axios') + const result = await axios + .get('https://www.tv.nu/alla-kanaler') + .then(response => response.data) + .catch(console.error) + + if (result) { + const [, data] = result.match(/\\"allModules\\":(\[(.*?)\])/i) || [null, null] + const modules = JSON.parse(data.replace(/\\/g, '')) + if (Array.isArray(modules) && modules.length) { + let offset = 0 + while (offset !== undefined) { + const data = await axios + .get('https://web-api.tv.nu/tableauLinearChannels', { + params: { + modules, + date: dayjs().format('YYYY-MM-DD'), + limit: 12, + offset + } + }) + .then(r => r.data) + .catch(console.error) + + data.data.modules.forEach(item => { + channels.push({ + lang: 'sv', + name: item.content.name, + site_id: item.content.slug + }) + }) + offset = data.data.nextOffset + } + } + } + + return channels + } +} + +function parseStart(item) { + if (!item.broadcast || !item.broadcast.startTime) return null + + return dayjs(item.broadcast.startTime) +} + +function parseStop(item) { + if (!item.broadcast || !item.broadcast.endTime) return null + + return dayjs(item.broadcast.endTime) +} + +function parseItems(content) { + const data = JSON.parse(content) + if (!data || !data.data || !Array.isArray(data.data.broadcasts)) return [] + + return data.data.broadcasts +} diff --git a/sites/tv.nu/tv.nu.test.js b/sites/tv.nu/tv.nu.test.js index 8683ce5de..8e37de2c9 100644 --- a/sites/tv.nu/tv.nu.test.js +++ b/sites/tv.nu/tv.nu.test.js @@ -1,48 +1,48 @@ -const fs = require('fs') -const path = require('path') -const { parser, url } = require('./tv.nu.config.js') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2024-12-03', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '3sat', - xmltv_id: '3sat.de' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://web-api.tv.nu/channels/3sat/schedule?date=2024-12-03&fullDay=true' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.join(__dirname, '__data__', 'content.json')) - const result = parser({ content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2024-12-03T11:50:00.000Z', - stop: '2024-12-03T12:15:00.000Z', - title: 'Natur im Garten', - description: - 'Der Gartenbuchautor Karl Ploberger gibt in der Sendung Tipps und Tricks zur Gartenpflege.', - category: ['Konsument', 'Underhållning', 'Trädgård'], - season: 29, - episode: 9 - } - ]) -}) - -it('can handle empty guide', () => { - const content = fs.readFileSync(path.join(__dirname, '__data__', 'no_content.json')) - const result = parser({ content }) - expect(result).toMatchObject([]) -}) +const fs = require('fs') +const path = require('path') +const { parser, url } = require('./tv.nu.config.js') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2024-12-03', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '3sat', + xmltv_id: '3sat.de' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://web-api.tv.nu/channels/3sat/schedule?date=2024-12-03&fullDay=true' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.join(__dirname, '__data__', 'content.json')) + const result = parser({ content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2024-12-03T11:50:00.000Z', + stop: '2024-12-03T12:15:00.000Z', + title: 'Natur im Garten', + description: + 'Der Gartenbuchautor Karl Ploberger gibt in der Sendung Tipps und Tricks zur Gartenpflege.', + category: ['Konsument', 'Underhållning', 'Trädgård'], + season: 29, + episode: 9 + } + ]) +}) + +it('can handle empty guide', () => { + const content = fs.readFileSync(path.join(__dirname, '__data__', 'no_content.json')) + const result = parser({ content }) + expect(result).toMatchObject([]) +}) diff --git a/sites/tv.post.lu/tv.post.lu.config.js b/sites/tv.post.lu/tv.post.lu.config.js index 6d7a49be9..fb840e0ff 100644 --- a/sites/tv.post.lu/tv.post.lu.config.js +++ b/sites/tv.post.lu/tv.post.lu.config.js @@ -1,56 +1,56 @@ -const axios = require('axios') -const dayjs = require('dayjs') - -module.exports = { - site: 'tv.post.lu', - days: 2, - url({ channel, date }) { - return `https://tv.post.lu/api/channels?id=${channel.site_id}&date=${date.format('YYYY-MM-DD')}` - }, - parser({ content }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - programs.push({ - title: item.title, - description: item.description, - category: item.program_type, - image: item.image_url, - start: dayjs.unix(item.tsStart), - stop: dayjs.unix(item.tsEnd) - }) - }) - - return programs - }, - async channels() { - const promises = [...Array(17).keys()].map(i => - axios.get(`https://tv.post.lu/api/channels/?page=${i + 1}`) - ) - - const channels = [] - await Promise.all(promises).then(values => { - values.forEach(r => { - let items = r.data.result.data - items.forEach(item => { - channels.push({ - lang: item.language.code, - name: item.name, - site_id: item.id - }) - }) - }) - }) - - return channels - } -} - -function parseItems(content) { - if (!content) return [] - const data = JSON.parse(content) - if (!data || !data.result || !data.result.epg || !Array.isArray(data.result.epg.programme)) - return [] - - return data.result.epg.programme -} +const axios = require('axios') +const dayjs = require('dayjs') + +module.exports = { + site: 'tv.post.lu', + days: 2, + url({ channel, date }) { + return `https://tv.post.lu/api/channels?id=${channel.site_id}&date=${date.format('YYYY-MM-DD')}` + }, + parser({ content }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + programs.push({ + title: item.title, + description: item.description, + category: item.program_type, + image: item.image_url, + start: dayjs.unix(item.tsStart), + stop: dayjs.unix(item.tsEnd) + }) + }) + + return programs + }, + async channels() { + const promises = [...Array(17).keys()].map(i => + axios.get(`https://tv.post.lu/api/channels/?page=${i + 1}`) + ) + + const channels = [] + await Promise.all(promises).then(values => { + values.forEach(r => { + let items = r.data.result.data + items.forEach(item => { + channels.push({ + lang: item.language.code, + name: item.name, + site_id: item.id + }) + }) + }) + }) + + return channels + } +} + +function parseItems(content) { + if (!content) return [] + const data = JSON.parse(content) + if (!data || !data.result || !data.result.epg || !Array.isArray(data.result.epg.programme)) + return [] + + return data.result.epg.programme +} diff --git a/sites/tv.post.lu/tv.post.lu.test.js b/sites/tv.post.lu/tv.post.lu.test.js index a113a2e5d..6a0a4a304 100644 --- a/sites/tv.post.lu/tv.post.lu.test.js +++ b/sites/tv.post.lu/tv.post.lu.test.js @@ -1,47 +1,47 @@ -const { parser, url } = require('./tv.post.lu.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-01-16', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '269695d0-8076-11e9-b5ca-f345a2ed0fbe', - xmltv_id: 'DasErste.de' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://tv.post.lu/api/channels?id=269695d0-8076-11e9-b5ca-f345a2ed0fbe&date=2023-01-16' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - const results = parser({ content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - title: 'Tagesschau', - description: - 'Das Flaggschiff unter den deutschen Nachrichtensendungen ist gleichzeitig die "dienstälteste" noch bestehende Sendung im deutschen Fernsehen. In bis zu 20 am Tag produzierten Sendungen wird die Komplexität des Weltgeschehens verständlich erklärt und in komprimierter Form über aktuelle politische, wirtschaftliche, soziale, kulturelle, sportliche und sonstige Ereignisse berichtet.', - category: 'Nachrichten', - image: - 'https://mp-photos-cdn.azureedge.net/container3cc71e4948ac40ab803c26e0abc2e3e5/original/e6eb49013a822f5c6eb2e7701e69a1f80aa0b947.jpg', - start: '2023-01-16T00:05:00.000Z', - stop: '2023-01-16T00:10:00.000Z' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ content: '' }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./tv.post.lu.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-01-16', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '269695d0-8076-11e9-b5ca-f345a2ed0fbe', + xmltv_id: 'DasErste.de' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://tv.post.lu/api/channels?id=269695d0-8076-11e9-b5ca-f345a2ed0fbe&date=2023-01-16' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + const results = parser({ content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + title: 'Tagesschau', + description: + 'Das Flaggschiff unter den deutschen Nachrichtensendungen ist gleichzeitig die "dienstälteste" noch bestehende Sendung im deutschen Fernsehen. In bis zu 20 am Tag produzierten Sendungen wird die Komplexität des Weltgeschehens verständlich erklärt und in komprimierter Form über aktuelle politische, wirtschaftliche, soziale, kulturelle, sportliche und sonstige Ereignisse berichtet.', + category: 'Nachrichten', + image: + 'https://mp-photos-cdn.azureedge.net/container3cc71e4948ac40ab803c26e0abc2e3e5/original/e6eb49013a822f5c6eb2e7701e69a1f80aa0b947.jpg', + start: '2023-01-16T00:05:00.000Z', + stop: '2023-01-16T00:10:00.000Z' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ content: '' }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/tv.trueid.net/tv.trueid.net.config.js b/sites/tv.trueid.net/tv.trueid.net.config.js index 2de101cf6..b92a317ae 100644 --- a/sites/tv.trueid.net/tv.trueid.net.config.js +++ b/sites/tv.trueid.net/tv.trueid.net.config.js @@ -1,74 +1,74 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') - -dayjs.extend(utc) - -module.exports = { - delay: 1000, - site: 'tv.trueid.net', - days: 1, - url({ channel }) { - return `https://tv.trueid.net/_next/data/1380644e0f1fb6b14c82894a0c682d147e015c9d/th-${channel.lang}.json?channelSlug=${channel.site_id}&path=${channel.site_id}` - }, - parser({ content, channel }) { - const programs = [] - parseItems(content, channel).forEach(item => { - programs.push({ - title: item.title, - description: parseDescription(item, channel.lang), - image: parseImage(item), - start: parseStart(item), - stop: parseStop(item) - }) - }) - - return programs - }, - async channels({ token, lang = 'en' }) { - const axios = require('axios') - const ACCESS_TOKEN = token - ? token - : 'MTM4MDY0NGUwZjFmYjZiMTRjODI4OTRhMGM2ODJkMTQ3ZTAxNWM5ZDoxZmI2YjE0YzgyODk0YTBjNjgyZDE0N2UwMTVjOWQ=' - - const data = await axios - .get(`https://tv.trueid.net/api/channel/getChannelListByAllCate?lang=${lang}&country=th`, { - headers: { - authorization: `Basic ${ACCESS_TOKEN}` - } - }) - .then(r => r.data) - .catch(console.error) - - return data.data.channelsList - .find(i => i.catSlug === 'TrueID : All') - .channels.map(item => { - return { - lang, - site_id: item.slug, - name: item.title - } - }) - } -} - -function parseDescription(item, lang) { - const description = item.info?.[`synopsis_${lang}`] - return description && description !== '.' ? description : null -} - -function parseImage(item) { - return item.info?.image || null -} - -function parseStart(item) { - return item.start_date ? dayjs.utc(item.start_date) : null -} - -function parseStop(item) { - return item.end_date ? dayjs.utc(item.end_date) : null -} - -function parseItems(content) { - const data = content ? JSON.parse(content) : null - return data?.pageProps?.epgList || [] -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') + +dayjs.extend(utc) + +module.exports = { + delay: 1000, + site: 'tv.trueid.net', + days: 1, + url({ channel }) { + return `https://tv.trueid.net/_next/data/1380644e0f1fb6b14c82894a0c682d147e015c9d/th-${channel.lang}.json?channelSlug=${channel.site_id}&path=${channel.site_id}` + }, + parser({ content, channel }) { + const programs = [] + parseItems(content, channel).forEach(item => { + programs.push({ + title: item.title, + description: parseDescription(item, channel.lang), + image: parseImage(item), + start: parseStart(item), + stop: parseStop(item) + }) + }) + + return programs + }, + async channels({ token, lang = 'en' }) { + const axios = require('axios') + const ACCESS_TOKEN = token + ? token + : 'MTM4MDY0NGUwZjFmYjZiMTRjODI4OTRhMGM2ODJkMTQ3ZTAxNWM5ZDoxZmI2YjE0YzgyODk0YTBjNjgyZDE0N2UwMTVjOWQ=' + + const data = await axios + .get(`https://tv.trueid.net/api/channel/getChannelListByAllCate?lang=${lang}&country=th`, { + headers: { + authorization: `Basic ${ACCESS_TOKEN}` + } + }) + .then(r => r.data) + .catch(console.error) + + return data.data.channelsList + .find(i => i.catSlug === 'TrueID : All') + .channels.map(item => { + return { + lang, + site_id: item.slug, + name: item.title + } + }) + } +} + +function parseDescription(item, lang) { + const description = item.info?.[`synopsis_${lang}`] + return description && description !== '.' ? description : null +} + +function parseImage(item) { + return item.info?.image || null +} + +function parseStart(item) { + return item.start_date ? dayjs.utc(item.start_date) : null +} + +function parseStop(item) { + return item.end_date ? dayjs.utc(item.end_date) : null +} + +function parseItems(content) { + const data = content ? JSON.parse(content) : null + return data?.pageProps?.epgList || [] +} diff --git a/sites/tv.trueid.net/tv.trueid.net.test.js b/sites/tv.trueid.net/tv.trueid.net.test.js index 02386c7ba..afd6d06fa 100644 --- a/sites/tv.trueid.net/tv.trueid.net.test.js +++ b/sites/tv.trueid.net/tv.trueid.net.test.js @@ -1,62 +1,62 @@ -const { parser, url } = require('./tv.trueid.net.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-12-11').startOf('d') -const channel = { - lang: 'en', - site_id: 'true-movie-hits', - xmltv_id: 'TrueMovieHits.th' -} -const channelTh = Object.assign({}, channel, { lang: 'th' }) -const data = fs.readFileSync(path.resolve(__dirname, '__data__/data.json')) - -it('can generate valid url', () => { - const result = url({ channel, date }) - expect(result).toBe( - 'https://tv.trueid.net/_next/data/1380644e0f1fb6b14c82894a0c682d147e015c9d/th-en.json?channelSlug=true-movie-hits&path=true-movie-hits' - ) -}) - -it('can parse English response', () => { - const result = parser({ date, channel, content: data }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - expect(result[0]).toMatchObject({ - start: '2023-12-11T19:05:00.000Z', - stop: '2023-12-11T20:55:00.000Z', - title: 'The Last Witch Hunter', - description: - 'A young man is all that stands between humanity and the most horrifying witches in history.', - image: 'https://bms.dmpcdn.com/uploads/pic/381f853da5f4a310bf248357fed21a57.jpg' - }) -}) - -it('can parse Thai response', () => { - const result = parser({ date, channel: channelTh, content: data }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - expect(result[0]).toMatchObject({ - start: '2023-12-11T19:05:00.000Z', - stop: '2023-12-11T20:55:00.000Z', - title: 'The Last Witch Hunter', - description: - 'หนุ่มนักล่าแม่มดถูกสาปให้เป็นอมตะจนกระทั่งราชินีแม่มดได้ฟื้นคืนชีพขึ้นมาจึงมีเพียงเขาคนเดียวเท่านั้นที่จะสามารถกอบกู้มวลมนุษยชาติได้', - image: 'https://bms.dmpcdn.com/uploads/pic/381f853da5f4a310bf248357fed21a57.jpg' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ date, channel, content: '{}' }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./tv.trueid.net.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-12-11').startOf('d') +const channel = { + lang: 'en', + site_id: 'true-movie-hits', + xmltv_id: 'TrueMovieHits.th' +} +const channelTh = Object.assign({}, channel, { lang: 'th' }) +const data = fs.readFileSync(path.resolve(__dirname, '__data__/data.json')) + +it('can generate valid url', () => { + const result = url({ channel, date }) + expect(result).toBe( + 'https://tv.trueid.net/_next/data/1380644e0f1fb6b14c82894a0c682d147e015c9d/th-en.json?channelSlug=true-movie-hits&path=true-movie-hits' + ) +}) + +it('can parse English response', () => { + const result = parser({ date, channel, content: data }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + expect(result[0]).toMatchObject({ + start: '2023-12-11T19:05:00.000Z', + stop: '2023-12-11T20:55:00.000Z', + title: 'The Last Witch Hunter', + description: + 'A young man is all that stands between humanity and the most horrifying witches in history.', + image: 'https://bms.dmpcdn.com/uploads/pic/381f853da5f4a310bf248357fed21a57.jpg' + }) +}) + +it('can parse Thai response', () => { + const result = parser({ date, channel: channelTh, content: data }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + expect(result[0]).toMatchObject({ + start: '2023-12-11T19:05:00.000Z', + stop: '2023-12-11T20:55:00.000Z', + title: 'The Last Witch Hunter', + description: + 'หนุ่มนักล่าแม่มดถูกสาปให้เป็นอมตะจนกระทั่งราชินีแม่มดได้ฟื้นคืนชีพขึ้นมาจึงมีเพียงเขาคนเดียวเท่านั้นที่จะสามารถกอบกู้มวลมนุษยชาติได้', + image: 'https://bms.dmpcdn.com/uploads/pic/381f853da5f4a310bf248357fed21a57.jpg' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ date, channel, content: '{}' }) + expect(result).toMatchObject([]) +}) diff --git a/sites/tv.yandex.ru/tv.yandex.ru.config.js b/sites/tv.yandex.ru/tv.yandex.ru.config.js index 0dd19c00c..241069dd6 100644 --- a/sites/tv.yandex.ru/tv.yandex.ru.config.js +++ b/sites/tv.yandex.ru/tv.yandex.ru.config.js @@ -1,295 +1,295 @@ -const dayjs = require('dayjs') -const doFetch = require('@ntlab/sfetch') -const debug = require('debug')('site:tv.yandex.ru') - -doFetch.setDebugger(debug).setMaxWorker(10) - -// enable to fetch guide description but its take a longer time -const detailedGuide = true - -// update this data by heading to https://tv.yandex.ru and change the values accordingly -const cookies = { - i: 'eIUfSP+/mzQWXcH+Cuz8o1vY+D2K8fhBd6Sj0xvbPZeO4l3cY+BvMp8fFIuM17l6UE1Z5+R2a18lP00ex9iYVJ+VT+c=', - spravka: - 'dD0xNzM0MjA0NjM4O2k9MTI1LjE2NC4xNDkuMjAwO0Q9QTVCQ0IyOTI5RDQxNkU5NkEyOTcwMTNDMzZGMDAzNjRDNTFFNDM4QkE2Q0IyOTJDRjhCOTZDRDIzODdBQzk2MzRFRDc5QTk2Qjc2OEI1MUY5MTM5M0QzNkY3OEQ2OUY3OTUwNkQ3RjBCOEJGOEJDMjAwMTQ0RDUwRkFCMDNEQzJFMDI2OEI5OTk5OUJBNEFERUYwOEQ1MjUwQTE0QTI3RDU1MEQwM0U0O3U9MTczNDIwNDYzODUyNDYyNzg1NDtoPTIxNTc0ZTc2MDQ1ZjcwMDBkYmY0NTVkM2Q2ZWMyM2Y1', - yandexuid: '1197179041732383499', - yashr: '4682342911732383504', - yuidss: '1197179041732383499', - user_display: 824 -} -const headers = { - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 OPR/114.0.0.0' -} -const caches = {} - -module.exports = { - site: 'tv.yandex.ru', - days: 2, - url({ date }) { - return getUrl(date) - }, - request: { - cache: { - ttl: 3600000 // 1 hour - }, - headers: getHeaders() - }, - async parser({ content, date, channel }) { - const programs = [] - const events = [] - - if (content && parseContent(content, date, true)) { - const cacheid = date.format('YYYY-MM-DD') - if (!caches[cacheid]) { - debug(`Please wait while fetching schedules for ${cacheid}`) - caches[cacheid] = await fetchSchedules({ date, content }) - } - if (detailedGuide) { - await fetchPrograms({ schedules: caches[cacheid], date, channel }) - } - caches[cacheid].forEach(schedule => { - schedule.events - .filter( - event => event.channelFamilyId == channel.site_id && date.isSame(event.start, 'day') - ) - .forEach(event => { - if (events.indexOf(event.id) < 0) { - events.push(event.id) - programs.push({ - title: event.title, - description: event.program.description, - category: event.program.type.name, - start: dayjs(event.start), - stop: dayjs(event.finish) - }) - } - }) - }) - } - - return programs - }, - async channels() { - const channels = [] - const included = [] - const schedules = await fetchSchedules({ date: dayjs() }) - schedules.forEach(schedule => { - if (schedule.channel && included.indexOf(schedule.channel.familyId) < 0) { - included.push(schedule.channel.familyId) - channels.push({ - lang: 'ru', - site_id: schedule.channel.familyId.toString(), - name: schedule.channel.title - }) - } - }) - - return channels - } -} - -async function fetchSchedules({ date, content = null }) { - const schedules = [] - const queues = [] - const fetches = [] - const url = getUrl(date) - - let mainApi - // parse content as schedules and add to queue if more requests is needed - const f = (src, res, headers) => { - if (src) { - fetches.push(src) - } - if (headers) { - parseCookies(headers) - } - const [q, s] = parseContent(res, date) - if (!mainApi) { - mainApi = true - if (caches.region) { - queues.push(getQueue(getUrl(date, caches.region), src)) - } - } - for (const url of q) { - if (fetches.indexOf(url) < 0) { - queues.push(getQueue(url, src)) - } - } - schedules.push(...s) - } - // is main html already fetched? - if (content) { - f(url, content) - } else { - queues.push(getQueue(url, 'https://tv.yandex.ru/')) - } - // fetch all queues - await doFetch(queues, f) - - return schedules -} - -async function fetchPrograms({ schedules, date, channel }) { - const queues = [] - schedules - .filter(schedule => schedule.channel.familyId == channel.site_id) - .forEach(schedule => { - queues.push( - ...schedule.events - .filter(event => date.isSame(event.start, 'day')) - .map(event => getQueue(getUrl(null, caches.region, null, event), 'https://tv.yandex.ru/')) - ) - }) - await doFetch(queues, (queue, res, headers) => { - if (headers) { - parseCookies(headers) - } - // is it a program? - if (res?.program) { - let updated = false - schedules.forEach(schedule => { - schedule.events.forEach(event => { - if (event.channelFamilyId === res.channelFamilyId && event.id === res.id) { - Object.assign(event, res) - updated = true - return true - } - }) - if (updated) { - return true - } - }) - } - }) -} - -function parseContent(content, date, checkOnly = false) { - const queues = [] - const schedules = [] - let valid = false - if (content) { - if (Buffer.isBuffer(content)) { - content = content.toString() - } - // got captcha, its look like our cookies has expired - if ( - content?.type === 'captcha' || - (typeof content === 'string' && content.match(/SmartCaptcha/)) - ) { - throw new Error('Got captcha, please goto https://tv.yandex.ru and update cookies!') - } - if (typeof content === 'object') { - let items - if (content.schedule) { - // fetch next request based on schedule map - if (Array.isArray(content.schedule.scheduleMap)) { - queues.push(...content.schedule.scheduleMap.map(m => getUrl(date, caches.region, m))) - } - // find some schedules? - if (Array.isArray(content.schedule.schedules)) { - items = content.schedule.schedules - } - } - // find another schedules? - if (Array.isArray(content.schedules)) { - items = content.schedules - } - // add programs - if (items && items.length) { - schedules.push(...getSchedules(items)) - } - } else { - // prepare headers for next http request - const [, region] = content.match(/region: '(\d+)'/i) || [null, null] - const [, initialSk] = content.match(/window.__INITIAL_SK__ = (.*);/i) || [null, null] - const [, sessionId] = content.match(/window.__USER_SESSION_ID__ = "(.*)";/i) || [null, null] - const tvSk = initialSk ? JSON.parse(initialSk) : {} - if (region) { - caches.region = region - } - if (tvSk.key) { - headers['X-Tv-Sk'] = tvSk.key - } - if (sessionId) { - headers['X-User-Session-Id'] = sessionId - } - if (checkOnly && region && tvSk.key && sessionId) { - valid = true - } - } - } - - return checkOnly ? valid : [queues, schedules] -} - -function parseCookies(headers) { - if (Array.isArray(headers['set-cookie'])) { - headers['set-cookie'].forEach(cookie => { - const [key, value] = cookie.split('; ')[0].split('=') - if (cookies[key] !== value) { - cookies[key] = value - debug(`Update cookie ${key}=${value}`) - } - }) - } -} - -function getSchedules(schedules) { - return schedules.filter(schedule => schedule.events.length) -} - -function getHeaders(data = {}) { - return Object.assign( - {}, - headers, - { - Cookie: Object.keys(cookies) - .map(cookie => `${cookie}=${cookies[cookie]}`) - .join('; ') - }, - data - ) -} - -function getUrl(date, region = null, page = null, event = null) { - let url = 'https://tv.yandex.ru/' - if (region) { - url += `api/${region}` - } - if (page && page.id !== undefined) { - url += `${url.endsWith('/') ? '' : '/'}main/chunk?page=${page.id}` - } - if (event && event.id !== undefined) { - url += `${url.endsWith('/') ? '' : '/'}event?eventId=${event.id}&programCoId=` - } - if (date) { - url += `${url.indexOf('?') < 0 ? '?' : '&'}date=${date.format('YYYY-MM-DD')}${ - !page ? '&grid=all' : '' - }&period=all-day` - } - if (page && page.id !== undefined && page.offset !== undefined) { - url += `${url.indexOf('?') < 0 ? '?' : '&'}offset=${page.offset}` - } - if (page && page.id !== undefined && page.limit !== undefined) { - url += `${url.indexOf('?') < 0 ? '?' : '&'}limit=${page.limit}` - } - return url -} - -function getQueue(url, referer) { - const data = { - Origin: 'https://tv.yandex.ru' - } - if (referer) { - data['Referer'] = referer - } - if (url.indexOf('api') > 0) { - data['X-Requested-With'] = 'XMLHttpRequest' - } - const headers = getHeaders(data) - return { - url, - params: { headers } - } -} +const dayjs = require('dayjs') +const doFetch = require('@ntlab/sfetch') +const debug = require('debug')('site:tv.yandex.ru') + +doFetch.setDebugger(debug).setMaxWorker(10) + +// enable to fetch guide description but its take a longer time +const detailedGuide = true + +// update this data by heading to https://tv.yandex.ru and change the values accordingly +const cookies = { + i: 'eIUfSP+/mzQWXcH+Cuz8o1vY+D2K8fhBd6Sj0xvbPZeO4l3cY+BvMp8fFIuM17l6UE1Z5+R2a18lP00ex9iYVJ+VT+c=', + spravka: + 'dD0xNzM0MjA0NjM4O2k9MTI1LjE2NC4xNDkuMjAwO0Q9QTVCQ0IyOTI5RDQxNkU5NkEyOTcwMTNDMzZGMDAzNjRDNTFFNDM4QkE2Q0IyOTJDRjhCOTZDRDIzODdBQzk2MzRFRDc5QTk2Qjc2OEI1MUY5MTM5M0QzNkY3OEQ2OUY3OTUwNkQ3RjBCOEJGOEJDMjAwMTQ0RDUwRkFCMDNEQzJFMDI2OEI5OTk5OUJBNEFERUYwOEQ1MjUwQTE0QTI3RDU1MEQwM0U0O3U9MTczNDIwNDYzODUyNDYyNzg1NDtoPTIxNTc0ZTc2MDQ1ZjcwMDBkYmY0NTVkM2Q2ZWMyM2Y1', + yandexuid: '1197179041732383499', + yashr: '4682342911732383504', + yuidss: '1197179041732383499', + user_display: 824 +} +const headers = { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 OPR/114.0.0.0' +} +const caches = {} + +module.exports = { + site: 'tv.yandex.ru', + days: 2, + url({ date }) { + return getUrl(date) + }, + request: { + cache: { + ttl: 3600000 // 1 hour + }, + headers: getHeaders() + }, + async parser({ content, date, channel }) { + const programs = [] + const events = [] + + if (content && parseContent(content, date, true)) { + const cacheid = date.format('YYYY-MM-DD') + if (!caches[cacheid]) { + debug(`Please wait while fetching schedules for ${cacheid}`) + caches[cacheid] = await fetchSchedules({ date, content }) + } + if (detailedGuide) { + await fetchPrograms({ schedules: caches[cacheid], date, channel }) + } + caches[cacheid].forEach(schedule => { + schedule.events + .filter( + event => event.channelFamilyId == channel.site_id && date.isSame(event.start, 'day') + ) + .forEach(event => { + if (events.indexOf(event.id) < 0) { + events.push(event.id) + programs.push({ + title: event.title, + description: event.program.description, + category: event.program.type.name, + start: dayjs(event.start), + stop: dayjs(event.finish) + }) + } + }) + }) + } + + return programs + }, + async channels() { + const channels = [] + const included = [] + const schedules = await fetchSchedules({ date: dayjs() }) + schedules.forEach(schedule => { + if (schedule.channel && included.indexOf(schedule.channel.familyId) < 0) { + included.push(schedule.channel.familyId) + channels.push({ + lang: 'ru', + site_id: schedule.channel.familyId.toString(), + name: schedule.channel.title + }) + } + }) + + return channels + } +} + +async function fetchSchedules({ date, content = null }) { + const schedules = [] + const queues = [] + const fetches = [] + const url = getUrl(date) + + let mainApi + // parse content as schedules and add to queue if more requests is needed + const f = (src, res, headers) => { + if (src) { + fetches.push(src) + } + if (headers) { + parseCookies(headers) + } + const [q, s] = parseContent(res, date) + if (!mainApi) { + mainApi = true + if (caches.region) { + queues.push(getQueue(getUrl(date, caches.region), src)) + } + } + for (const url of q) { + if (fetches.indexOf(url) < 0) { + queues.push(getQueue(url, src)) + } + } + schedules.push(...s) + } + // is main html already fetched? + if (content) { + f(url, content) + } else { + queues.push(getQueue(url, 'https://tv.yandex.ru/')) + } + // fetch all queues + await doFetch(queues, f) + + return schedules +} + +async function fetchPrograms({ schedules, date, channel }) { + const queues = [] + schedules + .filter(schedule => schedule.channel.familyId == channel.site_id) + .forEach(schedule => { + queues.push( + ...schedule.events + .filter(event => date.isSame(event.start, 'day')) + .map(event => getQueue(getUrl(null, caches.region, null, event), 'https://tv.yandex.ru/')) + ) + }) + await doFetch(queues, (queue, res, headers) => { + if (headers) { + parseCookies(headers) + } + // is it a program? + if (res?.program) { + let updated = false + schedules.forEach(schedule => { + schedule.events.forEach(event => { + if (event.channelFamilyId === res.channelFamilyId && event.id === res.id) { + Object.assign(event, res) + updated = true + return true + } + }) + if (updated) { + return true + } + }) + } + }) +} + +function parseContent(content, date, checkOnly = false) { + const queues = [] + const schedules = [] + let valid = false + if (content) { + if (Buffer.isBuffer(content)) { + content = content.toString() + } + // got captcha, its look like our cookies has expired + if ( + content?.type === 'captcha' || + (typeof content === 'string' && content.match(/SmartCaptcha/)) + ) { + throw new Error('Got captcha, please goto https://tv.yandex.ru and update cookies!') + } + if (typeof content === 'object') { + let items + if (content.schedule) { + // fetch next request based on schedule map + if (Array.isArray(content.schedule.scheduleMap)) { + queues.push(...content.schedule.scheduleMap.map(m => getUrl(date, caches.region, m))) + } + // find some schedules? + if (Array.isArray(content.schedule.schedules)) { + items = content.schedule.schedules + } + } + // find another schedules? + if (Array.isArray(content.schedules)) { + items = content.schedules + } + // add programs + if (items && items.length) { + schedules.push(...getSchedules(items)) + } + } else { + // prepare headers for next http request + const [, region] = content.match(/region: '(\d+)'/i) || [null, null] + const [, initialSk] = content.match(/window.__INITIAL_SK__ = (.*);/i) || [null, null] + const [, sessionId] = content.match(/window.__USER_SESSION_ID__ = "(.*)";/i) || [null, null] + const tvSk = initialSk ? JSON.parse(initialSk) : {} + if (region) { + caches.region = region + } + if (tvSk.key) { + headers['X-Tv-Sk'] = tvSk.key + } + if (sessionId) { + headers['X-User-Session-Id'] = sessionId + } + if (checkOnly && region && tvSk.key && sessionId) { + valid = true + } + } + } + + return checkOnly ? valid : [queues, schedules] +} + +function parseCookies(headers) { + if (Array.isArray(headers['set-cookie'])) { + headers['set-cookie'].forEach(cookie => { + const [key, value] = cookie.split('; ')[0].split('=') + if (cookies[key] !== value) { + cookies[key] = value + debug(`Update cookie ${key}=${value}`) + } + }) + } +} + +function getSchedules(schedules) { + return schedules.filter(schedule => schedule.events.length) +} + +function getHeaders(data = {}) { + return Object.assign( + {}, + headers, + { + Cookie: Object.keys(cookies) + .map(cookie => `${cookie}=${cookies[cookie]}`) + .join('; ') + }, + data + ) +} + +function getUrl(date, region = null, page = null, event = null) { + let url = 'https://tv.yandex.ru/' + if (region) { + url += `api/${region}` + } + if (page && page.id !== undefined) { + url += `${url.endsWith('/') ? '' : '/'}main/chunk?page=${page.id}` + } + if (event && event.id !== undefined) { + url += `${url.endsWith('/') ? '' : '/'}event?eventId=${event.id}&programCoId=` + } + if (date) { + url += `${url.indexOf('?') < 0 ? '?' : '&'}date=${date.format('YYYY-MM-DD')}${ + !page ? '&grid=all' : '' + }&period=all-day` + } + if (page && page.id !== undefined && page.offset !== undefined) { + url += `${url.indexOf('?') < 0 ? '?' : '&'}offset=${page.offset}` + } + if (page && page.id !== undefined && page.limit !== undefined) { + url += `${url.indexOf('?') < 0 ? '?' : '&'}limit=${page.limit}` + } + return url +} + +function getQueue(url, referer) { + const data = { + Origin: 'https://tv.yandex.ru' + } + if (referer) { + data['Referer'] = referer + } + if (url.indexOf('api') > 0) { + data['X-Requested-With'] = 'XMLHttpRequest' + } + const headers = getHeaders(data) + return { + url, + params: { headers } + } +} diff --git a/sites/tv24.co.uk/tv24.co.uk.config.js b/sites/tv24.co.uk/tv24.co.uk.config.js index 25458228d..5c4f6d6df 100644 --- a/sites/tv24.co.uk/tv24.co.uk.config.js +++ b/sites/tv24.co.uk/tv24.co.uk.config.js @@ -1,91 +1,91 @@ -const dayjs = require('dayjs') -const axios = require('axios') -const cheerio = require('cheerio') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'tv24.co.uk', - days: 2, - url: function ({ channel, date }) { - return `https://tv24.co.uk/x/channel/${channel.site_id}/0/${date.format('YYYY-MM-DD')}` - }, - parser: function ({ content, date }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - const prev = programs[programs.length - 1] - const $item = cheerio.load(item) - let start = parseStart($item, date) - if (prev) { - if (start.isBefore(prev.start)) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.add(30, 'm') - programs.push({ - title: parseTitle($item), - description: parseDescription($item), - start, - stop - }) - }) - - return programs - }, - async channels() { - let html = await axios - .get('https://tv24.co.uk/x/settings/addremove', { - headers: { - Cookie: 'selectedPostcode=-; selectedProvider=1000193' - } - }) - .then(r => r.data) - .catch(console.log) - let $ = cheerio.load(html) - - let channels = [] - $('li') - .toArray() - .forEach(item => { - const link = $(item).find('img').attr('src') - if (!link || link.includes('ic_channel_default')) return - const [, filename] = link.match(/channels\/(.*)\./) - const site_id = filename.replace('-l', '') - const name = $(item).find('h3').text().trim() - - channels.push({ - lang: 'en', - site_id, - name - }) - }) - - return channels - } -} - -function parseTitle($item) { - return $item('h3').text() -} - -function parseDescription($item) { - return $item('p').text() -} - -function parseStart($item, date) { - const time = $item('.time').text() - - return dayjs.utc(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD h:mma') -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('.program').toArray() -} +const dayjs = require('dayjs') +const axios = require('axios') +const cheerio = require('cheerio') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'tv24.co.uk', + days: 2, + url: function ({ channel, date }) { + return `https://tv24.co.uk/x/channel/${channel.site_id}/0/${date.format('YYYY-MM-DD')}` + }, + parser: function ({ content, date }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + const prev = programs[programs.length - 1] + const $item = cheerio.load(item) + let start = parseStart($item, date) + if (prev) { + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.add(30, 'm') + programs.push({ + title: parseTitle($item), + description: parseDescription($item), + start, + stop + }) + }) + + return programs + }, + async channels() { + let html = await axios + .get('https://tv24.co.uk/x/settings/addremove', { + headers: { + Cookie: 'selectedPostcode=-; selectedProvider=1000193' + } + }) + .then(r => r.data) + .catch(console.log) + let $ = cheerio.load(html) + + let channels = [] + $('li') + .toArray() + .forEach(item => { + const link = $(item).find('img').attr('src') + if (!link || link.includes('ic_channel_default')) return + const [, filename] = link.match(/channels\/(.*)\./) + const site_id = filename.replace('-l', '') + const name = $(item).find('h3').text().trim() + + channels.push({ + lang: 'en', + site_id, + name + }) + }) + + return channels + } +} + +function parseTitle($item) { + return $item('h3').text() +} + +function parseDescription($item) { + return $item('p').text() +} + +function parseStart($item, date) { + const time = $item('.time').text() + + return dayjs.utc(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD h:mma') +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('.program').toArray() +} diff --git a/sites/tv24.co.uk/tv24.co.uk.test.js b/sites/tv24.co.uk/tv24.co.uk.test.js index 60284eaa8..72330a99a 100644 --- a/sites/tv24.co.uk/tv24.co.uk.test.js +++ b/sites/tv24.co.uk/tv24.co.uk.test.js @@ -1,50 +1,50 @@ -const { parser, url } = require('./tv24.co.uk.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-08-28', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'bbc-two', - xmltv_id: 'BBCTwo.uk' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe('https://tv24.co.uk/x/channel/bbc-two/0/2022-08-28') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - const results = parser({ content, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2022-08-28T05:05:00.000Z', - stop: '2022-08-28T06:05:00.000Z', - title: "Gardeners' World", - description: - 'Arit Anderson discovers a paradise garden in Cambridge which has become a focal point for the local community, and Frances Tophill shares the joy of collecting and saving heirloom vegetable seeds on a visit to Pembrokeshire.' - }) - - expect(results[22]).toMatchObject({ - start: '2022-08-29T05:30:00.000Z', - stop: '2022-08-29T06:00:00.000Z', - title: 'Animal Park', - description: - "One of the park's vultures has laid an egg. It is ten years since Longleat had a successfully reared vulture chick, so the keepers send Hamza to find out if the parents are incubating their egg." - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: '' - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./tv24.co.uk.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-08-28', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'bbc-two', + xmltv_id: 'BBCTwo.uk' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe('https://tv24.co.uk/x/channel/bbc-two/0/2022-08-28') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + const results = parser({ content, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2022-08-28T05:05:00.000Z', + stop: '2022-08-28T06:05:00.000Z', + title: "Gardeners' World", + description: + 'Arit Anderson discovers a paradise garden in Cambridge which has become a focal point for the local community, and Frances Tophill shares the joy of collecting and saving heirloom vegetable seeds on a visit to Pembrokeshire.' + }) + + expect(results[22]).toMatchObject({ + start: '2022-08-29T05:30:00.000Z', + stop: '2022-08-29T06:00:00.000Z', + title: 'Animal Park', + description: + "One of the park's vultures has laid an egg. It is ten years since Longleat had a successfully reared vulture chick, so the keepers send Hamza to find out if the parents are incubating their egg." + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: '' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/tv24.se/tv24.se.config.js b/sites/tv24.se/tv24.se.config.js index 76d92535f..1b0c83d15 100644 --- a/sites/tv24.se/tv24.se.config.js +++ b/sites/tv24.se/tv24.se.config.js @@ -1,163 +1,163 @@ -const dayjs = require('dayjs') -const axios = require('axios') -const cheerio = require('cheerio') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'tv24.se', - days: 2, - url: function ({ channel, date }) { - return `https://tv24.se/x/channel/${channel.site_id}/0/${date.format('YYYY-MM-DD')}` - }, - parser: async function ({ content, date }) { - let programs = [] - const items = parseItems(content) - for (let item of items) { - const prev = programs[programs.length - 1] - const $item = cheerio.load(item) - let start = parseStart($item, date) - if (prev) { - if (start.isBefore(prev.start)) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.add(30, 'm') - const details = await loadProgramDetails($item) - programs.push({ - title: parseTitle($item), - description: details.description, - actors: details.actors, - image: details.image, - category: details.category, - sub_title: details.sub_title, - season: details.season, - episode: details.episode, - start, - stop - }) - } - - return programs - }, - async channels() { - let html = await axios - .get('https://tv24.se/x/settings/addremove') - .then(r => r.data) - .catch(console.log) - let $ = cheerio.load(html) - const nums = $('li') - .toArray() - .map(item => $(item).data('channel')) - html = await axios - .get('https://tv24.se', { - headers: { - Cookie: `selectedChannels=${nums.join(',')}` - } - }) - .then(r => r.data) - .catch(console.log) - $ = cheerio.load(html) - const items = $('li.c').toArray() - - return items.map(item => { - const name = $(item).find('h3').text().trim() - const link = $(item).find('.channel').attr('href') - const [, site_id] = link.match(/\/kanal\/(.*)/) || [null, null] - - return { - lang: 'sv', - site_id, - name - } - }) - } -} - -async function loadProgramDetails($item) { - const programId = $item('a').attr('href') - const data = await axios - .get(`https://tv24.se/x${programId}/0/0`) - .then(r => r.data) - .catch(console.error) - if (!data) return Promise.resolve({}) - const $ = cheerio.load(data.contentBefore + data.contentAfter) - - return Promise.resolve({ - image: parseImage($), - actors: parseActors($), - description: parseDescription($), - category: parseCategory($), - sub_title: parseSubTitle($), - season: parseSeason($), - episode: parseEpisode($) - }) -} - -function parseImage($) { - const style = $('.image > .actual').attr('style') - const [, url] = style.match(/background-image: url\('([^']+)'\)/) - - return url -} - -function parseSeason($) { - const [, season] = $('.sub-title') - .text() - .trim() - .match(/Säsong (\d+)/) || [null, ''] - - return parseInt(season) -} - -function parseEpisode($) { - const [, episode] = $('.sub-title') - .text() - .trim() - .match(/Avsnitt (\d+)/) || [null, ''] - - return parseInt(episode) -} - -function parseSubTitle($) { - const [, subtitle] = $('.sub-title').text().trim().split(': ') - - return subtitle -} - -function parseCategory($) { - return $('.extras > dt:contains(Kategori)').next().text().trim().split(' / ') -} - -function parseActors($) { - return $('.cast > li') - .map((i, el) => { - return $(el).find('.name').text().trim() - }) - .get() -} - -function parseDescription($) { - return $('.info > p').text().trim() -} - -function parseTitle($item) { - return $item('h3').text() -} - -function parseStart($item, date) { - const time = $item('.time') - - return dayjs.utc(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm') -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('.program').toArray() -} +const dayjs = require('dayjs') +const axios = require('axios') +const cheerio = require('cheerio') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'tv24.se', + days: 2, + url: function ({ channel, date }) { + return `https://tv24.se/x/channel/${channel.site_id}/0/${date.format('YYYY-MM-DD')}` + }, + parser: async function ({ content, date }) { + let programs = [] + const items = parseItems(content) + for (let item of items) { + const prev = programs[programs.length - 1] + const $item = cheerio.load(item) + let start = parseStart($item, date) + if (prev) { + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.add(30, 'm') + const details = await loadProgramDetails($item) + programs.push({ + title: parseTitle($item), + description: details.description, + actors: details.actors, + image: details.image, + category: details.category, + sub_title: details.sub_title, + season: details.season, + episode: details.episode, + start, + stop + }) + } + + return programs + }, + async channels() { + let html = await axios + .get('https://tv24.se/x/settings/addremove') + .then(r => r.data) + .catch(console.log) + let $ = cheerio.load(html) + const nums = $('li') + .toArray() + .map(item => $(item).data('channel')) + html = await axios + .get('https://tv24.se', { + headers: { + Cookie: `selectedChannels=${nums.join(',')}` + } + }) + .then(r => r.data) + .catch(console.log) + $ = cheerio.load(html) + const items = $('li.c').toArray() + + return items.map(item => { + const name = $(item).find('h3').text().trim() + const link = $(item).find('.channel').attr('href') + const [, site_id] = link.match(/\/kanal\/(.*)/) || [null, null] + + return { + lang: 'sv', + site_id, + name + } + }) + } +} + +async function loadProgramDetails($item) { + const programId = $item('a').attr('href') + const data = await axios + .get(`https://tv24.se/x${programId}/0/0`) + .then(r => r.data) + .catch(console.error) + if (!data) return Promise.resolve({}) + const $ = cheerio.load(data.contentBefore + data.contentAfter) + + return Promise.resolve({ + image: parseImage($), + actors: parseActors($), + description: parseDescription($), + category: parseCategory($), + sub_title: parseSubTitle($), + season: parseSeason($), + episode: parseEpisode($) + }) +} + +function parseImage($) { + const style = $('.image > .actual').attr('style') + const [, url] = style.match(/background-image: url\('([^']+)'\)/) + + return url +} + +function parseSeason($) { + const [, season] = $('.sub-title') + .text() + .trim() + .match(/Säsong (\d+)/) || [null, ''] + + return parseInt(season) +} + +function parseEpisode($) { + const [, episode] = $('.sub-title') + .text() + .trim() + .match(/Avsnitt (\d+)/) || [null, ''] + + return parseInt(episode) +} + +function parseSubTitle($) { + const [, subtitle] = $('.sub-title').text().trim().split(': ') + + return subtitle +} + +function parseCategory($) { + return $('.extras > dt:contains(Kategori)').next().text().trim().split(' / ') +} + +function parseActors($) { + return $('.cast > li') + .map((i, el) => { + return $(el).find('.name').text().trim() + }) + .get() +} + +function parseDescription($) { + return $('.info > p').text().trim() +} + +function parseTitle($item) { + return $item('h3').text() +} + +function parseStart($item, date) { + const time = $item('.time') + + return dayjs.utc(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm') +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('.program').toArray() +} diff --git a/sites/tv24.se/tv24.se.test.js b/sites/tv24.se/tv24.se.test.js index bfeeb2062..f7535e659 100644 --- a/sites/tv24.se/tv24.se.test.js +++ b/sites/tv24.se/tv24.se.test.js @@ -1,75 +1,75 @@ -const { parser, url } = require('./tv24.se.config.js') -const fs = require('fs') -const path = require('path') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -const date = dayjs.utc('2022-08-26', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'svt1', - xmltv_id: 'SVT1.se' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe('https://tv24.se/x/channel/svt1/0/2022-08-26') -}) - -it('can parse response', async () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - - axios.get.mockImplementation(url => { - if (url === 'https://tv24.se/x/b/rh7f40-1hkm/0/0') { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program1.json'))) - }) - } else if (url === 'https://tv24.se/x/b/rh9dhc-1hkm/0/0') { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program2.json'))) - }) - } else { - return Promise.resolve({ data: '' }) - } - }) - - let results = await parser({ content, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2022-08-26T04:00:00.000Z', - stop: '2022-08-26T07:10:00.000Z', - title: 'Morgonstudion', - image: 'https://jrsy.tmsimg.com/assets/p14436175_i_h9_ad.jpg', - description: - 'Dagens viktigaste nyheter och analyser med ständiga uppdateringar. Vi sänder direkt inrikes- och utrikesnyheter inklusive sport, kultur och nöje. Dessutom intervjuer med aktuella gäster. Nyhetssammanfattningar varje kvart med start kl 06.00.', - actors: ['Carolina Neurath', 'Karin Magnusson', 'Pelle Nilsson', 'Ted Wigren'] - }) - - expect(results[33]).toMatchObject({ - start: '2022-08-27T05:20:00.000Z', - stop: '2022-08-27T05:50:00.000Z', - title: 'Uppdrag granskning', - image: 'https://jrsy.tmsimg.com/assets/p22818697_e_h9_aa.jpg', - description: - 'När samtliga sex män frias för ännu en skjutning växer vreden inom polisen. Ökningen av skjutningar i Sverige ligger i topp i Europa - och nu är våldsspiralen på väg mot ett nattsvart rekord. Hur blev Sverige landet där mördare går fria?', - actors: ['Karin Mattisson', 'Ali Fegan'], - category: ['Dokumentär', 'Samhällsfrågor'], - season: 23, - episode: 5, - sub_title: 'Där mördare går fria' - }) -}) - -it('can handle empty guide', async () => { - const result = await parser({ content: '' }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./tv24.se.config.js') +const fs = require('fs') +const path = require('path') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.utc('2022-08-26', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'svt1', + xmltv_id: 'SVT1.se' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe('https://tv24.se/x/channel/svt1/0/2022-08-26') +}) + +it('can parse response', async () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + + axios.get.mockImplementation(url => { + if (url === 'https://tv24.se/x/b/rh7f40-1hkm/0/0') { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program1.json'))) + }) + } else if (url === 'https://tv24.se/x/b/rh9dhc-1hkm/0/0') { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program2.json'))) + }) + } else { + return Promise.resolve({ data: '' }) + } + }) + + let results = await parser({ content, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2022-08-26T04:00:00.000Z', + stop: '2022-08-26T07:10:00.000Z', + title: 'Morgonstudion', + image: 'https://jrsy.tmsimg.com/assets/p14436175_i_h9_ad.jpg', + description: + 'Dagens viktigaste nyheter och analyser med ständiga uppdateringar. Vi sänder direkt inrikes- och utrikesnyheter inklusive sport, kultur och nöje. Dessutom intervjuer med aktuella gäster. Nyhetssammanfattningar varje kvart med start kl 06.00.', + actors: ['Carolina Neurath', 'Karin Magnusson', 'Pelle Nilsson', 'Ted Wigren'] + }) + + expect(results[33]).toMatchObject({ + start: '2022-08-27T05:20:00.000Z', + stop: '2022-08-27T05:50:00.000Z', + title: 'Uppdrag granskning', + image: 'https://jrsy.tmsimg.com/assets/p22818697_e_h9_aa.jpg', + description: + 'När samtliga sex män frias för ännu en skjutning växer vreden inom polisen. Ökningen av skjutningar i Sverige ligger i topp i Europa - och nu är våldsspiralen på väg mot ett nattsvart rekord. Hur blev Sverige landet där mördare går fria?', + actors: ['Karin Mattisson', 'Ali Fegan'], + category: ['Dokumentär', 'Samhällsfrågor'], + season: 23, + episode: 5, + sub_title: 'Där mördare går fria' + }) +}) + +it('can handle empty guide', async () => { + const result = await parser({ content: '' }) + expect(result).toMatchObject([]) +}) diff --git a/sites/tv2go.t-2.net/jquery.md5.js b/sites/tv2go.t-2.net/jquery.md5.js index a8f351dda..43518dc67 100644 --- a/sites/tv2go.t-2.net/jquery.md5.js +++ b/sites/tv2go.t-2.net/jquery.md5.js @@ -1,264 +1,264 @@ -/* - * jQuery MD5 Plugin 1.2.1 - * https://github.com/blueimp/jQuery-MD5 - * - * Copyright 2010, Sebastian Tschan - * https://blueimp.net - * - * Licensed under the MIT license: - * http://creativecommons.org/licenses/MIT/ - * - * Based on - * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message - * Digest Algorithm, as defined in RFC 1321. - * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009 - * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet - * Distributed under the BSD License - * See http://pajhome.org.uk/crypt/md5 for more info. - */ - -/* - * Add integers, wrapping at 2^32. This uses 16-bit operations internally - * to work around bugs in some JS interpreters. - */ -function safe_add(x, y) { - var lsw = (x & 0xffff) + (y & 0xffff), - msw = (x >> 16) + (y >> 16) + (lsw >> 16) - return (msw << 16) | (lsw & 0xffff) -} - -/* - * Bitwise rotate a 32-bit number to the left. - */ -function bit_rol(num, cnt) { - return (num << cnt) | (num >>> (32 - cnt)) -} - -/* - * These functions implement the four basic operations the algorithm uses. - */ -function md5_cmn(q, a, b, x, s, t) { - return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s), b) -} -function md5_ff(a, b, c, d, x, s, t) { - return md5_cmn((b & c) | (~b & d), a, b, x, s, t) -} -function md5_gg(a, b, c, d, x, s, t) { - return md5_cmn((b & d) | (c & ~d), a, b, x, s, t) -} -function md5_hh(a, b, c, d, x, s, t) { - return md5_cmn(b ^ c ^ d, a, b, x, s, t) -} -function md5_ii(a, b, c, d, x, s, t) { - return md5_cmn(c ^ (b | ~d), a, b, x, s, t) -} - -/* - * Calculate the MD5 of an array of little-endian words, and a bit length. - */ -function binl_md5(x, len) { - /* append padding */ - x[len >> 5] |= 0x80 << len % 32 - x[(((len + 64) >>> 9) << 4) + 14] = len - - var i, - olda, - oldb, - oldc, - oldd, - a = 1732584193, - b = -271733879, - c = -1732584194, - d = 271733878 - - for (i = 0; i < x.length; i += 16) { - olda = a - oldb = b - oldc = c - oldd = d - - a = md5_ff(a, b, c, d, x[i], 7, -680876936) - d = md5_ff(d, a, b, c, x[i + 1], 12, -389564586) - c = md5_ff(c, d, a, b, x[i + 2], 17, 606105819) - b = md5_ff(b, c, d, a, x[i + 3], 22, -1044525330) - a = md5_ff(a, b, c, d, x[i + 4], 7, -176418897) - d = md5_ff(d, a, b, c, x[i + 5], 12, 1200080426) - c = md5_ff(c, d, a, b, x[i + 6], 17, -1473231341) - b = md5_ff(b, c, d, a, x[i + 7], 22, -45705983) - a = md5_ff(a, b, c, d, x[i + 8], 7, 1770035416) - d = md5_ff(d, a, b, c, x[i + 9], 12, -1958414417) - c = md5_ff(c, d, a, b, x[i + 10], 17, -42063) - b = md5_ff(b, c, d, a, x[i + 11], 22, -1990404162) - a = md5_ff(a, b, c, d, x[i + 12], 7, 1804603682) - d = md5_ff(d, a, b, c, x[i + 13], 12, -40341101) - c = md5_ff(c, d, a, b, x[i + 14], 17, -1502002290) - b = md5_ff(b, c, d, a, x[i + 15], 22, 1236535329) - - a = md5_gg(a, b, c, d, x[i + 1], 5, -165796510) - d = md5_gg(d, a, b, c, x[i + 6], 9, -1069501632) - c = md5_gg(c, d, a, b, x[i + 11], 14, 643717713) - b = md5_gg(b, c, d, a, x[i], 20, -373897302) - a = md5_gg(a, b, c, d, x[i + 5], 5, -701558691) - d = md5_gg(d, a, b, c, x[i + 10], 9, 38016083) - c = md5_gg(c, d, a, b, x[i + 15], 14, -660478335) - b = md5_gg(b, c, d, a, x[i + 4], 20, -405537848) - a = md5_gg(a, b, c, d, x[i + 9], 5, 568446438) - d = md5_gg(d, a, b, c, x[i + 14], 9, -1019803690) - c = md5_gg(c, d, a, b, x[i + 3], 14, -187363961) - b = md5_gg(b, c, d, a, x[i + 8], 20, 1163531501) - a = md5_gg(a, b, c, d, x[i + 13], 5, -1444681467) - d = md5_gg(d, a, b, c, x[i + 2], 9, -51403784) - c = md5_gg(c, d, a, b, x[i + 7], 14, 1735328473) - b = md5_gg(b, c, d, a, x[i + 12], 20, -1926607734) - - a = md5_hh(a, b, c, d, x[i + 5], 4, -378558) - d = md5_hh(d, a, b, c, x[i + 8], 11, -2022574463) - c = md5_hh(c, d, a, b, x[i + 11], 16, 1839030562) - b = md5_hh(b, c, d, a, x[i + 14], 23, -35309556) - a = md5_hh(a, b, c, d, x[i + 1], 4, -1530992060) - d = md5_hh(d, a, b, c, x[i + 4], 11, 1272893353) - c = md5_hh(c, d, a, b, x[i + 7], 16, -155497632) - b = md5_hh(b, c, d, a, x[i + 10], 23, -1094730640) - a = md5_hh(a, b, c, d, x[i + 13], 4, 681279174) - d = md5_hh(d, a, b, c, x[i], 11, -358537222) - c = md5_hh(c, d, a, b, x[i + 3], 16, -722521979) - b = md5_hh(b, c, d, a, x[i + 6], 23, 76029189) - a = md5_hh(a, b, c, d, x[i + 9], 4, -640364487) - d = md5_hh(d, a, b, c, x[i + 12], 11, -421815835) - c = md5_hh(c, d, a, b, x[i + 15], 16, 530742520) - b = md5_hh(b, c, d, a, x[i + 2], 23, -995338651) - - a = md5_ii(a, b, c, d, x[i], 6, -198630844) - d = md5_ii(d, a, b, c, x[i + 7], 10, 1126891415) - c = md5_ii(c, d, a, b, x[i + 14], 15, -1416354905) - b = md5_ii(b, c, d, a, x[i + 5], 21, -57434055) - a = md5_ii(a, b, c, d, x[i + 12], 6, 1700485571) - d = md5_ii(d, a, b, c, x[i + 3], 10, -1894986606) - c = md5_ii(c, d, a, b, x[i + 10], 15, -1051523) - b = md5_ii(b, c, d, a, x[i + 1], 21, -2054922799) - a = md5_ii(a, b, c, d, x[i + 8], 6, 1873313359) - d = md5_ii(d, a, b, c, x[i + 15], 10, -30611744) - c = md5_ii(c, d, a, b, x[i + 6], 15, -1560198380) - b = md5_ii(b, c, d, a, x[i + 13], 21, 1309151649) - a = md5_ii(a, b, c, d, x[i + 4], 6, -145523070) - d = md5_ii(d, a, b, c, x[i + 11], 10, -1120210379) - c = md5_ii(c, d, a, b, x[i + 2], 15, 718787259) - b = md5_ii(b, c, d, a, x[i + 9], 21, -343485551) - - a = safe_add(a, olda) - b = safe_add(b, oldb) - c = safe_add(c, oldc) - d = safe_add(d, oldd) - } - return [a, b, c, d] -} - -/* - * Convert an array of little-endian words to a string - */ -function binl2rstr(input) { - var i, - output = '' - for (i = 0; i < input.length * 32; i += 8) { - output += String.fromCharCode((input[i >> 5] >>> i % 32) & 0xff) - } - return output -} - -/* - * Convert a raw string to an array of little-endian words - * Characters >255 have their high-byte silently ignored. - */ -function rstr2binl(input) { - var i, - output = [] - output[(input.length >> 2) - 1] = undefined - for (i = 0; i < output.length; i += 1) { - output[i] = 0 - } - for (i = 0; i < input.length * 8; i += 8) { - output[i >> 5] |= (input.charCodeAt(i / 8) & 0xff) << i % 32 - } - return output -} - -/* - * Calculate the MD5 of a raw string - */ -function rstr_md5(s) { - return binl2rstr(binl_md5(rstr2binl(s), s.length * 8)) -} - -/* - * Calculate the HMAC-MD5, of a key and some data (raw strings) - */ -function rstr_hmac_md5(key, data) { - var i, - bkey = rstr2binl(key), - ipad = [], - opad = [], - hash - ipad[15] = opad[15] = undefined - if (bkey.length > 16) { - bkey = binl_md5(bkey, key.length * 8) - } - for (i = 0; i < 16; i += 1) { - ipad[i] = bkey[i] ^ 0x36363636 - opad[i] = bkey[i] ^ 0x5c5c5c5c - } - hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8) - return binl2rstr(binl_md5(opad.concat(hash), 512 + 128)) -} - -/* - * Convert a raw string to a hex string - */ -function rstr2hex(input) { - var hex_tab = '0123456789abcdef', - output = '', - x, - i - for (i = 0; i < input.length; i += 1) { - x = input.charCodeAt(i) - output += hex_tab.charAt((x >>> 4) & 0x0f) + hex_tab.charAt(x & 0x0f) - } - return output -} - -/* - * Encode a string as utf-8 - */ -function str2rstr_utf8(input) { - return unescape(encodeURIComponent(input)) -} - -/* - * Take string arguments and return either raw or hex encoded strings - */ -function raw_md5(s) { - return rstr_md5(str2rstr_utf8(s)) -} -function hex_md5(s) { - return rstr2hex(raw_md5(s)) -} -function raw_hmac_md5(k, d) { - return rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d)) -} -function hex_hmac_md5(k, d) { - return rstr2hex(raw_hmac_md5(k, d)) -} - -module.exports = function (string, key, raw) { - if (!key) { - if (!raw) { - return hex_md5(string) - } else { - return raw_md5(string) - } - } - if (!raw) { - return hex_hmac_md5(key, string) - } else { - return raw_hmac_md5(key, string) - } -} +/* + * jQuery MD5 Plugin 1.2.1 + * https://github.com/blueimp/jQuery-MD5 + * + * Copyright 2010, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://creativecommons.org/licenses/MIT/ + * + * Based on + * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message + * Digest Algorithm, as defined in RFC 1321. + * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009 + * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet + * Distributed under the BSD License + * See http://pajhome.org.uk/crypt/md5 for more info. + */ + +/* + * Add integers, wrapping at 2^32. This uses 16-bit operations internally + * to work around bugs in some JS interpreters. + */ +function safe_add(x, y) { + var lsw = (x & 0xffff) + (y & 0xffff), + msw = (x >> 16) + (y >> 16) + (lsw >> 16) + return (msw << 16) | (lsw & 0xffff) +} + +/* + * Bitwise rotate a 32-bit number to the left. + */ +function bit_rol(num, cnt) { + return (num << cnt) | (num >>> (32 - cnt)) +} + +/* + * These functions implement the four basic operations the algorithm uses. + */ +function md5_cmn(q, a, b, x, s, t) { + return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s), b) +} +function md5_ff(a, b, c, d, x, s, t) { + return md5_cmn((b & c) | (~b & d), a, b, x, s, t) +} +function md5_gg(a, b, c, d, x, s, t) { + return md5_cmn((b & d) | (c & ~d), a, b, x, s, t) +} +function md5_hh(a, b, c, d, x, s, t) { + return md5_cmn(b ^ c ^ d, a, b, x, s, t) +} +function md5_ii(a, b, c, d, x, s, t) { + return md5_cmn(c ^ (b | ~d), a, b, x, s, t) +} + +/* + * Calculate the MD5 of an array of little-endian words, and a bit length. + */ +function binl_md5(x, len) { + /* append padding */ + x[len >> 5] |= 0x80 << len % 32 + x[(((len + 64) >>> 9) << 4) + 14] = len + + var i, + olda, + oldb, + oldc, + oldd, + a = 1732584193, + b = -271733879, + c = -1732584194, + d = 271733878 + + for (i = 0; i < x.length; i += 16) { + olda = a + oldb = b + oldc = c + oldd = d + + a = md5_ff(a, b, c, d, x[i], 7, -680876936) + d = md5_ff(d, a, b, c, x[i + 1], 12, -389564586) + c = md5_ff(c, d, a, b, x[i + 2], 17, 606105819) + b = md5_ff(b, c, d, a, x[i + 3], 22, -1044525330) + a = md5_ff(a, b, c, d, x[i + 4], 7, -176418897) + d = md5_ff(d, a, b, c, x[i + 5], 12, 1200080426) + c = md5_ff(c, d, a, b, x[i + 6], 17, -1473231341) + b = md5_ff(b, c, d, a, x[i + 7], 22, -45705983) + a = md5_ff(a, b, c, d, x[i + 8], 7, 1770035416) + d = md5_ff(d, a, b, c, x[i + 9], 12, -1958414417) + c = md5_ff(c, d, a, b, x[i + 10], 17, -42063) + b = md5_ff(b, c, d, a, x[i + 11], 22, -1990404162) + a = md5_ff(a, b, c, d, x[i + 12], 7, 1804603682) + d = md5_ff(d, a, b, c, x[i + 13], 12, -40341101) + c = md5_ff(c, d, a, b, x[i + 14], 17, -1502002290) + b = md5_ff(b, c, d, a, x[i + 15], 22, 1236535329) + + a = md5_gg(a, b, c, d, x[i + 1], 5, -165796510) + d = md5_gg(d, a, b, c, x[i + 6], 9, -1069501632) + c = md5_gg(c, d, a, b, x[i + 11], 14, 643717713) + b = md5_gg(b, c, d, a, x[i], 20, -373897302) + a = md5_gg(a, b, c, d, x[i + 5], 5, -701558691) + d = md5_gg(d, a, b, c, x[i + 10], 9, 38016083) + c = md5_gg(c, d, a, b, x[i + 15], 14, -660478335) + b = md5_gg(b, c, d, a, x[i + 4], 20, -405537848) + a = md5_gg(a, b, c, d, x[i + 9], 5, 568446438) + d = md5_gg(d, a, b, c, x[i + 14], 9, -1019803690) + c = md5_gg(c, d, a, b, x[i + 3], 14, -187363961) + b = md5_gg(b, c, d, a, x[i + 8], 20, 1163531501) + a = md5_gg(a, b, c, d, x[i + 13], 5, -1444681467) + d = md5_gg(d, a, b, c, x[i + 2], 9, -51403784) + c = md5_gg(c, d, a, b, x[i + 7], 14, 1735328473) + b = md5_gg(b, c, d, a, x[i + 12], 20, -1926607734) + + a = md5_hh(a, b, c, d, x[i + 5], 4, -378558) + d = md5_hh(d, a, b, c, x[i + 8], 11, -2022574463) + c = md5_hh(c, d, a, b, x[i + 11], 16, 1839030562) + b = md5_hh(b, c, d, a, x[i + 14], 23, -35309556) + a = md5_hh(a, b, c, d, x[i + 1], 4, -1530992060) + d = md5_hh(d, a, b, c, x[i + 4], 11, 1272893353) + c = md5_hh(c, d, a, b, x[i + 7], 16, -155497632) + b = md5_hh(b, c, d, a, x[i + 10], 23, -1094730640) + a = md5_hh(a, b, c, d, x[i + 13], 4, 681279174) + d = md5_hh(d, a, b, c, x[i], 11, -358537222) + c = md5_hh(c, d, a, b, x[i + 3], 16, -722521979) + b = md5_hh(b, c, d, a, x[i + 6], 23, 76029189) + a = md5_hh(a, b, c, d, x[i + 9], 4, -640364487) + d = md5_hh(d, a, b, c, x[i + 12], 11, -421815835) + c = md5_hh(c, d, a, b, x[i + 15], 16, 530742520) + b = md5_hh(b, c, d, a, x[i + 2], 23, -995338651) + + a = md5_ii(a, b, c, d, x[i], 6, -198630844) + d = md5_ii(d, a, b, c, x[i + 7], 10, 1126891415) + c = md5_ii(c, d, a, b, x[i + 14], 15, -1416354905) + b = md5_ii(b, c, d, a, x[i + 5], 21, -57434055) + a = md5_ii(a, b, c, d, x[i + 12], 6, 1700485571) + d = md5_ii(d, a, b, c, x[i + 3], 10, -1894986606) + c = md5_ii(c, d, a, b, x[i + 10], 15, -1051523) + b = md5_ii(b, c, d, a, x[i + 1], 21, -2054922799) + a = md5_ii(a, b, c, d, x[i + 8], 6, 1873313359) + d = md5_ii(d, a, b, c, x[i + 15], 10, -30611744) + c = md5_ii(c, d, a, b, x[i + 6], 15, -1560198380) + b = md5_ii(b, c, d, a, x[i + 13], 21, 1309151649) + a = md5_ii(a, b, c, d, x[i + 4], 6, -145523070) + d = md5_ii(d, a, b, c, x[i + 11], 10, -1120210379) + c = md5_ii(c, d, a, b, x[i + 2], 15, 718787259) + b = md5_ii(b, c, d, a, x[i + 9], 21, -343485551) + + a = safe_add(a, olda) + b = safe_add(b, oldb) + c = safe_add(c, oldc) + d = safe_add(d, oldd) + } + return [a, b, c, d] +} + +/* + * Convert an array of little-endian words to a string + */ +function binl2rstr(input) { + var i, + output = '' + for (i = 0; i < input.length * 32; i += 8) { + output += String.fromCharCode((input[i >> 5] >>> i % 32) & 0xff) + } + return output +} + +/* + * Convert a raw string to an array of little-endian words + * Characters >255 have their high-byte silently ignored. + */ +function rstr2binl(input) { + var i, + output = [] + output[(input.length >> 2) - 1] = undefined + for (i = 0; i < output.length; i += 1) { + output[i] = 0 + } + for (i = 0; i < input.length * 8; i += 8) { + output[i >> 5] |= (input.charCodeAt(i / 8) & 0xff) << i % 32 + } + return output +} + +/* + * Calculate the MD5 of a raw string + */ +function rstr_md5(s) { + return binl2rstr(binl_md5(rstr2binl(s), s.length * 8)) +} + +/* + * Calculate the HMAC-MD5, of a key and some data (raw strings) + */ +function rstr_hmac_md5(key, data) { + var i, + bkey = rstr2binl(key), + ipad = [], + opad = [], + hash + ipad[15] = opad[15] = undefined + if (bkey.length > 16) { + bkey = binl_md5(bkey, key.length * 8) + } + for (i = 0; i < 16; i += 1) { + ipad[i] = bkey[i] ^ 0x36363636 + opad[i] = bkey[i] ^ 0x5c5c5c5c + } + hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8) + return binl2rstr(binl_md5(opad.concat(hash), 512 + 128)) +} + +/* + * Convert a raw string to a hex string + */ +function rstr2hex(input) { + var hex_tab = '0123456789abcdef', + output = '', + x, + i + for (i = 0; i < input.length; i += 1) { + x = input.charCodeAt(i) + output += hex_tab.charAt((x >>> 4) & 0x0f) + hex_tab.charAt(x & 0x0f) + } + return output +} + +/* + * Encode a string as utf-8 + */ +function str2rstr_utf8(input) { + return unescape(encodeURIComponent(input)) +} + +/* + * Take string arguments and return either raw or hex encoded strings + */ +function raw_md5(s) { + return rstr_md5(str2rstr_utf8(s)) +} +function hex_md5(s) { + return rstr2hex(raw_md5(s)) +} +function raw_hmac_md5(k, d) { + return rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d)) +} +function hex_hmac_md5(k, d) { + return rstr2hex(raw_hmac_md5(k, d)) +} + +module.exports = function (string, key, raw) { + if (!key) { + if (!raw) { + return hex_md5(string) + } else { + return raw_md5(string) + } + } + if (!raw) { + return hex_hmac_md5(key, string) + } else { + return raw_hmac_md5(key, string) + } +} diff --git a/sites/tv2go.t-2.net/tv2go.t-2.net.config.js b/sites/tv2go.t-2.net/tv2go.t-2.net.config.js index 2fc449f36..bb5ea9835 100644 --- a/sites/tv2go.t-2.net/tv2go.t-2.net.config.js +++ b/sites/tv2go.t-2.net/tv2go.t-2.net.config.js @@ -1,126 +1,126 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const md5 = require('./jquery.md5') - -const API = { - locale: 'sl-SI', - version: '9.4', - format: 'json', - uuid: '464830403846070', - token: '6dace810-55d5-11e3-949a-0800200c9a66' -} - -const config = { - site: 'tv2go.t-2.net', - days: 2, - url({ date, channel }) { - const data = config.request.data({ date, channel }) - const endpoint = 'client/tv/getEpg' - const hash = generateHash(data, endpoint) - - return `https://tv2go.t-2.net/Catherine/api/${API.version}/${API.format}/${API.uuid}/${hash}/${endpoint}` - }, - request: { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - data({ date, channel }) { - const channelId = parseInt(channel.site_id) - - return { - locale: API.locale, - channelId: [channelId], - startTime: date.valueOf(), - endTime: date.add(1, 'd').valueOf(), - imageInfo: [{ height: 500, width: 1100 }], - includeBookmarks: false, - includeShow: true - } - } - }, - parser({ content }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - programs.push({ - title: item.name, - category: parseCategory(item), - description: parseDescription(item), - image: parseImage(item), - start: parseStart(item), - stop: parseStop(item) - }) - }) - - return programs - }, - async channels() { - const data = { - locale: API.locale, - type: 'TV', - imageInfo: [{ type: 'DARK', height: 70, width: 98 }] - } - const endpoint = 'client/channels/list' - const hash = generateHash(data, endpoint) - const response = await axios - .post( - `https://tv2go.t-2.net/Catherine/api/${API.version}/${API.format}/${API.uuid}/${hash}/${endpoint}`, - data, - { - headers: { - 'Content-Type': 'application/json' - } - } - ) - .catch(console.log) - - return response.data.channels.map(item => { - return { - lang: 'sl', - site_id: item.id, - name: item.name - } - }) - } -} - -function parseStart(item) { - return dayjs(parseInt(item.startTimestamp)) -} - -function parseStop(item) { - return dayjs(parseInt(item.endTimestamp)) -} - -function parseImage(item) { - return item.images && item.images[0] ? `https://tv2go.t-2.net${item.images[0].url}` : null -} - -function parseCategory(item) { - return item.show && Array.isArray(item.show.genres) ? item.show.genres.map(c => c.name) : [] -} - -function parseDescription(item) { - return item.show ? item.show.shortDescription : null -} - -function parseItems(content) { - let data - try { - data = JSON.parse(content) - } catch { - return [] - } - if (!data || !Array.isArray(data.entries)) return [] - - return data.entries -} - -function generateHash(data, endpoint) { - const salt = `${API.token}${API.version}${API.format}${API.uuid}` - - return md5(salt + endpoint + JSON.stringify(data)) -} - -module.exports = config +const axios = require('axios') +const dayjs = require('dayjs') +const md5 = require('./jquery.md5') + +const API = { + locale: 'sl-SI', + version: '9.4', + format: 'json', + uuid: '464830403846070', + token: '6dace810-55d5-11e3-949a-0800200c9a66' +} + +const config = { + site: 'tv2go.t-2.net', + days: 2, + url({ date, channel }) { + const data = config.request.data({ date, channel }) + const endpoint = 'client/tv/getEpg' + const hash = generateHash(data, endpoint) + + return `https://tv2go.t-2.net/Catherine/api/${API.version}/${API.format}/${API.uuid}/${hash}/${endpoint}` + }, + request: { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + data({ date, channel }) { + const channelId = parseInt(channel.site_id) + + return { + locale: API.locale, + channelId: [channelId], + startTime: date.valueOf(), + endTime: date.add(1, 'd').valueOf(), + imageInfo: [{ height: 500, width: 1100 }], + includeBookmarks: false, + includeShow: true + } + } + }, + parser({ content }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + programs.push({ + title: item.name, + category: parseCategory(item), + description: parseDescription(item), + image: parseImage(item), + start: parseStart(item), + stop: parseStop(item) + }) + }) + + return programs + }, + async channels() { + const data = { + locale: API.locale, + type: 'TV', + imageInfo: [{ type: 'DARK', height: 70, width: 98 }] + } + const endpoint = 'client/channels/list' + const hash = generateHash(data, endpoint) + const response = await axios + .post( + `https://tv2go.t-2.net/Catherine/api/${API.version}/${API.format}/${API.uuid}/${hash}/${endpoint}`, + data, + { + headers: { + 'Content-Type': 'application/json' + } + } + ) + .catch(console.log) + + return response.data.channels.map(item => { + return { + lang: 'sl', + site_id: item.id, + name: item.name + } + }) + } +} + +function parseStart(item) { + return dayjs(parseInt(item.startTimestamp)) +} + +function parseStop(item) { + return dayjs(parseInt(item.endTimestamp)) +} + +function parseImage(item) { + return item.images && item.images[0] ? `https://tv2go.t-2.net${item.images[0].url}` : null +} + +function parseCategory(item) { + return item.show && Array.isArray(item.show.genres) ? item.show.genres.map(c => c.name) : [] +} + +function parseDescription(item) { + return item.show ? item.show.shortDescription : null +} + +function parseItems(content) { + let data + try { + data = JSON.parse(content) + } catch { + return [] + } + if (!data || !Array.isArray(data.entries)) return [] + + return data.entries +} + +function generateHash(data, endpoint) { + const salt = `${API.token}${API.version}${API.format}${API.uuid}` + + return md5(salt + endpoint + JSON.stringify(data)) +} + +module.exports = config diff --git a/sites/tvarenasport.com/tvarenasport.com.config.js b/sites/tvarenasport.com/tvarenasport.com.config.js index d2297d9c6..0f3ab942b 100644 --- a/sites/tvarenasport.com/tvarenasport.com.config.js +++ b/sites/tvarenasport.com/tvarenasport.com.config.js @@ -1,132 +1,132 @@ -const cheerio = require('cheerio') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'tvarenasport.com', - tz: 'Europe/Belgrade', - lang: 'sr', - days: 2, - request: { - cache: { - ttl: 24 * 60 * 60 * 1000 // 1 day - } - }, - url: 'https://www.tvarenasport.com/tv-scheme', - parser({ content, channel, date }) { - const programs = [] - const expectedDate = date.format('YYYY-MM-DD') - if (content) { - const dates = [] - const $ = cheerio.load(content) - const parent = $( - `.tv-scheme-chanel-header img[src*="chanel-${channel.site_id}.png"]` - ).parents('div') - parent - .siblings('.tv-scheme-days') - .find('a') - .toArray() - .forEach(el => { - const a = $(el) - const dt = a.find('span:nth-child(3)').text() - dates.push(dayjs(dt + date.year(), 'DD.MM.YYYY')) - }) - parent - .siblings('.tv-scheme-new-slider-wrapper') - .find('.tv-scheme-new-slider-item') - .toArray() - .forEach((el, i) => { - programs.push(...parseSchedules($(el), dates[i], module.exports.tz)) - }) - programs.forEach((s, i) => { - if (i < programs.length - 2) { - s.stop = programs[i + 1].start - } else { - s.stop = s.start.startOf('d').add(1, 'd') - } - }) - } - - return programs.filter( - p => - p.start.format('YYYY-MM-DD') === expectedDate || - p.stop.format('YYYY-MM-DD') === expectedDate - ) - }, - async channels() { - const channels = [] - const data = await axios - .get(this.url) - .then(r => r.data) - .catch(console.error) - - if (data) { - // channel naming rule - const names = id => { - let match = id.match(/^\d+$/) - if (match) { - return `Arena Sport ${parseInt(id)}` - } - match = id.match(/^\d/) - if (match) { - return `Arena Sport ${id}` - } - match = id.match(/^a(\d+)(p)?/) - if (match) { - return `Arena ${parseInt(match[1])}${match[2] === 'p' ? ' Premium' : ''}` - } - return `Arena ${id}` - } - const $ = cheerio.load(data) - const items = $('.tv-scheme-chanel-header img').toArray() - for (const item of items) { - const [, id] = $(item) - .attr('src') - .match(/chanel-([a-z0-9]+)\.png/) || [null, null] - if (id) { - channels.push({ - lang: this.lang, - site_id: id, - name: names(id) - }) - } - } - } - - return channels - } -} - -function parseSchedules($s, date, tz) { - const schedules = [] - const $ = $s._make - $s.find('.slider-content') - .toArray() - .forEach(el => { - schedules.push(parseSchedule($(el), date, tz)) - }) - - return schedules -} - -function parseSchedule($s, date, tz) { - const time = $s.find('.slider-content-top span').text() - const start = dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', tz) - const category = $s.find('.slider-content-middle span').text() - const title = $s.find('.slider-content-bottom p').text() - const description = $s.find('.slider-content-bottom span:first').text() - - return { - title: description ? description : title, - description: description ? title : description, - category, - start - } -} +const cheerio = require('cheerio') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'tvarenasport.com', + tz: 'Europe/Belgrade', + lang: 'sr', + days: 2, + request: { + cache: { + ttl: 24 * 60 * 60 * 1000 // 1 day + } + }, + url: 'https://www.tvarenasport.com/tv-scheme', + parser({ content, channel, date }) { + const programs = [] + const expectedDate = date.format('YYYY-MM-DD') + if (content) { + const dates = [] + const $ = cheerio.load(content) + const parent = $( + `.tv-scheme-chanel-header img[src*="chanel-${channel.site_id}.png"]` + ).parents('div') + parent + .siblings('.tv-scheme-days') + .find('a') + .toArray() + .forEach(el => { + const a = $(el) + const dt = a.find('span:nth-child(3)').text() + dates.push(dayjs(dt + date.year(), 'DD.MM.YYYY')) + }) + parent + .siblings('.tv-scheme-new-slider-wrapper') + .find('.tv-scheme-new-slider-item') + .toArray() + .forEach((el, i) => { + programs.push(...parseSchedules($(el), dates[i], module.exports.tz)) + }) + programs.forEach((s, i) => { + if (i < programs.length - 2) { + s.stop = programs[i + 1].start + } else { + s.stop = s.start.startOf('d').add(1, 'd') + } + }) + } + + return programs.filter( + p => + p.start.format('YYYY-MM-DD') === expectedDate || + p.stop.format('YYYY-MM-DD') === expectedDate + ) + }, + async channels() { + const channels = [] + const data = await axios + .get(this.url) + .then(r => r.data) + .catch(console.error) + + if (data) { + // channel naming rule + const names = id => { + let match = id.match(/^\d+$/) + if (match) { + return `Arena Sport ${parseInt(id)}` + } + match = id.match(/^\d/) + if (match) { + return `Arena Sport ${id}` + } + match = id.match(/^a(\d+)(p)?/) + if (match) { + return `Arena ${parseInt(match[1])}${match[2] === 'p' ? ' Premium' : ''}` + } + return `Arena ${id}` + } + const $ = cheerio.load(data) + const items = $('.tv-scheme-chanel-header img').toArray() + for (const item of items) { + const [, id] = $(item) + .attr('src') + .match(/chanel-([a-z0-9]+)\.png/) || [null, null] + if (id) { + channels.push({ + lang: this.lang, + site_id: id, + name: names(id) + }) + } + } + } + + return channels + } +} + +function parseSchedules($s, date, tz) { + const schedules = [] + const $ = $s._make + $s.find('.slider-content') + .toArray() + .forEach(el => { + schedules.push(parseSchedule($(el), date, tz)) + }) + + return schedules +} + +function parseSchedule($s, date, tz) { + const time = $s.find('.slider-content-top span').text() + const start = dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', tz) + const category = $s.find('.slider-content-middle span').text() + const title = $s.find('.slider-content-bottom p').text() + const description = $s.find('.slider-content-bottom span:first').text() + + return { + title: description ? description : title, + description: description ? title : description, + category, + start + } +} diff --git a/sites/tvarenasport.com/tvarenasport.com.test.js b/sites/tvarenasport.com/tvarenasport.com.test.js index 1866f9638..b1282699c 100644 --- a/sites/tvarenasport.com/tvarenasport.com.test.js +++ b/sites/tvarenasport.com/tvarenasport.com.test.js @@ -1,51 +1,51 @@ -const { parser, url } = require('./tvarenasport.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2024-12-07', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'a1p', - xmltv_id: 'ArenaSport1Premium.rs' -} - -it('can generate valid url', () => { - expect(url).toBe('https://www.tvarenasport.com/tv-scheme') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.join(__dirname, '__data__', 'content.html')) - const result = parser({ channel, date, content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result.length).toBe(19) - expect(result[4]).toMatchObject({ - start: '2024-12-07T03:30:00.000Z', - stop: '2024-12-07T05:00:00.000Z', - title: 'EVROPSKO PRVENSTVO Ž', - description: 'Francuska - Crna Gora', - category: 'Rukomet' - }) - expect(result[8]).toMatchObject({ - start: '2024-12-07T11:00:00.000Z', - stop: '2024-12-07T11:05:00.000Z', - title: 'Arena News' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: '' - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./tvarenasport.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2024-12-07', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'a1p', + xmltv_id: 'ArenaSport1Premium.rs' +} + +it('can generate valid url', () => { + expect(url).toBe('https://www.tvarenasport.com/tv-scheme') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.join(__dirname, '__data__', 'content.html')) + const result = parser({ channel, date, content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result.length).toBe(19) + expect(result[4]).toMatchObject({ + start: '2024-12-07T03:30:00.000Z', + stop: '2024-12-07T05:00:00.000Z', + title: 'EVROPSKO PRVENSTVO Ž', + description: 'Francuska - Crna Gora', + category: 'Rukomet' + }) + expect(result[8]).toMatchObject({ + start: '2024-12-07T11:00:00.000Z', + stop: '2024-12-07T11:05:00.000Z', + title: 'Arena News' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: '' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/tvarenasport.hr/tvarenasport.hr.config.js b/sites/tvarenasport.hr/tvarenasport.hr.config.js index c5ae1743a..30941e442 100644 --- a/sites/tvarenasport.hr/tvarenasport.hr.config.js +++ b/sites/tvarenasport.hr/tvarenasport.hr.config.js @@ -1,132 +1,132 @@ -const cheerio = require('cheerio') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'tvarenasport.hr', - tz: 'Europe/Budapest', - lang: 'hr', - url: 'https://tvarenaprogram.com/live/v2/hr', - days: 2, - request: { - cache: { - ttl: 24 * 60 * 60 * 1000 // 1 day - } - }, - parser({ content, channel, date }) { - const programs = [] - const expectedDate = date.format('YYYY-MM-DD') - if (content) { - const dates = [] - const $ = cheerio.load(content) - const parent = $( - `.tv-scheme-chanel-header img[src*="chanel-${channel.site_id}.png"]` - ).parents('div') - parent - .siblings('.tv-scheme-days') - .find('a') - .toArray() - .forEach(el => { - const a = $(el) - const dt = a.find('span:nth-child(3)').text() - dates.push(dayjs(dt + date.year(), 'DD.MM.YYYY')) - }) - parent - .siblings('.tv-scheme-new-slider-wrapper') - .find('.tv-scheme-new-slider-item') - .toArray() - .forEach((el, i) => { - programs.push(...parseSchedules($(el), dates[i], module.exports.tz)) - }) - programs.forEach((s, i) => { - if (i < programs.length - 2) { - s.stop = programs[i + 1].start - } else { - s.stop = s.start.startOf('d').add(1, 'd') - } - }) - } - - return programs.filter( - p => - p.start.format('YYYY-MM-DD') === expectedDate || - p.stop.format('YYYY-MM-DD') === expectedDate - ) - }, - async channels() { - const channels = [] - const data = await axios - .get(this.url) - .then(r => r.data) - .catch(console.error) - - if (data) { - // channel naming rule - const names = id => { - let match = id.match(/^\d+$/) - if (match) { - return `Arena Sport ${parseInt(id)}` - } - match = id.match(/^\d/) - if (match) { - return `Arena Sport ${id}` - } - match = id.match(/^a(\d+)(p)?/) - if (match) { - return `Arena ${parseInt(match[1])}${match[2] === 'p' ? ' Premium' : ''}` - } - return `Arena ${id}` - } - const $ = cheerio.load(data) - const items = $('.tv-scheme-chanel-header img').toArray() - for (const item of items) { - const [, id] = $(item) - .attr('src') - .match(/chanel-([a-z0-9]+)\.png/) || [null, null] - if (id) { - channels.push({ - lang: this.lang, - site_id: id, - name: names(id) - }) - } - } - } - - return channels - } -} - -function parseSchedules($s, date, tz) { - const schedules = [] - const $ = $s._make - $s.find('.slider-content') - .toArray() - .forEach(el => { - schedules.push(parseSchedule($(el), date, tz)) - }) - - return schedules -} - -function parseSchedule($s, date, tz) { - const time = $s.find('.slider-content-top span').text() - const start = dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', tz) - const category = $s.find('.slider-content-middle span').text() - const title = $s.find('.slider-content-bottom p').text() - const description = $s.find('.slider-content-bottom span:first').text() - - return { - title: description ? description : title, - description: description ? title : description, - category, - start - } -} +const cheerio = require('cheerio') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'tvarenasport.hr', + tz: 'Europe/Budapest', + lang: 'hr', + url: 'https://tvarenaprogram.com/live/v2/hr', + days: 2, + request: { + cache: { + ttl: 24 * 60 * 60 * 1000 // 1 day + } + }, + parser({ content, channel, date }) { + const programs = [] + const expectedDate = date.format('YYYY-MM-DD') + if (content) { + const dates = [] + const $ = cheerio.load(content) + const parent = $( + `.tv-scheme-chanel-header img[src*="chanel-${channel.site_id}.png"]` + ).parents('div') + parent + .siblings('.tv-scheme-days') + .find('a') + .toArray() + .forEach(el => { + const a = $(el) + const dt = a.find('span:nth-child(3)').text() + dates.push(dayjs(dt + date.year(), 'DD.MM.YYYY')) + }) + parent + .siblings('.tv-scheme-new-slider-wrapper') + .find('.tv-scheme-new-slider-item') + .toArray() + .forEach((el, i) => { + programs.push(...parseSchedules($(el), dates[i], module.exports.tz)) + }) + programs.forEach((s, i) => { + if (i < programs.length - 2) { + s.stop = programs[i + 1].start + } else { + s.stop = s.start.startOf('d').add(1, 'd') + } + }) + } + + return programs.filter( + p => + p.start.format('YYYY-MM-DD') === expectedDate || + p.stop.format('YYYY-MM-DD') === expectedDate + ) + }, + async channels() { + const channels = [] + const data = await axios + .get(this.url) + .then(r => r.data) + .catch(console.error) + + if (data) { + // channel naming rule + const names = id => { + let match = id.match(/^\d+$/) + if (match) { + return `Arena Sport ${parseInt(id)}` + } + match = id.match(/^\d/) + if (match) { + return `Arena Sport ${id}` + } + match = id.match(/^a(\d+)(p)?/) + if (match) { + return `Arena ${parseInt(match[1])}${match[2] === 'p' ? ' Premium' : ''}` + } + return `Arena ${id}` + } + const $ = cheerio.load(data) + const items = $('.tv-scheme-chanel-header img').toArray() + for (const item of items) { + const [, id] = $(item) + .attr('src') + .match(/chanel-([a-z0-9]+)\.png/) || [null, null] + if (id) { + channels.push({ + lang: this.lang, + site_id: id, + name: names(id) + }) + } + } + } + + return channels + } +} + +function parseSchedules($s, date, tz) { + const schedules = [] + const $ = $s._make + $s.find('.slider-content') + .toArray() + .forEach(el => { + schedules.push(parseSchedule($(el), date, tz)) + }) + + return schedules +} + +function parseSchedule($s, date, tz) { + const time = $s.find('.slider-content-top span').text() + const start = dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', tz) + const category = $s.find('.slider-content-middle span').text() + const title = $s.find('.slider-content-bottom p').text() + const description = $s.find('.slider-content-bottom span:first').text() + + return { + title: description ? description : title, + description: description ? title : description, + category, + start + } +} diff --git a/sites/tvarenasport.hr/tvarenasport.hr.test.js b/sites/tvarenasport.hr/tvarenasport.hr.test.js index 9358952c9..c7516c2df 100644 --- a/sites/tvarenasport.hr/tvarenasport.hr.test.js +++ b/sites/tvarenasport.hr/tvarenasport.hr.test.js @@ -1,53 +1,53 @@ -const { parser, url } = require('./tvarenasport.hr.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2024-12-07', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '01', - xmltv_id: 'ArenaSport1.hr' -} - -it('can generate valid url', () => { - expect(url).toBe('https://tvarenaprogram.com/live/v2/hr') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.join(__dirname, '__data__', 'content.html')) - const result = parser({ channel, date, content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result.length).toBe(15) - expect(result[0]).toMatchObject({ - start: '2024-12-07T00:00:00.000Z', - stop: '2024-12-07T00:30:00.000Z', - title: 'MAGAZIN', - description: 'NBA ACTION', - category: 'Košarka' - }) - expect(result[4]).toMatchObject({ - start: '2024-12-07T06:00:00.000Z', - stop: '2024-12-07T07:30:00.000Z', - title: 'EHF LIGA PRVAKA', - description: 'DINAMO BUKUREŠT - PSG', - category: 'Rukomet' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: '' - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./tvarenasport.hr.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2024-12-07', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '01', + xmltv_id: 'ArenaSport1.hr' +} + +it('can generate valid url', () => { + expect(url).toBe('https://tvarenaprogram.com/live/v2/hr') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.join(__dirname, '__data__', 'content.html')) + const result = parser({ channel, date, content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result.length).toBe(15) + expect(result[0]).toMatchObject({ + start: '2024-12-07T00:00:00.000Z', + stop: '2024-12-07T00:30:00.000Z', + title: 'MAGAZIN', + description: 'NBA ACTION', + category: 'Košarka' + }) + expect(result[4]).toMatchObject({ + start: '2024-12-07T06:00:00.000Z', + stop: '2024-12-07T07:30:00.000Z', + title: 'EHF LIGA PRVAKA', + description: 'DINAMO BUKUREŠT - PSG', + category: 'Rukomet' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: '' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/tvcubana.icrt.cu/tvcubana.icrt.cu.config.js b/sites/tvcubana.icrt.cu/tvcubana.icrt.cu.config.js index 41edebcad..94fd48f99 100644 --- a/sites/tvcubana.icrt.cu/tvcubana.icrt.cu.config.js +++ b/sites/tvcubana.icrt.cu/tvcubana.icrt.cu.config.js @@ -1,48 +1,48 @@ -const dayjs = require('dayjs') -const timezone = require('dayjs/plugin/timezone') - -dayjs.extend(timezone) - -module.exports = { - site: 'tvcubana.icrt.cu', - days: 2, - url({ channel, date }) { - const daysOfWeek = ['domingo', 'lunes', 'martes', 'miercoles', 'jueves', 'viernes', 'sabado'] - - return `https://www.tvcubana.icrt.cu/cartv/${channel.site_id}/${daysOfWeek[date.day()]}.php` - }, - parser: function ({ content }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - programs.push({ - title: item.title, - description: item.description, - start: parseStart(item), - stop: parseStop(item) - }) - }) - - return programs - } -} - -function parseStart(item) { - return dayjs.tz(item.eventInitialDateTime, 'America/Havana') -} - -function parseStop(item) { - return dayjs.tz(item.eventEndDateTime, 'America/Havana') -} - -function parseItems(content) { - let data - try { - data = JSON.parse(content) - } catch { - return [] - } - if (!data || !Array.isArray(data)) return [] - - return data -} +const dayjs = require('dayjs') +const timezone = require('dayjs/plugin/timezone') + +dayjs.extend(timezone) + +module.exports = { + site: 'tvcubana.icrt.cu', + days: 2, + url({ channel, date }) { + const daysOfWeek = ['domingo', 'lunes', 'martes', 'miercoles', 'jueves', 'viernes', 'sabado'] + + return `https://www.tvcubana.icrt.cu/cartv/${channel.site_id}/${daysOfWeek[date.day()]}.php` + }, + parser: function ({ content }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + programs.push({ + title: item.title, + description: item.description, + start: parseStart(item), + stop: parseStop(item) + }) + }) + + return programs + } +} + +function parseStart(item) { + return dayjs.tz(item.eventInitialDateTime, 'America/Havana') +} + +function parseStop(item) { + return dayjs.tz(item.eventEndDateTime, 'America/Havana') +} + +function parseItems(content) { + let data + try { + data = JSON.parse(content) + } catch { + return [] + } + if (!data || !Array.isArray(data)) return [] + + return data +} diff --git a/sites/tvgids.nl/tvgids.nl.config.js b/sites/tvgids.nl/tvgids.nl.config.js index 4df422070..ba3a12ee2 100644 --- a/sites/tvgids.nl/tvgids.nl.config.js +++ b/sites/tvgids.nl/tvgids.nl.config.js @@ -1,87 +1,87 @@ -const cheerio = require('cheerio') -const axios = require('axios') -const { DateTime } = require('luxon') - -module.exports = { - site: 'tvgids.nl', - days: 2, - url: function ({ date, channel }) { - const path = - DateTime.utc().day === DateTime.fromMillis(date.valueOf()).day - ? '' - : `${date.format('DD-MM-YYYY')}/` - - return `https://www.tvgids.nl/gids/${path}${channel.site_id}` - }, - parser: function ({ content, date }) { - date = date.subtract(1, 'd') - let programs = [] - const items = parseItems(content) - items.forEach(item => { - const $item = cheerio.load(item) - const prev = programs[programs.length - 1] - let start = parseStart($item, date) - if (prev) { - if (start < prev.start) { - start = start.plus({ days: 1 }) - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.plus({ minutes: 30 }) - programs.push({ - title: parseTitle($item), - description: parseDescription($item), - image: parseImage($item), - start, - stop - }) - }) - - return programs - }, - async channels() { - const data = await axios - .get('https://www.tvgids.nl/gids/') - .then(r => r.data) - .catch(console.log) - const $ = cheerio.load(data) - - const channels = [] - $('.guide__channel-logo-container').each((i, el) => { - channels.push({ - site_id: $(el).find('a').attr('id'), - name: $(el).find('img').attr('title'), - lang: 'nl' - }) - }) - - return channels - } -} - -function parseTitle($item) { - return $item('.program__title').text().trim() -} - -function parseDescription($item) { - return $item('.program__text').text().trim() -} - -function parseImage($item) { - return $item('.program__thumbnail').data('src') -} - -function parseStart($item, date) { - const time = $item('.program__starttime').clone().children().remove().end().text().trim() - - return DateTime.fromFormat(`${date.format('YYYY-MM-DD')} ${time}`, 'yyyy-MM-dd HH:mm', { - zone: 'Europe/Amsterdam' - }).toUTC() -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('.guide__guide .program').toArray() -} +const cheerio = require('cheerio') +const axios = require('axios') +const { DateTime } = require('luxon') + +module.exports = { + site: 'tvgids.nl', + days: 2, + url: function ({ date, channel }) { + const path = + DateTime.utc().day === DateTime.fromMillis(date.valueOf()).day + ? '' + : `${date.format('DD-MM-YYYY')}/` + + return `https://www.tvgids.nl/gids/${path}${channel.site_id}` + }, + parser: function ({ content, date }) { + date = date.subtract(1, 'd') + let programs = [] + const items = parseItems(content) + items.forEach(item => { + const $item = cheerio.load(item) + const prev = programs[programs.length - 1] + let start = parseStart($item, date) + if (prev) { + if (start < prev.start) { + start = start.plus({ days: 1 }) + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.plus({ minutes: 30 }) + programs.push({ + title: parseTitle($item), + description: parseDescription($item), + image: parseImage($item), + start, + stop + }) + }) + + return programs + }, + async channels() { + const data = await axios + .get('https://www.tvgids.nl/gids/') + .then(r => r.data) + .catch(console.log) + const $ = cheerio.load(data) + + const channels = [] + $('.guide__channel-logo-container').each((i, el) => { + channels.push({ + site_id: $(el).find('a').attr('id'), + name: $(el).find('img').attr('title'), + lang: 'nl' + }) + }) + + return channels + } +} + +function parseTitle($item) { + return $item('.program__title').text().trim() +} + +function parseDescription($item) { + return $item('.program__text').text().trim() +} + +function parseImage($item) { + return $item('.program__thumbnail').data('src') +} + +function parseStart($item, date) { + const time = $item('.program__starttime').clone().children().remove().end().text().trim() + + return DateTime.fromFormat(`${date.format('YYYY-MM-DD')} ${time}`, 'yyyy-MM-dd HH:mm', { + zone: 'Europe/Amsterdam' + }).toUTC() +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('.guide__guide .program').toArray() +} diff --git a/sites/tvgids.nl/tvgids.nl.test.js b/sites/tvgids.nl/tvgids.nl.test.js index 88da3f0a7..bb3226014 100644 --- a/sites/tvgids.nl/tvgids.nl.test.js +++ b/sites/tvgids.nl/tvgids.nl.test.js @@ -1,60 +1,60 @@ -const { parser, url } = require('./tvgids.nl.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-01-19', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'npo1', - xmltv_id: 'NPO1.nl' -} - -it('can generate valid url', () => { - jest.useFakeTimers().setSystemTime(new Date('2025-01-17')) - - expect(url({ date, channel })).toBe('https://www.tvgids.nl/gids/19-01-2025/npo1') -}) - -it('can generate valid url for today', () => { - const today = dayjs().startOf('d') - - expect(url({ date: today, channel })).toBe('https://www.tvgids.nl/gids/npo1') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - const results = parser({ content, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2025-01-18T22:57:00.000Z', - stop: '2025-01-18T23:58:00.000Z', - title: 'Op1', - image: 'https://tvgidsassets.nl/v301/upload/o/carrousel/op1-451542641.jpg', - description: "Talkshow met wisselende presentatieduo's, live vanuit Amsterdam." - }) - - expect(results[61]).toMatchObject({ - start: '2025-01-20T01:18:00.000Z', - stop: '2025-01-20T01:48:00.000Z', - title: 'NOS Journaal', - image: 'https://tvgidsassets.nl/v301/upload/n/carrousel/nos-journaal-452818771.jpg', - description: - 'Met het laatste nieuws, gebeurtenissen van nationaal en internationaal belang en de weersverwachting voor vandaag.' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: '', - date - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./tvgids.nl.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-01-19', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'npo1', + xmltv_id: 'NPO1.nl' +} + +it('can generate valid url', () => { + jest.useFakeTimers().setSystemTime(new Date('2025-01-17')) + + expect(url({ date, channel })).toBe('https://www.tvgids.nl/gids/19-01-2025/npo1') +}) + +it('can generate valid url for today', () => { + const today = dayjs().startOf('d') + + expect(url({ date: today, channel })).toBe('https://www.tvgids.nl/gids/npo1') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + const results = parser({ content, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2025-01-18T22:57:00.000Z', + stop: '2025-01-18T23:58:00.000Z', + title: 'Op1', + image: 'https://tvgidsassets.nl/v301/upload/o/carrousel/op1-451542641.jpg', + description: "Talkshow met wisselende presentatieduo's, live vanuit Amsterdam." + }) + + expect(results[61]).toMatchObject({ + start: '2025-01-20T01:18:00.000Z', + stop: '2025-01-20T01:48:00.000Z', + title: 'NOS Journaal', + image: 'https://tvgidsassets.nl/v301/upload/n/carrousel/nos-journaal-452818771.jpg', + description: + 'Met het laatste nieuws, gebeurtenissen van nationaal en internationaal belang en de weersverwachting voor vandaag.' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: '', + date + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/tvguide.com/tvguide.com.config.js b/sites/tvguide.com/tvguide.com.config.js index 940601d83..7e7f1ef6d 100644 --- a/sites/tvguide.com/tvguide.com.config.js +++ b/sites/tvguide.com/tvguide.com.config.js @@ -1,114 +1,114 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const doFetch = require('@ntlab/sfetch') -const debug = require('debug')('site:tvguide.com') - -dayjs.extend(utc) -dayjs.extend(timezone) - -doFetch.setDebugger(debug).setCheckResult(false) - -const providerId = '9100001138' -const maxDuration = 240 -const segments = 1440 / maxDuration - -module.exports = { - site: 'tvguide.com', - days: 2, - request: { - cache: { - ttl: 24 * 60 * 60 * 1000 // 1 day - } - }, - async url({ date, segment = 1 }) { - const params = [] - if (module.exports.apiKey === undefined) { - module.exports.apiKey = await module.exports.fetchApiKey() - debug('Got api key', module.exports.apiKey) - } - if (date) { - if (segment > 1) { - date = date.add((segment - 1) * maxDuration, 'm') - } - params.push(`start=${date.unix()}`, `duration=${maxDuration}`) - } - params.push(`apiKey=${module.exports.apiKey}`) - - return date ? - `https://backend.tvguide.com/tvschedules/tvguide/${providerId}/web?${params.join('&')}` : - `https://backend.tvguide.com/tvschedules/tvguide/serviceprovider/${providerId}/sources/web?${params.join('&')}` - }, - async parser({ content, date, channel }) { - const programs = [] - const f = data => { - const result = [] - if (typeof data === 'string') { - data = JSON.parse(data) - } - if (data && Array.isArray(data?.data?.items)) { - data.data.items - .filter(i => i.channel.sourceId.toString() === channel.site_id) - .forEach(i => { - result.push(...i.programSchedules.map(p => { - return { i: p, url: p.programDetails } - })) - }) - } - - return result - } - const queues = f(content) - if (queues.length) { - const parts = [] - for (let i = 2; i <= segments; i++) { - parts.push(await module.exports.url({ date, segment: i })) - } - await doFetch(parts, (url, res) => { - queues.push(...f(res)) - }) - await doFetch(queues, (queue, res) => { - const item = res?.data?.item ? res.data.item : queue.i - programs.push({ - title: item.title ? item.title : queue.i.title, - sub_title: item.episodeNumber ? item.episodeTitle : null, - description: item.description, - season: item.seasonNumber, - episode: item.episodeNumber, - rating: item.rating ? { system: 'MPA', value: item.rating } : null, - categories: Array.isArray(item.genres) ? item.genres.map(g => g.name) : null, - start: dayjs.unix(item.startTime ? item.startTime : queue.i.startTime), - stop: dayjs.unix(item.endTime ? item.endTime : queue.i.endTime) - }) - }) - } - - return programs - }, - async channels() { - const channels = [] - const data = await axios - .get(await this.url({})) - .then(r => r.data) - .catch(console.error) - - data.data.items.forEach(item => { - channels.push({ - lang: 'en', - site_id: item.sourceId, - name: item.fullName.replace(/Channel|Schedule/g, '').trim() - }) - }) - - return channels - }, - async fetchApiKey() { - const data = await axios - .get('https://www.tvguide.com/listings/') - .then(r => r.data) - .catch(console.error) - - return data ? data.match(/apiKey=([a-zA-Z0-9]+)&/)[1] : null - } -} +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const doFetch = require('@ntlab/sfetch') +const debug = require('debug')('site:tvguide.com') + +dayjs.extend(utc) +dayjs.extend(timezone) + +doFetch.setDebugger(debug).setCheckResult(false) + +const providerId = '9100001138' +const maxDuration = 240 +const segments = 1440 / maxDuration + +module.exports = { + site: 'tvguide.com', + days: 2, + request: { + cache: { + ttl: 24 * 60 * 60 * 1000 // 1 day + } + }, + async url({ date, segment = 1 }) { + const params = [] + if (module.exports.apiKey === undefined) { + module.exports.apiKey = await module.exports.fetchApiKey() + debug('Got api key', module.exports.apiKey) + } + if (date) { + if (segment > 1) { + date = date.add((segment - 1) * maxDuration, 'm') + } + params.push(`start=${date.unix()}`, `duration=${maxDuration}`) + } + params.push(`apiKey=${module.exports.apiKey}`) + + return date ? + `https://backend.tvguide.com/tvschedules/tvguide/${providerId}/web?${params.join('&')}` : + `https://backend.tvguide.com/tvschedules/tvguide/serviceprovider/${providerId}/sources/web?${params.join('&')}` + }, + async parser({ content, date, channel }) { + const programs = [] + const f = data => { + const result = [] + if (typeof data === 'string') { + data = JSON.parse(data) + } + if (data && Array.isArray(data?.data?.items)) { + data.data.items + .filter(i => i.channel.sourceId.toString() === channel.site_id) + .forEach(i => { + result.push(...i.programSchedules.map(p => { + return { i: p, url: p.programDetails } + })) + }) + } + + return result + } + const queues = f(content) + if (queues.length) { + const parts = [] + for (let i = 2; i <= segments; i++) { + parts.push(await module.exports.url({ date, segment: i })) + } + await doFetch(parts, (url, res) => { + queues.push(...f(res)) + }) + await doFetch(queues, (queue, res) => { + const item = res?.data?.item ? res.data.item : queue.i + programs.push({ + title: item.title ? item.title : queue.i.title, + sub_title: item.episodeNumber ? item.episodeTitle : null, + description: item.description, + season: item.seasonNumber, + episode: item.episodeNumber, + rating: item.rating ? { system: 'MPA', value: item.rating } : null, + categories: Array.isArray(item.genres) ? item.genres.map(g => g.name) : null, + start: dayjs.unix(item.startTime ? item.startTime : queue.i.startTime), + stop: dayjs.unix(item.endTime ? item.endTime : queue.i.endTime) + }) + }) + } + + return programs + }, + async channels() { + const channels = [] + const data = await axios + .get(await this.url({})) + .then(r => r.data) + .catch(console.error) + + data.data.items.forEach(item => { + channels.push({ + lang: 'en', + site_id: item.sourceId, + name: item.fullName.replace(/Channel|Schedule/g, '').trim() + }) + }) + + return channels + }, + async fetchApiKey() { + const data = await axios + .get('https://www.tvguide.com/listings/') + .then(r => r.data) + .catch(console.error) + + return data ? data.match(/apiKey=([a-zA-Z0-9]+)&/)[1] : null + } +} diff --git a/sites/tvguide.com/tvguide.com.test.js b/sites/tvguide.com/tvguide.com.test.js index 938adcb80..f0f92842c 100644 --- a/sites/tvguide.com/tvguide.com.test.js +++ b/sites/tvguide.com/tvguide.com.test.js @@ -1,95 +1,95 @@ -const { parser, url } = require('./tvguide.com.config.js') -const fs = require('fs') -const path = require('path') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -const date = dayjs.utc('2025-01-12', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '9200018514', - xmltv_id: 'CBSEast.us' -} - -axios.get.mockImplementation(url => { - const result = {} - const urls = { - 'https://www.tvguide.com/listings/': - 'content.html', - 'https://backend.tvguide.com/tvschedules/tvguide/9100001138/web?start=1736640000&duration=240&apiKey=DI9elXhZ3bU6ujsA2gXEKOANyncXGUGc': - 'content1.json', - 'https://backend.tvguide.com/tvschedules/tvguide/9100001138/web?start=1736654400&duration=240&apiKey=DI9elXhZ3bU6ujsA2gXEKOANyncXGUGc': - 'content2.json', - 'https://backend.tvguide.com/tvschedules/tvguide/programdetails/9000351140/web': - 'program1.json', - 'https://backend.tvguide.com/tvschedules/tvguide/programdetails/9000000408/web': - 'program2.json', - } - if (urls[url] !== undefined) { - result.data = fs.readFileSync(path.join(__dirname, '__data__', urls[url])).toString() - if (!urls[url].startsWith('content1') && !urls[url].endsWith('.html')) { - result.data = JSON.parse(result.data) - } - } - - return Promise.resolve(result) -}) - -it('can generate valid url', async () => { - expect(await url({ date })).toBe( - 'https://backend.tvguide.com/tvschedules/tvguide/9100001138/web?start=1736640000&duration=240&apiKey=DI9elXhZ3bU6ujsA2gXEKOANyncXGUGc' - ) -}) - -it('can parse response', async () => { - const content = fs.readFileSync(path.join(__dirname, '__data__', 'content1.json')).toString() - const results = (await parser({ content, channel, date })).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(5) - expect(results[0]).toMatchObject({ - start: '2025-01-12T01:00:00.000Z', - stop: '2025-01-12T02:00:00.000Z', - title: 'FBI: International', - sub_title: 'Gift', - description: - 'The owner of a prominent cyber security company is murdered in Copenhagen just before a massive data leak surfaces online, leading the NSA to ask the team for assistance in catching the killer and leaker before more data is revealed.', - categories: ['Action & Adventure', 'Suspense', 'Drama'], - season: 3, - episode: 12, - rating: { - system: 'MPA', - value: 'L' - } - }) - expect(results[4]).toMatchObject({ - start: '2025-01-12T06:00:00.000Z', - stop: '2025-01-12T08:00:00.000Z', - title: 'Local Programs', - description: - 'Local programming information.', - categories: [], - rating: { - system: 'MPA', - value: 'L' - } - }) -}) - -it('can handle empty guide', async () => { - const results = await parser({ - date, - channel, - content: fs.readFileSync(path.join(__dirname, '__data__', 'no-content.json')).toString() - }) - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./tvguide.com.config.js') +const fs = require('fs') +const path = require('path') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.utc('2025-01-12', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '9200018514', + xmltv_id: 'CBSEast.us' +} + +axios.get.mockImplementation(url => { + const result = {} + const urls = { + 'https://www.tvguide.com/listings/': + 'content.html', + 'https://backend.tvguide.com/tvschedules/tvguide/9100001138/web?start=1736640000&duration=240&apiKey=DI9elXhZ3bU6ujsA2gXEKOANyncXGUGc': + 'content1.json', + 'https://backend.tvguide.com/tvschedules/tvguide/9100001138/web?start=1736654400&duration=240&apiKey=DI9elXhZ3bU6ujsA2gXEKOANyncXGUGc': + 'content2.json', + 'https://backend.tvguide.com/tvschedules/tvguide/programdetails/9000351140/web': + 'program1.json', + 'https://backend.tvguide.com/tvschedules/tvguide/programdetails/9000000408/web': + 'program2.json', + } + if (urls[url] !== undefined) { + result.data = fs.readFileSync(path.join(__dirname, '__data__', urls[url])).toString() + if (!urls[url].startsWith('content1') && !urls[url].endsWith('.html')) { + result.data = JSON.parse(result.data) + } + } + + return Promise.resolve(result) +}) + +it('can generate valid url', async () => { + expect(await url({ date })).toBe( + 'https://backend.tvguide.com/tvschedules/tvguide/9100001138/web?start=1736640000&duration=240&apiKey=DI9elXhZ3bU6ujsA2gXEKOANyncXGUGc' + ) +}) + +it('can parse response', async () => { + const content = fs.readFileSync(path.join(__dirname, '__data__', 'content1.json')).toString() + const results = (await parser({ content, channel, date })).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(5) + expect(results[0]).toMatchObject({ + start: '2025-01-12T01:00:00.000Z', + stop: '2025-01-12T02:00:00.000Z', + title: 'FBI: International', + sub_title: 'Gift', + description: + 'The owner of a prominent cyber security company is murdered in Copenhagen just before a massive data leak surfaces online, leading the NSA to ask the team for assistance in catching the killer and leaker before more data is revealed.', + categories: ['Action & Adventure', 'Suspense', 'Drama'], + season: 3, + episode: 12, + rating: { + system: 'MPA', + value: 'L' + } + }) + expect(results[4]).toMatchObject({ + start: '2025-01-12T06:00:00.000Z', + stop: '2025-01-12T08:00:00.000Z', + title: 'Local Programs', + description: + 'Local programming information.', + categories: [], + rating: { + system: 'MPA', + value: 'L' + } + }) +}) + +it('can handle empty guide', async () => { + const results = await parser({ + date, + channel, + content: fs.readFileSync(path.join(__dirname, '__data__', 'no-content.json')).toString() + }) + expect(results).toMatchObject([]) +}) diff --git a/sites/tvguide.myjcom.jp/tvguide.myjcom.jp.config.js b/sites/tvguide.myjcom.jp/tvguide.myjcom.jp.config.js index 9651de74f..ab07d5734 100644 --- a/sites/tvguide.myjcom.jp/tvguide.myjcom.jp.config.js +++ b/sites/tvguide.myjcom.jp/tvguide.myjcom.jp.config.js @@ -1,114 +1,114 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'tvguide.myjcom.jp', - days: 2, - lang: 'ja', - url: function ({ date, channel }) { - const id = `${channel.site_id}_${date.format('YYYYMMDD')}` - - return `https://tvguide.myjcom.jp/api/getEpgInfo/?channels=${id}` - }, - parser: function ({ content, channel, date }) { - let programs = [] - const items = parseItems(content, channel, date) - items.forEach(item => { - programs.push({ - title: item.title, - description: item.commentary, - category: parseCategory(item), - image: parseImage(item), - start: parseStart(item), - stop: parseStop(item) - }) - }) - - return programs - }, - async channels() { - const requests = [ - axios.get( - 'https://tvguide.myjcom.jp/api/mypage/getEpgChannelList/?channelType=2&area=108&channelGenre&course&chart&is_adult=true' - ), - axios.get( - 'https://tvguide.myjcom.jp/api/mypage/getEpgChannelList/?channelType=3&area=108&channelGenre&course&chart&is_adult=true' - ), - axios.get( - 'https://tvguide.myjcom.jp/api/mypage/getEpgChannelList/?channelType=5&area=108&channelGenre&course&chart&is_adult=true' - ), - axios.get( - 'https://tvguide.myjcom.jp/api/mypage/getEpgChannelList/?channelType=120&area=108&channelGenre&course&chart&is_adult=true' - ), - axios.get( - 'https://tvguide.myjcom.jp/api/mypage/getEpgChannelList/?channelType=200&area=108&channelGenre&course&chart&is_adult=true' - ) - ] - - let items = [] - await Promise.all(requests) - .then(responses => { - for (const r of responses) { - items = items.concat(r.data.header) - } - }) - .catch(console.log) - - return items.map(item => { - return { - lang: 'ja', - site_id: `${item.channel_type}_${item.channel_id}_${item.network_id}`, - name: item.channel_name - } - }) - } -} - -function parseImage(item) { - return item.imgPath ? `https://tvguide.myjcom.jp${item.imgPath}` : null -} - -function parseCategory(item) { - if (!item.sortGenre) return null - - const id = item.sortGenre[0] - const genres = { - 0: 'ニュース/報道', - 1: 'スポーツ', - 2: '情報/ワイドショー', - 3: 'ドラマ', - 4: '音楽', - 5: 'バラエティ', - 6: '映画', - 7: 'アニメ/特撮', - 8: 'ドキュメンタリー/教養', - 9: '劇場/公演', - 10: '趣味/教育', - 11: '福祉', - 12: 'その他' - } - - return genres[id] -} - -function parseStart(item) { - return dayjs.tz(item.programStart.toString(), 'YYYYMMDDHHmmss', 'Asia/Tokyo') -} - -function parseStop(item) { - return dayjs.tz(item.programEnd.toString(), 'YYYYMMDDHHmmss', 'Asia/Tokyo') -} - -function parseItems(content, channel, date) { - const id = `${channel.site_id}_${date.format('YYYYMMDD')}` - const parsed = JSON.parse(content) - - return parsed[id] || [] -} +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'tvguide.myjcom.jp', + days: 2, + lang: 'ja', + url: function ({ date, channel }) { + const id = `${channel.site_id}_${date.format('YYYYMMDD')}` + + return `https://tvguide.myjcom.jp/api/getEpgInfo/?channels=${id}` + }, + parser: function ({ content, channel, date }) { + let programs = [] + const items = parseItems(content, channel, date) + items.forEach(item => { + programs.push({ + title: item.title, + description: item.commentary, + category: parseCategory(item), + image: parseImage(item), + start: parseStart(item), + stop: parseStop(item) + }) + }) + + return programs + }, + async channels() { + const requests = [ + axios.get( + 'https://tvguide.myjcom.jp/api/mypage/getEpgChannelList/?channelType=2&area=108&channelGenre&course&chart&is_adult=true' + ), + axios.get( + 'https://tvguide.myjcom.jp/api/mypage/getEpgChannelList/?channelType=3&area=108&channelGenre&course&chart&is_adult=true' + ), + axios.get( + 'https://tvguide.myjcom.jp/api/mypage/getEpgChannelList/?channelType=5&area=108&channelGenre&course&chart&is_adult=true' + ), + axios.get( + 'https://tvguide.myjcom.jp/api/mypage/getEpgChannelList/?channelType=120&area=108&channelGenre&course&chart&is_adult=true' + ), + axios.get( + 'https://tvguide.myjcom.jp/api/mypage/getEpgChannelList/?channelType=200&area=108&channelGenre&course&chart&is_adult=true' + ) + ] + + let items = [] + await Promise.all(requests) + .then(responses => { + for (const r of responses) { + items = items.concat(r.data.header) + } + }) + .catch(console.log) + + return items.map(item => { + return { + lang: 'ja', + site_id: `${item.channel_type}_${item.channel_id}_${item.network_id}`, + name: item.channel_name + } + }) + } +} + +function parseImage(item) { + return item.imgPath ? `https://tvguide.myjcom.jp${item.imgPath}` : null +} + +function parseCategory(item) { + if (!item.sortGenre) return null + + const id = item.sortGenre[0] + const genres = { + 0: 'ニュース/報道', + 1: 'スポーツ', + 2: '情報/ワイドショー', + 3: 'ドラマ', + 4: '音楽', + 5: 'バラエティ', + 6: '映画', + 7: 'アニメ/特撮', + 8: 'ドキュメンタリー/教養', + 9: '劇場/公演', + 10: '趣味/教育', + 11: '福祉', + 12: 'その他' + } + + return genres[id] +} + +function parseStart(item) { + return dayjs.tz(item.programStart.toString(), 'YYYYMMDDHHmmss', 'Asia/Tokyo') +} + +function parseStop(item) { + return dayjs.tz(item.programEnd.toString(), 'YYYYMMDDHHmmss', 'Asia/Tokyo') +} + +function parseItems(content, channel, date) { + const id = `${channel.site_id}_${date.format('YYYYMMDD')}` + const parsed = JSON.parse(content) + + return parsed[id] || [] +} diff --git a/sites/tvhebdo.com/tvhebdo.com.test.js b/sites/tvhebdo.com/tvhebdo.com.test.js index 550ec8e0b..eccf99924 100644 --- a/sites/tvhebdo.com/tvhebdo.com.test.js +++ b/sites/tvhebdo.com/tvhebdo.com.test.js @@ -1,53 +1,53 @@ -const { parser, url } = require('./tvhebdo.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-05-11', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'src/CBFT', - xmltv_id: 'CBFT.ca' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://www.tvhebdo.com/horaire-tele/src/CBFT/date/2022-05-11' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve('sites/tvhebdo.com/__data__/content.html')) - const results = parser({ content, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2022-05-11T15:30:00.000Z', - stop: '2022-05-11T16:00:00.000Z', - title: '5 chefs dans ma cuisine' - }) - - expect(results[16]).toMatchObject({ - start: '2022-05-12T04:09:00.000Z', - stop: '2022-05-12T05:19:00.000Z', - title: 'Outlander: Le chardon et le tartan' - }) - - expect(results[36]).toMatchObject({ - start: '2022-05-12T15:00:00.000Z', - stop: '2022-05-12T15:30:00.000Z', - title: 'Ricardo' - }) -}) - -it('can handle empty guide', () => { - const content = fs.readFileSync(path.resolve('sites/tvhebdo.com/__data__/no_content.html')) - const result = parser({ content, date }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./tvhebdo.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-05-11', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'src/CBFT', + xmltv_id: 'CBFT.ca' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://www.tvhebdo.com/horaire-tele/src/CBFT/date/2022-05-11' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve('sites/tvhebdo.com/__data__/content.html')) + const results = parser({ content, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2022-05-11T15:30:00.000Z', + stop: '2022-05-11T16:00:00.000Z', + title: '5 chefs dans ma cuisine' + }) + + expect(results[16]).toMatchObject({ + start: '2022-05-12T04:09:00.000Z', + stop: '2022-05-12T05:19:00.000Z', + title: 'Outlander: Le chardon et le tartan' + }) + + expect(results[36]).toMatchObject({ + start: '2022-05-12T15:00:00.000Z', + stop: '2022-05-12T15:30:00.000Z', + title: 'Ricardo' + }) +}) + +it('can handle empty guide', () => { + const content = fs.readFileSync(path.resolve('sites/tvhebdo.com/__data__/no_content.html')) + const result = parser({ content, date }) + expect(result).toMatchObject([]) +}) diff --git a/sites/tvheute.at/tvheute.at.config.js b/sites/tvheute.at/tvheute.at.config.js index d73706c88..fcc5e37bb 100644 --- a/sites/tvheute.at/tvheute.at.config.js +++ b/sites/tvheute.at/tvheute.at.config.js @@ -1,96 +1,96 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') - -module.exports = { - site: 'tvheute.at', - days: 2, - url({ channel, date }) { - return `https://tvheute.at/part/channel-shows/partial/${channel.site_id}/${date.format( - 'DD-MM-YYYY' - )}` - }, - parser: function ({ content }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - programs.push({ - title: parseTitle(item), - description: parseDescription(item), - image: parseImage(item), - category: parseCategory(item), - start: parseStart(item).toJSON(), - stop: parseStop(item).toJSON() - }) - }) - - return programs - }, - async channels() { - const axios = require('axios') - const html = await axios - .get('https://tvheute.at/part/channel-selection') - .then(r => r.data) - .catch(console.log) - - let channels = [] - - const $ = cheerio.load(html) - $('.sortable-list > li').each((i, el) => { - const name = $(el).find('label').text() - const site_id = $(el).find('input').attr('value') - - channels.push({ - lang: 'de', - site_id, - name - }) - }) - - return channels - } -} - -function parseTitle(item) { - const $ = cheerio.load(item) - - return $('.title-col strong').text() -} - -function parseDescription(item) { - const $ = cheerio.load(item) - - return $('.title-col .description').text() -} - -function parseCategory(item) { - const $ = cheerio.load(item) - - return $('.station-col > .type').text() -} - -function parseImage(item) { - const $ = cheerio.load(item) - const imgSrc = $('.title-col .image img').data('src-desktop') - - return imgSrc ? `https://tvheute.at${imgSrc}` : null -} - -function parseStart(item) { - const $ = cheerio.load(item) - const time = $('.end-col > .duration-wrapper').data('start') - - return dayjs(time) -} - -function parseStop(item) { - const $ = cheerio.load(item) - const time = $('.end-col > .duration-wrapper').data('stop') - - return dayjs(time) -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('#showListContainer > table > tbody > tr').toArray() -} +const cheerio = require('cheerio') +const dayjs = require('dayjs') + +module.exports = { + site: 'tvheute.at', + days: 2, + url({ channel, date }) { + return `https://tvheute.at/part/channel-shows/partial/${channel.site_id}/${date.format( + 'DD-MM-YYYY' + )}` + }, + parser: function ({ content }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + programs.push({ + title: parseTitle(item), + description: parseDescription(item), + image: parseImage(item), + category: parseCategory(item), + start: parseStart(item).toJSON(), + stop: parseStop(item).toJSON() + }) + }) + + return programs + }, + async channels() { + const axios = require('axios') + const html = await axios + .get('https://tvheute.at/part/channel-selection') + .then(r => r.data) + .catch(console.log) + + let channels = [] + + const $ = cheerio.load(html) + $('.sortable-list > li').each((i, el) => { + const name = $(el).find('label').text() + const site_id = $(el).find('input').attr('value') + + channels.push({ + lang: 'de', + site_id, + name + }) + }) + + return channels + } +} + +function parseTitle(item) { + const $ = cheerio.load(item) + + return $('.title-col strong').text() +} + +function parseDescription(item) { + const $ = cheerio.load(item) + + return $('.title-col .description').text() +} + +function parseCategory(item) { + const $ = cheerio.load(item) + + return $('.station-col > .type').text() +} + +function parseImage(item) { + const $ = cheerio.load(item) + const imgSrc = $('.title-col .image img').data('src-desktop') + + return imgSrc ? `https://tvheute.at${imgSrc}` : null +} + +function parseStart(item) { + const $ = cheerio.load(item) + const time = $('.end-col > .duration-wrapper').data('start') + + return dayjs(time) +} + +function parseStop(item) { + const $ = cheerio.load(item) + const time = $('.end-col > .duration-wrapper').data('stop') + + return dayjs(time) +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('#showListContainer > table > tbody > tr').toArray() +} diff --git a/sites/tvi.iol.pt/tvi.iol.pt.config.js b/sites/tvi.iol.pt/tvi.iol.pt.config.js index fce6b7dcb..49ad46314 100644 --- a/sites/tvi.iol.pt/tvi.iol.pt.config.js +++ b/sites/tvi.iol.pt/tvi.iol.pt.config.js @@ -1,76 +1,76 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'tvi.iol.pt', - url({ channel, date }) { - return `https://tvi.iol.pt/emissao/dia/${channel.site_id}?data=${date.format('YYYY-MM-DD')}` - }, - parser({ content, date }) { - let programs = [] - - const items = parseItems(content) - items.forEach(item => { - const prev = programs[programs.length - 1] - const $item = cheerio.load(item) - - let start = parseStart($item, date) - if (prev) { - if (start.isBefore(prev.start)) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } - prev.stop = start - } - - const stop = start.add(30, 'm') - - programs.push({ - title: parseTitle($item), - description: parseDescription($item), - icon: parseIcon($item), - start, - stop - }) - }) - - return programs - } -} - -function parseTitle($item) { - return $item('.guiatv-programa > h2').text().trim() -} - -function parseDescription($item) { - return $item('.guiatv-programa > .texto, .guiatv-programa > .texto2').text().trim() || null -} - -function parseIcon($item) { - const backgroundImage = $item('.picture16x9').css('background-image') - if (!backgroundImage) return null - const [, imageUrl] = backgroundImage.match(/url\((.*)\)/) || [null, null] - if (!imageUrl) return null - - return imageUrl -} - -function parseStart($item, date) { - const timezone = 'Europe/Madrid' - const time = $item('.hora').text().trim() - - return dayjs.tz(`${date.tz(timezone).format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', timezone) -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('.guiatv-linha').toArray() -} +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'tvi.iol.pt', + url({ channel, date }) { + return `https://tvi.iol.pt/emissao/dia/${channel.site_id}?data=${date.format('YYYY-MM-DD')}` + }, + parser({ content, date }) { + let programs = [] + + const items = parseItems(content) + items.forEach(item => { + const prev = programs[programs.length - 1] + const $item = cheerio.load(item) + + let start = parseStart($item, date) + if (prev) { + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } + prev.stop = start + } + + const stop = start.add(30, 'm') + + programs.push({ + title: parseTitle($item), + description: parseDescription($item), + icon: parseIcon($item), + start, + stop + }) + }) + + return programs + } +} + +function parseTitle($item) { + return $item('.guiatv-programa > h2').text().trim() +} + +function parseDescription($item) { + return $item('.guiatv-programa > .texto, .guiatv-programa > .texto2').text().trim() || null +} + +function parseIcon($item) { + const backgroundImage = $item('.picture16x9').css('background-image') + if (!backgroundImage) return null + const [, imageUrl] = backgroundImage.match(/url\((.*)\)/) || [null, null] + if (!imageUrl) return null + + return imageUrl +} + +function parseStart($item, date) { + const timezone = 'Europe/Madrid' + const time = $item('.hora').text().trim() + + return dayjs.tz(`${date.tz(timezone).format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', timezone) +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('.guiatv-linha').toArray() +} diff --git a/sites/tvi.iol.pt/tvi.iol.pt.test.js b/sites/tvi.iol.pt/tvi.iol.pt.test.js index 4ca367a4b..2a873211f 100644 --- a/sites/tvi.iol.pt/tvi.iol.pt.test.js +++ b/sites/tvi.iol.pt/tvi.iol.pt.test.js @@ -1,66 +1,66 @@ -const { parser, url } = require('./tvi.iol.pt.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-01-26', 'YYYY-MM-DD').startOf('d') -const channel = { site_id: 'tvi', xmltv_id: 'TVI.pt' } - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe('https://tvi.iol.pt/emissao/dia/tvi?data=2025-01-26') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - - let results = parser({ content, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - - return p - }) - - expect(results.length).toBe(16) - expect(results[0]).toMatchObject({ - title: 'As aventuras do Gato das Botas', - description: null, - icon: 'https://img.iol.pt/image/id/66d6fb1ad34e94b82904c3ce/300.jpg', - start: '2025-01-26T05:15:00.000Z', - stop: '2025-01-26T05:45:00.000Z' - }) - expect(results[5]).toMatchObject({ - title: 'Missa', - description: 'Gondomar', - icon: 'https://img.iol.pt/image/id/6218de030cf21a10a4218ba3/300.jpg', - start: '2025-01-26T09:00:00.000Z', - stop: '2025-01-26T10:00:00.000Z' - }) - expect(results[7]).toMatchObject({ - title: 'Por um Triz', - description: 'Um segundo pode mudar tudo.', - icon: 'https://img.iol.pt/image/id/6777dcffd34e94b829094756/300.jpg', - start: '2025-01-26T11:00:00.000Z', - stop: '2025-01-26T11:58:00.000Z' - }) - expect(results[15]).toMatchObject({ - title: 'As aventuras do Gato das Botas', - description: null, - icon: 'https://img.iol.pt/image/id/66d6fb1ad34e94b82904c3ce/300.jpg', - start: '2025-01-27T04:50:00.000Z', - stop: '2025-01-27T05:20:00.000Z' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - date, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) - }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./tvi.iol.pt.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-01-26', 'YYYY-MM-DD').startOf('d') +const channel = { site_id: 'tvi', xmltv_id: 'TVI.pt' } + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe('https://tvi.iol.pt/emissao/dia/tvi?data=2025-01-26') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + + let results = parser({ content, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + + return p + }) + + expect(results.length).toBe(16) + expect(results[0]).toMatchObject({ + title: 'As aventuras do Gato das Botas', + description: null, + icon: 'https://img.iol.pt/image/id/66d6fb1ad34e94b82904c3ce/300.jpg', + start: '2025-01-26T05:15:00.000Z', + stop: '2025-01-26T05:45:00.000Z' + }) + expect(results[5]).toMatchObject({ + title: 'Missa', + description: 'Gondomar', + icon: 'https://img.iol.pt/image/id/6218de030cf21a10a4218ba3/300.jpg', + start: '2025-01-26T09:00:00.000Z', + stop: '2025-01-26T10:00:00.000Z' + }) + expect(results[7]).toMatchObject({ + title: 'Por um Triz', + description: 'Um segundo pode mudar tudo.', + icon: 'https://img.iol.pt/image/id/6777dcffd34e94b829094756/300.jpg', + start: '2025-01-26T11:00:00.000Z', + stop: '2025-01-26T11:58:00.000Z' + }) + expect(results[15]).toMatchObject({ + title: 'As aventuras do Gato das Botas', + description: null, + icon: 'https://img.iol.pt/image/id/66d6fb1ad34e94b82904c3ce/300.jpg', + start: '2025-01-27T04:50:00.000Z', + stop: '2025-01-27T05:20:00.000Z' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + date, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) + }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/tvim.tv/tvim.tv.config.js b/sites/tvim.tv/tvim.tv.config.js index f67ac2ead..48b760adf 100644 --- a/sites/tvim.tv/tvim.tv.config.js +++ b/sites/tvim.tv/tvim.tv.config.js @@ -1,61 +1,61 @@ -const dayjs = require('dayjs') - -module.exports = { - site: 'tvim.tv', - days: 2, - url: function ({ date, channel }) { - return `https://www.tvim.tv/script/program_epg?date=${date.format('DD.MM.YYYY')}&prog=${ - channel.site_id - }&server_time=true` - }, - parser: function ({ content }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - const start = parseStart(item) - const stop = parseStop(item) - - programs.push({ - title: item.title, - description: item.desc, - category: item.genre, - start: start.toString(), - stop: stop.toString() - }) - }) - - return programs - }, - async channels() { - const axios = require('axios') - const data = await axios - .get('https://www.tvim.tv/script/epg/category_channels?category=all&filter=playable') - .then(r => r.data) - .catch(console.log) - - let channels = [] - data.data.forEach(item => { - channels.push({ - lang: 'sq', - site_id: item.epg_id, - name: item.name - }) - }) - - return channels - } -} - -function parseStart(item) { - return dayjs.unix(item.from_utc) -} - -function parseStop(item) { - return dayjs.unix(item.end_utc) -} - -function parseItems(content) { - const parsed = JSON.parse(content) - - return parsed.data.prog || [] -} +const dayjs = require('dayjs') + +module.exports = { + site: 'tvim.tv', + days: 2, + url: function ({ date, channel }) { + return `https://www.tvim.tv/script/program_epg?date=${date.format('DD.MM.YYYY')}&prog=${ + channel.site_id + }&server_time=true` + }, + parser: function ({ content }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + const start = parseStart(item) + const stop = parseStop(item) + + programs.push({ + title: item.title, + description: item.desc, + category: item.genre, + start: start.toString(), + stop: stop.toString() + }) + }) + + return programs + }, + async channels() { + const axios = require('axios') + const data = await axios + .get('https://www.tvim.tv/script/epg/category_channels?category=all&filter=playable') + .then(r => r.data) + .catch(console.log) + + let channels = [] + data.data.forEach(item => { + channels.push({ + lang: 'sq', + site_id: item.epg_id, + name: item.name + }) + }) + + return channels + } +} + +function parseStart(item) { + return dayjs.unix(item.from_utc) +} + +function parseStop(item) { + return dayjs.unix(item.end_utc) +} + +function parseItems(content) { + const parsed = JSON.parse(content) + + return parsed.data.prog || [] +} diff --git a/sites/tvinsider.com/tvinsider.com.test.js b/sites/tvinsider.com/tvinsider.com.test.js index ab6db7778..ffddcf6fc 100644 --- a/sites/tvinsider.com/tvinsider.com.test.js +++ b/sites/tvinsider.com/tvinsider.com.test.js @@ -1,56 +1,56 @@ -const { parser, url } = require('./tvinsider.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-01-18', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'movieplex', - xmltv_id: 'MoviePlexEast.us' -} - -it('can generate valid url', () => { - expect(url({ channel })).toBe('https://www.tvinsider.com/network/movieplex/schedule/') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - const results = parser({ content, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(15) - expect(results[0]).toMatchObject({ - start: '2025-01-18T05:45:00.000Z', - stop: '2025-01-18T07:12:00.000Z', - title: 'Wild Oats', - category: 'Feature Film', - date: '2016', - description: - 'Two best friends travel to the Canary Islands after one mistakenly receives a large amount of money.' - }) - expect(results[14]).toMatchObject({ - start: '2025-01-19T04:42:00.000Z', - stop: '2025-01-19T05:12:00.000Z', - title: 'Trading Mom', - category: 'Feature Film', - date: '1994', - description: - 'Kids (Anna Chlumsky, Aaron Michael Metchik) under magic spell shop for another mother.' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - date, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) - }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./tvinsider.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-01-18', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'movieplex', + xmltv_id: 'MoviePlexEast.us' +} + +it('can generate valid url', () => { + expect(url({ channel })).toBe('https://www.tvinsider.com/network/movieplex/schedule/') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + const results = parser({ content, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(15) + expect(results[0]).toMatchObject({ + start: '2025-01-18T05:45:00.000Z', + stop: '2025-01-18T07:12:00.000Z', + title: 'Wild Oats', + category: 'Feature Film', + date: '2016', + description: + 'Two best friends travel to the Canary Islands after one mistakenly receives a large amount of money.' + }) + expect(results[14]).toMatchObject({ + start: '2025-01-19T04:42:00.000Z', + stop: '2025-01-19T05:12:00.000Z', + title: 'Trading Mom', + category: 'Feature Film', + date: '1994', + description: + 'Kids (Anna Chlumsky, Aaron Michael Metchik) under magic spell shop for another mother.' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + date, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) + }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/tvireland.ie/tvireland.ie.test.js b/sites/tvireland.ie/tvireland.ie.test.js index 7088c1299..d65eaf8ea 100644 --- a/sites/tvireland.ie/tvireland.ie.test.js +++ b/sites/tvireland.ie/tvireland.ie.test.js @@ -1,50 +1,50 @@ -const { parser, url } = require('./tvireland.ie.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-11-25', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '2378/virgin-media-more', - xmltv_id: 'VirginMediaMore.ie' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://www.tvireland.ie/tv/listings/channel/2378/virgin-media-more?dt=2023-11-25' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - const results = parser({ content, channel, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2023-11-25T00:20:00.000Z', - stop: '2023-11-25T01:10:00.000Z', - title: 'Best of Rugby World Cup' - }) - - expect(results[13]).toMatchObject({ - start: '2023-11-25T23:30:00.000Z', - stop: '2023-11-26T00:00:00.000Z', - title: 'Stories from the Street' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: '' - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./tvireland.ie.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-11-25', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '2378/virgin-media-more', + xmltv_id: 'VirginMediaMore.ie' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://www.tvireland.ie/tv/listings/channel/2378/virgin-media-more?dt=2023-11-25' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + const results = parser({ content, channel, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2023-11-25T00:20:00.000Z', + stop: '2023-11-25T01:10:00.000Z', + title: 'Best of Rugby World Cup' + }) + + expect(results[13]).toMatchObject({ + start: '2023-11-25T23:30:00.000Z', + stop: '2023-11-26T00:00:00.000Z', + title: 'Stories from the Street' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: '' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/tvkaista.org/tvkaista.org.config.js b/sites/tvkaista.org/tvkaista.org.config.js index 6647f0bc0..4767d2edf 100644 --- a/sites/tvkaista.org/tvkaista.org.config.js +++ b/sites/tvkaista.org/tvkaista.org.config.js @@ -1,169 +1,169 @@ -const doFetch = require('@ntlab/sfetch') -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -const tz = 'Europe/Helsinki' - -module.exports = { - site: 'tvkaista.org', - days: 2, - url({ channel, date }) { - return `https://www.tvkaista.org/${channel.site_id}/${date.format('YYYY-MM-DD')}` - }, - parser({ content, date }) { - let programs = [] - const items = parseItems(content) - - items.forEach(item => { - const prev = programs[programs.length - 1] - const $item = cheerio.load(item) - - let start = parseStart($item, date) - let stop = parseStop($item, start) - - if (prev) { - if (start.isBefore(prev.start)) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } else if (stop.isBefore(start)) { - stop = stop.add(1, 'd') - date = date.add(1, 'd') - } - } else { - if (start.hour() > 18) { - start = start.subtract(1, 'd') - date = date.subtract(1, 'd') - } - } - - programs.push({ - title: parseTitle($item), - description: parseDescription($item), - season: parseSeason($item), - episode: parseEpisode($item), - categories: parseCategories($item), - rating: parseRating($item), - start, - stop - }) - }) - - return programs - }, - async channels() { - let channels = [] - - const queue = ['https://www.tvkaista.org/', 'https://www.tvkaista.org/maksukanavat/'] - await doFetch(queue, (url, res) => { - const $ = cheerio.load(res) - $('body > main > div > div.row > div').each((i, el) => { - const link = $(el).find('div > div > div > div.col-auto > a') - const img = link.find('img.channel-logo') - const name = link.text().trim() || img.attr('alt') - const [, site_id] = link.attr('href').split('/') - - channels.push({ - lang: 'fi', - name, - site_id - }) - }) - }) - - return channels - } -} - -function parseRating($item) { - let rating = $item( - 'div.d-flex.flex-row.bd-highlight > div.bd-highlight.flex-fill > span:nth-child(3) > img' - ).attr('alt') - - return rating - ? { - system: 'VET', - value: rating.replace(/\(|\)/g, '') - } - : null -} - -function parseCategories($item) { - return $item('div.collapse > .badge') - .map((i, el) => $item(el).text().trim()) - .get() -} - -function parseSeason($item) { - const string = $item( - 'div.d-flex.flex-row.bd-highlight > div.bd-highlight.flex-fill > span:nth-child(2)' - ) - .text() - .trim() - if (!string) return null - - let [, season] = string.match(/S(\d{2})/) || [null, null] - - return season ? parseInt(season) : null -} - -function parseEpisode($item) { - const string = $item( - 'div.d-flex.flex-row.bd-highlight > div.bd-highlight.flex-fill > span:nth-child(2)' - ) - .text() - .trim() - if (!string) return null - - let [, episode] = string.match(/E(\d{2})/) || [null, null] - - return episode ? parseInt(episode) : null -} - -function parseStart($item, date) { - const [time] = $item('div.d-flex.flex-row.bd-highlight > div.bd-highlight.me-2') - .text() - .trim() - .split('-') - - return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', tz) -} - -function parseStop($item, date) { - const [, time] = $item('div.d-flex.flex-row.bd-highlight > div.bd-highlight.me-2') - .text() - .trim() - .split('-') - - return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', tz) -} - -function parseTitle($item) { - return $item('div.d-flex.flex-row.bd-highlight > div.bd-highlight.flex-fill > span:nth-child(1)') - .text() - .trim() -} - -function parseDescription($item) { - return ( - $item('div.collapse > p') - .text() - .replace(/\n/g, '') - .replace(/\s\s+/g, ' ') - // eslint-disable-next-line no-irregular-whitespace - .replace(/ /g, ' ') - .trim() - ) -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('ul.list-group > li').toArray() -} +const doFetch = require('@ntlab/sfetch') +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +const tz = 'Europe/Helsinki' + +module.exports = { + site: 'tvkaista.org', + days: 2, + url({ channel, date }) { + return `https://www.tvkaista.org/${channel.site_id}/${date.format('YYYY-MM-DD')}` + }, + parser({ content, date }) { + let programs = [] + const items = parseItems(content) + + items.forEach(item => { + const prev = programs[programs.length - 1] + const $item = cheerio.load(item) + + let start = parseStart($item, date) + let stop = parseStop($item, start) + + if (prev) { + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } else if (stop.isBefore(start)) { + stop = stop.add(1, 'd') + date = date.add(1, 'd') + } + } else { + if (start.hour() > 18) { + start = start.subtract(1, 'd') + date = date.subtract(1, 'd') + } + } + + programs.push({ + title: parseTitle($item), + description: parseDescription($item), + season: parseSeason($item), + episode: parseEpisode($item), + categories: parseCategories($item), + rating: parseRating($item), + start, + stop + }) + }) + + return programs + }, + async channels() { + let channels = [] + + const queue = ['https://www.tvkaista.org/', 'https://www.tvkaista.org/maksukanavat/'] + await doFetch(queue, (url, res) => { + const $ = cheerio.load(res) + $('body > main > div > div.row > div').each((i, el) => { + const link = $(el).find('div > div > div > div.col-auto > a') + const img = link.find('img.channel-logo') + const name = link.text().trim() || img.attr('alt') + const [, site_id] = link.attr('href').split('/') + + channels.push({ + lang: 'fi', + name, + site_id + }) + }) + }) + + return channels + } +} + +function parseRating($item) { + let rating = $item( + 'div.d-flex.flex-row.bd-highlight > div.bd-highlight.flex-fill > span:nth-child(3) > img' + ).attr('alt') + + return rating + ? { + system: 'VET', + value: rating.replace(/\(|\)/g, '') + } + : null +} + +function parseCategories($item) { + return $item('div.collapse > .badge') + .map((i, el) => $item(el).text().trim()) + .get() +} + +function parseSeason($item) { + const string = $item( + 'div.d-flex.flex-row.bd-highlight > div.bd-highlight.flex-fill > span:nth-child(2)' + ) + .text() + .trim() + if (!string) return null + + let [, season] = string.match(/S(\d{2})/) || [null, null] + + return season ? parseInt(season) : null +} + +function parseEpisode($item) { + const string = $item( + 'div.d-flex.flex-row.bd-highlight > div.bd-highlight.flex-fill > span:nth-child(2)' + ) + .text() + .trim() + if (!string) return null + + let [, episode] = string.match(/E(\d{2})/) || [null, null] + + return episode ? parseInt(episode) : null +} + +function parseStart($item, date) { + const [time] = $item('div.d-flex.flex-row.bd-highlight > div.bd-highlight.me-2') + .text() + .trim() + .split('-') + + return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', tz) +} + +function parseStop($item, date) { + const [, time] = $item('div.d-flex.flex-row.bd-highlight > div.bd-highlight.me-2') + .text() + .trim() + .split('-') + + return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', tz) +} + +function parseTitle($item) { + return $item('div.d-flex.flex-row.bd-highlight > div.bd-highlight.flex-fill > span:nth-child(1)') + .text() + .trim() +} + +function parseDescription($item) { + return ( + $item('div.collapse > p') + .text() + .replace(/\n/g, '') + .replace(/\s\s+/g, ' ') + // eslint-disable-next-line no-irregular-whitespace + .replace(/ /g, ' ') + .trim() + ) +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('ul.list-group > li').toArray() +} diff --git a/sites/tvkaista.org/tvkaista.org.test.js b/sites/tvkaista.org/tvkaista.org.test.js index 2e829f175..a8cdacf56 100644 --- a/sites/tvkaista.org/tvkaista.org.test.js +++ b/sites/tvkaista.org/tvkaista.org.test.js @@ -1,93 +1,93 @@ -const { parser, url } = require('./tvkaista.org.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -let date = dayjs.utc('2025-03-01', 'YYYY-MM-DD').startOf('d') -const channel = { site_id: 'yle-tv1' } - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe('https://www.tvkaista.org/yle-tv1/2025-03-01') -}) - -it('can parse response for today', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_1.html')) - - let results = parser({ content, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - - return p - }) - - expect(results.length).toBe(45) - expect(results[0]).toMatchObject({ - title: 'Alice & Jack', - description: - 'Kausi 1, 2/6. Säröjä. Jack on onnellisesti naimisissa, ja on pienen tyttären isä. Yllättävä puhelu Alicelta suistaa Jackin elämän kuitenkin pois raiteiltaan. Tunteiden myllerryksessä Jack suostuu tapaamaan Alicen salassa vaimoltaa', - season: 1, - episode: 2, - rating: { - system: 'VET', - value: '12' - }, - categories: ['Sarja'], - start: '2025-02-28T21:20:00.000Z', - stop: '2025-02-28T22:04:00.000Z' - }) -}) - -it('can parse response for next day', () => { - date = dayjs.utc('2025-03-03', 'YYYY-MM-DD').startOf('d') - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_2.html')) - - let results = parser({ content, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - - return p - }) - - expect(results.length).toBe(39) - expect(results[0]).toMatchObject({ - title: 'Sodan silpoma elämä', - description: - 'Oleh Stahanov haavoittui vakavasti Itä-Ukrainan rintamalla. Miten elämä rakennetaan uudelleen, kun toipuminen vaatii selviytymistä niin fyysisistä vammoista kuin henkisestä taakastakin? Ohjaus: Viivi Berghem (Suomi 2024)', - start: '2025-03-02T21:05:00.000Z', - stop: '2025-03-02T22:02:00.000Z' - }) - expect(results[5]).toMatchObject({ - title: 'La Promesa - Salaisuuksien kartano', - description: - 'Kausi 1, 3/122. Päätöksen vaikeus. Jimena pääsee lennolle Manuelin kanssa tämän tunnustettua ensin lentokilpailuun osallistumisensa. Johtaako lento näiden kahden lähentymiseen? Onko mysteerikokin henkilöllisy', - season: 1, - episode: 3, - categories: ['Sarja'], - rating: { - system: 'VET', - value: '12' - }, - start: '2025-03-03T08:00:00.000Z', - stop: '2025-03-03T08:52:00.000Z' - }) - expect(results[38]).toMatchObject({ - title: 'Unelma työstä', - description: - 'Noin miljoona suomalaista on joko työttömänä tai työskentelee osa- tai määräaikaisessa työsuhteessa. Dokumentissa tarinansa kertoo entinen työministeri, loppuun palanut oikeustieteen tohtori, akateeminen pätkätyöläinen ja nuori teatte', - start: '2025-03-03T21:15:00.000Z', - stop: '2025-03-03T22:11:00.000Z' - }) -}) - -it('can handle empty guide', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) - const results = parser({ content, date }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./tvkaista.org.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +let date = dayjs.utc('2025-03-01', 'YYYY-MM-DD').startOf('d') +const channel = { site_id: 'yle-tv1' } + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe('https://www.tvkaista.org/yle-tv1/2025-03-01') +}) + +it('can parse response for today', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_1.html')) + + let results = parser({ content, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + + return p + }) + + expect(results.length).toBe(45) + expect(results[0]).toMatchObject({ + title: 'Alice & Jack', + description: + 'Kausi 1, 2/6. Säröjä. Jack on onnellisesti naimisissa, ja on pienen tyttären isä. Yllättävä puhelu Alicelta suistaa Jackin elämän kuitenkin pois raiteiltaan. Tunteiden myllerryksessä Jack suostuu tapaamaan Alicen salassa vaimoltaa', + season: 1, + episode: 2, + rating: { + system: 'VET', + value: '12' + }, + categories: ['Sarja'], + start: '2025-02-28T21:20:00.000Z', + stop: '2025-02-28T22:04:00.000Z' + }) +}) + +it('can parse response for next day', () => { + date = dayjs.utc('2025-03-03', 'YYYY-MM-DD').startOf('d') + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_2.html')) + + let results = parser({ content, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + + return p + }) + + expect(results.length).toBe(39) + expect(results[0]).toMatchObject({ + title: 'Sodan silpoma elämä', + description: + 'Oleh Stahanov haavoittui vakavasti Itä-Ukrainan rintamalla. Miten elämä rakennetaan uudelleen, kun toipuminen vaatii selviytymistä niin fyysisistä vammoista kuin henkisestä taakastakin? Ohjaus: Viivi Berghem (Suomi 2024)', + start: '2025-03-02T21:05:00.000Z', + stop: '2025-03-02T22:02:00.000Z' + }) + expect(results[5]).toMatchObject({ + title: 'La Promesa - Salaisuuksien kartano', + description: + 'Kausi 1, 3/122. Päätöksen vaikeus. Jimena pääsee lennolle Manuelin kanssa tämän tunnustettua ensin lentokilpailuun osallistumisensa. Johtaako lento näiden kahden lähentymiseen? Onko mysteerikokin henkilöllisy', + season: 1, + episode: 3, + categories: ['Sarja'], + rating: { + system: 'VET', + value: '12' + }, + start: '2025-03-03T08:00:00.000Z', + stop: '2025-03-03T08:52:00.000Z' + }) + expect(results[38]).toMatchObject({ + title: 'Unelma työstä', + description: + 'Noin miljoona suomalaista on joko työttömänä tai työskentelee osa- tai määräaikaisessa työsuhteessa. Dokumentissa tarinansa kertoo entinen työministeri, loppuun palanut oikeustieteen tohtori, akateeminen pätkätyöläinen ja nuori teatte', + start: '2025-03-03T21:15:00.000Z', + stop: '2025-03-03T22:11:00.000Z' + }) +}) + +it('can handle empty guide', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html')) + const results = parser({ content, date }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/tvmi.mt/tvmi.mt.config.js b/sites/tvmi.mt/tvmi.mt.config.js index e2692031f..a62b3f5a8 100644 --- a/sites/tvmi.mt/tvmi.mt.config.js +++ b/sites/tvmi.mt/tvmi.mt.config.js @@ -1,77 +1,77 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') - -dayjs.extend(utc) -dayjs.extend(timezone) - -module.exports = { - site: 'tvmi.mt', - days: 2, - url: function ({ date, channel }) { - return `https://tvmi.mt/schedule/${channel.site_id}/${date.format('YYYY-MM-DD')}` - }, - parser: function ({ content, date }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - const $item = cheerio.load(item) - const prev = programs[programs.length - 1] - let start = parseStart($item, date) - if (prev) { - if (start.isBefore(prev.start)) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.add(30, 'm') - programs.push({ - title: parseTitle($item), - description: parseDescription($item), - image: parseImage($item), - start, - stop - }) - }) - - return programs - } -} - -function parseTitle($item) { - return $item('div > div:nth-child(2) > div:nth-child(2),a > div:nth-child(2) > div:nth-child(2)') - .text() - .trim() -} - -function parseDescription($item) { - return $item('div > div:nth-child(2) > div:nth-child(3),a > div:nth-child(2) > div:nth-child(3)') - .text() - .trim() -} - -function parseImage($item) { - const bg = $item('div > div:nth-child(1) > div > div,a > div:nth-child(1) > div').data('bg') - - return bg ? `https:${bg}` : null -} - -function parseStart($item, date) { - const timeString = $item( - 'div > div:nth-child(2) > div:nth-child(1),a > div:nth-child(2) > div:nth-child(1)' - ) - .text() - .trim() - const [, HH, mm] = timeString.match(/^(\d{2}):(\d{2})/) || [null, null, null] - if (!HH || !mm) return null - - return dayjs.tz(`${date.format('YYYY-MM-DD')} ${HH}:${mm}`, 'YYYY-MM-DD HH:mm', 'Europe/Malta') -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('body > main > div.mt-8 > div').toArray() -} +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') + +dayjs.extend(utc) +dayjs.extend(timezone) + +module.exports = { + site: 'tvmi.mt', + days: 2, + url: function ({ date, channel }) { + return `https://tvmi.mt/schedule/${channel.site_id}/${date.format('YYYY-MM-DD')}` + }, + parser: function ({ content, date }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + const $item = cheerio.load(item) + const prev = programs[programs.length - 1] + let start = parseStart($item, date) + if (prev) { + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.add(30, 'm') + programs.push({ + title: parseTitle($item), + description: parseDescription($item), + image: parseImage($item), + start, + stop + }) + }) + + return programs + } +} + +function parseTitle($item) { + return $item('div > div:nth-child(2) > div:nth-child(2),a > div:nth-child(2) > div:nth-child(2)') + .text() + .trim() +} + +function parseDescription($item) { + return $item('div > div:nth-child(2) > div:nth-child(3),a > div:nth-child(2) > div:nth-child(3)') + .text() + .trim() +} + +function parseImage($item) { + const bg = $item('div > div:nth-child(1) > div > div,a > div:nth-child(1) > div').data('bg') + + return bg ? `https:${bg}` : null +} + +function parseStart($item, date) { + const timeString = $item( + 'div > div:nth-child(2) > div:nth-child(1),a > div:nth-child(2) > div:nth-child(1)' + ) + .text() + .trim() + const [, HH, mm] = timeString.match(/^(\d{2}):(\d{2})/) || [null, null, null] + if (!HH || !mm) return null + + return dayjs.tz(`${date.format('YYYY-MM-DD')} ${HH}:${mm}`, 'YYYY-MM-DD HH:mm', 'Europe/Malta') +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('body > main > div.mt-8 > div').toArray() +} diff --git a/sites/tvmi.mt/tvmi.mt.test.js b/sites/tvmi.mt/tvmi.mt.test.js index 7ce6f899f..f4cc4be2b 100644 --- a/sites/tvmi.mt/tvmi.mt.test.js +++ b/sites/tvmi.mt/tvmi.mt.test.js @@ -1,53 +1,53 @@ -const { parser, url } = require('./tvmi.mt.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-10-29', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '2', - xmltv_id: 'TVM.mt' -} - -it('can generate valid url', () => { - expect(url({ date, channel })).toBe('https://tvmi.mt/schedule/2/2022-10-29') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - const results = parser({ content, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2022-10-29T03:30:00.000Z', - stop: '2022-10-29T04:00:00.000Z', - title: 'Bizzilla', - description: - 'Storja ta’ tliet familji, tnejn minnhom miżżewġin bejniethom, u familja oħra li għalkemm mhijiex, b’daqshekk ma jfissirx li mhijiex parti ntegrali fil-kompliċitá li ilha għaddejja bejniethom għal dawn l-aħħar tletin sena.', - image: - 'https://dist4.tvmi.mt/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjMyNjEwNywiYXVkIjoiMTg4LjI0Mi40OC45MyIsImV4cCI6MTY2NzAxNjM1OH0.N4de761te_pRvWwSUnF6httRAzdukup5syejwXTUv8g/vod/663927/image.jpg' - }) - - expect(results[1]).toMatchObject({ - start: '2022-10-29T04:00:00.000Z', - stop: '2022-10-29T04:30:00.000Z', - title: 'The Adventures of Puss in Boots', - image: - 'https://dist4.tvmi.mt/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjMyNjEwNywiYXVkIjoiMTg4LjI0Mi40OC45MyIsImV4cCI6MTY2NzAxNjM1OH0.N4de761te_pRvWwSUnF6httRAzdukup5syejwXTUv8g/vod/747336/image.jpg' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: '', - channel - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./tvmi.mt.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-10-29', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '2', + xmltv_id: 'TVM.mt' +} + +it('can generate valid url', () => { + expect(url({ date, channel })).toBe('https://tvmi.mt/schedule/2/2022-10-29') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + const results = parser({ content, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2022-10-29T03:30:00.000Z', + stop: '2022-10-29T04:00:00.000Z', + title: 'Bizzilla', + description: + 'Storja ta’ tliet familji, tnejn minnhom miżżewġin bejniethom, u familja oħra li għalkemm mhijiex, b’daqshekk ma jfissirx li mhijiex parti ntegrali fil-kompliċitá li ilha għaddejja bejniethom għal dawn l-aħħar tletin sena.', + image: + 'https://dist4.tvmi.mt/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjMyNjEwNywiYXVkIjoiMTg4LjI0Mi40OC45MyIsImV4cCI6MTY2NzAxNjM1OH0.N4de761te_pRvWwSUnF6httRAzdukup5syejwXTUv8g/vod/663927/image.jpg' + }) + + expect(results[1]).toMatchObject({ + start: '2022-10-29T04:00:00.000Z', + stop: '2022-10-29T04:30:00.000Z', + title: 'The Adventures of Puss in Boots', + image: + 'https://dist4.tvmi.mt/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjMyNjEwNywiYXVkIjoiMTg4LjI0Mi40OC45MyIsImV4cCI6MTY2NzAxNjM1OH0.N4de761te_pRvWwSUnF6httRAzdukup5syejwXTUv8g/vod/747336/image.jpg' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: '', + channel + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/tvmusor.hu/tvmusor.hu.test.js b/sites/tvmusor.hu/tvmusor.hu.test.js index 79ed7e7ca..0336ff5ca 100644 --- a/sites/tvmusor.hu/tvmusor.hu.test.js +++ b/sites/tvmusor.hu/tvmusor.hu.test.js @@ -1,69 +1,69 @@ -const { parser, url, request } = require('./tvmusor.hu.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-11-19', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '290', - xmltv_id: 'M4Sport.hu' -} - -it('can generate valid url', () => { - expect(url).toBe('https://tvmusor.borsonline.hu/a/get-events/') -}) - -it('can generate valid request method', () => { - expect(request.method).toBe('POST') -}) - -it('can generate valid request headers', () => { - expect(request.headers).toMatchObject({ - 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' - }) -}) - -it('can generate valid request data', () => { - const result = request.data({ channel, date }) - expect(result.get('data')).toBe('{"blocks":["290|2022-11-19"]}') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - let results = parser({ content, channel, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2022-11-18T23:30:00.000Z', - stop: '2022-11-19T00:55:00.000Z', - title: 'Rövidpályás Úszó Országos Bajnokság', - category: 'sportműsor', - description: 'Forma-1 magazin. Hírek, információk, érdekességek a Forma-1 világából.', - image: - 'https://tvmusor.borsonline.hu/images/events/408/f1e45193930943d9ee29769e0afa902aff0e4a90-better-call-saul.jpg' - }) - - expect(results[1]).toMatchObject({ - start: '2022-11-19T00:55:00.000Z', - stop: '2022-11-19T01:10:00.000Z', - title: 'Sportlövészet', - category: 'sportműsor' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: '{"status":"error","reason":"invalid blocks"}' - }) - expect(result).toMatchObject([]) -}) +const { parser, url, request } = require('./tvmusor.hu.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-11-19', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '290', + xmltv_id: 'M4Sport.hu' +} + +it('can generate valid url', () => { + expect(url).toBe('https://tvmusor.borsonline.hu/a/get-events/') +}) + +it('can generate valid request method', () => { + expect(request.method).toBe('POST') +}) + +it('can generate valid request headers', () => { + expect(request.headers).toMatchObject({ + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' + }) +}) + +it('can generate valid request data', () => { + const result = request.data({ channel, date }) + expect(result.get('data')).toBe('{"blocks":["290|2022-11-19"]}') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + let results = parser({ content, channel, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2022-11-18T23:30:00.000Z', + stop: '2022-11-19T00:55:00.000Z', + title: 'Rövidpályás Úszó Országos Bajnokság', + category: 'sportműsor', + description: 'Forma-1 magazin. Hírek, információk, érdekességek a Forma-1 világából.', + image: + 'https://tvmusor.borsonline.hu/images/events/408/f1e45193930943d9ee29769e0afa902aff0e4a90-better-call-saul.jpg' + }) + + expect(results[1]).toMatchObject({ + start: '2022-11-19T00:55:00.000Z', + stop: '2022-11-19T01:10:00.000Z', + title: 'Sportlövészet', + category: 'sportműsor' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: '{"status":"error","reason":"invalid blocks"}' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/tvmustra.hu/tvmustra.hu.config.js b/sites/tvmustra.hu/tvmustra.hu.config.js index ca59ea391..0374d37ea 100644 --- a/sites/tvmustra.hu/tvmustra.hu.config.js +++ b/sites/tvmustra.hu/tvmustra.hu.config.js @@ -1,78 +1,78 @@ -const cheerio = require('cheerio') -const axios = require('axios') -const { DateTime } = require('luxon') - -module.exports = { - site: 'tvmustra.hu', - days: 2, - url({ channel, date }) { - return `https://www.tvmustra.hu/tvmusor/${channel.site_id}/${date.format('YYYY-MM-DD')}` - }, - parser({ content, date }) { - const programs = [] - const items = parseItems(content) - items.forEach(item => { - const prev = programs[programs.length - 1] - const $item = cheerio.load(item) - let start = parseStart($item, date) - if (!start) return - if (prev) { - if (start < prev.start) { - start = start.plus({ days: 1 }) - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.plus({ minute: 30 }) - - programs.push({ - title: parseTitle($item), - start, - stop - }) - }) - - return programs - }, - async channels() { - const html = await axios - .get('https://www.tvmustra.hu/') - .then(r => r.data) - .catch(console.log) - const $ = cheerio.load(html) - const items = $('.channel-selector option').toArray() - - const channels = [] - items.forEach(item => { - const name = $(item).text().trim() - const site_id = $(item).attr('value').trim() - if (!site_id) return - - channels.push({ - lang: 'hu', - site_id, - name - }) - }) - - return channels - } -} - -function parseTitle($item) { - return $item('.musor_lista_cim, .musor_lista_cim2').text().trim() -} - -function parseStart($item, date) { - const time = $item('.musor_lista_idopont, .musor_lista_idopont2').text().trim() - - return DateTime.fromFormat(`${date.format('YYYY-MM-DD')} ${time}`, 'yyyy-MM-dd HH:mm', { - zone: 'Europe/Budapest' - }).toUTC() -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('#epg-container > div:nth-child(4) > div.col-6_sor3 > div.showtime').toArray() -} +const cheerio = require('cheerio') +const axios = require('axios') +const { DateTime } = require('luxon') + +module.exports = { + site: 'tvmustra.hu', + days: 2, + url({ channel, date }) { + return `https://www.tvmustra.hu/tvmusor/${channel.site_id}/${date.format('YYYY-MM-DD')}` + }, + parser({ content, date }) { + const programs = [] + const items = parseItems(content) + items.forEach(item => { + const prev = programs[programs.length - 1] + const $item = cheerio.load(item) + let start = parseStart($item, date) + if (!start) return + if (prev) { + if (start < prev.start) { + start = start.plus({ days: 1 }) + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.plus({ minute: 30 }) + + programs.push({ + title: parseTitle($item), + start, + stop + }) + }) + + return programs + }, + async channels() { + const html = await axios + .get('https://www.tvmustra.hu/') + .then(r => r.data) + .catch(console.log) + const $ = cheerio.load(html) + const items = $('.channel-selector option').toArray() + + const channels = [] + items.forEach(item => { + const name = $(item).text().trim() + const site_id = $(item).attr('value').trim() + if (!site_id) return + + channels.push({ + lang: 'hu', + site_id, + name + }) + }) + + return channels + } +} + +function parseTitle($item) { + return $item('.musor_lista_cim, .musor_lista_cim2').text().trim() +} + +function parseStart($item, date) { + const time = $item('.musor_lista_idopont, .musor_lista_idopont2').text().trim() + + return DateTime.fromFormat(`${date.format('YYYY-MM-DD')} ${time}`, 'yyyy-MM-dd HH:mm', { + zone: 'Europe/Budapest' + }).toUTC() +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('#epg-container > div:nth-child(4) > div.col-6_sor3 > div.showtime').toArray() +} diff --git a/sites/tvmustra.hu/tvmustra.hu.test.js b/sites/tvmustra.hu/tvmustra.hu.test.js index a10936292..ce7a059f9 100644 --- a/sites/tvmustra.hu/tvmustra.hu.test.js +++ b/sites/tvmustra.hu/tvmustra.hu.test.js @@ -1,47 +1,47 @@ -const { parser, url } = require('./tvmustra.hu.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-01-17', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'M1HD', - xmltv_id: 'M1HD.hu' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe('https://www.tvmustra.hu/tvmusor/M1HD/2025-01-17') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - const results = parser({ content, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(98) - expect(results[0]).toMatchObject({ - start: '2025-01-17T05:00:00.000Z', - stop: '2025-01-17T05:30:00.000Z', - title: 'HÍRADÓ' - }) - expect(results[97]).toMatchObject({ - start: '2025-01-18T04:00:00.000Z', - stop: '2025-01-18T04:30:00.000Z', - title: 'Ma éjszaka' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - date, - content: '' - }) - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./tvmustra.hu.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-01-17', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'M1HD', + xmltv_id: 'M1HD.hu' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe('https://www.tvmustra.hu/tvmusor/M1HD/2025-01-17') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + const results = parser({ content, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(98) + expect(results[0]).toMatchObject({ + start: '2025-01-17T05:00:00.000Z', + stop: '2025-01-17T05:30:00.000Z', + title: 'HÍRADÓ' + }) + expect(results[97]).toMatchObject({ + start: '2025-01-18T04:00:00.000Z', + stop: '2025-01-18T04:30:00.000Z', + title: 'Ma éjszaka' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + date, + content: '' + }) + expect(results).toMatchObject([]) +}) diff --git a/sites/tvpassport.com/tvpassport.com.config.js b/sites/tvpassport.com/tvpassport.com.config.js index 95adc8744..b15ec990a 100644 --- a/sites/tvpassport.com/tvpassport.com.config.js +++ b/sites/tvpassport.com/tvpassport.com.config.js @@ -1,182 +1,182 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const cheerio = require('cheerio') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') -const doFetch = require('@ntlab/sfetch') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'tvpassport.com', - days: 3, - url({ channel, date }) { - return `https://www.tvpassport.com/tv-listings/stations/${channel.site_id}/${date.format( - 'YYYY-MM-DD' - )}` - }, - request: { - timeout: 30000, - headers: { - Cookie: 'cisession=e49ff13191d6875887193cae9e324b44ef85768d;' - } - }, - parser: function ({ content }) { - let programs = [] - const items = parseItems(content) - for (let item of items) { - const $item = cheerio.load(item) - const start = parseStart($item) - const duration = parseDuration($item) - const stop = start.add(duration, 'm') - let title = parseTitle($item) - let subtitle = parseSubTitle($item) - if (title === 'Movie') { - title = subtitle - subtitle = null - } - - programs.push({ - title, - subtitle, - description: parseDescription($item), - image: parseImage($item), - category: parseCategory($item), - rating: parseRating($item), - actors: parseActors($item), - guest: parseGuest($item), - director: parseDirector($item), - year: parseYear($item), - start, - stop - }) - } - - return programs - }, - async channels() { - function wait(ms) { - return new Promise(resolve => { - setTimeout(resolve, ms) - }) - } - - const xml = await axios - .get('https://www.tvpassport.com/sitemap.stations.xml') - .then(r => r.data) - .catch(console.error) - - const $ = cheerio.load(xml) - - const elements = $('loc').toArray() - const queue = elements.map(el => $(el).text()) - const total = queue.length - - let i = 1 - const channels = [] - - await doFetch(queue, async (url, res) => { - if (!res) return - - const [, site_id] = url.match(/\/tv-listings\/stations\/(.*)$/) - - console.log(`[${i}/${total}]`, url) - - await wait(1000) - - const $channelPage = cheerio.load(res) - const title = $channelPage('meta[property="og:title"]').attr('content') - const name = title.replace('TV Schedule for ', '') - - channels.push({ - lang: 'en', - site_id, - name - }) - - i++ - }) - - return channels - } -} - -function parseDescription($item) { - return $item('*').data('description') -} - -function parseImage($item) { - const showpicture = $item('*').data('showpicture') - const url = new URL(showpicture, 'https://cdn.tvpassport.com/image/show/960x540/') - - return url.href -} - -function parseTitle($item) { - return $item('*').data('showname').toString() -} - -function parseSubTitle($item) { - return $item('*').data('episodetitle').toString() || null -} - -function parseYear($item) { - return $item('*').data('year').toString() || null -} - -function parseCategory($item) { - const showtype = $item('*').data('showtype') - - return showtype ? showtype.split(', ') : [] -} - -function parseActors($item) { - const cast = $item('*').data('cast') - - return cast ? cast.split(', ') : [] -} - -function parseDirector($item) { - const director = $item('*').data('director') - - return director ? director.split(', ') : [] -} - -function parseGuest($item) { - const guest = $item('*').data('guest') - - return guest ? guest.split(', ') : [] -} - -function parseRating($item) { - const rating = $item('*').data('rating') - - return rating - ? { - system: 'MPA', - value: rating.replace(/^TV/, 'TV-') - } - : null -} - -function parseStart($item) { - const time = $item('*').data('st') - - return dayjs.tz(time, 'YYYY-MM-DD HH:mm:ss', 'America/New_York') -} - -function parseDuration($item) { - const duration = $item('*').data('duration') - - return parseInt(duration) -} - -function parseItems(content) { - if (!content) return [] - const $ = cheerio.load(content) - - return $('.station-listings .list-group-item').toArray() -} +const axios = require('axios') +const dayjs = require('dayjs') +const cheerio = require('cheerio') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const doFetch = require('@ntlab/sfetch') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'tvpassport.com', + days: 3, + url({ channel, date }) { + return `https://www.tvpassport.com/tv-listings/stations/${channel.site_id}/${date.format( + 'YYYY-MM-DD' + )}` + }, + request: { + timeout: 30000, + headers: { + Cookie: 'cisession=e49ff13191d6875887193cae9e324b44ef85768d;' + } + }, + parser: function ({ content }) { + let programs = [] + const items = parseItems(content) + for (let item of items) { + const $item = cheerio.load(item) + const start = parseStart($item) + const duration = parseDuration($item) + const stop = start.add(duration, 'm') + let title = parseTitle($item) + let subtitle = parseSubTitle($item) + if (title === 'Movie') { + title = subtitle + subtitle = null + } + + programs.push({ + title, + subtitle, + description: parseDescription($item), + image: parseImage($item), + category: parseCategory($item), + rating: parseRating($item), + actors: parseActors($item), + guest: parseGuest($item), + director: parseDirector($item), + year: parseYear($item), + start, + stop + }) + } + + return programs + }, + async channels() { + function wait(ms) { + return new Promise(resolve => { + setTimeout(resolve, ms) + }) + } + + const xml = await axios + .get('https://www.tvpassport.com/sitemap.stations.xml') + .then(r => r.data) + .catch(console.error) + + const $ = cheerio.load(xml) + + const elements = $('loc').toArray() + const queue = elements.map(el => $(el).text()) + const total = queue.length + + let i = 1 + const channels = [] + + await doFetch(queue, async (url, res) => { + if (!res) return + + const [, site_id] = url.match(/\/tv-listings\/stations\/(.*)$/) + + console.log(`[${i}/${total}]`, url) + + await wait(1000) + + const $channelPage = cheerio.load(res) + const title = $channelPage('meta[property="og:title"]').attr('content') + const name = title.replace('TV Schedule for ', '') + + channels.push({ + lang: 'en', + site_id, + name + }) + + i++ + }) + + return channels + } +} + +function parseDescription($item) { + return $item('*').data('description') +} + +function parseImage($item) { + const showpicture = $item('*').data('showpicture') + const url = new URL(showpicture, 'https://cdn.tvpassport.com/image/show/960x540/') + + return url.href +} + +function parseTitle($item) { + return $item('*').data('showname').toString() +} + +function parseSubTitle($item) { + return $item('*').data('episodetitle').toString() || null +} + +function parseYear($item) { + return $item('*').data('year').toString() || null +} + +function parseCategory($item) { + const showtype = $item('*').data('showtype') + + return showtype ? showtype.split(', ') : [] +} + +function parseActors($item) { + const cast = $item('*').data('cast') + + return cast ? cast.split(', ') : [] +} + +function parseDirector($item) { + const director = $item('*').data('director') + + return director ? director.split(', ') : [] +} + +function parseGuest($item) { + const guest = $item('*').data('guest') + + return guest ? guest.split(', ') : [] +} + +function parseRating($item) { + const rating = $item('*').data('rating') + + return rating + ? { + system: 'MPA', + value: rating.replace(/^TV/, 'TV-') + } + : null +} + +function parseStart($item) { + const time = $item('*').data('st') + + return dayjs.tz(time, 'YYYY-MM-DD HH:mm:ss', 'America/New_York') +} + +function parseDuration($item) { + const duration = $item('*').data('duration') + + return parseInt(duration) +} + +function parseItems(content) { + if (!content) return [] + const $ = cheerio.load(content) + + return $('.station-listings .list-group-item').toArray() +} diff --git a/sites/tvpassport.com/tvpassport.com.test.js b/sites/tvpassport.com/tvpassport.com.test.js index 2bff87077..60f79e8f4 100644 --- a/sites/tvpassport.com/tvpassport.com.test.js +++ b/sites/tvpassport.com/tvpassport.com.test.js @@ -1,76 +1,76 @@ -const { parser, url, request } = require('./tvpassport.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-10-04', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'youtoo-america-network/5463', - xmltv_id: 'YTATV.us' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://www.tvpassport.com/tv-listings/stations/youtoo-america-network/5463/2022-10-04' - ) -}) - -it('can generate valid request headers', () => { - expect(request.headers).toMatchObject({ - Cookie: 'cisession=e49ff13191d6875887193cae9e324b44ef85768d;' - }) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - - let results = parser({ content }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2022-10-04T10:00:00.000Z', - stop: '2022-10-04T10:30:00.000Z', - title: 'Charlie Moore: No Offense', - subtitle: 'Under the Influencer', - category: ['Sports', 'Outdoors'], - image: 'https://cdn.tvpassport.com/image/show/960x540/69103.jpg', - rating: { - system: 'MPA', - value: 'TV-G' - }, - actors: ['John Reardon', 'Mayko Nguyen', 'Justin Kelly'], - director: ['Rob McElhenney'], - guest: ['Sean Penn'], - description: - 'Celebrity interviews while fishing in various locations throughout the United States.', - year: null - }) - - expect(results[1]).toMatchObject({ - start: '2022-10-04T10:30:00.000Z', - stop: '2022-10-04T11:00:00.000Z', - title: '1900', - year: null - }) - - expect(results[2]).toMatchObject({ - start: '2022-10-04T11:00:00.000Z', - stop: '2022-10-04T12:00:00.000Z', - title: 'The Mark of Zorro', - subtitle: null, - year: '1940' - }) -}) - -it('can handle empty guide', () => { - const result = parser({ content: '' }) - expect(result).toMatchObject([]) -}) +const { parser, url, request } = require('./tvpassport.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-10-04', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'youtoo-america-network/5463', + xmltv_id: 'YTATV.us' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://www.tvpassport.com/tv-listings/stations/youtoo-america-network/5463/2022-10-04' + ) +}) + +it('can generate valid request headers', () => { + expect(request.headers).toMatchObject({ + Cookie: 'cisession=e49ff13191d6875887193cae9e324b44ef85768d;' + }) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + + let results = parser({ content }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2022-10-04T10:00:00.000Z', + stop: '2022-10-04T10:30:00.000Z', + title: 'Charlie Moore: No Offense', + subtitle: 'Under the Influencer', + category: ['Sports', 'Outdoors'], + image: 'https://cdn.tvpassport.com/image/show/960x540/69103.jpg', + rating: { + system: 'MPA', + value: 'TV-G' + }, + actors: ['John Reardon', 'Mayko Nguyen', 'Justin Kelly'], + director: ['Rob McElhenney'], + guest: ['Sean Penn'], + description: + 'Celebrity interviews while fishing in various locations throughout the United States.', + year: null + }) + + expect(results[1]).toMatchObject({ + start: '2022-10-04T10:30:00.000Z', + stop: '2022-10-04T11:00:00.000Z', + title: '1900', + year: null + }) + + expect(results[2]).toMatchObject({ + start: '2022-10-04T11:00:00.000Z', + stop: '2022-10-04T12:00:00.000Z', + title: 'The Mark of Zorro', + subtitle: null, + year: '1940' + }) +}) + +it('can handle empty guide', () => { + const result = parser({ content: '' }) + expect(result).toMatchObject([]) +}) diff --git a/sites/tvplus.com.tr/tvplus.com.tr.test.js b/sites/tvplus.com.tr/tvplus.com.tr.test.js index d7586cda4..b2950abec 100644 --- a/sites/tvplus.com.tr/tvplus.com.tr.test.js +++ b/sites/tvplus.com.tr/tvplus.com.tr.test.js @@ -1,77 +1,77 @@ -const { parser, url } = require('./tvplus.com.tr.config.js') -const fs = require('fs') -const path = require('path') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -const date = dayjs.utc('2024-12-15', 'YYYY-MM-DD').startOf('d') -const channel = { - lang: 'tr', - site_id: 'nick-jr/4353', - xmltv_id: 'NickJr.tr' -} - -axios.get.mockImplementation(url => { - if (url === 'https://tvplus.com.tr/canli-tv/yayin-akisi') { - return Promise.resolve({ - data: fs.readFileSync(path.join(__dirname, '__data__', 'build.html')).toString() - }) - } -}) - -it('can generate valid url', async () => { - expect(await url({ channel })).toBe( - 'https://tvplus.com.tr/_next/data/kUzvz_bbQJNaShlFUkrR3/tr/canli-tv/yayin-akisi/nick-jr--4353.json?title=nick-jr--4353' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.join(__dirname, '__data__', 'content.json')) - const results = parser({ date, channel, content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(88) - expect(results[0]).toMatchObject({ - start: '2024-12-14T21:10:00.000Z', - stop: '2024-12-14T21:20:00.000Z', - title: 'Camgöz (2020)', - description: - "Max'in Camgöz adında yarı köpek balığı yarı köpek eşsiz bir evcil havyanı vardır. İlk başlarda Camgöz'ü saklamaya çalışsa da Sisli Pınarlar'da, en iyi arkadaşlar, meraklı komşular ve hatta Max'in ailesi bile yaramaz yeni arkadaşını fark edecektir.", - image: - 'https://gbzeottvsc01.tvplus.com.tr:33207/CPS/images/universal/film/program/202412/20241209/21/2126356250845eb88428_0_XL.jpg', - category: 'Çocuk', - season: 1, - episode: 116 - }) - expect(results[10]).toMatchObject({ - start: '2024-12-14T23:00:00.000Z', - stop: '2024-12-14T23:25:00.000Z', - title: 'Blaze ve Yol Canavarları', - description: - 'Blaze ve Yol Canavarları, dünyanın en büyük canavar kamyonu Blaze ve en iyi arkadaşı ve sürücüsü AJ adında bir çocuk hakkındaki interaktif bir anaokulu animasyon dizisidir.', - image: - 'https://gbzeottvsc01.tvplus.com.tr:33207/CPS/images/universal/film/program/202412/20241209/94/2126356271145eb88428_0_XL.jpg', - category: 'Çocuk', - season: 6, - episode: 617 - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: '' - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./tvplus.com.tr.config.js') +const fs = require('fs') +const path = require('path') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.utc('2024-12-15', 'YYYY-MM-DD').startOf('d') +const channel = { + lang: 'tr', + site_id: 'nick-jr/4353', + xmltv_id: 'NickJr.tr' +} + +axios.get.mockImplementation(url => { + if (url === 'https://tvplus.com.tr/canli-tv/yayin-akisi') { + return Promise.resolve({ + data: fs.readFileSync(path.join(__dirname, '__data__', 'build.html')).toString() + }) + } +}) + +it('can generate valid url', async () => { + expect(await url({ channel })).toBe( + 'https://tvplus.com.tr/_next/data/kUzvz_bbQJNaShlFUkrR3/tr/canli-tv/yayin-akisi/nick-jr--4353.json?title=nick-jr--4353' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.join(__dirname, '__data__', 'content.json')) + const results = parser({ date, channel, content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(88) + expect(results[0]).toMatchObject({ + start: '2024-12-14T21:10:00.000Z', + stop: '2024-12-14T21:20:00.000Z', + title: 'Camgöz (2020)', + description: + "Max'in Camgöz adında yarı köpek balığı yarı köpek eşsiz bir evcil havyanı vardır. İlk başlarda Camgöz'ü saklamaya çalışsa da Sisli Pınarlar'da, en iyi arkadaşlar, meraklı komşular ve hatta Max'in ailesi bile yaramaz yeni arkadaşını fark edecektir.", + image: + 'https://gbzeottvsc01.tvplus.com.tr:33207/CPS/images/universal/film/program/202412/20241209/21/2126356250845eb88428_0_XL.jpg', + category: 'Çocuk', + season: 1, + episode: 116 + }) + expect(results[10]).toMatchObject({ + start: '2024-12-14T23:00:00.000Z', + stop: '2024-12-14T23:25:00.000Z', + title: 'Blaze ve Yol Canavarları', + description: + 'Blaze ve Yol Canavarları, dünyanın en büyük canavar kamyonu Blaze ve en iyi arkadaşı ve sürücüsü AJ adında bir çocuk hakkındaki interaktif bir anaokulu animasyon dizisidir.', + image: + 'https://gbzeottvsc01.tvplus.com.tr:33207/CPS/images/universal/film/program/202412/20241209/94/2126356271145eb88428_0_XL.jpg', + category: 'Çocuk', + season: 6, + episode: 617 + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: '' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/tvprofil.com/tvprofil.com.config.js b/sites/tvprofil.com/tvprofil.com.config.js index 52ce60465..10afad7cd 100644 --- a/sites/tvprofil.com/tvprofil.com.config.js +++ b/sites/tvprofil.com/tvprofil.com.config.js @@ -1,165 +1,165 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') - -module.exports = { - site: 'tvprofil.com', - days: 2, - url: function ({ channel, date }) { - const parts = channel.site_id.split('#') - const query = buildQuery(parts[1], date) - - return `https://tvprofil.com/${parts[0]}/program/?${query}` - }, - request: { - headers: { - 'x-requested-with': 'XMLHttpRequest' - } - }, - parser: function ({ content }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - const $item = cheerio.load(item) - const title = parseTitle($item) - const category = parseCategory($item) - const start = parseStart($item) - const duration = parseDuration($item) - const stop = start.add(duration, 's') - const image = parseImage($item) - - programs.push({ title, category, start, stop, image }) - }) - - return programs - }, - async channels() { - const axios = require('axios') - - // prettier-ignore - const countries = { - al: { channelsPath: '/al', progsPath: 'al/programacioni', lang: 'sq' }, - at: { channelsPath: '/at', progsPath: 'at/tvprogramm', lang: 'de' }, - ba: { channelsPath: '/ba', progsPath: 'ba/tvprogram', lang: 'bs' }, - bg: { channelsPath: '/bg', progsPath: 'bg/tv-programa', lang: 'bg' }, - ch: { channelsPath: '/ch', progsPath: 'ch/tv-programm', lang: 'de' }, - de: { channelsPath: '/de', progsPath: 'de/tvprogramm', lang: 'de' }, - es: { channelsPath: '/es', progsPath: 'es/programacion-tv', lang: 'es' }, - fr: { channelsPath: '/fr', progsPath: 'fr/programme-tv', lang: 'fr' }, - hr: { channelsPath: '', progsPath: 'tvprogram', lang: 'hr' }, - hu: { channelsPath: '/hu', progsPath: 'hu/tvmusor', lang: 'hu' }, - ie: { channelsPath: '/ie', progsPath: 'ie/tvschedule', lang: 'en' }, - it: { channelsPath: '/it', progsPath: 'it/guida-tv', lang: 'it' }, - ks: { channelsPath: '/ks', progsPath: 'ks/programacioni', lang: 'sq' }, - me: { channelsPath: '/me', progsPath: 'me/tvprogram', lang: 'en' }, - mk: { channelsPath: '/mk', progsPath: 'mk/tv-raspored', lang: 'mk' }, - pl: { channelsPath: '/pl', progsPath: 'pl/program', lang: 'pl' }, - pt: { channelsPath: '/pt', progsPath: 'pt/programacao', lang: 'pt' }, - ro: { channelsPath: '/ro', progsPath: 'ro/program-tv', lang: 'ro' }, - rs: { channelsPath: '/rs', progsPath: 'rs/tvprogram', lang: 'sr' }, - si: { channelsPath: '/si', progsPath: 'si/tvspored', lang: 'sl' }, - tr: { channelsPath: '/tr', progsPath: 'tr/tv-rehberi', lang: 'tr' }, - uk: { channelsPath: '/gb', progsPath: 'gb/tvschedule', lang: 'en' }, - } - - let channels = [] - for (let country in countries) { - const config = countries[country] - const lang = config.lang - - const url = `https://tvprofil.com${config.channelsPath}/channels/getChannels/` - - console.log(url) - - const cb = await axios - .get(url, { - params: { - callback: 'cb' - } - }) - .then(r => r.data) - .catch(err => { - console.error(err.message) - }) - - if (!cb) continue - - const [, json] = cb.match(/^cb\((.*)\)$/i) - const data = JSON.parse(json) - - data.data.forEach(group => { - group.channels.forEach(item => { - channels.push({ - lang, - site_id: `${config.progsPath}#${item.urlID}`, - name: item.title - }) - }) - }) - } - - return channels - } -} - -function parseImage($item) { - return $item(':root').data('image') -} - -function parseDuration($item) { - return $item(':root').data('len') -} - -function parseStart($item) { - const timestamp = $item(':root').data('ts') - - return dayjs.unix(timestamp) -} - -function parseCategory($item) { - return $item('.col:nth-child(2) > small').text() || null -} - -function parseTitle($item) { - let title = $item('.col:nth-child(2) > a').text() - title += $item('.col:nth-child(2)').clone().children().remove().end().text() - - return title.replace('®', '').trim().replace(/,$/, '') -} - -function parseItems(content) { - let data = (content.match(/cb\((.*)\)/) || [null, null])[1] - if (!data) return [] - let json = JSON.parse(data) - if (!json || !json.data || !json.data.program) return [] - - const $ = cheerio.load(json.data.program) - - return $('.row').toArray() -} - -function buildQuery(site_id, date) { - const query = { - datum: date.format('YYYY-MM-DD'), - kanal: site_id, - callback: 'cb' - } - - let c = 4 - const a = query.datum + query.kanal + c - const ua = query.kanal + query.datum - - let i = a.length, - b = 2 - - for (let j = 0; j < ua.length; j++) c += ua.charCodeAt(j) - while (i--) { - b += (a.charCodeAt(i) + c * 2) * i - } - - b = b.toString() - const key = 'b' + b.charCodeAt(b.length - 1) - - query[key] = b - - return new URLSearchParams(query).toString() -} +const cheerio = require('cheerio') +const dayjs = require('dayjs') + +module.exports = { + site: 'tvprofil.com', + days: 2, + url: function ({ channel, date }) { + const parts = channel.site_id.split('#') + const query = buildQuery(parts[1], date) + + return `https://tvprofil.com/${parts[0]}/program/?${query}` + }, + request: { + headers: { + 'x-requested-with': 'XMLHttpRequest' + } + }, + parser: function ({ content }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + const $item = cheerio.load(item) + const title = parseTitle($item) + const category = parseCategory($item) + const start = parseStart($item) + const duration = parseDuration($item) + const stop = start.add(duration, 's') + const image = parseImage($item) + + programs.push({ title, category, start, stop, image }) + }) + + return programs + }, + async channels() { + const axios = require('axios') + + // prettier-ignore + const countries = { + al: { channelsPath: '/al', progsPath: 'al/programacioni', lang: 'sq' }, + at: { channelsPath: '/at', progsPath: 'at/tvprogramm', lang: 'de' }, + ba: { channelsPath: '/ba', progsPath: 'ba/tvprogram', lang: 'bs' }, + bg: { channelsPath: '/bg', progsPath: 'bg/tv-programa', lang: 'bg' }, + ch: { channelsPath: '/ch', progsPath: 'ch/tv-programm', lang: 'de' }, + de: { channelsPath: '/de', progsPath: 'de/tvprogramm', lang: 'de' }, + es: { channelsPath: '/es', progsPath: 'es/programacion-tv', lang: 'es' }, + fr: { channelsPath: '/fr', progsPath: 'fr/programme-tv', lang: 'fr' }, + hr: { channelsPath: '', progsPath: 'tvprogram', lang: 'hr' }, + hu: { channelsPath: '/hu', progsPath: 'hu/tvmusor', lang: 'hu' }, + ie: { channelsPath: '/ie', progsPath: 'ie/tvschedule', lang: 'en' }, + it: { channelsPath: '/it', progsPath: 'it/guida-tv', lang: 'it' }, + ks: { channelsPath: '/ks', progsPath: 'ks/programacioni', lang: 'sq' }, + me: { channelsPath: '/me', progsPath: 'me/tvprogram', lang: 'en' }, + mk: { channelsPath: '/mk', progsPath: 'mk/tv-raspored', lang: 'mk' }, + pl: { channelsPath: '/pl', progsPath: 'pl/program', lang: 'pl' }, + pt: { channelsPath: '/pt', progsPath: 'pt/programacao', lang: 'pt' }, + ro: { channelsPath: '/ro', progsPath: 'ro/program-tv', lang: 'ro' }, + rs: { channelsPath: '/rs', progsPath: 'rs/tvprogram', lang: 'sr' }, + si: { channelsPath: '/si', progsPath: 'si/tvspored', lang: 'sl' }, + tr: { channelsPath: '/tr', progsPath: 'tr/tv-rehberi', lang: 'tr' }, + uk: { channelsPath: '/gb', progsPath: 'gb/tvschedule', lang: 'en' }, + } + + let channels = [] + for (let country in countries) { + const config = countries[country] + const lang = config.lang + + const url = `https://tvprofil.com${config.channelsPath}/channels/getChannels/` + + console.log(url) + + const cb = await axios + .get(url, { + params: { + callback: 'cb' + } + }) + .then(r => r.data) + .catch(err => { + console.error(err.message) + }) + + if (!cb) continue + + const [, json] = cb.match(/^cb\((.*)\)$/i) + const data = JSON.parse(json) + + data.data.forEach(group => { + group.channels.forEach(item => { + channels.push({ + lang, + site_id: `${config.progsPath}#${item.urlID}`, + name: item.title + }) + }) + }) + } + + return channels + } +} + +function parseImage($item) { + return $item(':root').data('image') +} + +function parseDuration($item) { + return $item(':root').data('len') +} + +function parseStart($item) { + const timestamp = $item(':root').data('ts') + + return dayjs.unix(timestamp) +} + +function parseCategory($item) { + return $item('.col:nth-child(2) > small').text() || null +} + +function parseTitle($item) { + let title = $item('.col:nth-child(2) > a').text() + title += $item('.col:nth-child(2)').clone().children().remove().end().text() + + return title.replace('®', '').trim().replace(/,$/, '') +} + +function parseItems(content) { + let data = (content.match(/cb\((.*)\)/) || [null, null])[1] + if (!data) return [] + let json = JSON.parse(data) + if (!json || !json.data || !json.data.program) return [] + + const $ = cheerio.load(json.data.program) + + return $('.row').toArray() +} + +function buildQuery(site_id, date) { + const query = { + datum: date.format('YYYY-MM-DD'), + kanal: site_id, + callback: 'cb' + } + + let c = 4 + const a = query.datum + query.kanal + c + const ua = query.kanal + query.datum + + let i = a.length, + b = 2 + + for (let j = 0; j < ua.length; j++) c += ua.charCodeAt(j) + while (i--) { + b += (a.charCodeAt(i) + c * 2) * i + } + + b = b.toString() + const key = 'b' + b.charCodeAt(b.length - 1) + + query[key] = b + + return new URLSearchParams(query).toString() +} diff --git a/sites/tvprofil.com/tvprofil.com.test.js b/sites/tvprofil.com/tvprofil.com.test.js index c73753d6c..380c42e89 100644 --- a/sites/tvprofil.com/tvprofil.com.test.js +++ b/sites/tvprofil.com/tvprofil.com.test.js @@ -1,47 +1,47 @@ -const { parser, url, request } = require('./tvprofil.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-01-19', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'bg/tv-programa#24kitchen-bg', - xmltv_id: '24KitchenBulgaria.bg' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://tvprofil.com/bg/tv-programa/program/?datum=2025-01-19&kanal=24kitchen-bg&callback=cb&b52=824084' - ) -}) - -it('can generate valid request headers', () => { - expect(request.headers).toMatchObject({ - 'x-requested-with': 'XMLHttpRequest' - }) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.txt'), 'utf8') - const results = parser({ content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - title: 'Мексиканска кухня с Пати 10, еп. 9', - start: '2023-01-12T04:00:00.000Z', - stop: '2023-01-12T04:30:00.000Z' - }) -}) - -it('can handle empty guide', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.txt'), 'utf8') - - expect(parser({ content })).toMatchObject([]) -}) +const { parser, url, request } = require('./tvprofil.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-01-19', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'bg/tv-programa#24kitchen-bg', + xmltv_id: '24KitchenBulgaria.bg' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://tvprofil.com/bg/tv-programa/program/?datum=2025-01-19&kanal=24kitchen-bg&callback=cb&b52=824084' + ) +}) + +it('can generate valid request headers', () => { + expect(request.headers).toMatchObject({ + 'x-requested-with': 'XMLHttpRequest' + }) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.txt'), 'utf8') + const results = parser({ content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + title: 'Мексиканска кухня с Пати 10, еп. 9', + start: '2023-01-12T04:00:00.000Z', + stop: '2023-01-12T04:30:00.000Z' + }) +}) + +it('can handle empty guide', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.txt'), 'utf8') + + expect(parser({ content })).toMatchObject([]) +}) diff --git a/sites/tvtv.us/tvtv.us.config.js b/sites/tvtv.us/tvtv.us.config.js index eef953fcc..ccb667bed 100644 --- a/sites/tvtv.us/tvtv.us.config.js +++ b/sites/tvtv.us/tvtv.us.config.js @@ -1,151 +1,151 @@ -const dayjs = require('dayjs') - -let cachedPrograms = {} - -module.exports = { - site: 'tvtv.us', - days: 2, - url({ date, channel }) { - return `https://www.tvtv.us/api/v1/lineup/USA-NY71652-X/grid/${date.toJSON()}/${date - .add(1, 'day') - .toJSON()}/${channel.site_id}` - }, - request: { - headers: { - Accept: '*/*', - Connection: 'keep-alive', - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36', - 'sec-ch-ua': '"Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114"', - 'sec-ch-ua-mobile': '?0', - 'sec-ch-ua-platform': '"Windows"' - } - }, - async parser(ctx) { - let programs = [] - let queue = [] - - const items = parseItems(ctx.content) - for (const item of items) { - const start = dayjs(item.startTime) - const stop = start.add(item.duration, 'minute') - - programs.push({ - id: item.programId, - title: item.title, - subtitle: item.subtitle || null, - start, - stop - }) - - // NOTE: This part of the code is commented out because loading additional data leads either to error 429 Too Many Requests or to even greater delays between requests. - // if (item.programId && !cachedPrograms[item.programId]) { - // queue.push({ - // programId: item.programId, - // url: `https://tvtv.us/api/v1/programs/${item.programId}`, - // httpAgent: ctx.request.agent, - // httpsAgent: ctx.request.agent, - // headers: module.exports.request.headers - // }) - // } - } - - const axios = require('axios') - for (const req of queue) { - await wait(5000) - - const data = await axios(req) - .then(r => r.data) - .catch(console.error) - - if (!data || !data.title) continue - - cachedPrograms[req.programId] = data - } - - programs.forEach(program => { - const data = cachedPrograms[program.id] - - if (!data) return - - program.description = data.description || null - program.image = data.image ? `https://tvtv.us${data.image}` : null - program.date = data.releaseYear ? data.releaseYear.toString() : null - program.directors = data.directors - program.categories = data.genres - program.actors = parseActors(data) - program.writers = parseWriters(data) - program.producers = parseProducers(data) - program.ratings = parseRatings(data) - program.season = parseSeason(data) - program.episode = parseEpisode(data) - }) - - return programs - } -} - -function parseEpisode(data) { - if (!data?.seriesEpisode?.seasonEpisode) return null - - const [, episode] = data.seriesEpisode.seasonEpisode.match(/Episode (\d+)/) || [null, null] - - return episode ? parseInt(episode) : null -} - -function parseSeason(data) { - if (!data?.seriesEpisode?.seasonEpisode) return null - - const [, season] = data.seriesEpisode.seasonEpisode.match(/Season (\d+);/) || [null, null] - - return season ? parseInt(season) : null -} - -function parseRatings(data) { - return Array.isArray(data.ratings) - ? data.ratings.map(rating => ({ - value: rating.code, - system: rating.body - })) - : [] -} - -function parseWriters(data) { - return data.crew.filter(member => member.role.includes('Writer')).map(member => member.name) -} - -function parseProducers(data) { - return data.crew.filter(member => member.role.includes('Producer')).map(member => member.name) -} - -function parseActors(data) { - return data.cast.map(actor => { - const guest = actor.role.includes('Guest Star') ? 'yes' : undefined - const role = actor.role.replace(' - Guest Star', '') - - return { - value: actor.name, - role, - guest - } - }) -} - -function parseItems(content) { - try { - const json = JSON.parse(content) - if (!json.length) return [] - - return json[0] - } catch { - return [] - } -} - -function wait(ms) { - if (process.env.NODE_ENV === 'test') return - - return new Promise(resolve => { - setTimeout(resolve, ms) - }) -} +const dayjs = require('dayjs') + +let cachedPrograms = {} + +module.exports = { + site: 'tvtv.us', + days: 2, + url({ date, channel }) { + return `https://www.tvtv.us/api/v1/lineup/USA-NY71652-X/grid/${date.toJSON()}/${date + .add(1, 'day') + .toJSON()}/${channel.site_id}` + }, + request: { + headers: { + Accept: '*/*', + Connection: 'keep-alive', + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36', + 'sec-ch-ua': '"Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"Windows"' + } + }, + async parser(ctx) { + let programs = [] + let queue = [] + + const items = parseItems(ctx.content) + for (const item of items) { + const start = dayjs(item.startTime) + const stop = start.add(item.duration, 'minute') + + programs.push({ + id: item.programId, + title: item.title, + subtitle: item.subtitle || null, + start, + stop + }) + + // NOTE: This part of the code is commented out because loading additional data leads either to error 429 Too Many Requests or to even greater delays between requests. + // if (item.programId && !cachedPrograms[item.programId]) { + // queue.push({ + // programId: item.programId, + // url: `https://tvtv.us/api/v1/programs/${item.programId}`, + // httpAgent: ctx.request.agent, + // httpsAgent: ctx.request.agent, + // headers: module.exports.request.headers + // }) + // } + } + + const axios = require('axios') + for (const req of queue) { + await wait(5000) + + const data = await axios(req) + .then(r => r.data) + .catch(console.error) + + if (!data || !data.title) continue + + cachedPrograms[req.programId] = data + } + + programs.forEach(program => { + const data = cachedPrograms[program.id] + + if (!data) return + + program.description = data.description || null + program.image = data.image ? `https://tvtv.us${data.image}` : null + program.date = data.releaseYear ? data.releaseYear.toString() : null + program.directors = data.directors + program.categories = data.genres + program.actors = parseActors(data) + program.writers = parseWriters(data) + program.producers = parseProducers(data) + program.ratings = parseRatings(data) + program.season = parseSeason(data) + program.episode = parseEpisode(data) + }) + + return programs + } +} + +function parseEpisode(data) { + if (!data?.seriesEpisode?.seasonEpisode) return null + + const [, episode] = data.seriesEpisode.seasonEpisode.match(/Episode (\d+)/) || [null, null] + + return episode ? parseInt(episode) : null +} + +function parseSeason(data) { + if (!data?.seriesEpisode?.seasonEpisode) return null + + const [, season] = data.seriesEpisode.seasonEpisode.match(/Season (\d+);/) || [null, null] + + return season ? parseInt(season) : null +} + +function parseRatings(data) { + return Array.isArray(data.ratings) + ? data.ratings.map(rating => ({ + value: rating.code, + system: rating.body + })) + : [] +} + +function parseWriters(data) { + return data.crew.filter(member => member.role.includes('Writer')).map(member => member.name) +} + +function parseProducers(data) { + return data.crew.filter(member => member.role.includes('Producer')).map(member => member.name) +} + +function parseActors(data) { + return data.cast.map(actor => { + const guest = actor.role.includes('Guest Star') ? 'yes' : undefined + const role = actor.role.replace(' - Guest Star', '') + + return { + value: actor.name, + role, + guest + } + }) +} + +function parseItems(content) { + try { + const json = JSON.parse(content) + if (!json.length) return [] + + return json[0] + } catch { + return [] + } +} + +function wait(ms) { + if (process.env.NODE_ENV === 'test') return + + return new Promise(resolve => { + setTimeout(resolve, ms) + }) +} diff --git a/sites/tvtv.us/tvtv.us.test.js b/sites/tvtv.us/tvtv.us.test.js index 2a5e6c9a9..85b1ddda3 100644 --- a/sites/tvtv.us/tvtv.us.test.js +++ b/sites/tvtv.us/tvtv.us.test.js @@ -1,172 +1,172 @@ -const { parser, url } = require('./tvtv.us.config.js') -const fs = require('fs') -const path = require('path') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -axios.mockImplementation(req => { - if (req.url === 'https://tvtv.us/api/v1/programs/EP009311820269') { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program_1.json'))) - }) - } else { - return Promise.resolve({ data: '' }) - } -}) - -const date = dayjs.utc('2025-01-30', 'YYYY-MM-DD').startOf('d') -const channel = { site_id: '20373' } - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://www.tvtv.us/api/v1/lineup/USA-NY71652-X/grid/2025-01-30T00:00:00.000Z/2025-01-31T00:00:00.000Z/20373' - ) -}) - -it('can parse response', async () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - - let results = await parser({ content, request: { agent: null } }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(33) - expect(results[0]).toMatchObject({ - start: '2025-01-30T00:00:00.000Z', - stop: '2025-01-30T00:30:00.000Z', - title: 'NY Sports Nation Nightly', - subtitle: null - }) - expect(results[1]).toMatchObject({ - start: '2025-01-30T00:30:00.000Z', - stop: '2025-01-30T01:00:00.000Z', - title: 'The Big Bang Theory', - subtitle: 'The Bow Tie Asymmetry' - // description: - // "When Amy's parents and Sheldon's family arrive, everybody is focused on making sure the wedding arrangements go according to plan -- everyone except the bride and groom.", - // image: 'https://tvtv.us/gn/pi/assets/p185554_b_v11_az.jpg?w=240&h=360', - // date: '2018', - // season: 11, - // episode: 24, - // actors: [ - // { - // value: 'Johnny Galecki', - // role: 'Leonard Hofstadter' - // }, - // { - // value: 'Jim Parsons', - // role: 'Sheldon Cooper' - // }, - // { - // value: 'Kaley Cuoco', - // role: 'Penny' - // }, - // { - // value: 'Simon Helberg', - // role: 'Howard Wolowitz' - // }, - // { - // value: 'Kunal Nayyar', - // role: 'Raj Koothrappali' - // }, - // { - // value: 'Mayim Bialik', - // role: 'Amy Farrah Fowler' - // }, - // { - // value: 'Melissa Rauch', - // role: 'Bernadette Rostenkowski' - // }, - // { - // value: 'Kevin Sussman', - // role: 'Stuart', - // guest: 'yes' - // }, - // { - // value: 'Laurie Metcalf', - // role: 'Mary', - // guest: 'yes' - // }, - // { - // value: 'John Ross Bowie', - // role: 'Kripke', - // guest: 'yes' - // }, - // { - // value: 'Wil Wheaton', - // role: 'Himself', - // guest: 'yes' - // }, - // { - // value: 'Brian Posehn', - // role: 'Bert', - // guest: 'yes' - // }, - // { - // value: "Jerry O'Connell", - // role: 'George', - // guest: 'yes' - // }, - // { - // value: 'Courtney Henggeler', - // role: 'Missy', - // guest: 'yes' - // }, - // { - // value: 'Lauren Lapkus', - // role: 'Denise', - // guest: 'yes' - // }, - // { - // value: 'Teller', - // role: 'Mr. Fowler', - // guest: 'yes' - // }, - // { - // value: 'Kathy Bates', - // role: 'Mrs. Fowler', - // guest: 'yes' - // }, - // { - // value: 'Mark Hamill', - // role: 'Himself', - // guest: 'yes' - // } - // ], - // directors: ['Mark Cendrowski'], - // producers: ['Chuck Lorre', 'Bill Prady', 'Steven Molaro'], - // writers: [ - // 'Chuck Lorre', - // 'Steven Molaro', - // 'Maria Ferrari', - // 'Steve Holland', - // 'Eric Kaplan', - // 'Tara Hernandez' - // ], - // categories: ['Sitcom'], - // ratings: [ - // { - // value: 'TVPG', - // system: 'USA Parental Rating' - // } - // ] - }) -}) - -it('can handle empty guide', async () => { - const results = await parser({ - content: '[]', - request: { agent: null } - }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./tvtv.us.config.js') +const fs = require('fs') +const path = require('path') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +axios.mockImplementation(req => { + if (req.url === 'https://tvtv.us/api/v1/programs/EP009311820269') { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/program_1.json'))) + }) + } else { + return Promise.resolve({ data: '' }) + } +}) + +const date = dayjs.utc('2025-01-30', 'YYYY-MM-DD').startOf('d') +const channel = { site_id: '20373' } + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://www.tvtv.us/api/v1/lineup/USA-NY71652-X/grid/2025-01-30T00:00:00.000Z/2025-01-31T00:00:00.000Z/20373' + ) +}) + +it('can parse response', async () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + + let results = await parser({ content, request: { agent: null } }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(33) + expect(results[0]).toMatchObject({ + start: '2025-01-30T00:00:00.000Z', + stop: '2025-01-30T00:30:00.000Z', + title: 'NY Sports Nation Nightly', + subtitle: null + }) + expect(results[1]).toMatchObject({ + start: '2025-01-30T00:30:00.000Z', + stop: '2025-01-30T01:00:00.000Z', + title: 'The Big Bang Theory', + subtitle: 'The Bow Tie Asymmetry' + // description: + // "When Amy's parents and Sheldon's family arrive, everybody is focused on making sure the wedding arrangements go according to plan -- everyone except the bride and groom.", + // image: 'https://tvtv.us/gn/pi/assets/p185554_b_v11_az.jpg?w=240&h=360', + // date: '2018', + // season: 11, + // episode: 24, + // actors: [ + // { + // value: 'Johnny Galecki', + // role: 'Leonard Hofstadter' + // }, + // { + // value: 'Jim Parsons', + // role: 'Sheldon Cooper' + // }, + // { + // value: 'Kaley Cuoco', + // role: 'Penny' + // }, + // { + // value: 'Simon Helberg', + // role: 'Howard Wolowitz' + // }, + // { + // value: 'Kunal Nayyar', + // role: 'Raj Koothrappali' + // }, + // { + // value: 'Mayim Bialik', + // role: 'Amy Farrah Fowler' + // }, + // { + // value: 'Melissa Rauch', + // role: 'Bernadette Rostenkowski' + // }, + // { + // value: 'Kevin Sussman', + // role: 'Stuart', + // guest: 'yes' + // }, + // { + // value: 'Laurie Metcalf', + // role: 'Mary', + // guest: 'yes' + // }, + // { + // value: 'John Ross Bowie', + // role: 'Kripke', + // guest: 'yes' + // }, + // { + // value: 'Wil Wheaton', + // role: 'Himself', + // guest: 'yes' + // }, + // { + // value: 'Brian Posehn', + // role: 'Bert', + // guest: 'yes' + // }, + // { + // value: "Jerry O'Connell", + // role: 'George', + // guest: 'yes' + // }, + // { + // value: 'Courtney Henggeler', + // role: 'Missy', + // guest: 'yes' + // }, + // { + // value: 'Lauren Lapkus', + // role: 'Denise', + // guest: 'yes' + // }, + // { + // value: 'Teller', + // role: 'Mr. Fowler', + // guest: 'yes' + // }, + // { + // value: 'Kathy Bates', + // role: 'Mrs. Fowler', + // guest: 'yes' + // }, + // { + // value: 'Mark Hamill', + // role: 'Himself', + // guest: 'yes' + // } + // ], + // directors: ['Mark Cendrowski'], + // producers: ['Chuck Lorre', 'Bill Prady', 'Steven Molaro'], + // writers: [ + // 'Chuck Lorre', + // 'Steven Molaro', + // 'Maria Ferrari', + // 'Steve Holland', + // 'Eric Kaplan', + // 'Tara Hernandez' + // ], + // categories: ['Sitcom'], + // ratings: [ + // { + // value: 'TVPG', + // system: 'USA Parental Rating' + // } + // ] + }) +}) + +it('can handle empty guide', async () => { + const results = await parser({ + content: '[]', + request: { agent: null } + }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/v3.myafn.dodmedia.osd.mil/v3.myafn.dodmedia.osd.mil.config.js b/sites/v3.myafn.dodmedia.osd.mil/v3.myafn.dodmedia.osd.mil.config.js index 3622e9546..137b8ac63 100644 --- a/sites/v3.myafn.dodmedia.osd.mil/v3.myafn.dodmedia.osd.mil.config.js +++ b/sites/v3.myafn.dodmedia.osd.mil/v3.myafn.dodmedia.osd.mil.config.js @@ -1,76 +1,76 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -module.exports = { - site: 'v3.myafn.dodmedia.osd.mil', - days: 2, - request: { - cache: { - ttl: 60 * 60 * 1000 // 1 hour - } - }, - url: function ({ date }) { - return `https://v3.myafn.dodmedia.osd.mil/api/json/32/${date.format( - 'YYYY-MM-DD' - )}@0000/${date.format('YYYY-MM-DD')}@2359/schedule.json` - }, - parser: function ({ content, date, channel }) { - let programs = [] - const items = parseItems(content, channel) - items.forEach(item => { - const start = parseStart(item, date) - const stop = start.add(item.o, 'm') - programs.push({ - title: item.h, - sub_title: item.i, - description: item.l, - rating: parseRating(item), - category: parseCategory(item), - start, - stop - }) - }) - - return programs - }, - async channels() { - const data = await axios - .get('https://v3.myafn.dodmedia.osd.mil/api/json/32/channels.json') - .then(r => r.data) - .catch(console.log) - - return data.map(item => ({ - site_id: item.Channel, - name: item.Title - })) - } -} - -function parseStart(item) { - return dayjs.utc(item.e, 'YYYY,M,D,H,m,s,0').add(1, 'month') -} - -function parseCategory(item) { - return item.m ? item.m.split(',') : [] -} - -function parseRating(item) { - return item.j - ? { - system: 'MPA', - value: item.j - } - : null -} - -function parseItems(content, channel) { - const items = JSON.parse(content) - if (!Array.isArray(items)) return [] - - return items.filter(i => i.b == channel.site_id) -} +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +module.exports = { + site: 'v3.myafn.dodmedia.osd.mil', + days: 2, + request: { + cache: { + ttl: 60 * 60 * 1000 // 1 hour + } + }, + url: function ({ date }) { + return `https://v3.myafn.dodmedia.osd.mil/api/json/32/${date.format( + 'YYYY-MM-DD' + )}@0000/${date.format('YYYY-MM-DD')}@2359/schedule.json` + }, + parser: function ({ content, date, channel }) { + let programs = [] + const items = parseItems(content, channel) + items.forEach(item => { + const start = parseStart(item, date) + const stop = start.add(item.o, 'm') + programs.push({ + title: item.h, + sub_title: item.i, + description: item.l, + rating: parseRating(item), + category: parseCategory(item), + start, + stop + }) + }) + + return programs + }, + async channels() { + const data = await axios + .get('https://v3.myafn.dodmedia.osd.mil/api/json/32/channels.json') + .then(r => r.data) + .catch(console.log) + + return data.map(item => ({ + site_id: item.Channel, + name: item.Title + })) + } +} + +function parseStart(item) { + return dayjs.utc(item.e, 'YYYY,M,D,H,m,s,0').add(1, 'month') +} + +function parseCategory(item) { + return item.m ? item.m.split(',') : [] +} + +function parseRating(item) { + return item.j + ? { + system: 'MPA', + value: item.j + } + : null +} + +function parseItems(content, channel) { + const items = JSON.parse(content) + if (!Array.isArray(items)) return [] + + return items.filter(i => i.b == channel.site_id) +} diff --git a/sites/v3.myafn.dodmedia.osd.mil/v3.myafn.dodmedia.osd.mil.test.js b/sites/v3.myafn.dodmedia.osd.mil/v3.myafn.dodmedia.osd.mil.test.js index 21b668fd1..988e100e9 100644 --- a/sites/v3.myafn.dodmedia.osd.mil/v3.myafn.dodmedia.osd.mil.test.js +++ b/sites/v3.myafn.dodmedia.osd.mil/v3.myafn.dodmedia.osd.mil.test.js @@ -1,55 +1,55 @@ -const { parser, url } = require('./v3.myafn.dodmedia.osd.mil.config.js') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-10-03', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '2', - xmltv_id: 'AFNPrimeAtlantic.us' -} - -it('can generate valid url', () => { - expect(url({ date })).toBe( - 'https://v3.myafn.dodmedia.osd.mil/api/json/32/2022-10-03@0000/2022-10-03@2359/schedule.json' - ) -}) - -it('can parse response', () => { - const content = - '[{"a":566,"b":2,"c":"2022,9,3,3,0,0,0","d":"2022,9,3,4,0,0,0","e":"2022,9,3,3,0,0,0","f":"2022,9,3,4,0,0,0","g":60,"h":"This Week with George Stephanopoulos (ABC)","i":"Episode Title","j":"TV-14","k":false,"l":"Former Clinton White House staffer and current co-anchor of ABC\'s weekday morning news show \\"\\"Good Morning America,\\"\\" George Stephanopoulos and co-anchors Martha Raddatz and Jonathan Karl offer a look at current events with a focus on the politics of the day. Each week\'s show includes interviews with top newsmakers (including some of the nation\'s top political leaders) as well as a roundtable discussion, usually featuring journalists from ABC and other news organizations, of the week\'s happenings. Since 2008, the program has broadcast from a studio at the Newseum in Washington, D.C.","m":"News,Politics,Public affairs,Talk","n":694284445,"o":60,"p":20,"q":true,"r":694285705,"s":null}]' - const result = parser({ content, date, channel }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2022-10-03T03:00:00.000Z', - stop: '2022-10-03T04:00:00.000Z', - title: 'This Week with George Stephanopoulos (ABC)', - sub_title: 'Episode Title', - description: - 'Former Clinton White House staffer and current co-anchor of ABC\'s weekday morning news show ""Good Morning America,"" George Stephanopoulos and co-anchors Martha Raddatz and Jonathan Karl offer a look at current events with a focus on the politics of the day. Each week\'s show includes interviews with top newsmakers (including some of the nation\'s top political leaders) as well as a roundtable discussion, usually featuring journalists from ABC and other news organizations, of the week\'s happenings. Since 2008, the program has broadcast from a studio at the Newseum in Washington, D.C.', - category: ['News', 'Politics', 'Public affairs', 'Talk'], - rating: { - system: 'MPA', - value: 'TV-14' - } - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: `{ - "Message": "An error has occurred." - }`, - date, - channel - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./v3.myafn.dodmedia.osd.mil.config.js') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-10-03', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '2', + xmltv_id: 'AFNPrimeAtlantic.us' +} + +it('can generate valid url', () => { + expect(url({ date })).toBe( + 'https://v3.myafn.dodmedia.osd.mil/api/json/32/2022-10-03@0000/2022-10-03@2359/schedule.json' + ) +}) + +it('can parse response', () => { + const content = + '[{"a":566,"b":2,"c":"2022,9,3,3,0,0,0","d":"2022,9,3,4,0,0,0","e":"2022,9,3,3,0,0,0","f":"2022,9,3,4,0,0,0","g":60,"h":"This Week with George Stephanopoulos (ABC)","i":"Episode Title","j":"TV-14","k":false,"l":"Former Clinton White House staffer and current co-anchor of ABC\'s weekday morning news show \\"\\"Good Morning America,\\"\\" George Stephanopoulos and co-anchors Martha Raddatz and Jonathan Karl offer a look at current events with a focus on the politics of the day. Each week\'s show includes interviews with top newsmakers (including some of the nation\'s top political leaders) as well as a roundtable discussion, usually featuring journalists from ABC and other news organizations, of the week\'s happenings. Since 2008, the program has broadcast from a studio at the Newseum in Washington, D.C.","m":"News,Politics,Public affairs,Talk","n":694284445,"o":60,"p":20,"q":true,"r":694285705,"s":null}]' + const result = parser({ content, date, channel }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2022-10-03T03:00:00.000Z', + stop: '2022-10-03T04:00:00.000Z', + title: 'This Week with George Stephanopoulos (ABC)', + sub_title: 'Episode Title', + description: + 'Former Clinton White House staffer and current co-anchor of ABC\'s weekday morning news show ""Good Morning America,"" George Stephanopoulos and co-anchors Martha Raddatz and Jonathan Karl offer a look at current events with a focus on the politics of the day. Each week\'s show includes interviews with top newsmakers (including some of the nation\'s top political leaders) as well as a roundtable discussion, usually featuring journalists from ABC and other news organizations, of the week\'s happenings. Since 2008, the program has broadcast from a studio at the Newseum in Washington, D.C.', + category: ['News', 'Politics', 'Public affairs', 'Talk'], + rating: { + system: 'MPA', + value: 'TV-14' + } + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: `{ + "Message": "An error has occurred." + }`, + date, + channel + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/virginmediatelevision.ie/virginmediatelevision.ie.config.js b/sites/virginmediatelevision.ie/virginmediatelevision.ie.config.js index ad225963d..95c544a67 100644 --- a/sites/virginmediatelevision.ie/virginmediatelevision.ie.config.js +++ b/sites/virginmediatelevision.ie/virginmediatelevision.ie.config.js @@ -1,82 +1,82 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'virginmediatelevision.ie', - days: 2, - url({ date }) { - return `https://www.virginmediatelevision.ie/includes/ajax/tv_guide.php?date=${date.format( - 'YYYY-MM-DD' - )}` - }, - request: { - cache: { - ttl: 60 * 60 * 1000 // 1h - } - }, - parser({ content, channel, date }) { - const programs = [] - const items = parseItems(content, channel) - items.forEach(item => { - const $item = cheerio.load(item) - let start = parseStart($item, date) - let duration = parseDuration($item) - let stop = start.add(duration, 'm') - programs.push({ - title: parseTitle($item), - description: parseDescription($item), - sub_title: parseSubTitle($item), - image: parseImage($item), - start, - stop - }) - }) - - return programs - } -} - -function parseTitle($item) { - return $item('.info > h2').text().trim() -} - -function parseDescription($item) { - return $item('.info').data('description') -} - -function parseSubTitle($item) { - return $item('.info').data('subtitle') -} - -function parseImage($item) { - return $item('.info').data('image') -} - -function parseStart($item, date) { - const [time] = $item('.info') - .data('time') - .match(/^\d{1,2}\.\d{2}(am|pm)/) || [null] - - if (!time) return null - - return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD h.mma', 'Europe/London') -} - -function parseDuration($item) { - const duration = $item('.info > .time').data('minutes') - - return duration ? parseInt(duration) : 30 -} - -function parseItems(content, channel) { - const $ = cheerio.load(content) - - return $(`.programs_parent > .programs[data-channel='${channel.site_id}'] > .program`).toArray() -} +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'virginmediatelevision.ie', + days: 2, + url({ date }) { + return `https://www.virginmediatelevision.ie/includes/ajax/tv_guide.php?date=${date.format( + 'YYYY-MM-DD' + )}` + }, + request: { + cache: { + ttl: 60 * 60 * 1000 // 1h + } + }, + parser({ content, channel, date }) { + const programs = [] + const items = parseItems(content, channel) + items.forEach(item => { + const $item = cheerio.load(item) + let start = parseStart($item, date) + let duration = parseDuration($item) + let stop = start.add(duration, 'm') + programs.push({ + title: parseTitle($item), + description: parseDescription($item), + sub_title: parseSubTitle($item), + image: parseImage($item), + start, + stop + }) + }) + + return programs + } +} + +function parseTitle($item) { + return $item('.info > h2').text().trim() +} + +function parseDescription($item) { + return $item('.info').data('description') +} + +function parseSubTitle($item) { + return $item('.info').data('subtitle') +} + +function parseImage($item) { + return $item('.info').data('image') +} + +function parseStart($item, date) { + const [time] = $item('.info') + .data('time') + .match(/^\d{1,2}\.\d{2}(am|pm)/) || [null] + + if (!time) return null + + return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD h.mma', 'Europe/London') +} + +function parseDuration($item) { + const duration = $item('.info > .time').data('minutes') + + return duration ? parseInt(duration) : 30 +} + +function parseItems(content, channel) { + const $ = cheerio.load(content) + + return $(`.programs_parent > .programs[data-channel='${channel.site_id}'] > .program`).toArray() +} diff --git a/sites/virginmediatelevision.ie/virginmediatelevision.ie.test.js b/sites/virginmediatelevision.ie/virginmediatelevision.ie.test.js index ad5c067a1..9d1514150 100644 --- a/sites/virginmediatelevision.ie/virginmediatelevision.ie.test.js +++ b/sites/virginmediatelevision.ie/virginmediatelevision.ie.test.js @@ -1,51 +1,51 @@ -const { parser, url } = require('./virginmediatelevision.ie.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-01-31', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'one', - xmltv_id: 'VirginMediaOne.ie' -} - -it('can generate valid url', () => { - expect(url({ date })).toBe( - 'https://www.virginmediatelevision.ie/includes/ajax/tv_guide.php?date=2023-01-31' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') - let results = parser({ content, channel, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(23) - expect(results[0]).toMatchObject({ - start: '2023-01-31T00:00:00.000Z', - stop: '2023-01-31T01:00:00.000Z', - title: 'Chasing Shadows', - sub_title: '', - description: - 'A detective sergeant and expert in the field of serial killers working for the Missing Persons Bureau tries to protect the general public from evil.', - image: 'https://bcboltvirgin.akamaized.net/player/shows/1498_517x291_1528141264.jpg' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - date, - channel, - content: '' - }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./virginmediatelevision.ie.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-01-31', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'one', + xmltv_id: 'VirginMediaOne.ie' +} + +it('can generate valid url', () => { + expect(url({ date })).toBe( + 'https://www.virginmediatelevision.ie/includes/ajax/tv_guide.php?date=2023-01-31' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') + let results = parser({ content, channel, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(23) + expect(results[0]).toMatchObject({ + start: '2023-01-31T00:00:00.000Z', + stop: '2023-01-31T01:00:00.000Z', + title: 'Chasing Shadows', + sub_title: '', + description: + 'A detective sergeant and expert in the field of serial killers working for the Missing Persons Bureau tries to protect the general public from evil.', + image: 'https://bcboltvirgin.akamaized.net/player/shows/1498_517x291_1528141264.jpg' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + date, + channel, + content: '' + }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/virgintvgo.virginmedia.com/virgintvgo.virginmedia.com.config.js b/sites/virgintvgo.virginmedia.com/virgintvgo.virginmedia.com.config.js index dba45f8db..1b38da434 100644 --- a/sites/virgintvgo.virginmedia.com/virgintvgo.virginmedia.com.config.js +++ b/sites/virgintvgo.virginmedia.com/virgintvgo.virginmedia.com.config.js @@ -1,114 +1,114 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const doFetch = require('@ntlab/sfetch') -const debug = require('debug')('site:virgintvgo.virginmedia.com') - -dayjs.extend(utc) - -doFetch.setDebugger(debug) - -const detailedGuide = true - -module.exports = { - site: 'virgintvgo.virginmedia.com', - days: 2, - request: { - cache: { - ttl: 24 * 60 * 60 * 1000 // 1 day - } - }, - url({ date, segment = 0 }) { - return `https://staticqbr-prod-gb.gnp.cloud.virgintvgo.virginmedia.com/eng/web/epg-service-lite/gb/en/events/segments/${date.format( - 'YYYYMMDD' - )}${segment.toString().padStart(2, '0')}0000` - }, - async parser({ content, channel, date }) { - const programs = [] - if (content) { - const items = typeof content === 'string' ? JSON.parse(content) : content - if (Array.isArray(items.entries)) { - // fetch other segments - const queues = [ - module.exports.url({ date, segment: 6 }), - module.exports.url({ date, segment: 12 }), - module.exports.url({ date, segment: 18 }) - ] - await doFetch(queues, (url, res) => { - if (Array.isArray(res.entries)) { - items.entries.push(...res.entries) - } - }) - items.entries - .filter(item => item.channelId === channel.site_id) - .forEach(item => { - if (Array.isArray(item.events)) { - if (detailedGuide) { - queues.push( - ...item.events.map( - event => - `https://spark-prod-gb.gnp.cloud.virgintvgo.virginmedia.com/eng/web/linear-service/v2/replayEvent/${event.id}?returnLinearContent=true&forceLinearResponse=true&language=en` - ) - ) - } else { - item.events.forEach(event => { - programs.push({ - title: event.title, - start: dayjs.utc(event.startTime * 1000), - stop: dayjs.utc(event.endTime * 1000) - }) - }) - } - } - }) - // fetch detailed guide - if (queues.length) { - await doFetch(queues, (url, res) => { - programs.push({ - title: res.title, - subTitle: res.episodeName, - description: res.longDescription ? res.longDescription : res.shortDescription, - category: res.genres, - season: res.seasonNumber, - episode: res.episodeNumber, - country: res.countryOfOrigin, - actor: res.actors, - director: res.directors, - producer: res.producers, - date: res.productionDate, - start: dayjs.utc(res.startTime * 1000), - stop: dayjs.utc(res.endTime * 1000) - }) - }) - } - } - } - - return programs - }, - async channels() { - const channels = [] - const axios = require('axios') - const res = await axios - .get( - 'https://spark-prod-gb.gnp.cloud.virgintvgo.virginmedia.com/eng/web/linear-service/v2/channels?cityId=40980&language=en&productClass=Orion-DASH&platform=web' - ) - .then(r => r.data) - .catch(console.error) - - if (Array.isArray(res)) { - channels.push( - ...res - .filter(item => !item.isHidden) - .map(item => { - return { - lang: 'en', - site_id: item.id, - name: item.name - } - }) - ) - } - - return channels - } -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const doFetch = require('@ntlab/sfetch') +const debug = require('debug')('site:virgintvgo.virginmedia.com') + +dayjs.extend(utc) + +doFetch.setDebugger(debug) + +const detailedGuide = true + +module.exports = { + site: 'virgintvgo.virginmedia.com', + days: 2, + request: { + cache: { + ttl: 24 * 60 * 60 * 1000 // 1 day + } + }, + url({ date, segment = 0 }) { + return `https://staticqbr-prod-gb.gnp.cloud.virgintvgo.virginmedia.com/eng/web/epg-service-lite/gb/en/events/segments/${date.format( + 'YYYYMMDD' + )}${segment.toString().padStart(2, '0')}0000` + }, + async parser({ content, channel, date }) { + const programs = [] + if (content) { + const items = typeof content === 'string' ? JSON.parse(content) : content + if (Array.isArray(items.entries)) { + // fetch other segments + const queues = [ + module.exports.url({ date, segment: 6 }), + module.exports.url({ date, segment: 12 }), + module.exports.url({ date, segment: 18 }) + ] + await doFetch(queues, (url, res) => { + if (Array.isArray(res.entries)) { + items.entries.push(...res.entries) + } + }) + items.entries + .filter(item => item.channelId === channel.site_id) + .forEach(item => { + if (Array.isArray(item.events)) { + if (detailedGuide) { + queues.push( + ...item.events.map( + event => + `https://spark-prod-gb.gnp.cloud.virgintvgo.virginmedia.com/eng/web/linear-service/v2/replayEvent/${event.id}?returnLinearContent=true&forceLinearResponse=true&language=en` + ) + ) + } else { + item.events.forEach(event => { + programs.push({ + title: event.title, + start: dayjs.utc(event.startTime * 1000), + stop: dayjs.utc(event.endTime * 1000) + }) + }) + } + } + }) + // fetch detailed guide + if (queues.length) { + await doFetch(queues, (url, res) => { + programs.push({ + title: res.title, + subTitle: res.episodeName, + description: res.longDescription ? res.longDescription : res.shortDescription, + category: res.genres, + season: res.seasonNumber, + episode: res.episodeNumber, + country: res.countryOfOrigin, + actor: res.actors, + director: res.directors, + producer: res.producers, + date: res.productionDate, + start: dayjs.utc(res.startTime * 1000), + stop: dayjs.utc(res.endTime * 1000) + }) + }) + } + } + } + + return programs + }, + async channels() { + const channels = [] + const axios = require('axios') + const res = await axios + .get( + 'https://spark-prod-gb.gnp.cloud.virgintvgo.virginmedia.com/eng/web/linear-service/v2/channels?cityId=40980&language=en&productClass=Orion-DASH&platform=web' + ) + .then(r => r.data) + .catch(console.error) + + if (Array.isArray(res)) { + channels.push( + ...res + .filter(item => !item.isHidden) + .map(item => { + return { + lang: 'en', + site_id: item.id, + name: item.name + } + }) + ) + } + + return channels + } +} diff --git a/sites/virgintvgo.virginmedia.com/virgintvgo.virginmedia.com.test.js b/sites/virgintvgo.virginmedia.com/virgintvgo.virginmedia.com.test.js index 1fbc3400b..683a92665 100644 --- a/sites/virgintvgo.virginmedia.com/virgintvgo.virginmedia.com.test.js +++ b/sites/virgintvgo.virginmedia.com/virgintvgo.virginmedia.com.test.js @@ -1,95 +1,95 @@ -const { parser, url } = require('./virgintvgo.virginmedia.com.config.js') -const fs = require('fs') -const path = require('path') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -const date = dayjs.utc('2024-12-14', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '1958', - xmltv_id: '5ActionHD.uk' -} - -axios.get.mockImplementation(url => { - const urls = { - 'https://staticqbr-prod-gb.gnp.cloud.virgintvgo.virginmedia.com/eng/web/epg-service-lite/gb/en/events/segments/20241214000000': - 'content00.json', - 'https://staticqbr-prod-gb.gnp.cloud.virgintvgo.virginmedia.com/eng/web/epg-service-lite/gb/en/events/segments/20241214060000': - 'content06.json', - 'https://staticqbr-prod-gb.gnp.cloud.virgintvgo.virginmedia.com/eng/web/epg-service-lite/gb/en/events/segments/20241214120000': - 'content12.json', - 'https://staticqbr-prod-gb.gnp.cloud.virgintvgo.virginmedia.com/eng/web/epg-service-lite/gb/en/events/segments/20241214180000': - 'content18.json', - 'https://spark-prod-gb.gnp.cloud.virgintvgo.virginmedia.com/eng/web/linear-service/v2/replayEvent/crid:~~2F~~2Fgn.tv~~2F16647964~~2FEP012911720228,imi:74a552c465e11e5fe6ed7bfae7aeda5b639322ff?returnLinearContent=true&forceLinearResponse=true&language=en': - 'program01.json', - 'https://spark-prod-gb.gnp.cloud.virgintvgo.virginmedia.com/eng/web/linear-service/v2/replayEvent/crid:~~2F~~2Fgn.tv~~2F17641069~~2FEP026460800059,imi:23c363d12af79f43134f4a15b96dd12df81b19ab?returnLinearContent=true&forceLinearResponse=true&language=en': - 'program02.json', - 'https://spark-prod-gb.gnp.cloud.virgintvgo.virginmedia.com/eng/web/linear-service/v2/replayEvent/crid:~~2F~~2Fgn.tv~~2F19221598~~2FSH037146530000~~2F333458689,imi:f1060b3f63cd5399e0f97901b25a85ef71097891?returnLinearContent=true&forceLinearResponse=true&language=en': - 'program03.json' - } - let data = '' - if (urls[url] !== undefined) { - data = fs.readFileSync(path.join(__dirname, '__data__', urls[url])).toString() - if (!urls[url].startsWith('content00')) { - data = JSON.parse(data) - } - } - return Promise.resolve({ data }) -}) - -it('can generate valid url', () => { - expect(url({ date })).toBe( - 'https://staticqbr-prod-gb.gnp.cloud.virgintvgo.virginmedia.com/eng/web/epg-service-lite/gb/en/events/segments/20241214000000' - ) -}) - -it('can parse response', async () => { - const content = await axios - .get(url({ date })) - .then(response => response.data) - .catch(console.error) - const result = (await parser({ content, channel, date })).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result.length).toBe(3) - expect(result[0]).toMatchObject({ - start: '2024-12-14T00:00:00.000Z', - stop: '2024-12-14T01:05:00.000Z', - title: 'Police Interceptors', - description: - 'Eight police cars and the eye in the sky hunt down a high powered Porsche Cayenne that is causing carnage. Undertaking at high speeds and goading the interceptors, the driver even manages to take out several police cars.', - category: ['Reality', 'Crime'], - season: 16, - episode: 1 - }) - expect(result[2]).toMatchObject({ - start: '2024-12-14T22:00:00.000Z', - stop: '2024-12-14T22:05:00.000Z', - title: 'Entertainment News On 5', - description: - 'A daily round-up of showbiz news and gossip from around the world, focusing on celebrities, movies, music and entertainment.', - category: ['News', 'Entertainment'], - season: 46530000, - episode: 333458689, - actor: ['Jamie Burton'] - }) -}) - -it('can handle empty guide', async () => { - const result = await parser({ - content: '', - channel, - date - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./virgintvgo.virginmedia.com.config.js') +const fs = require('fs') +const path = require('path') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.utc('2024-12-14', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '1958', + xmltv_id: '5ActionHD.uk' +} + +axios.get.mockImplementation(url => { + const urls = { + 'https://staticqbr-prod-gb.gnp.cloud.virgintvgo.virginmedia.com/eng/web/epg-service-lite/gb/en/events/segments/20241214000000': + 'content00.json', + 'https://staticqbr-prod-gb.gnp.cloud.virgintvgo.virginmedia.com/eng/web/epg-service-lite/gb/en/events/segments/20241214060000': + 'content06.json', + 'https://staticqbr-prod-gb.gnp.cloud.virgintvgo.virginmedia.com/eng/web/epg-service-lite/gb/en/events/segments/20241214120000': + 'content12.json', + 'https://staticqbr-prod-gb.gnp.cloud.virgintvgo.virginmedia.com/eng/web/epg-service-lite/gb/en/events/segments/20241214180000': + 'content18.json', + 'https://spark-prod-gb.gnp.cloud.virgintvgo.virginmedia.com/eng/web/linear-service/v2/replayEvent/crid:~~2F~~2Fgn.tv~~2F16647964~~2FEP012911720228,imi:74a552c465e11e5fe6ed7bfae7aeda5b639322ff?returnLinearContent=true&forceLinearResponse=true&language=en': + 'program01.json', + 'https://spark-prod-gb.gnp.cloud.virgintvgo.virginmedia.com/eng/web/linear-service/v2/replayEvent/crid:~~2F~~2Fgn.tv~~2F17641069~~2FEP026460800059,imi:23c363d12af79f43134f4a15b96dd12df81b19ab?returnLinearContent=true&forceLinearResponse=true&language=en': + 'program02.json', + 'https://spark-prod-gb.gnp.cloud.virgintvgo.virginmedia.com/eng/web/linear-service/v2/replayEvent/crid:~~2F~~2Fgn.tv~~2F19221598~~2FSH037146530000~~2F333458689,imi:f1060b3f63cd5399e0f97901b25a85ef71097891?returnLinearContent=true&forceLinearResponse=true&language=en': + 'program03.json' + } + let data = '' + if (urls[url] !== undefined) { + data = fs.readFileSync(path.join(__dirname, '__data__', urls[url])).toString() + if (!urls[url].startsWith('content00')) { + data = JSON.parse(data) + } + } + return Promise.resolve({ data }) +}) + +it('can generate valid url', () => { + expect(url({ date })).toBe( + 'https://staticqbr-prod-gb.gnp.cloud.virgintvgo.virginmedia.com/eng/web/epg-service-lite/gb/en/events/segments/20241214000000' + ) +}) + +it('can parse response', async () => { + const content = await axios + .get(url({ date })) + .then(response => response.data) + .catch(console.error) + const result = (await parser({ content, channel, date })).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result.length).toBe(3) + expect(result[0]).toMatchObject({ + start: '2024-12-14T00:00:00.000Z', + stop: '2024-12-14T01:05:00.000Z', + title: 'Police Interceptors', + description: + 'Eight police cars and the eye in the sky hunt down a high powered Porsche Cayenne that is causing carnage. Undertaking at high speeds and goading the interceptors, the driver even manages to take out several police cars.', + category: ['Reality', 'Crime'], + season: 16, + episode: 1 + }) + expect(result[2]).toMatchObject({ + start: '2024-12-14T22:00:00.000Z', + stop: '2024-12-14T22:05:00.000Z', + title: 'Entertainment News On 5', + description: + 'A daily round-up of showbiz news and gossip from around the world, focusing on celebrities, movies, music and entertainment.', + category: ['News', 'Entertainment'], + season: 46530000, + episode: 333458689, + actor: ['Jamie Burton'] + }) +}) + +it('can handle empty guide', async () => { + const result = await parser({ + content: '', + channel, + date + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/visionplus.id/visionplus.id.config.js b/sites/visionplus.id/visionplus.id.config.js index e4220ca48..9ad5e31bd 100644 --- a/sites/visionplus.id/visionplus.id.config.js +++ b/sites/visionplus.id/visionplus.id.config.js @@ -1,71 +1,71 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -const languages = { en: 'ENG', id: 'IND' } - -module.exports = { - site: 'visionplus.id', - days: 2, - url({ date, channel }) { - return `https://www.visionplus.id/managetv/tvinfo/events/schedule?language=${ - languages[channel.lang] - }&serviceId=${channel.site_id}&start=${date.format('YYYY-MM-DD')}T00%3A00%3A00Z&end=${date - .add(1, 'd') - .format('YYYY-MM-DD')}T00%3A00%3A00Z&view=cd-events-grid-view` - }, - parser({ content, channel }) { - const programs = [] - const json = JSON.parse(content) - if (Array.isArray(json.evs)) { - for (const ev of json.evs) { - if (ev.sid === channel.site_id) { - const title = ev.con && ev.con.loc ? ev.con.loc[0].tit : ev.con.oti - const [, , season, , episode] = title.match(/( S(\d+))?(, Ep (\d+))/) || [ - null, - null, - null, - null, - null - ] - programs.push({ - title, - description: ev.con && ev.con.loc ? ev.con.loc[0].syn : null, - categories: ev.con ? ev.con.categories : null, - season: season ? parseInt(season) : season, - episode: episode ? parseInt(episode) : episode, - start: dayjs(ev.sta), - stop: dayjs(ev.end) - }) - } - } - } - - return programs - }, - async channels({ lang = 'id' }) { - const result = [] - const axios = require('axios') - const json = await axios - .get(`https://www.visionplus.id/managetv/tvinfo/channels/get?language=${languages[lang]}`) - .then(response => response.data) - .catch(console.error) - - if (Array.isArray(json?.chs)) { - for (const ch of json.chs) { - result.push({ - lang, - site_id: ch.sid, - name: ch.loc[0].nam - }) - } - } - - return result - } -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +const languages = { en: 'ENG', id: 'IND' } + +module.exports = { + site: 'visionplus.id', + days: 2, + url({ date, channel }) { + return `https://www.visionplus.id/managetv/tvinfo/events/schedule?language=${ + languages[channel.lang] + }&serviceId=${channel.site_id}&start=${date.format('YYYY-MM-DD')}T00%3A00%3A00Z&end=${date + .add(1, 'd') + .format('YYYY-MM-DD')}T00%3A00%3A00Z&view=cd-events-grid-view` + }, + parser({ content, channel }) { + const programs = [] + const json = JSON.parse(content) + if (Array.isArray(json.evs)) { + for (const ev of json.evs) { + if (ev.sid === channel.site_id) { + const title = ev.con && ev.con.loc ? ev.con.loc[0].tit : ev.con.oti + const [, , season, , episode] = title.match(/( S(\d+))?(, Ep (\d+))/) || [ + null, + null, + null, + null, + null + ] + programs.push({ + title, + description: ev.con && ev.con.loc ? ev.con.loc[0].syn : null, + categories: ev.con ? ev.con.categories : null, + season: season ? parseInt(season) : season, + episode: episode ? parseInt(episode) : episode, + start: dayjs(ev.sta), + stop: dayjs(ev.end) + }) + } + } + } + + return programs + }, + async channels({ lang = 'id' }) { + const result = [] + const axios = require('axios') + const json = await axios + .get(`https://www.visionplus.id/managetv/tvinfo/channels/get?language=${languages[lang]}`) + .then(response => response.data) + .catch(console.error) + + if (Array.isArray(json?.chs)) { + for (const ch of json.chs) { + result.push({ + lang, + site_id: ch.sid, + name: ch.loc[0].nam + }) + } + } + + return result + } +} diff --git a/sites/visionplus.id/visionplus.id.test.js b/sites/visionplus.id/visionplus.id.test.js index f71f7c6fc..0579b669a 100644 --- a/sites/visionplus.id/visionplus.id.test.js +++ b/sites/visionplus.id/visionplus.id.test.js @@ -1,73 +1,73 @@ -const { parser, url } = require('./visionplus.id.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -const date = dayjs.utc('2024-11-24', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '00000000000000000079', - xmltv_id: 'AXN.id', - lang: 'en' -} -const channelId = { ...channel, lang: 'id' } - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://www.visionplus.id/managetv/tvinfo/events/schedule?language=ENG&serviceId=00000000000000000079&start=2024-11-24T00%3A00%3A00Z&end=2024-11-25T00%3A00%3A00Z&view=cd-events-grid-view' - ) - expect(url({ channel: channelId, date })).toBe( - 'https://www.visionplus.id/managetv/tvinfo/events/schedule?language=IND&serviceId=00000000000000000079&start=2024-11-24T00%3A00%3A00Z&end=2024-11-25T00%3A00%3A00Z&view=cd-events-grid-view' - ) -}) - -it('can parse response', () => { - let content = fs.readFileSync(path.resolve(__dirname, '__data__/content_en.json')) - let results = parser({ content, channel, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(1) - expect(results[0]).toMatchObject({ - start: '2024-11-23T23:30:00.000Z', - stop: '2024-11-24T00:15:00.000Z', - title: 'FBI: Most Wanted S4, Ep 18', - description: - 'After two agents from the Bureau of Land Management go missing while executing a land seizure warrant in Wyoming, the Fugitive Task Force heads west to track them down in an unwelcoming county.', - season: 4, - episode: 18 - }) - - content = fs.readFileSync(path.resolve(__dirname, '__data__/content_id.json')) - results = parser({ content, channel: channelId, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(1) - expect(results[0]).toMatchObject({ - start: '2024-11-23T23:30:00.000Z', - stop: '2024-11-24T00:15:00.000Z', - title: 'FBI: Most Wanted S4, Ep 18', - description: - 'Satgas Buronan pergi ke wilayah barat untuk melacak keberadaan dua petugas Biro Pengelolaan Lahan yang menghilang saat menjalankan perintah penyitaan lahan di negara bagian yang tak ramah, Wyoming.', - season: 4, - episode: 18 - }) -}) - -it('can handle empty guide', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - const results = parser({ content, channel }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./visionplus.id.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.utc('2024-11-24', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '00000000000000000079', + xmltv_id: 'AXN.id', + lang: 'en' +} +const channelId = { ...channel, lang: 'id' } + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://www.visionplus.id/managetv/tvinfo/events/schedule?language=ENG&serviceId=00000000000000000079&start=2024-11-24T00%3A00%3A00Z&end=2024-11-25T00%3A00%3A00Z&view=cd-events-grid-view' + ) + expect(url({ channel: channelId, date })).toBe( + 'https://www.visionplus.id/managetv/tvinfo/events/schedule?language=IND&serviceId=00000000000000000079&start=2024-11-24T00%3A00%3A00Z&end=2024-11-25T00%3A00%3A00Z&view=cd-events-grid-view' + ) +}) + +it('can parse response', () => { + let content = fs.readFileSync(path.resolve(__dirname, '__data__/content_en.json')) + let results = parser({ content, channel, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(1) + expect(results[0]).toMatchObject({ + start: '2024-11-23T23:30:00.000Z', + stop: '2024-11-24T00:15:00.000Z', + title: 'FBI: Most Wanted S4, Ep 18', + description: + 'After two agents from the Bureau of Land Management go missing while executing a land seizure warrant in Wyoming, the Fugitive Task Force heads west to track them down in an unwelcoming county.', + season: 4, + episode: 18 + }) + + content = fs.readFileSync(path.resolve(__dirname, '__data__/content_id.json')) + results = parser({ content, channel: channelId, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(1) + expect(results[0]).toMatchObject({ + start: '2024-11-23T23:30:00.000Z', + stop: '2024-11-24T00:15:00.000Z', + title: 'FBI: Most Wanted S4, Ep 18', + description: + 'Satgas Buronan pergi ke wilayah barat untuk melacak keberadaan dua petugas Biro Pengelolaan Lahan yang menghilang saat menjalankan perintah penyitaan lahan di negara bagian yang tak ramah, Wyoming.', + season: 4, + episode: 18 + }) +}) + +it('can handle empty guide', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + const results = parser({ content, channel }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/vivoplay.com.br/vivoplay.com.br.config.js b/sites/vivoplay.com.br/vivoplay.com.br.config.js index 97ee36dbd..a6f5b8a15 100644 --- a/sites/vivoplay.com.br/vivoplay.com.br.config.js +++ b/sites/vivoplay.com.br/vivoplay.com.br.config.js @@ -1,68 +1,68 @@ -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'vivoplay.com.br', - days: 2, - url({ channel, date }) { - const starttime = date.unix() - const endtime = date.add(1, 'd').unix() - - return `https://contentapi-br.cdn.telefonica.com/25/default/pt-BR/schedules?ca_deviceTypes=null%7C401&ca_channelmaps=779%7Cnull&fields=Pid,Title,Description,ChannelName,ChannelNumber,CallLetter,Start,End,LiveChannelPid,LiveProgramPid,EpgSerieId,SeriesPid,SeriesId,SeasonPid,SeasonNumber,EpisodeNumber,images.videoFrame,images.banner,LiveToVod,AgeRatingPid,forbiddenTechnology,IsSoDisabled&includeRelations=Genre&orderBy=START_TIME%3Aa&filteravailability=false&includeAttributes=ca_cpvrDisable,ca_descriptors,ca_blackout_target,ca_blackout_areas&starttime=${starttime}&endtime=${endtime}&livechannelpids=${channel.site_id}&offset=0&limit=1000` - }, - parser: function ({ content }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - programs.push({ - title: item['Title'], - description: item['Description'], - season: item['SeasonNumber'] > 0 ? item['SeasonNumber'] : null, - episode: item['EpisodeNumber'] > 0 ? item['EpisodeNumber'] : null, - images: parseImages(item), - start: dayjs.unix(item['Start']), - stop: dayjs.unix(item['End']) - }) - }) - - return programs - }, - async channels() { - const data = await axios - .get( - 'https://contentapi-br.cdn.telefonica.com/25/default/pt-BR/contents/all?ca_deviceTypes=401&contentTypes=LCH&ca_active=true&ca_requiresPin=false&includeAttributes=ca_channelmapnumber,ca_devicetypes_qualities,ca_deviceTypes_isPlayback,ca_deviceTypes_isStartOverEnabled,ca_deviceTypes_isPvrPlayback,ca_deviceTypes_isPvrManageable,ca_deviceTypes_isCatchup,ca_channelmaps&includeRelations=ProductDependencies,Media&fields=Pid,Name,ChannelNumber,Dvr,EpgLiveChannelReferenceId,CallLetter,ProviderChannel,LXDChannel,AdvancedCDNServices,CdnBuffer,DefaultLanguageOrders,DistributorId,IsLatencyKey,images.logo,images.icon,UxReference,HasPlaylistExperience,IsHomeBlocked,IsStoverFfwdDisabled,IsStoverRwdDisabled,IsCpvrFfwdDisabled,IsCpvrRwdDisabled,IsCatchupFfwdDisabled,IsCatchupRwdDisabled,IsCowatchEnabled,IsFastChannel,MaxLiveNowGap&orderBy=contentOrder&offset=0&limit=1000' - ) - .then(r => r.data) - .catch(console.error) - - return data['Content']['List'].map(channel => ({ - lang: 'pt', - name: channel['Name'], - site_id: channel['Pid'].toLowerCase() - })) - } -} - -function parseImages(item) { - return item['Images'] && Array.isArray(item['Images']['VideoFrame']) - ? item['Images']['VideoFrame'].map(vf => vf['Url']) - : [] -} - -function parseItems(content) { - try { - const data = JSON.parse(content) - if (!data || !Array.isArray(data['Content'])) return [] - - return data['Content'] - } catch { - return [] - } -} +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'vivoplay.com.br', + days: 2, + url({ channel, date }) { + const starttime = date.unix() + const endtime = date.add(1, 'd').unix() + + return `https://contentapi-br.cdn.telefonica.com/25/default/pt-BR/schedules?ca_deviceTypes=null%7C401&ca_channelmaps=779%7Cnull&fields=Pid,Title,Description,ChannelName,ChannelNumber,CallLetter,Start,End,LiveChannelPid,LiveProgramPid,EpgSerieId,SeriesPid,SeriesId,SeasonPid,SeasonNumber,EpisodeNumber,images.videoFrame,images.banner,LiveToVod,AgeRatingPid,forbiddenTechnology,IsSoDisabled&includeRelations=Genre&orderBy=START_TIME%3Aa&filteravailability=false&includeAttributes=ca_cpvrDisable,ca_descriptors,ca_blackout_target,ca_blackout_areas&starttime=${starttime}&endtime=${endtime}&livechannelpids=${channel.site_id}&offset=0&limit=1000` + }, + parser: function ({ content }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + programs.push({ + title: item['Title'], + description: item['Description'], + season: item['SeasonNumber'] > 0 ? item['SeasonNumber'] : null, + episode: item['EpisodeNumber'] > 0 ? item['EpisodeNumber'] : null, + images: parseImages(item), + start: dayjs.unix(item['Start']), + stop: dayjs.unix(item['End']) + }) + }) + + return programs + }, + async channels() { + const data = await axios + .get( + 'https://contentapi-br.cdn.telefonica.com/25/default/pt-BR/contents/all?ca_deviceTypes=401&contentTypes=LCH&ca_active=true&ca_requiresPin=false&includeAttributes=ca_channelmapnumber,ca_devicetypes_qualities,ca_deviceTypes_isPlayback,ca_deviceTypes_isStartOverEnabled,ca_deviceTypes_isPvrPlayback,ca_deviceTypes_isPvrManageable,ca_deviceTypes_isCatchup,ca_channelmaps&includeRelations=ProductDependencies,Media&fields=Pid,Name,ChannelNumber,Dvr,EpgLiveChannelReferenceId,CallLetter,ProviderChannel,LXDChannel,AdvancedCDNServices,CdnBuffer,DefaultLanguageOrders,DistributorId,IsLatencyKey,images.logo,images.icon,UxReference,HasPlaylistExperience,IsHomeBlocked,IsStoverFfwdDisabled,IsStoverRwdDisabled,IsCpvrFfwdDisabled,IsCpvrRwdDisabled,IsCatchupFfwdDisabled,IsCatchupRwdDisabled,IsCowatchEnabled,IsFastChannel,MaxLiveNowGap&orderBy=contentOrder&offset=0&limit=1000' + ) + .then(r => r.data) + .catch(console.error) + + return data['Content']['List'].map(channel => ({ + lang: 'pt', + name: channel['Name'], + site_id: channel['Pid'].toLowerCase() + })) + } +} + +function parseImages(item) { + return item['Images'] && Array.isArray(item['Images']['VideoFrame']) + ? item['Images']['VideoFrame'].map(vf => vf['Url']) + : [] +} + +function parseItems(content) { + try { + const data = JSON.parse(content) + if (!data || !Array.isArray(data['Content'])) return [] + + return data['Content'] + } catch { + return [] + } +} diff --git a/sites/vivoplay.com.br/vivoplay.com.br.test.js b/sites/vivoplay.com.br/vivoplay.com.br.test.js index 17d607ff1..8f4f1f692 100644 --- a/sites/vivoplay.com.br/vivoplay.com.br.test.js +++ b/sites/vivoplay.com.br/vivoplay.com.br.test.js @@ -1,61 +1,61 @@ -const { parser, url } = require('./vivoplay.com.br.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-01-19', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'lch5554', - xmltv_id: 'TVNovoTempo.br' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://contentapi-br.cdn.telefonica.com/25/default/pt-BR/schedules?ca_deviceTypes=null%7C401&ca_channelmaps=779%7Cnull&fields=Pid,Title,Description,ChannelName,ChannelNumber,CallLetter,Start,End,LiveChannelPid,LiveProgramPid,EpgSerieId,SeriesPid,SeriesId,SeasonPid,SeasonNumber,EpisodeNumber,images.videoFrame,images.banner,LiveToVod,AgeRatingPid,forbiddenTechnology,IsSoDisabled&includeRelations=Genre&orderBy=START_TIME%3Aa&filteravailability=false&includeAttributes=ca_cpvrDisable,ca_descriptors,ca_blackout_target,ca_blackout_areas&starttime=1737244800&endtime=1737331200&livechannelpids=lch5554&offset=0&limit=1000' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - let results = parser({ content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(44) - expect(results[0]).toMatchObject({ - start: '2025-01-19T00:00:00.000Z', - stop: '2025-01-19T00:30:00.000Z', - title: 'Reavivados para a Missão', - description: - 'Tudo sobre a missão com o Pastor Ted Wilson, líder mundial da Igreja Adventista do Sétimo Dia.', - season: 25, - episode: 3, - images: [ - 'http://media.gvp.telefonica.com/storageArea0/IMAGES/00/21/19/21190052_46294A7A7B0DF467.jpg' - ] - }) - expect(results[43]).toMatchObject({ - start: '2025-01-19T23:30:00.000Z', - stop: '2025-01-20T00:00:00.000Z', - title: 'A Máquina Humana', - description: - 'O documentário explora a complexidade e a perfeição do corpo humano por meio de uma série de analogias com máquinas, olhando para a questão da saúde através das perspectivas da ciência, tecnologia e espiritualidade.', - season: null, - episode: null, - images: [ - 'http://media.gvp.telefonica.com/storageArea0/IMAGES/00/20/86/20864769_7DD013A4CCCF7899.jpg' - ] - }) -}) - -it('can handle empty guide', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - const results = parser({ content }) - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./vivoplay.com.br.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-01-19', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'lch5554', + xmltv_id: 'TVNovoTempo.br' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://contentapi-br.cdn.telefonica.com/25/default/pt-BR/schedules?ca_deviceTypes=null%7C401&ca_channelmaps=779%7Cnull&fields=Pid,Title,Description,ChannelName,ChannelNumber,CallLetter,Start,End,LiveChannelPid,LiveProgramPid,EpgSerieId,SeriesPid,SeriesId,SeasonPid,SeasonNumber,EpisodeNumber,images.videoFrame,images.banner,LiveToVod,AgeRatingPid,forbiddenTechnology,IsSoDisabled&includeRelations=Genre&orderBy=START_TIME%3Aa&filteravailability=false&includeAttributes=ca_cpvrDisable,ca_descriptors,ca_blackout_target,ca_blackout_areas&starttime=1737244800&endtime=1737331200&livechannelpids=lch5554&offset=0&limit=1000' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + let results = parser({ content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(44) + expect(results[0]).toMatchObject({ + start: '2025-01-19T00:00:00.000Z', + stop: '2025-01-19T00:30:00.000Z', + title: 'Reavivados para a Missão', + description: + 'Tudo sobre a missão com o Pastor Ted Wilson, líder mundial da Igreja Adventista do Sétimo Dia.', + season: 25, + episode: 3, + images: [ + 'http://media.gvp.telefonica.com/storageArea0/IMAGES/00/21/19/21190052_46294A7A7B0DF467.jpg' + ] + }) + expect(results[43]).toMatchObject({ + start: '2025-01-19T23:30:00.000Z', + stop: '2025-01-20T00:00:00.000Z', + title: 'A Máquina Humana', + description: + 'O documentário explora a complexidade e a perfeição do corpo humano por meio de uma série de analogias com máquinas, olhando para a questão da saúde através das perspectivas da ciência, tecnologia e espiritualidade.', + season: null, + episode: null, + images: [ + 'http://media.gvp.telefonica.com/storageArea0/IMAGES/00/20/86/20864769_7DD013A4CCCF7899.jpg' + ] + }) +}) + +it('can handle empty guide', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + const results = parser({ content }) + expect(results).toMatchObject([]) +}) diff --git a/sites/vtm.be/vtm.be.config.js b/sites/vtm.be/vtm.be.config.js index c919c8ad0..d4cf37d33 100644 --- a/sites/vtm.be/vtm.be.config.js +++ b/sites/vtm.be/vtm.be.config.js @@ -1,48 +1,48 @@ -const dayjs = require('dayjs') -const isBetween = require('dayjs/plugin/isBetween') - -dayjs.extend(isBetween) - -module.exports = { - site: 'vtm.be', - days: 2, - url: function ({ channel }) { - return `https://vtm.be/tv-gids/${channel.site_id}` - }, - request: { - headers: { - Cookie: - 'ak_bmsc=8103DDA2C2C37ECD922124463C746A4C~000000000000000000000000000000~YAAQNwVJF7ndI+p8AQAAYDkcCg0mQAkQ2jDHjSfnXl9VIGnzditECZ1FDj1Yi72a8rv/Q454lDDY0Dm3TPqxJUuNLzxJGmgkLmei4IIIwzKJWbB6wC/FMQApoI1NbGz+tUErryic1HWdbZ2dz1IX+AkOHJ9RVupYG5GmkSEQdFG1+/dSZoBMWEeb/5VOCLmNXRDP7k8LnSXaIuKqp5c2MQB+uQ9DdHUd6bIje3dzuxbka9+nJZ+eX/pNbgWI41X2tiXLvPZKh91Tk9k98zrK0pwBnGpTJqDVxmafYH/CjkXoLgEUW3loZfgL9SqddG706a4LnRPhyLzW6W6SH7Q0QOFE4g54NKADVttS2gbXgVrICvo0bb0FAESaFjc5uDyOd+fV2XBGzw==; authId=54da9bc2-d387-4923-8773-3d33ec68710e; gtm_session=1; _sp_ses.417f=*; _ga=GA1.2.525677035.1636552212; _gid=GA1.2.386833723.1636552212; tcf20_purposes=functional|analytics|targeted_advertising|non-personalised_ads|personalisation|marketing|social_media|advertising_1|advertising_2|advertising_3|advertising_4|advertising_7|advertising_9|advertising_10; _gcl_au=1.1.112810754.1636552212; _gat_UA-538372-57=1; sp=4a32f074-5526-4654-9389-2516d799ec68; _gat_UA-6602938-21=1; _sp_id.417f=0c81a857-09dc-47c2-8e51-4fed976211c4.1636552212.1.1636552214.1636552212.55934f90-4bad-47ff-8c5e-cf904126dcfb; bm_sv=1A45EF31D80D05B688C17EAD85964E29~hFpINNxpFphfJ2LLPoLQTauvUpyAf3kaTeGZAMfI/UTMlTRFjoAGBQJPEUPvSw3rXw/swqqAICc74l56pEBVSw6aJYqaoRaiRAZXyWZzQ6jAoeP5SMsZwtvNzYQ3aJXVWM8W8a98J0trlnSjIIsRPQ==' - } - }, - parser: function ({ content, date }) { - let programs = [] - const data = parseContent(content) - const items = parseItems(data, date) - items.forEach(item => { - programs.push({ - title: item.title, - description: item.synopsis, - category: item.genre, - image: item.imageUrl, - start: dayjs(item.from).toJSON(), - stop: dayjs(item.to).toJSON() - }) - }) - - return programs - } -} - -function parseContent(content) { - const [, json] = content.match(/window.__EPG_REDUX_DATA__=(.*);\n/i) || [null, null] - const data = JSON.parse(json) - - return data -} - -function parseItems(data, date) { - if (!data || !data.broadcasts) return [] - - return Object.values(data.broadcasts).filter(i => dayjs(i.from).isBetween(date, date.add(1, 'd'))) -} +const dayjs = require('dayjs') +const isBetween = require('dayjs/plugin/isBetween') + +dayjs.extend(isBetween) + +module.exports = { + site: 'vtm.be', + days: 2, + url: function ({ channel }) { + return `https://vtm.be/tv-gids/${channel.site_id}` + }, + request: { + headers: { + Cookie: + 'ak_bmsc=8103DDA2C2C37ECD922124463C746A4C~000000000000000000000000000000~YAAQNwVJF7ndI+p8AQAAYDkcCg0mQAkQ2jDHjSfnXl9VIGnzditECZ1FDj1Yi72a8rv/Q454lDDY0Dm3TPqxJUuNLzxJGmgkLmei4IIIwzKJWbB6wC/FMQApoI1NbGz+tUErryic1HWdbZ2dz1IX+AkOHJ9RVupYG5GmkSEQdFG1+/dSZoBMWEeb/5VOCLmNXRDP7k8LnSXaIuKqp5c2MQB+uQ9DdHUd6bIje3dzuxbka9+nJZ+eX/pNbgWI41X2tiXLvPZKh91Tk9k98zrK0pwBnGpTJqDVxmafYH/CjkXoLgEUW3loZfgL9SqddG706a4LnRPhyLzW6W6SH7Q0QOFE4g54NKADVttS2gbXgVrICvo0bb0FAESaFjc5uDyOd+fV2XBGzw==; authId=54da9bc2-d387-4923-8773-3d33ec68710e; gtm_session=1; _sp_ses.417f=*; _ga=GA1.2.525677035.1636552212; _gid=GA1.2.386833723.1636552212; tcf20_purposes=functional|analytics|targeted_advertising|non-personalised_ads|personalisation|marketing|social_media|advertising_1|advertising_2|advertising_3|advertising_4|advertising_7|advertising_9|advertising_10; _gcl_au=1.1.112810754.1636552212; _gat_UA-538372-57=1; sp=4a32f074-5526-4654-9389-2516d799ec68; _gat_UA-6602938-21=1; _sp_id.417f=0c81a857-09dc-47c2-8e51-4fed976211c4.1636552212.1.1636552214.1636552212.55934f90-4bad-47ff-8c5e-cf904126dcfb; bm_sv=1A45EF31D80D05B688C17EAD85964E29~hFpINNxpFphfJ2LLPoLQTauvUpyAf3kaTeGZAMfI/UTMlTRFjoAGBQJPEUPvSw3rXw/swqqAICc74l56pEBVSw6aJYqaoRaiRAZXyWZzQ6jAoeP5SMsZwtvNzYQ3aJXVWM8W8a98J0trlnSjIIsRPQ==' + } + }, + parser: function ({ content, date }) { + let programs = [] + const data = parseContent(content) + const items = parseItems(data, date) + items.forEach(item => { + programs.push({ + title: item.title, + description: item.synopsis, + category: item.genre, + image: item.imageUrl, + start: dayjs(item.from).toJSON(), + stop: dayjs(item.to).toJSON() + }) + }) + + return programs + } +} + +function parseContent(content) { + const [, json] = content.match(/window.__EPG_REDUX_DATA__=(.*);\n/i) || [null, null] + const data = JSON.parse(json) + + return data +} + +function parseItems(data, date) { + if (!data || !data.broadcasts) return [] + + return Object.values(data.broadcasts).filter(i => dayjs(i.from).isBetween(date, date.add(1, 'd'))) +} diff --git a/sites/vtm.be/vtm.be.test.js b/sites/vtm.be/vtm.be.test.js index 0ea8d2b2d..5c2b8c202 100644 --- a/sites/vtm.be/vtm.be.test.js +++ b/sites/vtm.be/vtm.be.test.js @@ -1,44 +1,44 @@ -const { parser, url } = require('./vtm.be.config.js') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2021-11-10', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'vtm', - xmltv_id: 'VTM.be' -} -const content = ` ` - -it('can generate valid url', () => { - const result = url({ channel }) - expect(result).toBe('https://vtm.be/tv-gids/vtm') -}) - -it('can parse response', () => { - const result = parser({ date, channel, content }) - expect(result).toMatchObject([ - { - start: '2021-11-10T23:45:00.000Z', - stop: '2021-11-11T00:20:00.000Z', - title: 'Wooninspiraties', - image: - 'https://images4.persgroep.net/rcs/z5qrZHumkjuN5rWzoaRJ_BTdL7A/diocontent/209688322/_fill/600/400?appId=da11c75db9b73ea0f41f0cd0da631c71', - description: - 'Een team gaat op pad om inspiratie op te doen over alles wat met wonen en leven te maken heeft; Ze trekken heel het land door om de laatste trends en tips op het gebied van wonen te achterhalen.', - category: 'Magazine' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: ' ' - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./vtm.be.config.js') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2021-11-10', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'vtm', + xmltv_id: 'VTM.be' +} +const content = ` ` + +it('can generate valid url', () => { + const result = url({ channel }) + expect(result).toBe('https://vtm.be/tv-gids/vtm') +}) + +it('can parse response', () => { + const result = parser({ date, channel, content }) + expect(result).toMatchObject([ + { + start: '2021-11-10T23:45:00.000Z', + stop: '2021-11-11T00:20:00.000Z', + title: 'Wooninspiraties', + image: + 'https://images4.persgroep.net/rcs/z5qrZHumkjuN5rWzoaRJ_BTdL7A/diocontent/209688322/_fill/600/400?appId=da11c75db9b73ea0f41f0cd0da631c71', + description: + 'Een team gaat op pad om inspiratie op te doen over alles wat met wonen en leven te maken heeft; Ze trekken heel het land door om de laatste trends en tips op het gebied van wonen te achterhalen.', + category: 'Magazine' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: ' ' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/walesi.com.fj/walesi.com.fj.config.js b/sites/walesi.com.fj/walesi.com.fj.config.js index 156582ad4..4b715edce 100644 --- a/sites/walesi.com.fj/walesi.com.fj.config.js +++ b/sites/walesi.com.fj/walesi.com.fj.config.js @@ -1,91 +1,91 @@ -const axios = require('axios') -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'walesi.com.fj', - days: 2, - url: 'https://www.walesi.com.fj/wp-admin/admin-ajax.php', - request: { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' - }, - data({ channel, date }) { - const params = new URLSearchParams() - params.append('chanel', channel.site_id) - params.append('date', date.unix()) - params.append('action', 'extvs_get_schedule_simple') - - return params - } - }, - parser({ content, date }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - const prev = programs[programs.length - 1] - const $item = cheerio.load(item) - const start = parseStart($item, date) - const stop = start.add(30, 'm') - if (prev) prev.stop = start - programs.push({ - title: parseTitle($item), - start, - stop - }) - }) - - return programs - }, - async channels() { - const data = await axios - .get('https://www.walesi.com.fj/channel-guide/') - .then(r => r.data) - .catch(console.log) - - const $ = cheerio.load(data) - const channels = $( - 'div.ex-chanel-list > div.extvs-inline-chanel > ul > li.extvs-inline-select' - ).toArray() - return channels.map(item => { - const $item = cheerio.load(item) - const [, name] = $item('span') - .text() - .trim() - .match(/\d+\. (.*)/) || [null, null] - return { - lang: 'fj', - site_id: $item('*').data('value'), - name - } - }) - } -} - -function parseTitle($item) { - return $item('td.extvs-table1-programme > div > div > figure > h3').text() -} - -function parseStart($item, date) { - let time = $item('td.extvs-table1-time > span').text().trim() - if (!time) return null - time = `${date.format('YYYY-MM-DD')} ${time}` - - return dayjs.tz(time, 'YYYY-MM-DD H:mm a', 'Pacific/Fiji') -} - -function parseItems(content) { - const data = JSON.parse(content) - if (!data.html) return [] - const $ = cheerio.load(data.html) - - return $('table > tbody > tr').toArray() -} +const axios = require('axios') +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'walesi.com.fj', + days: 2, + url: 'https://www.walesi.com.fj/wp-admin/admin-ajax.php', + request: { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' + }, + data({ channel, date }) { + const params = new URLSearchParams() + params.append('chanel', channel.site_id) + params.append('date', date.unix()) + params.append('action', 'extvs_get_schedule_simple') + + return params + } + }, + parser({ content, date }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + const prev = programs[programs.length - 1] + const $item = cheerio.load(item) + const start = parseStart($item, date) + const stop = start.add(30, 'm') + if (prev) prev.stop = start + programs.push({ + title: parseTitle($item), + start, + stop + }) + }) + + return programs + }, + async channels() { + const data = await axios + .get('https://www.walesi.com.fj/channel-guide/') + .then(r => r.data) + .catch(console.log) + + const $ = cheerio.load(data) + const channels = $( + 'div.ex-chanel-list > div.extvs-inline-chanel > ul > li.extvs-inline-select' + ).toArray() + return channels.map(item => { + const $item = cheerio.load(item) + const [, name] = $item('span') + .text() + .trim() + .match(/\d+\. (.*)/) || [null, null] + return { + lang: 'fj', + site_id: $item('*').data('value'), + name + } + }) + } +} + +function parseTitle($item) { + return $item('td.extvs-table1-programme > div > div > figure > h3').text() +} + +function parseStart($item, date) { + let time = $item('td.extvs-table1-time > span').text().trim() + if (!time) return null + time = `${date.format('YYYY-MM-DD')} ${time}` + + return dayjs.tz(time, 'YYYY-MM-DD H:mm a', 'Pacific/Fiji') +} + +function parseItems(content) { + const data = JSON.parse(content) + if (!data.html) return [] + const $ = cheerio.load(data.html) + + return $('table > tbody > tr').toArray() +} diff --git a/sites/walesi.com.fj/walesi.com.fj.test.js b/sites/walesi.com.fj/walesi.com.fj.test.js index 61d18a173..6c7ee1034 100644 --- a/sites/walesi.com.fj/walesi.com.fj.test.js +++ b/sites/walesi.com.fj/walesi.com.fj.test.js @@ -1,65 +1,65 @@ -const { parser, url, request } = require('./walesi.com.fj.config.js') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2021-11-21', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'fbc-2', - xmltv_id: 'FBCTV.fj' -} - -it('can generate valid url', () => { - expect(url).toBe('https://www.walesi.com.fj/wp-admin/admin-ajax.php') -}) - -it('can generate valid request method', () => { - expect(request.method).toBe('POST') -}) - -it('can generate valid request headers', () => { - expect(request.headers).toMatchObject({ - 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' - }) -}) - -it('can generate valid request data', () => { - const result = request.data({ date, channel }) - expect(result.has('chanel')).toBe(true) - expect(result.has('date')).toBe(true) - expect(result.has('action')).toBe(true) -}) - -it('can parse response', () => { - const content = - '{"html":"\\t\\t\\t\\t\\t\\r\\n\\t\\t\\t\\t\\t\\t\\r\\n\\t\\t\\t\\t\\t\\t\\t\\r\\n\\t\\t\\t\\t\\t\\t\\t\\t\\r\\n\\t\\t\\t\\t\\t\\t\\t\\t\\r\\n\\t\\t\\t\\t\\t\\t\\t\\t\\r\\n\\t\\t\\t\\t\\t\\t\\t\\r\\n\\t\\t\\t\\t\\t\\t\\r\\n\\t\\t\\t\\t\\t\\t\\r\\n\\r\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\r\\n\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\r\\n
    ImageTimeProgramme
    \\r\\n\\t\\t\\t12:00 am\\r\\n\\t\\t
    \\r\\n\\t\\t\\t
    \\r\\n\\t\\t\\t\\t
    \\r\\n\\t\\t\\t\\t\\t
    \\r\\n\\t\\t\\t\\t\\t

    Aljazeera

    \\r\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t
    \\r\\n\\t\\t\\t
    \\r\\n\\t\\t\\t\\t\\t
    \\r\\n\\t
    6:00 am

    Move Fiji

    \\r\\n\\t\\t\\t\\t"}' - const result = parser({ content, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2021-11-20T12:00:00.000Z', - stop: '2021-11-20T18:00:00.000Z', - title: 'Aljazeera' - }, - { - start: '2021-11-20T18:00:00.000Z', - stop: '2021-11-20T18:30:00.000Z', - title: 'Move Fiji' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: '{"html":"

    No matching records found

    "}' - }) - expect(result).toMatchObject([]) -}) +const { parser, url, request } = require('./walesi.com.fj.config.js') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2021-11-21', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'fbc-2', + xmltv_id: 'FBCTV.fj' +} + +it('can generate valid url', () => { + expect(url).toBe('https://www.walesi.com.fj/wp-admin/admin-ajax.php') +}) + +it('can generate valid request method', () => { + expect(request.method).toBe('POST') +}) + +it('can generate valid request headers', () => { + expect(request.headers).toMatchObject({ + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' + }) +}) + +it('can generate valid request data', () => { + const result = request.data({ date, channel }) + expect(result.has('chanel')).toBe(true) + expect(result.has('date')).toBe(true) + expect(result.has('action')).toBe(true) +}) + +it('can parse response', () => { + const content = + '{"html":"\\t\\t\\t\\t\\t\\r\\n\\t\\t\\t\\t\\t\\t\\r\\n\\t\\t\\t\\t\\t\\t\\t\\r\\n\\t\\t\\t\\t\\t\\t\\t\\t\\r\\n\\t\\t\\t\\t\\t\\t\\t\\t\\r\\n\\t\\t\\t\\t\\t\\t\\t\\t\\r\\n\\t\\t\\t\\t\\t\\t\\t\\r\\n\\t\\t\\t\\t\\t\\t\\r\\n\\t\\t\\t\\t\\t\\t\\r\\n\\r\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\r\\n\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\r\\n
    ImageTimeProgramme
    \\r\\n\\t\\t\\t12:00 am\\r\\n\\t\\t
    \\r\\n\\t\\t\\t
    \\r\\n\\t\\t\\t\\t
    \\r\\n\\t\\t\\t\\t\\t
    \\r\\n\\t\\t\\t\\t\\t

    Aljazeera

    \\r\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t
    \\r\\n\\t\\t\\t
    \\r\\n\\t\\t\\t\\t\\t
    \\r\\n\\t
    6:00 am

    Move Fiji

    \\r\\n\\t\\t\\t\\t"}' + const result = parser({ content, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2021-11-20T12:00:00.000Z', + stop: '2021-11-20T18:00:00.000Z', + title: 'Aljazeera' + }, + { + start: '2021-11-20T18:00:00.000Z', + stop: '2021-11-20T18:30:00.000Z', + title: 'Move Fiji' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: '{"html":"

    No matching records found

    "}' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/watch.sportsnet.ca/watch.sportsnet.ca.config.js b/sites/watch.sportsnet.ca/watch.sportsnet.ca.config.js index a3e956e95..b95277eb0 100644 --- a/sites/watch.sportsnet.ca/watch.sportsnet.ca.config.js +++ b/sites/watch.sportsnet.ca/watch.sportsnet.ca.config.js @@ -1,69 +1,69 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') - -dayjs.extend(utc) - -module.exports = { - site: 'watch.sportsnet.ca', - days: 2, - url: function ({ channel, date }) { - return `https://production-cdn.sportsnet.ca/api/schedules?channels=${ - channel.site_id - }&date=${date.format('YYYY-MM-DD')}&duration=24&hour=0` - }, - parser: function ({ content }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - programs.push({ - title: item.item.title, - description: item.item.shortDescription, - image: parseImage(item), - start: parseStart(item), - stop: parseStop(item) - }) - }) - - return programs - }, - async channels() { - const axios = require('axios') - const html = await axios - .get('https://watch.sportsnet.ca/schedule/tvlistings') - .then(r => r.data) - .catch(console.log) - - let [, __data] = html.match(/window\.__data = ([^<]+)<\/script>/) - const func = new Function(`"use strict";return ${__data}`) - const data = func() - - return data.cache.list['678|page_size=24'].list.items.map(item => { - return { - lang: 'en', - site_id: item.id, - name: item.title - } - }) - } -} - -function parseImage(item) { - if (!item.item || !item.item.images) return null - - return item.item.images.tile -} - -function parseStart(item) { - return dayjs.utc(item.startDate) -} - -function parseStop(item) { - return dayjs.utc(item.endDate) -} - -function parseItems(content) { - const data = JSON.parse(content) - if (!Array.isArray(data) || !Array.isArray(data[0].schedules)) return [] - - return data[0].schedules -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') + +dayjs.extend(utc) + +module.exports = { + site: 'watch.sportsnet.ca', + days: 2, + url: function ({ channel, date }) { + return `https://production-cdn.sportsnet.ca/api/schedules?channels=${ + channel.site_id + }&date=${date.format('YYYY-MM-DD')}&duration=24&hour=0` + }, + parser: function ({ content }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + programs.push({ + title: item.item.title, + description: item.item.shortDescription, + image: parseImage(item), + start: parseStart(item), + stop: parseStop(item) + }) + }) + + return programs + }, + async channels() { + const axios = require('axios') + const html = await axios + .get('https://watch.sportsnet.ca/schedule/tvlistings') + .then(r => r.data) + .catch(console.log) + + let [, __data] = html.match(/window\.__data = ([^<]+)<\/script>/) + const func = new Function(`"use strict";return ${__data}`) + const data = func() + + return data.cache.list['678|page_size=24'].list.items.map(item => { + return { + lang: 'en', + site_id: item.id, + name: item.title + } + }) + } +} + +function parseImage(item) { + if (!item.item || !item.item.images) return null + + return item.item.images.tile +} + +function parseStart(item) { + return dayjs.utc(item.startDate) +} + +function parseStop(item) { + return dayjs.utc(item.endDate) +} + +function parseItems(content) { + const data = JSON.parse(content) + if (!Array.isArray(data) || !Array.isArray(data[0].schedules)) return [] + + return data[0].schedules +} diff --git a/sites/watch.sportsnet.ca/watch.sportsnet.ca.test.js b/sites/watch.sportsnet.ca/watch.sportsnet.ca.test.js index 130e27ac4..63fe31914 100644 --- a/sites/watch.sportsnet.ca/watch.sportsnet.ca.test.js +++ b/sites/watch.sportsnet.ca/watch.sportsnet.ca.test.js @@ -1,48 +1,48 @@ -const { parser, url } = require('./watch.sportsnet.ca.config.js') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-03-14', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '24533', - xmltv_id: 'SportsNetOntario.ca' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://production-cdn.sportsnet.ca/api/schedules?channels=24533&date=2022-03-14&duration=24&hour=0' - ) -}) - -it('can parse response', () => { - const content = - '[{"channelId":"24533","startDate":"2022-03-14T00:00:00.000Z","endDate":"2022-03-15T00:00:00.000Z","schedules":[{"channelId":"24533","customFields":{"ContentId":"EP029977175139","Checksum":"2DA90E7E66B9C311F98B186B89C50FAD"},"endDate":"2022-03-14T02:30:00Z","id":"826cb731-9de4-4cf3-bcca-d548d8a33d16","startDate":"2022-03-14T00:00:00Z","item":{"id":"34a028b0-eacf-40f3-9bf9-62ee3330df1b","type":"program","title":"Calgary Flames at Colorado Avalanche","shortDescription":"Johnny Gaudreau and the Flames pay a visit to the Avalanche. Calgary won 4-3 in overtime March 5.","path":"/channel/24533","duration":9000,"images":{"tile":"https://production-static.sportsnet-static.com/shain/v1/dataservice/ResizeImage/$value?Format=\'jpg\'&Quality=85&ImageId=\'785305\'&EntityType=\'LinearSchedule\'&EntityId=\'826cb731-9de4-4cf3-bcca-d548d8a33d16\'&Width=3840&Height=2160","wallpaper":"https://production-static.sportsnet-static.com/shain/v1/dataservice/ResizeImage/$value?Format=\'jpg\'&Quality=85&ImageId=\'785311\'&EntityType=\'LinearSchedule\'&EntityId=\'826cb731-9de4-4cf3-bcca-d548d8a33d16\'&Width=3840&Height=2160"}}}]}]' - const result = parser({ content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2022-03-14T00:00:00.000Z', - stop: '2022-03-14T02:30:00.000Z', - title: 'Calgary Flames at Colorado Avalanche', - description: - 'Johnny Gaudreau and the Flames pay a visit to the Avalanche. Calgary won 4-3 in overtime March 5.', - image: - "https://production-static.sportsnet-static.com/shain/v1/dataservice/ResizeImage/$value?Format='jpg'&Quality=85&ImageId='785305'&EntityType='LinearSchedule'&EntityId='826cb731-9de4-4cf3-bcca-d548d8a33d16'&Width=3840&Height=2160" - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: - '[{"channelId":"245321","startDate":"2022-03-14T00:00:00.000Z","endDate":"2022-03-15T00:00:00.000Z","schedules":[]}]' - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./watch.sportsnet.ca.config.js') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-03-14', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '24533', + xmltv_id: 'SportsNetOntario.ca' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://production-cdn.sportsnet.ca/api/schedules?channels=24533&date=2022-03-14&duration=24&hour=0' + ) +}) + +it('can parse response', () => { + const content = + '[{"channelId":"24533","startDate":"2022-03-14T00:00:00.000Z","endDate":"2022-03-15T00:00:00.000Z","schedules":[{"channelId":"24533","customFields":{"ContentId":"EP029977175139","Checksum":"2DA90E7E66B9C311F98B186B89C50FAD"},"endDate":"2022-03-14T02:30:00Z","id":"826cb731-9de4-4cf3-bcca-d548d8a33d16","startDate":"2022-03-14T00:00:00Z","item":{"id":"34a028b0-eacf-40f3-9bf9-62ee3330df1b","type":"program","title":"Calgary Flames at Colorado Avalanche","shortDescription":"Johnny Gaudreau and the Flames pay a visit to the Avalanche. Calgary won 4-3 in overtime March 5.","path":"/channel/24533","duration":9000,"images":{"tile":"https://production-static.sportsnet-static.com/shain/v1/dataservice/ResizeImage/$value?Format=\'jpg\'&Quality=85&ImageId=\'785305\'&EntityType=\'LinearSchedule\'&EntityId=\'826cb731-9de4-4cf3-bcca-d548d8a33d16\'&Width=3840&Height=2160","wallpaper":"https://production-static.sportsnet-static.com/shain/v1/dataservice/ResizeImage/$value?Format=\'jpg\'&Quality=85&ImageId=\'785311\'&EntityType=\'LinearSchedule\'&EntityId=\'826cb731-9de4-4cf3-bcca-d548d8a33d16\'&Width=3840&Height=2160"}}}]}]' + const result = parser({ content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2022-03-14T00:00:00.000Z', + stop: '2022-03-14T02:30:00.000Z', + title: 'Calgary Flames at Colorado Avalanche', + description: + 'Johnny Gaudreau and the Flames pay a visit to the Avalanche. Calgary won 4-3 in overtime March 5.', + image: + "https://production-static.sportsnet-static.com/shain/v1/dataservice/ResizeImage/$value?Format='jpg'&Quality=85&ImageId='785305'&EntityType='LinearSchedule'&EntityId='826cb731-9de4-4cf3-bcca-d548d8a33d16'&Width=3840&Height=2160" + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: + '[{"channelId":"245321","startDate":"2022-03-14T00:00:00.000Z","endDate":"2022-03-15T00:00:00.000Z","schedules":[]}]' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/watchyour.tv/watchyour.tv.config.js b/sites/watchyour.tv/watchyour.tv.config.js index 893c8a0b1..4236d5c3d 100644 --- a/sites/watchyour.tv/watchyour.tv.config.js +++ b/sites/watchyour.tv/watchyour.tv.config.js @@ -1,56 +1,56 @@ -const dayjs = require('dayjs') -const axios = require('axios') - -module.exports = { - site: 'watchyour.tv', - days: 2, - url: 'https://www.watchyour.tv/guide.json', - request: { - cache: { - ttl: 60 * 60 * 1000 // 1 hour - } - }, - parser: function ({ content, date, channel }) { - let programs = [] - const items = parseItems(content, date, channel) - items.forEach(item => { - const start = parseStart(item) - const stop = start.add(parseInt(item.duration), 'm') - programs.push({ - title: item.name, - icon: item.icon, - category: item.category, - start, - stop - }) - }) - - return programs - }, - async channels() { - const data = await axios - .get('https://www.watchyour.tv/guide.json') - .then(r => r.data) - .catch(console.log) - - return data.map(item => ({ - lang: 'en', - site_id: item.id, - name: item.name - })) - } -} - -function parseStart(item) { - return dayjs.unix(parseInt(item.tms)) -} - -function parseItems(content, date, channel) { - if (!content) return [] - const data = JSON.parse(content) - if (!Array.isArray(data)) return [] - const channelData = data.find(i => i.id == channel.site_id) - if (!channelData || !Array.isArray(channelData.shows)) return [] - - return channelData.shows.filter(i => i.start_day === date.format('YYYY-MM-DD')) -} +const dayjs = require('dayjs') +const axios = require('axios') + +module.exports = { + site: 'watchyour.tv', + days: 2, + url: 'https://www.watchyour.tv/guide.json', + request: { + cache: { + ttl: 60 * 60 * 1000 // 1 hour + } + }, + parser: function ({ content, date, channel }) { + let programs = [] + const items = parseItems(content, date, channel) + items.forEach(item => { + const start = parseStart(item) + const stop = start.add(parseInt(item.duration), 'm') + programs.push({ + title: item.name, + icon: item.icon, + category: item.category, + start, + stop + }) + }) + + return programs + }, + async channels() { + const data = await axios + .get('https://www.watchyour.tv/guide.json') + .then(r => r.data) + .catch(console.log) + + return data.map(item => ({ + lang: 'en', + site_id: item.id, + name: item.name + })) + } +} + +function parseStart(item) { + return dayjs.unix(parseInt(item.tms)) +} + +function parseItems(content, date, channel) { + if (!content) return [] + const data = JSON.parse(content) + if (!Array.isArray(data)) return [] + const channelData = data.find(i => i.id == channel.site_id) + if (!channelData || !Array.isArray(channelData.shows)) return [] + + return channelData.shows.filter(i => i.start_day === date.format('YYYY-MM-DD')) +} diff --git a/sites/watchyour.tv/watchyour.tv.test.js b/sites/watchyour.tv/watchyour.tv.test.js index ef1dd98b2..79fb077c6 100644 --- a/sites/watchyour.tv/watchyour.tv.test.js +++ b/sites/watchyour.tv/watchyour.tv.test.js @@ -1,45 +1,45 @@ -const { parser, url } = require('./watchyour.tv.config.js') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-10-03', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '735', - xmltv_id: 'TVSClassicSports.us' -} - -it('can generate valid url', () => { - expect(url).toBe('https://www.watchyour.tv/guide.json') -}) - -it('can parse response', () => { - const content = - '[{"name":"TVS Classic Sports","icon":"https://www.watchyour.tv/epg/channellogos/tvs-classic-sports.png","language":"English","id":"735","shows":[{"name":"1979 WVU vs Penn State","category":"Sports","start_day":"2022-10-03","start":"04:00:00","end_day":"2022-10-03","end":"06:00:45","duration":"121","url":"http://rpn1.bozztv.com/36bay2/gusa-tvs/index-1664769600-7245.m3u8?token=f7410a9414f61579dced17ac1bbdb971","icon":"https://example.com/icon.png","timezone":"+0000","tms":"1664769600"},{"name":"1958 NCAA University of Kentucky vs Seattle U","category":"Sports","start_day":"2022-10-04","start":"00:58:50","end_day":"2022-10-04","end":"01:44:11","duration":"46","url":"http://rpn1.bozztv.com/36bay2/gusa-tvs/index.m3u8?token=93e7b201f544c87296076b73f9d880ae","icon":"","timezone":"+0000","tms":"1664845130"}]}]' - const result = parser({ content, date, channel }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2022-10-03T04:00:00.000Z', - stop: '2022-10-03T06:01:00.000Z', - title: '1979 WVU vs Penn State', - icon: 'https://example.com/icon.png', - category: 'Sports' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: '', - date, - channel - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./watchyour.tv.config.js') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-10-03', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '735', + xmltv_id: 'TVSClassicSports.us' +} + +it('can generate valid url', () => { + expect(url).toBe('https://www.watchyour.tv/guide.json') +}) + +it('can parse response', () => { + const content = + '[{"name":"TVS Classic Sports","icon":"https://www.watchyour.tv/epg/channellogos/tvs-classic-sports.png","language":"English","id":"735","shows":[{"name":"1979 WVU vs Penn State","category":"Sports","start_day":"2022-10-03","start":"04:00:00","end_day":"2022-10-03","end":"06:00:45","duration":"121","url":"http://rpn1.bozztv.com/36bay2/gusa-tvs/index-1664769600-7245.m3u8?token=f7410a9414f61579dced17ac1bbdb971","icon":"https://example.com/icon.png","timezone":"+0000","tms":"1664769600"},{"name":"1958 NCAA University of Kentucky vs Seattle U","category":"Sports","start_day":"2022-10-04","start":"00:58:50","end_day":"2022-10-04","end":"01:44:11","duration":"46","url":"http://rpn1.bozztv.com/36bay2/gusa-tvs/index.m3u8?token=93e7b201f544c87296076b73f9d880ae","icon":"","timezone":"+0000","tms":"1664845130"}]}]' + const result = parser({ content, date, channel }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2022-10-03T04:00:00.000Z', + stop: '2022-10-03T06:01:00.000Z', + title: '1979 WVU vs Penn State', + icon: 'https://example.com/icon.png', + category: 'Sports' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: '', + date, + channel + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/wavve.com/wavve.com.config.js b/sites/wavve.com/wavve.com.config.js index 59755129f..347852ed6 100644 --- a/sites/wavve.com/wavve.com.config.js +++ b/sites/wavve.com/wavve.com.config.js @@ -1,62 +1,62 @@ -const axios = require('axios') -const { DateTime } = require('luxon') - -module.exports = { - site: 'wavve.com', - days: 2, - url: function ({ channel, date }) { - return `https://apis.pooq.co.kr/live/epgs/channels/${ - channel.site_id - }?startdatetime=${date.format('YYYY-MM-DD')}%2000%3A00&enddatetime=${date - .add(1, 'd') - .format('YYYY-MM-DD')}%2000%3A00&apikey=E5F3E0D30947AA5440556471321BB6D9&limit=500` - }, - parser: function ({ content }) { - let programs = [] - const items = parseItems(content) - items.forEach(item => { - programs.push({ - title: item.title, - start: parseStart(item), - stop: parseStop(item) - }) - }) - - return programs - }, - async channels() { - const channels = [] - - const data = await axios - .get( - 'https://apis.pooq.co.kr/live/epgs?enddatetime=2022-04-17%2019%3A00&genre=all&limit=500&startdatetime=2022-04-17%2016%3A00&apikey=E5F3E0D30947AA5440556471321BB6D9' - ) - .then(r => r.data) - .catch(console.log) - - data.list.forEach(i => { - channels.push({ - name: i.channelname, - site_id: i.channelid, - lang: 'ko' - }) - }) - - return channels - } -} - -function parseStart(item) { - return DateTime.fromFormat(item.starttime, 'yyyy-MM-dd HH:mm', { zone: 'Asia/Seoul' }).toUTC() -} - -function parseStop(item) { - return DateTime.fromFormat(item.endtime, 'yyyy-MM-dd HH:mm', { zone: 'Asia/Seoul' }).toUTC() -} - -function parseItems(content) { - const data = JSON.parse(content) - if (!data || !Array.isArray(data.list)) return [] - - return data.list -} +const axios = require('axios') +const { DateTime } = require('luxon') + +module.exports = { + site: 'wavve.com', + days: 2, + url: function ({ channel, date }) { + return `https://apis.pooq.co.kr/live/epgs/channels/${ + channel.site_id + }?startdatetime=${date.format('YYYY-MM-DD')}%2000%3A00&enddatetime=${date + .add(1, 'd') + .format('YYYY-MM-DD')}%2000%3A00&apikey=E5F3E0D30947AA5440556471321BB6D9&limit=500` + }, + parser: function ({ content }) { + let programs = [] + const items = parseItems(content) + items.forEach(item => { + programs.push({ + title: item.title, + start: parseStart(item), + stop: parseStop(item) + }) + }) + + return programs + }, + async channels() { + const channels = [] + + const data = await axios + .get( + 'https://apis.pooq.co.kr/live/epgs?enddatetime=2022-04-17%2019%3A00&genre=all&limit=500&startdatetime=2022-04-17%2016%3A00&apikey=E5F3E0D30947AA5440556471321BB6D9' + ) + .then(r => r.data) + .catch(console.log) + + data.list.forEach(i => { + channels.push({ + name: i.channelname, + site_id: i.channelid, + lang: 'ko' + }) + }) + + return channels + } +} + +function parseStart(item) { + return DateTime.fromFormat(item.starttime, 'yyyy-MM-dd HH:mm', { zone: 'Asia/Seoul' }).toUTC() +} + +function parseStop(item) { + return DateTime.fromFormat(item.endtime, 'yyyy-MM-dd HH:mm', { zone: 'Asia/Seoul' }).toUTC() +} + +function parseItems(content) { + const data = JSON.parse(content) + if (!data || !Array.isArray(data.list)) return [] + + return data.list +} diff --git a/sites/wavve.com/wavve.com.test.js b/sites/wavve.com/wavve.com.test.js index ce23e5377..7dd9b5453 100644 --- a/sites/wavve.com/wavve.com.test.js +++ b/sites/wavve.com/wavve.com.test.js @@ -1,43 +1,43 @@ -const { parser, url } = require('./wavve.com.config.js') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2022-04-17', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'K01', - xmltv_id: 'KBS1TV.kr' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://apis.pooq.co.kr/live/epgs/channels/K01?startdatetime=2022-04-17%2000%3A00&enddatetime=2022-04-18%2000%3A00&apikey=E5F3E0D30947AA5440556471321BB6D9&limit=500' - ) -}) - -it('can parse response', () => { - const content = - '{"pagecount":"37","count":"37","list":[{"cpid":"C3","channelid":"K01","channelname":"KBS 1TV","channelimage":"img.pooq.co.kr/BMS/Channelimage30/image/KBS-1TV-1.jpg","scheduleid":"K01_20220416223000","programid":"","title":"특파원 보고 세계는 지금","image":"wchimg.wavve.com/live/thumbnail/K01.jpg","starttime":"2022-04-16 22:30","endtime":"2022-04-16 23:15","timemachine":"Y","license":"y","livemarks":[],"targetage":"0","tvimage":"img.pooq.co.kr/BMS/Channelimage30/image/KBS 1TV-2.png","ispreorder":"n","preorderlink":"n","alarm":"n"}]}' - const result = parser({ content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2022-04-16T13:30:00.000Z', - stop: '2022-04-16T14:15:00.000Z', - title: '특파원 보고 세계는 지금' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: '{"pagecount":"0","count":"0","list":[]}' - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./wavve.com.config.js') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2022-04-17', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'K01', + xmltv_id: 'KBS1TV.kr' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://apis.pooq.co.kr/live/epgs/channels/K01?startdatetime=2022-04-17%2000%3A00&enddatetime=2022-04-18%2000%3A00&apikey=E5F3E0D30947AA5440556471321BB6D9&limit=500' + ) +}) + +it('can parse response', () => { + const content = + '{"pagecount":"37","count":"37","list":[{"cpid":"C3","channelid":"K01","channelname":"KBS 1TV","channelimage":"img.pooq.co.kr/BMS/Channelimage30/image/KBS-1TV-1.jpg","scheduleid":"K01_20220416223000","programid":"","title":"특파원 보고 세계는 지금","image":"wchimg.wavve.com/live/thumbnail/K01.jpg","starttime":"2022-04-16 22:30","endtime":"2022-04-16 23:15","timemachine":"Y","license":"y","livemarks":[],"targetage":"0","tvimage":"img.pooq.co.kr/BMS/Channelimage30/image/KBS 1TV-2.png","ispreorder":"n","preorderlink":"n","alarm":"n"}]}' + const result = parser({ content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2022-04-16T13:30:00.000Z', + stop: '2022-04-16T14:15:00.000Z', + title: '특파원 보고 세계는 지금' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: '{"pagecount":"0","count":"0","list":[]}' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/web.magentatv.de/web.magentatv.de.test.js b/sites/web.magentatv.de/web.magentatv.de.test.js index 71f566728..62efef9a1 100644 --- a/sites/web.magentatv.de/web.magentatv.de.test.js +++ b/sites/web.magentatv.de/web.magentatv.de.test.js @@ -1,138 +1,138 @@ -const { parser, url, request } = require('./web.magentatv.de.config.js') -const fs = require('fs') -const path = require('path') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -const date = dayjs.utc('2022-03-09', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '255', - xmltv_id: '13thStreet.de' -} - -axios.request.mockImplementation(req => { - const result = {} - if (req.url === 'https://api.prod.sngtv.magentatv.de/EPG/JSON/Authenticate') { - Object.assign(result, { - headers: { - 'set-cookie': [ - 'JSESSIONID=2147EBA9C59BCDC33822CFD2764E5C0B; Path=/EPG; HttpOnly; SameSite=None; Secure', - 'JSESSIONID=2147EBA9C59BCDC33822CFD2764E5C0B; Path=/EPG/; HttpOnly; SameSite=None; Secure', - 'CSESSIONID=1CF187ABCA12ED1B01ADF84C691048ED; Path=/EPG/; Secure; HttpOnly; SameSite=None', - 'CSRFSESSION=ea2329ba213271192bffd77c2fa276086a8e828c1a4ee379; Path=/EPG/; SameSite=None; Secure' - ] - }, - data: { - csrfToken: '6f678415702493d2c28813747c413aa05c87d8f87ecf05fe' - } - }) - } - - return Promise.resolve(result) -}) - -it('can generate valid url', () => { - expect(url).toBe('https://api.prod.sngtv.magentatv.de/EPG/JSON/PlayBillList') -}) - -it('can generate valid request method', () => { - expect(request.method).toBe('POST') -}) - -it('can generate valid request headers', async () => { - const headers = await request.headers() - - expect(headers).toHaveProperty('Cookie') - expect(headers).toHaveProperty('X_CSRFTOKEN') - - expect(headers.Cookie).toMatch(/JSESSIONID=[\dA-F]+;/i) - expect(headers.Cookie).toMatch(/CSESSIONID=[\dA-F]+;/i) - expect(headers.Cookie).toMatch(/CSRFSESSION=[\dA-F]+;/i) - expect(headers.X_CSRFTOKEN).toMatch(/[\dA-F]/i) -}) - -it('can generate valid request data', () => { - expect(request.data({ channel, date })).toMatchObject({ - count: -1, - isFillProgram: 1, - offset: 0, - properties: [ - { - include: - 'endtime,genres,id,name,starttime,channelid,pictures,introduce,subName,seasonNum,subNum,cast,country,producedate,externalIds', - name: 'playbill' - } - ], - type: 2, - begintime: '20220309000000', - channelid: '255', - endtime: '20220310000000' - }) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') - const result = parser({ content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2023-10-23T23:58:55.000Z', - stop: '2023-10-24T00:11:05.000Z', - title: 'Twenty Foot Plus', - description: - 'Die besten Big-Wave-Surfer werden bei ihrer Suche nach der nächsten großen Welle begleitet.', - image: - 'http://ngiss.t-online.de/cm1s/media/gracenote/2/4/p24832950_e_h9_aa_2023-06-22T10_12_01.jpg', - category: ['Sport'] - }, - { - start: '2024-11-05T15:37:03.000Z', - stop: '2024-11-05T16:03:48.000Z', - title: 'The Big Bang Theory', - sub_title: 'Tritte unter dem Tisch', - description: - 'Amy arbeitet für eine Weile in Sheldons Universität, er freut sich darüber, doch sie warnt ihn, dass sie sich jetzt häufiger zu Gesicht bekommen. Als Leonard, Sheldon, Raj und Howard zusammen sitzen, diskutieren sie darüber. Sheldon lässt auf sich einreden und informiert Amy, dass er ein Problem mit ihr auf seiner Arbeit hat. Sie ist enttäuscht, während Bernadette mit Howard darüber spricht, warum er auf Sheldon eingeredet hat.', - season: '7', - episode: '5', - image: - 'http://ngiss.t-online.de/cm1s/media/gracenote/1/0/p10262968_e_h9_ah_2021-10-20T07_16_16.jpg', - category: ['Sitcom'], - directors: ['Mark Cendrowski'], - producers: ['Chuck Lorre', 'Bill Prady', 'Steven Molaro'], - adapters: [ - 'Steven Molaro', - 'Steve Holland', - 'Maria Ferrari', - 'Chuck Lorre', - 'Eric Kaplan', - 'Jim Reynolds' - ], - country: 'US', - date: '2013-01-01', - urls: [ - { - system: 'imdb', - value: 'https://www.imdb.com/title/tt0898266' - } - ] - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - content: '{"counttotal":"0"}' - }) - expect(result).toMatchObject([]) -}) +const { parser, url, request } = require('./web.magentatv.de.config.js') +const fs = require('fs') +const path = require('path') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.utc('2022-03-09', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '255', + xmltv_id: '13thStreet.de' +} + +axios.request.mockImplementation(req => { + const result = {} + if (req.url === 'https://api.prod.sngtv.magentatv.de/EPG/JSON/Authenticate') { + Object.assign(result, { + headers: { + 'set-cookie': [ + 'JSESSIONID=2147EBA9C59BCDC33822CFD2764E5C0B; Path=/EPG; HttpOnly; SameSite=None; Secure', + 'JSESSIONID=2147EBA9C59BCDC33822CFD2764E5C0B; Path=/EPG/; HttpOnly; SameSite=None; Secure', + 'CSESSIONID=1CF187ABCA12ED1B01ADF84C691048ED; Path=/EPG/; Secure; HttpOnly; SameSite=None', + 'CSRFSESSION=ea2329ba213271192bffd77c2fa276086a8e828c1a4ee379; Path=/EPG/; SameSite=None; Secure' + ] + }, + data: { + csrfToken: '6f678415702493d2c28813747c413aa05c87d8f87ecf05fe' + } + }) + } + + return Promise.resolve(result) +}) + +it('can generate valid url', () => { + expect(url).toBe('https://api.prod.sngtv.magentatv.de/EPG/JSON/PlayBillList') +}) + +it('can generate valid request method', () => { + expect(request.method).toBe('POST') +}) + +it('can generate valid request headers', async () => { + const headers = await request.headers() + + expect(headers).toHaveProperty('Cookie') + expect(headers).toHaveProperty('X_CSRFTOKEN') + + expect(headers.Cookie).toMatch(/JSESSIONID=[\dA-F]+;/i) + expect(headers.Cookie).toMatch(/CSESSIONID=[\dA-F]+;/i) + expect(headers.Cookie).toMatch(/CSRFSESSION=[\dA-F]+;/i) + expect(headers.X_CSRFTOKEN).toMatch(/[\dA-F]/i) +}) + +it('can generate valid request data', () => { + expect(request.data({ channel, date })).toMatchObject({ + count: -1, + isFillProgram: 1, + offset: 0, + properties: [ + { + include: + 'endtime,genres,id,name,starttime,channelid,pictures,introduce,subName,seasonNum,subNum,cast,country,producedate,externalIds', + name: 'playbill' + } + ], + type: 2, + begintime: '20220309000000', + channelid: '255', + endtime: '20220310000000' + }) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') + const result = parser({ content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2023-10-23T23:58:55.000Z', + stop: '2023-10-24T00:11:05.000Z', + title: 'Twenty Foot Plus', + description: + 'Die besten Big-Wave-Surfer werden bei ihrer Suche nach der nächsten großen Welle begleitet.', + image: + 'http://ngiss.t-online.de/cm1s/media/gracenote/2/4/p24832950_e_h9_aa_2023-06-22T10_12_01.jpg', + category: ['Sport'] + }, + { + start: '2024-11-05T15:37:03.000Z', + stop: '2024-11-05T16:03:48.000Z', + title: 'The Big Bang Theory', + sub_title: 'Tritte unter dem Tisch', + description: + 'Amy arbeitet für eine Weile in Sheldons Universität, er freut sich darüber, doch sie warnt ihn, dass sie sich jetzt häufiger zu Gesicht bekommen. Als Leonard, Sheldon, Raj und Howard zusammen sitzen, diskutieren sie darüber. Sheldon lässt auf sich einreden und informiert Amy, dass er ein Problem mit ihr auf seiner Arbeit hat. Sie ist enttäuscht, während Bernadette mit Howard darüber spricht, warum er auf Sheldon eingeredet hat.', + season: '7', + episode: '5', + image: + 'http://ngiss.t-online.de/cm1s/media/gracenote/1/0/p10262968_e_h9_ah_2021-10-20T07_16_16.jpg', + category: ['Sitcom'], + directors: ['Mark Cendrowski'], + producers: ['Chuck Lorre', 'Bill Prady', 'Steven Molaro'], + adapters: [ + 'Steven Molaro', + 'Steve Holland', + 'Maria Ferrari', + 'Chuck Lorre', + 'Eric Kaplan', + 'Jim Reynolds' + ], + country: 'US', + date: '2013-01-01', + urls: [ + { + system: 'imdb', + value: 'https://www.imdb.com/title/tt0898266' + } + ] + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + content: '{"counttotal":"0"}' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/webtv.delta.nl/webtv.delta.nl.config.js b/sites/webtv.delta.nl/webtv.delta.nl.config.js index 961a3706a..16f007535 100644 --- a/sites/webtv.delta.nl/webtv.delta.nl.config.js +++ b/sites/webtv.delta.nl/webtv.delta.nl.config.js @@ -1,70 +1,70 @@ -const axios = require('axios') -const dayjs = require('dayjs') - -module.exports = { - site: 'webtv.delta.nl', - days: 2, - url: function ({ channel, date }) { - return `https://clientapi.tv.delta.nl/guide/channels/list?start=${date.unix()}&end=${date - .add(1, 'd') - .unix()}&includeDetails=true&channels=${channel.site_id}` - }, - async parser({ content, channel }) { - let programs = [] - const items = parseItems(content, channel) - for (let item of items) { - const details = await loadProgramDetails(item) - programs.push({ - title: item.title, - image: item.images.thumbnail.url, - description: details.description, - start: parseStart(item).toJSON(), - stop: parseStop(item).toJSON() - }) - } - - return programs - }, - async channels() { - const items = await axios - .get('https://clientapi.tv.delta.nl/channels/list') - .then(r => r.data) - .catch(console.log) - - return items - .filter(i => i.type === 'TV') - .map(item => { - return { - lang: 'nl', - site_id: item['ID'], - name: item.name - } - }) - } -} - -async function loadProgramDetails(item) { - if (!item.ID) return {} - const url = `https://clientapi.tv.delta.nl/guide/4/details/${item.ID}?X-Response-Version=4.5` - const data = await axios - .get(url) - .then(r => r.data) - .catch(console.log) - - return data || {} -} - -function parseStart(item) { - return dayjs.unix(item.start) -} - -function parseStop(item) { - return dayjs.unix(item.end) -} - -function parseItems(content, channel) { - const data = JSON.parse(content) - if (!data) return [] - - return data[channel.site_id] || [] -} +const axios = require('axios') +const dayjs = require('dayjs') + +module.exports = { + site: 'webtv.delta.nl', + days: 2, + url: function ({ channel, date }) { + return `https://clientapi.tv.delta.nl/guide/channels/list?start=${date.unix()}&end=${date + .add(1, 'd') + .unix()}&includeDetails=true&channels=${channel.site_id}` + }, + async parser({ content, channel }) { + let programs = [] + const items = parseItems(content, channel) + for (let item of items) { + const details = await loadProgramDetails(item) + programs.push({ + title: item.title, + image: item.images.thumbnail.url, + description: details.description, + start: parseStart(item).toJSON(), + stop: parseStop(item).toJSON() + }) + } + + return programs + }, + async channels() { + const items = await axios + .get('https://clientapi.tv.delta.nl/channels/list') + .then(r => r.data) + .catch(console.log) + + return items + .filter(i => i.type === 'TV') + .map(item => { + return { + lang: 'nl', + site_id: item['ID'], + name: item.name + } + }) + } +} + +async function loadProgramDetails(item) { + if (!item.ID) return {} + const url = `https://clientapi.tv.delta.nl/guide/4/details/${item.ID}?X-Response-Version=4.5` + const data = await axios + .get(url) + .then(r => r.data) + .catch(console.log) + + return data || {} +} + +function parseStart(item) { + return dayjs.unix(item.start) +} + +function parseStop(item) { + return dayjs.unix(item.end) +} + +function parseItems(content, channel) { + const data = JSON.parse(content) + if (!data) return [] + + return data[channel.site_id] || [] +} diff --git a/sites/webtv.delta.nl/webtv.delta.nl.test.js b/sites/webtv.delta.nl/webtv.delta.nl.test.js index 1b95868cb..15c405823 100644 --- a/sites/webtv.delta.nl/webtv.delta.nl.test.js +++ b/sites/webtv.delta.nl/webtv.delta.nl.test.js @@ -1,67 +1,67 @@ -const { parser, url } = require('./webtv.delta.nl.config.js') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -const date = dayjs.utc('2021-11-12', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '1', - xmltv_id: 'NPO1.nl' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://clientapi.tv.delta.nl/guide/channels/list?start=1636675200&end=1636761600&includeDetails=true&channels=1' - ) -}) - -it('can parse response', done => { - axios.get.mockImplementation(() => - Promise.resolve({ - data: JSON.parse( - '{"ID":"P~945cb98e-3d19-11ec-8456-953363d7a344","seriesID":"S~d37c4626-b691-11ea-ba69-255835135f02","channelID":"1","start":1636674960,"end":1636676520,"catchupAvailableUntil":1637279760,"title":"Eigen Huis & Tuin: Lekker Leven","description":"Nederlands lifestyleprogramma uit 2022 (ook in HD) met dagelijkse inspiratie voor een lekker leven in en om het huis.\\nPresentatrice Froukje de Both, kok Hugo Kennis en een team van experts, onder wie tuinman Tom Groot, geven praktische tips op het gebied van wonen, lifestyle, tuinieren en koken. Daarmee kun je zelf direct aan de slag om je leven leuker én gezonder te maken. Afl. 15 van seizoen 4.","images":{"thumbnail":{"url":"https://cdn.gvidi.tv/img/booxmedia/b291/561946.jpg"}},"additionalInformation":{"metadataID":"M~c512c206-95e5-11ec-87d8-494f70130311","externalMetadataID":"E~RTL4-89d99356_6599_4b65_a7a0_a93f39019645"},"parentalGuidance":{"kijkwijzer":["AL"]},"restrictions":{"startoverDisabled":false,"catchupDisabled":false,"recordingDisabled":false},"isFiller":false}' - ) - }) - ) - - const content = - '{"1":[{"ID":"P~945cb98e-3d19-11ec-8456-953363d7a344","seriesID":"S~d37c4626-b691-11ea-ba69-255835135f02","channelID":"1","start":1636674960,"end":1636676520,"catchupAvailableUntil":1637279760,"title":"NOS Journaal","images":{"thumbnail":{"url":"https://cdn.gvidi.tv/img/booxmedia/e19c/static/NOS%20Journaal5.jpg"}},"additionalInformation":{"metadataID":"M~944f3c6e-3d19-11ec-9faf-2735f2e98d2a","externalMetadataID":"E~TV01-2026117420668"},"parentalGuidance":{"kijkwijzer":["AL"]},"restrictions":{"startoverDisabled":false,"catchupDisabled":false,"recordingDisabled":false},"isFiller":false}]}' - - parser({ date, channel, content }) - .then(result => { - expect(result).toMatchObject([ - { - start: '2021-11-11T23:56:00.000Z', - stop: '2021-11-12T00:22:00.000Z', - title: 'NOS Journaal', - description: - 'Nederlands lifestyleprogramma uit 2022 (ook in HD) met dagelijkse inspiratie voor een lekker leven in en om het huis.\nPresentatrice Froukje de Both, kok Hugo Kennis en een team van experts, onder wie tuinman Tom Groot, geven praktische tips op het gebied van wonen, lifestyle, tuinieren en koken. Daarmee kun je zelf direct aan de slag om je leven leuker én gezonder te maken. Afl. 15 van seizoen 4.', - image: 'https://cdn.gvidi.tv/img/booxmedia/e19c/static/NOS%20Journaal5.jpg' - } - ]) - done() - }) - .catch(error => { - done(error) - }) -}) - -it('can handle empty guide', done => { - parser({ - date, - channel, - content: '{"code":500,"message":"Error retrieving guide"}' - }) - .then(result => { - expect(result).toMatchObject([]) - done() - }) - .catch(error => { - done(error) - }) -}) +const { parser, url } = require('./webtv.delta.nl.config.js') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.utc('2021-11-12', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '1', + xmltv_id: 'NPO1.nl' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://clientapi.tv.delta.nl/guide/channels/list?start=1636675200&end=1636761600&includeDetails=true&channels=1' + ) +}) + +it('can parse response', done => { + axios.get.mockImplementation(() => + Promise.resolve({ + data: JSON.parse( + '{"ID":"P~945cb98e-3d19-11ec-8456-953363d7a344","seriesID":"S~d37c4626-b691-11ea-ba69-255835135f02","channelID":"1","start":1636674960,"end":1636676520,"catchupAvailableUntil":1637279760,"title":"Eigen Huis & Tuin: Lekker Leven","description":"Nederlands lifestyleprogramma uit 2022 (ook in HD) met dagelijkse inspiratie voor een lekker leven in en om het huis.\\nPresentatrice Froukje de Both, kok Hugo Kennis en een team van experts, onder wie tuinman Tom Groot, geven praktische tips op het gebied van wonen, lifestyle, tuinieren en koken. Daarmee kun je zelf direct aan de slag om je leven leuker én gezonder te maken. Afl. 15 van seizoen 4.","images":{"thumbnail":{"url":"https://cdn.gvidi.tv/img/booxmedia/b291/561946.jpg"}},"additionalInformation":{"metadataID":"M~c512c206-95e5-11ec-87d8-494f70130311","externalMetadataID":"E~RTL4-89d99356_6599_4b65_a7a0_a93f39019645"},"parentalGuidance":{"kijkwijzer":["AL"]},"restrictions":{"startoverDisabled":false,"catchupDisabled":false,"recordingDisabled":false},"isFiller":false}' + ) + }) + ) + + const content = + '{"1":[{"ID":"P~945cb98e-3d19-11ec-8456-953363d7a344","seriesID":"S~d37c4626-b691-11ea-ba69-255835135f02","channelID":"1","start":1636674960,"end":1636676520,"catchupAvailableUntil":1637279760,"title":"NOS Journaal","images":{"thumbnail":{"url":"https://cdn.gvidi.tv/img/booxmedia/e19c/static/NOS%20Journaal5.jpg"}},"additionalInformation":{"metadataID":"M~944f3c6e-3d19-11ec-9faf-2735f2e98d2a","externalMetadataID":"E~TV01-2026117420668"},"parentalGuidance":{"kijkwijzer":["AL"]},"restrictions":{"startoverDisabled":false,"catchupDisabled":false,"recordingDisabled":false},"isFiller":false}]}' + + parser({ date, channel, content }) + .then(result => { + expect(result).toMatchObject([ + { + start: '2021-11-11T23:56:00.000Z', + stop: '2021-11-12T00:22:00.000Z', + title: 'NOS Journaal', + description: + 'Nederlands lifestyleprogramma uit 2022 (ook in HD) met dagelijkse inspiratie voor een lekker leven in en om het huis.\nPresentatrice Froukje de Both, kok Hugo Kennis en een team van experts, onder wie tuinman Tom Groot, geven praktische tips op het gebied van wonen, lifestyle, tuinieren en koken. Daarmee kun je zelf direct aan de slag om je leven leuker én gezonder te maken. Afl. 15 van seizoen 4.', + image: 'https://cdn.gvidi.tv/img/booxmedia/e19c/static/NOS%20Journaal5.jpg' + } + ]) + done() + }) + .catch(error => { + done(error) + }) +}) + +it('can handle empty guide', done => { + parser({ + date, + channel, + content: '{"code":500,"message":"Error retrieving guide"}' + }) + .then(result => { + expect(result).toMatchObject([]) + done() + }) + .catch(error => { + done(error) + }) +}) diff --git a/sites/winplay.co/winplay.co.config.js b/sites/winplay.co/winplay.co.config.js index 831fecc73..738ddb03f 100644 --- a/sites/winplay.co/winplay.co.config.js +++ b/sites/winplay.co/winplay.co.config.js @@ -1,45 +1,45 @@ -const dayjs = require('dayjs') - -module.exports = { - site: 'winplay.co', - days: 2, - url: 'https://next.platform.mediastre.am/graphql', - request: { - method: 'POST', - headers: { - accept: 'application/json', - 'x-client-id': 'a084524ea449c15dfe5e75636fb55ce6a9d0d7601aac946daa', - 'x-ott-language': 'es' - }, - data() { - return { - operationName: 'getLivesEpg', - variables: { page: 1, hours: 48 }, - query: - 'query getLivesEpg($page: Int = 1, $hours: Int, $ids: [String]) {\n getLives(ids: $ids) {\n _id\n logo\n name\n schedules(hours: $hours, page: {limit: 0, page: $page}) {\n _id\n name\n date_start\n date_end\n current\n match {\n matchDay\n __typename\n }\n show {\n _id\n title\n __typename\n }\n live {\n _id\n dvr\n type\n purchased\n __typename\n }\n __typename\n }\n __typename\n }\n}\n' - } - } - }, - parser({ content, channel, date }) { - let programs = [] - const items = parseItems(content, channel, date) - for (let item of items) { - programs.push({ - title: item.name, - start: dayjs(item.date_start), - stop: dayjs(item.date_end) - }) - } - - return programs - } -} - -function parseItems(content, channel, date) { - const data = JSON.parse(content) - if (!data || !data.data || !data.data.getLives) return [] - const channelData = data.data.getLives.find(i => i._id === channel.site_id) - if (!Array.isArray(channelData.schedules)) return [] - - return channelData.schedules.filter(i => date.isSame(dayjs(i.date_start), 'd')) -} +const dayjs = require('dayjs') + +module.exports = { + site: 'winplay.co', + days: 2, + url: 'https://next.platform.mediastre.am/graphql', + request: { + method: 'POST', + headers: { + accept: 'application/json', + 'x-client-id': 'a084524ea449c15dfe5e75636fb55ce6a9d0d7601aac946daa', + 'x-ott-language': 'es' + }, + data() { + return { + operationName: 'getLivesEpg', + variables: { page: 1, hours: 48 }, + query: + 'query getLivesEpg($page: Int = 1, $hours: Int, $ids: [String]) {\n getLives(ids: $ids) {\n _id\n logo\n name\n schedules(hours: $hours, page: {limit: 0, page: $page}) {\n _id\n name\n date_start\n date_end\n current\n match {\n matchDay\n __typename\n }\n show {\n _id\n title\n __typename\n }\n live {\n _id\n dvr\n type\n purchased\n __typename\n }\n __typename\n }\n __typename\n }\n}\n' + } + } + }, + parser({ content, channel, date }) { + let programs = [] + const items = parseItems(content, channel, date) + for (let item of items) { + programs.push({ + title: item.name, + start: dayjs(item.date_start), + stop: dayjs(item.date_end) + }) + } + + return programs + } +} + +function parseItems(content, channel, date) { + const data = JSON.parse(content) + if (!data || !data.data || !data.data.getLives) return [] + const channelData = data.data.getLives.find(i => i._id === channel.site_id) + if (!Array.isArray(channelData.schedules)) return [] + + return channelData.schedules.filter(i => date.isSame(dayjs(i.date_start), 'd')) +} diff --git a/sites/winplay.co/winplay.co.test.js b/sites/winplay.co/winplay.co.test.js index 492388254..b3fe45c27 100644 --- a/sites/winplay.co/winplay.co.test.js +++ b/sites/winplay.co/winplay.co.test.js @@ -1,68 +1,68 @@ -const { parser, url, request } = require('./winplay.co.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2024-12-24', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '529cff6f6bd2ea6b610000e0', - xmltv_id: 'WinPlusFutbol.co' -} - -it('can generate valid url', () => { - expect(url).toBe('https://next.platform.mediastre.am/graphql') -}) - -it('can generate valid request method', () => { - expect(request.method).toBe('POST') -}) - -it('can generate valid request headers', () => { - expect(request.headers).toMatchObject({ - accept: 'application/json', - 'x-client-id': 'a084524ea449c15dfe5e75636fb55ce6a9d0d7601aac946daa', - 'x-ott-language': 'es' - }) -}) - -it('can generate valid request data', () => { - expect(request.data()).toMatchObject({ - operationName: 'getLivesEpg', - variables: { page: 1, hours: 48 }, - query: - 'query getLivesEpg($page: Int = 1, $hours: Int, $ids: [String]) {\n getLives(ids: $ids) {\n _id\n logo\n name\n schedules(hours: $hours, page: {limit: 0, page: $page}) {\n _id\n name\n date_start\n date_end\n current\n match {\n matchDay\n __typename\n }\n show {\n _id\n title\n __typename\n }\n live {\n _id\n dvr\n type\n purchased\n __typename\n }\n __typename\n }\n __typename\n }\n}\n' - }) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') - - const results = parser({ content, channel, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2024-12-24T00:30:00.000Z', - stop: '2024-12-24T02:30:00.000Z', - title: 'Los Disruptivos de Win' - }) - - expect(results[1]).toMatchObject({ - start: '2024-12-24T02:30:00.000Z', - stop: '2024-12-24T03:30:00.000Z', - title: 'WIn Noticias' - }) -}) - -it('can handle empty guide', () => { - const content = '{"status":"ERROR","error":"UNAUTHORIZED_REQUEST"}' - const results = parser({ content, channel, date }) - - expect(results).toMatchObject([]) -}) +const { parser, url, request } = require('./winplay.co.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2024-12-24', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '529cff6f6bd2ea6b610000e0', + xmltv_id: 'WinPlusFutbol.co' +} + +it('can generate valid url', () => { + expect(url).toBe('https://next.platform.mediastre.am/graphql') +}) + +it('can generate valid request method', () => { + expect(request.method).toBe('POST') +}) + +it('can generate valid request headers', () => { + expect(request.headers).toMatchObject({ + accept: 'application/json', + 'x-client-id': 'a084524ea449c15dfe5e75636fb55ce6a9d0d7601aac946daa', + 'x-ott-language': 'es' + }) +}) + +it('can generate valid request data', () => { + expect(request.data()).toMatchObject({ + operationName: 'getLivesEpg', + variables: { page: 1, hours: 48 }, + query: + 'query getLivesEpg($page: Int = 1, $hours: Int, $ids: [String]) {\n getLives(ids: $ids) {\n _id\n logo\n name\n schedules(hours: $hours, page: {limit: 0, page: $page}) {\n _id\n name\n date_start\n date_end\n current\n match {\n matchDay\n __typename\n }\n show {\n _id\n title\n __typename\n }\n live {\n _id\n dvr\n type\n purchased\n __typename\n }\n __typename\n }\n __typename\n }\n}\n' + }) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') + + const results = parser({ content, channel, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2024-12-24T00:30:00.000Z', + stop: '2024-12-24T02:30:00.000Z', + title: 'Los Disruptivos de Win' + }) + + expect(results[1]).toMatchObject({ + start: '2024-12-24T02:30:00.000Z', + stop: '2024-12-24T03:30:00.000Z', + title: 'WIn Noticias' + }) +}) + +it('can handle empty guide', () => { + const content = '{"status":"ERROR","error":"UNAUTHORIZED_REQUEST"}' + const results = parser({ content, channel, date }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/worldfishingnetwork.com/worldfishingnetwork.com.config.js b/sites/worldfishingnetwork.com/worldfishingnetwork.com.config.js index b50e1f8f6..a20635bb8 100644 --- a/sites/worldfishingnetwork.com/worldfishingnetwork.com.config.js +++ b/sites/worldfishingnetwork.com/worldfishingnetwork.com.config.js @@ -1,79 +1,79 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'worldfishingnetwork.com', - days: 2, - url({ date }) { - return `https://www.worldfishingnetwork.com/schedule/77420?day=${date.format('ddd')}` - }, - parser({ content, date }) { - const programs = [] - const items = parseItems(content) - items.forEach(item => { - let $item = cheerio.load(item) - const prev = programs[programs.length - 1] - let start = parseStart($item, date) - if (prev) { - if (start.isBefore(prev.start)) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.add(30, 'm') - programs.push({ - title: parseTitle($item), - sub_title: parseSubTitle($item), - description: parseDescription($item), - image: parseImage($item), - start, - stop - }) - }) - - return programs - } -} - -function parseTitle($item) { - return $item('.show-title > h3').text().trim() -} - -function parseSubTitle($item) { - return $item('.show-title').clone().children().remove().end().text().trim() -} - -function parseDescription($item) { - return $item('.show-title > p').text().trim() -} - -function parseImage($item) { - const url = $item('.show-img > img').attr('src') - - return url ? `https:${url}` : null -} - -function parseStart($item, date) { - const time = $item('.show-time > h2').clone().children().remove().end().text().trim() - const period = $item('.show-time > h2 > span > strong').text().trim() - - return dayjs.tz( - `${date.format('YYYY-MM-DD')} ${time} ${period}`, - 'YYYY-MM-DD HH:mm A', - 'America/New_York' - ) -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('.show-item').toArray() -} +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'worldfishingnetwork.com', + days: 2, + url({ date }) { + return `https://www.worldfishingnetwork.com/schedule/77420?day=${date.format('ddd')}` + }, + parser({ content, date }) { + const programs = [] + const items = parseItems(content) + items.forEach(item => { + let $item = cheerio.load(item) + const prev = programs[programs.length - 1] + let start = parseStart($item, date) + if (prev) { + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.add(30, 'm') + programs.push({ + title: parseTitle($item), + sub_title: parseSubTitle($item), + description: parseDescription($item), + image: parseImage($item), + start, + stop + }) + }) + + return programs + } +} + +function parseTitle($item) { + return $item('.show-title > h3').text().trim() +} + +function parseSubTitle($item) { + return $item('.show-title').clone().children().remove().end().text().trim() +} + +function parseDescription($item) { + return $item('.show-title > p').text().trim() +} + +function parseImage($item) { + const url = $item('.show-img > img').attr('src') + + return url ? `https:${url}` : null +} + +function parseStart($item, date) { + const time = $item('.show-time > h2').clone().children().remove().end().text().trim() + const period = $item('.show-time > h2 > span > strong').text().trim() + + return dayjs.tz( + `${date.format('YYYY-MM-DD')} ${time} ${period}`, + 'YYYY-MM-DD HH:mm A', + 'America/New_York' + ) +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('.show-item').toArray() +} diff --git a/sites/worldfishingnetwork.com/worldfishingnetwork.com.test.js b/sites/worldfishingnetwork.com/worldfishingnetwork.com.test.js index 8629089ea..a67c8c955 100644 --- a/sites/worldfishingnetwork.com/worldfishingnetwork.com.test.js +++ b/sites/worldfishingnetwork.com/worldfishingnetwork.com.test.js @@ -1,53 +1,53 @@ -const { parser, url } = require('./worldfishingnetwork.com.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-01-24', 'YYYY-MM-DD').startOf('d') - -it('can generate valid url', () => { - expect(url({ date })).toBe('https://www.worldfishingnetwork.com/schedule/77420?day=Tue') -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') - let results = parser({ content, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2023-01-24T05:00:00.000Z', - stop: '2023-01-24T07:00:00.000Z', - title: 'Major League Fishing', - sub_title: 'Challenge Cup Sudden Death Round 2', - description: - 'Nine anglers race to a target weight on Lake Wylie in the Lucas Oil Challenge Cup, presented by B&W Trailer Hitches, Rock Hill, South Carolina. Only four will move on to the Championship Round.', - image: 'https://content.osgnetworks.tv/shows/major-league-fishing-thumbnail.jpg' - }) - - expect(results[41]).toMatchObject({ - start: '2023-01-25T04:30:00.000Z', - stop: '2023-01-25T05:00:00.000Z', - title: 'Fishing 411', - sub_title: 'Flint Wilderness Walleye', - description: - 'Mark Romanack and Bryan Darland fish walleye on Klotz Lake in the famed Flint Wilderness of Ontario', - image: 'https://content.osgnetworks.tv/shows/fishin-411-thumbnail.jpg' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - date, - content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') - }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./worldfishingnetwork.com.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-01-24', 'YYYY-MM-DD').startOf('d') + +it('can generate valid url', () => { + expect(url({ date })).toBe('https://www.worldfishingnetwork.com/schedule/77420?day=Tue') +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'), 'utf8') + let results = parser({ content, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2023-01-24T05:00:00.000Z', + stop: '2023-01-24T07:00:00.000Z', + title: 'Major League Fishing', + sub_title: 'Challenge Cup Sudden Death Round 2', + description: + 'Nine anglers race to a target weight on Lake Wylie in the Lucas Oil Challenge Cup, presented by B&W Trailer Hitches, Rock Hill, South Carolina. Only four will move on to the Championship Round.', + image: 'https://content.osgnetworks.tv/shows/major-league-fishing-thumbnail.jpg' + }) + + expect(results[41]).toMatchObject({ + start: '2023-01-25T04:30:00.000Z', + stop: '2023-01-25T05:00:00.000Z', + title: 'Fishing 411', + sub_title: 'Flint Wilderness Walleye', + description: + 'Mark Romanack and Bryan Darland fish walleye on Klotz Lake in the famed Flint Wilderness of Ontario', + image: 'https://content.osgnetworks.tv/shows/fishin-411-thumbnail.jpg' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + date, + content: fs.readFileSync(path.resolve(__dirname, '__data__/no_content.html'), 'utf8') + }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/www3.nhk.or.jp/www3.nhk.or.jp.config.js b/sites/www3.nhk.or.jp/www3.nhk.or.jp.config.js index 4a7f59f05..1d696b3ab 100644 --- a/sites/www3.nhk.or.jp/www3.nhk.or.jp.config.js +++ b/sites/www3.nhk.or.jp/www3.nhk.or.jp.config.js @@ -1,68 +1,68 @@ -const dayjs = require('dayjs') - -module.exports = { - site: 'www3.nhk.or.jp', - days: 5, - lang: 'en', - delay: 5000, - - url: function ({ date }) { - return `https://nwapi.nhk.jp/nhkworld/epg/v7b/world/s${date.unix() * 1000}-e${ - date.add(1, 'd').unix() * 1000 - }.json` - }, - - request: { - method: 'GET', - timeout: 5000, - cache: { ttl: 60 * 1000 }, - headers: { - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36' - } - }, - - logo: function (context) { - return context.channel.logo - }, - - parser: function (context) { - const programs = [] - - const items = parseItems(context.content) - - items.forEach(item => { - programs.push({ - title: item.title, - start: parseStart(item), - stop: parseStop(item), - description: item.description, - image: parseImage(item), - sub_title: item.subtitle - }) - }) - - return programs - } -} - -function parseItems(content) { - if (content != '') { - const data = JSON.parse(content) - return !data || !data.channel || !Array.isArray(data.channel.item) ? [] : data.channel.item - } else { - return [] - } -} - -function parseStart(item) { - return dayjs.unix(parseInt(item.pubDate) / 1000) -} - -function parseStop(item) { - return dayjs.unix(parseInt(item.endDate) / 1000) -} - -function parseImage(item) { - return 'https://www.nhk.or.jp' + item.thumbnail -} +const dayjs = require('dayjs') + +module.exports = { + site: 'www3.nhk.or.jp', + days: 5, + lang: 'en', + delay: 5000, + + url: function ({ date }) { + return `https://nwapi.nhk.jp/nhkworld/epg/v7b/world/s${date.unix() * 1000}-e${ + date.add(1, 'd').unix() * 1000 + }.json` + }, + + request: { + method: 'GET', + timeout: 5000, + cache: { ttl: 60 * 1000 }, + headers: { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36' + } + }, + + logo: function (context) { + return context.channel.logo + }, + + parser: function (context) { + const programs = [] + + const items = parseItems(context.content) + + items.forEach(item => { + programs.push({ + title: item.title, + start: parseStart(item), + stop: parseStop(item), + description: item.description, + image: parseImage(item), + sub_title: item.subtitle + }) + }) + + return programs + } +} + +function parseItems(content) { + if (content != '') { + const data = JSON.parse(content) + return !data || !data.channel || !Array.isArray(data.channel.item) ? [] : data.channel.item + } else { + return [] + } +} + +function parseStart(item) { + return dayjs.unix(parseInt(item.pubDate) / 1000) +} + +function parseStop(item) { + return dayjs.unix(parseInt(item.endDate) / 1000) +} + +function parseImage(item) { + return 'https://www.nhk.or.jp' + item.thumbnail +} diff --git a/sites/www3.nhk.or.jp/www3.nhk.or.jp.test.js b/sites/www3.nhk.or.jp/www3.nhk.or.jp.test.js index ffe551393..ed65daef3 100644 --- a/sites/www3.nhk.or.jp/www3.nhk.or.jp.test.js +++ b/sites/www3.nhk.or.jp/www3.nhk.or.jp.test.js @@ -1,43 +1,43 @@ -const { url, parser } = require('./www3.nhk.or.jp.config.js') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -dayjs.extend(utc) - -const date = dayjs.utc('2023-04-29', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '0', - xmltv_id: 'NHKWorldJapan.jp', - lang: 'en', - logo: 'https://www3.nhk.or.jp/nhkworld/common/site_images/nw_webapp_1024x1024.png' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://nwapi.nhk.jp/nhkworld/epg/v7b/world/s1682726400000-e1682812800000.json' - ) -}) - -it('can parse response', () => { - const content = - '{"channel":{"item":[{"seriesId":"1007","airingId":"000","title":"NHK NEWSLINE","description":"NHK WORLD-JAPAN\'s flagship hourly news program delivers the latest world news, business and weather, with a focus on Japan and the rest of Asia.","link":"/nhkworld/en/news/","pubDate":"1682726400000","endDate":"1682727000000","vodReserved":false,"jstrm":"1","wstrm":"1","subtitle":"","content":"","content_clean":"","pgm_gr_id":"","thumbnail":"/nhkworld/upld/thumbnails/en/tv/regular_program/340aed63308aafd1178172abf6325231_large.jpg","thumbnail_s":"/nhkworld/upld/thumbnails/en/tv/regular_program/340aed63308aafd1178172abf6325231_small.jpg","showlist":"0","internal":"0","genre":{"TV":"11","Top":"","LC":""},"vod_id":"","vod_url":"","analytics":"[nhkworld]simul;NHK NEWSLINE;w02,001;1007-000-2023;2023-04-29T09:00:00+09:00"}]}}' - const results = parser({ content }) - - expect(results).toMatchObject([ - { - title: 'NHK NEWSLINE', - start: dayjs(1682726400000), - stop: dayjs(1682727000000), - description: - "NHK WORLD-JAPAN's flagship hourly news program delivers the latest world news, business and weather, with a focus on Japan and the rest of Asia.", - image: - 'https://www.nhk.or.jp/nhkworld/upld/thumbnails/en/tv/regular_program/340aed63308aafd1178172abf6325231_large.jpg', - sub_title: '' - } - ]) -}) - -it('can handle empty guide', () => { - const results = parser({ content: '' }) - - expect(results).toMatchObject([]) -}) +const { url, parser } = require('./www3.nhk.or.jp.config.js') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +dayjs.extend(utc) + +const date = dayjs.utc('2023-04-29', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '0', + xmltv_id: 'NHKWorldJapan.jp', + lang: 'en', + logo: 'https://www3.nhk.or.jp/nhkworld/common/site_images/nw_webapp_1024x1024.png' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://nwapi.nhk.jp/nhkworld/epg/v7b/world/s1682726400000-e1682812800000.json' + ) +}) + +it('can parse response', () => { + const content = + '{"channel":{"item":[{"seriesId":"1007","airingId":"000","title":"NHK NEWSLINE","description":"NHK WORLD-JAPAN\'s flagship hourly news program delivers the latest world news, business and weather, with a focus on Japan and the rest of Asia.","link":"/nhkworld/en/news/","pubDate":"1682726400000","endDate":"1682727000000","vodReserved":false,"jstrm":"1","wstrm":"1","subtitle":"","content":"","content_clean":"","pgm_gr_id":"","thumbnail":"/nhkworld/upld/thumbnails/en/tv/regular_program/340aed63308aafd1178172abf6325231_large.jpg","thumbnail_s":"/nhkworld/upld/thumbnails/en/tv/regular_program/340aed63308aafd1178172abf6325231_small.jpg","showlist":"0","internal":"0","genre":{"TV":"11","Top":"","LC":""},"vod_id":"","vod_url":"","analytics":"[nhkworld]simul;NHK NEWSLINE;w02,001;1007-000-2023;2023-04-29T09:00:00+09:00"}]}}' + const results = parser({ content }) + + expect(results).toMatchObject([ + { + title: 'NHK NEWSLINE', + start: dayjs(1682726400000), + stop: dayjs(1682727000000), + description: + "NHK WORLD-JAPAN's flagship hourly news program delivers the latest world news, business and weather, with a focus on Japan and the rest of Asia.", + image: + 'https://www.nhk.or.jp/nhkworld/upld/thumbnails/en/tv/regular_program/340aed63308aafd1178172abf6325231_large.jpg', + sub_title: '' + } + ]) +}) + +it('can handle empty guide', () => { + const results = parser({ content: '' }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/xem.kplus.vn/xem.kplus.vn.config.js b/sites/xem.kplus.vn/xem.kplus.vn.config.js index e648ca2fc..c14c11ce2 100644 --- a/sites/xem.kplus.vn/xem.kplus.vn.config.js +++ b/sites/xem.kplus.vn/xem.kplus.vn.config.js @@ -1,146 +1,146 @@ -const dayjs = require('dayjs') -const axios = require('axios') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') -const doFetch = require('@ntlab/sfetch') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -let session - -module.exports = { - site: 'xem.kplus.vn', - days: 2, - url({ channel, date }) { - return `https://tvapi-sgn.solocoo.tv/v1/assets?query=schedule,forrelated,${ - channel.site_id - }&from=${date.format('YYYY-MM-DDTHH:mm:ss[Z]')}&limit=1000` - }, - request: { - async headers() { - if (!session) { - session = await loadSessionDetails() - if (!session || !session.token) return null - } - - return { - authorization: `Bearer ${session.token}` - } - } - }, - parser: function ({ content, date }) { - let programs = [] - const items = parseItems(content, date) - items.forEach(item => { - programs.push({ - title: item.title, - categories: parseCategories(item), - images: parseImages(item), - start: parseStart(item), - stop: parseStop(item) - }) - }) - - return programs - }, - async channels() { - const session = await loadSessionDetails() - if (!session || !session.token) throw new Error('The session token is missing') - - const groups = [ - 'Channels_Kplus', - 'Channels_VTV', - 'Channels_VTVcab', - 'Channels_Kênh Quốc Tế', - 'Channels_SCTV', - 'Channels_HTV-HTVC', - 'Channels_THVL', - 'Channels_Kênh Thiết Yếu', - 'Channels_Kênh Địa Phương' - ] - - const queue = groups.map(group => ({ - url: `https://tvapi-sgn.solocoo.tv/v1/assets?query=nav,${group}&limit=100`, - params: { - headers: { - authorization: `Bearer ${session.token}` - } - } - })) - - let channels = [] - await doFetch(queue, (url, data) => { - data.assets.forEach(channel => { - if (!channel?.params?.params?.id) return - - channels.push({ - lang: 'vi', - name: channel.params.internalTitle.replace('Channels_', ''), - site_id: channel.params.params.id - }) - }) - }) - - return channels - } -} - -function parseCategories(item) { - return Array.isArray(item?.params?.genres) ? item.params.genres.map(i => i.title) : [] -} - -function parseImages(item) { - return Array.isArray(item?.images) - ? item.images - .filter(i => i.url.indexOf('orientation=landscape') > 0) - .map(i => `${i.url}&w=460&h=260`) - : [] -} - -function parseStart(item) { - return item?.params?.start ? dayjs.utc(item.params.start, 'YYYY-MM-DDTHH:mm:ss[Z]') : null -} - -function parseStop(item) { - return item?.params?.end ? dayjs.utc(item.params.end, 'YYYY-MM-DDTHH:mm:ss[Z]') : null -} - -function parseItems(content, date) { - try { - const data = JSON.parse(content) - if (!data || !Array.isArray(data.assets)) return [] - - return data.assets.filter( - p => p?.params?.start && date.isSame(dayjs.utc(p.params.start, 'YYYY-MM-DDTHH:mm:ss[Z]'), 'd') - ) - } catch (err) { - console.log(err) - return [] - } -} - -function loadSessionDetails() { - return axios - .post('https://tvapi-sgn.solocoo.tv/v1/session', { - ssoToken: - 'eyJhbGciOiJkaXIiLCJrZXkiOiJ2c3R2IiwiZW5jIjoiQTEyOENCQy1IUzI1NiJ9..6jMKWv5bSqODWOWLmeERqw._WcKmMW2ij3yPJkhFllQHgXOkW7powvzT-5p6G4_jjYa8vzJybmHu_1CwIEb_s2hVOyaNDi6M-NVLNY9CaNU3aSC-ojZ4UoQ7QLRTFWP-2uY-mL5IgJtL7Xknus5blHJbR8B-xaOODXIJh8PneZORmPHa5EHhs1vOmqpGb1COZwqlw_WFbGT9EsFq6W8fsYH3O5cUqec608Uad-wK59OQIJyofZJwrb6VTthmwwIDxX6Dn-kyYssfdXvPF_BXu5A-e2MFOsdzvMjENdq0FHCk-b9OojzENR6S-JEtSTrZHrgSfHsqb1DwVbtuaetFlV-A3-gxyqqHH7QIvkRM38StNMAp_q8TUauhluwKK3nuXbgogiQ9d9Kc9s7WGoBPOVHsZ4w6wJ9fDBIyhApOJUAdEINi7dLpe1pTBBk6ZA504PVyQ0d6DtdhJhkbT6I88wwxz2U6sF5tInZBcdyZzCa1KKHWQuonTJ4IPcILGQFuzo.lhVv2QaTOaxTS9F4Ht2L3A', - osVersion: 'Windows 10', - deviceModel: 'Chrome', - deviceType: 'PC', - deviceSerial: 'w408a0eb0-d50f-11ef-affa-af9775b838ad', - deviceOem: 'Chrome', - devicePrettyName: 'Chrome 128.0.0.0', - appVersion: '12.1', - language: 'en_US', - brand: 'vstv', - memberId: '0', - featureLevel: 6, - provisionData: - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE3MzcxNDU4MjYsImljIjp0cnVlLCJ1cCI6ImNwaSIsImJyIjoidnN0diIsImRzIjoidzQwOGEwZWIwLWQ1MGYtMTFlZi1hZmZhLWFmOTc3NWI4MzhhZCIsImRlIjoiYnJhbmRNYXBwaW5nIn0.Ou6yh5qXtlK4NhyWHciVszARr98PLL1TkaXKpqQtub8' - }) - .then(r => r.data) - .catch(console.log) -} +const dayjs = require('dayjs') +const axios = require('axios') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const doFetch = require('@ntlab/sfetch') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +let session + +module.exports = { + site: 'xem.kplus.vn', + days: 2, + url({ channel, date }) { + return `https://tvapi-sgn.solocoo.tv/v1/assets?query=schedule,forrelated,${ + channel.site_id + }&from=${date.format('YYYY-MM-DDTHH:mm:ss[Z]')}&limit=1000` + }, + request: { + async headers() { + if (!session) { + session = await loadSessionDetails() + if (!session || !session.token) return null + } + + return { + authorization: `Bearer ${session.token}` + } + } + }, + parser: function ({ content, date }) { + let programs = [] + const items = parseItems(content, date) + items.forEach(item => { + programs.push({ + title: item.title, + categories: parseCategories(item), + images: parseImages(item), + start: parseStart(item), + stop: parseStop(item) + }) + }) + + return programs + }, + async channels() { + const session = await loadSessionDetails() + if (!session || !session.token) throw new Error('The session token is missing') + + const groups = [ + 'Channels_Kplus', + 'Channels_VTV', + 'Channels_VTVcab', + 'Channels_Kênh Quốc Tế', + 'Channels_SCTV', + 'Channels_HTV-HTVC', + 'Channels_THVL', + 'Channels_Kênh Thiết Yếu', + 'Channels_Kênh Địa Phương' + ] + + const queue = groups.map(group => ({ + url: `https://tvapi-sgn.solocoo.tv/v1/assets?query=nav,${group}&limit=100`, + params: { + headers: { + authorization: `Bearer ${session.token}` + } + } + })) + + let channels = [] + await doFetch(queue, (url, data) => { + data.assets.forEach(channel => { + if (!channel?.params?.params?.id) return + + channels.push({ + lang: 'vi', + name: channel.params.internalTitle.replace('Channels_', ''), + site_id: channel.params.params.id + }) + }) + }) + + return channels + } +} + +function parseCategories(item) { + return Array.isArray(item?.params?.genres) ? item.params.genres.map(i => i.title) : [] +} + +function parseImages(item) { + return Array.isArray(item?.images) + ? item.images + .filter(i => i.url.indexOf('orientation=landscape') > 0) + .map(i => `${i.url}&w=460&h=260`) + : [] +} + +function parseStart(item) { + return item?.params?.start ? dayjs.utc(item.params.start, 'YYYY-MM-DDTHH:mm:ss[Z]') : null +} + +function parseStop(item) { + return item?.params?.end ? dayjs.utc(item.params.end, 'YYYY-MM-DDTHH:mm:ss[Z]') : null +} + +function parseItems(content, date) { + try { + const data = JSON.parse(content) + if (!data || !Array.isArray(data.assets)) return [] + + return data.assets.filter( + p => p?.params?.start && date.isSame(dayjs.utc(p.params.start, 'YYYY-MM-DDTHH:mm:ss[Z]'), 'd') + ) + } catch (err) { + console.log(err) + return [] + } +} + +function loadSessionDetails() { + return axios + .post('https://tvapi-sgn.solocoo.tv/v1/session', { + ssoToken: + 'eyJhbGciOiJkaXIiLCJrZXkiOiJ2c3R2IiwiZW5jIjoiQTEyOENCQy1IUzI1NiJ9..6jMKWv5bSqODWOWLmeERqw._WcKmMW2ij3yPJkhFllQHgXOkW7powvzT-5p6G4_jjYa8vzJybmHu_1CwIEb_s2hVOyaNDi6M-NVLNY9CaNU3aSC-ojZ4UoQ7QLRTFWP-2uY-mL5IgJtL7Xknus5blHJbR8B-xaOODXIJh8PneZORmPHa5EHhs1vOmqpGb1COZwqlw_WFbGT9EsFq6W8fsYH3O5cUqec608Uad-wK59OQIJyofZJwrb6VTthmwwIDxX6Dn-kyYssfdXvPF_BXu5A-e2MFOsdzvMjENdq0FHCk-b9OojzENR6S-JEtSTrZHrgSfHsqb1DwVbtuaetFlV-A3-gxyqqHH7QIvkRM38StNMAp_q8TUauhluwKK3nuXbgogiQ9d9Kc9s7WGoBPOVHsZ4w6wJ9fDBIyhApOJUAdEINi7dLpe1pTBBk6ZA504PVyQ0d6DtdhJhkbT6I88wwxz2U6sF5tInZBcdyZzCa1KKHWQuonTJ4IPcILGQFuzo.lhVv2QaTOaxTS9F4Ht2L3A', + osVersion: 'Windows 10', + deviceModel: 'Chrome', + deviceType: 'PC', + deviceSerial: 'w408a0eb0-d50f-11ef-affa-af9775b838ad', + deviceOem: 'Chrome', + devicePrettyName: 'Chrome 128.0.0.0', + appVersion: '12.1', + language: 'en_US', + brand: 'vstv', + memberId: '0', + featureLevel: 6, + provisionData: + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE3MzcxNDU4MjYsImljIjp0cnVlLCJ1cCI6ImNwaSIsImJyIjoidnN0diIsImRzIjoidzQwOGEwZWIwLWQ1MGYtMTFlZi1hZmZhLWFmOTc3NWI4MzhhZCIsImRlIjoiYnJhbmRNYXBwaW5nIn0.Ou6yh5qXtlK4NhyWHciVszARr98PLL1TkaXKpqQtub8' + }) + .then(r => r.data) + .catch(console.log) +} diff --git a/sites/xem.kplus.vn/xem.kplus.vn.test.js b/sites/xem.kplus.vn/xem.kplus.vn.test.js index d5bd2bfd2..451279a47 100644 --- a/sites/xem.kplus.vn/xem.kplus.vn.test.js +++ b/sites/xem.kplus.vn/xem.kplus.vn.test.js @@ -1,77 +1,77 @@ -const { parser, url, request } = require('./xem.kplus.vn.config.js') -const axios = require('axios') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -axios.post.mockImplementation(url => { - if (url === 'https://tvapi-sgn.solocoo.tv/v1/session') { - return Promise.resolve({ - data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/session.json'))) - }) - } else { - return Promise.resolve({ - data: {} - }) - } -}) - -const date = dayjs.utc('2025-01-18', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'F31WvdXdwYNOInUaTeIC-ixsLQVsrSNgUczDSFCN', - xmltv_id: 'KPlusKids.vn' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://tvapi-sgn.solocoo.tv/v1/assets?query=schedule,forrelated,F31WvdXdwYNOInUaTeIC-ixsLQVsrSNgUczDSFCN&from=2025-01-18T00:00:00Z&limit=1000' - ) -}) - -it('can generate valid request headers', async () => { - expect(await request.headers()).toMatchObject({ - authorization: - 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0di5zb2xvY29vLmF1dGgiOnsicyI6Inc0MDhhMGViMC1kNTBmLTExZWYtYWZmYS1hZjk3NzViODM4YWQiLCJ1IjoiQW8yZXd1S3o3M2tjX2UtOFZmWGVnZyIsImwiOiJlbl9VUyIsImQiOiJQQyIsImRtIjoiQ2hyb21lIiwib20iOiJPIiwiYyI6ImJ6bXZPVEFOM05qdzZadjYtYnZveThwbnMwNHBtbTdxeG9QOUVwaVNQVzAiLCJzdCI6ImZ1bGwiLCJnIjoiZXlKaWNpSTZJblp6ZEhZaUxDSjFjQ0k2SW1Od2FTSXNJbkIwSWpwbVlXeHpaU3dpWkdVaU9pSmljbUZ1WkUxaGNIQnBibWNpTENKa1lpSTZabUZzYzJWOSIsImYiOjYsImIiOiJ2c3R2In0sIm5iZiI6MTczNzE0NTk1NCwiZXhwIjoxNzM3MTYzNzg0LCJpYXQiOjE3MzcxNDU5NTQsImF1ZCI6ImNwaSJ9.25av5gdR38FW0SmnzNiE4EV1D4Gozox2Wgvoh7QKZaM' - }) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - let results = parser({ content, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results.length).toBe(100) - expect(results[0]).toMatchObject({ - start: '2025-01-18T00:03:00.000Z', - stop: '2025-01-18T00:10:00.000Z', - title: 'Masha and the Bear S1, Ep01', - categories: ['Children'], - images: [ - 'https://img.kplus.vn/images?filename=Media/HDVN/2021_11/KID_CAR_21__2660472_0a13b965-0b37-4552-99d5-a5998ca20156.jpg&orientation=landscape&w=460&h=260' - ] - }) - expect(results[99]).toMatchObject({ - start: '2025-01-18T20:59:00.000Z', - stop: '2025-01-18T21:28:00.000Z', - title: 'KID SHOW: BUG SHAPE BOOKMARK - WOODEATER PAPERWEIGHT', - categories: ['Children'], - images: [ - 'https://img.kplus.vn/images?filename=Media/HDVN/2012_02/KID_EDU_HNCP__VN_20200_9d92b5d2-02da-49ac-969e-4b20aad8ccec.jpg&orientation=landscape&w=460&h=260' - ] - }) -}) - -it('can handle empty guide', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) - const result = parser({ content, channel, date }) - expect(result).toMatchObject([]) -}) +const { parser, url, request } = require('./xem.kplus.vn.config.js') +const axios = require('axios') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +axios.post.mockImplementation(url => { + if (url === 'https://tvapi-sgn.solocoo.tv/v1/session') { + return Promise.resolve({ + data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/session.json'))) + }) + } else { + return Promise.resolve({ + data: {} + }) + } +}) + +const date = dayjs.utc('2025-01-18', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'F31WvdXdwYNOInUaTeIC-ixsLQVsrSNgUczDSFCN', + xmltv_id: 'KPlusKids.vn' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://tvapi-sgn.solocoo.tv/v1/assets?query=schedule,forrelated,F31WvdXdwYNOInUaTeIC-ixsLQVsrSNgUczDSFCN&from=2025-01-18T00:00:00Z&limit=1000' + ) +}) + +it('can generate valid request headers', async () => { + expect(await request.headers()).toMatchObject({ + authorization: + 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0di5zb2xvY29vLmF1dGgiOnsicyI6Inc0MDhhMGViMC1kNTBmLTExZWYtYWZmYS1hZjk3NzViODM4YWQiLCJ1IjoiQW8yZXd1S3o3M2tjX2UtOFZmWGVnZyIsImwiOiJlbl9VUyIsImQiOiJQQyIsImRtIjoiQ2hyb21lIiwib20iOiJPIiwiYyI6ImJ6bXZPVEFOM05qdzZadjYtYnZveThwbnMwNHBtbTdxeG9QOUVwaVNQVzAiLCJzdCI6ImZ1bGwiLCJnIjoiZXlKaWNpSTZJblp6ZEhZaUxDSjFjQ0k2SW1Od2FTSXNJbkIwSWpwbVlXeHpaU3dpWkdVaU9pSmljbUZ1WkUxaGNIQnBibWNpTENKa1lpSTZabUZzYzJWOSIsImYiOjYsImIiOiJ2c3R2In0sIm5iZiI6MTczNzE0NTk1NCwiZXhwIjoxNzM3MTYzNzg0LCJpYXQiOjE3MzcxNDU5NTQsImF1ZCI6ImNwaSJ9.25av5gdR38FW0SmnzNiE4EV1D4Gozox2Wgvoh7QKZaM' + }) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + let results = parser({ content, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results.length).toBe(100) + expect(results[0]).toMatchObject({ + start: '2025-01-18T00:03:00.000Z', + stop: '2025-01-18T00:10:00.000Z', + title: 'Masha and the Bear S1, Ep01', + categories: ['Children'], + images: [ + 'https://img.kplus.vn/images?filename=Media/HDVN/2021_11/KID_CAR_21__2660472_0a13b965-0b37-4552-99d5-a5998ca20156.jpg&orientation=landscape&w=460&h=260' + ] + }) + expect(results[99]).toMatchObject({ + start: '2025-01-18T20:59:00.000Z', + stop: '2025-01-18T21:28:00.000Z', + title: 'KID SHOW: BUG SHAPE BOOKMARK - WOODEATER PAPERWEIGHT', + categories: ['Children'], + images: [ + 'https://img.kplus.vn/images?filename=Media/HDVN/2012_02/KID_EDU_HNCP__VN_20200_9d92b5d2-02da-49ac-969e-4b20aad8ccec.jpg&orientation=landscape&w=460&h=260' + ] + }) +}) + +it('can handle empty guide', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/no_content.json')) + const result = parser({ content, channel, date }) + expect(result).toMatchObject([]) +}) diff --git a/sites/xumo.tv/xumo.tv.config.js b/sites/xumo.tv/xumo.tv.config.js index 17306c344..6b8353dc6 100644 --- a/sites/xumo.tv/xumo.tv.config.js +++ b/sites/xumo.tv/xumo.tv.config.js @@ -1,134 +1,134 @@ -const axios = require('axios') -const dayjs = require('dayjs') - -const API_ENDPOINT = 'https://valencia-app-mds.xumo.com/v2' - -const client = axios.create({ - baseURL: API_ENDPOINT, - responseType: 'arraybuffer' -}) - -module.exports = { - site: 'xumo.tv', - days: 2, - request: { - cache: { - ttl: 60 * 60 * 1000 // 1 hour - } - }, - url: function ({ date, channel }) { - const [offset] = channel.site_id.split('#') - - return `${API_ENDPOINT}/epg/10006/${date.format( - 'YYYYMMDD' - )}/0.json?f=asset.title&f=asset.descriptions&limit=50&offset=${offset}` - }, - async parser({ content, channel, date }) { - let programs = [] - let items = parseItems(content, channel) - if (!items.length) return programs - const d = date.format('YYYYMMDD') - const [offset] = channel.site_id.split('#') - const promises = [ - client.get( - `/epg/10006/${d}/1.json?f=asset.title&f=asset.descriptions&limit=50&offset=${offset}` - ), - client.get( - `/epg/10006/${d}/2.json?f=asset.title&f=asset.descriptions&limit=50&offset=${offset}` - ), - client.get( - `/epg/10006/${d}/3.json?f=asset.title&f=asset.descriptions&limit=50&offset=${offset}` - ) - ] - const results = await Promise.allSettled(promises) - results.forEach(r => { - if (r.status === 'fulfilled') { - items = items.concat(parseItems(r.value.data, channel)) - } - }) - - items.forEach(item => { - programs.push({ - title: item.title, - sub_title: item.episodeTitle, - description: parseDescription(item), - start: parseStart(item), - stop: parseStop(item) - }) - }) - - return programs - }, - async channels() { - const channels = await axios - .get( - 'https://valencia-app-mds.xumo.com/v2/channels/list/10006.json?sort=hybrid&geoId=unknown' - ) - .then(r => r.data.channel.item) - .catch(console.log) - - const promises = [ - axios.get(`${API_ENDPOINT}/epg/10006/19700101/0.json?limit=50&offset=0`), - axios.get(`${API_ENDPOINT}/epg/10006/19700101/0.json?limit=50&offset=50`), - axios.get(`${API_ENDPOINT}/epg/10006/19700101/0.json?limit=50&offset=100`), - axios.get(`${API_ENDPOINT}/epg/10006/19700101/0.json?limit=50&offset=150`), - axios.get(`${API_ENDPOINT}/epg/10006/19700101/0.json?limit=50&offset=200`), - axios.get(`${API_ENDPOINT}/epg/10006/19700101/0.json?limit=50&offset=250`), - axios.get(`${API_ENDPOINT}/epg/10006/19700101/0.json?limit=50&offset=300`) - ] - - const output = [] - const results = await Promise.allSettled(promises) - results.forEach((r, i) => { - if (r.status !== 'fulfilled') return - - r.value.data.channels.forEach(item => { - const info = channels.find(c => c.guid.value == item.channelId) - - if (!info) { - console.log(item.channelId) - } - - output.push({ - lang: 'en', - site_id: `${i * 50}#${item.channelId}`, - name: info.title - }) - }) - }) - - return output - } -} - -function parseDescription(item) { - if (!item.descriptions) return null - - return item.descriptions.medium || item.descriptions.small || item.descriptions.tiny -} - -function parseStart(item) { - return dayjs(item.start) -} - -function parseStop(item) { - return dayjs(item.end) -} - -function parseItems(content, channel) { - if (!content) return [] - const [, channelId] = channel.site_id.split('#') - const data = JSON.parse(content) - if (!data || !Array.isArray(data.channels)) return [] - const channelData = data.channels.find(c => c.channelId == channelId) - if (!channelData || !Array.isArray(channelData.schedule)) return [] - - return channelData.schedule - .map(item => { - const details = data.assets[item.assetId] - if (!details) return null - - return { ...item, ...details } - }) - .filter(Boolean) -} +const axios = require('axios') +const dayjs = require('dayjs') + +const API_ENDPOINT = 'https://valencia-app-mds.xumo.com/v2' + +const client = axios.create({ + baseURL: API_ENDPOINT, + responseType: 'arraybuffer' +}) + +module.exports = { + site: 'xumo.tv', + days: 2, + request: { + cache: { + ttl: 60 * 60 * 1000 // 1 hour + } + }, + url: function ({ date, channel }) { + const [offset] = channel.site_id.split('#') + + return `${API_ENDPOINT}/epg/10006/${date.format( + 'YYYYMMDD' + )}/0.json?f=asset.title&f=asset.descriptions&limit=50&offset=${offset}` + }, + async parser({ content, channel, date }) { + let programs = [] + let items = parseItems(content, channel) + if (!items.length) return programs + const d = date.format('YYYYMMDD') + const [offset] = channel.site_id.split('#') + const promises = [ + client.get( + `/epg/10006/${d}/1.json?f=asset.title&f=asset.descriptions&limit=50&offset=${offset}` + ), + client.get( + `/epg/10006/${d}/2.json?f=asset.title&f=asset.descriptions&limit=50&offset=${offset}` + ), + client.get( + `/epg/10006/${d}/3.json?f=asset.title&f=asset.descriptions&limit=50&offset=${offset}` + ) + ] + const results = await Promise.allSettled(promises) + results.forEach(r => { + if (r.status === 'fulfilled') { + items = items.concat(parseItems(r.value.data, channel)) + } + }) + + items.forEach(item => { + programs.push({ + title: item.title, + sub_title: item.episodeTitle, + description: parseDescription(item), + start: parseStart(item), + stop: parseStop(item) + }) + }) + + return programs + }, + async channels() { + const channels = await axios + .get( + 'https://valencia-app-mds.xumo.com/v2/channels/list/10006.json?sort=hybrid&geoId=unknown' + ) + .then(r => r.data.channel.item) + .catch(console.log) + + const promises = [ + axios.get(`${API_ENDPOINT}/epg/10006/19700101/0.json?limit=50&offset=0`), + axios.get(`${API_ENDPOINT}/epg/10006/19700101/0.json?limit=50&offset=50`), + axios.get(`${API_ENDPOINT}/epg/10006/19700101/0.json?limit=50&offset=100`), + axios.get(`${API_ENDPOINT}/epg/10006/19700101/0.json?limit=50&offset=150`), + axios.get(`${API_ENDPOINT}/epg/10006/19700101/0.json?limit=50&offset=200`), + axios.get(`${API_ENDPOINT}/epg/10006/19700101/0.json?limit=50&offset=250`), + axios.get(`${API_ENDPOINT}/epg/10006/19700101/0.json?limit=50&offset=300`) + ] + + const output = [] + const results = await Promise.allSettled(promises) + results.forEach((r, i) => { + if (r.status !== 'fulfilled') return + + r.value.data.channels.forEach(item => { + const info = channels.find(c => c.guid.value == item.channelId) + + if (!info) { + console.log(item.channelId) + } + + output.push({ + lang: 'en', + site_id: `${i * 50}#${item.channelId}`, + name: info.title + }) + }) + }) + + return output + } +} + +function parseDescription(item) { + if (!item.descriptions) return null + + return item.descriptions.medium || item.descriptions.small || item.descriptions.tiny +} + +function parseStart(item) { + return dayjs(item.start) +} + +function parseStop(item) { + return dayjs(item.end) +} + +function parseItems(content, channel) { + if (!content) return [] + const [, channelId] = channel.site_id.split('#') + const data = JSON.parse(content) + if (!data || !Array.isArray(data.channels)) return [] + const channelData = data.channels.find(c => c.channelId == channelId) + if (!channelData || !Array.isArray(channelData.schedule)) return [] + + return channelData.schedule + .map(item => { + const details = data.assets[item.assetId] + if (!details) return null + + return { ...item, ...details } + }) + .filter(Boolean) +} diff --git a/sites/xumo.tv/xumo.tv.test.js b/sites/xumo.tv/xumo.tv.test.js index 3fc9c9d0e..329d2d735 100644 --- a/sites/xumo.tv/xumo.tv.test.js +++ b/sites/xumo.tv/xumo.tv.test.js @@ -1,74 +1,74 @@ -const { parser, url } = require('./xumo.tv.config.js') -const fs = require('fs') -const path = require('path') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios', () => { - return { - create: jest.fn().mockReturnValue({ - get: jest.fn() - }) - } -}) - -const API_ENDPOINT = 'https://valencia-app-mds.xumo.com/v2' - -const date = dayjs.utc('2022-11-06', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '0#99991247', - xmltv_id: 'NBCNewsNow.us' -} - -it('can generate valid url', () => { - expect(url({ date, channel })).toBe( - `${API_ENDPOINT}/epg/10006/20221106/0.json?f=asset.title&f=asset.descriptions&limit=50&offset=0` - ) -}) - -it('can parse response', async () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_0.json')) - - axios.create().get.mockImplementation(url => { - if ( - url === - `${API_ENDPOINT}/epg/10006/20221106/1.json?f=asset.title&f=asset.descriptions&limit=50&offset=0` - ) { - return Promise.resolve({ - data: Buffer.from(fs.readFileSync(path.resolve(__dirname, '__data__/content_1.json'))) - }) - } else { - return Promise.resolve({ data: '' }) - } - }) - - let results = await parser({ content, channel, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2022-11-05T23:00:00.000Z', - stop: '2022-11-06T01:00:00.000Z', - title: 'Dateline', - sub_title: 'The Disappearance of Laci Peterson', - description: - "After following Laci Peterson's case for more than 15 years, the show delivers a comprehensive report with rarely seen interrogation video, new insight from prosecutors, and surprising details from Amber Frey, who helped uncover the truth." - }) -}) - -it('can handle empty guide', async () => { - const results = await parser({ - content: Buffer.from(fs.readFileSync(path.resolve(__dirname, '__data__/no-content.json'))), - channel, - date - }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./xumo.tv.config.js') +const fs = require('fs') +const path = require('path') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios', () => { + return { + create: jest.fn().mockReturnValue({ + get: jest.fn() + }) + } +}) + +const API_ENDPOINT = 'https://valencia-app-mds.xumo.com/v2' + +const date = dayjs.utc('2022-11-06', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '0#99991247', + xmltv_id: 'NBCNewsNow.us' +} + +it('can generate valid url', () => { + expect(url({ date, channel })).toBe( + `${API_ENDPOINT}/epg/10006/20221106/0.json?f=asset.title&f=asset.descriptions&limit=50&offset=0` + ) +}) + +it('can parse response', async () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content_0.json')) + + axios.create().get.mockImplementation(url => { + if ( + url === + `${API_ENDPOINT}/epg/10006/20221106/1.json?f=asset.title&f=asset.descriptions&limit=50&offset=0` + ) { + return Promise.resolve({ + data: Buffer.from(fs.readFileSync(path.resolve(__dirname, '__data__/content_1.json'))) + }) + } else { + return Promise.resolve({ data: '' }) + } + }) + + let results = await parser({ content, channel, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2022-11-05T23:00:00.000Z', + stop: '2022-11-06T01:00:00.000Z', + title: 'Dateline', + sub_title: 'The Disappearance of Laci Peterson', + description: + "After following Laci Peterson's case for more than 15 years, the show delivers a comprehensive report with rarely seen interrogation video, new insight from prosecutors, and surprising details from Amber Frey, who helped uncover the truth." + }) +}) + +it('can handle empty guide', async () => { + const results = await parser({ + content: Buffer.from(fs.readFileSync(path.resolve(__dirname, '__data__/no-content.json'))), + channel, + date + }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/yes.co.il/yes.co.il.config.js b/sites/yes.co.il/yes.co.il.config.js index f60202bee..5594815ec 100644 --- a/sites/yes.co.il/yes.co.il.config.js +++ b/sites/yes.co.il/yes.co.il.config.js @@ -1,57 +1,57 @@ -const axios = require('axios') - -module.exports = { - site: 'yes.co.il', - days: 2, - url({ channel, date }) { - return `https://svc.yes.co.il/api/content/broadcast-schedule/channels/${ - channel.site_id - }?date=${date.format('YYYY-M-D')}&ignorePastItems=true` - }, - request: { - headers: { - 'user-agent': - 'Mozilla/5.0 (Linux; Linux x86_64) AppleWebKit/600.3 (KHTML, like Gecko) Chrome/48.0.2544.291 Safari/600' - } - }, - parser({ content }) { - const items = parseItems(content) - - return items.map(item => ({ - title: item.title, - description: item.description, - image: item.imageUrl, - start: item.starts, - stop: item.ends - })) - }, - async channels() { - const data = await axios - .get('https://svc.yes.co.il/api/content/broadcast-schedule/channels?page=0&pageSize=1000', { - headers: { - 'accept-language': 'he-IL', - 'user-agent': - 'Mozilla/5.0 (Linux; Linux x86_64) AppleWebKit/600.3 (KHTML, like Gecko) Chrome/48.0.2544.291 Safari/600' - } - }) - .then(r => r.data) - .catch(console.error) - - return data.items.map(channel => ({ - lang: 'he', - name: channel.title, - site_id: channel.channelId - })) - } -} - -function parseItems(content) { - try { - const data = JSON.parse(content) - if (!data || !Array.isArray(data.items)) return [] - - return data.items - } catch { - return [] - } -} +const axios = require('axios') + +module.exports = { + site: 'yes.co.il', + days: 2, + url({ channel, date }) { + return `https://svc.yes.co.il/api/content/broadcast-schedule/channels/${ + channel.site_id + }?date=${date.format('YYYY-M-D')}&ignorePastItems=true` + }, + request: { + headers: { + 'user-agent': + 'Mozilla/5.0 (Linux; Linux x86_64) AppleWebKit/600.3 (KHTML, like Gecko) Chrome/48.0.2544.291 Safari/600' + } + }, + parser({ content }) { + const items = parseItems(content) + + return items.map(item => ({ + title: item.title, + description: item.description, + image: item.imageUrl, + start: item.starts, + stop: item.ends + })) + }, + async channels() { + const data = await axios + .get('https://svc.yes.co.il/api/content/broadcast-schedule/channels?page=0&pageSize=1000', { + headers: { + 'accept-language': 'he-IL', + 'user-agent': + 'Mozilla/5.0 (Linux; Linux x86_64) AppleWebKit/600.3 (KHTML, like Gecko) Chrome/48.0.2544.291 Safari/600' + } + }) + .then(r => r.data) + .catch(console.error) + + return data.items.map(channel => ({ + lang: 'he', + name: channel.title, + site_id: channel.channelId + })) + } +} + +function parseItems(content) { + try { + const data = JSON.parse(content) + if (!data || !Array.isArray(data.items)) return [] + + return data.items + } catch { + return [] + } +} diff --git a/sites/yes.co.il/yes.co.il.test.js b/sites/yes.co.il/yes.co.il.test.js index e6aa8eebb..f0e346fa5 100644 --- a/sites/yes.co.il/yes.co.il.test.js +++ b/sites/yes.co.il/yes.co.il.test.js @@ -1,48 +1,48 @@ -const { parser, url, request } = require('./yes.co.il.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-01-30', 'YYYY-MM-DD').startOf('d') -const channel = { site_id: 'YSA1' } - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe( - 'https://svc.yes.co.il/api/content/broadcast-schedule/channels/YSA1?date=2025-1-30&ignorePastItems=true' - ) -}) - -it('can generate valid request headers', () => { - expect(request.headers).toMatchObject({ - 'user-agent': - 'Mozilla/5.0 (Linux; Linux x86_64) AppleWebKit/600.3 (KHTML, like Gecko) Chrome/48.0.2544.291 Safari/600' - }) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) - - let results = parser({ content }) - - expect(results.length).toBe(13) - expect(results[0]).toMatchObject({ - title: 'הבית', - description: - "דרמת מתח סוחפת. זוג צעיר שוכר וילה בכפר רומנטי באיטליה כדי לשפר את הזוגיות שלהם, אך עד מהרה מוצאים עצמם קורבנות בתוכנית זדונית של בעל המקום. עם: ארון פול ('שובר שורות'), אמילי רטאייקאוסקי. במאי: ג'ורג' רטליף. 2018.", - image: 'https://fykswkmjb.filerobot.com/VodAndHomeChan/VP001212610.JPG', - start: '2025-01-29T23:52:00Z', - stop: '2025-01-30T01:31:00Z' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - content: '{"items":[]}' - }) - - expect(results).toMatchObject([]) -}) +const { parser, url, request } = require('./yes.co.il.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-01-30', 'YYYY-MM-DD').startOf('d') +const channel = { site_id: 'YSA1' } + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe( + 'https://svc.yes.co.il/api/content/broadcast-schedule/channels/YSA1?date=2025-1-30&ignorePastItems=true' + ) +}) + +it('can generate valid request headers', () => { + expect(request.headers).toMatchObject({ + 'user-agent': + 'Mozilla/5.0 (Linux; Linux x86_64) AppleWebKit/600.3 (KHTML, like Gecko) Chrome/48.0.2544.291 Safari/600' + }) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json')) + + let results = parser({ content }) + + expect(results.length).toBe(13) + expect(results[0]).toMatchObject({ + title: 'הבית', + description: + "דרמת מתח סוחפת. זוג צעיר שוכר וילה בכפר רומנטי באיטליה כדי לשפר את הזוגיות שלהם, אך עד מהרה מוצאים עצמם קורבנות בתוכנית זדונית של בעל המקום. עם: ארון פול ('שובר שורות'), אמילי רטאייקאוסקי. במאי: ג'ורג' רטליף. 2018.", + image: 'https://fykswkmjb.filerobot.com/VodAndHomeChan/VP001212610.JPG', + start: '2025-01-29T23:52:00Z', + stop: '2025-01-30T01:31:00Z' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + content: '{"items":[]}' + }) + + expect(results).toMatchObject([]) +}) diff --git a/sites/zap.co.ao/zap.co.ao.config.js b/sites/zap.co.ao/zap.co.ao.config.js index 267ac8595..165900a0d 100644 --- a/sites/zap.co.ao/zap.co.ao.config.js +++ b/sites/zap.co.ao/zap.co.ao.config.js @@ -1,48 +1,48 @@ -const { DateTime } = require('luxon') -const axios = require('axios') - -module.exports = { - site: 'zap.co.ao', - days: 2, - url: function ({ date, channel }) { - return `https://zapon.zapsi.net/ao/m/api/epg/events?date=${date.format('YYYYMMDD')}&channel=${ - channel.site_id - }` - }, - parser: function ({ content }) { - const programs = [] - const items = parseItems(content) - if (!items.length) return programs - items.forEach(item => { - programs.push({ - title: item.programName, - description: item.programDescription, - category: item.categoryName, - start: DateTime.fromSeconds(item.utcBeginDate).toUTC(), - stop: DateTime.fromSeconds(item.utcEndDate).toUTC() - }) - }) - - return programs - }, - async channels() { - const channels = await axios - .get('https://zapon.zapsi.net/ao/m/api/epg/channels') - .then(r => r.data.data) - .catch(console.log) - - return channels.map(item => { - return { - lang: 'pt', - site_id: item.id, - name: item.name - } - }) - } -} - -function parseItems(content) { - const data = JSON.parse(content) - - return data.data || [] -} +const { DateTime } = require('luxon') +const axios = require('axios') + +module.exports = { + site: 'zap.co.ao', + days: 2, + url: function ({ date, channel }) { + return `https://zapon.zapsi.net/ao/m/api/epg/events?date=${date.format('YYYYMMDD')}&channel=${ + channel.site_id + }` + }, + parser: function ({ content }) { + const programs = [] + const items = parseItems(content) + if (!items.length) return programs + items.forEach(item => { + programs.push({ + title: item.programName, + description: item.programDescription, + category: item.categoryName, + start: DateTime.fromSeconds(item.utcBeginDate).toUTC(), + stop: DateTime.fromSeconds(item.utcEndDate).toUTC() + }) + }) + + return programs + }, + async channels() { + const channels = await axios + .get('https://zapon.zapsi.net/ao/m/api/epg/channels') + .then(r => r.data.data) + .catch(console.log) + + return channels.map(item => { + return { + lang: 'pt', + site_id: item.id, + name: item.name + } + }) + } +} + +function parseItems(content) { + const data = JSON.parse(content) + + return data.data || [] +} diff --git a/sites/zap.co.ao/zap.co.ao.test.js b/sites/zap.co.ao/zap.co.ao.test.js index 53de59bb0..1f04c99d6 100644 --- a/sites/zap.co.ao/zap.co.ao.test.js +++ b/sites/zap.co.ao/zap.co.ao.test.js @@ -1,45 +1,45 @@ -const { parser, url } = require('./zap.co.ao.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2023-05-28', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: '2275', - xmltv_id: 'TPA1.ao' -} - -it('can generate valid url', () => { - expect(url({ date, channel })).toBe( - 'https://zapon.zapsi.net/ao/m/api/epg/events?date=20230528&channel=2275' - ) -}) - -it('can parse response', () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') - const results = parser({ content }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2023-05-27T23:00:00.000Z', - stop: '2023-05-28T00:00:00.000Z', - title: 'Jornal da Meia-Noite', - description: - 'Um jornal diferente do Telejornal, por conter análise, comentários e coluna com jornalistas experientes sobre factos do dia a dia.', - category: 'Noticiário' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ - content: '[]' - }) - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./zap.co.ao.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2023-05-28', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: '2275', + xmltv_id: 'TPA1.ao' +} + +it('can generate valid url', () => { + expect(url({ date, channel })).toBe( + 'https://zapon.zapsi.net/ao/m/api/epg/events?date=20230528&channel=2275' + ) +}) + +it('can parse response', () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') + const results = parser({ content }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2023-05-27T23:00:00.000Z', + stop: '2023-05-28T00:00:00.000Z', + title: 'Jornal da Meia-Noite', + description: + 'Um jornal diferente do Telejornal, por conter análise, comentários e coluna com jornalistas experientes sobre factos do dia a dia.', + category: 'Noticiário' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ + content: '[]' + }) + expect(results).toMatchObject([]) +}) diff --git a/sites/zap2it.com/zap2it.com.config.js b/sites/zap2it.com/zap2it.com.config.js index 91a2fb867..9cb96a66b 100644 --- a/sites/zap2it.com/zap2it.com.config.js +++ b/sites/zap2it.com/zap2it.com.config.js @@ -1,71 +1,71 @@ -const dayjs = require('dayjs') -const timezone = require('dayjs/plugin/timezone') -const utc = require('dayjs/plugin/utc') -const isBetween = require('dayjs/plugin/isBetween') - -dayjs.extend(timezone) -dayjs.extend(utc) -dayjs.extend(isBetween) - -module.exports = { - site: 'zap2it.com', - days: 2, - url: 'https://tvlistings.gracenote.com/api/sslgrid', - request: { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36', - }, - data({ date, channel }) { - const [device, lineupId, headendId, countryCode, postalCode, prgsvcid] = channel.site_id.split('/') - - const timestamp = dayjs(date).unix().toString() - - return { - lineupId, - IsSSLinkNavigation: 'true', - timespan: '336', - timestamp, - prgsvcid, - headendId, - countryCode, - postalCode, - device, - userId: '-', - aid: 'gapzap', - DSTUTCOffset: '-240', - STDUTCOffset: '-300', - DSTStart: '2025-03-09T02:00Z', - DSTEnd: '2025-11-02T02:00Z', - languagecode: 'en-us', - } - }, - }, - parser: function ({ content, date }) { - const data = JSON.parse(content) - const programs = [] - - Object.keys(data).forEach(dateKey => { - data[dateKey].forEach(item => { - programs.push({ - title: item.program.title, - subTitle: item.program.episodeTitle || '', - description: item.program.shortDesc || '', - genres: item.program.genres ? item.program.genres.map(genre => genre.name) : [], - start: dayjs.unix(item.startTime).utc().format('YYYY-MM-DD HH:mm:ss'), - stop: dayjs.unix(item.endTime).utc().format('YYYY-MM-DD HH:mm:ss'), - icon: item.thumbnail ? `https://zap2it.tmsimg.com/assets/${item.thumbnail}.jpg` : '', - rating: item.rating || '', - season: item.program.season || '', - episode: item.program.episode || '', - date: item.program.releaseYear || '', - }) - }) - }) - - return programs.filter(p => dayjs(p.start).add(dayjs(p.start).utcOffset(), 'minute').isBetween(date.startOf('day').subtract(dayjs().utcOffset(), 'minute').utc(), - date.endOf('day').subtract(dayjs().utcOffset(), 'minute').utc(), 'second', '[]')) - } -} +const dayjs = require('dayjs') +const timezone = require('dayjs/plugin/timezone') +const utc = require('dayjs/plugin/utc') +const isBetween = require('dayjs/plugin/isBetween') + +dayjs.extend(timezone) +dayjs.extend(utc) +dayjs.extend(isBetween) + +module.exports = { + site: 'zap2it.com', + days: 2, + url: 'https://tvlistings.gracenote.com/api/sslgrid', + request: { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36', + }, + data({ date, channel }) { + const [device, lineupId, headendId, countryCode, postalCode, prgsvcid] = channel.site_id.split('/') + + const timestamp = dayjs(date).unix().toString() + + return { + lineupId, + IsSSLinkNavigation: 'true', + timespan: '336', + timestamp, + prgsvcid, + headendId, + countryCode, + postalCode, + device, + userId: '-', + aid: 'gapzap', + DSTUTCOffset: '-240', + STDUTCOffset: '-300', + DSTStart: '2025-03-09T02:00Z', + DSTEnd: '2025-11-02T02:00Z', + languagecode: 'en-us', + } + }, + }, + parser: function ({ content, date }) { + const data = JSON.parse(content) + const programs = [] + + Object.keys(data).forEach(dateKey => { + data[dateKey].forEach(item => { + programs.push({ + title: item.program.title, + subTitle: item.program.episodeTitle || '', + description: item.program.shortDesc || '', + genres: item.program.genres ? item.program.genres.map(genre => genre.name) : [], + start: dayjs.unix(item.startTime).utc().format('YYYY-MM-DD HH:mm:ss'), + stop: dayjs.unix(item.endTime).utc().format('YYYY-MM-DD HH:mm:ss'), + icon: item.thumbnail ? `https://zap2it.tmsimg.com/assets/${item.thumbnail}.jpg` : '', + rating: item.rating || '', + season: item.program.season || '', + episode: item.program.episode || '', + date: item.program.releaseYear || '', + }) + }) + }) + + return programs.filter(p => dayjs(p.start).add(dayjs(p.start).utcOffset(), 'minute').isBetween(date.startOf('day').subtract(dayjs().utcOffset(), 'minute').utc(), + date.endOf('day').subtract(dayjs().utcOffset(), 'minute').utc(), 'second', '[]')) + } +} diff --git a/sites/zap2it.com/zap2it.com.test.js b/sites/zap2it.com/zap2it.com.test.js index 0b8b64668..625abac66 100644 --- a/sites/zap2it.com/zap2it.com.test.js +++ b/sites/zap2it.com/zap2it.com.test.js @@ -1,74 +1,74 @@ -const { parser, url } = require('./zap2it.com.config.js') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -// Se cambia la fecha para que, tras aplicar el offset (debido a TZ=Pacific/Nauru), -// el rango filtrado incluya los programas cuyo start en UTC es el 6 de febrero. -const date = dayjs('2025-02-07', 'YYYY-MM-DD').startOf('d') -const channel = { site_id: 'X/USA-NY31695-DEFAULT/NY31695/USA/13302/49141', xmltv_id: 'Spectrum News 1' } - -it('can generate valid url', () => { - expect(url).toBe('https://tvlistings.gracenote.com/api/sslgrid') -}) - -it('can parse response', () => { - const content = JSON.stringify({ - '2025-02-06': [ - { - program: { - title: 'Your Afternoon on Spectrum News 1 - Central NY', - episodeTitle: '', - shortDesc: '', - genres: [], - season: '', - episode: '', - releaseYear: '' - }, - startTime: 1738868400, - endTime: 1738872000, - thumbnail: '' - }, - { - program: { - title: 'Your Afternoon on Spectrum News 1 - Central NY', - episodeTitle: '', - shortDesc: '', - genres: [], - season: '', - episode: '', - releaseYear: '' - }, - startTime: 1738872000, - endTime: 1738875600, - thumbnail: '' - } - ] - }) - - const results = parser({ content, date }) - - expect(results.length).toBe(2) - expect(results[0]).toMatchObject({ - title: 'Your Afternoon on Spectrum News 1 - Central NY', - start: dayjs.unix(1738868400).utc().format('YYYY-MM-DD HH:mm:ss'), - stop: dayjs.unix(1738872000).utc().format('YYYY-MM-DD HH:mm:ss') - }) - expect(results[1]).toMatchObject({ - title: 'Your Afternoon on Spectrum News 1 - Central NY', - start: dayjs.unix(1738872000).utc().format('YYYY-MM-DD HH:mm:ss'), - stop: dayjs.unix(1738875600).utc().format('YYYY-MM-DD HH:mm:ss') - }) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: '{}' - }) - - expect(result).toEqual([]) -}) +const { parser, url } = require('./zap2it.com.config.js') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +// Se cambia la fecha para que, tras aplicar el offset (debido a TZ=Pacific/Nauru), +// el rango filtrado incluya los programas cuyo start en UTC es el 6 de febrero. +const date = dayjs('2025-02-07', 'YYYY-MM-DD').startOf('d') +const channel = { site_id: 'X/USA-NY31695-DEFAULT/NY31695/USA/13302/49141', xmltv_id: 'Spectrum News 1' } + +it('can generate valid url', () => { + expect(url).toBe('https://tvlistings.gracenote.com/api/sslgrid') +}) + +it('can parse response', () => { + const content = JSON.stringify({ + '2025-02-06': [ + { + program: { + title: 'Your Afternoon on Spectrum News 1 - Central NY', + episodeTitle: '', + shortDesc: '', + genres: [], + season: '', + episode: '', + releaseYear: '' + }, + startTime: 1738868400, + endTime: 1738872000, + thumbnail: '' + }, + { + program: { + title: 'Your Afternoon on Spectrum News 1 - Central NY', + episodeTitle: '', + shortDesc: '', + genres: [], + season: '', + episode: '', + releaseYear: '' + }, + startTime: 1738872000, + endTime: 1738875600, + thumbnail: '' + } + ] + }) + + const results = parser({ content, date }) + + expect(results.length).toBe(2) + expect(results[0]).toMatchObject({ + title: 'Your Afternoon on Spectrum News 1 - Central NY', + start: dayjs.unix(1738868400).utc().format('YYYY-MM-DD HH:mm:ss'), + stop: dayjs.unix(1738872000).utc().format('YYYY-MM-DD HH:mm:ss') + }) + expect(results[1]).toMatchObject({ + title: 'Your Afternoon on Spectrum News 1 - Central NY', + start: dayjs.unix(1738872000).utc().format('YYYY-MM-DD HH:mm:ss'), + stop: dayjs.unix(1738875600).utc().format('YYYY-MM-DD HH:mm:ss') + }) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: '{}' + }) + + expect(result).toEqual([]) +}) diff --git a/sites/ziggogo.tv/ziggogo.tv.config.js b/sites/ziggogo.tv/ziggogo.tv.config.js index 480904dcb..ecc5ad2bd 100644 --- a/sites/ziggogo.tv/ziggogo.tv.config.js +++ b/sites/ziggogo.tv/ziggogo.tv.config.js @@ -1,114 +1,114 @@ -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const doFetch = require('@ntlab/sfetch') -const debug = require('debug')('site:ziggogo.tv') - -dayjs.extend(utc) - -doFetch.setDebugger(debug) - -const detailedGuide = true - -module.exports = { - site: 'ziggogo.tv', - days: 2, - request: { - cache: { - ttl: 24 * 60 * 60 * 1000 // 1 day - } - }, - url({ date, segment = 0 }) { - return `https://static.spark.ziggogo.tv/eng/web/epg-service-lite/nl/en/events/segments/${date.format( - 'YYYYMMDD' - )}${segment.toString().padStart(2, '0')}0000` - }, - async parser({ content, channel, date }) { - const programs = [] - if (content) { - const items = typeof content === 'string' ? JSON.parse(content) : content - if (Array.isArray(items.entries)) { - // fetch other segments - const queues = [ - module.exports.url({ date, segment: 6 }), - module.exports.url({ date, segment: 12 }), - module.exports.url({ date, segment: 18 }) - ] - await doFetch(queues, (url, res) => { - if (Array.isArray(res.entries)) { - items.entries.push(...res.entries) - } - }) - items.entries - .filter(item => item.channelId === channel.site_id) - .forEach(item => { - if (Array.isArray(item.events)) { - if (detailedGuide) { - queues.push( - ...item.events.map( - event => - `https://spark-prod-nl.gnp.cloud.ziggogo.tv/eng/web/linear-service/v2/replayEvent/${event.id}?returnLinearContent=true&forceLinearResponse=true&language=nl` - ) - ) - } else { - item.events.forEach(event => { - programs.push({ - title: event.title, - start: dayjs.utc(event.startTime * 1000), - stop: dayjs.utc(event.endTime * 1000) - }) - }) - } - } - }) - // fetch detailed guide - if (queues.length) { - await doFetch(queues, (url, res) => { - programs.push({ - title: res.title, - subTitle: res.episodeName, - description: res.longDescription ? res.longDescription : res.shortDescription, - category: res.genres, - season: res.seasonNumber, - episode: res.episodeNumber, - country: res.countryOfOrigin, - actor: res.actors, - director: res.directors, - producer: res.producers, - date: res.productionDate, - start: dayjs.utc(res.startTime * 1000), - stop: dayjs.utc(res.endTime * 1000) - }) - }) - } - } - } - - return programs - }, - async channels() { - const channels = [] - const axios = require('axios') - const res = await axios - .get( - 'https://spark-prod-nl.gnp.cloud.ziggogo.tv/eng/web/linear-service/v2/channels?cityId=65535&language=en&productClass=Orion-DASH&platform=web' - ) - .then(r => r.data) - .catch(console.error) - - if (Array.isArray(res)) { - channels.push( - ...res - .filter(item => !item.isHidden) - .map(item => { - return { - lang: 'nl', - site_id: item.id, - name: item.name - } - }) - ) - } - - return channels - } -} +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const doFetch = require('@ntlab/sfetch') +const debug = require('debug')('site:ziggogo.tv') + +dayjs.extend(utc) + +doFetch.setDebugger(debug) + +const detailedGuide = true + +module.exports = { + site: 'ziggogo.tv', + days: 2, + request: { + cache: { + ttl: 24 * 60 * 60 * 1000 // 1 day + } + }, + url({ date, segment = 0 }) { + return `https://static.spark.ziggogo.tv/eng/web/epg-service-lite/nl/en/events/segments/${date.format( + 'YYYYMMDD' + )}${segment.toString().padStart(2, '0')}0000` + }, + async parser({ content, channel, date }) { + const programs = [] + if (content) { + const items = typeof content === 'string' ? JSON.parse(content) : content + if (Array.isArray(items.entries)) { + // fetch other segments + const queues = [ + module.exports.url({ date, segment: 6 }), + module.exports.url({ date, segment: 12 }), + module.exports.url({ date, segment: 18 }) + ] + await doFetch(queues, (url, res) => { + if (Array.isArray(res.entries)) { + items.entries.push(...res.entries) + } + }) + items.entries + .filter(item => item.channelId === channel.site_id) + .forEach(item => { + if (Array.isArray(item.events)) { + if (detailedGuide) { + queues.push( + ...item.events.map( + event => + `https://spark-prod-nl.gnp.cloud.ziggogo.tv/eng/web/linear-service/v2/replayEvent/${event.id}?returnLinearContent=true&forceLinearResponse=true&language=nl` + ) + ) + } else { + item.events.forEach(event => { + programs.push({ + title: event.title, + start: dayjs.utc(event.startTime * 1000), + stop: dayjs.utc(event.endTime * 1000) + }) + }) + } + } + }) + // fetch detailed guide + if (queues.length) { + await doFetch(queues, (url, res) => { + programs.push({ + title: res.title, + subTitle: res.episodeName, + description: res.longDescription ? res.longDescription : res.shortDescription, + category: res.genres, + season: res.seasonNumber, + episode: res.episodeNumber, + country: res.countryOfOrigin, + actor: res.actors, + director: res.directors, + producer: res.producers, + date: res.productionDate, + start: dayjs.utc(res.startTime * 1000), + stop: dayjs.utc(res.endTime * 1000) + }) + }) + } + } + } + + return programs + }, + async channels() { + const channels = [] + const axios = require('axios') + const res = await axios + .get( + 'https://spark-prod-nl.gnp.cloud.ziggogo.tv/eng/web/linear-service/v2/channels?cityId=65535&language=en&productClass=Orion-DASH&platform=web' + ) + .then(r => r.data) + .catch(console.error) + + if (Array.isArray(res)) { + channels.push( + ...res + .filter(item => !item.isHidden) + .map(item => { + return { + lang: 'nl', + site_id: item.id, + name: item.name + } + }) + ) + } + + return channels + } +} diff --git a/sites/ziggogo.tv/ziggogo.tv.test.js b/sites/ziggogo.tv/ziggogo.tv.test.js index bfc5b2d44..39a603d12 100644 --- a/sites/ziggogo.tv/ziggogo.tv.test.js +++ b/sites/ziggogo.tv/ziggogo.tv.test.js @@ -1,105 +1,105 @@ -const { parser, url } = require('./ziggogo.tv.config.js') -const fs = require('fs') -const path = require('path') -const axios = require('axios') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -const date = dayjs.utc('2024-12-17', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'NL_000001_019401', - xmltv_id: 'NPO1.nl' -} - -axios.get.mockImplementation(url => { - const urls = { - 'https://static.spark.ziggogo.tv/eng/web/epg-service-lite/nl/en/events/segments/20241217000000': - 'content00.json', - 'https://static.spark.ziggogo.tv/eng/web/epg-service-lite/nl/en/events/segments/20241217060000': - 'content06.json', - 'https://static.spark.ziggogo.tv/eng/web/epg-service-lite/nl/en/events/segments/20241217120000': - 'content12.json', - 'https://static.spark.ziggogo.tv/eng/web/epg-service-lite/nl/en/events/segments/20241217180000': - 'content18.json', - 'https://spark-prod-nl.gnp.cloud.ziggogo.tv/eng/web/linear-service/v2/replayEvent/crid:~~2F~~2Fgn.tv~~2F28844562~~2FEP027607161610,imi:1d49feeb2ef4e3db0bde030e7cf6e55e06d56fed?returnLinearContent=true&forceLinearResponse=true&language=nl': - 'program01.json', - 'https://spark-prod-nl.gnp.cloud.ziggogo.tv/eng/web/linear-service/v2/replayEvent/crid:~~2F~~2Fgn.tv~~2F28842707~~2FEP022675661065,imi:33138a61bfa639696f386a5b8da9052e98cffdf8?returnLinearContent=true&forceLinearResponse=true&language=nl': - 'program02.json', - 'https://spark-prod-nl.gnp.cloud.ziggogo.tv/eng/web/linear-service/v2/replayEvent/crid:~~2F~~2Fgn.tv~~2F28728829~~2FEP052397600066,imi:34a0b026912de96e3546b15ad2983070a250dfd5?returnLinearContent=true&forceLinearResponse=true&language=nl': - 'program03.json' - } - let data = '' - if (urls[url] !== undefined) { - data = fs.readFileSync(path.join(__dirname, '__data__', urls[url])).toString() - if (!urls[url].startsWith('content00')) { - data = JSON.parse(data) - } - } - return Promise.resolve({ data }) -}) - -it('can generate valid url', () => { - expect(url({ date })).toBe( - 'https://static.spark.ziggogo.tv/eng/web/epg-service-lite/nl/en/events/segments/20241217000000' - ) -}) - -it('can parse response', async () => { - const content = await axios - .get(url({ date })) - .then(response => response.data) - .catch(console.error) - const result = (await parser({ content, channel, date })).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result.length).toBe(3) - expect(result[0]).toMatchObject({ - start: '2024-12-17T00:10:00.000Z', - stop: '2024-12-17T00:35:00.000Z', - title: 'EenVandaag', - description: - 'Op pad met HTS-rebellen in Syrië. Nieuwe aanpak tegen te veel zitten. VS heeft Tiktok-ban bijna rond. Wat is de rol van Nederland in de onderhandeling rondom Oekraïne?', - category: ['Nieuws', 'Actualiteit'], - season: 11, - episode: 300, - actor: [ - 'Rik van de Westelaken', - 'Roos Moggré', - 'Pieter Jan Hagens', - 'Toine van Peperstraten', - 'Charlotte Nijs', - 'Hila Noorzai', - 'Rob Hadders', - 'Joyce Boverhuis' - ] - }) - expect(result[2]).toMatchObject({ - start: '2024-12-17T14:55:00.000Z', - stop: '2024-12-17T15:58:00.000Z', - title: 'Bar Laat', - description: - 'Bij het Rijnstate Ziekenhuis zijn opnieuw enorme misstanden aan het licht gekomen rond spermadonatie. KRO-NCRV maakte er een docuserie over. Maker Annemieke Ruggenberg schuift aan samen met zaaddonor Peter en donorkinderen Roos en Maria.', - category: ['Talkshow'], - season: 1, - episode: 65, - actor: ['Sophie Hilbrand', 'Jeroen Pauw', 'Tim de Wit'] - }) -}) - -it('can handle empty guide', async () => { - const result = await parser({ - content: '', - channel, - date - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./ziggogo.tv.config.js') +const fs = require('fs') +const path = require('path') +const axios = require('axios') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.utc('2024-12-17', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'NL_000001_019401', + xmltv_id: 'NPO1.nl' +} + +axios.get.mockImplementation(url => { + const urls = { + 'https://static.spark.ziggogo.tv/eng/web/epg-service-lite/nl/en/events/segments/20241217000000': + 'content00.json', + 'https://static.spark.ziggogo.tv/eng/web/epg-service-lite/nl/en/events/segments/20241217060000': + 'content06.json', + 'https://static.spark.ziggogo.tv/eng/web/epg-service-lite/nl/en/events/segments/20241217120000': + 'content12.json', + 'https://static.spark.ziggogo.tv/eng/web/epg-service-lite/nl/en/events/segments/20241217180000': + 'content18.json', + 'https://spark-prod-nl.gnp.cloud.ziggogo.tv/eng/web/linear-service/v2/replayEvent/crid:~~2F~~2Fgn.tv~~2F28844562~~2FEP027607161610,imi:1d49feeb2ef4e3db0bde030e7cf6e55e06d56fed?returnLinearContent=true&forceLinearResponse=true&language=nl': + 'program01.json', + 'https://spark-prod-nl.gnp.cloud.ziggogo.tv/eng/web/linear-service/v2/replayEvent/crid:~~2F~~2Fgn.tv~~2F28842707~~2FEP022675661065,imi:33138a61bfa639696f386a5b8da9052e98cffdf8?returnLinearContent=true&forceLinearResponse=true&language=nl': + 'program02.json', + 'https://spark-prod-nl.gnp.cloud.ziggogo.tv/eng/web/linear-service/v2/replayEvent/crid:~~2F~~2Fgn.tv~~2F28728829~~2FEP052397600066,imi:34a0b026912de96e3546b15ad2983070a250dfd5?returnLinearContent=true&forceLinearResponse=true&language=nl': + 'program03.json' + } + let data = '' + if (urls[url] !== undefined) { + data = fs.readFileSync(path.join(__dirname, '__data__', urls[url])).toString() + if (!urls[url].startsWith('content00')) { + data = JSON.parse(data) + } + } + return Promise.resolve({ data }) +}) + +it('can generate valid url', () => { + expect(url({ date })).toBe( + 'https://static.spark.ziggogo.tv/eng/web/epg-service-lite/nl/en/events/segments/20241217000000' + ) +}) + +it('can parse response', async () => { + const content = await axios + .get(url({ date })) + .then(response => response.data) + .catch(console.error) + const result = (await parser({ content, channel, date })).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result.length).toBe(3) + expect(result[0]).toMatchObject({ + start: '2024-12-17T00:10:00.000Z', + stop: '2024-12-17T00:35:00.000Z', + title: 'EenVandaag', + description: + 'Op pad met HTS-rebellen in Syrië. Nieuwe aanpak tegen te veel zitten. VS heeft Tiktok-ban bijna rond. Wat is de rol van Nederland in de onderhandeling rondom Oekraïne?', + category: ['Nieuws', 'Actualiteit'], + season: 11, + episode: 300, + actor: [ + 'Rik van de Westelaken', + 'Roos Moggré', + 'Pieter Jan Hagens', + 'Toine van Peperstraten', + 'Charlotte Nijs', + 'Hila Noorzai', + 'Rob Hadders', + 'Joyce Boverhuis' + ] + }) + expect(result[2]).toMatchObject({ + start: '2024-12-17T14:55:00.000Z', + stop: '2024-12-17T15:58:00.000Z', + title: 'Bar Laat', + description: + 'Bij het Rijnstate Ziekenhuis zijn opnieuw enorme misstanden aan het licht gekomen rond spermadonatie. KRO-NCRV maakte er een docuserie over. Maker Annemieke Ruggenberg schuift aan samen met zaaddonor Peter en donorkinderen Roos en Maria.', + category: ['Talkshow'], + season: 1, + episode: 65, + actor: ['Sophie Hilbrand', 'Jeroen Pauw', 'Tim de Wit'] + }) +}) + +it('can handle empty guide', async () => { + const result = await parser({ + content: '', + channel, + date + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/znbc.co.zm/znbc.co.zm.config.js b/sites/znbc.co.zm/znbc.co.zm.config.js index 3d020e88f..555d51e0d 100644 --- a/sites/znbc.co.zm/znbc.co.zm.config.js +++ b/sites/znbc.co.zm/znbc.co.zm.config.js @@ -1,65 +1,65 @@ -const cheerio = require('cheerio') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') -const tabletojson = require('tabletojson').Tabletojson - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'znbc.co.zm', - days: 2, - url({ channel }) { - return `https://www.znbc.co.zm/${channel.site_id}/` - }, - parser({ content, date }) { - const programs = [] - const items = parseItems(content, date) - items.forEach(item => { - const prev = programs[programs.length - 1] - let start = parseStart(item, date) - if (prev) { - if (start.isBefore(prev.start)) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.add(30, 'm') - programs.push({ title: item.title, start, stop }) - }) - - return programs - } -} - -function parseStart(item, date) { - const dateString = `${date.format('YYYY-MM-DD')} ${item.time}` - - return dayjs.tz(dateString, 'YYYY-MM-DD HH:mm', 'Africa/Lusaka') -} - -function parseItems(content, date) { - const dayOfWeek = date.format('dddd').toUpperCase() - const $ = cheerio.load(content) - const table = $(`.elementor-tab-mobile-title:contains("${dayOfWeek}")`).next().html() - if (!table) return [] - const data = tabletojson.convert(table) - if (!Array.isArray(data) || !Array.isArray(data[0])) return [] - - return data[0] - .map(row => { - const [, time, title] = row['0'].replace(/\s\s/g, ' ').match(/^(\d{2}:\d{2}) (.*)/) || [ - null, - null, - null - ] - if (!time || !title.trim()) return null - - return { time, title: title.trim() } - }) - .filter(i => i) -} +const cheerio = require('cheerio') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') +const tabletojson = require('tabletojson').Tabletojson + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'znbc.co.zm', + days: 2, + url({ channel }) { + return `https://www.znbc.co.zm/${channel.site_id}/` + }, + parser({ content, date }) { + const programs = [] + const items = parseItems(content, date) + items.forEach(item => { + const prev = programs[programs.length - 1] + let start = parseStart(item, date) + if (prev) { + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.add(30, 'm') + programs.push({ title: item.title, start, stop }) + }) + + return programs + } +} + +function parseStart(item, date) { + const dateString = `${date.format('YYYY-MM-DD')} ${item.time}` + + return dayjs.tz(dateString, 'YYYY-MM-DD HH:mm', 'Africa/Lusaka') +} + +function parseItems(content, date) { + const dayOfWeek = date.format('dddd').toUpperCase() + const $ = cheerio.load(content) + const table = $(`.elementor-tab-mobile-title:contains("${dayOfWeek}")`).next().html() + if (!table) return [] + const data = tabletojson.convert(table) + if (!Array.isArray(data) || !Array.isArray(data[0])) return [] + + return data[0] + .map(row => { + const [, time, title] = row['0'].replace(/\s\s/g, ' ').match(/^(\d{2}:\d{2}) (.*)/) || [ + null, + null, + null + ] + if (!time || !title.trim()) return null + + return { time, title: title.trim() } + }) + .filter(i => i) +} diff --git a/sites/znbc.co.zm/znbc.co.zm.test.js b/sites/znbc.co.zm/znbc.co.zm.test.js index 1bbe0835f..e8ddf2be7 100644 --- a/sites/znbc.co.zm/znbc.co.zm.test.js +++ b/sites/znbc.co.zm/znbc.co.zm.test.js @@ -1,53 +1,53 @@ -const { parser, url } = require('./znbc.co.zm.config.js') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2021-11-25', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'tv1', - xmltv_id: 'ZNBCTV1.zm' -} -const content = - '
    ' - -it('can generate valid url', () => { - expect(url({ channel })).toBe('https://www.znbc.co.zm/tv1/') -}) - -it('can parse response', () => { - const result = parser({ content, channel, date }).map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(result).toMatchObject([ - { - start: '2021-11-24T22:00:00.000Z', - stop: '2021-11-24T23:00:00.000Z', - title: 'MAIN NEWS – RPT' - }, - { - start: '2021-11-24T23:00:00.000Z', - stop: '2021-11-25T00:00:00.000Z', - title: 'BORN & BRED – Rebroadcast (Tuesday Edition)' - }, - { - start: '2021-11-25T00:00:00.000Z', - stop: '2021-11-25T00:30:00.000Z', - title: 'DOCUMENTARY – DW' - } - ]) -}) - -it('can handle empty guide', () => { - const result = parser({ - date, - channel, - content: '' - }) - expect(result).toMatchObject([]) -}) +const { parser, url } = require('./znbc.co.zm.config.js') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2021-11-25', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'tv1', + xmltv_id: 'ZNBCTV1.zm' +} +const content = + '
    ' + +it('can generate valid url', () => { + expect(url({ channel })).toBe('https://www.znbc.co.zm/tv1/') +}) + +it('can parse response', () => { + const result = parser({ content, channel, date }).map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(result).toMatchObject([ + { + start: '2021-11-24T22:00:00.000Z', + stop: '2021-11-24T23:00:00.000Z', + title: 'MAIN NEWS – RPT' + }, + { + start: '2021-11-24T23:00:00.000Z', + stop: '2021-11-25T00:00:00.000Z', + title: 'BORN & BRED – Rebroadcast (Tuesday Edition)' + }, + { + start: '2021-11-25T00:00:00.000Z', + stop: '2021-11-25T00:30:00.000Z', + title: 'DOCUMENTARY – DW' + } + ]) +}) + +it('can handle empty guide', () => { + const result = parser({ + date, + channel, + content: '' + }) + expect(result).toMatchObject([]) +}) diff --git a/sites/zuragt.mn/zuragt.mn.config.js b/sites/zuragt.mn/zuragt.mn.config.js index e20681b42..ed197342d 100644 --- a/sites/zuragt.mn/zuragt.mn.config.js +++ b/sites/zuragt.mn/zuragt.mn.config.js @@ -1,89 +1,89 @@ -const dayjs = require('dayjs') -const axios = require('axios') -const cheerio = require('cheerio') -const utc = require('dayjs/plugin/utc') -const timezone = require('dayjs/plugin/timezone') -const customParseFormat = require('dayjs/plugin/customParseFormat') - -dayjs.extend(utc) -dayjs.extend(timezone) -dayjs.extend(customParseFormat) - -module.exports = { - site: 'zuragt.mn', - days: 2, - url: function ({ channel, date }) { - return `https://m.zuragt.mn/channel/${channel.site_id}/?date=${date.format('YYYY-MM-DD')}` - }, - request: { - maxRedirects: 0, - validateStatus: function (status) { - return status >= 200 && status < 303 - } - }, - parser: async function ({ content, date }) { - let programs = [] - const items = parseItems(content) - for (let item of items) { - const prev = programs[programs.length - 1] - const $item = cheerio.load(item) - let start = parseStart($item, date) - if (prev) { - if (start.isBefore(prev.start)) { - start = start.add(1, 'd') - date = date.add(1, 'd') - } - prev.stop = start - } - const stop = start.add(30, 'm') - programs.push({ - title: parseTitle($item), - start, - stop - }) - } - - return programs - }, - async channels() { - let html = await axios - .get('https://www.zuragt.mn/') - .then(r => r.data) - .catch(console.log) - let $ = cheerio.load(html) - - const items = $('.tv-box > ul > li').toArray() - return items - .map(item => { - const name = $(item).text().trim() - const link = $(item).find('a').attr('href') - - if (!link) return null - - const [, site_id] = link.match(/\/channel\/(.*)\//) || [null, null] - - return { - lang: 'mn', - site_id, - name - } - }) - .filter(Boolean) - } -} - -function parseTitle($item) { - return $item('.program').text().trim() -} - -function parseStart($item, date) { - const time = $item('.time') - - return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'Asia/Ulaanbaatar') -} - -function parseItems(content) { - const $ = cheerio.load(content) - - return $('body > div > div > div > ul > li').toArray() -} +const dayjs = require('dayjs') +const axios = require('axios') +const cheerio = require('cheerio') +const utc = require('dayjs/plugin/utc') +const timezone = require('dayjs/plugin/timezone') +const customParseFormat = require('dayjs/plugin/customParseFormat') + +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(customParseFormat) + +module.exports = { + site: 'zuragt.mn', + days: 2, + url: function ({ channel, date }) { + return `https://m.zuragt.mn/channel/${channel.site_id}/?date=${date.format('YYYY-MM-DD')}` + }, + request: { + maxRedirects: 0, + validateStatus: function (status) { + return status >= 200 && status < 303 + } + }, + parser: async function ({ content, date }) { + let programs = [] + const items = parseItems(content) + for (let item of items) { + const prev = programs[programs.length - 1] + const $item = cheerio.load(item) + let start = parseStart($item, date) + if (prev) { + if (start.isBefore(prev.start)) { + start = start.add(1, 'd') + date = date.add(1, 'd') + } + prev.stop = start + } + const stop = start.add(30, 'm') + programs.push({ + title: parseTitle($item), + start, + stop + }) + } + + return programs + }, + async channels() { + let html = await axios + .get('https://www.zuragt.mn/') + .then(r => r.data) + .catch(console.log) + let $ = cheerio.load(html) + + const items = $('.tv-box > ul > li').toArray() + return items + .map(item => { + const name = $(item).text().trim() + const link = $(item).find('a').attr('href') + + if (!link) return null + + const [, site_id] = link.match(/\/channel\/(.*)\//) || [null, null] + + return { + lang: 'mn', + site_id, + name + } + }) + .filter(Boolean) + } +} + +function parseTitle($item) { + return $item('.program').text().trim() +} + +function parseStart($item, date) { + const time = $item('.time') + + return dayjs.tz(`${date.format('YYYY-MM-DD')} ${time}`, 'YYYY-MM-DD HH:mm', 'Asia/Ulaanbaatar') +} + +function parseItems(content) { + const $ = cheerio.load(content) + + return $('body > div > div > div > ul > li').toArray() +} diff --git a/sites/zuragt.mn/zuragt.mn.test.js b/sites/zuragt.mn/zuragt.mn.test.js index a7f9f693b..e1d471eaa 100644 --- a/sites/zuragt.mn/zuragt.mn.test.js +++ b/sites/zuragt.mn/zuragt.mn.test.js @@ -1,46 +1,46 @@ -const { parser, url, request } = require('./zuragt.mn.config.js') -const fs = require('fs') -const path = require('path') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -jest.mock('axios') - -const date = dayjs.utc('2023-01-15', 'YYYY-MM-DD').startOf('d') -const channel = { - site_id: 'mnb', - xmltv_id: 'MNB.mn' -} - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe('https://m.zuragt.mn/channel/mnb/?date=2023-01-15') -}) - -it('can generate valid request object', () => { - expect(request.maxRedirects).toBe(0) - expect(request.validateStatus(302)).toBe(true) -}) - -it('can parse response', async () => { - const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) - let results = await parser({ content, date }) - results = results.map(p => { - p.start = p.start.toJSON() - p.stop = p.stop.toJSON() - return p - }) - - expect(results[0]).toMatchObject({ - start: '2023-01-14T23:00:00.000Z', - stop: '2023-01-15T00:00:00.000Z', - title: '“Цагийн хүрд” мэдээллийн хөтөлбөр' - }) -}) - -it('can handle empty guide', async () => { - const result = await parser({ content: '' }) - expect(result).toMatchObject([]) -}) +const { parser, url, request } = require('./zuragt.mn.config.js') +const fs = require('fs') +const path = require('path') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +jest.mock('axios') + +const date = dayjs.utc('2023-01-15', 'YYYY-MM-DD').startOf('d') +const channel = { + site_id: 'mnb', + xmltv_id: 'MNB.mn' +} + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe('https://m.zuragt.mn/channel/mnb/?date=2023-01-15') +}) + +it('can generate valid request object', () => { + expect(request.maxRedirects).toBe(0) + expect(request.validateStatus(302)).toBe(true) +}) + +it('can parse response', async () => { + const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) + let results = await parser({ content, date }) + results = results.map(p => { + p.start = p.start.toJSON() + p.stop = p.stop.toJSON() + return p + }) + + expect(results[0]).toMatchObject({ + start: '2023-01-14T23:00:00.000Z', + stop: '2023-01-15T00:00:00.000Z', + title: '“Цагийн хүрд” мэдээллийн хөтөлбөр' + }) +}) + +it('can handle empty guide', async () => { + const result = await parser({ content: '' }) + expect(result).toMatchObject([]) +}) diff --git a/tests/__data__/expected/sites_init/example.com.config.js b/tests/__data__/expected/sites_init/example.com.config.js index 472b22e96..b4178aa6f 100644 --- a/tests/__data__/expected/sites_init/example.com.config.js +++ b/tests/__data__/expected/sites_init/example.com.config.js @@ -1,16 +1,16 @@ -module.exports = { - site: 'example.com', - url({ channel, date }) { - return `https://example.com/api/${channel.site_id}/${date.format('YYYY-MM-DD')}` - }, - parser({ content }) { - try { - return JSON.parse(content) - } catch { - return [] - } - }, - channels() { - return [] - } -} +module.exports = { + site: 'example.com', + url({ channel, date }) { + return `https://example.com/api/${channel.site_id}/${date.format('YYYY-MM-DD')}` + }, + parser({ content }) { + try { + return JSON.parse(content) + } catch { + return [] + } + }, + channels() { + return [] + } +} diff --git a/tests/__data__/expected/sites_init/example.com.test.js b/tests/__data__/expected/sites_init/example.com.test.js index f462b6ef9..b5162a00d 100644 --- a/tests/__data__/expected/sites_init/example.com.test.js +++ b/tests/__data__/expected/sites_init/example.com.test.js @@ -1,38 +1,38 @@ -const { parser, url } = require('./example.com.config.js') -const dayjs = require('dayjs') -const utc = require('dayjs/plugin/utc') -const customParseFormat = require('dayjs/plugin/customParseFormat') -dayjs.extend(customParseFormat) -dayjs.extend(utc) - -const date = dayjs.utc('2025-01-12', 'YYYY-MM-DD').startOf('d') -const channel = { site_id: 'bbc1' } - -it('can generate valid url', () => { - expect(url({ channel, date })).toBe('https://example.com/api/bbc1/2025-01-12') -}) - -it('can parse response', () => { - const content = - '[{"title":"Program 1","start":"2025-01-12T00:00:00.000Z","stop":"2025-01-12T00:30:00.000Z"},{"title":"Program 2","start":"2025-01-12T00:30:00.000Z","stop":"2025-01-12T01:00:00.000Z"}]' - - const results = parser({ content }) - - expect(results.length).toBe(2) - expect(results[0]).toMatchObject({ - title: 'Program 1', - start: '2025-01-12T00:00:00.000Z', - stop: '2025-01-12T00:30:00.000Z' - }) - expect(results[1]).toMatchObject({ - title: 'Program 2', - start: '2025-01-12T00:30:00.000Z', - stop: '2025-01-12T01:00:00.000Z' - }) -}) - -it('can handle empty guide', () => { - const results = parser({ content: '' }) - - expect(results).toMatchObject([]) -}) +const { parser, url } = require('./example.com.config.js') +const dayjs = require('dayjs') +const utc = require('dayjs/plugin/utc') +const customParseFormat = require('dayjs/plugin/customParseFormat') +dayjs.extend(customParseFormat) +dayjs.extend(utc) + +const date = dayjs.utc('2025-01-12', 'YYYY-MM-DD').startOf('d') +const channel = { site_id: 'bbc1' } + +it('can generate valid url', () => { + expect(url({ channel, date })).toBe('https://example.com/api/bbc1/2025-01-12') +}) + +it('can parse response', () => { + const content = + '[{"title":"Program 1","start":"2025-01-12T00:00:00.000Z","stop":"2025-01-12T00:30:00.000Z"},{"title":"Program 2","start":"2025-01-12T00:30:00.000Z","stop":"2025-01-12T01:00:00.000Z"}]' + + const results = parser({ content }) + + expect(results.length).toBe(2) + expect(results[0]).toMatchObject({ + title: 'Program 1', + start: '2025-01-12T00:00:00.000Z', + stop: '2025-01-12T00:30:00.000Z' + }) + expect(results[1]).toMatchObject({ + title: 'Program 2', + start: '2025-01-12T00:30:00.000Z', + stop: '2025-01-12T01:00:00.000Z' + }) +}) + +it('can handle empty guide', () => { + const results = parser({ content: '' }) + + expect(results).toMatchObject([]) +}) diff --git a/tests/__data__/input/channels_parse/example.com.config.js b/tests/__data__/input/channels_parse/example.com.config.js index e1b5c368d..4d87c89c8 100644 --- a/tests/__data__/input/channels_parse/example.com.config.js +++ b/tests/__data__/input/channels_parse/example.com.config.js @@ -1,21 +1,21 @@ -module.exports = { - site: 'example.com', - url: 'https://example.com', - parser() { - return [] - }, - channels() { - return [ - { - lang: 'en', - site_id: 140, - name: 'CNN International' - }, - { - lang: 'en', - site_id: 240, - name: 'BBC World News' - } - ] - } -} +module.exports = { + site: 'example.com', + url: 'https://example.com', + parser() { + return [] + }, + channels() { + return [ + { + lang: 'en', + site_id: 140, + name: 'CNN International' + }, + { + lang: 'en', + site_id: 240, + name: 'BBC World News' + } + ] + } +} diff --git a/tests/commands/channels/parse.test.ts b/tests/commands/channels/parse.test.ts index 3e67da0ba..905296da4 100644 --- a/tests/commands/channels/parse.test.ts +++ b/tests/commands/channels/parse.test.ts @@ -1,30 +1,30 @@ -import { execSync } from 'child_process' -import fs from 'fs-extra' -import { pathToFileURL } from 'node:url' - -beforeEach(() => { - fs.emptyDirSync('tests/__data__/output') - fs.copySync( - 'tests/__data__/input/channels_parse/example.com.channels.xml', - 'tests/__data__/output/example.com.channels.xml' - ) -}) - -describe('channels:parse', () => { - it('can parse channels', () => { - const cmd = - 'npm run channels:parse --- --config=tests/__data__/input/channels_parse/example.com.config.js --output=tests/__data__/output/example.com.channels.xml' - const stdout = execSync(cmd, { encoding: 'utf8' }) - if (process.env.DEBUG === 'true') console.log(cmd, stdout) - - expect(content('tests/__data__/output/example.com.channels.xml')).toEqual( - content('tests/__data__/expected/channels_parse/example.com.channels.xml') - ) - }) -}) - -function content(filepath: string) { - return fs.readFileSync(pathToFileURL(filepath), { - encoding: 'utf8' - }) -} +import { execSync } from 'child_process' +import fs from 'fs-extra' +import { pathToFileURL } from 'node:url' + +beforeEach(() => { + fs.emptyDirSync('tests/__data__/output') + fs.copySync( + 'tests/__data__/input/channels_parse/example.com.channels.xml', + 'tests/__data__/output/example.com.channels.xml' + ) +}) + +describe('channels:parse', () => { + it('can parse channels', () => { + const cmd = + 'npm run channels:parse --- --config=tests/__data__/input/channels_parse/example.com.config.js --output=tests/__data__/output/example.com.channels.xml' + const stdout = execSync(cmd, { encoding: 'utf8' }) + if (process.env.DEBUG === 'true') console.log(cmd, stdout) + + expect(content('tests/__data__/output/example.com.channels.xml')).toEqual( + content('tests/__data__/expected/channels_parse/example.com.channels.xml') + ) + }) +}) + +function content(filepath: string) { + return fs.readFileSync(pathToFileURL(filepath), { + encoding: 'utf8' + }) +}