Compare commits

...

34 Commits
1.2.0 ... 1.4.0

Author SHA1 Message Date
0690e1551b ci: add new argument release to release workflow 2025-04-10 04:34:04 -07:00
d1a7460c05 chore: update icon colors 2025-04-10 03:45:00 -07:00
70e349d7e3 chore: update health animation 2025-04-10 03:39:28 -07:00
568c3fc219 chore: add health check template var healthTimer 2025-04-10 03:00:04 -07:00
f55ecae8f3 feat: add heartbeat animation to header health icon 2025-04-10 02:56:39 -07:00
e4436ad7b7 feat: add healthcheck countdown as tooltip to header icon 2025-04-10 02:25:58 -07:00
67d7019a93 refactor: restyle console logs, add new api endpoints 2025-04-10 02:25:32 -07:00
4b45c0a2a2 feat: add release category to template 2025-04-10 02:22:38 -07:00
b3aae7b837 feat: add bootstrap tooltips 2025-04-10 02:19:48 -07:00
122286bd7b build: bump to v1.4.0 2025-04-10 02:17:58 -07:00
6708bb17a3 chore: set resync icon to continue animation until page refreshes 2025-04-09 09:12:23 -07:00
5fa7cd9d85 feat: new health api check every 10 minutes with toast notification to user 2025-04-09 09:00:33 -07:00
25ac27dd64 feat: add toast notifications 2025-04-09 09:00:12 -07:00
a659e03512 build: add new env variable HEALTH_TIMER
allows you to specify how often a health check is done
2025-04-09 08:59:38 -07:00
4d081adda2 docs(readme): update env definition 2025-04-08 18:08:41 -07:00
279d48d8ee feat: add api/health, json responses with info about each message thrown 2025-04-08 15:30:33 -07:00
c017578631 feat: add healthcheck api endpoint 2025-04-08 13:12:59 -07:00
f4baade73b build: create two new system env variables on app startup 2025-04-08 11:37:58 -07:00
f68053b461 build: add package express for webserver replacement 2025-04-08 10:00:16 -07:00
ec18ceb6db feat: add ability to restart / resync m3u playlist and epg guide data 2025-04-08 09:52:30 -07:00
68c4778ed8 refactor: rename vars with tar to gzp 2025-04-08 07:47:24 -07:00
cf28156e70 feat: new restart api route returns json 2025-04-08 07:46:32 -07:00
149fe89f89 build: add bootstrap v5.x 2025-04-08 07:41:50 -07:00
c124d93285 feat: add re-sync functionality / button to header 2025-04-08 03:21:46 -07:00
06e5d42c9c chore: update jellyfin accept-encoding comment block 2025-04-08 01:52:35 -07:00
c42b60a58c chore: add comment to new jellyfin encoding fix 2025-04-08 01:50:59 -07:00
1e8bdcddd8 fix: add header-encoding env variable to fix gzip compression for jellyfin xml grab
If gzip compression is enabled for the tvapp2 webserver, this may cause Jellyfin to fail when fetching newest xml guide data.

