Compare commits
29 Commits
Author | SHA1 | Date | |
---|---|---|---|
ac6aed6aa4 | |||
c0905a30f0 | |||
8555fae94a | |||
a4575ccf18 | |||
abc9f19ac3 | |||
5e40ec15af | |||
aa9ddceb06 | |||
8f58693ef5 | |||
fd12d6508f | |||
b078fd94ae | |||
30fdcd055d | |||
14526d29e2 | |||
7dd9fab7e6 | |||
65c94b6585 | |||
c95f6abf4f | |||
26db08b189 | |||
57501829c1 | |||
a7e52a2952 | |||
2da2e0e4ab | |||
f55db2a2f2 | |||
9dbc13ee24 | |||
bf767c9878 | |||
12b1343bd4 | |||
0e70d67740 | |||
6e16e71ab5 | |||
f66ad104af | |||
df87aec731 | |||
6c8efd06c6 | |||
d197c843ee |
@ -11,7 +11,7 @@ jobs:
|
|||||||
working-directory: ${{ GITHUB_WORKSPACE }}
|
working-directory: ${{ GITHUB_WORKSPACE }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- name: Install Docker
|
- name: Install Docker
|
||||||
run: |
|
run: |
|
||||||
echo "Checking docker installation"
|
echo "Checking docker installation"
|
||||||
@ -23,17 +23,24 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: https://github.com/docker/setup-buildx-action@v3
|
uses: https://github.com/docker/setup-buildx-action@v3
|
||||||
- name: Docker login
|
- name: Login to Gitea container registry
|
||||||
uses: https://github.com/docker/login-action@v3
|
uses: https://github.com/docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: git.claeyscloud.com
|
registry: git.claeyscloud.com
|
||||||
username: nologin
|
username: nologin
|
||||||
password: ${{ secrets.PACKAGE_TOKEN }}
|
password: ${{ secrets.PACKAGE_TOKEN }}
|
||||||
|
- name: Login to DockerHub container registry
|
||||||
|
uses: https://github.com/docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_HUB_USERNAME}}
|
||||||
|
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||||
- name: Extract metadata (tags, labels) for Docker
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: git.claeyscloud.com/david/epg-info
|
images: |
|
||||||
|
davidquinonescl/epg-info
|
||||||
|
git.claeyscloud.com/david/epg-info
|
||||||
tags: |
|
tags: |
|
||||||
type=semver,pattern={{raw}}
|
type=semver,pattern={{raw}}
|
||||||
type=sha
|
type=sha
|
||||||
|
21
Dockerfile
21
Dockerfile
@ -4,23 +4,24 @@ ARG GIT_BRANCH=master
|
|||||||
ENV CRON_SCHEDULE="0 0,12 * * *"
|
ENV CRON_SCHEDULE="0 0,12 * * *"
|
||||||
ENV DAYS=14
|
ENV DAYS=14
|
||||||
ENV MAX_CONNECTIONS=10
|
ENV MAX_CONNECTIONS=10
|
||||||
|
ENV ENABLE_FIXES=false
|
||||||
ARG BIN_FOLDER=/bin
|
ARG BIN_FOLDER=/bin
|
||||||
ARG EPG_FOLDER=epg
|
ARG EPG_FOLDER=epg
|
||||||
|
ARG FIXES_FOLDER_ARG=fixes
|
||||||
ARG START_SCRIPT_ARG=$BIN_FOLDER/$EPG_FOLDER/start.sh
|
ARG START_SCRIPT_ARG=$BIN_FOLDER/$EPG_FOLDER/start.sh
|
||||||
ENV WORKDIR=${BIN_FOLDER}/${EPG_FOLDER}
|
ENV WORKDIR=${BIN_FOLDER}/${EPG_FOLDER}
|
||||||
|
ENV FIXES_FOLDER=$FIXES_FOLDER_ARG
|
||||||
ENV START_SCRIPT=$START_SCRIPT_ARG
|
ENV START_SCRIPT=$START_SCRIPT_ARG
|
||||||
COPY channels.xml /config/channels.xml
|
COPY channels.xml /config/channels.xml
|
||||||
|
ADD $FIXES_FOLDER /fixes
|
||||||
RUN apk update \
|
RUN apk update \
|
||||||
&& apk upgrade --available \
|
&& apk upgrade --available \
|
||||||
&& apk add curl \
|
&& apk add curl git tzdata bash \
|
||||||
&& apk add git \
|
|
||||||
&& apk add tzdata \
|
|
||||||
&& apk add bash \
|
|
||||||
&& npm install -g npm@latest \
|
&& npm install -g npm@latest \
|
||||||
&& npm install pm2 -g \
|
&& npm install pm2 -g \
|
||||||
&& mkdir $(echo "${BIN_FOLDER}/${EPG_FOLDER}") -p \
|
&& mkdir $(echo "${BIN_FOLDER}/${EPG_FOLDER}") -p \
|
||||||
&& git -C $(echo "${BIN_FOLDER}") clone --depth 1 -b $(echo "${GIT_BRANCH} ${GIT_REPO}") \
|
&& git -C $(echo "${BIN_FOLDER}") clone --depth 1 -b $(echo "${GIT_BRANCH} ${GIT_REPO}") \
|
||||||
&& cd $WORKDIR cat && npm install && npm update \
|
&& cd $WORKDIR && npm install && npm update \
|
||||||
&& rm .eslintrc.json \
|
&& rm .eslintrc.json \
|
||||||
&& rm -rf .github \
|
&& rm -rf .github \
|
||||||
&& rm -rf .git \
|
&& rm -rf .git \
|
||||||
@ -33,6 +34,13 @@ RUN apk update \
|
|||||||
&& rm sites/**/readme.md \
|
&& rm sites/**/readme.md \
|
||||||
&& rm -rf sites/**/__data__ \
|
&& rm -rf sites/**/__data__ \
|
||||||
&& rm sites/**/**.test.js \
|
&& rm sites/**/**.test.js \
|
||||||
|
&& rm -rf node_modules/**/.package-lock.json \
|
||||||
|
&& rm -rf node_modules/**/.tsconfig.json \
|
||||||
|
&& rm -rf node_modules/**/.tsconfig.tsbuildinfo.json \
|
||||||
|
&& rm -rf node_modules/**/.github \
|
||||||
|
&& rm -rf node_modules/**/docs \
|
||||||
|
&& rm -rf node_modules/**/LICENSE \
|
||||||
|
&& rm -rf node_modules/**/**.md \
|
||||||
&& ln -s /config/channels.xml $(echo "${WORKDIR}/channels.xml") \
|
&& ln -s /config/channels.xml $(echo "${WORKDIR}/channels.xml") \
|
||||||
&& mkdir /public
|
&& mkdir /public
|
||||||
COPY start.sh $WORKDIR
|
COPY start.sh $WORKDIR
|
||||||
@ -40,5 +48,6 @@ COPY serve.json $WORKDIR
|
|||||||
RUN chmod +x "$START_SCRIPT" \
|
RUN chmod +x "$START_SCRIPT" \
|
||||||
&& apk del git curl \
|
&& apk del git curl \
|
||||||
&& rm -rf /var/cache/apk/*
|
&& rm -rf /var/cache/apk/*
|
||||||
ENTRYPOINT bash $START_SCRIPT chron-schedule="$CRON_SCHEDULE" work-dir="$WORKDIR" days="$DAYS" max_connections="$MAX_CONNECTIONS"
|
SHELL ["/bin/bash", "-c"]
|
||||||
|
ENTRYPOINT bash $START_SCRIPT chron-schedule="$CRON_SCHEDULE" work-dir="$WORKDIR" days="$DAYS" max_connections="$MAX_CONNECTIONS" enable_fixes="$ENABLE_FIXES"
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
9
LICENSE
Normal file
9
LICENSE
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 David Claeys
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
49
README.md
49
README.md
@ -17,6 +17,7 @@ The `pm2` and `serve` packages are used in order to run the application in the c
|
|||||||
|
|
||||||
### Paths
|
### Paths
|
||||||
|
|
||||||
|
#### Channels file
|
||||||
An example `channels.xml` is included by default in the image.<br>
|
An example `channels.xml` is included by default in the image.<br>
|
||||||
```xml
|
```xml
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
@ -28,6 +29,14 @@ An example `channels.xml` is included by default in the image.<br>
|
|||||||
However if you want to configure your own channels you need to provide your own configuration file.<br>
|
However if you want to configure your own channels you need to provide your own configuration file.<br>
|
||||||
You can do this by creating a mapping in the `/config` folder.
|
You can do this by creating a mapping in the `/config` folder.
|
||||||
|
|
||||||
|
#### Custom fixes
|
||||||
|
|
||||||
|
Through the `ENABLE_FIXES` variable custom provider fixes can be applied to the container.
|
||||||
|
By default some fixes are available. If you have suggestions or a problem with them please submit an issue.
|
||||||
|
If for some reason you want to include your own provider fixes this is possible by creation a mapping in the `/fixes` folder.<br>
|
||||||
|
The expected structure is */fixes/`provider_name`/`provider_name`.config.js*.<br>
|
||||||
|
It is recommended that you take existing provider code as a base for your customisations.
|
||||||
|
|
||||||
### Environment Variables
|
### Environment Variables
|
||||||
|
|
||||||
| Variable | Description | Default |
|
| Variable | Description | Default |
|
||||||
@ -35,6 +44,7 @@ You can do this by creating a mapping in the `/config` folder.
|
|||||||
| CRON_SCHEDULE | CRON expression describing the recurrence for epg retrieval. | `0 0,12 * * *` |
|
| CRON_SCHEDULE | CRON expression describing the recurrence for epg retrieval. | `0 0,12 * * *` |
|
||||||
| DAYS | Describes the desired amount of days in the future for for epg retrieval. | 14 |
|
| DAYS | Describes the desired amount of days in the future for for epg retrieval. | 14 |
|
||||||
| MAX_CONNECTIONS | The maximum amount of parallel connections that can be established | 10 |
|
| MAX_CONNECTIONS | The maximum amount of parallel connections that can be established | 10 |
|
||||||
|
| ENABLE_FIXES | Some fixes to providers take a long time to be merged into the main branch.<br>When this option is enabled some of these fixes will also be included.<br>The source code for these fixes can be seen under the `fixes` folder.<br> Recreate the container when changing this variable in order for it to take effect | false |
|
||||||
|
|
||||||
### Compose file
|
### Compose file
|
||||||
|
|
||||||
@ -43,7 +53,8 @@ version: '3.3'
|
|||||||
services:
|
services:
|
||||||
epg:
|
epg:
|
||||||
image: git.claeyscloud.com/david/epg-info:latest
|
image: git.claeyscloud.com/david/epg-info:latest
|
||||||
#image: image: git.claeyscloud.com/david/epg-info:latest:latest
|
#image: ghcr.io/davidclaeysquinones/epg-info:latest
|
||||||
|
#image: davidquinonescl/epg-info:latest
|
||||||
volumes:
|
volumes:
|
||||||
# add a mapping in order to add the channels file
|
# add a mapping in order to add the channels file
|
||||||
- /docker/epg:/config
|
- /docker/epg:/config
|
||||||
@ -52,12 +63,42 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
# specify the time zone for the server
|
# specify the time zone for the server
|
||||||
- TZ=Etc/UTC
|
- TZ=Etc/UTC
|
||||||
|
# uncomment the underlying line if you want to enable custom fixes
|
||||||
|
#- ENABLE_FIXES=true
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
```
|
```
|
||||||
|
|
||||||
### Versions
|
### Versions
|
||||||
|
|
||||||
- 1.0.0
|
This image is bound to the content of the [iptv-org/epg](https://github.com/iptv-org/epg) repository. In the underlying list you can see to which commit each version of the docker image is bound.
|
||||||
|
|
||||||
|
Normally when a change is made in the source repository the documentation is updated and a new tag is created in this repository. This is completely normal since the source repository is only cloned during the build process of the docker image.
|
||||||
|
|
||||||
|
Sometimes a new version of this image will be bound to the same source commit. This will happen when improvements are made to the image.
|
||||||
|
|
||||||
|
- 1.0.0
|
||||||
[08-01-2024](https://github.com/iptv-org/epg/commit/793c74ca397504fc2afc8fbfa998e0b8e4ca45d9)
|
[08-01-2024](https://github.com/iptv-org/epg/commit/793c74ca397504fc2afc8fbfa998e0b8e4ca45d9)
|
||||||
- 1.0.1
|
- 1.0.1
|
||||||
[08-14-2024](https://github.com/iptv-org/epg/commit/270e85cfae6f0f691c2e6ab7ce511d60fd687565)
|
[08-14-2024](https://github.com/iptv-org/epg/commit/270e85cfae6f0f691c2e6ab7ce511d60fd687565)
|
||||||
|
- 1.0.2
|
||||||
|
[09-07-2024](https://github.com/iptv-org/epg/commit/4e3b06a86e225cdd1b9362a683e6770fb68ff28f)
|
||||||
|
- 1.0.3
|
||||||
|
[09-14-2024](https://github.com/iptv-org/epg/commit/c69f3c93b1123ddf0fecc62c7067fced59ae4e99)
|
||||||
|
- 1.0.4
|
||||||
|
[09-30-2024](https://github.com/iptv-org/epg/commit/d90c7a54b941238cb92391b33d80a75e746d3002)
|
||||||
|
- 1.0.5
|
||||||
|
[10-02-2024](https://github.com/iptv-org/epg/commit/713dbf60a1cb9623ffcab6ab370ee9a78b32102b)
|
||||||
|
- 1.0.6
|
||||||
|
[10-02-2024](https://github.com/iptv-org/epg/commit/713dbf60a1cb9623ffcab6ab370ee9a78b32102b)<br>Adds possibility to enable custom fixes
|
||||||
|
- 1.0.7
|
||||||
|
[10-02-2024](https://github.com/iptv-org/epg/commit/713dbf60a1cb9623ffcab6ab370ee9a78b32102b)<br>Adds improvement to the docker image size
|
||||||
|
- 1.0.8
|
||||||
|
[10-10-2024](https://github.com/iptv-org/epg/commit/2241bc261fd37b8b16e036a0b61167030a5ce2e6)
|
||||||
|
- 1.0.9
|
||||||
|
[10-12-2024](https://github.com/iptv-org/epg/commit/fd382db08da7a96150928b8dcfef115e29e661d3)
|
||||||
|
- 1.0.10
|
||||||
|
[10-14-2024 12:50](https://github.com/iptv-org/epg/commit/a3e7661f95103cbee4bcb78bd483396680e9abfc)
|
||||||
|
- 1.0.11
|
||||||
|
[10-14-2024 17:34](https://github.com/iptv-org/epg/commit/7610f7b9f5cc1ccab8d17f3408a95d31b36ace7c)
|
||||||
|
- 1.0.12
|
||||||
|
[10-14-2024 17:34](https://github.com/iptv-org/epg/commit/7610f7b9f5cc1ccab8d17f3408a95d31b36ace7c)<br>Fix Pickx.be url
|
74
fixes/movistarplus.es/movistarplus.es.config.js
Normal file
74
fixes/movistarplus.es/movistarplus.es.config.js
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
const { DateTime } = require('luxon')
|
||||||
|
|
||||||
|
const API_PROD_ENDPOINT = 'https://www.movistarplus.es/programacion-tv'
|
||||||
|
const API_IMAGE_ENDPOINT = 'https://www.movistarplus.es/recorte/n/caratulaH/';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
site: 'movistarplus.es',
|
||||||
|
days: 2,
|
||||||
|
url: function ({ date }) {
|
||||||
|
return `${API_PROD_ENDPOINT}/${date.format('YYYY-MM-DD')}?v=json`
|
||||||
|
},
|
||||||
|
parser({ content, channel, date }) {
|
||||||
|
let programs = []
|
||||||
|
let items = parseItems(content, channel)
|
||||||
|
if (!items.length) return programs
|
||||||
|
let guideDate = date
|
||||||
|
|
||||||
|
items.forEach(item => {
|
||||||
|
let startTime = DateTime.fromFormat(
|
||||||
|
`${guideDate.format('YYYY-MM-DD')} ${item.HORA_INICIO}`,
|
||||||
|
'yyyy-MM-dd HH:mm',
|
||||||
|
{
|
||||||
|
zone: 'Europe/Madrid'
|
||||||
|
}
|
||||||
|
).toUTC()
|
||||||
|
let stopTime = DateTime.fromFormat(
|
||||||
|
`${guideDate.format('YYYY-MM-DD')} ${item.HORA_FIN}`,
|
||||||
|
'yyyy-MM-dd HH:mm',
|
||||||
|
{
|
||||||
|
zone: 'Europe/Madrid'
|
||||||
|
}
|
||||||
|
).toUTC()
|
||||||
|
if (stopTime < startTime) {
|
||||||
|
guideDate = guideDate.add(1, 'd')
|
||||||
|
stopTime = stopTime.plus({ days: 1 })
|
||||||
|
}
|
||||||
|
programs.push({
|
||||||
|
title: item.TITULO,
|
||||||
|
icon: parseIcon(item, channel),
|
||||||
|
category: item.GENERO,
|
||||||
|
start: startTime,
|
||||||
|
stop: stopTime
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return programs
|
||||||
|
},
|
||||||
|
async channels() {
|
||||||
|
const axios = require('axios')
|
||||||
|
const dayjs = require('dayjs')
|
||||||
|
const data = await axios
|
||||||
|
.get(`${API_PROD_ENDPOINT}/${dayjs().format('YYYY-MM-DD')}?v=json`)
|
||||||
|
.then(r => r.data)
|
||||||
|
.catch(console.log)
|
||||||
|
|
||||||
|
return Object.values(data.data).map(item => {
|
||||||
|
return {
|
||||||
|
lang: 'es',
|
||||||
|
site_id: item.DATOS_CADENA.CODIGO,
|
||||||
|
name: item.DATOS_CADENA.NOMBRE
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseIcon(item, channel) {
|
||||||
|
return `${API_IMAGE_ENDPOINT}/M${channel.site_id}P${item.ELEMENTO}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseItems(content, channel) {
|
||||||
|
const json = typeof content === 'string' ? JSON.parse(content) : content
|
||||||
|
if (!(`${channel.site_id}-CODE` in json.data)) return []
|
||||||
|
const data = json.data[`${channel.site_id}-CODE`]
|
||||||
|
return data ? data.PROGRAMAS : []
|
||||||
|
}
|
179
fixes/pickx.be/pickx.be.config.js
Normal file
179
fixes/pickx.be/pickx.be.config.js
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
const axios = require('axios')
|
||||||
|
const dayjs = require('dayjs')
|
||||||
|
const utc = require('dayjs/plugin/utc')
|
||||||
|
|
||||||
|
let apiVersion
|
||||||
|
let isApiVersionFetched = false
|
||||||
|
|
||||||
|
;(async () => {
|
||||||
|
try {
|
||||||
|
await fetchApiVersion()
|
||||||
|
isApiVersionFetched = true
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error during script initialization:', error)
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
dayjs.extend(utc)
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
site: 'pickx.be',
|
||||||
|
days: 2,
|
||||||
|
apiVersion: function () {
|
||||||
|
return apiVersion
|
||||||
|
},
|
||||||
|
fetchApiVersion: fetchApiVersion, // Export fetchApiVersion
|
||||||
|
url: async function ({ channel, date }) {
|
||||||
|
while (!isApiVersionFetched) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100)) // Wait for 100 milliseconds
|
||||||
|
}
|
||||||
|
return `https://px-epg.azureedge.net/airings/${apiVersion}/${date.format(
|
||||||
|
'YYYY-MM-DD'
|
||||||
|
)}/channel/${channel.site_id}?timezone=Europe%2FBrussels`
|
||||||
|
},
|
||||||
|
request: {
|
||||||
|
headers: {
|
||||||
|
Origin: 'https://www.pickx.be',
|
||||||
|
Referer: 'https://www.pickx.be/'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
parser({ channel, content }) {
|
||||||
|
const programs = []
|
||||||
|
if (content) {
|
||||||
|
const items = JSON.parse(content)
|
||||||
|
items.forEach(item => {
|
||||||
|
programs.push({
|
||||||
|
title: item.program.title,
|
||||||
|
sub_title: item.program.episodeTitle,
|
||||||
|
description: item.program.description,
|
||||||
|
category: item.program.translatedCategory?.[channel.lang]
|
||||||
|
? item.program.translatedCategory[channel.lang]
|
||||||
|
: item.program.category.split('.')[1],
|
||||||
|
image: item.program.posterFileName
|
||||||
|
? `https://experience-cache.proximustv.be/posterserver/poster/EPG/w-166_h-110/${item.program.posterFileName}`
|
||||||
|
: null,
|
||||||
|
season: item.program.seasonNumber,
|
||||||
|
episode: item.program.episodeNumber,
|
||||||
|
actors: item.program.actors,
|
||||||
|
director: item.program.director ? [item.program.director] : null,
|
||||||
|
start: dayjs.utc(item.programScheduleStart),
|
||||||
|
stop: dayjs.utc(item.programScheduleEnd)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return programs
|
||||||
|
},
|
||||||
|
async channels({ lang = '' }) {
|
||||||
|
const query = {
|
||||||
|
operationName: 'getChannels',
|
||||||
|
variables: {
|
||||||
|
language: lang,
|
||||||
|
queryParams: {},
|
||||||
|
id: '0',
|
||||||
|
params: {
|
||||||
|
shouldReadFromCache: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
query: `query getChannels($language: String!, $queryParams: ChannelQueryParams, $id: String, $params: ChannelParams) {
|
||||||
|
channels(language: $language, queryParams: $queryParams, id: $id, params: $params) {
|
||||||
|
id
|
||||||
|
channelReferenceNumber
|
||||||
|
name
|
||||||
|
callLetter
|
||||||
|
number
|
||||||
|
logo {
|
||||||
|
key
|
||||||
|
url
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
language
|
||||||
|
hd
|
||||||
|
radio
|
||||||
|
replayable
|
||||||
|
ottReplayable
|
||||||
|
playable
|
||||||
|
ottPlayable
|
||||||
|
recordable
|
||||||
|
subscribed
|
||||||
|
cloudRecordable
|
||||||
|
catchUpWindowInHours
|
||||||
|
isOttNPVREnabled
|
||||||
|
ottNPVRStart
|
||||||
|
subscription {
|
||||||
|
channelRef
|
||||||
|
subscribed
|
||||||
|
upselling {
|
||||||
|
upsellable
|
||||||
|
packages
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
packages
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
const result = await axios
|
||||||
|
.post('https://api.proximusmwc.be/tiams/v2/graphql', query)
|
||||||
|
.then(r => r.data)
|
||||||
|
.catch(console.error)
|
||||||
|
|
||||||
|
return (
|
||||||
|
result?.data?.channels
|
||||||
|
.filter(
|
||||||
|
channel =>
|
||||||
|
!channel.radio && (!lang || channel.language === (lang === 'de' ? 'ger' : lang))
|
||||||
|
)
|
||||||
|
.map(channel => {
|
||||||
|
return {
|
||||||
|
lang: channel.language === 'ger' ? 'de' : channel.language,
|
||||||
|
site_id: channel.id,
|
||||||
|
name: channel.name
|
||||||
|
}
|
||||||
|
}) || []
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function fetchApiVersion() {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
// https://px-epg.azureedge.net/version is deprecated
|
||||||
|
// probably the version url will be changed around over time
|
||||||
|
|
||||||
|
//history of used version urls
|
||||||
|
//const versionUrl = 'https://www.pickx.be/api/s-3b36540f3cef64510112f3f95c2c0cdca321997ed2b1042ad778523235e155eb'
|
||||||
|
//const versionUrl = 'https://www.pickx.be/api/s-671f172425e1bc74cd0440fd67aaa6cbe68b582f3f401186c2f46ae97e80516b'
|
||||||
|
//const versionUrl = 'https://www.pickx.be/api/s-a6b4b4fefaa20e438523a6167e63b8504d96b9df8303473349763c4418cffe30'
|
||||||
|
//const versionUrl = 'https://www.pickx.be/api/s-8546c5fd136241d42aab714d2fe3ccc5671fd899035efae07cd0b8f4eb23994e'
|
||||||
|
//const versionUrl = 'https://www.pickx.be/api/s-64464ad9a3bc117af5dca620027216ecade6a51c230135a0f134c0ee042ff407';
|
||||||
|
//const versionUrl = 'https://www.pickx.be/api/s-626d8fdabfb1d44e5a614cd69f4b45d6843fdb63566fc80ea4f97f40e4ea3152';
|
||||||
|
//const versionUrl = 'https://www.pickx.be/api/s-cefaf96e249e53648c4895c279e7a621233c50b4357d62b0bdf6bff45f31b5c0';
|
||||||
|
//const versionUrl = 'https://www.pickx.be/api/s-7fa35253080e9665f9c7d9d85e707d6fb1d1bf07ede11965e859fcb57c723949';
|
||||||
|
//const versionUrl = 'https://www.pickx.be/api/s-0e58be3938175b6b900dfb5233bd5cfc0bcf915b633fe57b935f7ce8dbe5f6eb';
|
||||||
|
//the new strategy to break the provider is to leave old version url's available and to return invalid results on those endpoints
|
||||||
|
|
||||||
|
const versionUrl = 'https://www.pickx.be//api/s-600b22979b1e1e1dc91773795eed4a630dea2f9452aa1aab9a2947f4c89b901d';
|
||||||
|
|
||||||
|
|
||||||
|
const response = await axios.get(versionUrl, {
|
||||||
|
headers: {
|
||||||
|
Origin: 'https://www.pickx.be',
|
||||||
|
Referer: 'https://www.pickx.be/'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response.status === 200) {
|
||||||
|
apiVersion = response.data.version
|
||||||
|
resolve()
|
||||||
|
} else {
|
||||||
|
console.error(`Failed to fetch API version. Status: ${response.status}`)
|
||||||
|
reject(`Failed to fetch API version. Status: ${response.status}`)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching API version:', error.message)
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
138
fixes/telenet.tv/telenet.tv.config.js
Normal file
138
fixes/telenet.tv/telenet.tv.config.js
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
const axios = require('axios')
|
||||||
|
const dayjs = require('dayjs')
|
||||||
|
|
||||||
|
const API_STATIC_ENDPOINT = 'https://static.spark.telenet.tv/eng/web/epg-service-lite/be'
|
||||||
|
const API_PROD_ENDPOINT = 'https://spark-prod-be.gnp.cloud.telenet.tv/eng/web/linear-service/v2'
|
||||||
|
const API_IMAGE_ENDPOINT = 'https://staticqbr-prod-be.gnp.cloud.telenet.tv/image-service';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
site: 'telenet.tv',
|
||||||
|
days: 2,
|
||||||
|
request: {
|
||||||
|
cache: {
|
||||||
|
ttl: 60 * 60 * 1000 // 1 hour
|
||||||
|
}
|
||||||
|
},
|
||||||
|
url: function ({ date, channel }) {
|
||||||
|
return `${API_STATIC_ENDPOINT}/${channel.lang}/events/segments/${date.format('YYYYMMDDHHmmss')}`
|
||||||
|
},
|
||||||
|
async parser({ content, channel, date }) {
|
||||||
|
let programs = []
|
||||||
|
let items = parseItems(content, channel)
|
||||||
|
if (!items.length) return programs
|
||||||
|
const promises = [
|
||||||
|
axios.get(
|
||||||
|
`${API_STATIC_ENDPOINT}/${channel.lang}/events/segments/${date
|
||||||
|
.add(6, 'h')
|
||||||
|
.format('YYYYMMDDHHmmss')}`,
|
||||||
|
{
|
||||||
|
responseType: 'arraybuffer'
|
||||||
|
}
|
||||||
|
),
|
||||||
|
axios.get(
|
||||||
|
`${API_STATIC_ENDPOINT}/${channel.lang}/events/segments/${date
|
||||||
|
.add(12, 'h')
|
||||||
|
.format('YYYYMMDDHHmmss')}`,
|
||||||
|
{
|
||||||
|
responseType: 'arraybuffer'
|
||||||
|
}
|
||||||
|
),
|
||||||
|
axios.get(
|
||||||
|
`${API_STATIC_ENDPOINT}/${channel.lang}/events/segments/${date
|
||||||
|
.add(18, 'h')
|
||||||
|
.format('YYYYMMDDHHmmss')}`,
|
||||||
|
{
|
||||||
|
responseType: 'arraybuffer'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
await Promise.allSettled(promises)
|
||||||
|
.then(results => {
|
||||||
|
results.forEach(r => {
|
||||||
|
if (r.status === 'fulfilled') {
|
||||||
|
const parsed = parseItems(r.value.data, channel)
|
||||||
|
|
||||||
|
items = items.concat(parsed)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(console.error)
|
||||||
|
|
||||||
|
for (let item of items) {
|
||||||
|
const detail = await loadProgramDetails(item, channel)
|
||||||
|
programs.push({
|
||||||
|
title: item.title,
|
||||||
|
icon: parseIcon(item),
|
||||||
|
description: detail.longDescription,
|
||||||
|
category: detail.genres,
|
||||||
|
actors: detail.actors,
|
||||||
|
season: parseSeason(detail),
|
||||||
|
episode: parseEpisode(detail),
|
||||||
|
start: parseStart(item),
|
||||||
|
stop: parseStop(item)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return programs
|
||||||
|
},
|
||||||
|
async channels() {
|
||||||
|
const data = await axios
|
||||||
|
.get(`${API_PROD_ENDPOINT}/channels?cityId=28001&language=en&productClass=Orion-DASH`)
|
||||||
|
.then(r => r.data)
|
||||||
|
.catch(console.log)
|
||||||
|
|
||||||
|
return data.map(item => {
|
||||||
|
return {
|
||||||
|
lang: 'nl',
|
||||||
|
site_id: item.id,
|
||||||
|
name: item.name
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadProgramDetails(item, channel) {
|
||||||
|
if (!item.id) return {}
|
||||||
|
const url = `${API_PROD_ENDPOINT}/replayEvent/${item.id}?returnLinearContent=true&language=${channel.lang}`
|
||||||
|
const data = await axios
|
||||||
|
.get(url)
|
||||||
|
.then(r => r.data)
|
||||||
|
.catch(console.log)
|
||||||
|
|
||||||
|
return data || {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseStart(item) {
|
||||||
|
return dayjs.unix(item.startTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseStop(item) {
|
||||||
|
return dayjs.unix(item.endTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseItems(content, channel) {
|
||||||
|
if (!content) return []
|
||||||
|
const data = JSON.parse(content)
|
||||||
|
if (!data || !Array.isArray(data.entries)) return []
|
||||||
|
const channelData = data.entries.find(e => e.channelId === channel.site_id)
|
||||||
|
if (!channelData) return []
|
||||||
|
|
||||||
|
return Array.isArray(channelData.events) ? channelData.events : []
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseSeason(detail) {
|
||||||
|
if (!detail.seasonNumber) return null
|
||||||
|
if (String(detail.seasonNumber).length > 2) return null
|
||||||
|
return detail.seasonNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseEpisode(detail) {
|
||||||
|
if (!detail.episodeNumber) return null
|
||||||
|
if (String(detail.episodeNumber).length > 3) return null
|
||||||
|
return detail.episodeNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseIcon(item) {
|
||||||
|
return `${API_IMAGE_ENDPOINT}/intent/${item.id}/posterTile`;
|
||||||
|
}
|
6
start.sh
6
start.sh
@ -7,6 +7,7 @@ for arg in "$@"; do
|
|||||||
work-dir=*) work_dir="${arg#*=}" ;;
|
work-dir=*) work_dir="${arg#*=}" ;;
|
||||||
days=*) days="${arg#*=}" ;;
|
days=*) days="${arg#*=}" ;;
|
||||||
max_connections=*) max_connections="${arg#*=}" ;;
|
max_connections=*) max_connections="${arg#*=}" ;;
|
||||||
|
enable_fixes=*) enable_fixes="${arg#*=}" ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
@ -15,6 +16,11 @@ cd $work_dir
|
|||||||
echo "working dir : " $(pwd)
|
echo "working dir : " $(pwd)
|
||||||
echo "days : ${days}"
|
echo "days : ${days}"
|
||||||
echo "max_connections : ${max_connections}"
|
echo "max_connections : ${max_connections}"
|
||||||
|
echo "enable_fixes : ${enable_fixes}"
|
||||||
|
|
||||||
|
if [ "$enable_fixes" = true ] ; then
|
||||||
|
cp -R /fixes/* /bin/epg/sites/
|
||||||
|
fi
|
||||||
|
|
||||||
pm2 --name epg start npm -- run serve
|
pm2 --name epg start npm -- run serve
|
||||||
npm run grab -- --channels=channels.xml --maxConnections=$max_connections --days=$days --gzip
|
npm run grab -- --channels=channels.xml --maxConnections=$max_connections --days=$days --gzip
|
||||||
|
Reference in New Issue
Block a user