Merge pull request #2562 from iptv-org/freearhey-patch-5

Patch 2025.01.1
This commit is contained in:
Ismaël Moret 2025-01-08 13:24:56 +01:00 committed by GitHub
commit 8a5ec2cfac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
180 changed files with 7340 additions and 7179 deletions

View file

@ -1,39 +0,0 @@
{
"env": {
"node": true,
"es2021": true,
"jest": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": [
"@typescript-eslint"
],
"rules": {
"@typescript-eslint/no-var-requires": "off",
"no-case-declarations": "off",
"linebreak-style": [
"error",
"windows"
],
"quotes": [
"error",
"single",
{
"avoidEscape": true
}
],
"semi": [
"error",
"never"
]
}
}

42
.github/workflows/check.yml vendored Normal file
View file

@ -0,0 +1,42 @@
name: check
on:
workflow_dispatch:
pull_request:
types: [opened, synchronize, reopened, edited]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- uses: tj-actions/changed-files@v45
id: files
with:
since_last_remote_commit: true
files_yaml: |
js:
- tests/**/*.{js,ts}
- scripts/**/*.{js,ts}
- sites/**/*.{js,ts}
channels:
- sites/**/*.channels.xml
- uses: actions/setup-node@v4
if: ${{ !env.ACT && (steps.files.outputs.js_any_changed == 'true' || steps.files.outputs.channels_any_changed == 'true') }}
with:
node-version: 22
cache: 'npm'
- name: install dependencies
if: steps.files.outputs.js_any_changed == 'true' || steps.files.outputs.channels_any_changed == 'true'
run: SKIP_POSTINSTALL=1 npm install
- name: check changed js-files
if: steps.files.outputs.js_any_changed == 'true'
run: |
npx eslint ${{ steps.files.outputs.js_all_changed_files }}
- name: check changed *.channels.xml
if: steps.files.outputs.channels_any_changed == 'true'
run: |
npm run channels:lint -- ${{ steps.files.outputs.channels_all_changed_files }}

2
.husky/pre-commit Normal file
View file

@ -0,0 +1,2 @@
npm run lint
npm run channels:lint

View file