to correct this, we create an env variable which allows us to disable gzip compression for `Accept-Encoding`
2025-04-08 01:44:17 -07:00
90c2295bb8 docs(readme): update build version for commands 2025-04-08 00:57:41 -07:00
cca7b48d3b docs(readme): update build commands 2025-04-08 00:52:24 -07:00
2d9dec2d74 build: update package files 2025-04-08 00:20:51 -07:00
30741f124e refactor: update error logs 2025-04-08 00:17:01 -07:00
f256a9db06 Merge remote-tracking branch 'origin/main' 2025-04-07 23:01:27 -07:00
baf850308f fix: source 1 and 2 broken for streams 2025-04-07 23:01:12 -07:00
renovate[bot]
d9174d2a20 chore(deps): lock file maintenance (#44)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-07 07:27:00 +00:00
15 changed files with 2447 additions and 377 deletions

View File

@@ -452,6 +452,7 @@ jobs:
sbom: false sbom: false
build-args: |- build-args: |-
ARCH=amd64 ARCH=amd64
RELEASE=${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}
VERSION=${{ env.IMAGE_VERSION }} VERSION=${{ env.IMAGE_VERSION }}
BUILDDATE=${{ env.NOW_DOCKER_LABEL }} BUILDDATE=${{ env.NOW_DOCKER_LABEL }}
@@ -503,6 +504,7 @@ jobs:
sbom: false sbom: false
build-args: |- build-args: |-
ARCH=arm64 ARCH=arm64
RELEASE=${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}
VERSION=${{ env.IMAGE_VERSION }} VERSION=${{ env.IMAGE_VERSION }}
BUILDDATE=${{ env.NOW_DOCKER_LABEL }} BUILDDATE=${{ env.NOW_DOCKER_LABEL }}

View File

@@ -576,6 +576,7 @@ jobs:
sbom: false sbom: false
build-args: |- build-args: |-
ARCH=amd64 ARCH=amd64
RELEASE=${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}
VERSION=${{ env.IMAGE_VERSION }} VERSION=${{ env.IMAGE_VERSION }}
BUILDDATE=${{ env.NOW_DOCKER_LABEL }} BUILDDATE=${{ env.NOW_DOCKER_LABEL }}
@@ -627,6 +628,7 @@ jobs:
sbom: false sbom: false
build-args: |- build-args: |-
ARCH=arm64 ARCH=arm64
RELEASE=${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}
VERSION=${{ env.IMAGE_VERSION }} VERSION=${{ env.IMAGE_VERSION }}
BUILDDATE=${{ env.NOW_DOCKER_LABEL }} BUILDDATE=${{ env.NOW_DOCKER_LABEL }}

View File

@@ -455,6 +455,7 @@ jobs:
sbom: false sbom: false
build-args: |- build-args: |-
ARCH=amd64 ARCH=amd64
RELEASE=${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}
VERSION=${{ env.IMAGE_VERSION }} VERSION=${{ env.IMAGE_VERSION }}
BUILDDATE=${{ env.NOW_DOCKER_LABEL }} BUILDDATE=${{ env.NOW_DOCKER_LABEL }}
@@ -511,6 +512,7 @@ jobs:
sbom: false sbom: false
build-args: |- build-args: |-
ARCH=arm64 ARCH=arm64
RELEASE=${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}
VERSION=${{ env.IMAGE_VERSION }} VERSION=${{ env.IMAGE_VERSION }}
BUILDDATE=${{ env.NOW_DOCKER_LABEL }} BUILDDATE=${{ env.NOW_DOCKER_LABEL }}

View File

@@ -37,6 +37,7 @@ FROM --platform=linux/${ARCH} ghcr.io/aetherinox/alpine-base:3.21
ARG ARCH=amd64 ARG ARCH=amd64
ARG BUILDDATE ARG BUILDDATE
ARG VERSION ARG VERSION
ARG RELEASE
# # # #
# Set Labels # Set Labels
@@ -56,6 +57,7 @@ LABEL org.opencontainers.image.licenses="MIT"
LABEL org.opencontainers.image.architecture="${ARCH}" LABEL org.opencontainers.image.architecture="${ARCH}"
LABEL org.opencontainers.image.ref.name="main" LABEL org.opencontainers.image.ref.name="main"
LABEL org.opencontainers.image.registry="local" LABEL org.opencontainers.image.registry="local"
LABEL org.opencontainers.image.release="${RELEASE}"
LABEL org.tvapp2.image.maintainers="Aetherinox, iFlip721, Optx" LABEL org.tvapp2.image.maintainers="Aetherinox, iFlip721, Optx"
LABEL org.tvapp2.image.build-version="Version:- ${VERSION} Date:- ${BUILDDATE}" LABEL org.tvapp2.image.build-version="Version:- ${VERSION} Date:- ${BUILDDATE}"
@@ -65,16 +67,19 @@ LABEL org.tvapp2.image.build-version="Version:- ${VERSION} Date:- ${BUILDDATE}"
ENV NODE_VERSION=22.8.0 ENV NODE_VERSION=22.8.0
ENV YARN_VERSION=1.22.22 ENV YARN_VERSION=1.22.22
ENV RELEASE="${RELEASE}"
ENV DIR_BUILD=/usr/src/app ENV DIR_BUILD=/usr/src/app
ENV DIR_RUN=/usr/bin/app ENV DIR_RUN=/usr/bin/app
ENV URL_REPO="https://git.binaryninja.net/binaryninja/" ENV URL_REPO="https://git.binaryninja.net/binaryninja/"
ENV WEB_IP="0.0.0.0" ENV WEB_IP="0.0.0.0"
ENV WEB_PORT=4124 ENV WEB_PORT=4124
ENV WEB_ENCODING="deflate, br"
ENV STREAM_QUALITY="hd" ENV STREAM_QUALITY="hd"
ENV FILE_URL="urls.txt" ENV FILE_URL="urls.txt"
ENV FILE_M3U="playlist.m3u8" ENV FILE_M3U="playlist.m3u8"
ENV FILE_EPG="xmltv.xml" ENV FILE_EPG="xmltv.xml"
ENV FILE_TAR="xmltv.xml.gz" ENV FILE_TAR="xmltv.xml.gz"
ENV HEALTH_TIMER=600000
ENV LOG_LEVEL=4 ENV LOG_LEVEL=4
ENV TZ="Etc/UTC" ENV TZ="Etc/UTC"

183
README.md
View File

@@ -69,6 +69,7 @@
- [Docker Compose](#docker-compose-1) - [Docker Compose](#docker-compose-1)
- [Environment Variables](#environment-variables-1) - [Environment Variables](#environment-variables-1)
- [Mountable Volumes](#mountable-volumes-1) - [Mountable Volumes](#mountable-volumes-1)
- [Docker health check](#docker-health-check)
- [Traefik Integration](#traefik-integration) - [Traefik Integration](#traefik-integration)
- [Labels](#labels) - [Labels](#labels)
- [Dynamic.yml](#dynamicyml) - [Dynamic.yml](#dynamicyml)
@@ -84,6 +85,7 @@
- [Run Error: `Error serving playlist: ENOENT: no such file or directory, open /usr/src/app/xmltv.1.xml`](#run-error-error-serving-playlist-enoent-no-such-file-or-directory-open-usrsrcappxmltv1xml) - [Run Error: `Error serving playlist: ENOENT: no such file or directory, open /usr/src/app/xmltv.1.xml`](#run-error-error-serving-playlist-enoent-no-such-file-or-directory-open-usrsrcappxmltv1xml)
- [Build Error: `s6-rc-compile: fatal: invalid /etc/s6-overlay/s6-rc.d/certsync/type: must be oneshot, longrun, or bundle`](#build-error-s6-rc-compile-fatal-invalid-etcs6-overlays6-rcdcertsynctype-must-be-oneshot-longrun-or-bundle) - [Build Error: `s6-rc-compile: fatal: invalid /etc/s6-overlay/s6-rc.d/certsync/type: must be oneshot, longrun, or bundle`](#build-error-s6-rc-compile-fatal-invalid-etcs6-overlays6-rcdcertsynctype-must-be-oneshot-longrun-or-bundle)
- [Build Error: `unable to exec /etc/s6-overlay/s6-rc.d/init-envfile/run: Permission denied`](#build-error-unable-to-exec-etcs6-overlays6-rcdinit-envfilerun-permission-denied) - [Build Error: `unable to exec /etc/s6-overlay/s6-rc.d/init-envfile/run: Permission denied`](#build-error-unable-to-exec-etcs6-overlays6-rcdinit-envfilerun-permission-denied)
- [Build Error: `[ERR] [27] Jellyfin.LiveTv.Guide.GuideManager: Error getting programs for channel XXXXXXXXXXXXXXX (Source 2) System.Xml.XmlException: '', hexadecimal value 0x1F, is an invalid character. Line 1, position 1.`](#build-error-err-27-jellyfinlivetvguideguidemanager-error-getting-programs-for-channel-xxxxxxxxxxxxxxx-source-2-systemxmlxmlexception--hexadecimal-value-0x1f-is-an-invalid-character-line-1-position-1)
- [Extra Notes](#extra-notes) - [Extra Notes](#extra-notes)
- [Accessing Container Shell](#accessing-container-shell) - [Accessing Container Shell](#accessing-container-shell)
- [ash](#ash) - [ash](#ash)
@@ -112,6 +114,7 @@
This project contains several repositories which all share the same code; use them as backups: This project contains several repositories which all share the same code; use them as backups:
- [🔀 dockerhub:thebinaryninja/tvapp2](https://hub.docker.com/r/thebinaryninja/tvapp2)
- [🔀 github:thebinaryninja/tvapp2](https://github.com/thebinaryninja/tvapp2) - [🔀 github:thebinaryninja/tvapp2](https://github.com/thebinaryninja/tvapp2)
- [🔀 gitea:git.binaryninja.net/binaryninja/tvapp2](https://git.binaryninja.net/binaryninja/tvapp2) - [🔀 gitea:git.binaryninja.net/binaryninja/tvapp2](https://git.binaryninja.net/binaryninja/tvapp2)
@@ -152,11 +155,12 @@ For the [environment variables](#environment-variables), you may specify these i
| `TZ` | `Etc/UTC` | Timezone for error / log reporting | | `TZ` | `Etc/UTC` | Timezone for error / log reporting |
| `WEB_IP` | `0.0.0.0` | IP to use for webserver | | `WEB_IP` | `0.0.0.0` | IP to use for webserver |
| `WEB_PORT` | `4124` | Port to use for webserver | | `WEB_PORT` | `4124` | Port to use for webserver |
| `WEB_ENCODING` | `deflate, br` | Defines the HTTP `Accept-Encoding` request and response header. This value specifies what content encoding the sender can understand<br /><br />Gzip compression can be enabled by specifying `'gzip, deflate, br'` |
| `URL_REPO` | `https://git.binaryninja.net/BinaryNinja/` | Determines where the data files will be downloaded from. Do not change this or you will be unable to get M3U and EPG data. | | `URL_REPO` | `https://git.binaryninja.net/BinaryNinja/` | Determines where the data files will be downloaded from. Do not change this or you will be unable to get M3U and EPG data. |
| `FILE_URL` | `urls.txt` | Filename for url cache file | | `FILE_URL` | `urls.txt` | Filename for `urls.txt` cache file |
| `FILE_M3U` | `playlist.m3u8` | Filename for M3U playlist file | | `FILE_M3U` | `playlist.m3u8` | Filename for M3U playlist file |
| `FILE_EPG` | `xmltv.xml` | Filename for XML guide data file | | `FILE_EPG` | `xmltv.xml` | Filename for XML guide data file |
| `FILE_GZIP` | `xmltv.xml.gz` | Filename for XML compressed as gzip .gz | | `FILE_GZP` | `xmltv.xml.gz` | Filename for XML compressed as gzip .gz |
| `STREAM_QUALITY` | `hd` | Stream quality<br />Can be either `hd` or `sd` | | `STREAM_QUALITY` | `hd` | Stream quality<br />Can be either `hd` or `sd` |
| `DIR_BUILD` | `/usr/src/app` | Path inside container where TVApp2 will be built. <br /><br /> <sup>⚠️ This should not be used unless you know what you're doing</sup> | | `DIR_BUILD` | `/usr/src/app` | Path inside container where TVApp2 will be built. <br /><br /> <sup>⚠️ This should not be used unless you know what you're doing</sup> |
| `DIR_RUN` | `/usr/bin/app` | Path inside container where TVApp2 will be placed after it is built <br /><br /> <sup>⚠️ This should not be used unless you know what you're doing</sup> | | `DIR_RUN` | `/usr/bin/app` | Path inside container where TVApp2 will be placed after it is built <br /><br /> <sup>⚠️ This should not be used unless you know what you're doing</sup> |
@@ -206,7 +210,7 @@ docker run -d --restart=unless-stopped \
If you want to use a `📄 docker-compose.yml` to bring TVApp2 up; you may use the following example: If you want to use a `📄 docker-compose.yml` to bring TVApp2 up; you may use the following example:
```yml ignore ```yml
services: services:
tvapp2: tvapp2:
container_name: tvapp2 container_name: tvapp2
@@ -356,7 +360,7 @@ This repository offers two types of docker image; `stable` and `development`. Yo
| Build | Tags | | Build | Tags |
| ------------------------- | ----------------------------------------------------------------------------- | | ------------------------- | ----------------------------------------------------------------------------- |
| `Stable` | `🔖 tvapp2:latest` <br /> `🔖 tvapp2:1.1.0` <br /> `🔖 tvapp2:1.1` <br /> `🔖 tvapp2:1` | | `Stable` | `🔖 tvapp2:latest` <br /> `🔖 tvapp2:1.2.0` <br /> `🔖 tvapp2:1.2` <br /> `🔖 tvapp2:1` |
| `Development` | `🔖 tvapp2:development` | | `Development` | `🔖 tvapp2:development` |
<br /> <br />
@@ -563,11 +567,11 @@ Creates the TVApp2 `amd64` docker image:
# Build tvapp2 amd64 # Build tvapp2 amd64
docker buildx build \ docker buildx build \
--build-arg ARCH=amd64 \ --build-arg ARCH=amd64 \
--build-arg VERSION=1.1.0 \ --build-arg VERSION=1.2.0 \
--build-arg BUILDDATE=20250325 \ --build-arg BUILDDATE=20250407 \
--tag ghcr.io/thebinaryninja/tvapp2:1.1.0 \ --tag ghcr.io/thebinaryninja/tvapp2:1.2.0 \
--tag ghcr.io/thebinaryninja/tvapp2:1.1 \ --tag ghcr.io/thebinaryninja/tvapp2:1.2 \
--tag ghcr.io/thebinaryninja/tvapp2:1 \ --tag ghcr.io/thebinaryninja/tvapp2:2 \
--tag ghcr.io/thebinaryninja/tvapp2:latest \ --tag ghcr.io/thebinaryninja/tvapp2:latest \
--attest type=provenance,disabled=true \ --attest type=provenance,disabled=true \
--attest type=sbom,disabled=true \ --attest type=sbom,disabled=true \
@@ -591,10 +595,10 @@ Creates the TVApp2 `arm64` docker image:
# Build tvapp2 arm64 # Build tvapp2 arm64
docker buildx build \ docker buildx build \
--build-arg ARCH=arm64 \ --build-arg ARCH=arm64 \
--build-arg VERSION=1.1.0 \ --build-arg VERSION=1.2.0 \
--build-arg BUILDDATE=20250325 \ --build-arg BUILDDATE=20250407 \
--tag ghcr.io/thebinaryninja/tvapp2:1.1.0 \ --tag ghcr.io/thebinaryninja/tvapp2:1.2.0 \
--tag ghcr.io/thebinaryninja/tvapp2:1.1 \ --tag ghcr.io/thebinaryninja/tvapp2:1.2 \
--tag ghcr.io/thebinaryninja/tvapp2:1 \ --tag ghcr.io/thebinaryninja/tvapp2:1 \
--tag ghcr.io/thebinaryninja/tvapp2:latest \ --tag ghcr.io/thebinaryninja/tvapp2:latest \
--attest type=provenance,disabled=true \ --attest type=provenance,disabled=true \
@@ -723,8 +727,8 @@ docker run --privileged --rm tonistiigi/binfmt --install all
Once the emulator is installed; we will now build two images. When building these two images; we will ensure the `--tag` value is different for each one, by adding the architecture to the end. This ensures we don't overwrite one image with the newer one. We need to have two seperate docker images with two different tags. Once the emulator is installed; we will now build two images. When building these two images; we will ensure the `--tag` value is different for each one, by adding the architecture to the end. This ensures we don't overwrite one image with the newer one. We need to have two seperate docker images with two different tags.
- `--tag ghcr.io/thebinaryninja/tvapp2:1.1.0-amd64` - `--tag ghcr.io/thebinaryninja/tvapp2:1.2.0-amd64`
- `--tag ghcr.io/thebinaryninja/tvapp2:1.1.0-arm64` - `--tag ghcr.io/thebinaryninja/tvapp2:1.2.0-arm64`
<br /> <br />
@@ -735,10 +739,10 @@ Once the emulator is installed; we will now build two images. When building thes
> >
> | Registry | Tag | > | Registry | Tag |
> | --- | --- | > | --- | --- |
> | Dockerhub | `--tag thebinaryninja/tvapp2:1.1.0-amd64`<br>`--tag thebinaryninja/tvapp2:1.1.0-arm64` | > | Dockerhub | `--tag thebinaryninja/tvapp2:1.2.0-amd64`<br>`--tag thebinaryninja/tvapp2:1.2.0-arm64` |
> | Github (GHCR) | `--tag ghcr.io/thebinaryninja/tvapp2:1.1.0-amd64`<br>`--tag ghcr.io/thebinaryninja/tvapp2:1.1.0-arm64` | > | Github (GHCR) | `--tag ghcr.io/thebinaryninja/tvapp2:1.2.0-amd64`<br>`--tag ghcr.io/thebinaryninja/tvapp2:1.2.0-arm64` |
> | Registry v2 | `--tag registry.domain.lan/thebinaryninja/tvapp2:1.1.0-amd64`<br>`--tag registry.domain.lan/thebinaryninja/tvapp2:1.1.0-arm64` | > | Registry v2 | `--tag registry.domain.lan/thebinaryninja/tvapp2:1.2.0-amd64`<br>`--tag registry.domain.lan/thebinaryninja/tvapp2:1.2.0-arm64` |
> | Gitea | `--tag git.binaryninja.net/binaryninja/tvapp2:1.1.0-amd64`<br>`--tag git.binaryninja.net/binaryninja/tvapp2:1.1.0-arm64` | > | Gitea | `--tag git.binaryninja.net/binaryninja/tvapp2:1.2.0-amd64`<br>`--tag git.binaryninja.net/binaryninja/tvapp2:1.2.0-arm64` |
<br /> <br />
@@ -759,9 +763,9 @@ Creates the TVApp2 **Stable** release `amd64` docker image:
# Build Tvapp2 amd64 - (stable release) # Build Tvapp2 amd64 - (stable release)
docker buildx build \ docker buildx build \
--build-arg ARCH=amd64 \ --build-arg ARCH=amd64 \
--build-arg VERSION=1.1.0 \ --build-arg VERSION=1.2.0 \
--build-arg BUILDDATE=20250325 \ --build-arg BUILDDATE=20250407 \
--tag ghcr.io/thebinaryninja/tvapp2:1.1.0-amd64 \ --tag ghcr.io/thebinaryninja/tvapp2:1.2.0-amd64 \
--attest type=provenance,disabled=true \ --attest type=provenance,disabled=true \
--attest type=sbom,disabled=true \ --attest type=sbom,disabled=true \
--file Dockerfile \ --file Dockerfile \
@@ -785,9 +789,9 @@ Creates the TVApp2 **Stable** release `arm64` docker image:
# Build Tvapp2 arm64 - (stable release) # Build Tvapp2 arm64 - (stable release)
docker buildx build \ docker buildx build \
--build-arg ARCH=arm64 \ --build-arg ARCH=arm64 \
--build-arg VERSION=1.1.0 \ --build-arg VERSION=1.2.0 \
--build-arg BUILDDATE=20250325 \ --build-arg BUILDDATE=20250407 \
--tag ghcr.io/thebinaryninja/tvapp2:1.1.0-arm64 \ --tag ghcr.io/thebinaryninja/tvapp2:1.2.0-arm64 \
--attest type=provenance,disabled=true \ --attest type=provenance,disabled=true \
--attest type=sbom,disabled=true \ --attest type=sbom,disabled=true \
--file Dockerfile \ --file Dockerfile \
@@ -811,8 +815,8 @@ Creates the TVApp2 **Development** release `amd64` docker image:
# Build Tvapp2 amd64 - (development release) # Build Tvapp2 amd64 - (development release)
docker buildx build \ docker buildx build \
--build-arg ARCH=amd64 \ --build-arg ARCH=amd64 \
--build-arg VERSION=1.1.0 \ --build-arg VERSION=1.2.0 \
--build-arg BUILDDATE=20250325 \ --build-arg BUILDDATE=20250407 \
--tag ghcr.io/thebinaryninja/tvapp2:development-amd64 \ --tag ghcr.io/thebinaryninja/tvapp2:development-amd64 \
--attest type=provenance,disabled=true \ --attest type=provenance,disabled=true \
--attest type=sbom,disabled=true \ --attest type=sbom,disabled=true \
@@ -837,8 +841,8 @@ Creates the TVApp2 **Development** release `arm64` docker image:
# Build Tvapp2 arm64 - (development release) # Build Tvapp2 arm64 - (development release)
docker buildx build \ docker buildx build \
--build-arg ARCH=arm64 \ --build-arg ARCH=arm64 \
--build-arg VERSION=1.1.0 \ --build-arg VERSION=1.2.0 \
--build-arg BUILDDATE=20250325 \ --build-arg BUILDDATE=20250407 \
--tag ghcr.io/thebinaryninja/tvapp2:development-arm64 \ --tag ghcr.io/thebinaryninja/tvapp2:development-arm64 \
--attest type=provenance,disabled=true \ --attest type=provenance,disabled=true \
--attest type=sbom,disabled=true \ --attest type=sbom,disabled=true \
@@ -857,8 +861,8 @@ docker buildx build \
After completing the `docker buildx` commands above; you should now have a few new images. Each image should have its own separate docker tags which do not conflict. If you decided to not build the **development** releases below; that is fine. After completing the `docker buildx` commands above; you should now have a few new images. Each image should have its own separate docker tags which do not conflict. If you decided to not build the **development** releases below; that is fine.
- `--tag ghcr.io/thebinaryninja/tvapp2:1.1.0-amd64` - `--tag ghcr.io/thebinaryninja/tvapp2:1.2.0-amd64`
- `--tag ghcr.io/thebinaryninja/tvapp2:1.1.0-arm64` - `--tag ghcr.io/thebinaryninja/tvapp2:1.2.0-arm64`
- `--tag ghcr.io/thebinaryninja/tvapp2:development-amd64` - `--tag ghcr.io/thebinaryninja/tvapp2:development-amd64`
- `--tag ghcr.io/thebinaryninja/tvapp2:development-arm64` - `--tag ghcr.io/thebinaryninja/tvapp2:development-arm64`
@@ -884,15 +888,15 @@ You can also get the hash digests by running the commands:
<br /> <br />
```shell ```shell
$ docker buildx imagetools inspect ghcr.io/thebinaryninja/tvapp2:1.1.0-amd64 $ docker buildx imagetools inspect ghcr.io/thebinaryninja/tvapp2:1.2.0-amd64
Name: ghcr.io/thebinaryninja/tvapp2:1.1.0-amd64 Name: ghcr.io/thebinaryninja/tvapp2:1.2.0-amd64
MediaType: application/vnd.docker.distribution.manifest.v2+json MediaType: application/vnd.docker.distribution.manifest.v2+json
Digest: sha256:0abe1b1c119959b3b1ccc23c56a7ee2c4c908c6aaef290d4ab2993859d807a3b Digest: sha256:0abe1b1c119959b3b1ccc23c56a7ee2c4c908c6aaef290d4ab2993859d807a3b
$ docker buildx imagetools inspect ghcr.io/thebinaryninja/tvapp2:1.1.0-arm64 $ docker buildx imagetools inspect ghcr.io/thebinaryninja/tvapp2:1.2.0-arm64
Name: ghcr.io/thebinaryninja/tvapp2:1.1.0-arm64 Name: ghcr.io/thebinaryninja/tvapp2:1.2.0-arm64
MediaType: application/vnd.docker.distribution.manifest.v2+json MediaType: application/vnd.docker.distribution.manifest.v2+json
Digest: sha256:e68b9de8669eac64d4e4d2a8343c56705e05e9a907cf0b542343f9b536d9c473 Digest: sha256:e68b9de8669eac64d4e4d2a8343c56705e05e9a907cf0b542343f9b536d9c473
``` ```
@@ -938,14 +942,14 @@ Digest: sha256:c719ccb034946e3f0625003f25026d001768794e38a1ba8aafc9146291d548
> ```shell > ```shell
> $ docker images --all --no-trunc | grep thebinaryninja > $ docker images --all --no-trunc | grep thebinaryninja
> >
> ghcr.io/thebinaryninja/tvapp2 1.1.0-arm64 sha256:48520ca15fed6483d2d5b79993126c311f833002345b0e12b8eceb5bf9def966 42 minutes ago 46MB > ghcr.io/thebinaryninja/tvapp2 1.2.0-arm64 sha256:48520ca15fed6483d2d5b79993126c311f833002345b0e12b8eceb5bf9def966 42 minutes ago 46MB
> >
> ghcr.io/thebinaryninja/tvapp2 1.1.0-amd64 sha256:54a9b7d390199532d5667fae67120d77e2f459bd6108b27ce94e0cfec8f3c41f 43 minutes ago 45MB > ghcr.io/thebinaryninja/tvapp2 1.2.0-amd64 sha256:54a9b7d390199532d5667fae67120d77e2f459bd6108b27ce94e0cfec8f3c41f 43 minutes ago 45MB
> ``` > ```
> >
> To get the correct sha256 digest, use: > To get the correct sha256 digest, use:
> - `docker buildx imagetools inspect ghcr.io/thebinaryninja/tvapp2:1.1.0-amd64` > - `docker buildx imagetools inspect ghcr.io/thebinaryninja/tvapp2:1.2.0-amd64`
> - `docker buildx imagetools inspect ghcr.io/thebinaryninja/tvapp2:1.1.0-arm64` > - `docker buildx imagetools inspect ghcr.io/thebinaryninja/tvapp2:1.2.0-arm64`
> - `docker buildx imagetools inspect ghcr.io/thebinaryninja/tvapp2:development-amd64` > - `docker buildx imagetools inspect ghcr.io/thebinaryninja/tvapp2:development-amd64`
> - `docker buildx imagetools inspect ghcr.io/thebinaryninja/tvapp2:development-arm64` > - `docker buildx imagetools inspect ghcr.io/thebinaryninja/tvapp2:development-arm64`
> >
@@ -965,8 +969,8 @@ For the **stable** releases, use:
# # # #
docker buildx imagetools create \ docker buildx imagetools create \
--tag ghcr.io/thebinaryninja/tvapp2:1.1.0 \ --tag ghcr.io/thebinaryninja/tvapp2:1.2.0 \
--tag ghcr.io/thebinaryninja/tvapp2:1.1 \ --tag ghcr.io/thebinaryninja/tvapp2:1.2 \
--tag ghcr.io/thebinaryninja/tvapp2:1 \ --tag ghcr.io/thebinaryninja/tvapp2:1 \
--tag ghcr.io/thebinaryninja/tvapp2:latest \ --tag ghcr.io/thebinaryninja/tvapp2:latest \
sha256:0abe1b1c119959b3b1ccc23c56a7ee2c4c908c6aaef290d4ab2993859d807a3b \ sha256:0abe1b1c119959b3b1ccc23c56a7ee2c4c908c6aaef290d4ab2993859d807a3b \
@@ -974,9 +978,9 @@ docker buildx imagetools create \
[+] Building 0.2s (4/4) FINISHED [+] Building 0.2s (4/4) FINISHED
=> [internal] pushing ghcr.io/thebinaryninja/tvapp2:latest 0.2s => [internal] pushing ghcr.io/thebinaryninja/tvapp2:latest 0.2s
=> [internal] pushing ghcr.io/thebinaryninja/tvapp2:1.1 0.2s => [internal] pushing ghcr.io/thebinaryninja/tvapp2:1.2 0.2s
=> [internal] pushing ghcr.io/thebinaryninja/tvapp2:1 0.2s => [internal] pushing ghcr.io/thebinaryninja/tvapp2:1 0.2s
=> [internal] pushing ghcr.io/thebinaryninja/tvapp2:1.1.0 0.2s => [internal] pushing ghcr.io/thebinaryninja/tvapp2:1.2.0 0.2s
``` ```
<br /> <br />
@@ -1022,8 +1026,8 @@ In this example, we take the existing two files we created earlier, and merge th
```shell ```shell
# Example 1 (using tag) # Example 1 (using tag)
docker manifest create ghcr.io/thebinaryninja/tvapp2:latest \ docker manifest create ghcr.io/thebinaryninja/tvapp2:latest \
--amend ghcr.io/thebinaryninja/tvapp2:1.1.0-amd64 \ --amend ghcr.io/thebinaryninja/tvapp2:1.2.0-amd64 \
--amend ghcr.io/thebinaryninja/tvapp2:1.1.0-arm64 --amend ghcr.io/thebinaryninja/tvapp2:1.2.0-arm64
# Example 2 (using sha256 hash) # Example 2 (using sha256 hash)
docker manifest create ghcr.io/thebinaryninja/tvapp2:latest \ docker manifest create ghcr.io/thebinaryninja/tvapp2:latest \
@@ -1064,7 +1068,7 @@ To build the project, `🗔 cd` into the project folder and run the build comman
```shell ```shell
cd /home/docker/tvapp2/ cd /home/docker/tvapp2/
npm run docker:build:amd64 --VERSION=1.1.0 --BUILDDATE=20250325 npm run docker:build:amd64 --VERSION=1.2.0 --BUILDDATE=20250407
``` ```
<br /> <br />
@@ -1089,7 +1093,7 @@ The run command above has several variables you must specify:
| Variable | Description | | Variable | Description |
| --- | --- | | --- | --- |
| `--VERSION=1.X.X` | The version to assign to the docker image | | `--VERSION=1.X.X` | The version to assign to the docker image |
| `--BUILDDATE=20250325` | The date to assign to the docker image. <br /> Date format: `YYYYMMDD` | | `--BUILDDATE=20250407` | The date to assign to the docker image. <br /> Date format: `YYYYMMDD` |
| `--ARCH=amd64` | Architecture for image<br /> <sub><sup>Options:</sup></sub> `amd64`, `arm64` | | `--ARCH=amd64` | Architecture for image<br /> <sub><sup>Options:</sup></sub> `amd64`, `arm64` |
<br /> <br />
@@ -1139,7 +1143,7 @@ sudo nano /home/docker/tvapp2/docker-compose.yml
Add the following to your `📄 docker-compose.yml`: Add the following to your `📄 docker-compose.yml`:
```yml ignore ```yml
services: services:
tvapp2: tvapp2:
container_name: tvapp2 container_name: tvapp2
@@ -1188,11 +1192,12 @@ This docker container contains the following env variables:
| `TZ` | `Etc/UTC` | Timezone for error / log reporting | | `TZ` | `Etc/UTC` | Timezone for error / log reporting |
| `WEB_IP` | `0.0.0.0` | IP to use for webserver | | `WEB_IP` | `0.0.0.0` | IP to use for webserver |
| `WEB_PORT` | `4124` | Port to use for webserver | | `WEB_PORT` | `4124` | Port to use for webserver |
| `WEB_ENCODING` | `deflate, br` | Defines the HTTP `Accept-Encoding` request and response header. This value specifies what content encoding the sender can understand<br /><br />Gzip compression can be enabled by specifying `'gzip, deflate, br'` |
| `URL_REPO` | `https://git.binaryninja.net/BinaryNinja/` | Determines where the data files will be downloaded from. Do not change this or you will be unable to get M3U and EPG data. | | `URL_REPO` | `https://git.binaryninja.net/BinaryNinja/` | Determines where the data files will be downloaded from. Do not change this or you will be unable to get M3U and EPG data. |
| `FILE_URL` | `urls.txt` | Filename for `urls.txt` cache file | | `FILE_URL` | `urls.txt` | Filename for `urls.txt` cache file |
| `FILE_M3U` | `playlist.m3u8` | Filename for M3U playlist file | | `FILE_M3U` | `playlist.m3u8` | Filename for M3U playlist file |
| `FILE_EPG` | `xmltv.xml` | Filename for XML guide data file | | `FILE_EPG` | `xmltv.xml` | Filename for XML guide data file |
| `FILE_GZIP` | `xmltv.xml.gz` | Filename for XML compressed as gzip .gz | | `FILE_GZP` | `xmltv.xml.gz` | Filename for XML compressed as gzip .gz |
| `STREAM_QUALITY` | `hd` | Stream quality<br />Can be either `hd` or `sd` | | `STREAM_QUALITY` | `hd` | Stream quality<br />Can be either `hd` or `sd` |
| `DIR_BUILD` | `/usr/src/app` | Path inside container where TVApp2 will be built. <br /><br /> <sup>⚠️ This should not be used unless you know what you're doing</sup> | | `DIR_BUILD` | `/usr/src/app` | Path inside container where TVApp2 will be built. <br /><br /> <sup>⚠️ This should not be used unless you know what you're doing</sup> |
| `DIR_RUN` | `/usr/bin/app` | Path inside container where TVApp2 will be placed after it is built <br /><br /> <sup>⚠️ This should not be used unless you know what you're doing</sup> | | `DIR_RUN` | `/usr/bin/app` | Path inside container where TVApp2 will be placed after it is built <br /><br /> <sup>⚠️ This should not be used unless you know what you're doing</sup> |
@@ -1210,6 +1215,51 @@ These paths can be mounted and shared between the TVApp2 docker container and yo
| `📁 /usr/bin/app` | <sub>Path where TVApp2 files will be placed once the app has been built. Includes `📄 formatted.dat`, `📄 xmltv.1.xml`, `📄 urls.txt`, `📁 node_modules`, and `📄 package.json`</sub> | | `📁 /usr/bin/app` | <sub>Path where TVApp2 files will be placed once the app has been built. Includes `📄 formatted.dat`, `📄 xmltv.1.xml`, `📄 urls.txt`, `📁 node_modules`, and `📄 package.json`</sub> |
| `📁 /config` | <sub>Where logs will be placed, as well as the web server generated SSH key and cert `🔑 cert.key` and `🪪 cert.crt`</sub> | | `📁 /config` | <sub>Where logs will be placed, as well as the web server generated SSH key and cert `🔑 cert.key` and `🪪 cert.crt`</sub> |
<br />
<br />
### Docker health check
This image includes a docker health check that you can define in your `📄 docker-compose.yml` file. You can view the health check status by opening your browser and going to your TVApp2 container's `health` path:
```
http://container-ip:4124/api/health
```
<br />
You should see something similar to the following response:
```json
{
"ip": "172.XX.XX.4",
"gateway": "172.XX.XX.1",
"uptime": 2703.316357306,
"message": "Healthy",
"timestamp": 1744152471451
}
```
<br />
To apply a health check, open your TVApp2 `docker-compose.yml` and add any of the following options:
```yml
# Example 1 (Using curl)
health check:
test: "curl --fail --silent http://127.0.0.1:${JELLYFIN_SCRAPER_TVAPP2_PORT_MAIN:-4124}/api/health | grep -i healthy || exit 1"
interval: 15s
timeout: 10s
retries: 3
# Example 1 (Using wget)
health check:
test: "wget -qO- http://127.0.0.1:${JELLYFIN_SCRAPER_TVAPP2_PORT_MAIN:-4124}/api/health | grep -i healthy || exit 1"
interval: 15s
timeout: 10s
retries: 3
```
<br /> <br />
--- ---
@@ -1245,7 +1295,7 @@ We will be setting up the following:
To add TVApp2 to Traefik, you will need to open your `📄 docker-compose.yml` and apply the following labels to your TVApp2 container. Ensure you change `domain.lan` to your actual domain name. To add TVApp2 to Traefik, you will need to open your `📄 docker-compose.yml` and apply the following labels to your TVApp2 container. Ensure you change `domain.lan` to your actual domain name.
```yml ignore ```yml
services: services:
tvapp2: tvapp2:
container_name: tvapp2 container_name: tvapp2
@@ -1843,6 +1893,39 @@ find ./ -name 'run' -exec sudo chmod +x {} \;
After you have set these permissions, re-build your docker image using `docker build` or `docker buildx`. Then spin the container up. After you have set these permissions, re-build your docker image using `docker build` or `docker buildx`. Then spin the container up.
<br />
<br />
#### Build Error: `[ERR] [27] Jellyfin.LiveTv.Guide.GuideManager: Error getting programs for channel XXXXXXXXXXXXXXX (Source 2) System.Xml.XmlException: '', hexadecimal value 0x1F, is an invalid character. Line 1, position 1.`
This error may be seen if you are attempting to import our EPG guide data directly into Jellyfin. The cause of this is due to you having **GZIP Compression** enabled in your header request and response. See the example below; which is in your TVApp2 `📄 docker-compose.yml` file:
```yml
tvapp2:
container_name: tvapp2
image: ghcr.io/thebinaryninja/tvapp2:latest
hostname: tvapp2
restart: unless-stopped
environment:
LOG_LEVEL: 10
WEB_ENCODING: 'gzip, deflate, br'
```
<br />
To fix the issue, add or change the environment variable `WEB_ENCODING` and ensure `gzip` is not specified in the list like the following:
```yml
tvapp2:
container_name: tvapp2
image: ghcr.io/thebinaryninja/tvapp2:latest
hostname: tvapp2
restart: unless-stopped
environment:
LOG_LEVEL: 10
WEB_ENCODING: 'deflate, br'
```
<br /> <br />
--- ---

120
docs/docs/config/volumes.md Normal file
View File

@@ -0,0 +1,120 @@
---
title: Environment Variables
tags:
- config
---
# Mountable Volumes
Mountable volumes in Docker allow you to share folders within a docker container with your host machine. This allows you to access these specific files without having to bash into the container and using the terminal to navigate around.
The TVApp2 docker image provides a few different paths that you can mount to your host machine; as outlined below.
<br />
## 📁 /usr/bin/app
<!-- md:control env -->
<!-- md:version stable-1.0.0 -->
<!-- md:default `Etc/UTC` -->
The `TZ` environment variable specifies the timezone that your docker container will
utilize. This is useful for syncing your local time with console outputs such as
our logging system.
=== "Example"
``` { .yaml .copy .select title="docker-compose.yml" linenums="1" hl_lines="13" }
services:
tvapp2:
container_name: tvapp2
image: ghcr.io/thebinaryninja/tvapp2:latest
restart: unless-stopped
volumes:
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
- /var/run/docker.sock:/var/run/docker.sock
- ./config:/config
- ./app:/usr/bin/app
environment:
- TZ=Etc/UTC # (1)
```
1. :information: Changing this env variable will change the time for anything
related to the TVApp2 docker container.
=== "Timezones"
``` yaml
Etc/UTC
Africa/Cairo
Africa/Johannesburg
Africa/Lagos
America/Argentina/Buenos_Aires
America/Bogota
America/Caracas
America/Chicago
America/El_Salvador
America/Juneau
America/Lima
America/Los_Angeles
America/Mexico_City
America/New_York
America/Phoenix
America/Santiago
America/Sao_Paulo
America/Toronto
America/Vancouver
Asia/Almaty
Asia/Ashkhabad
Asia/Bahrain
Asia/Bangkok
Asia/Chongqing
Asia/Dubai
Asia/Ho_Chi_Minh
Asia/Hong_Kong
Asia/Jakarta
Asia/Jerusalem
Asia/Kathmandu
Asia/Kolkata
Asia/Kuwait
Asia/Muscat
Asia/Qatar
Asia/Riyadh
Asia/Seoul
Asia/Shanghai
Asia/Singapore
Asia/Taipei
Asia/Tehran
Asia/Tokyo
Atlantic/Reykjavik
Australia/ACT
Australia/Adelaide
Australia/Brisbane
Australia/Sydney
Europe/Athens
Europe/Belgrade
Europe/Berlin
Europe/Copenhagen
Europe/Helsinki
Europe/Istanbul
Europe/London
Europe/Luxembourg
Europe/Madrid
Europe/Moscow
Europe/Paris
Europe/Riga
Europe/Rome
Europe/Stockholm
Europe/Tallinn
Europe/Vilnius
Europe/Warsaw
Europe/Zurich
Pacific/Auckland
Pacific/Chatham
Pacific/Fakaofo
Pacific/Honolulu
Pacific/Norfolk
US/Mountain
```
<br />

View File

@@ -0,0 +1,13 @@
[mkdocs]: https://www.mkdocs.org
[mkdocs.dotfiles]: https://www.mkdocs.org/dev-guide/themes/#dot-files
[mkdocs.metadata]: https://www.mkdocs.org/user-guide/writing-your-docs/#yaml-style-meta-data
[mkdocs.env]: https://www.mkdocs.org/user-guide/configuration/#environment-variables
[mkdocs.docs_dir]: https://www.mkdocs.org/user-guide/configuration/#docs_dir
[mkdocs.extra_templates]: https://www.mkdocs.org/user-guide/configuration/#extra_templates
[mkdocs.site_dir]: https://www.mkdocs.org/user-guide/configuration/#site_dir
[mkdocs.site_url]: https://www.mkdocs.org/user-guide/configuration/#site_url
[mkdocs.site_description]: https://www.mkdocs.org/user-guide/configuration/#site_description
[mkdocs.nav]: https://www.mkdocs.org/user-guide/configuration/#nav
[mkdocs.plugins]: https://www.mkdocs.org/user-guide/configuration/#plugins
[mkdocs.strict]: https://www.mkdocs.org/user-guide/configuration/#strict
[mkdocs.use_directory_urls]: https://www.mkdocs.org/user-guide/configuration/#use_directory_urls

View File

@@ -240,6 +240,7 @@ nav:
- Tags: 'about/tags.md' - Tags: 'about/tags.md'
- Config: - Config:
- Environment Variables: 'config/env.md' - Environment Variables: 'config/env.md'
- Volumes: 'config/volumes.md'
- Changelog: 'about/changelog.md' - Changelog: 'about/changelog.md'
- Discord: 'https://discord.gg/gTze6hRe' - Discord: 'https://discord.gg/gTze6hRe'

View File

@@ -1,5 +1,18 @@
#!/usr/bin/with-contenv sh #!/usr/bin/with-contenv sh
# #
# Store env variables in s6
# #
ip_gateway=$(/sbin/ip route|awk '/default/ { print $3 }')
ip_container=$(ifconfig | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1')
printf "$ip_gateway" > /var/run/s6/container_environment/IP_GATEWAY
printf "$ip_container" > /var/run/s6/container_environment/IP_CONTAINER
export IP_GATEWAY=$ip_gateway
export IP_GATEWAY=$ip_container
# # # #
# install and startup for tvapp2 # install and startup for tvapp2
# # # #
@@ -9,4 +22,3 @@ rm -rf ${DIR_BUILD}/*
cd ${DIR_RUN} cd ${DIR_RUN}
npm install --omit=dev npm install --omit=dev
npm start npm start

View File

@@ -54,25 +54,31 @@ chalk.level = 3;
let FILE_URL; let FILE_URL;
let FILE_M3U; let FILE_M3U;
let FILE_XML; let FILE_XML;
let FILE_TAR; let FILE_GZP;
let FILE_M3U_SIZE = 0; let FILE_M3U_SIZE = 0;
let FILE_XML_SIZE = 0; let FILE_XML_SIZE = 0;
let FILE_TAR_SIZE = 0; let FILE_GZP_SIZE = 0;
let FILE_M3U_MODIFIED = 0; let FILE_M3U_MODIFIED = 0;
let FILE_XML_MODIFIED = 0; let FILE_XML_MODIFIED = 0;
let FILE_TAR_MODIFIED = 0; let FILE_GZP_MODIFIED = 0;
const FOLDER_WWW = 'www'; const FOLDER_WWW = 'www';
/* /*
Define > Environment Variables || Defaults Define > Environment Variables || Defaults
*/ */
const envAppRelease = process.env.RELEASE || 'stable';
const envUrlRepo = process.env.URL_REPO || 'https://git.binaryninja.net/binaryninja'; const envUrlRepo = process.env.URL_REPO || 'https://git.binaryninja.net/binaryninja';
const envStreamQuality = process.env.STREAM_QUALITY || 'hd'; const envStreamQuality = process.env.STREAM_QUALITY || 'hd';
const envFileURL = process.env.FILE_URL || 'urls.txt'; const envFileURL = process.env.FILE_URL || 'urls.txt';
const envFileM3U = process.env.FILE_M3U || 'playlist.m3u8'; const envFileM3U = process.env.FILE_M3U || 'playlist.m3u8';
const envFileXML = process.env.FILE_EPG || 'xmltv.xml'; const envFileXML = process.env.FILE_EPG || 'xmltv.xml';
const envFileTAR = process.env.FILE_TAR || 'xmltv.xml.gz'; const envFileGZP = process.env.FILE_GZP || 'xmltv.xml.gz';
const envApiKey = process.env.API_KEY || null;
const envWebIP = process.env.WEB_IP || '0.0.0.0';
const envWebPort = process.env.WEB_PORT || `4124`;
const envWebEncoding = process.env.WEB_ENCODING || 'deflate, br';
const envHealthTimer = process.env.HEALTH_TIMER || 600000;
const LOG_LEVEL = process.env.LOG_LEVEL || 10; const LOG_LEVEL = process.env.LOG_LEVEL || 10;
/* /*
@@ -101,13 +107,27 @@ const USERAGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
http://127.0.0.1:4124/playlist http://127.0.0.1:4124/playlist
http://127.0.0.1:4124/key http://127.0.0.1:4124/key
http://127.0.0.1:4124/channel http://127.0.0.1:4124/channel
http://127.0.0.1:4124/health
*/ */
const subdomainGZ = [ 'gzip', 'gz' ]; const subdomainRestart = [ 'restart', 'sync', 'resync' ];
const subdomainGZP = [ 'gzip', 'gz' ];
const subdomainM3U = [ 'playlist', 'm3u', 'm3u8' ]; const subdomainM3U = [ 'playlist', 'm3u', 'm3u8' ];
const subdomainEPG = [ 'guide', 'epg', 'xml' ]; const subdomainEPG = [ 'guide', 'epg', 'xml' ];
const subdomainKey = [ 'key', 'keys' ]; const subdomainKey = [ 'key', 'keys' ];
const subdomainChan = [ 'channels', 'channel' ]; const subdomainChan = [ 'channels', 'channel', 'chan' ];
const subdomainHealth = [ 'api/status', 'api/health' ];
/*
Container Information
these environment variables are defined from the s6-overlay layer of the docker image
*/
const fileIpGateway = '/var/run/s6/container_environment/IP_GATEWAY';
const fileIpContainer = '/var/run/s6/container_environment/IP_CONTAINER';
const envIpGateway = fs.existsSync( fileIpGateway ) ? fs.readFileSync( fileIpGateway, 'utf8' ) : `0.0.0.0`;
const envIpContainer = fs.existsSync( fileIpContainer ) ? fs.readFileSync( fileIpContainer, 'utf8' ) : `0.0.0.0`;
/* /*
Define > Logs Define > Logs
@@ -180,13 +200,13 @@ class Log
static warn( ...msg ) static warn( ...msg )
{ {
if ( LOG_LEVEL >= 2 ) if ( LOG_LEVEL >= 2 )
console.warn( chalk.white.bgYellow.bold( ` ${ name } ` ), chalk.white( `` ), this.now(), chalk.yellow( msg.join( ' ' ) ) ); console.warn( chalk.white.bgYellow.bold( ` ${ name } ` ), chalk.white( `` ), this.now(), chalk.yellowBright( msg.join( ' ' ) ) );
} }
static error( ...msg ) static error( ...msg )
{ {
if ( LOG_LEVEL >= 1 ) if ( LOG_LEVEL >= 1 )
console.error( chalk.white.bgRedBright.bold( ` ${ name } ` ), chalk.white( `` ), this.now(), chalk.red( msg.join( ' ' ) ) ); console.error( chalk.white.bgRedBright.bold( ` ${ name } ` ), chalk.white( `` ), this.now(), chalk.redBright( msg.join( ' ' ) ) );
} }
} }
@@ -196,23 +216,23 @@ class Log
if ( process.pkg ) if ( process.pkg )
{ {
Log.info( `Processing Package` ); Log.info( `core`, chalk.yellow( `[init]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `Starting server utilizing process.execPath` ) );
const basePath = path.dirname( process.execPath ); const basePath = path.dirname( process.execPath );
FILE_URL = path.join( basePath, FOLDER_WWW, `${ envFileURL }` ); FILE_URL = path.join( basePath, FOLDER_WWW, `${ envFileURL }` );
FILE_M3U = path.join( basePath, FOLDER_WWW, `${ envFileM3U }` ); FILE_M3U = path.join( basePath, FOLDER_WWW, `${ envFileM3U }` );
FILE_XML = path.join( basePath, FOLDER_WWW, `${ envFileXML }` ); FILE_XML = path.join( basePath, FOLDER_WWW, `${ envFileXML }` );
FILE_XML.length; FILE_XML.length;
FILE_TAR = path.join( basePath, FOLDER_WWW, `${ envFileTAR }` ); FILE_GZP = path.join( basePath, FOLDER_WWW, `${ envFileGZP }` );
} }
else else
{ {
Log.info( `Processing Locals` ); Log.info( `core`, chalk.yellow( `[init]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `Starting server utilizing processed locals` ) );
FILE_URL = path.resolve( __dirname, FOLDER_WWW, `${ envFileURL }` ); FILE_URL = path.resolve( __dirname, FOLDER_WWW, `${ envFileURL }` );
FILE_M3U = path.resolve( __dirname, FOLDER_WWW, `${ envFileM3U }` ); FILE_M3U = path.resolve( __dirname, FOLDER_WWW, `${ envFileM3U }` );
FILE_XML = path.resolve( __dirname, FOLDER_WWW, `${ envFileXML }` ); FILE_XML = path.resolve( __dirname, FOLDER_WWW, `${ envFileXML }` );
FILE_TAR = path.resolve( __dirname, FOLDER_WWW, `${ envFileTAR }` ); FILE_GZP = path.resolve( __dirname, FOLDER_WWW, `${ envFileGZP }` );
} }
/* /*
@@ -268,7 +288,7 @@ const semaphore = new Semaphore( 5 );
async function downloadFile( url, filePath ) async function downloadFile( url, filePath )
{ {
Log.info( `Fetching`, chalk.white( `` ), chalk.grey( `${ url }` ), chalk.white( `` ), chalk.grey( `${ filePath }` ) ); Log.info( `netw`, chalk.yellow( `[start]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `Downloading external file` ), chalk.blueBright( `<source>` ), chalk.gray( `${ url }` ), chalk.blueBright( `<destination>` ), chalk.gray( `${ filePath }` ) );
return new Promise( ( resolve, reject ) => return new Promise( ( resolve, reject ) =>
{ {
@@ -280,19 +300,19 @@ async function downloadFile( url, filePath )
{ {
if ( response.statusCode !== 200 ) if ( response.statusCode !== 200 )
{ {
Log.error( `Failed to download file: ${ url }`, chalk.white( `` ), chalk.grey( `Status code: ${ response.statusCode }` ) ); Log.error( `netw`, chalk.yellow( `[error]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `Failed to download source file` ), chalk.blueBright( `<source>` ), chalk.gray( `${ url }` ), chalk.blueBright( `<destination>` ), chalk.gray( `${ filePath }` ), chalk.blueBright( `<statusCode>` ), chalk.gray( `${ response.statusCode }` ) );
return reject( new Error( `Failed to download file: ${ url }. Status code: ${ response.statusCode }` ) ); return reject( new Error( `Failed to download file: ${ url }. Status code: ${ response.statusCode }` ) );
} }
response.pipe( file ); response.pipe( file );
file.on( 'finish', () => file.on( 'finish', () =>
{ {
Log.ok( `Received`, chalk.white( `` ), `${ filePath }` ); Log.ok( `netw`, chalk.yellow( `[finish]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `Successfully downloaded and wrote new file` ), chalk.blueBright( `<source>` ), chalk.gray( `${ url }` ), chalk.blueBright( `<destination>` ), chalk.gray( `${ filePath }` ) );
file.close( () => resolve( true ) ); file.close( () => resolve( true ) );
}); });
}) })
.on( 'error', ( err ) => .on( 'error', ( err ) =>
{ {
Log.error( `Error downloading file: ${ url }`, chalk.white( `` ), chalk.grey( `Status code: ${ err.message }` ) ); Log.error( `netw`, chalk.yellow( `[error]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `Failed to download source file` ), chalk.blueBright( `<error>` ), chalk.gray( `${ err.message }`, chalk.blueBright( `<source>` ), chalk.gray( `${ url }` ), chalk.blueBright( `<destination>` ), chalk.gray( `${ filePath }` ) ) );
fs.unlink( filePath, () => reject( err ) ); fs.unlink( filePath, () => reject( err ) );
}); });
}); });
@@ -357,7 +377,7 @@ function getFileSizeHuman( filename, si = true, decimal = 1 )
} }
/* /*
Func > Ensure File Exists Func > Get Files
if file exists; start download from external website utilizing url and file path arguments; or if file exists; start download from external website utilizing url and file path arguments; or
throw error to user that file does not exist via the URL. throw error to user that file does not exist via the URL.
@@ -379,59 +399,56 @@ async function getFile( url, filePath )
{ {
if ( fs.existsSync( filePath ) ) if ( fs.existsSync( filePath ) )
{ {
Log.warn( `Using existing local file ${ filePath }, download failed`, chalk.white( `` ), chalk.grey( `${ url }` ) ); Log.warn( `netw`, chalk.yellow( `[get]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `Download failed - Using existing local file ${ filePath }` ), chalk.blueBright( `<source>` ), chalk.gray( `${ url }` ), chalk.blueBright( `<destination>` ), chalk.gray( `${ filePath }` ) );
} }
else else
{ {
Log.error( `Failed to download file, and no local file exists; aborting`, chalk.white( `` ), chalk.grey( `${ url }` ) ); Log.error( `netw`, chalk.yellow( `[error]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `Download filed and no local backup exists, aborting` ), chalk.blueBright( `<error>` ), chalk.redBright( `${ err.message }` ), chalk.blueBright( `<source>` ), chalk.gray( `${ url }` ), chalk.blueBright( `<destination>` ), chalk.gray( `${ filePath }` ) );
throw err; throw err;
} }
} }
} }
/* /*
Func > Package GZip Func > Create GZip
locates the xmltv.xml and packages it into a xmltv.gz archive locates the xmltv.xml and packages it into a xmltv.gz archive
*/ */
async function createGzip( ) async function createGzip( )
{ {
Log.debug( `Preparing to gzip`, chalk.white( `` ), chalk.grey( `${ envFileXML }` ) ); Log.info( `gzip`, chalk.yellow( `[create]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `Preparing to create compressed XML gz file` ), chalk.blueBright( `<source>` ), chalk.gray( `${ envFileXML }` ), chalk.blueBright( `<destination>` ), chalk.gray( `${ envFileGZP }` ) );
return new Promise( ( resolve, reject ) => return new Promise( ( resolve, reject ) =>
{ {
Log.debug( `createGzip[promise]`, chalk.white( `` ), chalk.grey( `${ envFileXML }` ) ); Log.debug( `gzip`, chalk.yellow( `[create]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `Promise to create compressed gz started` ), chalk.blueBright( `<source>` ), chalk.gray( `${ envFileXML }` ), chalk.blueBright( `<destination>` ), chalk.gray( `${ envFileGZP }` ) );
fs.readFile( FILE_XML, ( err, buf ) => fs.readFile( FILE_XML, ( err, buf ) =>
{ {
Log.debug( `createGzip[fs.readFile]`, chalk.white( `` ), chalk.grey( `${ envFileXML }` ) ); Log.debug( `gzip`, chalk.yellow( `[create]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `Reading source XML file` ), chalk.blueBright( `<source>` ), chalk.gray( `${ envFileXML }` ), chalk.blueBright( `<destination>` ), chalk.gray( `${ envFileGZP }` ) );
if ( err ) if ( err )
{ {
Log.error( `Could not read file ${ envFileXML }. Error: `, chalk.white( `` ), chalk.grey( `${ err }` ) ); Log.error( `gzip`, chalk.yellow( `[create]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `Could not read source XML file` ), chalk.blueBright( `<error>` ), chalk.redBright( `${ err }` ), chalk.blueBright( `<source>` ), chalk.gray( `${ envFileXML }` ), chalk.blueBright( `<destination>` ), chalk.gray( `${ envFileGZP }` ) );
return reject( new Error( `Could not read file ${ envFileXML }. Error: ${ err }` ) ); return reject( new Error( `Could not read file ${ envFileXML }. Error: ${ err }` ) );
} }
zlib.gzip( buf, ( err, buf ) => zlib.gzip( buf, ( err, buf ) =>
{ {
Log.debug( `createGzip[zlib.gzip]`, chalk.white( `` ), chalk.grey( `${ envFileXML }` ), chalk.white( `` ), chalk.grey( `${ envFileTAR }` ) ); Log.debug( `gzip`, chalk.yellow( `[create]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `Starting zlib.gzip` ), chalk.blueBright( `<source>` ), chalk.gray( `${ envFileXML }` ), chalk.blueBright( `<destination>` ), chalk.gray( `${ envFileGZP }` ) );
if ( err ) if ( err )
{ {
Log.error( `Could not write to archive. Error: `, chalk.white( `` ), chalk.grey( `${ err }` ) ); Log.error( `gzip`, chalk.yellow( `[create]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `Could not create gz archive` ), chalk.blueBright( `<error>` ), chalk.redBright( `${ err }` ), chalk.blueBright( `<source>` ), chalk.gray( `${ envFileXML }` ), chalk.blueBright( `<destination>` ), chalk.gray( `${ envFileGZP }` ) );
return reject( new Error( `Could not create ${ envFileTAR }. Error: ${ err }` ) ); return reject( new Error( `Could not create ${ envFileGZP }. Error: ${ err }` ) );
} }
Log.info( `Compressing`, chalk.white( `` ), `${ envFileXML }`, chalk.white( `` ), `${ FILE_TAR }` ); Log.info( `gzip`, chalk.yellow( `[create]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `Started creating gz archive from XML source` ), chalk.blueBright( `<source>` ), chalk.gray( `${ envFileXML }` ), chalk.blueBright( `<destination>` ), chalk.gray( `${ envFileGZP }` ) );
fs.writeFile( `${ FILE_TAR }`, buf, ( err ) => fs.writeFile( `${ FILE_GZP }`, buf, ( err ) =>
{ {
if ( err ) if ( err )
{ {
Log.error( `Could not write XML file to archive. Error: `, chalk.white( `` ), chalk.grey( `${ err }` ) ); Log.error( `gzip`, chalk.yellow( `[create]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `Could not write to and create gz archive` ), chalk.blueBright( `<error>` ), chalk.redBright( `${ err }` ), chalk.blueBright( `<source>` ), chalk.gray( `${ envFileXML }` ), chalk.blueBright( `<destination>` ), chalk.gray( `${ envFileGZP }` ) );
return reject( new Error( `Could not write XML file ${ envFileXML } to ${ envFileTAR }. Error: ${ err }` ) ); return reject( new Error( `Could not write XML file ${ envFileXML } to ${ envFileGZP }. Error: ${ err }` ) );
} }
Log.ok( `Compressed`, chalk.white( `` ), `${ envFileXML }`, chalk.white( `` ), `${ FILE_TAR }` ); Log.ok( `gzip`, chalk.yellow( `[create]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `Successfully created compressed gz archive from XML source file` ), chalk.blueBright( `<source>` ), chalk.gray( `${ envFileXML }` ), chalk.blueBright( `<destination>` ), chalk.gray( `${ envFileGZP }` ) );
resolve( true ); resolve( true );
}); });
}); });
@@ -440,19 +457,12 @@ async function createGzip( )
} }
/* /*
Func > Ensure File Exists Func > Get Gzip
if file exists; start download from external website utilizing url and file path arguments; or try; catch to create a .gz compressed file from the .xml guide data
throw error to user that file does not exist via the URL.
If file cannot be obtained from external url; use local copy if available
@arg str url https://git.binaryninja.net/binaryninja/tvapp2-externals/raw/branch/main/urls.txt
@arg str filePath H:\Repos\github\BinaryNinja\tvapp2\tvapp2\urls.txt
@ret none
*/ */
async function prepareGzip( ) async function getGzip( )
{ {
try try
{ {
@@ -462,16 +472,37 @@ async function prepareGzip( )
{ {
if ( fs.existsSync( FILE_XML ) ) if ( fs.existsSync( FILE_XML ) )
{ {
Log.warn( `XML file found, but gzip failed to compress XML`, chalk.white( `` ), chalk.grey( `${ FILE_XML }` ) ); Log.warn( `gzip`, chalk.yellow( `[compress]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.yellowBright( `Source xml file found, but gzip failed generate a compressed .gz fileL` ), chalk.blueBright( `<source>` ), chalk.gray( `${ FILE_XML }` ) );
} }
else else
{ {
Log.error( `XML file not found`, chalk.white( `` ), chalk.grey( `${ FILE_XML }` ) ); Log.error( `gzip`, chalk.yellow( `[error]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.redBright( `Source XML file not found; cannot create compressed gzip` ), chalk.blueBright( `<error>` ), chalk.redBright( `${ err.message }` ), chalk.blueBright( `<source>` ), chalk.gray( `${ FILE_XML }` ) );
throw err; throw err;
} }
} }
} }
/*
@note Jellyfin Users
Originally, this node webserver enabled gzip compression for the value `Accept-Encoding`. Doing this
may cause an error to appear in Jellyfin logs / console when attempting to fetch the latest guide data
from the tvapp2 xml file.
[ERR] [27] Jellyfin.LiveTv.Guide.GuideManager: Error getting programs for channel XXXXXXXXXXXXXXX (Source 2)
System.Xml.XmlException: '', hexadecimal value 0x1F, is an invalid character. Line 1, position 1.
To fix the error, we create a customizable env variable that allows the user to override the encoding header.
We change the following:
'Accept-Encoding': 'gzip, deflate, br'
to
'Accept-Encoding': 'deflate, br'
This error does not appear if you load the xml guide data into Cabernet, and then use a tuner to fetch the data from
cabernet to jellyfin
*/
async function fetchRemote( url ) async function fetchRemote( url )
{ {
return new Promise( ( resolve, reject ) => return new Promise( ( resolve, reject ) =>
@@ -480,13 +511,13 @@ async function fetchRemote( url )
mod mod
.get( url, { .get( url, {
headers: { headers: {
'Accept-Encoding': 'gzip, deflate, br' 'Accept-Encoding': envWebEncoding
} }
}, ( resp ) => }, ( resp ) =>
{ {
if ( resp.statusCode !== 200 ) if ( resp.statusCode !== 200 )
{ {
Log.error( `Server returned status code other than 200`, chalk.white( `` ), chalk.grey( `${ url } - ${ resp.statusCode }` ) ); Log.error( `core`, chalk.yellow( `[fetch]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.redBright( `Server returned status code other than 200` ), chalk.blueBright( `<statusCode>` ), chalk.redBright( `${ resp.statusCode }` ), chalk.blueBright( `<url>` ), chalk.gray( `${ url }` ) );
return reject( new Error( `HTTP ${ resp.statusCode } for ${ url }` ) ); return reject( new Error( `HTTP ${ resp.statusCode } for ${ url }` ) );
} }
@@ -539,13 +570,26 @@ async function serveKey( req, res )
const uriParam = new URL( req.url, `http://${ req.headers.host }` ).searchParams.get( 'uri' ); const uriParam = new URL( req.url, `http://${ req.headers.host }` ).searchParams.get( 'uri' );
if ( !uriParam ) if ( !uriParam )
{ {
res.writeHead( 400, { const statusCheck =
'Content-Type': 'text/plain' {
ip: envIpContainer,
gateway: envIpGateway,
uptime: process.uptime(),
message: 'Error: Missing "uri" parameter for key download.',
status: 'unhealthy',
ref: req.url,
method: req.method || 'GET',
code: 400,
timestamp: Date.now()
};
res.writeHead( statusCheck.code, {
'Content-Type': 'application/json'
}); });
Log.error( `Missing "uri" parameter for key download`, chalk.white( `` ), chalk.grey( `${ req.url }` ) ); Log.error( `key`, chalk.yellow( `[error]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.redBright( `${ statusCheck.message }` ), chalk.blueBright( `<url>` ), chalk.gray( `${ req.url }` ), chalk.blueBright( `<statusCode>` ), chalk.gray( `${ statusCheck.code }` ) );
return res.end( 'Error: Missing "uri" parameter for key download.' ); return res.end( JSON.stringify( statusCheck ) );
} }
const keyData = await fetchRemote( uriParam ); const keyData = await fetchRemote( uriParam );
@@ -557,13 +601,27 @@ async function serveKey( req, res )
} }
catch ( err ) catch ( err )
{ {
Log.error( `ServeKey Error:`, chalk.white( `` ), chalk.grey( `${ err.message }` ) ); const statusCheck =
{
ip: envIpContainer,
gateway: envIpGateway,
uptime: process.uptime(),
message: `Failed to serve key`,
error: `${ err.message }`,
status: 'unhealthy',
ref: req.url,
method: req.method || 'GET',
code: 500,
timestamp: Date.now()
};
res.writeHead( 500, { res.writeHead( statusCheck.code, {
'Content-Type': 'text/plain' 'Content-Type': 'application/json'
}); });
res.end( 'Error fetching key.' ); Log.error( `key`, chalk.yellow( `[error]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.redBright( `${ statusCheck.message }` ), chalk.blueBright( `<error>` ), chalk.redBright( `${ statusCheck.error }` ), chalk.blueBright( `<url>` ), chalk.gray( `${ req.url }` ), chalk.blueBright( `<statusCode>` ), chalk.gray( `${ statusCheck.code }` ) );
res.end( JSON.stringify( statusCheck ) );
} }
} }
@@ -649,7 +707,7 @@ async function getTokenizedUrl( channelUrl )
const streamNameMatch = html.match( /id="stream_name" name="([^"]+)"/ ); const streamNameMatch = html.match( /id="stream_name" name="([^"]+)"/ );
if ( !streamNameMatch ) if ( !streamNameMatch )
{ {
Log.error( `Cannot find "stream_name"`, chalk.white( `` ), chalk.grey( `${ channelUrl }` ) ); Log.error( `playlist`, chalk.yellow( `[error]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.redBright( `Cannot find "stream_name` ), chalk.blueBright( `<url>` ), chalk.grey( `${ channelUrl }` ) );
return null; return null;
} }
streamName = streamNameMatch[1]; streamName = streamNameMatch[1];
@@ -669,6 +727,8 @@ async function getTokenizedUrl( channelUrl )
const tokenResponse = await fetchPage( tokenUrl ); const tokenResponse = await fetchPage( tokenUrl );
let finalUrl; let finalUrl;
Log.debug( `playlist`, chalk.yellow( `[tokenize]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `Generating tokenized final stream URL` ), chalk.blueBright( `<streamName>` ), chalk.gray( `${ streamName }` ), chalk.blueBright( `<quality>` ), chalk.gray( `${ envStreamQuality }` ), chalk.blueBright( `<host>` ), chalk.gray( `${ streamHost }` ) );
try try
{ {
const json = JSON.parse( tokenResponse ); const json = JSON.parse( tokenResponse );
@@ -676,23 +736,22 @@ async function getTokenizedUrl( channelUrl )
} }
catch ( err ) catch ( err )
{ {
Log.error( `Failed to parse token JSON for channel`, chalk.white( `` ), chalk.grey( `${ channelUrl } - ${ err.message }` ) ); Log.error( `playlist`, chalk.yellow( `[error]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.redBright( `Failed to parse token JSON for channel` ), chalk.blueBright( `<error>` ), chalk.redBright( `${ err.message }` ), chalk.blueBright( `<url>` ), chalk.gray( `${ channelUrl }` ) );
return null; return null;
} }
if ( !finalUrl ) if ( !finalUrl )
{ {
Log.error( `No URL found in token JSON for channel`, chalk.white( `` ), chalk.grey( `${ channelUrl }` ) ); Log.error( `playlist`, chalk.yellow( `[error]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.redBright( `No URL found in token JSON for channel` ), chalk.blueBright( `<url>` ), chalk.gray( `${ channelUrl }` ) );
return null; return null;
} }
Log.debug( `Tokenized URL:`, chalk.white( `` ), chalk.grey( `${ finalUrl }` ) ); Log.debug( `playlist`, chalk.yellow( `[tokenize]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `Completed generated tokenized final stream URL` ), chalk.blueBright( `<streamName>` ), chalk.gray( `${ streamName }` ), chalk.blueBright( `<quality>` ), chalk.gray( `${ envStreamQuality }` ), chalk.blueBright( `<host>` ), chalk.gray( `${ streamHost }` ), chalk.blueBright( `<url>` ), chalk.gray( `${ finalUrl }` ) );
return finalUrl; return finalUrl;
} }
catch ( err ) catch ( err )
{ {
Log.error( `Fatal error fetching token:`, chalk.white( `` ), chalk.grey( `${ err.message }` ) ); Log.error( `playlist`, chalk.yellow( `[error]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.redBright( `Fatal error fetching token` ), chalk.blueBright( `<error>` ), chalk.redBright( `${ err.message }` ), chalk.blueBright( `<url>` ), chalk.grey( `${ channelUrl }` ) );
return null; return null;
} }
} }
@@ -705,11 +764,26 @@ async function serveM3UPlaylist( req, res )
const urlParam = new URL( req.url, `http://${ req.headers.host }` ).searchParams.get( 'url' ); const urlParam = new URL( req.url, `http://${ req.headers.host }` ).searchParams.get( 'url' );
if ( !urlParam ) if ( !urlParam )
{ {
Log.error( `Missing parameter`, chalk.white( `` ), chalk.grey( `URL` ) ); const statusCheck =
res.writeHead( 400, { {
'Content-Type': 'text/plain' ip: envIpContainer,
gateway: envIpGateway,
uptime: process.uptime(),
message: `Missing ?url= parameter`,
status: `unhealthy`,
ref: req.url,
method: req.method || 'GET',
code: 404,
timestamp: Date.now()
};
res.writeHead( statusCheck.code, {
'Content-Type': 'application/json'
}); });
res.end( 'Error: Missing URL parameter.' );
Log.error( `channel`, chalk.yellow( `[error]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.redBright( `${ statusCheck.message }` ), chalk.blueBright( `<expected>` ), chalk.grey( `http://${ req.headers.host }/channel?url=XXXX` ), chalk.blueBright( `<statusCode>` ), chalk.gray( `${ statusCheck.code }` ) );
res.end( JSON.stringify( statusCheck ) );
return; return;
} }
@@ -733,22 +807,38 @@ async function serveM3UPlaylist( req, res )
'Content-Disposition': 'inline; filename="' + envFileM3U 'Content-Disposition': 'inline; filename="' + envFileM3U
}); });
Log.debug( `playlist`, chalk.yellow( `[fetch]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.redBright( `retrieving cached playlist` ), chalk.blueBright( `<cachedUrl>` ), chalk.gray( `${ cachedUrl }` ), chalk.blueBright( `<stream>` ), chalk.gray( `${ urlParam }` ), chalk.blueBright( `<statusCode>` ), chalk.gray( `200` ) );
res.end( rewrittenPlaylist ); res.end( rewrittenPlaylist );
return; return;
} }
Log.info( `Fetching stream:`, chalk.white( `` ), chalk.grey( `${ urlParam }` ) ); Log.info( `playlist`, chalk.yellow( `[fetch]` ), chalk.white( `` ), chalk.blueBright( `<stream>` ), chalk.gray( `${ urlParam }` ) );
const finalUrl = await getTokenizedUrl( decodedUrl ); const finalUrl = await getTokenizedUrl( decodedUrl );
if ( !finalUrl ) if ( !finalUrl )
{ {
Log.error( `Failed to retrieve tokenized URL` ); const statusCheck =
{
ip: envIpContainer,
gateway: envIpGateway,
uptime: process.uptime(),
message: `Failed to retrieve tokenized URL.`,
status: `unhealthy`,
ref: req.url,
method: req.method || 'GET',
code: 500,
timestamp: Date.now()
};
res.writeHead( 500, { res.writeHead( statusCheck.code, {
'Content-Type': 'text/plain' 'Content-Type': 'application/json'
}); });
res.end( 'Error: Failed to retrieve tokenized URL.' ); Log.error( `playlist`, chalk.yellow( `[error]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.redBright( `${ statusCheck.message }` ), chalk.blueBright( `<stream>` ), chalk.gray( `${ urlParam }` ), chalk.blueBright( `<statusCode>` ), chalk.gray( `${ statusCheck.code }` ) );
res.end( JSON.stringify( statusCheck ) );
return; return;
} }
@@ -765,15 +855,95 @@ async function serveM3UPlaylist( req, res )
} }
catch ( err ) catch ( err )
{ {
Log.error( `Error processing request:`, chalk.white( `` ), chalk.grey( `${ err.message }` ) );
if ( !res.headersSent ) if ( !res.headersSent )
{ {
res.writeHead( 500, { const statusCheck =
'Content-Type': 'text/plain' {
ip: envIpContainer,
gateway: envIpGateway,
uptime: process.uptime(),
message: `Cannot process request when fetching channel playlist`,
error: `${ err.message }`,
status: 'unhealthy',
ref: req.url,
method: req.method || 'GET',
code: 500,
timestamp: Date.now()
};
res.writeHead( statusCheck.code, {
'Content-Type': 'application/json'
}); });
res.end( 'Error processing request.' ); Log.error( `playlist`, chalk.yellow( `[error]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.redBright( `${ statusCheck.message }` ), chalk.blueBright( `<error>` ), chalk.redBright( `${ statusCheck.message }` ), chalk.blueBright( `<statusCode>` ), chalk.gray( `${ statusCheck.code }` ) );
res.end( JSON.stringify( statusCheck ) );
}
}
finally
{
semaphore.release();
}
}
async function serveHealthCheck( req, res )
{
await semaphore.acquire();
try
{
const urlParam = new URL( req.url, `http://${ req.headers.host }` ).searchParams.get( 'api' );
if ( !urlParam )
{
Log.debug( `health`, chalk.yellow( `[api]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `No API key passed to health check` ) );
}
const statusCheck =
{
ip: envIpContainer,
gateway: envIpGateway,
uptime: process.uptime(),
message: `healthy`,
status: `healthy`,
ref: req.url,
method: req.method || 'GET',
code: 200,
timestamp: Date.now()
};
res.writeHead( statusCheck.code, {
'Content-Type': 'application/json'
});
Log.ok( `health`, chalk.yellow( `[api]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `health check returned` ), chalk.greenBright( `${ statusCheck.status }` ), chalk.blueBright( `<statusCode>` ), chalk.gray( `${ statusCheck.code }` ), chalk.blueBright( `<uptime>` ), chalk.gray( `${ process.uptime() }` ) );
res.end( JSON.stringify( statusCheck ) );
return;
}
catch ( err )
{
if ( !res.headersSent )
{
const statusCheck =
{
ip: envIpContainer,
gateway: envIpGateway,
uptime: process.uptime(),
message: `health check failed`,
error: `${ err.message }`,
status: `unhealthy`,
ref: req.url,
method: req.method || 'GET',
code: 503,
timestamp: Date.now()
};
res.writeHead( statusCheck.code, {
'Content-Type': 'application/json'
});
Log.error( `health`, chalk.yellow( `[error]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `${ statusCheck.message }; returned` ), chalk.redBright( `${ statusCheck.status }` ), chalk.blueBright( `<error>` ), chalk.redBright( `${ err.message }` ), chalk.blueBright( `<statusCode>` ), chalk.gray( `${ statusCheck.code }` ), chalk.blueBright( `<uptime>` ), chalk.gray( `${ process.uptime() }` ) );
res.end( JSON.stringify( statusCheck ) );
} }
} }
finally finally
@@ -815,7 +985,7 @@ async function rewriteM3U( originalUrl, req )
Serves IPTV .m3u playlist Serves IPTV .m3u playlist
*/ */
async function serveM3U( response, req ) async function serveM3U( res, req )
{ {
try try
{ {
@@ -833,22 +1003,36 @@ async function serveM3U( response, req )
return `${ baseUrl }/channel?url=${ encodeURIComponent( fullUrl ) }`; return `${ baseUrl }/channel?url=${ encodeURIComponent( fullUrl ) }`;
}); });
response.writeHead( 200, { res.writeHead( 200, {
'Content-Type': 'application/x-mpegURL', 'Content-Type': 'application/x-mpegURL',
'Content-Disposition': 'inline; filename="' + envFileM3U 'Content-Disposition': 'inline; filename="' + envFileM3U
}); });
response.end( updatedContent ); res.end( updatedContent );
} }
catch ( err ) catch ( err )
{ {
Log.error( `Error in serveM3U:`, chalk.white( `` ), chalk.grey( `${ err.message }` ) ); const statusCheck =
{
ip: envIpContainer,
gateway: envIpGateway,
uptime: process.uptime(),
message: `Fatal error serving playlist`,
error: `${ err.message }`,
status: 'unhealthy',
ref: req.url,
method: req.method || 'GET',
code: 500,
timestamp: Date.now()
};
response.writeHead( 500, { Log.error( `playlist`, chalk.yellow( `[serve]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.redBright( `${ statusCheck.message }` ), chalk.blueBright( `<error>` ), chalk.redBright( `${ statusCheck.message }` ), chalk.blueBright( `<url>` ), chalk.gray( `${ req.url }` ), chalk.blueBright( `<statusCode>` ), chalk.gray( `${ statusCheck.code }` ) );
'Content-Type': 'text/plain'
res.writeHead( statusCheck.code, {
'Content-Type': 'application/json'
}); });
response.end( `Error serving playlist: ${ err.message }` ); res.end( JSON.stringify( statusCheck ) );
} }
} }
@@ -874,13 +1058,13 @@ async function serveXML( response, req )
} }
catch ( err ) catch ( err )
{ {
Log.error( `Error in serveM3U:`, chalk.white( `` ), chalk.grey( `${ err.message }` ) ); Log.error( `playlist`, chalk.yellow( `[serve]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.redBright( `Fatal serving xml / epg guide data` ), chalk.blueBright( `<error>` ), chalk.redBright( `${ err.message }` ), chalk.blueBright( `<url>` ), chalk.gray( `${ req.url }` ) );
response.writeHead( 500, { response.writeHead( 500, {
'Content-Type': 'text/plain' 'Content-Type': 'text/plain'
}); });
response.end( `Error serving playlist: ${ err.message }` ); response.end( `Error serving xml/epg guide data: ${ err.message }` );
} }
}; };
@@ -888,25 +1072,25 @@ async function serveXML( response, req )
Serves IPTV .gz guide data Serves IPTV .gz guide data
*/ */
async function serveTAR( response, req ) async function serveGZP( response, req )
{ {
try try
{ {
const protocol = req.headers['x-forwarded-proto']?.split( ',' )[0] || ( req.socket.encrypted ? 'https' : 'http' ); const protocol = req.headers['x-forwarded-proto']?.split( ',' )[0] || ( req.socket.encrypted ? 'https' : 'http' );
const host = req.headers.host; const host = req.headers.host;
const baseUrl = `${ protocol }://${ host }`; const baseUrl = `${ protocol }://${ host }`;
const formattedContent = fs.readFileSync( FILE_TAR ); const formattedContent = fs.readFileSync( FILE_GZP );
response.writeHead( 200, { response.writeHead( 200, {
'Content-Type': 'application/gzip', 'Content-Type': 'application/gzip',
'Content-Disposition': 'inline; filename="' + envFileTAR 'Content-Disposition': 'inline; filename="' + envFileGZP
}); });
response.end( formattedContent ); response.end( formattedContent );
} }
catch ( err ) catch ( err )
{ {
Log.error( `Error in serveTAR:`, chalk.white( `` ), chalk.grey( `${ err.message }` ) ); Log.error( `playlist`, chalk.yellow( `[serve]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.redBright( `Fatal serving compressed gzip file` ), chalk.blueBright( `<error>` ), chalk.redBright( `${ err.message }` ), chalk.blueBright( `<url>` ), chalk.gray( `${ req.url }` ) );
response.writeHead( 500, { response.writeHead( 500, {
'Content-Type': 'text/plain' 'Content-Type': 'text/plain'
@@ -924,7 +1108,7 @@ function setCache( key, value, ttl )
expiry expiry
}); });
Log.debug( `Cache set for key ${ key } which expires in`, chalk.white( `` ), chalk.grey( `${ ttl / 1000 } seconds` ) ); Log.debug( `cache`, chalk.yellow( `[set]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `new key created` ), chalk.blueBright( `<key>` ), chalk.gray( `${ key }` ), chalk.blueBright( `<expire>` ), chalk.gray( `${ ttl / 1000 } seconds` ) );
} }
function getCache( key ) function getCache( key )
@@ -937,7 +1121,7 @@ function getCache( key )
else else
{ {
if ( cached ) if ( cached )
Log.debug( `Cache expired for key`, chalk.white( `` ), chalk.grey( `${ key }` ) ); Log.debug( `cache`, chalk.yellow( `[get]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `key has expired, marked for deletion` ), chalk.blueBright( `<key>` ), chalk.gray( `${ key }` ) );
cache.delete( key ); cache.delete( key );
return null; return null;
@@ -954,12 +1138,18 @@ async function initialize()
{ {
try try
{ {
Log.info( `Initialization Started` ); const start = performance.now();
Log.info( `core`, chalk.yellow( `[init]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `Starting TVApp2 container. Assigning bound IP to host network adapter` ), chalk.blueBright( `<hostIp>` ), chalk.gray( `${ envWebIP }` ), chalk.blueBright( `<containerIp>` ), chalk.gray( `${ envIpContainer }` ), chalk.blueBright( `<port>` ), chalk.gray( `${ envWebPort }` ) );
Log.debug( `.env`, chalk.yellow( `[set]` ), chalk.white( `` ), chalk.blueBright( `<variable>` ), chalk.gray( `FILE_URL` ), chalk.blueBright( `<value>` ), chalk.gray( `${ FILE_URL }` ) );
Log.debug( `.env`, chalk.yellow( `[set]` ), chalk.white( `` ), chalk.blueBright( `<variable>` ), chalk.gray( `FILE_M3U` ), chalk.blueBright( `<value>` ), chalk.gray( `${ FILE_M3U }` ) );
Log.debug( `.env`, chalk.yellow( `[set]` ), chalk.white( `` ), chalk.blueBright( `<variable>` ), chalk.gray( `FILE_XML` ), chalk.blueBright( `<value>` ), chalk.gray( `${ FILE_XML }` ) );
Log.debug( `.env`, chalk.yellow( `[set]` ), chalk.white( `` ), chalk.blueBright( `<variable>` ), chalk.gray( `FILE_GZP` ), chalk.blueBright( `<value>` ), chalk.gray( `${ FILE_GZP }` ) );
await getFile( extURL, FILE_URL ); await getFile( extURL, FILE_URL );
await getFile( extXML, FILE_XML ); await getFile( extXML, FILE_XML );
await getFile( extM3U, FILE_M3U ); await getFile( extM3U, FILE_M3U );
await prepareGzip(); await getGzip();
urls = fs.readFileSync( FILE_URL, 'utf-8' ).split( '\n' ).filter( Boolean ); urls = fs.readFileSync( FILE_URL, 'utf-8' ).split( '\n' ).filter( Boolean );
if ( urls.length === 0 ) if ( urls.length === 0 )
@@ -971,17 +1161,18 @@ async function initialize()
FILE_M3U_SIZE = getFileSizeHuman( FILE_M3U ); FILE_M3U_SIZE = getFileSizeHuman( FILE_M3U );
FILE_XML_SIZE = getFileSizeHuman( FILE_XML ); FILE_XML_SIZE = getFileSizeHuman( FILE_XML );
FILE_TAR_SIZE = getFileSizeHuman( FILE_TAR ); FILE_GZP_SIZE = getFileSizeHuman( FILE_GZP );
FILE_M3U_MODIFIED = getFileModified( FILE_M3U ); FILE_M3U_MODIFIED = getFileModified( FILE_M3U );
FILE_XML_MODIFIED = getFileModified( FILE_XML ); FILE_XML_MODIFIED = getFileModified( FILE_XML );
FILE_TAR_MODIFIED = getFileModified( FILE_TAR ); FILE_GZP_MODIFIED = getFileModified( FILE_GZP );
Log.info( `Initialization Complete` ); const end = performance.now();
Log.info( `core`, chalk.yellow( `[init]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `TVApp2 container is ready` ), chalk.blueBright( `took ${ end - start }ms` ), chalk.blueBright( `<message>` ), chalk.gray( `TVApp2 container is ready; took ${ end - start }ms` ), chalk.blueBright( `<ip>` ), chalk.gray( `${ envIpContainer }` ), chalk.blueBright( `<gateway>` ), chalk.gray( `${ envIpGateway }` ), chalk.blueBright( `<port>` ), chalk.gray( `${ envWebPort }` ) );
} }
catch ( err ) catch ( err )
{ {
Log.error( `Initialization error:`, chalk.white( `` ), chalk.grey( `${ err.message }` ) ); Log.error( `core`, chalk.yellow( `[init]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.redBright( `Could not start up TVApp2 container due to error` ), chalk.blueBright( `<error>` ), chalk.redBright( `${ err }` ), chalk.blueBright( `<ip>` ), chalk.gray( `${ envIpContainer }` ), chalk.blueBright( `<gateway>` ), chalk.gray( `${ envIpGateway }` ), chalk.blueBright( `<port>` ), chalk.gray( `${ envWebPort }` ) );
} }
} }
@@ -1023,52 +1214,89 @@ const server = http.createServer( ( request, response ) =>
const loadFile = reqUrl.replace( /^\/+/, '' ); const loadFile = reqUrl.replace( /^\/+/, '' );
Log.debug( `www`, chalk.yellow( ` [GET] ` ), chalk.white( `` ), chalk.grey( `${ loadFile }` ) );
const handleRequest = async() => const handleRequest = async() =>
{ {
/* /*
Define the different routes. Define the different routes.
Place the template system last. Getting TVApp data should take priority. Place the template system last. Getting TVApp data should take priority.
subdomainM3U array []
loadFile channel?url=https%3A%2F%2Ftvpass.org%2Fchannel%2Fabc-wabc-new-york-ny%2F
*/ */
if ( subdomainM3U.includes( `${ loadFile }` ) && method === 'GET' ) if ( subdomainRestart.some( ( urlKeyword ) => loadFile.startsWith( urlKeyword ) ) )
{ {
Log.info( `Received request for m3u playlist data`, chalk.white( `` ), chalk.grey( `${ loadFile }` ) ); await initialize();
const statusCheck =
{
ip: envIpContainer,
gateway: envIpGateway,
uptime: process.uptime(),
message: 'Restart command received',
status: 'ok',
ref: request.url,
method: method || 'GET',
code: 200,
timestamp: Date.now()
};
response.writeHead( statusCheck.code, {
'Content-Type': 'application/json'
});
Log.info( `www`, chalk.yellow( `[req]` ), chalk.white( `` ), chalk.blueBright( `<type>` ), chalk.gray( `api/restart` ), chalk.blueBright( `<file>` ), chalk.gray( `${ loadFile }` ), chalk.blueBright( `<method>` ), chalk.gray( `${ method }` ) );
response.end( JSON.stringify( statusCheck ) );
return;
}
if ( subdomainM3U.some( ( urlKeyword ) => loadFile.startsWith( urlKeyword ) ) && method === 'GET' )
{
Log.info( `www`, chalk.yellow( `[req]` ), chalk.white( `` ), chalk.blueBright( `<type>` ), chalk.gray( `m3u playlist` ), chalk.blueBright( `<file>` ), chalk.gray( `${ loadFile }` ), chalk.blueBright( `<method>` ), chalk.gray( `${ method }` ) );
await serveM3U( response, request ); await serveM3U( response, request );
return; return;
} }
if ( subdomainChan.includes( `${ loadFile }` ) && method === 'GET' ) if ( subdomainChan.some( ( urlKeyword ) => loadFile.startsWith( urlKeyword ) ) && method === 'GET' )
{ {
Log.info( `Received request for channel data`, chalk.white( `` ), chalk.grey( `${ loadFile }` ) ); Log.info( `www`, chalk.yellow( `[req]` ), chalk.white( `` ), chalk.blueBright( `<type>` ), chalk.gray( `channel` ), chalk.blueBright( `<file>` ), chalk.gray( `${ loadFile }` ), chalk.blueBright( `<method>` ), chalk.gray( `${ method }` ) );
await serveM3UPlaylist( request, response ); await serveM3UPlaylist( request, response );
return; return;
} }
if ( subdomainKey.includes( `${ loadFile }` ) && method === 'GET' ) if ( subdomainKey.some( ( urlKeyword ) => loadFile.startsWith( urlKeyword ) ) && method === 'GET' )
{ {
Log.info( `Received request for key data`, chalk.white( `` ), chalk.grey( `${ loadFile }` ) ); Log.info( `www`, chalk.yellow( `[req]` ), chalk.white( `` ), chalk.blueBright( `<type>` ), chalk.gray( `key` ), chalk.blueBright( `<file>` ), chalk.gray( `${ loadFile }` ), chalk.blueBright( `<method>` ), chalk.gray( `${ method }` ) );
await serveKey( request, response ); await serveKey( request, response );
return; return;
} }
if ( subdomainEPG.includes( `${ loadFile }` ) && method === 'GET' ) if ( subdomainEPG.some( ( urlKeyword ) => loadFile.startsWith( urlKeyword ) ) && method === 'GET' )
{ {
Log.info( `Received request for raw EPG data`, chalk.white( `` ), chalk.grey( `${ loadFile }` ) ); Log.info( `www`, chalk.yellow( `[req]` ), chalk.white( `` ), chalk.blueBright( `<type>` ), chalk.gray( `epg-uncompressed` ), chalk.blueBright( `<file>` ), chalk.gray( `${ loadFile }` ), chalk.blueBright( `<method>` ), chalk.gray( `${ method }` ) );
await serveXML( response, request ); await serveXML( response, request );
return; return;
} }
if ( subdomainGZ.includes( `${ loadFile }` ) && method === 'GET' ) if ( subdomainGZP.some( ( urlKeyword ) => loadFile.startsWith( urlKeyword ) ) && method === 'GET' )
{ {
Log.info( `Received request for compressed EPG data`, chalk.white( `` ), chalk.grey( `${ loadFile }` ) ); Log.info( `www`, chalk.yellow( `[req]` ), chalk.white( `` ), chalk.blueBright( `<type>` ), chalk.gray( `epg-compressed` ), chalk.blueBright( `<file>` ), chalk.gray( `${ loadFile }` ), chalk.blueBright( `<method>` ), chalk.gray( `${ method }` ) );
await serveTAR( response, request ); await serveGZP( response, request );
return;
}
if ( subdomainHealth.some( ( urlKeyword ) => loadFile.startsWith( urlKeyword ) ) && method === 'GET' )
{
Log.info( `www`, chalk.yellow( `[req]` ), chalk.white( `` ), chalk.blueBright( `<type>` ), chalk.gray( `api` ), chalk.blueBright( `<file>` ), chalk.gray( `${ loadFile }` ), chalk.blueBright( `<method>` ), chalk.gray( `${ method }` ) );
await serveHealthCheck( request, response );
return; return;
} }
@@ -1087,10 +1315,12 @@ const server = http.createServer( ( request, response ) =>
sizeXML: FILE_XML_SIZE, sizeXML: FILE_XML_SIZE,
dateXML: FILE_XML_MODIFIED, dateXML: FILE_XML_MODIFIED,
fileTAR: envFileTAR, fileGZP: envFileGZP,
sizeTAR: FILE_TAR_SIZE, sizeGZP: FILE_GZP_SIZE,
dateTAR: FILE_TAR_MODIFIED, dateGZP: FILE_GZP_MODIFIED,
healthTimer: envHealthTimer,
appRelease: envAppRelease,
appName: name, appName: name,
appVersion: version, appVersion: version,
appUrlGithub: repository.url, appUrlGithub: repository.url,
@@ -1116,6 +1346,7 @@ const server = http.createServer( ( request, response ) =>
'.png' : 'image/png', '.png' : 'image/png',
'.gif' : 'image/gif', '.gif' : 'image/gif',
'.css' : 'text/css', '.css' : 'text/css',
'.scss' : 'text/x-sass',
'.gz' : 'application/gzip', '.gz' : 'application/gzip',
'.js' : 'text/javascript', '.js' : 'text/javascript',
'.txt' : 'text/plain', '.txt' : 'text/plain',
@@ -1134,24 +1365,46 @@ const server = http.createServer( ( request, response ) =>
response.setHeader( 'Content-type', fileMime ); response.setHeader( 'Content-type', fileMime );
response.end( data ); response.end( data );
Log.debug( `www`, chalk.green( ` [LOAD] ` ), chalk.white( `` ), chalk.grey( `asset:${ loadFile } mime:${ fileMime }` ) ); Log.ok( `www`, chalk.yellow( `[load]` ), chalk.white( `` ), chalk.blueBright( `<file>` ), chalk.gray( `${ loadFile }` ), chalk.blueBright( `<mime>` ), chalk.gray( `${ fileMime }` ) );
} }
else else
{ {
Log.error( `www file not found:`, chalk.white( `` ), chalk.grey( `${ request.url }` ) ); if ( loadFile === 'discovery.json' )
response.writeHead( 404, 'Not Found' ); {
response.end(); Log.notice( `www`, chalk.yellowBright( `[notice]` ), chalk.white( `` ), chalk.grey( `If you are attempting to load TVApp2 using an HDHomeRun tuner, please switch to the` ), chalk.yellowBright( `M3U Tuner` ) );
}
const statusCheck =
{
ip: envIpContainer,
gateway: envIpGateway,
uptime: process.uptime(),
message: 'Page not found',
status: 'healthy',
ref: request.url,
method: method || 'GET',
code: 404,
timestamp: Date.now()
};
response.writeHead( statusCheck.code, {
'Content-Type': 'application/json'
});
Log.error( `www`, chalk.redBright( `[error]` ), chalk.white( `` ), chalk.grey( `${ statusCheck.message }` ), chalk.redBright( `${ loadFile }` ), chalk.blueBright( `<statusCode>` ), chalk.gray( `${ statusCheck.code }` ) );
response.end( JSON.stringify( statusCheck ) );
} }
}); });
}; };
handleRequest().catch( ( err ) => handleRequest().catch( ( err ) =>
{ {
Log.error( `Error handling request:`, chalk.white( `` ), chalk.grey( `${ err }` ) );
response.writeHead( 500, { response.writeHead( 500, {
'Content-Type': 'text/plain' 'Content-Type': 'text/plain'
}); });
Log.error( `Error handling request:`, chalk.white( `` ), chalk.grey( `${ err }` ) );
response.end( 'Internal Server Error' ); response.end( 'Internal Server Error' );
}); });
}); });
@@ -1162,17 +1415,15 @@ const server = http.createServer( ( request, response ) =>
( async() => ( async() =>
{ {
const envWebIP = process.env.WEB_IP || '0.0.0.0'; if ( !envApiKey )
const envWebPort = process.env.WEB_PORT || `4124`; {
Log.warn( `core`, chalk.yellow( `[api]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `API_KEY environment variable not defined for api, leaving blank` ) );
Log.debug( `Assigned FILE_URL`, chalk.white( `` ), chalk.grey( `${ FILE_URL }` ) ); }
Log.debug( `Assigned FILE_M3U`, chalk.white( `` ), chalk.grey( `${ FILE_M3U }` ) );
Log.debug( `Assigned FILE_XML`, chalk.white( `` ), chalk.grey( `${ FILE_XML }` ) );
Log.debug( `Assigned FILE_TAR`, chalk.white( `` ), chalk.grey( `${ FILE_TAR }` ) );
await initialize(); await initialize();
server.listen( envWebPort, envWebIP, () => server.listen( envWebPort, envWebIP, () =>
{ {
Log.info( `Server is running on ${ envWebIP }:${ envWebPort }` ); Log.warn( `core`, chalk.yellow( `[init]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `server is now running on` ), chalk.whiteBright.bgBlack( ` ${ envWebIP }:${ envWebPort } ` ) );
}); });
})(); })();

View File

@@ -1,6 +1,6 @@
{ {
"name": "tvapp2", "name": "tvapp2",
"version": "1.2.0", "version": "1.4.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
@@ -132,13 +132,13 @@
} }
}, },
"node_modules/@eslint/plugin-kit": { "node_modules/@eslint/plugin-kit": {
"version": "0.2.7", "version": "0.2.8",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz",
"integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@eslint/core": "^0.12.0", "@eslint/core": "^0.13.0",
"levn": "^0.4.1" "levn": "^0.4.1"
}, },
"engines": { "engines": {
@@ -146,9 +146,9 @@
} }
}, },
"node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": {
"version": "0.12.0", "version": "0.13.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz",
"integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
@@ -276,6 +276,19 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/accepts": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
"integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
"license": "MIT",
"dependencies": {
"mime-types": "^3.0.0",
"negotiator": "^1.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.14.1", "version": "8.14.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
@@ -567,6 +580,38 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/body-parser": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
"integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
"license": "MIT",
"dependencies": {
"bytes": "^3.1.2",
"content-type": "^1.0.5",
"debug": "^4.4.0",
"http-errors": "^2.0.0",
"iconv-lite": "^0.6.3",
"on-finished": "^2.4.1",
"qs": "^6.14.0",
"raw-body": "^3.0.0",
"type-is": "^2.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/body-parser/node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -577,6 +622,15 @@
"concat-map": "0.0.1" "concat-map": "0.0.1"
} }
}, },
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/call-bind": { "node_modules/call-bind": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
@@ -600,7 +654,6 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
@@ -614,7 +667,6 @@
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"call-bind-apply-helpers": "^1.0.2", "call-bind-apply-helpers": "^1.0.2",
@@ -735,6 +787,45 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/content-disposition": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
"integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
"license": "MIT",
"dependencies": {
"safe-buffer": "5.2.1"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/content-type": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
"integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
"license": "MIT",
"engines": {
"node": ">=6.6.0"
}
},
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.6", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -808,7 +899,6 @@
"version": "4.4.0", "version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ms": "^2.1.3" "ms": "^2.1.3"
@@ -875,6 +965,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/didyoumean": { "node_modules/didyoumean": {
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@@ -899,7 +998,6 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"call-bind-apply-helpers": "^1.0.1", "call-bind-apply-helpers": "^1.0.1",
@@ -910,6 +1008,12 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"license": "MIT"
},
"node_modules/ejs": { "node_modules/ejs": {
"version": "3.1.10", "version": "3.1.10",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
@@ -932,6 +1036,15 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/enhanced-resolve": { "node_modules/enhanced-resolve": {
"version": "5.18.1", "version": "5.18.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
@@ -1033,7 +1146,6 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@@ -1043,7 +1155,6 @@
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@@ -1053,7 +1164,6 @@
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"es-errors": "^1.3.0" "es-errors": "^1.3.0"
@@ -1109,6 +1219,12 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"license": "MIT"
},
"node_modules/escape-string-regexp": { "node_modules/escape-string-regexp": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -1548,6 +1664,57 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
"license": "MIT",
"dependencies": {
"accepts": "^2.0.0",
"body-parser": "^2.2.0",
"content-disposition": "^1.0.0",
"content-type": "^1.0.5",
"cookie": "^0.7.1",
"cookie-signature": "^1.2.1",
"debug": "^4.4.0",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"etag": "^1.8.1",
"finalhandler": "^2.1.0",
"fresh": "^2.0.0",
"http-errors": "^2.0.0",
"merge-descriptors": "^2.0.0",
"mime-types": "^3.0.0",
"on-finished": "^2.4.1",
"once": "^1.4.0",
"parseurl": "^1.3.3",
"proxy-addr": "^2.0.7",
"qs": "^6.14.0",
"range-parser": "^1.2.1",
"router": "^2.2.0",
"send": "^1.1.0",
"serve-static": "^2.2.0",
"statuses": "^2.0.1",
"type-is": "^2.0.1",
"vary": "^1.1.2"
},
"engines": {
"node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/external-editor": { "node_modules/external-editor": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
@@ -1653,6 +1820,23 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/finalhandler": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
"integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.0",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"on-finished": "^2.4.1",
"parseurl": "^1.3.3",
"statuses": "^2.0.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/find-up": { "node_modules/find-up": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@@ -1707,11 +1891,28 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/fresh": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
"integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/function-bind": { "node_modules/function-bind": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true,
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
@@ -1762,7 +1963,6 @@
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"call-bind-apply-helpers": "^1.0.2", "call-bind-apply-helpers": "^1.0.2",
@@ -1787,7 +1987,6 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"dunder-proto": "^1.0.1", "dunder-proto": "^1.0.1",
@@ -1875,7 +2074,6 @@
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@@ -1946,7 +2144,6 @@
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@@ -1975,7 +2172,6 @@
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"function-bind": "^1.1.2" "function-bind": "^1.1.2"
@@ -1984,6 +2180,22 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/http-errors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
"license": "MIT",
"dependencies": {
"depd": "2.0.0",
"inherits": "2.0.4",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"toidentifier": "1.0.1"
},
"engines": {
"node": ">= 0.8"
}
},
"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",
@@ -2034,6 +2246,12 @@
"node": ">=0.8.19" "node": ">=0.8.19"
} }
}, },
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/inquirer": { "node_modules/inquirer": {
"version": "7.3.3", "version": "7.3.3",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz",
@@ -2091,6 +2309,15 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
"license": "MIT",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/is-array-buffer": { "node_modules/is-array-buffer": {
"version": "3.0.5", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@@ -2324,6 +2551,12 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/is-promise": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
"license": "MIT"
},
"node_modules/is-regex": { "node_modules/is-regex": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
@@ -2658,12 +2891,53 @@
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/media-typer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
"integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/merge-descriptors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
"integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/mime-db": {
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
"license": "MIT",
"dependencies": {
"mime-db": "^1.54.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mimic-fn": { "node_modules/mimic-fn": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
@@ -2709,7 +2983,6 @@
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/mute-stream": { "node_modules/mute-stream": {
@@ -2726,6 +2999,15 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/negotiator": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/node-fetch": { "node_modules/node-fetch": {
"version": "2.7.0", "version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
@@ -2751,7 +3033,6 @@
"version": "1.13.4", "version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@@ -2844,6 +3125,27 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
"license": "MIT",
"dependencies": {
"ee-first": "1.1.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"license": "ISC",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/onetime": { "node_modules/onetime": {
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
@@ -2961,6 +3263,15 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/path-exists": { "node_modules/path-exists": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -2988,6 +3299,15 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/path-to-regexp": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
"integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
"license": "MIT",
"engines": {
"node": ">=16"
}
},
"node_modules/pegjs": { "node_modules/pegjs": {
"version": "0.10.0", "version": "0.10.0",
"resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz", "resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz",
@@ -3016,8 +3336,6 @@
}, },
"node_modules/playwright": { "node_modules/playwright": {
"version": "1.51.1", "version": "1.51.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.51.1.tgz",
"integrity": "sha512-kkx+MB2KQRkyxjYPc3a0wLZZoDczmppyGJIvQ43l+aZihkaVvmu/21kiyaHeHjiFxjxNNFnUncKmcGIyOojsaw==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"playwright-core": "1.51.1" "playwright-core": "1.51.1"
@@ -3034,8 +3352,6 @@
}, },
"node_modules/playwright-core": { "node_modules/playwright-core": {
"version": "1.51.1", "version": "1.51.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.51.1.tgz",
"integrity": "sha512-/crRMj8+j/Nq5s8QcvegseuyeZPxpQCZb6HNk3Sos3BlZyAknRjoyJPFWkpNn8v0+P3WiwqFF8P+zQo4eqiNuw==",
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {
"playwright-core": "cli.js" "playwright-core": "cli.js"
@@ -3081,6 +3397,19 @@
"url": "https://github.com/prettier/prettier?sponsor=1" "url": "https://github.com/prettier/prettier?sponsor=1"
} }
}, },
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
"license": "MIT",
"dependencies": {
"forwarded": "0.2.0",
"ipaddr.js": "1.9.1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/punycode": { "node_modules/punycode": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -3091,6 +3420,57 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/qs": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/raw-body": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
"integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
"license": "MIT",
"dependencies": {
"bytes": "3.1.2",
"http-errors": "2.0.0",
"iconv-lite": "0.6.3",
"unpipe": "1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/raw-body/node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/reflect.getprototypeof": { "node_modules/reflect.getprototypeof": {
"version": "1.0.10", "version": "1.0.10",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
@@ -3214,6 +3594,22 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/router": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
"integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.0",
"depd": "^2.0.0",
"is-promise": "^4.0.0",
"parseurl": "^1.3.3",
"path-to-regexp": "^8.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/run-async": { "node_modules/run-async": {
"version": "2.4.1", "version": "2.4.1",
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
@@ -3257,6 +3653,26 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/safe-push-apply": { "node_modules/safe-push-apply": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
@@ -3296,7 +3712,6 @@
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/semver": { "node_modules/semver": {
@@ -3309,6 +3724,43 @@
"semver": "bin/semver.js" "semver": "bin/semver.js"
} }
}, },
"node_modules/send": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
"integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
"license": "MIT",
"dependencies": {
"debug": "^4.3.5",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"etag": "^1.8.1",
"fresh": "^2.0.0",
"http-errors": "^2.0.0",
"mime-types": "^3.0.1",
"ms": "^2.1.3",
"on-finished": "^2.4.1",
"range-parser": "^1.2.1",
"statuses": "^2.0.1"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/serve-static": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
"integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
"license": "MIT",
"dependencies": {
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"parseurl": "^1.3.3",
"send": "^1.2.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/set-blocking": { "node_modules/set-blocking": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
@@ -3365,6 +3817,12 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC"
},
"node_modules/shebang-command": { "node_modules/shebang-command": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -3392,7 +3850,6 @@
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
@@ -3412,7 +3869,6 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
@@ -3429,7 +3885,6 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"call-bound": "^1.0.2", "call-bound": "^1.0.2",
@@ -3448,7 +3903,6 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"call-bound": "^1.0.2", "call-bound": "^1.0.2",
@@ -3471,6 +3925,15 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/string-width": { "node_modules/string-width": {
"version": "4.2.3", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
@@ -3636,6 +4099,15 @@
"node": ">=0.6.0" "node": ">=0.6.0"
} }
}, },
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"license": "MIT",
"engines": {
"node": ">=0.6"
}
},
"node_modules/tr46": { "node_modules/tr46": {
"version": "0.0.3", "version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
@@ -3689,6 +4161,20 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/type-is": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
"integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
"license": "MIT",
"dependencies": {
"content-type": "^1.0.5",
"media-typer": "^1.1.0",
"mime-types": "^3.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/typed-array-buffer": { "node_modules/typed-array-buffer": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
@@ -3786,6 +4272,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/uri-js": { "node_modules/uri-js": {
"version": "4.4.1", "version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
@@ -3798,8 +4293,6 @@
}, },
"node_modules/user-agents": { "node_modules/user-agents": {
"version": "1.1.497", "version": "1.1.497",
"resolved": "https://registry.npmjs.org/user-agents/-/user-agents-1.1.497.tgz",
"integrity": "sha512-HF4bW97dI25MWBYY6U07OzGICTmtUfz2UK5KfgP/4DLtupQgCDe6WlL2WQfLk4I6hYDcv45/7ngTmJExYRdHiw==",
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"lodash.clonedeep": "^4.5.0" "lodash.clonedeep": "^4.5.0"
@@ -3819,6 +4312,15 @@
"uuid": "dist/esm/bin/uuid" "uuid": "dist/esm/bin/uuid"
} }
}, },
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/webidl-conversions": { "node_modules/webidl-conversions": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
@@ -3974,6 +4476,12 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
},
"node_modules/y18n": { "node_modules/y18n": {
"version": "4.0.3", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",

587
tvapp2/package-lock.json generated
View File

@@ -1,16 +1,17 @@
{ {
"name": "tvapp2", "name": "tvapp2",
"version": "1.2.0", "version": "1.4.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "tvapp2", "name": "tvapp2",
"version": "1.2.0", "version": "1.4.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"chalk": "^5.3.0", "chalk": "^5.3.0",
"ejs": "^3.1.10", "ejs": "^3.1.10",
"express": "5.1.0",
"moment": "2.30.1", "moment": "2.30.1",
"playwright": "^1.51.0", "playwright": "^1.51.0",
"user-agents": "^1.1.480" "user-agents": "^1.1.480"
@@ -159,13 +160,13 @@
} }
}, },
"node_modules/@eslint/plugin-kit": { "node_modules/@eslint/plugin-kit": {
"version": "0.2.7", "version": "0.2.8",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz",
"integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@eslint/core": "^0.12.0", "@eslint/core": "^0.13.0",
"levn": "^0.4.1" "levn": "^0.4.1"
}, },
"engines": { "engines": {
@@ -173,9 +174,9 @@
} }
}, },
"node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": {
"version": "0.12.0", "version": "0.13.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz",
"integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
@@ -303,6 +304,19 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/accepts": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
"integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
"license": "MIT",
"dependencies": {
"mime-types": "^3.0.0",
"negotiator": "^1.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.14.1", "version": "8.14.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
@@ -594,6 +608,38 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/body-parser": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
"integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
"license": "MIT",
"dependencies": {
"bytes": "^3.1.2",
"content-type": "^1.0.5",
"debug": "^4.4.0",
"http-errors": "^2.0.0",
"iconv-lite": "^0.6.3",
"on-finished": "^2.4.1",
"qs": "^6.14.0",
"raw-body": "^3.0.0",
"type-is": "^2.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/body-parser/node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -604,6 +650,15 @@
"concat-map": "0.0.1" "concat-map": "0.0.1"
} }
}, },
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/call-bind": { "node_modules/call-bind": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
@@ -627,7 +682,6 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
@@ -641,7 +695,6 @@
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"call-bind-apply-helpers": "^1.0.2", "call-bind-apply-helpers": "^1.0.2",
@@ -762,6 +815,45 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/content-disposition": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
"integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
"license": "MIT",
"dependencies": {
"safe-buffer": "5.2.1"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/content-type": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
"integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
"license": "MIT",
"engines": {
"node": ">=6.6.0"
}
},
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.6", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -835,7 +927,6 @@
"version": "4.4.0", "version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ms": "^2.1.3" "ms": "^2.1.3"
@@ -902,6 +993,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/didyoumean": { "node_modules/didyoumean": {
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@@ -926,7 +1026,6 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"call-bind-apply-helpers": "^1.0.1", "call-bind-apply-helpers": "^1.0.1",
@@ -937,6 +1036,12 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"license": "MIT"
},
"node_modules/ejs": { "node_modules/ejs": {
"version": "3.1.10", "version": "3.1.10",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
@@ -959,6 +1064,15 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/enhanced-resolve": { "node_modules/enhanced-resolve": {
"version": "5.18.1", "version": "5.18.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
@@ -1060,7 +1174,6 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@@ -1070,7 +1183,6 @@
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@@ -1080,7 +1192,6 @@
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"es-errors": "^1.3.0" "es-errors": "^1.3.0"
@@ -1136,6 +1247,12 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"license": "MIT"
},
"node_modules/escape-string-regexp": { "node_modules/escape-string-regexp": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -1575,6 +1692,57 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
"license": "MIT",
"dependencies": {
"accepts": "^2.0.0",
"body-parser": "^2.2.0",
"content-disposition": "^1.0.0",
"content-type": "^1.0.5",
"cookie": "^0.7.1",
"cookie-signature": "^1.2.1",
"debug": "^4.4.0",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"etag": "^1.8.1",
"finalhandler": "^2.1.0",
"fresh": "^2.0.0",
"http-errors": "^2.0.0",
"merge-descriptors": "^2.0.0",
"mime-types": "^3.0.0",
"on-finished": "^2.4.1",
"once": "^1.4.0",
"parseurl": "^1.3.3",
"proxy-addr": "^2.0.7",
"qs": "^6.14.0",
"range-parser": "^1.2.1",
"router": "^2.2.0",
"send": "^1.1.0",
"serve-static": "^2.2.0",
"statuses": "^2.0.1",
"type-is": "^2.0.1",
"vary": "^1.1.2"
},
"engines": {
"node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/external-editor": { "node_modules/external-editor": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
@@ -1680,6 +1848,23 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/finalhandler": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
"integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.0",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"on-finished": "^2.4.1",
"parseurl": "^1.3.3",
"statuses": "^2.0.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/find-up": { "node_modules/find-up": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@@ -1734,25 +1919,28 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/fsevents": { "node_modules/forwarded": {
"version": "2.3.2", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
"hasInstallScript": true,
"license": "MIT", "license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": { "engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0" "node": ">= 0.6"
}
},
"node_modules/fresh": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
"integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
} }
}, },
"node_modules/function-bind": { "node_modules/function-bind": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true,
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
@@ -1803,7 +1991,6 @@
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"call-bind-apply-helpers": "^1.0.2", "call-bind-apply-helpers": "^1.0.2",
@@ -1828,7 +2015,6 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"dunder-proto": "^1.0.1", "dunder-proto": "^1.0.1",
@@ -1916,7 +2102,6 @@
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@@ -1987,7 +2172,6 @@
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@@ -2016,7 +2200,6 @@
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"function-bind": "^1.1.2" "function-bind": "^1.1.2"
@@ -2025,6 +2208,22 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/http-errors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
"license": "MIT",
"dependencies": {
"depd": "2.0.0",
"inherits": "2.0.4",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"toidentifier": "1.0.1"
},
"engines": {
"node": ">= 0.8"
}
},
"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",
@@ -2075,6 +2274,12 @@
"node": ">=0.8.19" "node": ">=0.8.19"
} }
}, },
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/inquirer": { "node_modules/inquirer": {
"version": "7.3.3", "version": "7.3.3",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz",
@@ -2132,6 +2337,15 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
"license": "MIT",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/is-array-buffer": { "node_modules/is-array-buffer": {
"version": "3.0.5", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@@ -2365,6 +2579,12 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/is-promise": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
"license": "MIT"
},
"node_modules/is-regex": { "node_modules/is-regex": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
@@ -2699,12 +2919,53 @@
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/media-typer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
"integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/merge-descriptors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
"integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/mime-db": {
"version": "1.54.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
"license": "MIT",
"dependencies": {
"mime-db": "^1.54.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mimic-fn": { "node_modules/mimic-fn": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
@@ -2750,7 +3011,6 @@
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/mute-stream": { "node_modules/mute-stream": {
@@ -2767,6 +3027,15 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/negotiator": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/node-fetch": { "node_modules/node-fetch": {
"version": "2.7.0", "version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
@@ -2792,7 +3061,6 @@
"version": "1.13.4", "version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@@ -2885,6 +3153,27 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
"license": "MIT",
"dependencies": {
"ee-first": "1.1.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"license": "ISC",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/onetime": { "node_modules/onetime": {
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
@@ -3002,6 +3291,15 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/path-exists": { "node_modules/path-exists": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -3029,6 +3327,15 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/path-to-regexp": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
"integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
"license": "MIT",
"engines": {
"node": ">=16"
}
},
"node_modules/pegjs": { "node_modules/pegjs": {
"version": "0.10.0", "version": "0.10.0",
"resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz", "resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz",
@@ -3057,8 +3364,6 @@
}, },
"node_modules/playwright": { "node_modules/playwright": {
"version": "1.51.1", "version": "1.51.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.51.1.tgz",
"integrity": "sha512-kkx+MB2KQRkyxjYPc3a0wLZZoDczmppyGJIvQ43l+aZihkaVvmu/21kiyaHeHjiFxjxNNFnUncKmcGIyOojsaw==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"playwright-core": "1.51.1" "playwright-core": "1.51.1"
@@ -3075,8 +3380,6 @@
}, },
"node_modules/playwright-core": { "node_modules/playwright-core": {
"version": "1.51.1", "version": "1.51.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.51.1.tgz",
"integrity": "sha512-/crRMj8+j/Nq5s8QcvegseuyeZPxpQCZb6HNk3Sos3BlZyAknRjoyJPFWkpNn8v0+P3WiwqFF8P+zQo4eqiNuw==",
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {
"playwright-core": "cli.js" "playwright-core": "cli.js"
@@ -3122,6 +3425,19 @@
"url": "https://github.com/prettier/prettier?sponsor=1" "url": "https://github.com/prettier/prettier?sponsor=1"
} }
}, },
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
"license": "MIT",
"dependencies": {
"forwarded": "0.2.0",
"ipaddr.js": "1.9.1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/punycode": { "node_modules/punycode": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -3132,6 +3448,57 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/qs": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/raw-body": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
"integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
"license": "MIT",
"dependencies": {
"bytes": "3.1.2",
"http-errors": "2.0.0",
"iconv-lite": "0.6.3",
"unpipe": "1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/raw-body/node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/reflect.getprototypeof": { "node_modules/reflect.getprototypeof": {
"version": "1.0.10", "version": "1.0.10",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
@@ -3255,6 +3622,22 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/router": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
"integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.0",
"depd": "^2.0.0",
"is-promise": "^4.0.0",
"parseurl": "^1.3.3",
"path-to-regexp": "^8.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/run-async": { "node_modules/run-async": {
"version": "2.4.1", "version": "2.4.1",
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
@@ -3298,6 +3681,26 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/safe-push-apply": { "node_modules/safe-push-apply": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
@@ -3337,7 +3740,6 @@
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/semver": { "node_modules/semver": {
@@ -3350,6 +3752,43 @@
"semver": "bin/semver.js" "semver": "bin/semver.js"
} }
}, },
"node_modules/send": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
"integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
"license": "MIT",
"dependencies": {
"debug": "^4.3.5",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"etag": "^1.8.1",
"fresh": "^2.0.0",
"http-errors": "^2.0.0",
"mime-types": "^3.0.1",
"ms": "^2.1.3",
"on-finished": "^2.4.1",
"range-parser": "^1.2.1",
"statuses": "^2.0.1"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/serve-static": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
"integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
"license": "MIT",
"dependencies": {
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"parseurl": "^1.3.3",
"send": "^1.2.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/set-blocking": { "node_modules/set-blocking": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
@@ -3406,6 +3845,12 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC"
},
"node_modules/shebang-command": { "node_modules/shebang-command": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -3433,7 +3878,6 @@
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
@@ -3453,7 +3897,6 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
@@ -3470,7 +3913,6 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"call-bound": "^1.0.2", "call-bound": "^1.0.2",
@@ -3489,7 +3931,6 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"call-bound": "^1.0.2", "call-bound": "^1.0.2",
@@ -3512,6 +3953,15 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/string-width": { "node_modules/string-width": {
"version": "4.2.3", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
@@ -3677,6 +4127,15 @@
"node": ">=0.6.0" "node": ">=0.6.0"
} }
}, },
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"license": "MIT",
"engines": {
"node": ">=0.6"
}
},
"node_modules/tr46": { "node_modules/tr46": {
"version": "0.0.3", "version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
@@ -3730,6 +4189,20 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/type-is": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
"integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
"license": "MIT",
"dependencies": {
"content-type": "^1.0.5",
"media-typer": "^1.1.0",
"mime-types": "^3.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/typed-array-buffer": { "node_modules/typed-array-buffer": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
@@ -3827,6 +4300,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/uri-js": { "node_modules/uri-js": {
"version": "4.4.1", "version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
@@ -3839,8 +4321,6 @@
}, },
"node_modules/user-agents": { "node_modules/user-agents": {
"version": "1.1.497", "version": "1.1.497",
"resolved": "https://registry.npmjs.org/user-agents/-/user-agents-1.1.497.tgz",
"integrity": "sha512-HF4bW97dI25MWBYY6U07OzGICTmtUfz2UK5KfgP/4DLtupQgCDe6WlL2WQfLk4I6hYDcv45/7ngTmJExYRdHiw==",
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"lodash.clonedeep": "^4.5.0" "lodash.clonedeep": "^4.5.0"
@@ -3860,6 +4340,15 @@
"uuid": "dist/esm/bin/uuid" "uuid": "dist/esm/bin/uuid"
} }
}, },
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/webidl-conversions": { "node_modules/webidl-conversions": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
@@ -4015,6 +4504,12 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
},
"node_modules/y18n": { "node_modules/y18n": {
"version": "4.0.3", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",

View File

@@ -1,6 +1,6 @@
{ {
"name": "tvapp2", "name": "tvapp2",
"version": "1.2.0", "version": "1.4.0",
"description": "This package allows you to generate M3U playlists and EPG guides from various online IPTV services.", "description": "This package allows you to generate M3U playlists and EPG guides from various online IPTV services.",
"author": "BinaryNinja", "author": "BinaryNinja",
"license": "MIT", "license": "MIT",
@@ -76,7 +76,8 @@
"user-agents": "^1.1.480", "user-agents": "^1.1.480",
"chalk": "^5.3.0", "chalk": "^5.3.0",
"ejs": "^3.1.10", "ejs": "^3.1.10",
"moment": "2.30.1" "moment": "2.30.1",
"express": "5.1.0"
}, },
"devDependencies": { "devDependencies": {
"@types/uuid": "^10.0.0", "@types/uuid": "^10.0.0",
@@ -94,10 +95,10 @@
"node": ">=20" "node": ">=20"
}, },
"files": [ "files": [
"root", "root",
"LICENSE", "LICENSE",
"README.md", "README.md",
"index.js", "index.js",
"manifest.json" "manifest.json"
] ]
} }

View File

@@ -1,8 +1,55 @@
/*
Boostrap 5 > Table
*/
.table
{
--bs-table-bg: #1b1b1b;
--bs-table-border-color: #313131;
}
.table-striped > tbody > tr:nth-of-type(2n+1) > *
{
--bs-table-bg-type: #242424;
}
/*
Boostrap 5 > Tooltips
*/
.tooltip-inner
{
background-color: #3061a1;
box-shadow: 0px 0px 4px black;
opacity: 1;
color: #FFF;
}
.tooltip.bs-tooltip-right .tooltip-arrow::before
{
border-right-color: #3061a1 !important;
}
.tooltip.bs-tooltip-left .tooltip-arrow::before
{
border-left-color: #3061a1 !important;
}
.tooltip.bs-tooltip-bottom .tooltip-arrow::before
{
border-bottom-color: #3061a1 !important;
}
.tooltip.bs-tooltip-top .tooltip-arrow::before
{
border-top-color: #3061a1 !important;
}
body body
{ {
background-color: #f8f9fa; background-color: #f8f9fa;
padding-bottom: 20px; padding-bottom: 20px;
overflow: auto; overflow: auto;
} }
@media (prefers-color-scheme: dark) @media (prefers-color-scheme: dark)
@@ -32,13 +79,45 @@ body
to { opacity: 1; } to { opacity: 1; }
} }
@keyframes fade-in-dimmer
{
from { opacity: 0; }
to { opacity: 0.90; }
}
@keyframes fade-in-dimmer-repeat
{
from { opacity: 0.90; }
to { opacity: 0.7; }
}
@keyframes scale-in @keyframes scale-in
{ {
from { from {
transform: scale(1, 1); transform: scale(1, 1);
} }
to { to {
transform: scale(1.1, 1.1); transform: scale(1.5, 1.5);
}
}
@keyframes spin-scale
{
to {
transform: rotate(0deg) scale(1, 1);
}
from {
transform: rotate(360deg) scale(1.5, 1.5);
}
}
@keyframes spin
{
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
} }
} }
@@ -105,18 +184,6 @@ p
margin-left: auto; margin-left: auto;
} }
.header .logo
{
animation-name: fade-in, scale-in;
animation-duration: 1s, 0.5s;
animation-timing-function: ease-in, linear;
animation-direction: alternate, alternate;
animation-iteration-count: infinite, 1;
transition: all 0.3s;
opacity: 0.5;
transform: scale(1.1);
}
.footer .footer
{ {
position: absolute; position: absolute;
@@ -129,8 +196,8 @@ p
.footer-inner .footer-inner
{ {
margin: 0; margin: 0;
padding-bottom: 20px; padding-bottom: 10px;
padding-top: 20px; padding-top: 10px;
background-color: #151515; background-color: #151515;
} }
@@ -155,7 +222,6 @@ p
{ {
background-color: #a82147; background-color: #a82147;
color: #fff; color: #fff;
height: 55px;
} }
.header .navbar-brand .header .navbar-brand
@@ -173,6 +239,12 @@ p
padding-left: 7px; padding-left: 7px;
} }
.header .health
{
color: #ff7575;
text-decoration: none;
}
#breadcrumbs::before #breadcrumbs::before
{ {
margin-top: 4px; margin-top: 4px;
@@ -275,12 +347,11 @@ p
.navbar-social svg .navbar-social svg
{ {
font-size: clamp(0.7em, 2vw, 1.1em); font-size: clamp(0.7em, 2vw, 1.0em);
padding-top: 2px; margin-left: 10px;
padding-left: 10px;
} }
.navbar-social svg:hover .logo
{ {
animation-name: fade-in, scale-in; animation-name: fade-in, scale-in;
animation-duration: 1s, 0.5s; animation-duration: 1s, 0.5s;
@@ -288,19 +359,68 @@ p
animation-direction: alternate, alternate; animation-direction: alternate, alternate;
animation-iteration-count: infinite, 1; animation-iteration-count: infinite, 1;
transition: all 0.3s; transition: all 0.3s;
opacity: 0.5;
transform: scale(1.2);
}
.logo:hover
{
animation-name: scale-in, spin;
animation-duration: 1s, 0.5s;
animation-timing-function: ease-in, linear;
animation-direction: alternate, alternate;
animation-iteration-count: infinite, 1;
transition: all 0.3s;
opacity: 1; opacity: 1;
transform: scale(1.8); transform: scale(1.8);
} }
.restart
{
animation-name: fade-in, scale-in;
animation-duration: 1s, 0.5s;
animation-timing-function: linear, linear;
animation-direction: alternate, alternate;
animation-iteration-count: infinite, 1;
transition: all 0.3s;
opacity: 0.5;
transform: scale(1.2);
}
.restart:hover
{
animation-name: spin-scale;
animation-duration: 1s;
animation-delay: 0s;
animation-timing-function: linear;
animation-direction: alternate;
animation-iteration-count: infinite;
transition: all 0.3s;
opacity: 1;
}
.spin
{
transition-property: transform;
transition-duration: 0.5s;
animation-name: spin;
animation-duration: 0.5s;
animation-iteration-count: infinite;
animation-timing-function: linear;
}
.table thead th a .table thead th a
{ {
color: #9b9b9b !important; color: #9b9b9b !important;
font-weight: normal; font-weight: normal;
} }
.table td, .table th .table td, .table th
{ {
padding: .75rem; padding: .35rem;
vertical-align: baseline; vertical-align: baseline;
border-top: 0px solid #dee2e6; border-top: 0px solid #dee2e6;
font-size: clamp(0.5em, 1vw, 0.8em); font-size: clamp(0.5em, 1vw, 0.8em);
@@ -309,17 +429,13 @@ p
.table thead tr .table thead tr
{ {
border-bottom: 2px solid #575757; border-bottom: 2px solid #325eb3;
background-color: #181818;
color: #717171;
} }
.table thead th .table thead th
{ {
vertical-align: bottom; vertical-align: bottom;
border-bottom: 0px solid #575757; border-bottom: 0px solid #325eb3;
font-size: clamp(0.5em, 1vw, 0.8em);
line-height: 2.5vmin;
} }
.table-hover tbody tr:hover .table-hover tbody tr:hover
@@ -333,27 +449,63 @@ p
color: #d0c273; color: #d0c273;
} }
#warning-firewall .ntfy
{
padding-top: 1.5vh;
padding-bottom: 1.5vh;
padding-left: 2vh;
padding-right: 2vh;
margin-bottom: 1vh;
line-height: 25px;
width: 100%;
}
#ntfy-restart
{
background-color: #1a1a1a;
font-size: clamp(0.5em, 1vw, 0.8em);
font-weight: 100;
border: 1px dashed #9e973a;
display: none;
z-index: 5000 !important;
}
#ntfy-firewall
{ {
background-color: #1A1A1A; background-color: #1A1A1A;
padding: 2vh;
margin-bottom: 1vh;
font-size: clamp(0.5em, 1vw, 0.8em); font-size: clamp(0.5em, 1vw, 0.8em);
font-weight: 100; font-weight: 100;
border: 1px dashed #FF6C00; border: 1px dashed #FF6C00;
width: 100%; display: none;
line-height: 25px;
} }
#warning-localhost #ntfy-localhost
{ {
background-color: #1A1A1A; background-color: #1A1A1A;
padding: 2vh;
font-size: clamp(0.5em, 1vw, 0.8em); font-size: clamp(0.5em, 1vw, 0.8em);
font-weight: 100; font-weight: 100;
border: 1px dashed #FF0048; border: 1px dashed #FF0048;
width: 100%; display: none;
line-height: 25px; }
span.success
{
color: #FFF;
background-color: #97950A;
padding-left: 7px;
padding-right: 7px;
padding-top: 2px;
padding-bottom: 2px;
font-family: SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;
margin-right: 8px;
animation-name: fade-in, scale-in;
animation-duration: 1s, 0.5s;
animation-timing-function: ease-in, linear;
animation-direction: alternate, alternate;
animation-iteration-count: infinite, 1;
transition: all 0.3s;
opacity: 0.5;
transform: scale(1.1);
} }
span.notice span.notice
@@ -399,8 +551,8 @@ span.warning
code code
{ {
font-size: 96%; font-size: 96%;
color: #ff4985; color: #ff4985 !important;
word-break: break-word; word-break: break-word !important;
padding-right: 5px; padding-right: 5px;
padding-left: 4px; padding-left: 4px;
} }
@@ -428,7 +580,7 @@ code
top: 0; top: 0;
right: 0; right: 0;
left: 0; left: 0;
z-index: 999; z-index: 200;
} }
.fixed-bottom .fixed-bottom
@@ -437,7 +589,7 @@ code
right: 0; right: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
z-index: 999; z-index: 200;
} }
.sticky-top .sticky-top
@@ -446,16 +598,115 @@ code
{ {
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 999; z-index: 99999;
} }
} }
.sticky-bottom
{
position: -webkit-sticky;
position: relative !important;
bottom: 0;
z-index: 500 !important;
}
.sticky-bottom .sticky-bottom
{ {
@supports (position: sticky) @supports (position: sticky)
{ {
position: sticky; position: sticky;
bottom: 0; bottom: 0;
z-index: 999; z-index: 200;
} }
} }
.aboveDimmer
{
z-index: 301;
position: relative;
}
@keyframes heartbeat
{
0%
{
transform: scale(1.2);
}
20%
{
transform: scale(1.5);
}
40%
{
transform: scale(1.2);
}
60%
{
transform: scale(1.5);
}
80%
{
transform: scale(1.2);
}
100%
{
transform: scale(1.2);
}
}
.heart
{
position: relative;
animation: heartbeat 1s infinite;
}
@-webkit-keyframes fadeOut {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@keyframes fadeOut {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
.dimmer-out
{
transition: opacity 1s ease-in-out !important;
opacity: 0 !important;
}
.dimmer-in
{
opacity: 0.7;
animation-name: fade-in-dimmer, fade-in-dimmer-repeat;
animation-delay: 0s, 0.7s;
animation-duration: 0.7s, 1s;
animation-timing-function: ease-in, linear;
animation-direction: alternate, alternate;
animation-iteration-count: 1, infinite;
transition: opacity 250ms ease-in, visibility 0ms ease-in 250ms;
}
#dimmer
{
z-index: 200;
position: fixed;
background-color: #000000;
width: 100%;
float: left;
height: 100%;
visibility: hidden;
top: 0px;
left: 0px;
}

View File

@@ -1,40 +1,48 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en" data-bs-theme="dark">
<head> <head>
<title><%= appName %> - v<%= appVersion %></title>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title><%= appName %> - v<%= appVersion %></title>
<meta name="robots" content="noindex, nofollow"> <meta name="robots" content="noindex, nofollow">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/css/bootstrap.min.css" integrity="sha384-SgOJa3DmI69IUzQ2PVdRZhwQ+dy64/BUtbMJw1MZ8t5HZApcHrRKUc4W0kG879m7" crossorigin="anonymous">
<link rel="stylesheet" href="css/tvapp2.min.css"> <link rel="stylesheet" href="css/tvapp2.min.css">
<link rel="icon" type="image/x-icon" href="favicon.ico"> <link rel="icon" type="image/x-icon" href="favicon.ico">
<script src='https://cdn.jsdelivr.net/npm/bootstrap@5.3.5/dist/js/bootstrap.bundle.min.js' integrity='sha384-k6d4wzSIapyDyv1kpU366/PK5hCdSbCRGRCMv+eplOQJWyd1fbcAu9OCUj5zNLiq' crossorigin='anonymous'></script>
<script src=' https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js '></script>
<script src='js/tvapp2.min.js'></script>
</head> </head>
<body> <body>
<!-- Header -->
<div class="header"> <div class="header">
<nav class="navbar sticky-top container"> <nav class="navbar sticky-top container">
<div class="navbar-brand"> <div class="navbar-brand">
<i class="logo fa-sharp-duotone fa-regular fa-tv" style="--fa-primary-color: rgb(255, 255, 255); --fa-secondary-color: rgb(255, 255, 255);" aria-hidden="true"></i> <i data-bs-toggle="tooltip" title="v<%= appVersion %>" class="logo fa-sharp-duotone fa-regular fa-tv" style="--fa-primary-color: rgb(255, 255, 255); --fa-secondary-color: rgb(255, 255, 255);" aria-hidden="true"></i>
<a class="header-name" href="<%= appUrlGithub %>">TVApp2 for Docker</a> <a class="header-name" href="<%= appUrlGithub %>">TVApp2 for Docker</a>
</div> </div>
<div class="navbar-social"> <div class="navbar-social">
<a href="<%= appUrlDocs %>"><i class="logo fa-duotone fa-solid fa-book-open-cover" style="--fa-primary-color: rgb(255, 255, 255); --fa-secondary-color: rgb(255, 255, 255);" aria-hidden="true"></i></a> <a href=""><i id="action-health" data-bs-toggle="tooltip" title="Health" class="heart logo health fa-duotone fa-solid fa-heart" style="--fa-primary-color: rgb(255, 255, 255); --fa-secondary-color: rgb(255, 255, 255);" aria-hidden="true"></i></a>
<a href="<%= appUrlGithub %>"><i class="logo fa-logos fa-github" style="--fa-primary-color: rgb(255, 255, 255); --fa-secondary-color: rgb(255, 255, 255);" aria-hidden="true"></i></a> <a href="javascript:runResync();"><i id="action-resync" data-bs-toggle="tooltip" title="Resync" class="restart fa-solid fa-rotate" style="--fa-primary-color: rgb(255, 255, 255); --fa-secondary-color: rgb(255, 255, 255);" aria-hidden="true"></i></a>
<a href="<%= appUrlDiscord %>"><i class="logo fa-logos fa-discord" style="--fa-primary-color: rgb(255, 255, 255); --fa-secondary-color: rgb(255, 255, 255);" aria-hidden="true"></i></a> <a href="<%= appUrlDocs %>"><i data-bs-toggle="tooltip" title="Documentation" class="logo fa-duotone fa-solid fa-book-open-cover" style="--fa-primary-color: rgb(255, 255, 255); --fa-secondary-color: rgb(255, 255, 255);" aria-hidden="true"></i></a>
<a href="<%= appUrlGithub %>"><i data-bs-toggle="tooltip" title="Github" class="logo fa-logos fa-github" style="--fa-primary-color: rgb(255, 255, 255); --fa-secondary-color: rgb(255, 255, 255);" aria-hidden="true"></i></a>
<a href="<%= appUrlDiscord %>"><i data-bs-toggle="tooltip" title="Discord" class="logo fa-logos fa-discord" style="--fa-primary-color: rgb(255, 255, 255); --fa-secondary-color: rgb(255, 255, 255);" aria-hidden="true"></i></a>
</div> </div>
</nav> </nav>
</div> </div>
<!-- Header Notification: description -->
<div class="container"> <div class="container">
<div class="container header-container"> <div class="container header-container">
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<div class="about">This page displays your most recent copies of the <code><%= fileM3U %></code> playlist and <code><%= fileXML %></code> EPG guide data. Right-click each file, select <span class="text-accent">Copy Link</span> and paste the URLs within an IPTV app such as Jellyfin. The <code><%= fileXML %></code> and <code><%= fileTAR %></code> have identical guide data, however the <code><%= fileTAR %></code> is compressed and will import into your IPTV application much faster.</div> <div class="about">This page displays your most recent copies of the <code><%= fileM3U %></code> playlist and <code><%= fileXML %></code> EPG guide data. Right-click each file, select <span class="text-accent">Copy Link</span> and paste the URLs within an IPTV app such as Jellyfin. The <code><%= fileXML %></code> and <code><%= fileGZP %></code> have identical guide data, however the <code><%= fileGZP %></code> is compressed and will import into your IPTV application much faster.</div>
</div> </div>
</div> </div>
</div> </div>
<!-- Header Fontawesome Icons -->
<div class="container main-container"> <div class="container main-container">
<table id="list" class="table table-sm table-hover"> <table id="list" class="table table-dark table-striped">
<thead> <thead>
<tr class="d-none d-md-table-row"> <tr class="d-none d-md-table-row">
<td class="icon cell-icon"></td> <td class="icon cell-icon"></td>
@@ -64,7 +72,7 @@
<!-- <i class="fa fa-fw fa-solid fa-file-lines" aria-hidden="true"></i> --> <!-- <i class="fa fa-fw fa-solid fa-file-lines" aria-hidden="true"></i> -->
</td> </td>
<td class="file cell-file"> <td class="file cell-file">
<a id="m3u-name" target="_blank"></a> <a id="m3u-name" target="_blank" data-bs-toggle="tooltip" title="IPTV channel list"></a>
</td> </td>
<td class="link cell-link"><a id="m3u-link" target="_blank"></a></td> <td class="link cell-link"><a id="m3u-link" target="_blank"></a></td>
<td class="size cell-size"><span id="m3u-size"></span></td> <td class="size cell-size"><span id="m3u-size"></span></td>
@@ -79,7 +87,7 @@
<!-- <i class="fa fa-fw fa-solid fa-file-lines" aria-hidden="true"></i> --> <!-- <i class="fa fa-fw fa-solid fa-file-lines" aria-hidden="true"></i> -->
</td> </td>
<td class="file cell-file"> <td class="file cell-file">
<a id="xml-name" target="_blank"></a> <a id="xml-name" target="_blank" data-bs-toggle="tooltip" title="Uncompressed TV guide data"></a>
</td> </td>
<td class="link cell-link"><a id="xml-link" target="_blank"></a></td> <td class="link cell-link"><a id="xml-link" target="_blank"></a></td>
<td class="size cell-size"><span id="xml-size"></span></td> <td class="size cell-size"><span id="xml-size"></span></td>
@@ -93,12 +101,12 @@
</svg> </svg>
<!-- <i class="fa fa-fw fa-solid fa-file-lines" aria-hidden="true"></i> --> <!-- <i class="fa fa-fw fa-solid fa-file-lines" aria-hidden="true"></i> -->
</td> </td>
<td class="file cell-tar"> <td class="file cell-gzp">
<a id="tar-name" target="_blank"></a> <a id="gzp-name" target="_blank" data-bs-toggle="tooltip" title="Compressed TV guide data"></a>
</td> </td>
<td class="link cell-link"><a id="tar-link" target="_blank"></a></td> <td class="link cell-link"><a id="gzp-link" target="_blank"></a></td>
<td class="size cell-size"><span id="tar-size"></span></td> <td class="size cell-size"><span id="gzp-size"></span></td>
<td class="date cell-date"><span id="tar-date"></span></td> <td class="date cell-date"><span id="gzp-date"></span></td>
<td class="desc cell-desc">XML / EPG guide data <code>(compressed)</code></td> <td class="desc cell-desc">XML / EPG guide data <code>(compressed)</code></td>
</tr> </tr>
</tbody> </tbody>
@@ -106,78 +114,394 @@
</div> </div>
</div> </div>
<!-- Footer -->
<footer class="footer"> <footer class="footer">
<div class="container" style="padding-bottom:20px;"> <div class="container" style="padding-bottom:20px;">
<div id="warning-firewall" class="sticky-bottom"></div> <div id="ntfy-restart" class="ntfy ntfy-success sticky-bottom"></div>
<div id="warning-localhost" class="sticky-bottom"></div> <div id="ntfy-firewall" class="ntfy ntfy-warning sticky-bottom"></div>
<div id="ntfy-localhost" class="ntfy ntfy-danger sticky-bottom"></div>
</div> </div>
<div class="footer-inner"> <div class="footer-inner">
<div class="container"> <div class="container">
<div class="col text-center text-muted text-small text-nowrap"> <div class="col text-center text-muted text-small text-nowrap">
<small>Developed by BinaryNinja - <a href="<%= appUrlGithub %>"><%= appName %></a> - v<%= appVersion %></small><br /> <small>Developed by BinaryNinja - <a data-bs-toggle="tooltip" title="<%= appRelease %> build" href="<%= appUrlGithub %>"><%= appName %> (<%= appRelease %>)</a> - v<%= appVersion %></small><br />
<small>This utility is for educational purposes only</small> <small>This utility is for educational purposes only</small>
</div> </div>
</div> </div>
</div> </div>
</footer> </footer>
<!-- Toast Notifications -->
<!-- <button type="button" class="btn btn-primary" id="btnTestToasts">Show toast</button> -->
<div style="z-index: 9999;" class="toast position-fixed bottom-0 end-0 p-8 m-3" id="tvapp2Toast" role="alert" aria-live="assertive" aria-atomic="true" data-bs-autohide="true" data-bs-delay="4000">
<div class="toast-body">
<div class="d-flex gap-4">
<span><i class="fa-solid fa-circle-check fa-lg icon-success"></i></span>
<div class="d-flex flex-column flex-grow-1 gap-2">
<div class="d-flex align-items-center">
<span id="toast-title" class="fw-semibold">Toast Title</span>
<button type="button" class="btn-close btn-close-sm ms-auto" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<span id="toast-message">Dismiss in 6 seconds</span>
</div>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="modalTvapp2" tabindex="-1" data-bs-backdrop="static" aria-labelledby="modalTvapp2Label" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalTvapp2Label">Modal title</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
...
</div>
<div class="modal-footer">
<button type="button" id="btn-secondary" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" id="btn-primary" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>
<script> <script>
/*
this is test code. enable the "Show Toast" button and then uncomment this code.
document.getElementById("btnTestToasts").onclick = function()
{
var toastElList = [].slice.call(document.querySelectorAll('.toast'))
var toastList = toastElList.map(function(toastEl)
{
return new bootstrap.Toast(toastEl)
});
toastList.forEach(toast => toast.show());
console.log(toastList);
};
*/
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl)
})
const urlBase = window.location.origin; const urlBase = window.location.origin;
const urlM3U = urlBase + "/playlist"; const urlM3U = urlBase + '/playlist';
const urlXML = urlBase + "/epg"; const urlXML = urlBase + '/epg';
const urlTAR = urlBase + "/gzip"; const urlGZP = urlBase + '/gzip';
document.getElementById("m3u-name").textContent = "<%= fileM3U %>"; document.getElementById('m3u-name').textContent = '<%= fileM3U %>';
document.getElementById("m3u-name").href = urlM3U; document.getElementById('m3u-name').href = urlM3U;
document.getElementById("m3u-link").textContent = urlM3U; document.getElementById('m3u-link').textContent = urlM3U;
document.getElementById("m3u-link").href = urlM3U; document.getElementById('m3u-link').href = urlM3U;
document.getElementById("m3u-size").textContent = "<%= sizeM3U %>"; document.getElementById('m3u-size').textContent = '<%= sizeM3U %>';
document.getElementById("m3u-date").textContent = "<%= dateM3U %>"; document.getElementById('m3u-date').textContent = '<%= dateM3U %>';
document.getElementById("xml-name").textContent = "<%= fileXML %>"; document.getElementById('xml-name').textContent = '<%= fileXML %>';
document.getElementById("xml-name").href = urlXML; document.getElementById('xml-name').href = urlXML;
document.getElementById("xml-link").textContent = urlXML; document.getElementById('xml-link').textContent = urlXML;
document.getElementById("xml-link").href = urlXML; document.getElementById('xml-link').href = urlXML;
document.getElementById("xml-size").textContent = "<%= sizeXML %>"; document.getElementById('xml-size').textContent = '<%= sizeXML %>';
document.getElementById("xml-date").textContent = "<%= dateXML %>"; document.getElementById('xml-date').textContent = '<%= dateXML %>';
document.getElementById("tar-name").textContent = "<%= fileTAR %>"; document.getElementById('gzp-name').textContent = '<%= fileGZP %>';
document.getElementById("tar-name").href = urlTAR; document.getElementById('gzp-name').href = urlGZP;
document.getElementById("tar-link").textContent = urlTAR; document.getElementById('gzp-link').textContent = urlGZP;
document.getElementById("tar-link").href = urlTAR; document.getElementById('gzp-link').href = urlGZP;
document.getElementById("tar-size").textContent = "<%= sizeTAR %>"; document.getElementById('gzp-size').textContent = '<%= sizeGZP %>';
document.getElementById("tar-date").textContent = "<%= dateTAR %>"; document.getElementById('gzp-date').textContent = '<%= dateGZP %>';
</script> </script>
<script> <script>
document.addEventListener("DOMContentLoaded", function()
{
const host = window.location.hostname;
if (host === "localhost" || host === "127.0.0.1")
{
const warning = document.createElement("div");
warning.innerHTML = "<p><span class='warning'>Warning</span> If you are accessing this page via 127.0.0.1 or localhost, proxying will not work on other devices.Please load \
this page using your computer's IP address (e.g., 192.168.x.x) and port in order to access the playlist from other devices on your network.</p> \
<br> \
<p> Learn how to locate your IP address on <a href='https://youtube.com/watch?v=UAhDHXN2c6E' target = '_blank' > Windows</a> or \
<a href='https://youtube.com/watch?v=gaIYP4TZfHI' target = '_blank' > Linux</a>.</p>";
document.getElementById("warning-localhost").appendChild(warning); /*
Document Ready
*/
$(function(){
$("[data-bs-toggle=tooltip]").tooltip({ placement: 'bottom'});
});
/*
Action > DOM Status
*/
document.addEventListener("DOMContentReady", function() {
$("#tvapp2Toast").toast();
});
/*
document.addEventListener("DOMContentLoaded", function() {
$('#tvapp2Toast').toast("show");
});
*/
/*
Notify > Localhost
*/
document.addEventListener('DOMContentLoaded', function() {
const host = window.location.hostname;
const port = window.location.port || (window.location.protocol === 'https:' ? '443' : '80');
if (host === 'localhost' || host === '127.0.0.1')
{
const msg = "<p><span class='warning'>Warning</span> If you are accessing this page via 127.0.0.1 or localhost, proxying will not work on other devices.Please load \
this page using your computer's IP address (e.g., 192.168.x.x) and port in order to access the playlist from other devices on your network.</p> \
<br> \
<p> Learn how to locate your IP address on <a href='https://youtube.com/watch?v=UAhDHXN2c6E' target = '_blank' > Windows</a> or \
<a href='https://youtube.com/watch?v=gaIYP4TZfHI' target = '_blank' > Linux</a>.</p>";
document.getElementById('ntfy-localhost').innerHTML = msg;
document.getElementById('ntfy-localhost').style.display = 'block';
} else { } else {
document.getElementById("warning-localhost").style.display = "none"; document.getElementById('ntfy-localhost').style.display = 'none';
} }
}); });
document.addEventListener("DOMContentLoaded", function() /*
{ Notify > Firewall
const port = window.location.port || (window.location.protocol === "https:" ? "443" : "80"); */
const warningMessage = "<p><span class='notice'>Notice</span> Port <strong> " + port + " </strong> must be open and allowed through your <a href='https://youtu.be/zOZWlTplrcA?si=nGXrHKU4sAQsy18e&t=18 target='_blank'>Windows</a> \
or <a href='https://youtu.be/7c_V_3nWWbA?si=Hkd_II9myn-AkNnS&t=12' target='_blank'>Linux</a> OS firewall settings \
This action enables devices such as Firestick or Android to connect to the server and request the playlist through the proxy.</p>";
document.getElementById("warning-firewall").innerHTML = warningMessage; document.addEventListener('DOMContentLoaded', function() {
const port = window.location.port || (window.location.protocol === 'https:' ? '443' : '80');
const msg = "<p><span class='notice'>Notice</span> Port <strong> " + port + " </strong> must be open and allowed through your <a href='https://youtu.be/zOZWlTplrcA?si=nGXrHKU4sAQsy18e&t=18 target='_blank'>Windows</a> \
or <a href='https://youtu.be/7c_V_3nWWbA?si=Hkd_II9myn-AkNnS&t=12' target='_blank'>Linux</a> OS firewall settings \
This action enables devices such as Firestick or Android to connect to the server and request the playlist through the proxy.</p>";
document.getElementById('ntfy-firewall').innerHTML = msg;
document.getElementById('ntfy-firewall').style.display = 'block';
}); });
/*
Notify > Restart / Resync
*/
document.addEventListener('DOMContentLoaded', function() {
const port = window.location.port || (window.location.protocol === 'https:' ? '443' : '80');
const msg = "<p><span class='success'>Success</span> Your IPTV m3u channels and xml guide data has been successfully re-synced. \
Please refresh this window to see new data</p>";
document.getElementById('ntfy-restart').innerHTML = msg;
document.getElementById('ntfy-restart').style.display = 'none';
});
/*
Set initial health check sync time
first health check runs after 10 seconds
all future health checks run after <%= healthTimer %>
*/
let timerDelayMS = 10000;
let timerStartMS = Date.now(); // returns milliseconds
const timerHealthRun = '<%= healthTimer %>'; // time in milliseconds until health check ran AFTER initial run
/*
Action > Healthcheck
*/
function runHealthCheck()
{
const toastTypeClass = [];
toastTypeClass[ 'DEFAULT' ] = 'text-bg-primary';
toastTypeClass[ 'UNHEALTHY' ] = 'text-bg-warning';
toastTypeClass[ 'HEALTHY' ] = 'text-bg-success';
toastTypeClass[ 'ERROR' ] = 'text-bg-danger';
$.ajax(
{
url: 'api/health',
type: 'GET',
data: {
internal: 1
},
beforeSend: function( data )
{
console.log('Sending health check ...')
},
success: function( data )
{
const status = data.message;
const code = data.code;
if ( status )
{
const toastClass = toastTypeClass[status.toUpperCase()];
const toastElm = document.getElementById('tvapp2Toast');
toastElm.classList.add(toastClass);
$('.toast #toast-title').html(`<%= appName %> is ${ status }`);
$('.toast #toast-message').html(`Health check returned ${ status } (${ code })`);
$('#tvapp2Toast').toast('show');
}
},
error: function( data )
{
const toastClass = toastTypeClass['ERROR'];
const toastElm = document.getElementById('tvapp2Toast');
toastElm.classList.add(toastClass);
$('.toast #toast-title').html(`Could not connect to health check api`);
$('.toast #toast-message').html(`Failed to communicate with health check api. Try restarting the docker container to restore connection.`);
$('#tvapp2Toast').toast('show');
}
}).always(function()
{
timerDelayMS = parseInt(timerHealthRun);
timerStartMS = Date.now();
setTimeout(function()
{
runHealthCheck();
}, parseInt(timerHealthRun));
}).responseText;
}
/*
Action > Do Resync
*/
function runResync()
{
$.ajax(
{
url: 'restart',
type: 'POST',
data: {
internal: 1
},
beforeSend: function( data )
{
const dimmer = document.createElement('div');
dimmer.setAttribute('id', 'dimmer');
dimmer.style.visibility = 'visible';
dimmer.classList.add('dimmer-in');
document.getElementsByTagName('body')[0].appendChild(dimmer);
document.getElementById('ntfy-firewall').style.display = 'none';
document.getElementById('ntfy-localhost').style.display = 'none';
document.getElementById('ntfy-restart').style.display = 'none';
const iconResync = document.getElementsByClassName('fa-rotate');
iconResync[0].classList.remove('restart');
iconResync[0].classList.add('spin');
$('.modal-content .modal-body').html('<small>The M3U and EPG data will now be re-downloaded and synced with your TVApp2 container. Afterward, this page will be refreshed automatically.</small><br /><br /><small>Please wait...</small>')
$('.modal-content .modal-title').html('Resyncing Data')
$('#modalTvapp2').modal('show');
const modalBtnPrimary = document.querySelector('#btn-primary');
modalBtnPrimary.style.display = 'none';
modalBtnPrimary.style.visibility= 'hidden';
},
success: function( data )
{
/*
On successful restart, wait 1 second, remove dimmer, reload page in 5 seconds
*/
setTimeout( () =>
{
document.getElementById('ntfy-restart').style.display = 'block'
const dimmer = document.getElementById('dimmer');
dimmer.classList.remove('dimmer-in');
dimmer.classList.add('dimmer-out');
dimmer.remove();
setTimeout( function()
{
const iconResync = document.getElementsByClassName('fa-rotate'); // resync favicon
iconResync[0].classList.remove('spin'); // stop spinning
iconResync[0].classList.add('restart'); // normal spinner class
document.location.reload() // reload page
}, 5000 ); // how long until refresh page
}, 1000 ); // how long until dimmer is removed / reload page activated (also on delay)
}
});
}
/*
Health check > Show time remaining as tooltip
*/
function runTooltipCountdown( )
{
let timerHours, timerMins, timerRemainsLS;
function twoDigits( n )
{
return (n <= 9 ? "0" + n : n);
}
/*
Update Tooltip Countdown
MS = milliseconds
LS = long string (Wed Dec 31 1969 10:01:42 (Coordinated Universal Time))
*/
function updateTooltipCountdown()
{
const timerElapsedMS = Date.now() - timerStartMS; // ( 2091 )
const timerRemainsMS = timerDelayMS - timerElapsedMS; // ( 7909 ) divide by 1000 for seconds
timerRemainsLS = new Date( timerRemainsMS ); // (Wed Dec 31 1969 10:01:42 (Coordinated Universal Time))
timerHours = timerRemainsLS.getUTCHours(); // ( 0 )
timerMins = timerRemainsLS.getUTCMinutes(); // ( 9 )
const timeLeft = (timerHours ? timerHours + ':' + twoDigits( timerMins ) : timerMins) + ':' + twoDigits( timerRemainsLS.getUTCSeconds() );
jQuery(function($)
{
$(document.body).tooltip({ selector: "[title]" });
$('#action-health')
.attr('data-original-title', `Health check in ${ timeLeft }`)
.attr('aria-label', `Health check in ${ timeLeft }`)
.attr('data-bs-original-title', `Health check in ${ timeLeft }`)
});
const Heart = document.getElementsByClassName('fa-heart');
Heart[0].style.color = '#FFF';
setTimeout( function()
{
const Heart = document.getElementsByClassName('fa-heart');
Heart[0].style.color = '#FFF';
setTimeout( function()
{
Heart[0].style.color = '#FF6593';
}, timerRemainsLS.getUTCMilliseconds() + 100 );
}, timerRemainsLS.getUTCMilliseconds() + 500 );
setTimeout( function()
{
updateTooltipCountdown();
}, timerRemainsLS.getUTCMilliseconds() + 500 );
}
updateTooltipCountdown();
}
/*
Action > Healthcheck > Initialize
*/
setTimeout( function() { runHealthCheck(); }, timerDelayMS );
/*
Action > Tooltip Resync Timers
*/
runTooltipCountdown( );
</script> </script>
<script src="js/tvapp2.min.js"></script>
</body> </body>
</html> </html>