@ -1,7 +1,7 @@
module.exports = { module.exports = {
tabWidth: 2, tabWidth: 2,
useTabs: false, useTabs: false,
endOfLine: 'lf', endOfLine: 'crlf',
semi: false, semi: false,
singleQuote: true, singleQuote: true,
printWidth: 100, printWidth: 100,

1
.sites/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
_table.md

View file

@ -1,207 +0,0 @@
<table>
<thead>
<tr><th align="left">Site</th><th align="left">Status</th><th align="left">Notes</th></tr>
</thead>
<tbody>
<tr><td><a href="sites/9tv.co.il">9tv.co.il</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/abc.net.au">abc.net.au</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/allente.dk">allente.dk</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/allente.fi">allente.fi</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/allente.no">allente.no</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/allente.se">allente.se</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/andorradifusio.ad">andorradifusio.ad</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/anteltv.com.uy">anteltv.com.uy</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/arianaafgtv.com">arianaafgtv.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/arianatelevision.com">arianatelevision.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/arirang.com">arirang.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/artonline.tv">artonline.tv</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/awilime.com">awilime.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/bein.com">bein.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/beinsports.com">beinsports.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/berrymedia.co.kr">berrymedia.co.kr</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/cablego.com.pe">cablego.com.pe</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/cableplus.com.uy">cableplus.com.uy</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/canalplus-haiti.com">canalplus-haiti.com</a></td><td>🔴</td><td>https://github.com/iptv-org/epg/issues/2237</td></tr>
<tr><td><a href="sites/canalplus.com">canalplus.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/cgates.lt">cgates.lt</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/chada.ma">chada.ma</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/chaines-tv.orange.fr">chaines-tv.orange.fr</a></td><td>🟡</td><td>https://github.com/iptv-org/epg/issues/2395</td></tr>
<tr><td><a href="sites/clickthecity.com">clickthecity.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/comteco.com.bo">comteco.com.bo</a></td><td>🔴</td><td>https://github.com/iptv-org/epg/issues/2239</td></tr>
<tr><td><a href="sites/content.astro.com.my">content.astro.com.my</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/cosmote.gr">cosmote.gr</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/cubmu.com">cubmu.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/dens.tv">dens.tv</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/digiturk.com.tr">digiturk.com.tr</a></td><td>🔴</td><td>https://github.com/iptv-org/epg/issues/2304, https://github.com/iptv-org/epg/issues/2547</td></tr>
<tr><td><a href="sites/directv.com">directv.com</a></td><td>🔴</td><td>https://github.com/iptv-org/epg/issues/2284</td></tr>
<tr><td><a href="sites/directv.com.ar">directv.com.ar</a></td><td>🔴</td><td>https://github.com/iptv-org/epg/issues/2339</td></tr>
<tr><td><a href="sites/directv.com.uy">directv.com.uy</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/dishtv.in">dishtv.in</a></td><td>🔴</td><td>https://github.com/iptv-org/epg/issues/2445</td></tr>
<tr><td><a href="sites/dsmart.com.tr">dsmart.com.tr</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/dstv.com">dstv.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/elcinema.com">elcinema.com</a></td><td>🔴</td><td>https://github.com/iptv-org/epg/issues/2541</td></tr>
<tr><td><a href="sites/ena.skylifetv.co.kr">ena.skylifetv.co.kr</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/energeek.cl">energeek.cl</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/entertainment.ie">entertainment.ie</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/firstmedia.com">firstmedia.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/flixed.io">flixed.io</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/foxsports.com.au">foxsports.com.au</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/foxtel.com.au">foxtel.com.au</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/frikanalen.no">frikanalen.no</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/gatotv.com">gatotv.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/getafteritmedia.com">getafteritmedia.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/guida.tv">guida.tv</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/guidatv.sky.it">guidatv.sky.it</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/hd-plus.de">hd-plus.de</a></td><td>🔴</td><td>https://github.com/iptv-org/epg/issues/2173</td></tr>
<tr><td><a href="sites/horizon.tv">horizon.tv</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/hoy.tv">hoy.tv</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/i.mjh.nz">i.mjh.nz</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/i24news.tv">i24news.tv</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/iltalehti.fi">iltalehti.fi</a></td><td>🟡</td><td>https://github.com/iptv-org/epg/issues/2396</td></tr>
<tr><td><a href="sites/indihometv.com">indihometv.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/ionplustv.com">ionplustv.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/ipko.tv">ipko.tv</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/kan.org.il">kan.org.il</a></td><td>🔴</td><td>https://github.com/iptv-org/epg/issues/2273</td></tr>
<tr><td><a href="sites/knr.gl">knr.gl</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/kplus.vn">kplus.vn</a></td><td>🔴</td><td>https://github.com/iptv-org/epg/issues/2240</td></tr>
<tr><td><a href="sites/kvf.fo">kvf.fo</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/m.tv.sms.cz">m.tv.sms.cz</a></td><td>🔴</td><td>https://github.com/iptv-org/epg/issues/2241</td></tr>
<tr><td><a href="sites/m.tving.com">m.tving.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/magticom.ge">magticom.ge</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/mako.co.il">mako.co.il</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/maxtv.hrvatskitelekom.hr">maxtv.hrvatskitelekom.hr</a></td><td>🔴</td><td>https://github.com/iptv-org/epg/issues/2509</td></tr>
<tr><td><a href="sites/maxtvgo.mk">maxtvgo.mk</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/mediagenie.co.kr">mediagenie.co.kr</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/mediaklikk.hu">mediaklikk.hu</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/mediasetinfinity.mediaset.it">mediasetinfinity.mediaset.it</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/melita.com">melita.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/meo.pt">meo.pt</a></td><td>🟡</td><td>https://github.com/iptv-org/epg/issues/2446</td></tr>
<tr><td><a href="sites/meuguia.tv">meuguia.tv</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/mewatch.sg">mewatch.sg</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/mi.tv">mi.tv</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/mncvision.id">mncvision.id</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/moji.id">moji.id</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/mon-programme-tv.be">mon-programme-tv.be</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/movistarplus.es">movistarplus.es</a></td><td>🔴</td><td>https://github.com/iptv-org/epg/issues/2498</td></tr>
<tr><td><a href="sites/mtel.ba">mtel.ba</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/mts.rs">mts.rs</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/mujtvprogram.cz">mujtvprogram.cz</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/musor.tv">musor.tv</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/mysky.com.ph">mysky.com.ph</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/mytelly.co.uk">mytelly.co.uk</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/mytvsuper.com">mytvsuper.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/neo.io">neo.io</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/nhkworldpremium.com">nhkworldpremium.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/nhl.com">nhl.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/nostv.pt">nostv.pt</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/novacyprus.com">novacyprus.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/novasports.gr">novasports.gr</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/nowplayer.now.com">nowplayer.now.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/nuevosiglo.com.uy">nuevosiglo.com.uy</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/nzxmltv.com">nzxmltv.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/ontvtonight.com">ontvtonight.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/osn.com">osn.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/pbsguam.org">pbsguam.org</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/pickx.be">pickx.be</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/player.ee.co.uk">player.ee.co.uk</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/playtv.unifi.com.my">playtv.unifi.com.my</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/plex.tv">plex.tv</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/programacion-tv.elpais.com">programacion-tv.elpais.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/programacion.tcc.com.uy">programacion.tcc.com.uy</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/programetv.ro">programetv.ro</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/programme-tv.net">programme-tv.net</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/programme-tv.vini.pf">programme-tv.vini.pf</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/programme.tvb.com">programme.tvb.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/programtv.onet.pl">programtv.onet.pl</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/raiplay.it">raiplay.it</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/reportv.com.ar">reportv.com.ar</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/rev.bs">rev.bs</a></td><td>🔴</td><td>https://github.com/iptv-org/epg/issues/2255</td></tr>
<tr><td><a href="sites/rotana.net">rotana.net</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/rtb.gov.bn">rtb.gov.bn</a></td><td>🔴</td><td>https://github.com/iptv-org/epg/issues/2257</td></tr>
<tr><td><a href="sites/rthk.hk">rthk.hk</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/rtmklik.rtm.gov.my">rtmklik.rtm.gov.my</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/rtp.pt">rtp.pt</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/ruv.is">ruv.is</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/s.mxtv.jp">s.mxtv.jp</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/sat.tv">sat.tv</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/shahid.mbc.net">shahid.mbc.net</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/siba.com.co">siba.com.co</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/singtel.com">singtel.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/sjonvarp.is">sjonvarp.is</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/sky.co.nz">sky.co.nz</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/sky.com">sky.com</a></td><td>🟡</td><td>https://github.com/iptv-org/epg/issues/2516, https://github.com/iptv-org/epg/issues/2501</td></tr>
<tr><td><a href="sites/sky.de">sky.de</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/skylife.co.kr">skylife.co.kr</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/skyperfectv.co.jp">skyperfectv.co.jp</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/snrt.ma">snrt.ma</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/sporttv.pt">sporttv.pt</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/starhubtvplus.com">starhubtvplus.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/startimestv.com">startimestv.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/streamingtvguides.com">streamingtvguides.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/superguidatv.it">superguidatv.it</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/taiwanplus.com">taiwanplus.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/tapdmv.com">tapdmv.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/telenet.tv">telenet.tv</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/teliatv.ee">teliatv.ee</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/telkussa.fi">telkussa.fi</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/telsu.fi">telsu.fi</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/tivu.tv">tivu.tv</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/toonamiaftermath.com">toonamiaftermath.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/turksatkablo.com.tr">turksatkablo.com.tr</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/tv-programme.telecablesat.fr">tv-programme.telecablesat.fr</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/tv.blue.ch">tv.blue.ch</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/tv.cctv.com">tv.cctv.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/tv.dir.bg">tv.dir.bg</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/tv.lv">tv.lv</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/tv.magenta.at">tv.magenta.at</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/tv.mail.ru">tv.mail.ru</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/tv.movistar.com.pe">tv.movistar.com.pe</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/tv.nu">tv.nu</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/tv.post.lu">tv.post.lu</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/tv.trueid.net">tv.trueid.net</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/tv.yandex.ru">tv.yandex.ru</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/tv.yettel.hu">tv.yettel.hu</a></td><td>🔴</td><td>https://github.com/iptv-org/epg/issues/2263</td></tr>
<tr><td><a href="sites/tv24.co.uk">tv24.co.uk</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/tv24.se">tv24.se</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/tv2go.t-2.net">tv2go.t-2.net</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/tva.tv">tva.tv</a></td><td>🔴</td><td>https://github.com/iptv-org/epg/issues/2264</td></tr>
<tr><td><a href="sites/tvarenasport.com">tvarenasport.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/tvarenasport.hr">tvarenasport.hr</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/tvcesoir.fr">tvcesoir.fr</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/tvcubana.icrt.cu">tvcubana.icrt.cu</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/tvgids.nl">tvgids.nl</a></td><td>🟡</td><td>https://github.com/iptv-org/epg/issues/2400</td></tr>
<tr><td><a href="sites/tvguide.com">tvguide.com</a></td><td>🔴</td><td>https://github.com/iptv-org/epg/issues/2542</td></tr>
<tr><td><a href="sites/tvguide.myjcom.jp">tvguide.myjcom.jp</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/tvhebdo.com">tvhebdo.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/tvheute.at">tvheute.at</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/tvim.tv">tvim.tv</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/tvireland.ie">tvireland.ie</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/tvmi.mt">tvmi.mt</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/tvmusor.hu">tvmusor.hu</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/tvpassport.com">tvpassport.com</a></td><td>🟡</td><td>https://github.com/iptv-org/epg/issues/2272</td></tr>
<tr><td><a href="sites/tvplus.com.tr">tvplus.com.tr</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/tvprofil.com">tvprofil.com</a></td><td>🟡</td><td>https://github.com/iptv-org/epg/issues/2399</td></tr>
<tr><td><a href="sites/tvtv.us">tvtv.us</a></td><td>🟡</td><td>https://github.com/iptv-org/epg/issues/2176</td></tr>
<tr><td><a href="sites/v3.myafn.dodmedia.osd.mil">v3.myafn.dodmedia.osd.mil</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/vidio.com">vidio.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/virginmediatelevision.ie">virginmediatelevision.ie</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/virgintvgo.virginmedia.com">virgintvgo.virginmedia.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/visionplus.id">visionplus.id</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/vivacom.bg">vivacom.bg</a></td><td>🔴</td><td>https://github.com/iptv-org/epg/issues/2270</td></tr>
<tr><td><a href="sites/vtm.be">vtm.be</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/walesi.com.fj">walesi.com.fj</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/watch.sportsnet.ca">watch.sportsnet.ca</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/watchyour.tv">watchyour.tv</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/wavve.com">wavve.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/web.magentatv.de">web.magentatv.de</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/webtv.delta.nl">webtv.delta.nl</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/winplay.co">winplay.co</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/worldfishingnetwork.com">worldfishingnetwork.com</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/www3.nhk.or.jp">www3.nhk.or.jp</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/xumo.tv">xumo.tv</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/zap.co.ao">zap.co.ao</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/ziggogo.tv">ziggogo.tv</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/znbc.co.zm">znbc.co.zm</a></td><td>🟢</td><td></td></tr>
<tr><td><a href="sites/zuragt.mn">zuragt.mn</a></td><td>🟢</td><td></td></tr>
</tbody>
</table>

View file

@ -147,6 +147,7 @@ For scripts to work, you must have [Node.js](https://nodejs.org/en) installed on
To run scripts use the `npm run <script-name>` command. To run scripts use the `npm run <script-name>` command.
- `act:check`: allows to test the [check](https://github.com/iptv-org/iptv/blob/master/.github/workflows/check.yml) workflow locally. Depends on [nektos/act](https://github.com/nektos/act).
- `act:update`: allows to test the [update](https://github.com/iptv-org/iptv/blob/master/.github/workflows/update.yml) workflow locally. Depends on [nektos/act](https://github.com/nektos/act). - `act:update`: allows to test the [update](https://github.com/iptv-org/iptv/blob/master/.github/workflows/update.yml) workflow locally. Depends on [nektos/act](https://github.com/nektos/act).
- `api:load`: downloads the latest channels data from the [iptv-org/api](https://github.com/iptv-org/api). - `api:load`: downloads the latest channels data from the [iptv-org/api](https://github.com/iptv-org/api).
- `api:generate`: generates a JSON file with all channels for the [iptv-org/api](https://github.com/iptv-org/api) repository. - `api:generate`: generates a JSON file with all channels for the [iptv-org/api](https://github.com/iptv-org/api) repository.

52
eslint.config.mjs Normal file
View file

@ -0,0 +1,52 @@
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', 'windows'],
quotes: [
'error',
'single',
{
avoidEscape: true
}
],
semi: ['error', 'never']
}
}
]

631
package-lock.json generated
View file

@ -9,11 +9,15 @@
"license": "UNLICENSED", "license": "UNLICENSED",
"dependencies": { "dependencies": {
"@alex_neo/jest-expect-message": "^1.0.5", "@alex_neo/jest-expect-message": "^1.0.5",
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.17.0",
"@freearhey/core": "^0.3.1", "@freearhey/core": "^0.3.1",
"@freearhey/search-js": "^0.1.1", "@freearhey/search-js": "^0.1.1",
"@ntlab/sfetch": "^1.0.0", "@ntlab/sfetch": "^1.0.0",
"@octokit/plugin-paginate-rest": "^11.3.6", "@octokit/plugin-paginate-rest": "^11.3.6",
"@octokit/plugin-rest-endpoint-methods": "^13.2.6", "@octokit/plugin-rest-endpoint-methods": "^13.2.6",
"@swc/core": "^1.10.4",
"@swc/jest": "^0.2.37",
"@types/cli-progress": "^3.11.3", "@types/cli-progress": "^3.11.3",
"@types/fs-extra": "^11.0.2", "@types/fs-extra": "^11.0.2",
"@types/inquirer": "^9.0.3", "@types/inquirer": "^9.0.3",
@ -40,9 +44,12 @@
"form-data": "^4.0.0", "form-data": "^4.0.0",
"fs-extra": "^10.0.1", "fs-extra": "^10.0.1",
"glob": "^7.2.0", "glob": "^7.2.0",
"globals": "^15.14.0",
"husky": "^9.1.7",
"iconv-lite": "^0.4.24", "iconv-lite": "^0.4.24",
"inquirer": "^8.2.6", "inquirer": "^8.2.6",
"jest": "^29.7.0", "jest": "^29.7.0",
"jest-offline": "^1.0.1",
"langs": "^2.0.0", "langs": "^2.0.0",
"libxmljs2": "^0.35.0", "libxmljs2": "^0.35.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
@ -57,12 +64,12 @@
"pdf-parse": "^1.1.1", "pdf-parse": "^1.1.1",
"serve": "^14.2.4", "serve": "^14.2.4",
"signale": "^1.4.0", "signale": "^1.4.0",
"skip-postinstall": "^1.0.0",
"srcset": "^4.0.0", "srcset": "^4.0.0",
"table2array": "^0.0.2", "table2array": "^0.0.2",
"tabletojson": "^2.0.7", "tabletojson": "^2.0.7",
"tough-cookie": "^5.0.0", "tough-cookie": "^5.0.0",
"transliteration": "^2.2.0", "transliteration": "^2.2.0",
"ts-jest": "^29.1.1",
"tsx": "^4.19.2", "tsx": "^4.19.2",
"unzipit": "^1.4.0", "unzipit": "^1.4.0",
"wildcard-match": "^5.1.2" "wildcard-match": "^5.1.2"
@ -641,6 +648,14 @@
"node": ">=6.9.0" "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==",
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/types": { "node_modules/@babel/types": {
"version": "7.23.3", "version": "7.23.3",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.3.tgz", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.3.tgz",
@ -1510,6 +1525,17 @@
} }
} }
}, },
"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==",
"dependencies": {
"@jest/types": "^29.6.3"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/@jest/environment": { "node_modules/@jest/environment": {
"version": "29.7.0", "version": "29.7.0",
"resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz",
@ -2074,6 +2100,222 @@
"@sinonjs/commons": "^3.0.0" "@sinonjs/commons": "^3.0.0"
} }
}, },
"node_modules/@swc/core": {
"version": "1.10.4",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.10.4.tgz",
"integrity": "sha512-ut3zfiTLORMxhr6y/GBxkHmzcGuVpwJYX4qyXWuBKkpw/0g0S5iO1/wW7RnLnZbAi8wS/n0atRZoaZlXWBkeJg==",
"hasInstallScript": true,
"dependencies": {
"@swc/counter": "^0.1.3",
"@swc/types": "^0.1.17"
},
"engines": {
"node": ">=10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/swc"
},
"optionalDependencies": {
"@swc/core-darwin-arm64": "1.10.4",
"@swc/core-darwin-x64": "1.10.4",
"@swc/core-linux-arm-gnueabihf": "1.10.4",
"@swc/core-linux-arm64-gnu": "1.10.4",
"@swc/core-linux-arm64-musl": "1.10.4",
"@swc/core-linux-x64-gnu": "1.10.4",
"@swc/core-linux-x64-musl": "1.10.4",
"@swc/core-win32-arm64-msvc": "1.10.4",
"@swc/core-win32-ia32-msvc": "1.10.4",
"@swc/core-win32-x64-msvc": "1.10.4"
},
"peerDependencies": {
"@swc/helpers": "*"
},
"peerDependenciesMeta": {
"@swc/helpers": {
"optional": true
}
}
},
"node_modules/@swc/core-darwin-arm64": {
"version": "1.10.4",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.10.4.tgz",
"integrity": "sha512-sV/eurLhkjn/197y48bxKP19oqcLydSel42Qsy2zepBltqUx+/zZ8+/IS0Bi7kaWVFxerbW1IPB09uq8Zuvm3g==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-darwin-x64": {
"version": "1.10.4",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.10.4.tgz",
"integrity": "sha512-gjYNU6vrAUO4+FuovEo9ofnVosTFXkF0VDuo1MKPItz6e2pxc2ale4FGzLw0Nf7JB1sX4a8h06CN16/pLJ8Q2w==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm-gnueabihf": {
"version": "1.10.4",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.10.4.tgz",
"integrity": "sha512-zd7fXH5w8s+Sfvn2oO464KDWl+ZX1MJiVmE4Pdk46N3PEaNwE0koTfgx2vQRqRG4vBBobzVvzICC3618WcefOA==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm64-gnu": {
"version": "1.10.4",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.10.4.tgz",
"integrity": "sha512-+UGfoHDxsMZgFD3tABKLeEZHqLNOkxStu+qCG7atGBhS4Slri6h6zijVvf4yI5X3kbXdvc44XV/hrP/Klnui2A==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm64-musl": {
"version": "1.10.4",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.10.4.tgz",
"integrity": "sha512-cDDj2/uYsOH0pgAnDkovLZvKJpFmBMyXkxEG6Q4yw99HbzO6QzZ5HDGWGWVq/6dLgYKlnnmpjZCPPQIu01mXEg==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-x64-gnu": {
"version": "1.10.4",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.10.4.tgz",
"integrity": "sha512-qJXh9D6Kf5xSdGWPINpLGixAbB5JX8JcbEJpRamhlDBoOcQC79dYfOMEIxWPhTS1DGLyFakAx2FX/b2VmQmj0g==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-x64-musl": {
"version": "1.10.4",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.10.4.tgz",
"integrity": "sha512-A76lIAeyQnHCVt0RL/pG+0er8Qk9+acGJqSZOZm67Ve3B0oqMd871kPtaHBM0BW3OZAhoILgfHW3Op9Q3mx3Cw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-arm64-msvc": {
"version": "1.10.4",
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.10.4.tgz",
"integrity": "sha512-e6j5kBu4fIY7fFxFxnZI0MlEovRvp50Lg59Fw+DVbtqHk3C85dckcy5xKP+UoXeuEmFceauQDczUcGs19SRGSQ==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-ia32-msvc": {
"version": "1.10.4",
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.10.4.tgz",
"integrity": "sha512-RSYHfdKgNXV/amY5Tqk1EWVsyQnhlsM//jeqMLw5Fy9rfxP592W9UTumNikNRPdjI8wKKzNMXDb1U29tQjN0dg==",
"cpu": [
"ia32"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-x64-msvc": {
"version": "1.10.4",
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.10.4.tgz",
"integrity": "sha512-1ujYpaqfqNPYdwKBlvJnOqcl+Syn3UrQ4XE0Txz6zMYgyh6cdU6a3pxqLqIUSJ12MtXRA9ZUhEz1ekU3LfLWXw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/counter": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="
},
"node_modules/@swc/jest": {
"version": "0.2.37",
"resolved": "https://registry.npmjs.org/@swc/jest/-/jest-0.2.37.tgz",
"integrity": "sha512-CR2BHhmXKGxTiFr21DYPRHQunLkX3mNIFGFkxBGji6r9uyIR5zftTOVYj1e0sFNMV2H7mf/+vpaglqaryBtqfQ==",
"dependencies": {
"@jest/create-cache-key-function": "^29.7.0",
"@swc/counter": "^0.1.3",
"jsonc-parser": "^3.2.0"
},
"engines": {
"npm": ">= 7.0.0"
},
"peerDependencies": {
"@swc/core": "*"
}
},
"node_modules/@swc/types": {
"version": "0.1.17",
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.17.tgz",
"integrity": "sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==",
"dependencies": {
"@swc/counter": "^0.1.3"
}
},
"node_modules/@szmarczak/http-timer": { "node_modules/@szmarczak/http-timer": {
"version": "4.0.6", "version": "4.0.6",
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
@ -3082,17 +3324,6 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
} }
}, },
"node_modules/bs-logger": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz",
"integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==",
"dependencies": {
"fast-json-stable-stringify": "2.x"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/bser": { "node_modules/bser": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
@ -4857,11 +5088,14 @@
} }
}, },
"node_modules/globals": { "node_modules/globals": {
"version": "11.12.0", "version": "15.14.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz",
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==",
"engines": { "engines": {
"node": ">=4" "node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/got": { "node_modules/got": {
@ -5012,6 +5246,20 @@
"node": ">=10.17.0" "node": ">=10.17.0"
} }
}, },
"node_modules/husky": {
"version": "9.1.7",
"resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz",
"integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==",
"bin": {
"husky": "bin.js"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/typicode"
}
},
"node_modules/iconv-lite": { "node_modules/iconv-lite": {
"version": "0.4.24", "version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@ -5793,6 +6041,14 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0" "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
} }
}, },
"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==",
"dependencies": {
"mitm": "^1.3.2"
}
},
"node_modules/jest-pnp-resolver": { "node_modules/jest-pnp-resolver": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz",
@ -6129,6 +6385,11 @@
"node": ">=6" "node": ">=6"
} }
}, },
"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=="
},
"node_modules/jsonfile": { "node_modules/jsonfile": {
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
@ -6280,11 +6541,6 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
}, },
"node_modules/lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
"integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag=="
},
"node_modules/lodash.merge": { "node_modules/lodash.merge": {
"version": "4.6.2", "version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@ -6355,7 +6611,9 @@
"node_modules/make-error": { "node_modules/make-error": {
"version": "1.3.6", "version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"optional": true,
"peer": true
}, },
"node_modules/make-fetch-happen": { "node_modules/make-fetch-happen": {
"version": "13.0.1", "version": "13.0.1",
@ -6642,6 +6900,25 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
}, },
"node_modules/mitm": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/mitm/-/mitm-1.7.3.tgz",
"integrity": "sha512-linie/mGisDH73C7aiW6JmstA5XskXd15JBJAEeNQBdH3/L0dJdE/yZ+rw/y2zT7Fcib5KAnL5OvxYOOFQbsgw==",
"dependencies": {
"semver": ">= 5 < 6"
},
"engines": {
"node": ">= 0.10.24"
}
},
"node_modules/mitm/node_modules/semver": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
"bin": {
"semver": "bin/semver"
}
},
"node_modules/mkdirp": { "node_modules/mkdirp": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
@ -8040,6 +8317,15 @@
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="
}, },
"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.",
"bin": {
"skip-postinstall": "index.js"
}
},
"node_modules/slash": { "node_modules/slash": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@ -8510,86 +8796,6 @@
"typescript": ">=4.2.0" "typescript": ">=4.2.0"
} }
}, },
"node_modules/ts-jest": {
"version": "29.1.1",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz",
"integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==",
"dependencies": {
"bs-logger": "0.x",
"fast-json-stable-stringify": "2.x",
"jest-util": "^29.0.0",
"json5": "^2.2.3",
"lodash.memoize": "4.x",
"make-error": "1.x",
"semver": "^7.5.3",
"yargs-parser": "^21.0.1"
},
"bin": {
"ts-jest": "cli.js"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
},
"peerDependencies": {
"@babel/core": ">=7.0.0-beta.0 <8",
"@jest/types": "^29.0.0",
"babel-jest": "^29.0.0",
"jest": "^29.0.0",
"typescript": ">=4.3 <6"
},
"peerDependenciesMeta": {
"@babel/core": {
"optional": true
},
"@jest/types": {
"optional": true
},
"babel-jest": {
"optional": true
},
"esbuild": {
"optional": true
}
}
},
"node_modules/ts-jest/node_modules/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==",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/ts-jest/node_modules/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==",
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/ts-jest/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/ts-jest/node_modules/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==",
"engines": {
"node": ">=12"
}
},
"node_modules/ts-node": { "node_modules/ts-node": {
"version": "10.9.1", "version": "10.9.1",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
@ -9529,6 +9735,13 @@
"@babel/types": "^7.23.3", "@babel/types": "^7.23.3",
"debug": "^4.1.0", "debug": "^4.1.0",
"globals": "^11.1.0" "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": { "@babel/types": {
@ -10039,6 +10252,14 @@
"strip-ansi": "^6.0.0" "strip-ansi": "^6.0.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/environment": { "@jest/environment": {
"version": "29.7.0", "version": "29.7.0",
"resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz",
@ -10483,6 +10704,108 @@
"@sinonjs/commons": "^3.0.0" "@sinonjs/commons": "^3.0.0"
} }
}, },
"@swc/core": {
"version": "1.10.4",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.10.4.tgz",
"integrity": "sha512-ut3zfiTLORMxhr6y/GBxkHmzcGuVpwJYX4qyXWuBKkpw/0g0S5iO1/wW7RnLnZbAi8wS/n0atRZoaZlXWBkeJg==",
"requires": {
"@swc/core-darwin-arm64": "1.10.4",
"@swc/core-darwin-x64": "1.10.4",
"@swc/core-linux-arm-gnueabihf": "1.10.4",
"@swc/core-linux-arm64-gnu": "1.10.4",
"@swc/core-linux-arm64-musl": "1.10.4",
"@swc/core-linux-x64-gnu": "1.10.4",
"@swc/core-linux-x64-musl": "1.10.4",
"@swc/core-win32-arm64-msvc": "1.10.4",
"@swc/core-win32-ia32-msvc": "1.10.4",
"@swc/core-win32-x64-msvc": "1.10.4",
"@swc/counter": "^0.1.3",
"@swc/types": "^0.1.17"
}
},
"@swc/core-darwin-arm64": {
"version": "1.10.4",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.10.4.tgz",
"integrity": "sha512-sV/eurLhkjn/197y48bxKP19oqcLydSel42Qsy2zepBltqUx+/zZ8+/IS0Bi7kaWVFxerbW1IPB09uq8Zuvm3g==",
"optional": true
},
"@swc/core-darwin-x64": {
"version": "1.10.4",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.10.4.tgz",
"integrity": "sha512-gjYNU6vrAUO4+FuovEo9ofnVosTFXkF0VDuo1MKPItz6e2pxc2ale4FGzLw0Nf7JB1sX4a8h06CN16/pLJ8Q2w==",
"optional": true
},
"@swc/core-linux-arm-gnueabihf": {
"version": "1.10.4",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.10.4.tgz",
"integrity": "sha512-zd7fXH5w8s+Sfvn2oO464KDWl+ZX1MJiVmE4Pdk46N3PEaNwE0koTfgx2vQRqRG4vBBobzVvzICC3618WcefOA==",
"optional": true
},
"@swc/core-linux-arm64-gnu": {
"version": "1.10.4",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.10.4.tgz",
"integrity": "sha512-+UGfoHDxsMZgFD3tABKLeEZHqLNOkxStu+qCG7atGBhS4Slri6h6zijVvf4yI5X3kbXdvc44XV/hrP/Klnui2A==",
"optional": true
},
"@swc/core-linux-arm64-musl": {
"version": "1.10.4",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.10.4.tgz",
"integrity": "sha512-cDDj2/uYsOH0pgAnDkovLZvKJpFmBMyXkxEG6Q4yw99HbzO6QzZ5HDGWGWVq/6dLgYKlnnmpjZCPPQIu01mXEg==",
"optional": true
},
"@swc/core-linux-x64-gnu": {
"version": "1.10.4",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.10.4.tgz",
"integrity": "sha512-qJXh9D6Kf5xSdGWPINpLGixAbB5JX8JcbEJpRamhlDBoOcQC79dYfOMEIxWPhTS1DGLyFakAx2FX/b2VmQmj0g==",
"optional": true
},
"@swc/core-linux-x64-musl": {
"version": "1.10.4",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.10.4.tgz",
"integrity": "sha512-A76lIAeyQnHCVt0RL/pG+0er8Qk9+acGJqSZOZm67Ve3B0oqMd871kPtaHBM0BW3OZAhoILgfHW3Op9Q3mx3Cw==",
"optional": true
},
"@swc/core-win32-arm64-msvc": {
"version": "1.10.4",
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.10.4.tgz",
"integrity": "sha512-e6j5kBu4fIY7fFxFxnZI0MlEovRvp50Lg59Fw+DVbtqHk3C85dckcy5xKP+UoXeuEmFceauQDczUcGs19SRGSQ==",
"optional": true
},
"@swc/core-win32-ia32-msvc": {
"version": "1.10.4",
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.10.4.tgz",
"integrity": "sha512-RSYHfdKgNXV/amY5Tqk1EWVsyQnhlsM//jeqMLw5Fy9rfxP592W9UTumNikNRPdjI8wKKzNMXDb1U29tQjN0dg==",
"optional": true
},
"@swc/core-win32-x64-msvc": {
"version": "1.10.4",
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.10.4.tgz",
"integrity": "sha512-1ujYpaqfqNPYdwKBlvJnOqcl+Syn3UrQ4XE0Txz6zMYgyh6cdU6a3pxqLqIUSJ12MtXRA9ZUhEz1ekU3LfLWXw==",
"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.37",
"resolved": "https://registry.npmjs.org/@swc/jest/-/jest-0.2.37.tgz",
"integrity": "sha512-CR2BHhmXKGxTiFr21DYPRHQunLkX3mNIFGFkxBGji6r9uyIR5zftTOVYj1e0sFNMV2H7mf/+vpaglqaryBtqfQ==",
"requires": {
"@jest/create-cache-key-function": "^29.7.0",
"@swc/counter": "^0.1.3",
"jsonc-parser": "^3.2.0"
}
},
"@swc/types": {
"version": "0.1.17",
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.17.tgz",
"integrity": "sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==",
"requires": {
"@swc/counter": "^0.1.3"
}
},
"@szmarczak/http-timer": { "@szmarczak/http-timer": {
"version": "4.0.6", "version": "4.0.6",
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
@ -11213,14 +11536,6 @@
"update-browserslist-db": "^1.0.11" "update-browserslist-db": "^1.0.11"
} }
}, },
"bs-logger": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz",
"integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==",
"requires": {
"fast-json-stable-stringify": "2.x"
}
},
"bser": { "bser": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
@ -12496,9 +12811,9 @@
} }
}, },
"globals": { "globals": {
"version": "11.12.0", "version": "15.14.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz",
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig=="
}, },
"got": { "got": {
"version": "11.8.5", "version": "11.8.5",
@ -12602,6 +12917,11 @@
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
"integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==" "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": { "iconv-lite": {
"version": "0.4.24", "version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@ -13144,6 +13464,14 @@
"jest-util": "^29.7.0" "jest-util": "^29.7.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": { "jest-pnp-resolver": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz",
@ -13410,6 +13738,11 @@
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==" "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": { "jsonfile": {
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
@ -13533,11 +13866,6 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
}, },
"lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
"integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag=="
},
"lodash.merge": { "lodash.merge": {
"version": "4.6.2", "version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@ -13592,7 +13920,9 @@
"make-error": { "make-error": {
"version": "1.3.6", "version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"optional": true,
"peer": true
}, },
"make-fetch-happen": { "make-fetch-happen": {
"version": "13.0.1", "version": "13.0.1",
@ -13822,6 +14152,21 @@
} }
} }
}, },
"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": { "mkdirp": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
@ -14825,6 +15170,11 @@
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="
}, },
"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": { "slash": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@ -15172,49 +15522,6 @@
"integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==",
"requires": {} "requires": {}
}, },
"ts-jest": {
"version": "29.1.1",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz",
"integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==",
"requires": {
"bs-logger": "0.x",
"fast-json-stable-stringify": "2.x",
"jest-util": "^29.0.0",
"json5": "^2.2.3",
"lodash.memoize": "4.x",
"make-error": "1.x",
"semver": "^7.5.3",
"yargs-parser": "^21.0.1"
},
"dependencies": {
"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=="
},
"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=="
}
}
},
"ts-node": { "ts-node": {
"version": "10.9.1", "version": "10.9.1",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",

View file

@ -1,6 +1,7 @@
{ {
"name": "epg", "name": "epg",
"scripts": { "scripts": {
"act:check": "act pull_request -W .github/workflows/check.yml",
"act:update": "act workflow_dispatch -W .github/workflows/update.yml", "act:update": "act workflow_dispatch -W .github/workflows/update.yml",
"api:load": "npx tsx scripts/commands/api/load.ts", "api:load": "npx tsx scripts/commands/api/load.ts",
"api:generate": "npx tsx scripts/commands/api/generate.ts", "api:generate": "npx tsx scripts/commands/api/generate.ts",
@ -11,29 +12,37 @@
"sites:update": "npx tsx scripts/commands/sites/update.ts", "sites:update": "npx tsx scripts/commands/sites/update.ts",
"grab": "npx tsx scripts/commands/epg/grab.ts", "grab": "npx tsx scripts/commands/epg/grab.ts",
"serve": "npx serve", "serve": "npx serve",
"lint": "npx eslint \"{scripts,tests}/**/*.{ts,js}\"", "lint": "npx eslint \"{scripts,tests,sites}/**/*.{ts,js}\"",
"test": "run-script-os", "test": "run-script-os",
"test:win32": "SET \"TZ=Pacific/Nauru\" && npx jest --runInBand", "test:win32": "SET \"TZ=Pacific/Nauru\" && npx jest --runInBand",
"test:default": "TZ=Pacific/Nauru npx jest --runInBand", "test:default": "TZ=Pacific/Nauru npx jest --runInBand",
"postinstall": "npm run api:load" "postinstall": "skip-postinstall || npm run api:load",
"prepare": "husky"
}, },
"private": true, "private": true,
"author": "Arhey", "author": "Arhey",
"license": "UNLICENSED", "license": "UNLICENSED",
"jest": { "jest": {
"setupFiles": [
"<rootDir>/node_modules/jest-offline"
],
"transform": { "transform": {
"^.+\\.(ts|js)$": "ts-jest" "^.+\\.(ts|js)$": "@swc/jest"
}, },
"testRegex": "(tests|sites)/(.*?/)?.*test.(js|ts)$", "testRegex": "(tests|sites)/(.*?/)?.*test.(js|ts)$",
"testTimeout": 10000 "testTimeout": 10000
}, },
"dependencies": { "dependencies": {
"@alex_neo/jest-expect-message": "^1.0.5", "@alex_neo/jest-expect-message": "^1.0.5",
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.17.0",
"@freearhey/core": "^0.3.1", "@freearhey/core": "^0.3.1",
"@freearhey/search-js": "^0.1.1", "@freearhey/search-js": "^0.1.1",
"@ntlab/sfetch": "^1.0.0", "@ntlab/sfetch": "^1.0.0",
"@octokit/plugin-paginate-rest": "^11.3.6", "@octokit/plugin-paginate-rest": "^11.3.6",
"@octokit/plugin-rest-endpoint-methods": "^13.2.6", "@octokit/plugin-rest-endpoint-methods": "^13.2.6",
"@swc/core": "^1.10.4",
"@swc/jest": "^0.2.37",
"@types/cli-progress": "^3.11.3", "@types/cli-progress": "^3.11.3",
"@types/fs-extra": "^11.0.2", "@types/fs-extra": "^11.0.2",
"@types/inquirer": "^9.0.3", "@types/inquirer": "^9.0.3",
@ -60,9 +69,12 @@
"form-data": "^4.0.0", "form-data": "^4.0.0",
"fs-extra": "^10.0.1", "fs-extra": "^10.0.1",
"glob": "^7.2.0", "glob": "^7.2.0",
"globals": "^15.14.0",
"husky": "^9.1.7",
"iconv-lite": "^0.4.24", "iconv-lite": "^0.4.24",
"inquirer": "^8.2.6", "inquirer": "^8.2.6",
"jest": "^29.7.0", "jest": "^29.7.0",
"jest-offline": "^1.0.1",
"langs": "^2.0.0", "langs": "^2.0.0",
"libxmljs2": "^0.35.0", "libxmljs2": "^0.35.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
@ -77,12 +89,12 @@
"pdf-parse": "^1.1.1", "pdf-parse": "^1.1.1",
"serve": "^14.2.4", "serve": "^14.2.4",
"signale": "^1.4.0", "signale": "^1.4.0",
"skip-postinstall": "^1.0.0",
"srcset": "^4.0.0", "srcset": "^4.0.0",
"table2array": "^0.0.2", "table2array": "^0.0.2",
"tabletojson": "^2.0.7", "tabletojson": "^2.0.7",
"tough-cookie": "^5.0.0", "tough-cookie": "^5.0.0",
"transliteration": "^2.2.0", "transliteration": "^2.2.0",
"ts-jest": "^29.1.1",
"tsx": "^4.19.2", "tsx": "^4.19.2",
"unzipit": "^1.4.0", "unzipit": "^1.4.0",
"wildcard-match": "^5.1.2" "wildcard-match": "^5.1.2"

View file

@ -43,7 +43,7 @@ async function main() {
const channelsIndex = sj.createIndex(channelsContent) const channelsIndex = sj.createIndex(channelsContent)
const buffer = new Dictionary() const buffer = new Dictionary()
for (let option of options.all()) { for (const option of options.all()) {
const channel: Channel = option.channel const channel: Channel = option.channel
if (channel.xmltv_id) { if (channel.xmltv_id) {
if (channel.xmltv_id !== '-') { if (channel.xmltv_id !== '-') {
@ -150,7 +150,7 @@ function getOptions(channelsIndex, channel: Channel) {
const query = channel.name const query = channel.name
.replace(/\s(SD|TV|HD|SD\/HD|HDTV)$/i, '') .replace(/\s(SD|TV|HD|SD\/HD|HDTV)$/i, '')
.replace(/(\(|\)|,)/gi, '') .replace(/(\(|\)|,)/gi, '')
.replace(/\-/gi, ' ') .replace(/-/gi, ' ')
.replace(/\+/gi, '') .replace(/\+/gi, '')
const similar = channelsIndex.search(query).map(item => new ApiChannel(item)) const similar = channelsIndex.search(query).map(item => new ApiChannel(item))

View file

@ -1,7 +1,7 @@
import chalk from 'chalk' import chalk from 'chalk'
import libxml, { ValidationError } from 'libxmljs2' import libxml, { ValidationError } from 'libxmljs2'
import { program } from 'commander' import { program } from 'commander'
import { Logger, Storage, File } from '@freearhey/core' import { Storage, File } from '@freearhey/core'
const xsd = `<?xml version="1.0" encoding="UTF-8"?> const xsd = `<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
@ -23,26 +23,14 @@ const xsd = `<?xml version="1.0" encoding="UTF-8"?>
</xs:element> </xs:element>
</xs:schema>` </xs:schema>`
program program.argument('[filepath]', 'Path to *.channels.xml files to validate').parse(process.argv)
.option(
'-c, --channels <path>',
'Path to channels.xml file to validate',
'sites/**/*.channels.xml'
)
.parse(process.argv)
const options = program.opts()
async function main() { async function main() {
const logger = new Logger()
const storage = new Storage() const storage = new Storage()
logger.info('options:')
logger.tree(options)
let errors: ValidationError[] = [] let errors: ValidationError[] = []
const files: string[] = await storage.list(options.channels) const files = program.args.length ? program.args : await storage.list('sites/**/*.channels.xml')
for (const filepath of files) { for (const filepath of files) {
const file = new File(filepath) const file = new File(filepath)
if (file.extension() !== 'xml') continue if (file.extension() !== 'xml') continue
@ -51,12 +39,16 @@ async function main() {
let localErrors: ValidationError[] = [] let localErrors: ValidationError[] = []
try {
const xsdDoc = libxml.parseXml(xsd) const xsdDoc = libxml.parseXml(xsd)
const doc = libxml.parseXml(xml) const doc = libxml.parseXml(xml)
if (!doc.validate(xsdDoc)) { if (!doc.validate(xsdDoc)) {
localErrors = doc.validationErrors localErrors = doc.validationErrors
} }
} catch (error) {
localErrors.push(error)
}
if (localErrors.length) { if (localErrors.length) {
console.log(`\n${chalk.underline(filepath)}`) console.log(`\n${chalk.underline(filepath)}`)

View file

@ -2,7 +2,7 @@ import { Logger, File, Collection, Storage } from '@freearhey/core'
import { ChannelsParser, XML } from '../../core' import { ChannelsParser, XML } from '../../core'
import { Channel } from 'epg-grabber' import { Channel } from 'epg-grabber'
import { Command } from 'commander' import { Command } from 'commander'
import path from 'path' import { pathToFileURL } from 'node:url'
const program = new Command() const program = new Command()
program program
@ -26,7 +26,7 @@ async function main() {
const logger = new Logger() const logger = new Logger()
const file = new File(options.config) const file = new File(options.config)
const dir = file.dirname() const dir = file.dirname()
const config = require(path.resolve(options.config)) const config = (await import(pathToFileURL(options.config))).default
const outputFilepath = options.output || `${dir}/${config.site}.channels.xml` const outputFilepath = options.output || `${dir}/${config.site}.channels.xml`
let channels = new Collection() let channels = new Collection()

View file

@ -47,7 +47,6 @@ async function main() {
const parsedChannels = await parser.parse(filepath) const parsedChannels = await parser.parse(filepath)
const bufferById = new Dictionary()
const bufferBySiteId = new Dictionary() const bufferBySiteId = new Dictionary()
const errors: ValidationError[] = [] const errors: ValidationError[] = []
parsedChannels.forEach((channel: Channel) => { parsedChannels.forEach((channel: Channel) => {

View file

@ -1,4 +1,4 @@
import { Logger, Storage, Collection, Dictionary } from '@freearhey/core' import { Logger, Storage, Collection } from '@freearhey/core'
import { IssueLoader, HTMLTable, Markdown } from '../../core' import { IssueLoader, HTMLTable, Markdown } from '../../core'
import { Issue, Site } from '../../models' import { Issue, Site } from '../../models'
import { SITES_DIR, DOT_SITES_DIR } from '../../constants' import { SITES_DIR, DOT_SITES_DIR } from '../../constants'
@ -28,7 +28,7 @@ async function main() {
}) })
logger.info('creating sites table...') logger.info('creating sites table...')
let data = new Collection() const data = new Collection()
sites.forEach((site: Site) => { sites.forEach((site: Site) => {
data.add([ data.add([
`<a href="sites/${site.domain}">${site.domain}</a>`, `<a href="sites/${site.domain}">${site.domain}</a>`,

View file

@ -1,5 +1,6 @@
const dayjs = require('dayjs') import dayjs from 'dayjs'
const utc = require('dayjs/plugin/utc') import utc from 'dayjs/plugin/utc'
dayjs.extend(utc) dayjs.extend(utc)
const date = {} const date = {}
@ -10,4 +11,4 @@ date.getUTC = function (d = null) {
return dayjs.utc().startOf('d') return dayjs.utc().startOf('d')
} }
module.exports = date export default date

View file

@ -16,15 +16,15 @@ export class HTMLTable {
} }
toString() { toString() {
let output = '<table>\n' let output = '<table>\r\n'
output += ' <thead>\n <tr>' output += ' <thead>\r\n <tr>'
for (const column of this.columns) { for (const column of this.columns) {
output += `<th align="left">${column.name}</th>` output += `<th align="left">${column.name}</th>`
} }
output += '</tr>\n </thead>\n' output += '</tr>\r\n </thead>\r\n'
output += ' <tbody>\n' output += ' <tbody>\r\n'
for (const item of this.data) { for (const item of this.data) {
output += ' <tr>' output += ' <tr>'
let i = 0 let i = 0
@ -35,9 +35,9 @@ export class HTMLTable {
output += `<td${align}${nowrap}>${item[prop]}</td>` output += `<td${align}${nowrap}>${item[prop]}</td>`
i++ i++
} }
output += '</tr>\n' output += '</tr>\r\n'
} }
output += ' </tbody>\n' output += ' </tbody>\r\n'
output += '</table>' output += '</table>'

View file

@ -15,10 +15,11 @@ export class IssueLoader {
if (TESTING) { if (TESTING) {
switch (labels) { switch (labels) {
case 'broken guide,status:warning': case 'broken guide,status:warning':
issues = require('../../tests/__data__/input/issues/broken_guide_warning.js') issues = (await import('../../tests/__data__/input/issues/broken_guide_warning.mjs'))
.default
break break
case 'broken guide,status:down': case 'broken guide,status:down':
issues = require('../../tests/__data__/input/issues/broken_guide_down.js') issues = (await import('../../tests/__data__/input/issues/broken_guide_down.mjs')).default
break break
} }
} else { } else {

View file

@ -11,7 +11,7 @@ export class IssueParser {
const data = new Dictionary() const data = new Dictionary()
fields.forEach((field: string) => { fields.forEach((field: string) => {
let parsed = field.split(/\r?\n/).filter(Boolean) const parsed = field.split(/\r?\n/).filter(Boolean)
let _label = parsed.shift() let _label = parsed.shift()
_label = _label ? _label.trim() : '' _label = _label ? _label.trim() : ''
let _value = parsed.join('\r\n') let _value = parsed.join('\r\n')

View file

@ -1,7 +1,7 @@
import { Storage, Collection, DateTime, Logger } from '@freearhey/core' import { Storage, Collection, DateTime, Logger } from '@freearhey/core'
import { ChannelsParser, ConfigLoader, ApiChannel, Queue } from './' import { ChannelsParser, ConfigLoader, ApiChannel, Queue } from './'
import { SITES_DIR, DATA_DIR } from '../constants' import { SITES_DIR, DATA_DIR } from '../constants'
import { Channel, SiteConfig } from 'epg-grabber' import { SiteConfig } from 'epg-grabber'
import path from 'path' import path from 'path'
import { GrabOptions } from '../commands/epg/grab' import { GrabOptions } from '../commands/epg/grab'

View file

@ -92,7 +92,7 @@ function parseItems(content, channel) {
const [, channelId] = channel.site_id.split('#') const [, channelId] = channel.site_id.split('#')
const channelData = data.schedule.find(i => i.channel == channelId) const channelData = data.schedule.find(i => i.channel == channelId)
return channelData.listing && Array.isArray(channelData.listing) ? channelData.listing : [] return channelData.listing && Array.isArray(channelData.listing) ? channelData.listing : []
} catch (err) { } catch {
return [] return []
} }
} }

View file

@ -1,4 +1,4 @@
const { parser, url, request } = require('./awilime.com.config.js') const { parser, url } = require('./awilime.com.config.js')
const fs = require('fs') const fs = require('fs')
const path = require('path') const path = require('path')
const dayjs = require('dayjs') const dayjs = require('dayjs')

View file

@ -1,5 +1,4 @@
const axios = require('axios') const axios = require('axios')
const cheerio = require('cheerio')
const dayjs = require('dayjs') const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc') const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone') const timezone = require('dayjs/plugin/timezone')
@ -62,7 +61,7 @@ function parseItems(content) {
let data let data
try { try {
data = JSON.parse(content) data = JSON.parse(content)
} catch (error) { } catch {
return [] return []
} }

View file

@ -1,6 +1,4 @@
const { parser, url } = require('./beinsports.com.config.js') const { parser, url } = require('./beinsports.com.config.js')
const fs = require('fs')
const path = require('path')
const dayjs = require('dayjs') const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc') const utc = require('dayjs/plugin/utc')
const customParseFormat = require('dayjs/plugin/customParseFormat') const customParseFormat = require('dayjs/plugin/customParseFormat')

View file

@ -1,13 +1,12 @@
const axios = require('axios'); const cheerio = require('cheerio')
const cheerio = require('cheerio'); const dayjs = require('dayjs')
const dayjs = require('dayjs'); const utc = require('dayjs/plugin/utc')
const utc = require('dayjs/plugin/utc'); const timezone = require('dayjs/plugin/timezone')
const timezone = require('dayjs/plugin/timezone'); const customParseFormat = require('dayjs/plugin/customParseFormat')
const customParseFormat = require('dayjs/plugin/customParseFormat');
dayjs.extend(utc); dayjs.extend(utc)
dayjs.extend(timezone); dayjs.extend(timezone)
dayjs.extend(customParseFormat); dayjs.extend(customParseFormat)
module.exports = { module.exports = {
site: 'chada.ma', site: 'chada.ma',
@ -19,36 +18,38 @@ module.exports = {
} }
}, },
url() { url() {
return 'https://chada.ma/fr/chada-tv/grille-tv/'; return 'https://chada.ma/fr/chada-tv/grille-tv/'
}, },
parser: function ({ content }) { parser: function ({ content }) {
const $ = cheerio.load(content); const $ = cheerio.load(content)
const programs = []; const programs = []
$('#stopfix .posts-area h2').each((i, element) => { $('#stopfix .posts-area h2').each((i, element) => {
const timeRange = $(element).text().trim(); const timeRange = $(element).text().trim()
const [start, stop] = timeRange.split(' - ').map(t => parseProgramTime(t.trim())); const [start, stop] = timeRange.split(' - ').map(t => parseProgramTime(t.trim()))
const titleElement = $(element).next('div').next('h3'); const titleElement = $(element).next('div').next('h3')
const title = titleElement.text().trim(); const title = titleElement.text().trim()
const description = titleElement.next('div').text().trim() || 'No description available'; const description = titleElement.next('div').text().trim() || 'No description available'
programs.push({ programs.push({
title, title,
description, description,
start, start,
stop stop
}); })
}); })
return programs; return programs
} }
}; }
function parseProgramTime(timeStr) { function parseProgramTime(timeStr) {
const timeZone = 'Africa/Casablanca'; const timeZone = 'Africa/Casablanca'
const currentDate = dayjs().format('YYYY-MM-DD'); const currentDate = dayjs().format('YYYY-MM-DD')
return dayjs.tz(`${currentDate} ${timeStr}`, 'YYYY-MM-DD HH:mm', timeZone).format('YYYY-MM-DDTHH:mm:ssZ'); return dayjs
.tz(`${currentDate} ${timeStr}`, 'YYYY-MM-DD HH:mm', timeZone)
.format('YYYY-MM-DDTHH:mm:ssZ')
} }

View file

@ -1,7 +1,5 @@
const { parser, url } = require('./chada.ma.config.js') const { parser, url } = require('./chada.ma.config.js')
const axios = require('axios')
const dayjs = require('dayjs') const dayjs = require('dayjs')
const cheerio = require('cheerio')
const utc = require('dayjs/plugin/utc') const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone') const timezone = require('dayjs/plugin/timezone')
const customParseFormat = require('dayjs/plugin/customParseFormat') const customParseFormat = require('dayjs/plugin/customParseFormat')
@ -27,11 +25,11 @@ const mockHtmlContent = `
<div class="ssprogramme row"></div> <div class="ssprogramme row"></div>
</div> </div>
</div> </div>
`; `
it('can generate valid url', () => { it('can generate valid url', () => {
expect(url()).toBe('https://chada.ma/fr/chada-tv/grille-tv/') expect(url()).toBe('https://chada.ma/fr/chada-tv/grille-tv/')
}); })
it('can parse response', () => { it('can parse response', () => {
const content = mockHtmlContent const content = mockHtmlContent
@ -44,8 +42,8 @@ it('can parse response', () => {
expect(result).toMatchObject([ expect(result).toMatchObject([
{ {
title: "Bloc Prime + Clips", title: 'Bloc Prime + Clips',
description: "No description available", description: 'No description available',
start: dayjs.tz('00:00', 'HH:mm', 'Africa/Casablanca').format('YYYY-MM-DDTHH:mm:ssZ'), 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') stop: dayjs.tz('09:00', 'HH:mm', 'Africa/Casablanca').format('YYYY-MM-DDTHH:mm:ssZ')
} }

View file

@ -32,7 +32,7 @@ module.exports = {
}, },
async channels() { async channels() {
const html = await axios const html = await axios
.get(`https://chaines-tv.orange.fr/programme-tv?filtres=all`) .get('https://chaines-tv.orange.fr/programme-tv?filtres=all')
.then(r => r.data) .then(r => r.data)
.catch(console.log) .catch(console.log)

View file

@ -40,7 +40,7 @@ module.exports = {
}, },
async channels() { async channels() {
const data = await axios const data = await axios
.get(`https://contenthub-api.eco.astro.com.my/channel/all.json`) .get('https://contenthub-api.eco.astro.com.my/channel/all.json')
.then(r => r.data) .then(r => r.data)
.catch(console.log) .catch(console.log)
@ -85,7 +85,7 @@ function parseItems(content, date) {
const schedules = data.response.schedule const schedules = data.response.schedule
return schedules[date.format('YYYY-MM-DD')] || [] return schedules[date.format('YYYY-MM-DD')] || []
} catch (e) { } catch {
return [] return []
} }
} }

View file

@ -16,12 +16,13 @@ module.exports = {
}, },
method: 'GET', method: 'GET',
headers: { headers: {
'referer': 'https://www.cosmotetv.gr/', 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', 'User-Agent':
'Accept': '*/*', '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-Language': 'en-US,en;q=0.9',
'Accept-Encoding': 'gzip, deflate, br, zstd', 'Accept-Encoding': 'gzip, deflate, br, zstd',
'Origin': 'https://www.cosmotetv.gr', Origin: 'https://www.cosmotetv.gr',
'Sec-Ch-Ua': '"Not.A/Brand";v="24", "Chromium";v="131", "Google Chrome";v="131"', 'Sec-Ch-Ua': '"Not.A/Brand";v="24", "Chromium";v="131", "Google Chrome";v="131"',
'Sec-Ch-Ua-Mobile': '?0', 'Sec-Ch-Ua-Mobile': '?0',
'Sec-Ch-Ua-Platform': '"Windows"', 'Sec-Ch-Ua-Platform': '"Windows"',
@ -30,12 +31,12 @@ module.exports = {
'Sec-Fetch-Site': 'cross-site' 'Sec-Fetch-Site': 'cross-site'
} }
}, },
url: function ({date, channel}) { url: function ({ date, channel }) {
const startOfDay = dayjs(date).startOf('day').utc().unix() const startOfDay = dayjs(date).startOf('day').utc().unix()
const endOfDay = dayjs(date).endOf('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` return `https://mwapi-prod.cosmotetvott.gr/api/v3.4/epg/listings/el?from=${startOfDay}&to=${endOfDay}&callSigns=${channel.site_id}&endingIncludedInRange=false`
}, },
parser: function ({ date, content }) { parser: function ({ content }) {
let programs = [] let programs = []
const data = JSON.parse(content) const data = JSON.parse(content)
data.channels.forEach(channel => { data.channels.forEach(channel => {
@ -57,16 +58,19 @@ module.exports = {
async channels() { async channels() {
const axios = require('axios') const axios = require('axios')
try { try {
const response = await axios.get('https://mwapi-prod.cosmotetvott.gr/api/v3.4/epg/channels/all/el', { const response = await axios.get(
'https://mwapi-prod.cosmotetvott.gr/api/v3.4/epg/channels/all/el',
{
headers: this.request.headers headers: this.request.headers
}) }
)
const data = response.data const data = response.data
if (data && data.channels) { if (data && data.channels) {
return data.channels.map(item => ({ return data.channels.map(item => ({
lang: 'el', lang: 'el',
site_id: item.callSign, site_id: item.callSign,
name: item.title, name: item.title
//logo: item.logos.square //logo: item.logos.square
})) }))
} else { } else {

View file

@ -1,9 +1,8 @@
const { parser, url, channels } = require('./cosmotetv.gr.config.js') const { parser, url } = require('./cosmotetv.gr.config.js')
const dayjs = require('dayjs') const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc') const utc = require('dayjs/plugin/utc')
const customParseFormat = require('dayjs/plugin/customParseFormat') const customParseFormat = require('dayjs/plugin/customParseFormat')
const timezone = require('dayjs/plugin/timezone') const timezone = require('dayjs/plugin/timezone')
const axios = require('axios')
dayjs.extend(utc) dayjs.extend(utc)
dayjs.extend(customParseFormat) dayjs.extend(customParseFormat)
@ -14,34 +13,22 @@ jest.mock('axios')
const date = dayjs.utc('2024-12-26', 'YYYY-MM-DD').startOf('d') const date = dayjs.utc('2024-12-26', 'YYYY-MM-DD').startOf('d')
const channel = { site_id: 'vouli', xmltv_id: 'HellenicParliamentTV.gr' } const channel = { site_id: 'vouli', xmltv_id: 'HellenicParliamentTV.gr' }
const mockChannelData = {
"channels": [
{
"guid": "XTV100000954",
"title": "ΒΟΥΛΗ HD",
"callSign": "vouli",
"logos": {
"square": "https://tr.static.cdn.cosmotetvott.gr/ote-prod/channel_logos/vouli1-normal.png",
"wide": "https://tr.static.cdn.cosmotetvott.gr/ote-prod/channel_logos/vouli1-wide.png"
}
}
]
}
const mockEpgData = { const mockEpgData = {
"channels": [ channels: [
{ {
"items": [ items: [
{ {
"startTime": "2024-12-26T23:00:00+00:00", startTime: '2024-12-26T23:00:00+00:00',
"endTime": "2024-12-27T00:00:00+00:00", endTime: '2024-12-27T00:00:00+00:00',
"title": "Τι Λέει ο Νόμος", title: 'Τι Λέει ο Νόμος',
"description": "νημερωτική εκπομπή. Συζήτηση με τους εισηγητές των κομμάτων για το νομοθετικό έργο.", description:
"qoe": { 'νημερωτική εκπομπή. Συζήτηση με τους εισηγητές των κομμάτων για το νομοθετικό έργο.',
"genre": "Special" qoe: {
genre: 'Special'
}, },
"thumbnails": { thumbnails: {
"standard": "https://gr-ermou-prod-cache05.static.cdn.cosmotetvott.gr/ote-prod/70/280/040029714812000800_1734415727199.jpg" standard:
'https://gr-ermou-prod-cache05.static.cdn.cosmotetvott.gr/ote-prod/70/280/040029714812000800_1734415727199.jpg'
} }
} }
] ]
@ -52,7 +39,9 @@ const mockEpgData = {
it('can generate valid url', () => { it('can generate valid url', () => {
const startOfDay = dayjs(date).startOf('day').utc().unix() const startOfDay = dayjs(date).startOf('day').utc().unix()
const endOfDay = dayjs(date).endOf('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`) 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', () => { it('can parse response', () => {
@ -65,17 +54,23 @@ it('can parse response', () => {
expect(result).toMatchObject([ expect(result).toMatchObject([
{ {
title: "Τι Λέει ο Νόμος", title: 'Τι Λέει ο Νόμος',
description: "νημερωτική εκπομπή. Συζήτηση με τους εισηγητές των κομμάτων για το νομοθετικό έργο.", description:
category: "Special", 'νημερωτική εκπομπή. Συζήτηση με τους εισηγητές των κομμάτων για το νομοθετικό έργο.',
image: "https://gr-ermou-prod-cache05.static.cdn.cosmotetvott.gr/ote-prod/70/280/040029714812000800_1734415727199.jpg", category: 'Special',
start: "2024-12-26T23:00:00.000Z", image:
stop: "2024-12-27T00:00:00.000Z" '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', () => { it('can handle empty guide', () => {
const result = parser({ date, channel, content: '{"date":"2024-12-26","categories":[],"channels":[]}' }); const result = parser({
date,
channel,
content: '{"date":"2024-12-26","categories":[],"channels":[]}'
})
expect(result).toMatchObject([]) expect(result).toMatchObject([])
}) })

View file

@ -9,7 +9,9 @@ module.exports = {
site: 'cubmu.com', site: 'cubmu.com',
days: 2, days: 2,
url({ channel, date }) { 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}` 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 }) { parser({ content, channel }) {
const programs = [] const programs = []
@ -46,13 +48,19 @@ module.exports = {
} }
} }
// login to service bus // login to service bus
const token = await axios 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) .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) .then(response => response.data)
.catch(console.error) .catch(console.error)
// list channels // list channels
const subscribedChannels = await axios const subscribedChannels = await axios
.post(`https://servicebuss.transvision.co.id/tvs/subscribe_product/list?platformId=${config.platformId}`, options) .post(
`https://servicebuss.transvision.co.id/tvs/subscribe_product/list?platformId=${config.platformId}`,
options
)
.then(response => response.data) .then(response => response.data)
.catch(console.error) .catch(console.error)
@ -98,5 +106,9 @@ function parseStart(item) {
} }
function parseStop(item) { function parseStop(item) {
return dayjs.tz([item.schedule_date.split(' ')[0], item.schedule_end_time].join(' '), 'YYYY-MM-DD HH:mm:ss', 'Asia/Jakarta') return dayjs.tz(
[item.schedule_date.split(' ')[0], item.schedule_end_time].join(' '),
'YYYY-MM-DD HH:mm:ss',
'Asia/Jakarta'
)
} }

View file

@ -2,7 +2,6 @@ const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc') const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone') const timezone = require('dayjs/plugin/timezone')
const customParseFormat = require('dayjs/plugin/customParseFormat') const customParseFormat = require('dayjs/plugin/customParseFormat')
const cheerio = require('cheerio')
dayjs.extend(utc) dayjs.extend(utc)
dayjs.extend(timezone) dayjs.extend(timezone)
@ -16,21 +15,21 @@ module.exports = {
ttl: 60 * 60 * 1000 // 1 hour ttl: 60 * 60 * 1000 // 1 hour
} }
}, },
url: function ({date, channel}) { url: function ({ date, channel }) {
// Get the epoch timestamp // Get the epoch timestamp
const todayEpoch = date.startOf('day').utc().valueOf() const todayEpoch = date.startOf('day').utc().valueOf()
// Get the epoch timestamp for the next day // Get the epoch timestamp for the next day
const nextDayEpoch = date.add(1, 'day').startOf('day').utc().valueOf() 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}` return `https://epg.cyta.com.cy/api/mediacatalog/fetchEpg?startTimeEpoch=${todayEpoch}&endTimeEpoch=${nextDayEpoch}&language=1&channelIds=${channel.site_id}`
}, },
parser: function ({content}) { parser: function ({ content }) {
const data = JSON.parse(content) const data = JSON.parse(content)
const programs = [] const programs = []
data.channelEpgs.forEach(channel => { data.channelEpgs.forEach(channel => {
channel.epgPlayables.forEach(epg => { channel.epgPlayables.forEach(epg => {
const start = new Date(epg.startTime).toISOString(); const start = new Date(epg.startTime).toISOString()
const stop = new Date(epg.endTime).toISOString(); const stop = new Date(epg.endTime).toISOString()
programs.push({ programs.push({
title: epg.name, title: epg.name,
@ -45,7 +44,7 @@ module.exports = {
async channels() { async channels() {
const axios = require('axios') const axios = require('axios')
const data = await axios const data = await axios
.get(`https://epg.cyta.com.cy/api/mediacatalog/fetchChannels?language=1`) .get('https://epg.cyta.com.cy/api/mediacatalog/fetchChannels?language=1')
.then(r => r.data) .then(r => r.data)
.catch(console.log) .catch(console.log)

View file

@ -30,11 +30,7 @@ it('can parse response', () => {
] ]
}` }`
const result = parser({ content }).map(p => { const result = parser({ content })
p.start = p.start
p.stop = p.stop
return p
})
expect(result).toMatchObject([ expect(result).toMatchObject([
{ {

View file

@ -13,9 +13,9 @@ module.exports = {
site: 'dens.tv', site: 'dens.tv',
days: 2, days: 2,
url({ channel, date }) { url({ channel, date }) {
return `https://www.dens.tv/api/dens3/tv/TvChannels/listEpgByDate?date=${date.format('YYYY-MM-DD')}&id_channel=${ return `https://www.dens.tv/api/dens3/tv/TvChannels/listEpgByDate?date=${date.format(
channel.site_id 'YYYY-MM-DD'
}&app_type=10` )}&id_channel=${channel.site_id}&app_type=10`
}, },
parser({ content }) { parser({ content }) {
// parsing // parsing
@ -25,8 +25,9 @@ module.exports = {
if (Array.isArray(response?.data)) { if (Array.isArray(response?.data)) {
response.data.forEach(item => { response.data.forEach(item => {
const title = item.title const title = item.title
const [, , , season, , , episode] = title.match(/( (Season |Season|S)(\d+))?( (Episode|Ep) (\d+))/) || const [, , , season, , , episode] = title.match(
[null, null, null, null, null, null, null] /( (Season |Season|S)(\d+))?( (Episode|Ep) (\d+))/
) || [null, null, null, null, null, null, null]
programs.push({ programs.push({
title, title,
description: item.description, description: item.description,
@ -52,7 +53,7 @@ module.exports = {
const channels = [] const channels = []
for (const id_category of Object.values(categories)) { for (const id_category of Object.values(categories)) {
const data = await axios const data = await axios
.get(`https://www.dens.tv/api/dens3/tv/TvChannels/listByCategory`, { .get('https://www.dens.tv/api/dens3/tv/TvChannels/listByCategory', {
params: { id_category } params: { id_category }
}) })
.then(r => r.data) .then(r => r.data)

View file

@ -10,7 +10,9 @@ const date = dayjs.utc('2024-11-24').startOf('d')
const channel = { site_id: '38', xmltv_id: 'AniplusAsia.sg', lang: 'id' } const channel = { site_id: '38', xmltv_id: 'AniplusAsia.sg', lang: 'id' }
it('can generate valid url', () => { 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') 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', () => { it('can parse response', () => {

View file

@ -65,7 +65,7 @@ module.exports = {
const cheerio = require('cheerio') const cheerio = require('cheerio')
const data = await axios const data = await axios
.get(`https://www.digiturk.com.tr/`, { .get('https://www.digiturk.com.tr/', {
headers: { headers: {
'User-Agent': 'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36' 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36'

View file

@ -60,7 +60,7 @@ module.exports = {
$('.pgrid').each((i, el) => { $('.pgrid').each((i, el) => {
const onclick = $(el).find('.chnl-logo').attr('onclick') const onclick = $(el).find('.chnl-logo').attr('onclick')
const number = $(el).find('.cnl-fav > a > span').text().trim() const number = $(el).find('.cnl-fav > a > span').text().trim()
const [, name, site_id] = onclick.match(/ShowChannelGuid\('([^']+)','([^']+)'/) || [ const [, , site_id] = onclick.match(/ShowChannelGuid\('([^']+)','([^']+)'/) || [
null, null,
'', '',
'' ''

View file

@ -49,7 +49,7 @@ module.exports = {
const channels = [] const channels = []
for (let provider of providers) { for (let provider of providers) {
const data = await axios const data = await axios
.post(`https://www.guida.tv/guide/schedule`, null, { .post('https://www.guida.tv/guide/schedule', null, {
params: { params: {
provider, provider,
region: 'Italy', region: 'Italy',
@ -81,7 +81,7 @@ module.exports = {
} }
} }
function parseStart($item, date, channel) { function parseStart($item, date) {
const timeString = $item('td:eq(0)').text().trim() const timeString = $item('td:eq(0)').text().trim()
const dateString = `${date.format('YYYY-MM-DD')} ${timeString}` const dateString = `${date.format('YYYY-MM-DD')} ${timeString}`

View file

@ -35,7 +35,7 @@ module.exports = {
const cheerio = require('cheerio') const cheerio = require('cheerio')
const data = await axios const data = await axios
.get(`https://guidatv.sky.it/canali`) .get('https://guidatv.sky.it/canali')
.then(r => r.data) .then(r => r.data)
.catch(console.log) .catch(console.log)

View file

@ -16,7 +16,7 @@ module.exports = {
url: function ({ channel, date }) { url: function ({ channel, date }) {
return `https://epg-file.hoy.tv/hoy/OTT${channel.site_id}${date.format('YYYYMMDD')}.xml` return `https://epg-file.hoy.tv/hoy/OTT${channel.site_id}${date.format('YYYYMMDD')}.xml`
}, },
parser({ content, channel, date }) { parser({ content, date }) {
const data = convert.xml2js(content, { const data = convert.xml2js(content, {
compact: true, compact: true,
ignoreDeclaration: true, ignoreDeclaration: true,
@ -28,7 +28,7 @@ module.exports = {
for (let item of data.ProgramGuide.Channel.EpgItem) { for (let item of data.ProgramGuide.Channel.EpgItem) {
const start = dayjs.tz(item.EpgStartDateTime._text, 'YYYY-MM-DD HH:mm:ss', 'Asia/Hong_Kong') const start = dayjs.tz(item.EpgStartDateTime._text, 'YYYY-MM-DD HH:mm:ss', 'Asia/Hong_Kong')
if (! date.isSame(start, 'day')) { if (!date.isSame(start, 'day')) {
continue continue
} }
@ -40,13 +40,13 @@ module.exports = {
sub_title: subtitle, sub_title: subtitle,
description: item.EpisodeInfo.EpisodeLongDescription._text, description: item.EpisodeInfo.EpisodeLongDescription._text,
start, start,
stop: dayjs.tz(item.EpgEndDateTime._text, 'YYYY-MM-DD HH:mm:ss', 'Asia/Hong_Kong'), stop: dayjs.tz(item.EpgEndDateTime._text, 'YYYY-MM-DD HH:mm:ss', 'Asia/Hong_Kong')
}) })
} }
return programs return programs
}, },
async channels({ lang }) { async channels() {
const data = await axios const data = await axios
.get('https://api2.hoy.tv/api/v2/a/channel') .get('https://api2.hoy.tv/api/v2/a/channel')
.then(r => r.data) .then(r => r.data)
@ -56,7 +56,7 @@ module.exports = {
return { return {
site_id: c.videos.id, site_id: c.videos.id,
name: c.name.zh_hk, name: c.name.zh_hk,
lang: 'zh', lang: 'zh'
} }
}) })
} }

View file

@ -87,9 +87,7 @@ const content = `<?xml version="1.0" encoding="UTF-8" ?>
</ProgramGuide>` </ProgramGuide>`
it('can generate valid url', () => { it('can generate valid url', () => {
expect(url({ channel, date })).toBe( expect(url({ channel, date })).toBe('https://epg-file.hoy.tv/hoy/OTT7620240913.xml')
'https://epg-file.hoy.tv/hoy/OTT7620240913.xml'
)
}) })
it('can parse response', () => { it('can parse response', () => {
@ -104,13 +102,14 @@ it('can parse response', () => {
start: '2024-09-13T03:30:00.000Z', start: '2024-09-13T03:30:00.000Z',
stop: '2024-09-13T04:30:00.000Z', stop: '2024-09-13T04:30:00.000Z',
title: '點講都係一家人[PG]', title: '點講都係一家人[PG]',
sub_title: '第46集', sub_title: '第46集'
}, },
{ {
start: '2024-09-13T04:30:00.000Z', start: '2024-09-13T04:30:00.000Z',
stop: '2024-09-13T05:30:00.000Z', stop: '2024-09-13T05:30:00.000Z',
title: '麝香之路', title: '麝香之路',
description: 'Ep. 2 .The Secret of disappeared kingdom.shows the mysterious disappearance of the ancient Tibetan kingdom which gained world', description:
'Ep. 2 .The Secret of disappeared kingdom.shows the mysterious disappearance of the ancient Tibetan kingdom which gained world'
} }
]) ])
}) })

View file

@ -172,14 +172,17 @@ function parseItems(content, channel, date) {
if (!data || !Array.isArray(data.programs)) return [] if (!data || !Array.isArray(data.programs)) return []
return data.programs return data.programs
.filter(p => p.channel === site_id && dayjs(p.start, 'YYYYMMDDHHmmss ZZ').isBetween(curr_day, next_day)) .filter(
p =>
p.channel === site_id && dayjs(p.start, 'YYYYMMDDHHmmss ZZ').isBetween(curr_day, next_day)
)
.map(p => { .map(p => {
if (Array.isArray(p.date) && p.date.length) { if (Array.isArray(p.date) && p.date.length) {
p.date = p.date[0] p.date = p.date[0]
} }
return p return p
}) })
} catch (error) { } catch {
return [] return []
} }
} }

View file

@ -12,27 +12,30 @@ module.exports = {
site: 'ipko.tv', site: 'ipko.tv',
timezone: 'Europe/Belgrade', timezone: 'Europe/Belgrade',
days: 5, days: 5,
url({ date, channel }) { return 'https://stargate.ipko.tv/api/titan.tv.WebEpg/GetWebEpgData' }, url() {
return 'https://stargate.ipko.tv/api/titan.tv.WebEpg/GetWebEpgData'
},
request: { request: {
method: 'POST', method: 'POST',
headers: { headers: {
'Host': 'stargate.ipko.tv', Host: 'stargate.ipko.tv',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0', 'User-Agent':
'Accept': 'application/json, text/plain, */*', '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', 'Accept-Language': 'nl,en-US;q=0.7,en;q=0.3',
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-AppLayout': '1', 'X-AppLayout': '1',
'x-language': 'sq', 'x-language': 'sq',
'Origin': 'https://ipko.tv', Origin: 'https://ipko.tv',
'Sec-Fetch-Dest': 'empty', 'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors', 'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'cross-site', 'Sec-Fetch-Site': 'cross-site',
'Sec-GPC': '1', 'Sec-GPC': '1',
'Connection': 'keep-alive' Connection: 'keep-alive'
}, },
data({ channel, date }) { data({ channel, date }) {
const todayEpoch = date.startOf('day').unix(); const todayEpoch = date.startOf('day').unix()
const nextDayEpoch = date.add(1, 'day').startOf('day').unix(); const nextDayEpoch = date.add(1, 'day').startOf('day').unix()
return JSON.stringify({ return JSON.stringify({
ch_ext_id: channel.site_id, ch_ext_id: channel.site_id,
from: todayEpoch, from: todayEpoch,
@ -41,11 +44,11 @@ module.exports = {
} }
}, },
parser: function ({ content }) { parser: function ({ content }) {
const programs = []; const programs = []
const data = JSON.parse(content); const data = JSON.parse(content)
data.shows.forEach(show => { data.shows.forEach(show => {
const start = dayjs.unix(show.show_start).utc(); const start = dayjs.unix(show.show_start).utc()
const stop = dayjs.unix(show.show_end).utc(); const stop = dayjs.unix(show.show_end).utc()
const programData = { const programData = {
title: show.title, title: show.title,
description: show.summary || 'No description available', description: show.summary || 'No description available',
@ -58,15 +61,19 @@ module.exports = {
return programs return programs
}, },
async channels() { async channels() {
const response = await axios.post('https://stargate.ipko.tv/api/titan.tv.WebEpg/ZapList', JSON.stringify({ includeRadioStations: true }), { const response = await axios.post(
'https://stargate.ipko.tv/api/titan.tv.WebEpg/ZapList',
JSON.stringify({ includeRadioStations: true }),
{
headers: this.request.headers headers: this.request.headers
}); }
)
const data = response.data.data; const data = response.data.data
return data.map(item => ({ return data.map(item => ({
lang: 'sq', lang: 'sq',
name: String(item.channel.title), name: String(item.channel.title),
site_id: String(item.channel.id), site_id: String(item.channel.id)
//logo: String(item.channel.logo) //logo: String(item.channel.logo)
})) }))
} }

View file

@ -76,33 +76,29 @@ it('can parse response', () => {
] ]
}` }`
const result = parser({ content, channel }).map(p => { const result = parser({ content, channel })
p.start = p.start
p.stop = p.stop
return p
})
expect(result).toMatchObject([ expect(result).toMatchObject([
{ {
title: "IPKO Promo", title: 'IPKO Promo',
description: "No description available", description: 'No description available',
start: "2024-12-24T04:00:00.000Z", start: '2024-12-24T04:00:00.000Z',
stop: "2024-12-24T06: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" thumbnail: 'https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg'
}, },
{ {
title: "IPKO Promo", title: 'IPKO Promo',
description: "No description available", description: 'No description available',
start: "2024-12-24T06:00:00.000Z", start: '2024-12-24T06:00:00.000Z',
stop: "2024-12-24T08: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" thumbnail: 'https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg'
}, },
{ {
title: "IPKO Promo", title: 'IPKO Promo',
description: "No description available", description: 'No description available',
start: "2024-12-24T08:00:00.000Z", start: '2024-12-24T08:00:00.000Z',
stop: "2024-12-24T10: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" thumbnail: 'https://vimg.ipko.tv/mtcms/18/2/1/1821cc68-a9bf-4733-b1af-9a5d80163b78.jpg'
} }
]) ])
}) })

View file

@ -38,7 +38,7 @@ module.exports = {
async channels() { async channels() {
const axios = require('axios') const axios = require('axios')
const data = await axios const data = await axios
.get(`https://m.tv.sms.cz/?zmen_stanice=true`) .get('https://m.tv.sms.cz/?zmen_stanice=true')
.then(r => r.data) .then(r => r.data)
.catch(console.log) .catch(console.log)

View file

@ -77,8 +77,8 @@ function parseItems(content) {
let data let data
try { try {
data = JSON.parse(content) data = JSON.parse(content)
} catch (error) { } catch {
console.log(error.message) return []
} }
if (!data || !Array.isArray(data)) return [] if (!data || !Array.isArray(data)) return []

View file

@ -32,7 +32,7 @@ module.exports = {
async channels() { async channels() {
const axios = require('axios') const axios = require('axios')
const data = await axios const data = await axios
.get(`https://player.maxtvtogo.tportal.hr:8082/OTT4Proxy/proxy/epg/channels`) .get('https://player.maxtvtogo.tportal.hr:8082/OTT4Proxy/proxy/epg/channels')
.then(r => r.data) .then(r => r.data)
.catch(console.log) .catch(console.log)

View file

@ -56,7 +56,7 @@ function parseStop($item) {
try { try {
return dayjs(timeString, 'YYYY-MM-DD HH:mm:ssZZ') return dayjs(timeString, 'YYYY-MM-DD HH:mm:ssZZ')
} catch (err) { } catch {
return null return null
} }
} }

View file

@ -10,31 +10,35 @@ dayjs.extend(timezone)
module.exports = { module.exports = {
site: 'mediasetinfinity.mediaset.it', site: 'mediasetinfinity.mediaset.it',
days: 2, days: 2,
url: function ({date, channel}) { url: function ({ date, channel }) {
// Get the epoch timestamp // Get the epoch timestamp
const todayEpoch = date.startOf('day').utc().valueOf() const todayEpoch = date.startOf('day').utc().valueOf()
// Get the epoch timestamp for the next day // Get the epoch timestamp for the next day
const nextDayEpoch = date.add(1, 'day').startOf('day').utc().valueOf() 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}` return `https://api-ott-prod-fe.mediaset.net/PROD/play/feed/allListingFeedEpg/v2.0?byListingTime=${todayEpoch}~${nextDayEpoch}&byCallSign=${channel.site_id}`
}, },
parser: function ({content}) { parser: function ({ content }) {
const programs = [] const programs = []
const data = JSON.parse(content) const data = JSON.parse(content)
if (!data.response || !data.response.entries || !data.response.entries[0] || !data.response.entries[0].listings) { 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 // If the structure is not as expected, return an empty array
return programs return programs
} }
const listings = data.response.entries[0].listings const listings = data.response.entries[0].listings
listings.forEach((listing) => { listings.forEach(listing => {
const title = listing.mediasetlisting$epgTitle const title = listing.mediasetlisting$epgTitle
const subTitle = listing.program.title const subTitle = listing.program.title
const season = parseSeason(listing) const season = parseSeason(listing)
const episode = parseEpisode(listing) const episode = parseEpisode(listing)
if (listing.program.title && listing.startTime && listing.endTime) { if (listing.program.title && listing.startTime && listing.endTime) {
programs.push({ programs.push({
title: title || subTitle, title: title || subTitle,
@ -54,7 +58,6 @@ module.exports = {
} }
} }
function parseTime(timestamp) { function parseTime(timestamp) {
return dayjs(timestamp).utc().format('YYYY-MM-DD HH:mm') return dayjs(timestamp).utc().format('YYYY-MM-DD HH:mm')
} }
@ -77,17 +80,18 @@ function getMaxResolutionThumbnails(item) {
for (const key in thumbnails) { for (const key in thumbnails) {
const type = key.split('-')[0] // Estrarre il tipo di thumbnail const type = key.split('-')[0] // Estrarre il tipo di thumbnail
const {width, height, url, title} = thumbnails[key] const { width, height, url, title } = thumbnails[key]
if (!maxResolutionThumbnails[type] || if (
(width * height > maxResolutionThumbnails[type].width * maxResolutionThumbnails[type].height)) { !maxResolutionThumbnails[type] ||
maxResolutionThumbnails[type] = {width, height, url, title} width * height > maxResolutionThumbnails[type].width * maxResolutionThumbnails[type].height
) {
maxResolutionThumbnails[type] = { width, height, url, title }
} }
} }
if (maxResolutionThumbnails.image_keyframe_poster) if (maxResolutionThumbnails.image_keyframe_poster)
return maxResolutionThumbnails.image_keyframe_poster.url return maxResolutionThumbnails.image_keyframe_poster.url
else if (maxResolutionThumbnails.image_header_poster) else if (maxResolutionThumbnails.image_header_poster)
return maxResolutionThumbnails.image_header_poster.url return maxResolutionThumbnails.image_header_poster.url
else else return null
return null
} }

View file

@ -1,4 +1,4 @@
const {parser, url} = require('./mediasetinfinity.mediaset.it.config.js') const { parser, url } = require('./mediasetinfinity.mediaset.it.config.js')
const fs = require('fs') const fs = require('fs')
const path = require('path') const path = require('path')
const dayjs = require('dayjs') const dayjs = require('dayjs')
@ -9,19 +9,24 @@ dayjs.extend(utc)
const date = dayjs.utc('2024-01-20', 'YYYY-MM-DD').startOf('d') const date = dayjs.utc('2024-01-20', 'YYYY-MM-DD').startOf('d')
const channel = { const channel = {
site_id: 'LB', xmltv_id: '20.it' site_id: 'LB',
xmltv_id: '20.it'
} }
it('can generate valid url', () => { it('can generate valid url', () => {
expect(url({ expect(
url({
channel, channel,
date date
})).toBe('https://api-ott-prod-fe.mediaset.net/PROD/play/feed/allListingFeedEpg/v2.0?byListingTime=1705708800000~1705795200000&byCallSign=LB') })
).toBe(
'https://api-ott-prod-fe.mediaset.net/PROD/play/feed/allListingFeedEpg/v2.0?byListingTime=1705708800000~1705795200000&byCallSign=LB'
)
}) })
it('can parse response', () => { it('can parse response', () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8') const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.json'), 'utf8')
const results = parser({content, date}).map(p => { const results = parser({ content, date }).map(p => {
return p return p
}) })
@ -30,11 +35,13 @@ it('can parse response', () => {
stop: '2024-01-20 02:54', stop: '2024-01-20 02:54',
title: 'Chicago Fire', title: 'Chicago Fire',
sub_title: 'Ep. 22 - Io non ti lascio', 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.', 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', category: 'Intrattenimento',
season: '7', season: '7',
episode: '22', 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' 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'
}) })
}) })

View file

@ -40,7 +40,7 @@ module.exports = {
async channels() { async channels() {
const axios = require('axios') const axios = require('axios')
const data = await axios const data = await axios
.post(`https://authservice.apps.meo.pt/Services/GridTv/GridTvMng.svc/getGridAnon`, null, { .post('https://authservice.apps.meo.pt/Services/GridTv/GridTvMng.svc/getGridAnon', null, {
headers: { headers: {
Origin: 'https://www.meo.pt' Origin: 'https://www.meo.pt'
} }

View file

@ -42,7 +42,7 @@ module.exports = {
.catch(console.error) .catch(console.error)
if (content) { if (content) {
const [ $, items ] = getItems(content) const [$, items] = getItems(content)
if (seq === 0) { if (seq === 0) {
queues.push(...items.map(category => baseUrl + $(category).attr('href'))) queues.push(...items.map(category => baseUrl + $(category).attr('href')))
} else { } else {
@ -86,7 +86,11 @@ function parseItems(content, date) {
data.season = parseInt(ep[1]) data.season = parseInt(ep[1])
data.episode = parseInt(ep[2]) data.episode = parseInt(ep[2])
} }
data.start = dayjs.tz(`${lastDate} ${$item.find('.time').text()}`, 'DD/MM/YYYY HH:mm', 'America/Sao_Paulo') data.start = dayjs.tz(
`${lastDate} ${$item.find('.time').text()}`,
'DD/MM/YYYY HH:mm',
'America/Sao_Paulo'
)
result.push(data) result.push(data)
} }
} }

View file

@ -42,7 +42,7 @@ module.exports = {
const axios = require('axios') const axios = require('axios')
const cheerio = require('cheerio') const cheerio = require('cheerio')
const data = await axios const data = await axios
.get(`https://www.mewatch.sg/channel-guide`) .get('https://www.mewatch.sg/channel-guide')
.then(r => r.data) .then(r => r.data)
.catch(console.log) .catch(console.log)

View file

@ -11,9 +11,7 @@ dayjs.extend(utc)
dayjs.extend(timezone) dayjs.extend(timezone)
dayjs.extend(customParseFormat) dayjs.extend(customParseFormat)
doFetch doFetch.setCheckResult(false).setDebugger(debug)
.setCheckResult(false)
.setDebugger(debug)
const languages = { en: 'english', id: 'indonesia' } const languages = { en: 'english', id: 'indonesia' }
const cookies = {} const cookies = {}
@ -125,7 +123,7 @@ async function parseItems(content, date, cookies) {
const url = $item.find('a').attr('href') const url = $item.find('a').attr('href')
const headers = { const headers = {
'X-Requested-With': 'XMLHttpRequest', 'X-Requested-With': 'XMLHttpRequest',
Cookie: cookies, Cookie: cookies
} }
queues.push({ i: $item, url, params: { headers, timeout } }) queues.push({ i: $item, url, params: { headers, timeout } })
} }

View file

@ -48,8 +48,10 @@ function parseItems(context) {
schDayPrograms.forEach((program, i) => { schDayPrograms.forEach((program, i) => {
const itemDay = { const itemDay = {
progStart: parseStart($(schDayMonth), $(program)), progStart: parseStart($(schDayMonth), $(program)),
progStop: parseStop($(schDayMonth), schDayPrograms[i + 1] ? progStop: parseStop(
$(schDayPrograms[i + 1]) : null), $(schDayMonth),
schDayPrograms[i + 1] ? $(schDayPrograms[i + 1]) : null
),
progTitle: parseTitle($(program)), progTitle: parseTitle($(program)),
progDesc: parseDescription($(program)) progDesc: parseDescription($(program))
} }
@ -91,7 +93,9 @@ function parseStop(schDayMonth, itemNext) {
) )
} else { } else {
return dayjs.tz( return dayjs.tz(
`${currentYear}-${monthDate[0]}-${(parseInt(monthDate[1]) + 1).toString().padStart(2, '0')} 00:00`, `${currentYear}-${monthDate[0]}-${(parseInt(monthDate[1]) + 1)
.toString()
.padStart(2, '0')} 00:00`,
'YYYY-MMM-DD HH:mm', 'YYYY-MMM-DD HH:mm',
tz tz
) )

File diff suppressed because one or more lines are too long

View file

@ -41,7 +41,7 @@ module.exports = {
const pages = Array.from(Array(totalPages).keys()) const pages = Array.from(Array(totalPages).keys())
for (let page of pages) { for (let page of pages) {
const data = await axios const data = await axios
.get(`https://mtel.ba/oec/epg/program`, { .get('https://mtel.ba/oec/epg/program', {
params: { page, date: dayjs().format('YYYY-MM-DD') }, params: { page, date: dayjs().format('YYYY-MM-DD') },
headers: { headers: {
'X-Requested-With': 'XMLHttpRequest' 'X-Requested-With': 'XMLHttpRequest'
@ -65,7 +65,7 @@ module.exports = {
async function getTotalPageCount() { async function getTotalPageCount() {
const data = await axios const data = await axios
.get(`https://mtel.ba/oec/epg/program`, { .get('https://mtel.ba/oec/epg/program', {
params: { page: 0, date: dayjs().format('YYYY-MM-DD') }, params: { page: 0, date: dayjs().format('YYYY-MM-DD') },
headers: { headers: {
'X-Requested-With': 'XMLHttpRequest' 'X-Requested-With': 'XMLHttpRequest'

View file

@ -43,7 +43,7 @@ module.exports = {
const pages = Array.from(Array(totalPages).keys()) const pages = Array.from(Array(totalPages).keys())
for (let page of pages) { for (let page of pages) {
const data = await axios const data = await axios
.get(`https://mts.rs/oec/epg/program`, { .get('https://mts.rs/oec/epg/program', {
params: { page, date: dayjs().format('YYYY-MM-DD') }, params: { page, date: dayjs().format('YYYY-MM-DD') },
headers: { headers: {
'X-Requested-With': 'XMLHttpRequest' 'X-Requested-With': 'XMLHttpRequest'
@ -67,7 +67,7 @@ module.exports = {
async function getTotalPageCount() { async function getTotalPageCount() {
const data = await axios const data = await axios
.get(`https://mts.rs/oec/epg/program`, { .get('https://mts.rs/oec/epg/program', {
params: { page: 0, date: dayjs().format('YYYY-MM-DD') }, params: { page: 0, date: dayjs().format('YYYY-MM-DD') },
headers: { headers: {
'X-Requested-With': 'XMLHttpRequest' 'X-Requested-With': 'XMLHttpRequest'
@ -84,8 +84,8 @@ function parseContent(content, channel) {
let data let data
try { try {
data = JSON.parse(content) data = JSON.parse(content)
} catch (error) { } catch {
console.log(error) return []
} }
if (!data || !data.channels || !data.channels.length) return null if (!data || !data.channels || !data.channels.length) return null

View file

@ -47,7 +47,7 @@ module.exports = {
const data = await axios const data = await axios
.post( .post(
`https://services.mujtvprogram.cz/tvprogram2services/services/tvchannellist_mobile.php`, 'https://services.mujtvprogram.cz/tvprogram2services/services/tvchannellist_mobile.php',
params, params,
{ {
headers: { headers: {
@ -86,7 +86,7 @@ function parseItems(content) {
if (!data) return [] if (!data) return []
const programmes = data['tv-program-programmes'].programme const programmes = data['tv-program-programmes'].programme
return programmes && Array.isArray(programmes) ? programmes : [] return programmes && Array.isArray(programmes) ? programmes : []
} catch (err) { } catch {
return [] return []
} }
} }

View file

@ -9,7 +9,7 @@ dayjs.extend(customParseFormat)
const headers = { const headers = {
'User-Agent': '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', '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 = { module.exports = {

View file

@ -26,8 +26,7 @@ it('can generate valid url for today', () => {
it('can parse response', () => { it('can parse response', () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html')) const content = fs.readFileSync(path.resolve(__dirname, '__data__/content.html'))
const results = parser({ content, date }) const results = parser({ content, date }).map(p => {
.map(p => {
p.start = p.start.toJSON() p.start = p.start.toJSON()
p.stop = p.stop.toJSON() p.stop = p.stop.toJSON()
return p return p

View file

@ -23,14 +23,15 @@ module.exports = {
channel.site_id channel.site_id
}.html?dt=${date.format('YYYY-MM-DD')}` }.html?dt=${date.format('YYYY-MM-DD')}`
}, },
async parser({ content, date, channel }) { async parser({ content, date }) {
const programs = [] const programs = []
if (content) { if (content) {
const queues = [] const queues = []
const $ = cheerio.load(content) const $ = cheerio.load(content)
$('table.table > tbody > tr').toArray() $('table.table > tbody > tr')
.toArray()
.forEach(el => { .forEach(el => {
const td = $(el).find('td:eq(1)') const td = $(el).find('td:eq(1)')
const title = td.find('h5 a') const title = td.find('h5 a')
@ -66,12 +67,16 @@ module.exports = {
const subTitle = parseText($('.tab-pane > h5 > strong')) const subTitle = parseText($('.tab-pane > h5 > strong'))
const description = parseText($('.tab-pane > .tvbody > p')) const description = parseText($('.tab-pane > .tvbody > p'))
const image = $('.program-media-image img').attr('src') const image = $('.program-media-image img').attr('src')
const category = $('.schedule-attributes-genres span').toArray() const category = $('.schedule-attributes-genres span')
.toArray()
.map(el => $(el).text()) .map(el => $(el).text())
const casts = $('.single-cast-head:not([id])').toArray() const casts = $('.single-cast-head:not([id])')
.toArray()
.map(el => { .map(el => {
const cast = { name: parseText($(el).find('a')) } const cast = { name: parseText($(el).find('a')) }
const [, role] = $(el).text().match(/\((.*)\)/) || [null, null] const [, role] = $(el)
.text()
.match(/\((.*)\)/) || [null, null]
if (role) { if (role) {
cast.role = role cast.role = role
} }
@ -115,11 +120,17 @@ module.exports = {
// process form -> provider // process form -> provider
if (queue.t === 'p') { if (queue.t === 'p') {
const $ = cheerio.load(res) const $ = cheerio.load(res)
$('#guide_provider option').toArray() $('#guide_provider option')
.toArray()
.forEach(el => { .forEach(el => {
const opt = $(el) const opt = $(el)
const provider = opt.attr('value') const provider = opt.attr('value')
queues.push({ t: 'r', method: 'post', url: 'https://www.mytelly.co.uk/getregions', params: { provider } }) queues.push({
t: 'r',
method: 'post',
url: 'https://www.mytelly.co.uk/getregions',
params: { provider }
})
}) })
} }
// process provider -> region // process provider -> region
@ -135,14 +146,18 @@ module.exports = {
u_time: now.format('HHmm'), u_time: now.format('HHmm'),
is_mobile: 1 is_mobile: 1
} }
queues.push({ t: 's', method: 'post', url: 'https://www.mytelly.co.uk/tv-guide/schedule', params }) queues.push({
t: 's',
method: 'post',
url: 'https://www.mytelly.co.uk/tv-guide/schedule',
params
})
} }
} }
// process schedule -> channels // process schedule -> channels
if (queue.t === 's') { if (queue.t === 's') {
const $ = cheerio.load(res) const $ = cheerio.load(res)
$('.channelname') $('.channelname').each((i, el) => {
.each((i, el) => {
const name = $(el).find('center > a:eq(1)').text() const name = $(el).find('center > a:eq(1)').text()
const url = $(el).find('center > a:eq(1)').attr('href') const url = $(el).find('center > a:eq(1)').attr('href')
const [, number, slug] = url.match(/\/(\d+)\/(.*)\.html$/) const [, number, slug] = url.match(/\/(\d+)\/(.*)\.html$/)
@ -178,13 +193,10 @@ function parseTime(date, time) {
} }
function parseText($item) { function parseText($item) {
let text = $item.text() let text = $item.text().replace(/\t/g, '').replace(/\n/g, ' ').trim()
.replace(/\t/g, '')
.replace(/\n/g, ' ')
.trim()
while (true) { while (true) {
if (text.match(/ /)) { if (text.match(/\s\s/)) {
text = text.replace(/ /g, ' ') text = text.replace(/\s\s/g, ' ')
continue continue
} }
break break

View file

@ -17,16 +17,18 @@ const channel = {
xmltv_id: 'BBCOneLondon.uk' xmltv_id: 'BBCOneLondon.uk'
} }
axios.get.mockImplementation((url, opts) => { axios.get.mockImplementation(url => {
if ( if (
url === 'https://www.mytelly.co.uk/tv-guide/listings/programme?cid=713&pid=1906433&tm=2024-12-07+00%3A00%3A00' url ===
'https://www.mytelly.co.uk/tv-guide/listings/programme?cid=713&pid=1906433&tm=2024-12-07+00%3A00%3A00'
) { ) {
return Promise.resolve({ return Promise.resolve({
data: fs.readFileSync(path.join(__dirname, '__data__', 'programme.html')) data: fs.readFileSync(path.join(__dirname, '__data__', 'programme.html'))
}) })
} }
if ( if (
url === 'https://www.mytelly.co.uk/tv-guide/listings/programme?cid=713&pid=5656624&tm=2024-12-07+23%3A35%3A00' url ===
'https://www.mytelly.co.uk/tv-guide/listings/programme?cid=713&pid=5656624&tm=2024-12-07+23%3A35%3A00'
) { ) {
return Promise.resolve({ return Promise.resolve({
data: fs.readFileSync(path.join(__dirname, '__data__', 'programme2.html')) data: fs.readFileSync(path.join(__dirname, '__data__', 'programme2.html'))
@ -57,7 +59,8 @@ it('can parse response', async () => {
title: 'Captain Phillips', title: 'Captain Phillips',
description: 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', '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', image:
'https://d16ia5iwuvax6y.cloudfront.net/uk-prog-images/c44ce7b0d3ae602c0c93ece5af140815.jpg?k=VeeNdUjml3bSHdlZ0OXbGLy%2BmsLdYPwTV6iAxGkzq4dsylOCGGE7OWlqwSWt0cd0Qtrin4DkEMC0Zzdp8ZeNk2vNIQzjMF0DG0h3IeTR5NM%3D',
category: ['Factual', 'Movie/Drama', 'Thriller'] category: ['Factual', 'Movie/Drama', 'Thriller']
}) })
expect(results[1]).toMatchObject({ expect(results[1]).toMatchObject({
@ -67,7 +70,8 @@ it('can parse response', async () => {
subTitle: 'Past and Pressure Season 6, Episode 5', subTitle: 'Past and Pressure Season 6, Episode 5',
description: 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', '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', image:
'https://d16ia5iwuvax6y.cloudfront.net/uk-prog-images/2039278182b27cc279570b9ab9b89379.jpg?k=VeeNdUjml3bSHdlZ0OXbGLy%2BmsLdYPwTV6iAxGkzq4cDhR7jXTNFW3tgwQCdOPUobhXwlT81mIsqOe93HPusDG6tw1aoeYOgafojtynNWxc%3D',
category: ['Challenge/Reality Show', 'Show/Game Show'], category: ['Challenge/Reality Show', 'Show/Game Show'],
season: 6, season: 6,
episode: 5 episode: 5

View file

@ -12,27 +12,30 @@ module.exports = {
site: 'neo.io', site: 'neo.io',
timezone: 'Europe/Ljubljana', timezone: 'Europe/Ljubljana',
days: 5, days: 5,
url({ date, channel }) { return 'https://stargate.telekom.si/api/titan.tv.WebEpg/GetWebEpgData' }, url() {
return 'https://stargate.telekom.si/api/titan.tv.WebEpg/GetWebEpgData'
},
request: { request: {
method: 'POST', method: 'POST',
headers: { headers: {
'Host': 'stargate.telekom.si', Host: 'stargate.telekom.si',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0', 'User-Agent':
'Accept': 'application/json, text/plain, */*', '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', 'Accept-Language': 'nl,en-US;q=0.7,en;q=0.3',
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-AppLayout': '1', 'X-AppLayout': '1',
'x-language': 'sl', 'x-language': 'sl',
'Origin': 'https://neo.io', Origin: 'https://neo.io',
'Sec-Fetch-Dest': 'empty', 'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors', 'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'cross-site', 'Sec-Fetch-Site': 'cross-site',
'Sec-GPC': '1', 'Sec-GPC': '1',
'Connection': 'keep-alive' Connection: 'keep-alive'
}, },
data({ channel, date }) { data({ channel, date }) {
const todayEpoch = date.startOf('day').unix(); const todayEpoch = date.startOf('day').unix()
const nextDayEpoch = date.add(1, 'day').startOf('day').unix(); const nextDayEpoch = date.add(1, 'day').startOf('day').unix()
return JSON.stringify({ return JSON.stringify({
ch_ext_id: channel.site_id, ch_ext_id: channel.site_id,
from: todayEpoch, from: todayEpoch,
@ -41,11 +44,11 @@ module.exports = {
} }
}, },
parser: function ({ content }) { parser: function ({ content }) {
const programs = []; const programs = []
const data = JSON.parse(content); const data = JSON.parse(content)
data.shows.forEach(show => { data.shows.forEach(show => {
const start = dayjs.unix(show.show_start).utc(); const start = dayjs.unix(show.show_start).utc()
const stop = dayjs.unix(show.show_end).utc(); const stop = dayjs.unix(show.show_end).utc()
const programData = { const programData = {
title: show.title, title: show.title,
description: show.summary || 'No description available', description: show.summary || 'No description available',
@ -58,15 +61,19 @@ module.exports = {
return programs return programs
}, },
async channels() { async channels() {
const response = await axios.post('https://stargate.telekom.si/api/titan.tv.WebEpg/ZapList', JSON.stringify({ includeRadioStations: true }), { const response = await axios.post(
'https://stargate.telekom.si/api/titan.tv.WebEpg/ZapList',
JSON.stringify({ includeRadioStations: true }),
{
headers: this.request.headers headers: this.request.headers
}); }
)
const data = response.data.data; const data = response.data.data
return data.map(item => ({ return data.map(item => ({
lang: 'sq', lang: 'sq',
name: String(item.channel.title), name: String(item.channel.title),
site_id: String(item.channel.id), site_id: String(item.channel.id)
//logo: String(item.channel.logo) //logo: String(item.channel.logo)
})) }))
} }

View file

@ -12,7 +12,9 @@ const channel = {
} }
it('can generate valid url', () => { it('can generate valid url', () => {
expect(url({ date, channel })).toBe('https://stargate.telekom.si/api/titan.tv.WebEpg/GetWebEpgData') expect(url({ date, channel })).toBe(
'https://stargate.telekom.si/api/titan.tv.WebEpg/GetWebEpgData'
)
}) })
it('can parse response', () => { it('can parse response', () => {
@ -82,33 +84,34 @@ it('can parse response', () => {
] ]
}` }`
const result = parser({ content, channel }).map(p => { const result = parser({ content, channel })
p.start = p.start
p.stop = p.stop
return p
})
expect(result).toMatchObject([ expect(result).toMatchObject([
{ {
title: "Napovedujemo", title: 'Napovedujemo',
description: "Vabilo k ogledu naših oddaj.", description: 'Vabilo k ogledu naših oddaj.',
start: "2024-12-26T04:05:00.000Z", start: '2024-12-26T04:05:00.000Z',
stop: "2024-12-26T05:50: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" thumbnail:
'https://ngimg.siol.tv/sioltv/mtcmsprod/52/0/0/5200d01a-fe5f-487e-835a-274e77227a6b.jpg'
}, },
{ {
title: "S0E0 - Hrabri zajčki: Prvi sneg", 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.", description:
start: "2024-12-26T05:50:00.000Z", '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.',
stop: "2024-12-26T06:00:00.000Z", start: '2024-12-26T05:50:00.000Z',
thumbnail: "https://ngimg.siol.tv/sioltv/mtcmsprod/d6/4/5/d6456f4a-4f0a-4825-90c1-1749abd59688.jpg" 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", title: 'Dobro jutro',
description: "Oddaja Dobro jutro poleg informativnih in zabavnih vsebin podaja koristne nasvete o najrazličnejših tematikah iz vsakdanjega življenja.", description:
start: "2024-12-26T06:00:00.000Z", 'Oddaja Dobro jutro poleg informativnih in zabavnih vsebin podaja koristne nasvete o najrazličnejših tematikah iz vsakdanjega življenja.',
stop: "2024-12-26T09:05:00.000Z", start: '2024-12-26T06:00:00.000Z',
thumbnail: "https://ngimg.siol.tv/sioltv/mtcmsprod/e1/2/d/e12d8eb4-693a-43d3-89d4-fd96dade9f0f.jpg" stop: '2024-12-26T09:05:00.000Z',
thumbnail:
'https://ngimg.siol.tv/sioltv/mtcmsprod/e1/2/d/e12d8eb4-693a-43d3-89d4-fd96dade9f0f.jpg'
} }
]) ])
}) })

View file

@ -50,7 +50,7 @@ function parseItems(content, date) {
if (!data || !data.item || !Array.isArray(data.item.episodes)) return [] if (!data || !data.item || !Array.isArray(data.item.episodes)) return []
return data.item.episodes.filter(ep => ep.schedule.startsWith(date.format('YYYY-MM-DD'))) return data.item.episodes.filter(ep => ep.schedule.startsWith(date.format('YYYY-MM-DD')))
} catch (err) { } catch {
return [] return []
} }
} }

View file

@ -5,7 +5,8 @@ module.exports = {
// I'm not sure what `endDate` represents but they only return 1 day of // I'm not sure what `endDate` represents but they only return 1 day of
// results, with `endTime`s ocassionally in the following day. // results, with `endTime`s ocassionally in the following day.
days: 1, days: 1,
url: ({ date }) => `https://api-web.nhle.com/v1/network/tv-schedule/${date.toJSON().split("T")[0]}`, url: ({ date }) =>
`https://api-web.nhle.com/v1/network/tv-schedule/${date.toJSON().split('T')[0]}`,
parser({ content }) { parser({ content }) {
const programs = [] const programs = []
const items = parseItems(content) const items = parseItems(content)
@ -13,7 +14,7 @@ module.exports = {
programs.push({ programs.push({
title: item.title, title: item.title,
description: item.description === item.title ? undefined : item.description, description: item.description === item.title ? undefined : item.description,
category: "Sports", category: 'Sports',
// image: parseImage(item), // image: parseImage(item),
start: parseStart(item), start: parseStart(item),
stop: parseStop(item) stop: parseStop(item)

View file

@ -10,9 +10,7 @@ dayjs.extend(utc)
const date = dayjs.utc('2024-11-21', 'YYYY-MM-DD').startOf('d') const date = dayjs.utc('2024-11-21', 'YYYY-MM-DD').startOf('d')
it('can generate valid url', () => { it('can generate valid url', () => {
expect(url({ date })).toBe( expect(url({ date })).toBe('https://api-web.nhle.com/v1/network/tv-schedule/2024-11-21')
'https://api-web.nhle.com/v1/network/tv-schedule/2024-11-21'
)
}) })
it('can parse response', () => { it('can parse response', () => {
@ -28,17 +26,19 @@ it('can parse response', () => {
start: '2024-11-21T12:00:00.000Z', start: '2024-11-21T12:00:00.000Z',
stop: '2024-11-21T13:00:00.000Z', stop: '2024-11-21T13:00:00.000Z',
title: 'On The Fly', title: 'On The Fly',
category: 'Sports', category: 'Sports'
}) })
}) })
it('can handle empty guide', () => { it('can handle empty guide', () => {
const results = parser({ content: JSON.stringify({ const results = parser({
content: JSON.stringify({
// extra props not necessary but they form a valid response // extra props not necessary but they form a valid response
date: "2024-11-21", date: '2024-11-21',
startDate: "2024-11-07", startDate: '2024-11-07',
endDate: "2024-12-05", endDate: '2024-12-05',
broadcasts: [], broadcasts: []
}) }) })
})
expect(results).toMatchObject([]) expect(results).toMatchObject([])
}) })

View file

@ -55,7 +55,7 @@ module.exports = {
.map(function () { .map(function () {
return { return {
lang: 'es', lang: 'es',
site_id: $(this).attr('alt').replace(/\&/gi, '&amp;'), site_id: $(this).attr('alt').replace(/&/gi, '&amp;'),
name: $(this).attr('alt') name: $(this).attr('alt')
} }
}) })

View file

@ -27,7 +27,7 @@ module.exports = {
if (item.episodeNum) { if (item.episodeNum) {
item.episodeNum.forEach(ep => { item.episodeNum.forEach(ep => {
if (ep.system === 'xmltv_ns') { if (ep.system === 'xmltv_ns') {
const [season, episode, _] = ep.value.split('.') const [season, episode] = ep.value.split('.')
program.season = parseInt(season) + 1 program.season = parseInt(season) + 1
program.episode = parseInt(episode) + 1 program.episode = parseInt(episode) + 1
return true return true

View file

@ -132,7 +132,7 @@ module.exports = {
const $ = cheerio.load(data) const $ = cheerio.load(data)
$('.channelname').each((i, el) => { $('.channelname').each((i, el) => {
let name = $(el).find('center > a:eq(1)').text() let name = $(el).find('center > a:eq(1)').text()
name = name.replace(/\-\-/gi, '-') name = name.replace(/--/gi, '-')
const url = $(el).find('center > a:eq(1)').attr('href') const url = $(el).find('center > a:eq(1)').attr('href')
if (!url) return if (!url) return
const [, number, slug] = url.match(/\/(\d+)\/(.*)\.html$/) const [, number, slug] = url.match(/\/(\d+)\/(.*)\.html$/)

View file

@ -5,7 +5,8 @@ const axios = require('axios')
dayjs.extend(utc) dayjs.extend(utc)
const API_PROGRAM_ENDPOINT = 'https://epg.orangetv.orange.es/epg/Smartphone_Android/1_PRO' 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_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' const API_IMAGE_ENDPOINT = 'https://pc.orangetv.orange.es/pc/api/rtv/v1/images'
module.exports = { module.exports = {
@ -25,15 +26,9 @@ module.exports = {
if (!items.length) return programs if (!items.length) return programs
const promises = [ const promises = [
axios.get( axios.get(`${API_PROGRAM_ENDPOINT}/${date.format('YYYYMMDD')}_8h_1.json`),
`${API_PROGRAM_ENDPOINT}/${date.format('YYYYMMDD')}_8h_1.json`, axios.get(`${API_PROGRAM_ENDPOINT}/${date.format('YYYYMMDD')}_8h_2.json`),
), axios.get(`${API_PROGRAM_ENDPOINT}/${date.format('YYYYMMDD')}_8h_3.json`)
axios.get(
`${API_PROGRAM_ENDPOINT}/${date.format('YYYYMMDD')}_8h_2.json`,
),
axios.get(
`${API_PROGRAM_ENDPOINT}/${date.format('YYYYMMDD')}_8h_3.json`,
),
] ]
await Promise.allSettled(promises) await Promise.allSettled(promises)
@ -42,7 +37,9 @@ module.exports = {
if (r.status === 'fulfilled') { if (r.status === 'fulfilled') {
const parsed = parseItems(r.value.data, channel) const parsed = parseItems(r.value.data, channel)
items = items.filter((item, index) => items.findIndex(oi => oi.id === item.id) === index).concat(parsed) items = items
.filter((item, index) => items.findIndex(oi => oi.id === item.id) === index)
.concat(parsed)
} }
}) })
}) })
@ -57,7 +54,7 @@ module.exports = {
episode: item.episodeId || null, episode: item.episodeId || null,
icon: parseIcon(item), icon: parseIcon(item),
start: dayjs.utc(item.startDate) || null, start: dayjs.utc(item.startDate) || null,
stop: dayjs.utc(item.endDate) || null, stop: dayjs.utc(item.endDate) || null
}) })
}) })
@ -79,35 +76,33 @@ module.exports = {
} }
} }
function parseIcon(item){ function parseIcon(item) {
if (item.attachments.length > 0) {
const cover = item.attachments.find(i => i.name === 'COVER' || i.name === 'cover')
if(item.attachments.length > 0){ if (cover) {
const cover = item.attachments.find(i => i.name === "COVER" || i.name === "cover") return `${API_IMAGE_ENDPOINT}${cover.value}`
if(cover)
{
return `${API_IMAGE_ENDPOINT}${cover.value}`;
} }
} }
return '' return ''
} }
function parseGenres(item){ function parseGenres(item) {
return item.genres.map(i => i.name); return item.genres.map(i => i.name)
} }
function parseItems(content, channel) { function parseItems(content, channel) {
const json = typeof content === 'string' ? JSON.parse(content) : Array.isArray(content) ? content : [] const json =
typeof content === 'string' ? JSON.parse(content) : Array.isArray(content) ? content : []
if (!Array.isArray(json)) { if (!Array.isArray(json)) {
return []; return []
} }
const channelData = json.find(i => i.channelExternalId == channel.site_id); const channelData = json.find(i => i.channelExternalId == channel.site_id)
if(!channelData) if (!channelData) return []
return [];
return channelData.programs; return channelData.programs
} }

View file

@ -14,7 +14,11 @@ const channel = {
} }
it('can generate valid url', () => { it('can generate valid url', () => {
expect(url({ date })).toBe(`https://epg.orangetv.orange.es/epg/Smartphone_Android/1_PRO/${date.format('YYYYMMDD')}_8h_1.json`) expect(url({ date })).toBe(
`https://epg.orangetv.orange.es/epg/Smartphone_Android/1_PRO/${date.format(
'YYYYMMDD'
)}_8h_1.json`
)
}) })
it('can parse response', async () => { it('can parse response', async () => {
@ -28,13 +32,14 @@ it('can parse response', async () => {
expect(results.length).toBe(4) expect(results.length).toBe(4)
var sampleResult = results[0]; var sampleResult = results[0]
expect(sampleResult).toMatchObject({ expect(sampleResult).toMatchObject({
start: '2024-11-30T22:36:51.000Z', start: '2024-11-30T22:36:51.000Z',
stop: '2024-11-30T23:57:25.000Z', stop: '2024-11-30T23:57:25.000Z',
category: ['Cine', 'Romance', 'Comedia', 'Comedia Romántica'], category: ['Cine', 'Romance', 'Comedia', 'Comedia Romántica'],
description: 'Charlie trabaja como director en una escuela de primaria y goza de una placentera existencia junto a sus amigos. A pesar de ello, no es feliz porque cada vez que se enamora pierde la cordura.', description:
'Charlie trabaja como director en una escuela de primaria y goza de una placentera existencia junto a sus amigos. A pesar de ello, no es feliz porque cada vez que se enamora pierde la cordura.',
title: 'Loco de amor' title: 'Loco de amor'
}) })
}) })

View file

@ -5,7 +5,12 @@ const timezone = require('dayjs/plugin/timezone')
dayjs.extend(utc) dayjs.extend(utc)
dayjs.extend(timezone) dayjs.extend(timezone)
const packages = { 'OSNTV CONNECT': 3720, 'OSNTV PRIME': 3733, 'ALFA': 1281, 'OSN PINOY PLUS EXTRA': 3519 } const packages = {
'OSNTV CONNECT': 3720,
'OSNTV PRIME': 3733,
ALFA: 1281,
'OSN PINOY PLUS EXTRA': 3519
}
const country = 'AE' const country = 'AE'
const tz = 'Asia/Dubai' const tz = 'Asia/Dubai'
@ -13,11 +18,9 @@ module.exports = {
site: 'osn.com', site: 'osn.com',
days: 2, days: 2,
url({ channel, date }) { url({ channel, date }) {
return `https://www.osn.com/api/TVScheduleWebService.asmx/time?dt=${ return `https://www.osn.com/api/TVScheduleWebService.asmx/time?dt=${encodeURIComponent(
encodeURIComponent(date.format('MM/DD/YYYY')) date.format('MM/DD/YYYY')
}&co=${country}&ch=${ )}&co=${country}&ch=${channel.site_id}&mo=false&hr=0`
channel.site_id
}&mo=false&hr=0`
}, },
request: { request: {
headers({ channel }) { headers({ channel }) {
@ -46,7 +49,9 @@ module.exports = {
const axios = require('axios') const axios = require('axios')
for (const pkg of Object.values(packages)) { for (const pkg of Object.values(packages)) {
const channels = await axios const channels = await axios
.get(`https://www.osn.com/api/tvchannels.ashx?culture=en-US&packageId=${pkg}&country=${country}`) .get(
`https://www.osn.com/api/tvchannels.ashx?culture=en-US&packageId=${pkg}&country=${country}`
)
.then(response => response.data) .then(response => response.data)
.catch(console.error) .catch(console.error)

View file

@ -28,8 +28,7 @@ it('can generate valid url', () => {
}) })
it('can parse response (ar)', () => { it('can parse response (ar)', () => {
const result = parser({ date, channel: channelAR, content }) const result = parser({ date, channel: channelAR, content }).map(a => {
.map(a => {
a.start = a.start.toJSON() a.start = a.start.toJSON()
a.stop = a.stop.toJSON() a.stop = a.stop.toJSON()
return a return a
@ -38,13 +37,12 @@ it('can parse response (ar)', () => {
expect(result[1]).toMatchObject({ expect(result[1]).toMatchObject({
start: '2024-11-26T20:50:00.000Z', start: '2024-11-26T20:50:00.000Z',
stop: '2024-11-26T21:45:00.000Z', stop: '2024-11-26T21:45:00.000Z',
title: 'بيت الحلويات: الحلقة 3', title: 'بيت الحلويات: الحلقة 3'
}) })
}) })
it('can parse response (en)', () => { it('can parse response (en)', () => {
const result = parser({ date, channel: channelEN, content }) const result = parser({ date, channel: channelEN, content }).map(a => {
.map(a => {
a.start = a.start.toJSON() a.start = a.start.toJSON()
a.stop = a.stop.toJSON() a.stop = a.stop.toJSON()
return a return a
@ -53,7 +51,7 @@ it('can parse response (en)', () => {
expect(result[1]).toMatchObject({ expect(result[1]).toMatchObject({
start: '2024-11-26T20:50:00.000Z', start: '2024-11-26T20:50:00.000Z',
stop: '2024-11-26T21:45:00.000Z', stop: '2024-11-26T21:45:00.000Z',
title: 'House Of Desserts: Episode 3', title: 'House Of Desserts: Episode 3'
}) })
}) })

View file

@ -27,7 +27,7 @@ function parseItems(content, date) {
let data let data
try { try {
data = JSON.parse(json) data = JSON.parse(json)
} catch (error) { } catch {
return [] return []
} }

View file

@ -129,16 +129,13 @@ module.exports = {
) )
} }
} }
function fetchApiVersion() { async function fetchApiVersion() {
return new Promise(async (resolve, reject) => {
try {
// you'll never find what happened here :) // you'll never find what happened here :)
// load the pickx page and get the hash from the MWC configuration. // load the pickx page and get the hash from the MWC configuration.
// it's not the best way to get the version but it's the only way to get it. // it's not the best way to get the version but it's the only way to get it.
const hashUrl = 'https://www.pickx.be/nl/televisie/tv-gids'
const hashUrl = 'https://www.pickx.be/nl/televisie/tv-gids'; const hashData = await axios
.get(hashUrl)
const hashData = await axios.get(hashUrl)
.then(r => { .then(r => {
const re = /"hashes":\["(.*)"\]/ const re = /"hashes":\["(.*)"\]/
const match = r.data.match(re) const match = r.data.match(re)
@ -148,10 +145,9 @@ function fetchApiVersion() {
throw new Error('React app version hash not found') throw new Error('React app version hash not found')
} }
}) })
.catch(console.error); .catch(console.error)
const versionUrl = `https://www.pickx.be/api/s-${hashData}` const versionUrl = `https://www.pickx.be/api/s-${hashData}`
const response = await axios.get(versionUrl, { const response = await axios.get(versionUrl, {
headers: { headers: {
Origin: 'https://www.pickx.be', Origin: 'https://www.pickx.be',
@ -159,6 +155,8 @@ function fetchApiVersion() {
} }
}) })
return new Promise((resolve, reject) => {
try {
if (response.status === 200) { if (response.status === 200) {
apiVersion = response.data.version apiVersion = response.data.version
resolve() resolve()

View file

@ -6,14 +6,7 @@ jest.mock('./pickx.be.config.js', () => {
} }
}) })
const { const { parser, url, request, setApiVersion } = require('./pickx.be.config.js')
parser,
url,
request,
fetchApiVersion,
setApiVersion,
getApiVersion
} = require('./pickx.be.config.js')
const fs = require('fs') const fs = require('fs')
const path = require('path') const path = require('path')
@ -36,7 +29,7 @@ beforeEach(() => {
it('can generate valid url', async () => { it('can generate valid url', async () => {
const generatedUrl = await url({ channel, date }) const generatedUrl = await url({ channel, date })
expect(generatedUrl).toBe( expect(generatedUrl).toBe(
`https://px-epg.azureedge.net/airings/mockedApiVersion/2023-12-13/channel/UID0118?timezone=Europe%2FBrussels` 'https://px-epg.azureedge.net/airings/mockedApiVersion/2023-12-13/channel/UID0118?timezone=Europe%2FBrussels'
) )
}) })

View file

@ -8,9 +8,9 @@ module.exports = {
site: 'player.ee.co.uk', site: 'player.ee.co.uk',
days: 2, days: 2,
url({ date, channel, hour = 0 }) { url({ date, channel, hour = 0 }) {
return `https://api.youview.tv/metadata/linear/v2/schedule/by-servicelocator?serviceLocator=${ return `https://api.youview.tv/metadata/linear/v2/schedule/by-servicelocator?serviceLocator=${encodeURIComponent(
encodeURIComponent(channel.site_id) channel.site_id
}&interval=${date.format('YYYY-MM-DD')}T${hour.toString().padStart(2,'0')}Z/PT12H` )}&interval=${date.format('YYYY-MM-DD')}T${hour.toString().padStart(2, '0')}Z/PT12H`
}, },
request: { request: {
headers: { headers: {
@ -39,7 +39,7 @@ module.exports = {
const stop = start.add(item.publishedDuration, 's') const stop = start.add(item.publishedDuration, 's')
const description = item.synopsis const description = item.synopsis
if (description) { if (description) {
const matches = description.trim().match(/\(?S(\d+)[\/\s]Ep(\d+)\)?/) const matches = description.trim().match(/\(?S(\d+)[/\s]Ep(\d+)\)?/)
if (matches) { if (matches) {
if (matches[1]) { if (matches[1]) {
season = parseInt(matches[1]) season = parseInt(matches[1])
@ -79,7 +79,7 @@ module.exports = {
'BTSubscriptionCodesExtension' 'BTSubscriptionCodesExtension'
] ]
const result = await axios const result = await axios
.get(`https://api.youview.tv/metadata/linear/v2/linear-services`, { .get('https://api.youview.tv/metadata/linear/v2/linear-services', {
params: { params: {
contentTargetingToken: token, contentTargetingToken: token,
extensions: extensions.join(',') extensions: extensions.join(',')
@ -89,7 +89,8 @@ module.exports = {
.then(response => response.data) .then(response => response.data)
.catch(console.error) .catch(console.error)
return result?.items return (
result?.items
.filter(channel => channel.contentTypes.indexOf('tv') >= 0) .filter(channel => channel.contentTypes.indexOf('tv') >= 0)
.map(channel => { .map(channel => {
return { return {
@ -98,5 +99,6 @@ module.exports = {
name: channel.fullName name: channel.fullName
} }
}) || [] }) || []
)
} }
} }

View file

@ -15,8 +15,11 @@ const channel = {
xmltv_id: 'HGTV.uk' xmltv_id: 'HGTV.uk'
} }
axios.get.mockImplementation((url, opts) => { 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') { 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({ return Promise.resolve({
data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/data1.json'))) data: JSON.parse(fs.readFileSync(path.resolve(__dirname, '__data__/data1.json')))
}) })
@ -33,8 +36,7 @@ it('can generate valid url', () => {
it('can parse response', async () => { it('can parse response', async () => {
const content = fs.readFileSync(path.resolve(__dirname, '__data__/data.json')) const content = fs.readFileSync(path.resolve(__dirname, '__data__/data.json'))
const result = (await parser({ content, channel, date })) const result = (await parser({ content, channel, date })).map(p => {
.map(p => {
p.start = p.start.toJSON() p.start = p.start.toJSON()
p.stop = p.stop.toJSON() p.stop = p.stop.toJSON()
return p return p

View file

@ -43,7 +43,7 @@ module.exports = {
const axios = require('axios') const axios = require('axios')
const data = await axios const data = await axios
.post( .post(
`https://playtv.unifi.com.my:7053/VSP/V3/QueryAllChannel`, 'https://playtv.unifi.com.my:7053/VSP/V3/QueryAllChannel',
{ isReturnAllMedia: '0' }, { isReturnAllMedia: '0' },
{ {
params: { params: {
@ -74,7 +74,7 @@ function parseItems(content, channel) {
const channelData = data.find(i => i.id == channel.site_id) const channelData = data.find(i => i.id == channel.site_id)
return channelData.items && Array.isArray(channelData.items) ? channelData.items : [] return channelData.items && Array.isArray(channelData.items) ? channelData.items : []
} catch (err) { } catch {
return [] return []
} }
} }

View file

@ -1,7 +1,6 @@
const dayjs = require('dayjs') const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc') const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone') const timezone = require('dayjs/plugin/timezone')
const axios = require('axios')
dayjs.extend(utc) dayjs.extend(utc)
dayjs.extend(timezone) dayjs.extend(timezone)

View file

@ -33,14 +33,17 @@ it('can parse response', () => {
start: '2024-12-28T00:21:00.000Z', start: '2024-12-28T00:21:00.000Z',
stop: '2024-12-28T00:48:00.000Z', stop: '2024-12-28T00:48:00.000Z',
title: 'Naruto: El Tercer Hokage, Eternamente', 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.', 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', subTitle: 'El Tercer Hokage, Eternamente',
episode: 80, episode: 80,
season: 2, season: 2,
actors: ["Isabel Martion (Naruto Uzumaki)", actors: [
"Christine Byrd (Sakura Haruno)", 'Isabel Martion (Naruto Uzumaki)',
"Victor Ugarte (Sasuke Uchiha)", 'Christine Byrd (Sakura Haruno)',
"Alfonso Obreg (Kakashi Hatake)"], 'Victor Ugarte (Sasuke Uchiha)',
'Alfonso Obreg (Kakashi Hatake)'
],
categories: ['Anime', 'Anime Action & Adventure'], categories: ['Anime', 'Anime Action & Adventure'],
rating: 'TV-14', rating: 'TV-14',
date: '2004-04-21T00:00:00.000Z', date: '2004-04-21T00:00:00.000Z',

View file

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<channels> <channels>
<channel site="pluto.tv" lang="en" xmltv_id="PlutoTVAdventCalendar.ca" site_id="6712256349c4060008e9e0f0">Pluto TV Advent Calendar</channel> <channel site="pluto.tv" lang="en" xmltv_id="PlutoTVAdventCalendar.ca" site_id="6712256349c4060008e9e0f0">Pluto TV Advent Calendar</channel>

View file

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<channels> <channels>
<channel site="pluto.tv" lang="en" xmltv_id="Diane,femmeflic.uk" site_id="6671b26836a2f90008d9333c">Diane, femme flic</channel> <channel site="pluto.tv" lang="en" xmltv_id="Diane,femmeflic.uk" site_id="6671b26836a2f90008d9333c">Diane, femme flic</channel>

View file

@ -46,10 +46,10 @@ module.exports = {
return programs return programs
}, },
async channels({ country, lang }) { async channels() {
const axios = require('axios') const axios = require('axios')
const data = await axios const data = await axios
.get(`https://www.programetv.ro/api/station/index/`) .get('https://www.programetv.ro/api/station/index/')
.then(r => r.data) .then(r => r.data)
.catch(console.log) .catch(console.log)

View file

@ -62,7 +62,7 @@ module.exports = {
$('.channelList-listItemsLink').each((i, el) => { $('.channelList-listItemsLink').each((i, el) => {
const name = $(el).attr('title') const name = $(el).attr('title')
const url = $(el).attr('href') const url = $(el).attr('href')
const [, site_id] = url.match(/\/programme\-(.*)\.html$/i) const [, site_id] = url.match(/\/programme-(.*)\.html$/i)
channels.push({ channels.push({
lang: 'fr', lang: 'fr',

View file

@ -51,7 +51,7 @@ module.exports = {
return data.programmes.map(item => { return data.programmes.map(item => {
const site_id = item.url.replace('/', '') const site_id = item.url.replace('/', '')
const name = site_id.replace(/\-/gi, ' ') const name = site_id.replace(/-/gi, ' ')
return { return {
lang: 'fr', lang: 'fr',

View file

@ -13,19 +13,17 @@ module.exports = {
site: 'programme.tvb.com', site: 'programme.tvb.com',
days: 2, days: 2,
url({ channel, date, time = null }) { url({ channel, date, time = null }) {
return `https://programme.tvb.com/api/schedule?input_date=${ return `https://programme.tvb.com/api/schedule?input_date=${date.format(
date.format('YYYYMMDD') 'YYYYMMDD'
}&network_code=${channel.site_id}&_t=${time ? time : parseInt(Date.now() / 1000)}` )}&network_code=${channel.site_id}&_t=${time ? time : parseInt(Date.now() / 1000)}`
}, },
parser({ content, channel, date }) { parser({ content, channel, date }) {
const programs = [] const programs = []
const data = content ? JSON.parse(content) : {} const data = content ? JSON.parse(content) : {}
if (Array.isArray(data.data?.list)) { if (Array.isArray(data.data?.list)) {
const dt = date.format('YYYY-MM-DD')
for (const d of data.data.list) { for (const d of data.data.list) {
if (Array.isArray(d.schedules)) { if (Array.isArray(d.schedules)) {
const schedules = d.schedules const schedules = d.schedules.filter(s => s.network_code === channel.site_id)
.filter(s => s.network_code === channel.site_id)
schedules.forEach((s, i) => { schedules.forEach((s, i) => {
const start = dayjs.tz(s.event_datetime, 'YYYY-MM-DD HH:mm:ss', tz) const start = dayjs.tz(s.event_datetime, 'YYYY-MM-DD HH:mm:ss', tz)
let stop let stop
@ -64,7 +62,7 @@ module.exports = {
if (assets) { if (assets) {
queues.push(...assets.map(a => base + '/' + a)) queues.push(...assets.map(a => base + '/' + a))
} else { } else {
const metadata = content.match(/e\=(\[(.*?)\])/) const metadata = content.match(/e=(\[(.*?)\])/)
if (metadata) { if (metadata) {
const infos = eval(metadata[1]) const infos = eval(metadata[1])
if (Array.isArray(infos)) { if (Array.isArray(infos)) {

View file

@ -34,7 +34,7 @@ it('can parse response (en)', () => {
expect(results[1]).toMatchObject({ expect(results[1]).toMatchObject({
start: '2024-12-06T15:55:00.000Z', start: '2024-12-06T15:55:00.000Z',
stop: '2024-12-06T16:55:00.000Z', stop: '2024-12-06T16:55:00.000Z',
title: 'Line Walker: Bull Fight#16[Can][PG]', title: 'Line Walker: Bull Fight#16[Can][PG]'
}) })
}) })
@ -51,7 +51,7 @@ it('can parse response (zh)', () => {
stop: '2024-12-06T16:55:00.000Z', stop: '2024-12-06T16:55:00.000Z',
title: '使徒行者3#16[粵][PG]', title: '使徒行者3#16[粵][PG]',
description: description:
'文鼎從淑梅手上救走大聖爺兒子,大聖爺還恩於歡喜,答允支持九指強。崇聯社定下選舉日子,恰巧是韋傑出獄之日,頭目們顧念舊日恩義,紛紛轉投浩洋。浩洋帶亞希逛傢俬店,憧憬二人未來。亞希向家強承認愛上浩洋,要求退出臥底任務。作榮與歡喜暗中會面,將國際犯罪組織「永恆幫」情報交給他。阿火遭家強出賣,到沐足店搶錢。家強逮住阿火,惟被合星誤會而受拘捕。家強把正植遺下的頸鏈和學生證交還,合星意識到家強已知悉正植身世。', '文鼎從淑梅手上救走大聖爺兒子,大聖爺還恩於歡喜,答允支持九指強。崇聯社定下選舉日子,恰巧是韋傑出獄之日,頭目們顧念舊日恩義,紛紛轉投浩洋。浩洋帶亞希逛傢俬店,憧憬二人未來。亞希向家強承認愛上浩洋,要求退出臥底任務。作榮與歡喜暗中會面,將國際犯罪組織「永恆幫」情報交給他。阿火遭家強出賣,到沐足店搶錢。家強逮住阿火,惟被合星誤會而受拘捕。家強把正植遺下的頸鏈和學生證交還,合星意識到家強已知悉正植身世。'
}) })
}) })

View file

@ -50,7 +50,7 @@ module.exports = {
$('ul.channelList a').each((i, el) => { $('ul.channelList a').each((i, el) => {
const name = $(el).text() const name = $(el).text()
const url = $(el).attr('href') const url = $(el).attr('href')
const [, site_id] = url.match(/^\/program\-tv\/(.*)$/i) const [, site_id] = url.match(/^\/program-tv\/(.*)$/i)
channels.push({ channels.push({
lang: 'pl', lang: 'pl',

View file

@ -58,7 +58,7 @@ function parseItems(content, channel) {
let data let data
try { try {
data = JSON.parse(content) data = JSON.parse(content)
} catch (error) { } catch {
return [] return []
} }

View file

@ -11,9 +11,7 @@ dayjs.extend(timezone)
dayjs.extend(utc) dayjs.extend(utc)
dayjs.extend(customParseFormat) dayjs.extend(customParseFormat)
doFetch doFetch.setCheckResult(false).setDebugger(debug)
.setCheckResult(false)
.setDebugger(debug)
const tz = 'Asia/Riyadh' const tz = 'Asia/Riyadh'
const defaultHeaders = { const defaultHeaders = {
@ -47,7 +45,7 @@ module.exports = {
headers: { headers: {
...defaultHeaders, ...defaultHeaders,
'X-Requested-With': 'XMLHttpRequest', 'X-Requested-With': 'XMLHttpRequest',
cookie: cookies[channel.lang], cookie: cookies[channel.lang]
} }
} }
queues.push({ i: item, url, params }) queues.push({ i: item, url, params })
@ -61,7 +59,7 @@ module.exports = {
}, },
async channels({ lang = 'en' }) { async channels({ lang = 'en' }) {
const result = await axios const result = await axios
.get(`https://rotana.net/api/channels`) .get('https://rotana.net/api/channels')
.then(response => response.data) .then(response => response.data)
.catch(console.error) .catch(console.error)
@ -88,34 +86,37 @@ function parseProgram(item, result) {
item.description = desc item.description = desc
} }
} }
break; break
case 'Element': case 'Element':
if (el.name === 'span') { if (el.name === 'span') {
const [k, v] = $(el).text().split(':').map(a => a.trim()) const [k, v] = $(el)
.text()
.split(':')
.map(a => a.trim())
switch (k) { switch (k) {
case 'Category': case 'Category':
case 'التصنيف': case 'التصنيف':
item.category = v; item.category = v
break; break
case 'Country': case 'Country':
case 'البلد': case 'البلد':
item.country = v; item.country = v
break; break
case 'Director': case 'Director':
case 'المخرج': case 'المخرج':
item.director = v; item.director = v
break; break
case 'Language': case 'Language':
case 'اللغة': case 'اللغة':
item.language = v; item.language = v
break; break
case 'Release Year': case 'Release Year':
case 'سنة الإصدار': case 'سنة الإصدار':
item.date = v; item.date = v
break; break
} }
} }
break; break
} }
} }
} }
@ -142,7 +143,9 @@ function parseItems(content, date) {
const heading = top.find('.iq-accordion-title .big-title') const heading = top.find('.iq-accordion-title .big-title')
if (heading.length) { if (heading.length) {
const progId = top.attr('id') const progId = top.attr('id')
const title = heading.find('span:eq(1)').text() const title = heading
.find('span:eq(1)')
.text()
.split('\n') .split('\n')
.map(a => a.trim()) .map(a => a.trim())
.join(' ') .join(' ')
@ -151,7 +154,7 @@ function parseItems(content, date) {
items.push({ items.push({
program: progId.substr(progId.indexOf('-') + 1), program: progId.substr(progId.indexOf('-') + 1),
title: title ? title.trim() : title, title: title ? title.trim() : title,
start: `${y}-${m}-${d} ${time.trim()}`, start: `${y}-${m}-${d} ${time.trim()}`
}) })
} }
} }

View file

@ -19,7 +19,7 @@ const channel = {
} }
const channelAr = Object.assign({}, channel, { lang: 'ar' }) const channelAr = Object.assign({}, channel, { lang: 'ar' })
axios.get.mockImplementation((url, opts) => { axios.get.mockImplementation(url => {
if (url === 'https://rotana.net/en/streams?channel=439&itemId=736970') { if (url === 'https://rotana.net/en/streams?channel=439&itemId=736970') {
return Promise.resolve({ return Promise.resolve({
data: fs.readFileSync(path.resolve(__dirname, '__data__/program_en.html')) data: fs.readFileSync(path.resolve(__dirname, '__data__/program_en.html'))
@ -52,11 +52,13 @@ it('can generate valid arabic url', () => {
}) })
it('can parse english response', async () => { it('can parse english response', async () => {
const result = (await parser({ const result = (
await parser({
channel, channel,
date, date,
content: fs.readFileSync(path.join(__dirname, '/__data__/content_en.html')) content: fs.readFileSync(path.join(__dirname, '/__data__/content_en.html'))
})).map(a => { })
).map(a => {
a.start = a.start.toJSON() a.start = a.start.toJSON()
a.stop = a.stop.toJSON() a.stop = a.stop.toJSON()
return a return a
@ -69,17 +71,20 @@ it('can parse english response', async () => {
title: 'Khiyana Mashroua', title: 'Khiyana Mashroua',
description: 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...', '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', image:
'https://s3.eu-central-1.amazonaws.com/rotana.website/spider_storage/1398X1000/1687084565',
category: 'Movie' category: 'Movie'
}) })
}) })
it('can parse arabic response', async () => { it('can parse arabic response', async () => {
const result = (await parser({ const result = (
await parser({
channel: channelAr, channel: channelAr,
date, date,
content: fs.readFileSync(path.join(__dirname, '/__data__/content_ar.html')) content: fs.readFileSync(path.join(__dirname, '/__data__/content_ar.html'))
})).map(a => { })
).map(a => {
a.start = a.start.toJSON() a.start = a.start.toJSON()
a.stop = a.stop.toJSON() a.stop = a.stop.toJSON()
return a return a
@ -92,7 +97,8 @@ it('can parse arabic response', async () => {
title: 'خيانة مشروعة', title: 'خيانة مشروعة',
description: description:
'يعلم هشام البحيري أن والده قد حرمه من الميراث، ووهب كل ثروته لشقيقه اﻷكبر، وهو ما يدفعه لتدبير جريمة قتل شقيقه لتبدو وكأنها دفاع عن الشرف، وذلك حين يقتل هشام زوجته مع شقيقه.', 'يعلم هشام البحيري أن والده قد حرمه من الميراث، ووهب كل ثروته لشقيقه اﻷكبر، وهو ما يدفعه لتدبير جريمة قتل شقيقه لتبدو وكأنها دفاع عن الشرف، وذلك حين يقتل هشام زوجته مع شقيقه.',
image: 'https://s3.eu-central-1.amazonaws.com/rotana.website/spider_storage/1398X1000/1687084565', image:
'https://s3.eu-central-1.amazonaws.com/rotana.website/spider_storage/1398X1000/1687084565',
category: 'فيلم' category: 'فيلم'
}) })
}) })

View file

@ -53,7 +53,7 @@ async function parseItems(buffer) {
let data let data
try { try {
data = await pdf(buffer) data = await pdf(buffer)
} catch (err) { } catch {
return [] return []
} }

View file

@ -1,5 +1,4 @@
const _ = require('lodash') const _ = require('lodash')
const axios = require('axios')
const dayjs = require('dayjs') const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc') const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone') const timezone = require('dayjs/plugin/timezone')

View file

@ -1,5 +1,5 @@
const dayjs = require('dayjs') const dayjs = require('dayjs')
const duration = require("dayjs/plugin/duration") const duration = require('dayjs/plugin/duration')
const utc = require('dayjs/plugin/utc') const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone') const timezone = require('dayjs/plugin/timezone')
const customParseFormat = require('dayjs/plugin/customParseFormat') const customParseFormat = require('dayjs/plugin/customParseFormat')
@ -50,13 +50,13 @@ module.exports = {
} }
} }
function parseImage(item) { function parseImage() {
// Should return a string if we can output an image URL // Should return a string if we can output an image URL
// Might be done with `https://s.mxtv.jp/bangumi/link/weblinkU.csv?1722421896752` ? // Might be done with `https://s.mxtv.jp/bangumi/link/weblinkU.csv?1722421896752` ?
return null return null
} }
function parseCategory(item) { function parseCategory() {
// Should return a string if we can determine the category // 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` ? // Might be done with `https://s.mxtv.jp/index_set/csv/ranking_bangumi_allU.csv` ?
return null return null
@ -68,12 +68,14 @@ function parseStart(item) {
function parseStop(item) { function parseStop(item) {
// Add the duration to the start time // Add the duration to the start time
const durationDate = dayjs(item.Duration, 'HH:mm:ss'); const durationDate = dayjs(item.Duration, 'HH:mm:ss')
return parseStart(item).add(dayjs.duration({ return parseStart(item).add(
dayjs.duration({
hours: durationDate.hour(), hours: durationDate.hour(),
minutes: durationDate.minute(), minutes: durationDate.minute(),
seconds: durationDate.second() seconds: durationDate.second()
})) })
)
} }
function parseItems(content) { function parseItems(content) {

View file

@ -11,7 +11,8 @@ const channel = {
name: 'Tokyo MX2', name: 'Tokyo MX2',
xmltv_id: 'TokyoMX2.jp' 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": ""}]` 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', () => { it('can generate valid url', () => {
const result = url({ date, channel }) const result = url({ date, channel })

View file

@ -114,7 +114,7 @@ module.exports = {
const $ = cheerio.load(data) const $ = cheerio.load(data)
$('.main-container-channels-events > .container-channel-events').each((i, el) => { $('.main-container-channels-events > .container-channel-events').each((i, el) => {
const name = $(el).find('.channel-title').text().trim() const name = $(el).find('.channel-title').text().trim()
const channelId = name.replace(/\s\&\s/gi, ' &amp; ') const channelId = name.replace(/\s&\s/gi, ' &amp; ')
if (!name) return if (!name) return

View file

@ -10,12 +10,15 @@ dayjs.extend(customParseFormat)
module.exports = { module.exports = {
site: 'shahid.mbc.net', site: 'shahid.mbc.net',
days: 2, days: 2,
url({ channel, date}) { 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}` 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 }) { parser({ content, channel }) {
const programs = parseItems(content, channel) const programs = parseItems(content, channel).map(item => {
.map(item => {
return { return {
title: item.title, title: item.title,
description: item.description, description: item.description,
@ -28,13 +31,15 @@ module.exports = {
return programs return programs
}, },
async channels({lang = 'en'}) { async channels({ lang = 'en' }) {
const axios = require('axios') const axios = require('axios')
const items = [] const items = []
let page = 0 let page = 0
while (true) { while (true) {
const result = await axios 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}`) .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) .then(response => response.data)
.catch(console.error) .catch(console.error)
if (result.productList) { if (result.productList) {
@ -44,7 +49,7 @@ module.exports = {
continue continue
} }
} }
break; break
} }
const channels = items.map(channel => { const channels = items.map(channel => {
return { return {

Some files were not shown because too many files have changed in this diff Show more