diff --git a/.nojekyll b/.nojekyll deleted file mode 100644 index e69de29b..00000000 diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 4705e643..00000000 --- a/Dockerfile +++ /dev/null @@ -1,171 +0,0 @@ -# syntax=docker/dockerfile:1 - -# # -# @project TVApp2 -# @usage docker image which allows you to download a m3u playlist and EPG guide data from -# multiple IPTV services. -# @file Dockerfile -# @repo https://github.com/TheBinaryNinja/tvapp2 -# https://git.binaryninja.net/BinaryNinja/tvapp2 -# https://github.com/aetherinox/docker-base-alpine -# -# build your own image by running -# amd64 docker build --build-arg VERSION=1.5.0 --build-arg BUILDDATE=20260812 -t tvapp2:latest -t tvapp2:1.5.0 -t tvapp2:1.5.0-amd64 -f Dockerfile . -# arm64 docker build --build-arg VERSION=1.5.0 --build-arg BUILDDATE=20260812 -t tvapp2:1.5.0-arm64 -f Dockerfile.aarch64 . -# -# OR; build using `docker buildx` -# create docker buildx create --driver docker-container --name container --bootstrap --use -# amd64 docker buildx build --build-arg ARCH=amd64 --build-arg VERSION=1.5.0 --build-arg BUILDDATE=20260812 --build-arg RELEASE=stable --tag ghcr.io/thebinaryninja/tvapp2:1.5.0-amd64 --attest type=provenance,disabled=true --attest type=sbom,disabled=true --file Dockerfile --platform linux/amd64 --output type=docker --allow network.host --network host --no-cache --pull --push . -# arm64 docker buildx build --build-arg ARCH=arm64 --build-arg VERSION=1.5.0 --build-arg BUILDDATE=20260812 --build-arg RELEASE=stable --tag ghcr.io/thebinaryninja/tvapp2:1.5.0-arm64 --attest type=provenance,disabled=true --attest type=sbom,disabled=true --file Dockerfile --platform linux/arm64 --output type=docker --allow network.host --network host --no-cache --pull --push . -# -# OR; build single amd64 image -# create docker buildx create --driver docker-container --name container --bootstrap --use -# amd64 docker buildx build --build-arg ARCH=amd64 --build-arg VERSION=1.5.0 --build-arg BUILDDATE=20260812 --build-arg RELEASE=stable --tag ghcr.io/thebinaryninja/tvapp2:1.5.0 --tag ghcr.io/thebinaryninja/tvapp2:1.5 --tag ghcr.io/thebinaryninja/tvapp2:1 --tag ghcr.io/thebinaryninja/tvapp2:latest --attest type=provenance,disabled=true --attest type=sbom,disabled=true --file Dockerfile --platform linux/amd64 --output type=docker --allow network.host --network host --no-cache --push . -# -# OR; build official image (publish) -# create docker buildx create --driver docker-container --name container --bootstrap --use -# amd64-stable docker buildx build --build-arg ARCH=amd64 --build-arg VERSION=1.5.0 --build-arg BUILDDATE=20260812 --build-arg RELEASE=stable --tag ghcr.io/thebinaryninja/tvapp2:1.5.0-amd64 --attest type=provenance,disabled=true --attest type=sbom,disabled=true --file Dockerfile --platform linux/amd64 --output type=docker --allow network.host --network host --no-cache --pull --push . -# arm64-stable docker buildx build --build-arg ARCH=arm64 --build-arg VERSION=1.5.0 --build-arg BUILDDATE=20260812 --build-arg RELEASE=stable --tag ghcr.io/thebinaryninja/tvapp2:1.5.0-arm64 --attest type=provenance,disabled=true --attest type=sbom,disabled=true --file Dockerfile --platform linux/arm64 --output type=docker --allow network.host --network host --no-cache --pull --push . -# amd64-dev docker buildx build --build-arg ARCH=amd64 --build-arg VERSION=1.5.0 --build-arg BUILDDATE=20260812 --build-arg RELEASE=development --tag ghcr.io/thebinaryninja/tvapp2:development-amd64 --attest type=provenance,disabled=true --attest type=sbom,disabled=true --file Dockerfile --platform linux/amd64 --output type=docker --allow network.host --network host --no-cache --pull --push . -# arm64-dev docker buildx build --build-arg ARCH=arm64 --build-arg VERSION=1.5.0 --build-arg BUILDDATE=20260812 --build-arg RELEASE=development --tag ghcr.io/thebinaryninja/tvapp2:development-arm64 --attest type=provenance,disabled=true --attest type=sbom,disabled=true --file Dockerfile --platform linux/arm64 --output type=docker --allow network.host --network host --no-cache --pull --push . -# amd64-stable-hash docker buildx imagetools inspect ghcr.io/thebinaryninja/tvapp2:1.5.0-amd64 -# arm64-stable-hash docker buildx imagetools inspect ghcr.io/thebinaryninja/tvapp2:1.5.0-arm64 -# amd64-dev-hash docker buildx imagetools inspect ghcr.io/thebinaryninja/tvapp2:development-amd64 -# arm64-dev-hash docker buildx imagetools inspect ghcr.io/thebinaryninja/tvapp2:development-arm64 -# merge-stable docker buildx imagetools create --tag ghcr.io/thebinaryninja/tvapp2:1.5.0 --tag ghcr.io/thebinaryninja/tvapp2:1.5 --tag ghcr.io/thebinaryninja/tvapp2:1 --tag ghcr.io/thebinaryninja/tvapp2:latest sha256:0abe1b1c119959b3b1ccc23c56a7ee2c4c908c6aaef290d4ab2993859d807a3b sha256:e68b9de8669eac64d4e4d2a8343c56705e05e9a907cf0b542343f9b536d9c473 -# merge-dev docker buildx imagetools create --tag ghcr.io/thebinaryninja/tvapp2:development sha256:8f36385a28c8f6eb7394d903c9a7a2765b06f94266b32628389ee9e3e3d7e69d sha256:c719ccb034946e3f0625003f25026d001768794e38a1ba8aafc9146291d548c5 -# # - -# # -# FROM -# any args defined before FROM cannot be called after FROM and the ARE is classified outside the build process. -# You will have to re-define the arg after FROM to utilize it anywhere else in the build process. -# -# @ref https://docs.docker.com/reference/dockerfile/#understand-how-arg-and-from-interact -# # - -ARG ARCH=amd64 -ARG ALPINE_VERSION=3.22 -FROM --platform=linux/${ARCH} ghcr.io/aetherinox/alpine-base:${ALPINE_VERSION} - -# # -# Set Args -# # - -ARG ARCH=amd64 -ARG ALPINE_VERSION=3.22 -ARG BUILDDATE -ARG VERSION -ARG RELEASE -ARG GIT_SHA1=0000000000000000000000000000000000000000 -ARG REGISTRY=local - -# # -# Set Labels -# # - -LABEL org.opencontainers.image.authors="Aetherinox, iFlip721, Optx" -LABEL org.opencontainers.image.vendor="BinaryNinja" -LABEL org.opencontainers.image.title="TVApp2" -LABEL org.opencontainers.image.description="Automatic m3u and xml guide updater for TheTvApp, TVPass, and MoveOnJoy utilized within your IPTV client." -LABEL org.opencontainers.image.source="https://github.com/thebinaryninja/tvapp2" -LABEL org.opencontainers.image.repo.1="https://github.com/thebinaryninja/tvapp2" -LABEL org.opencontainers.image.repo.2="https://git.binaryninja.net/binaryninja/tvapp2" -LABEL org.opencontainers.image.repo.3="https://github.com/aetherinox/docker-base-alpine" -LABEL org.opencontainers.image.documentation="https://thebinaryninja.github.io/tvapp2" -LABEL org.opencontainers.image.url="https://github.com/thebinaryninja/tvapp2/pkgs/container/tvapp2" -LABEL org.opencontainers.image.licenses="MIT" -LABEL org.opencontainers.image.architecture="${ARCH:-amd64}" -LABEL org.opencontainers.image.ref.name="main" -LABEL org.opencontainers.image.registry="${REGISTRY:-local}" -LABEL org.opencontainers.image.release="${RELEASE:-stable}" -LABEL org.tvapp2.image.maintainers="Aetherinox, iFlip721, Optx" -LABEL org.tvapp2.image.build-version="Version:- ${VERSION} Date:- ${BUILDDATE:-3.21}" -LABEL org.tvapp2.image.build-version-alpine="${ALPINE_VERSION:-3.21}" -LABEL org.tvapp2.image.build-architecture="${ARCH:-amd64}" -LABEL org.tvapp2.image.build-release="${RELEASE:-stable}" -LABEL org.tvapp2.image.build-sha1="${GIT_SHA1:-0000000000000000000000000000000000000000}" - -# # -# Set Env Var -# # - -ENV NODE_VERSION=22.16.0 -ENV YARN_VERSION=1.22.22 -ENV NPM_VERSION=10.9.2 -ENV RELEASE="${RELEASE:-stable}" -ENV DIR_BUILD=/usr/src/app -ENV DIR_RUN=/usr/bin/app -ENV URL_REPO="https://git.binaryninja.net/binaryninja/" -ENV WEB_IP="0.0.0.0" -ENV WEB_PORT=4124 -ENV HDHR_PORT=6077 -ENV WEB_ENCODING="deflate, br" -ENV WEB_PROXY_HEADER="x-forwarded-for" -ENV STREAM_QUALITY="hd" -ENV FILE_URL="urls.txt" -ENV FILE_M3U="playlist.m3u8" -ENV FILE_EPG="xmltv.xml" -ENV FILE_TAR="xmltv.xml.gz" -ENV HEALTH_TIMER=600000 -ENV TASK_CRON_SYNC="0 0 */3 * *" -ENV LOG_LEVEL=4 -ENV TZ="Etc/UTC" -ENV GIT_SHA1=${GIT_SHA1:-0000000000000000000000000000000000000000} - -# # -# Install -# # - -RUN \ - apk add --no-cache \ - wget \ - curl \ - bash \ - nano \ - git \ - npm \ - openssl - -# # -# Copy docker-entrypoint -# # - -COPY docker-entrypoint.sh /usr/local/bin/ - -# # -# copy s6-overlays root to image root -# # - -COPY root/ / - -# # -# set work directory -# # - -WORKDIR ${DIR_BUILD} - -# # -# copy tvapp2 project to workdir -# # - -COPY tvapp2/ ./ - -# # -# set work dir to built app -# # - -WORKDIR ${DIR_RUN} - -# # -# Ports and volumes -# # - -EXPOSE ${WEB_PORT}/tcp - -# # -# In case user sets up the cron for a longer duration, do a first run -# and then keep the container running. Hacky, but whatever. -# # - -ENTRYPOINT ["/init"] diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100755 index eb1e178e..00000000 --- a/docker-compose.yml +++ /dev/null @@ -1,50 +0,0 @@ -# # -# TVApp2 Docker-compose.yml -# -# Automatic M3U playlist and XML guide updater for TheTvApp, TVPass, and MoveOnJoy utilized within your IPTV client. -# -# @url https://github.com/TheBinaryNinja/tvapp2 -# https://git.binaryninja.net/BinaryNinja/tvapp2 -# -# @image:github ghcr.io/thebinaryninja/tvapp2:latest -# ghcr.io/thebinaryninja/tvapp2:amd64 -# ghcr.io/thebinaryninja/tvapp2:arm64 -# -# @image:dockerhub thebinaryninja/tvapp2:latest -# thebinaryninja/tvapp2:1.0.0-amd64 -# thebinaryninja/tvapp2:1.0.0-arm64 -# -# @image:gitea git.binaryninja.net/binaryninja/tvapp2:latest -# git.binaryninja.net/binaryninja/tvapp2:1.0.0-amd64 -# git.binaryninja.net/binaryninja/tvapp2:1.0.0-arm64 -# # - -services: - - # # - # Service > TVApp2 - # # - - tvapp2: - container_name: tvapp2 - image: ghcr.io/thebinaryninja/tvapp2:latest # Image: Github - # image: thebinaryninja/tvapp2:latest # Image: Dockerhub - # image: git.binaryninja.net/binaryninja/tvapp2:latest # Image: Gitea - # image: tvapp2:latest # Image: Locally built - hostname: tvapp2 - environment: - TZ: "Etc/UTC" - 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 - ulimits: - memlock: - soft: -1 - hard: -1 - healthcheck: - test: [ "CMD", "curl", "--fail", "http://127.0.0.1:4124/api/health?silent=true" ] - interval: 30s - retries: 5 diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh deleted file mode 100644 index 6381f448..00000000 --- a/docker-entrypoint.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/sh - -# # -# @project TVApp2 -# @usage docker image which allows you to download a m3u playlist and EPG guide data from -# multiple IPTV services. -# @file docker-entrypoint.sh -# @repo https://github.com/TheBinaryNinja/tvapp2 -# https://git.binaryninja.net/BinaryNinja/tvapp2 -# https://github.com/aetherinox/docker-base-alpine -# -# you can build your own image by running -# amd64 docker build --build-arg VERSION=1.0.0 --build-arg BUILDDATE=20250218 -t tvapp2:latest -t tvapp2:1.0.0 -t tvapp2:1.0.0-amd64 -f Dockerfile . -# arm64 docker build --build-arg VERSION=1.0.0 --build-arg BUILDDATE=20250218 -t tvapp2:1.0.0-arm64 -f Dockerfile.aarch64 . -# -# if you prefer to use `docker buildx` -# create docker buildx create --driver docker-container --name container --bootstrap --use -# amd64 docker buildx build --no-cache --pull --build-arg VERSION=1.0.0 --build-arg BUILDDATE=20250218 -t tvapp2:latest -t tvapp2:1.0.0 --platform=linux/amd64 --output type=docker --output type=docker . -# arm64 docker buildx build --no-cache --pull --build-arg VERSION=1.0.0 --build-arg BUILDDATE=20250218 -t tvapp2:latest -t tvapp2:1.0.0 --platform=linux/arm64 --output type=docker --output type=docker . -# # - -set -e - -# Run command with node if the first argument contains a "-" or is not a system command. The last -# part inside the "{}" is a workaround for the following bug in ash/dash: -# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=874264 -if [ "${1#-}" != "${1}" ] || [ -z "$(command -v "${1}")" ] || { [ -f "${1}" ] && ! [ -x "${1}" ]; }; then - set -- node "$@" -fi - -exec "$@" diff --git a/root/custom-cont-init.d/plugins b/root/custom-cont-init.d/plugins deleted file mode 100644 index 2a57a86f..00000000 --- a/root/custom-cont-init.d/plugins +++ /dev/null @@ -1,88 +0,0 @@ -#!/bin/bash - -# # -# @project TVApp2 -# @usage Automatic m3u and xml guide updater for TheTvApp, TVPass, and MoveOnJoy utilized within your IPTV client. -# @file plugins -# @repo.1 https://github.com/TheBinaryNinja/tvapp2 -# @repo.2 https://git.binaryninja.net/BinaryNinja/tvapp2 -# @repo.3 https://github.com/aetherinox/docker-base-alpine -# # - -# # -# define > colors -# -# Use the color table at: -# - https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797 -# # - -declare -A c=( - [end]=$'\e[0m' - [white]=$'\e[97m' - [bold]=$'\e[1m' - [dim]=$'\e[2m' - [underline]=$'\e[4m' - [strike]=$'\e[9m' - [blink]=$'\e[5m' - [inverted]=$'\e[7m' - [hidden]=$'\e[8m' - [black]=$'\e[0;30m' - [redl]=$'\e[0;91m' - [redd]=$'\e[0;31m' - [magental]=$'\e[0;95m' - [magentad]=$'\e[0;35mm' - [bluel]=$'\e[0;94m' - [blued]=$'\e[0;34m' - [cyanl]=$'\e[0;96m' - [cyand]=$'\e[0;36m' - [greenl]=$'\e[0;92m' - [greend]=$'\e[0;32m' - [yellowl]=$'\e[0;93m' - [yellowd]=$'\e[0;33m' - [greyl]=$'\e[0;37m' - [greyd]=$'\e[0;90m' - [navy]=$'\e[38;5;62m' - [olive]=$'\e[38;5;144m' - [peach]=$'\e[38;5;210m' -) - -# # -# unicode for emojis -# https://apps.timwhitlock.info/emoji/tables/unicode -# # - -declare -A icon=( - ["symbolic link"]=$'\xF0\x9F\x94\x97' # πŸ”— - ["regular file"]=$'\xF0\x9F\x93\x84' # πŸ“„ - ["directory"]=$'\xF0\x9F\x93\x81' # πŸ“ - ["regular empty file"]=$'\xe2\xad\x95' # β­• - ["log"]=$'\xF0\x9F\x93\x9C' # πŸ“œ - ["1"]=$'\xF0\x9F\x93\x9C' # πŸ“œ - ["2"]=$'\xF0\x9F\x93\x9C' # πŸ“œ - ["3"]=$'\xF0\x9F\x93\x9C' # πŸ“œ - ["4"]=$'\xF0\x9F\x93\x9C' # πŸ“œ - ["5"]=$'\xF0\x9F\x93\x9C' # πŸ“œ - ["pem"]=$'\xF0\x9F\x94\x92' # πŸ”‘ - ["pub"]=$'\xF0\x9F\x94\x91' # πŸ”’ - ["pfx"]=$'\xF0\x9F\x94\x92' # πŸ”‘ - ["p12"]=$'\xF0\x9F\x94\x92' # πŸ”‘ - ["key"]=$'\xF0\x9F\x94\x91' # πŸ”’ - ["crt"]=$'\xF0\x9F\xAA\xAA ' # πŸͺͺ - ["gz"]=$'\xF0\x9F\x93\xA6' # πŸ“¦ - ["zip"]=$'\xF0\x9F\x93\xA6' # πŸ“¦ - ["gzip"]=$'\xF0\x9F\x93\xA6' # πŸ“¦ - ["deb"]=$'\xF0\x9F\x93\xA6' # πŸ“¦ - ["sh"]=$'\xF0\x9F\x97\x94' # πŸ—” -) - -# # -# define > general -# # - -PLUGINS_PATH="/config/www/plugins" - -# # -# Plugins > Start -# # - -printf '%-29s %-65s\n' " ${c[bluel]}Loader${c[end]}" "${c[end]}Checking tvapp2-plugins${c[end]}" diff --git a/root/etc/s6-overlay/s6-rc.d/ci-service-check/dependencies.d/legacy-services b/root/etc/s6-overlay/s6-rc.d/ci-service-check/dependencies.d/legacy-services deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/ci-service-check/type b/root/etc/s6-overlay/s6-rc.d/ci-service-check/type deleted file mode 100644 index bdd22a18..00000000 --- a/root/etc/s6-overlay/s6-rc.d/ci-service-check/type +++ /dev/null @@ -1 +0,0 @@ -oneshot diff --git a/root/etc/s6-overlay/s6-rc.d/ci-service-check/up b/root/etc/s6-overlay/s6-rc.d/ci-service-check/up deleted file mode 100644 index 37be1277..00000000 --- a/root/etc/s6-overlay/s6-rc.d/ci-service-check/up +++ /dev/null @@ -1 +0,0 @@ -echo -e " Completed loading container" diff --git a/root/etc/s6-overlay/s6-rc.d/init-adduser/branding b/root/etc/s6-overlay/s6-rc.d/init-adduser/branding deleted file mode 100644 index b87cb4d6..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-adduser/branding +++ /dev/null @@ -1,21 +0,0 @@ -────────────────────────────────────────────────────────────────────────────────────────── - TVApp2 Docker Image -────────────────────────────────────────────────────────────────────────────────────────── - The TvApp2 image allows you to fetch M3U playlist and EPG data for numerous - IPTV services online. - - Once the files are fetched by the image, you can visit the self-hosted webpage, - copy the links to the M3U and EPG files; and add them to your favorite IPTV app - such as Jellyfin, Plex, or Emby. - - For more information about this project; visit the links below. This app is - served on multiple repositories as backup. Use any of the repo links below: - - TVApp2 Repo 1 https://github.com/TheBinaryNinja/tvapp2 - TVApp2 Repo 2 https://git.binaryninja.net/BinaryNinja/tvapp2 - Base Alpine Image https://github.com/Aetherinox/docker-base-alpine - - If you are making this container available on a public-facing domain, - please consider using Traefik and Authentik to protect this container from - outside access. Your M3U and EPG files will be available for the public to - download and use. diff --git a/root/etc/s6-overlay/s6-rc.d/init-adduser/dependencies.d/init-migrations b/root/etc/s6-overlay/s6-rc.d/init-adduser/dependencies.d/init-migrations deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/init-adduser/run b/root/etc/s6-overlay/s6-rc.d/init-adduser/run deleted file mode 100755 index 0093a21e..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-adduser/run +++ /dev/null @@ -1,176 +0,0 @@ -#!/usr/bin/with-contenv bash -# shellcheck shell=bash - -# # -# defaults -# # - -PUID=${PUID:-911} -PGID=${PGID:-911} -DIR_BUILD=${DIR_BUILD:-/usr/src/app} -DIR_RUN=${DIR_RUN:-/usr/bin/app} -bHasError=false - -# # -# define > colors -# -# Use the color table at: -# - https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797 -# # - -declare -A c=( - [end]=$'\e[0m' - [white]=$'\e[97m' - [bold]=$'\e[1m' - [dim]=$'\e[2m' - [underline]=$'\e[4m' - [strike]=$'\e[9m' - [blink]=$'\e[5m' - [inverted]=$'\e[7m' - [hidden]=$'\e[8m' - [black]=$'\e[0;30m' - [redl]=$'\e[0;91m' - [redd]=$'\e[0;31m' - [magental]=$'\e[0;95m' - [magentad]=$'\e[0;35mm' - [bluel]=$'\e[0;94m' - [blued]=$'\e[0;34m' - [cyanl]=$'\e[0;96m' - [cyand]=$'\e[0;36m' - [greenl]=$'\e[0;92m' - [greend]=$'\e[0;32m' - [yellowl]=$'\e[0;93m' - [yellowd]=$'\e[0;33m' - [greyl]=$'\e[0;37m' - [greyd]=$'\e[0;90m' - [navy]=$'\e[38;5;62m' - [olive]=$'\e[38;5;144m' - [peach]=$'\e[38;5;210m' -) - -# # -# unicode for emojis -# https://apps.timwhitlock.info/emoji/tables/unicode -# # - -declare -A icon=( - ["symbolic link"]=$'\xF0\x9F\x94\x97' # πŸ”— - ["regular file"]=$'\xF0\x9F\x93\x84' # πŸ“„ - ["directory"]=$'\xF0\x9F\x93\x81' # πŸ“ - ["regular empty file"]=$'\xe2\xad\x95' # β­• - ["log"]=$'\xF0\x9F\x93\x9C' # πŸ“œ - ["1"]=$'\xF0\x9F\x93\x9C' # πŸ“œ - ["2"]=$'\xF0\x9F\x93\x9C' # πŸ“œ - ["3"]=$'\xF0\x9F\x93\x9C' # πŸ“œ - ["4"]=$'\xF0\x9F\x93\x9C' # πŸ“œ - ["5"]=$'\xF0\x9F\x93\x9C' # πŸ“œ - ["pem"]=$'\xF0\x9F\x94\x92' # πŸ”‘ - ["pub"]=$'\xF0\x9F\x94\x91' # πŸ”’ - ["pfx"]=$'\xF0\x9F\x94\x92' # πŸ”‘ - ["p12"]=$'\xF0\x9F\x94\x92' # πŸ”‘ - ["key"]=$'\xF0\x9F\x94\x91' # πŸ”’ - ["crt"]=$'\xF0\x9F\xAA\xAA ' # πŸͺͺ - ["gz"]=$'\xF0\x9F\x93\xA6' # πŸ“¦ - ["zip"]=$'\xF0\x9F\x93\xA6' # πŸ“¦ - ["gzip"]=$'\xF0\x9F\x93\xA6' # πŸ“¦ - ["deb"]=$'\xF0\x9F\x93\xA6' # πŸ“¦ - ["sh"]=$'\xF0\x9F\x97\x94' # πŸ—” -) - -# # -# distro info -# # - -sys_os_name="Unknown" -sys_os_ver="1.0.0" - -if [ -e /etc/alpine-release ]; then - sys_os_name="Alpine" - sys_os_ver="$(cat /etc/alpine-release)" -fi - -# # -# get container ips -# # - -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') - -# # -# usermod -# -o, --non-unique allow using duplicate (non-unique) UID -# -g, --gid GROUP force use GROUP as new primary group -# -G, --groups GROUPS new list of supplementary GROUPS -# -u, --uid UID new UID for the user account -# -U, --unlock unlock the user account -# -# groupmod -# -g, --gid GID change the group ID to GID -# -o, --non-unique allow to use a duplicate (non-unique) GID -# # - -if [[ -z ${TVAPP_READ_ONLY_FS} ]] && [[ -z ${TVAPP_NON_ROOT_USER} ]]; then - groupmod -o -g "$PGID" dockerx - usermod -o -u "$PUID" dockerx -fi - -# # -# s6 > branding -# # - -printf '%-1s\n' " ${c[greyd]}──────────────────────────────────────────────────────────────────────────────────────────${c[end]}" -printf '%-1s\n' " ${c[greyd]} TVApp2 Docker Image${c[end]}" -printf '%-1s\n' " ${c[greyd]}──────────────────────────────────────────────────────────────────────────────────────────${c[end]}" - -printf '%-2s\n' " ${c[greyd]}The TvApp2 image allows you to fetch M3U playlist and EPG data for numerous IPTV ${c[end]}" -printf '%-2s\n' " ${c[greyd]}services online. ${c[end]}" -echo -e -printf '%-2s\n' " ${c[greyd]}Once the files are fetched by the image, you can visit the self-hosted webpage, copy ${c[end]}" -printf '%-2s\n' " ${c[greyd]}the links to the M3U and EPG files; and add them to your favorite IPTV app such as ${c[end]}" -printf '%-2s\n' " ${c[greyd]}Jellyfin, Plex, or Emby. ${c[end]}" -echo -e -printf '%-2s\n' " ${c[greyd]}For more information about this project; visit the links below. This app is served on ${c[end]}" -printf '%-2s\n' " ${c[greyd]}multiple repositories as backup. Use any of the repo links below: ${c[end]}" -echo -e -printf '%-6s %-35s %-65s\n' "" " ${c[greenl]}TVApp2 Repo 1${c[end]}" "${c[end]}https://github.com/TheBinaryNinja/tvapp2 ${c[end]}" -printf '%-6s %-35s %-65s\n' "" " ${c[greenl]}TVApp2 Repo 2${c[end]}" "${c[end]}https://git.binaryninja.net/BinaryNinja/tvapp2 ${c[end]}" -printf '%-6s %-35s %-65s\n' "" " ${c[greenl]}Base Alpine Image${c[end]}" "${c[end]}https://github.com/Aetherinox/docker-base-alpine ${c[end]}" -echo -e - -printf '%-2s\n' " ${c[greyd]}If you are making this container available on a public-facing domain, please consider ${c[end]}" -printf '%-2s\n' " ${c[greyd]}using Traefik and Authentik to protect this container from outside access. Your M3U ${c[end]}" -printf '%-2s\n' " ${c[greyd]}and EPG files will be available for the public to download and use. ${c[end]}" - -# if { [[ -z ${TVAPP_READ_ONLY_FS} ]] && [[ -z ${TVAPP_NON_ROOT_USER} ]]; } || [[ ! ${TVAPP_FIRST_PARTY} = "true" ]]; then -# cat /etc/s6-overlay/s6-rc.d/init-adduser/branding -# else -# cat /run/branding -# fi - -# # -# branding > non-root user -# # - -if [[ -z ${TVAPP_NON_ROOT_USER} ]]; then -echo -e -printf '%-6s %-35s %-65s\n' "" " ${c[greenl]}Distro${c[end]}" "${c[end]}${sys_os_name} ${sys_os_ver}${c[end]}" -printf '%-6s %-35s %-65s\n' "" " ${c[greenl]}User:Group${c[end]}" "${c[end]}$(id -u dockerx):$(id -g dockerx)${c[end]}" -else -printf '%-6s %-35s %-65s\n' "" " ${c[greenl]}User:Group${c[end]}" "${c[end]}$(stat /run -c %u):$(stat /run -c %g)${c[end]}" -fi -printf '%-6s %-35s %-65s\n' "" " ${c[greenl]}Port(s)${c[end]}" "${c[end]}$(echo $WEB_PORT)${c[end]}" -printf '%-6s %-35s %-65s\n' "" " ${c[greenl]}Gateway${c[end]}" "${c[end]}$(echo $IP_GATEWAY)${c[end]}" -printf '%-6s %-35s %-65s\n' "" " ${c[greenl]}Web Server${c[end]}" "${c[end]}$(echo $IP_CONTAINER:$WEB_PORT)${c[end]}" -printf '%-6s %-35s %-65s\n' "" " ${c[greenl]}App Folder${c[end]}" "${c[end]}$(echo $DIR_RUN)${c[end]}" -echo -e -printf '%-1s\n' " ${c[greyd]}──────────────────────────────────────────────────────────────────────────────────────────${c[end]}" - -# # -# set permissions -# # - -if [[ -z ${TVAPP_READ_ONLY_FS} ]] && [[ -z ${TVAPP_NON_ROOT_USER} ]]; then - aetherxown dockerx:dockerx /app - aetherxown dockerx:dockerx /config - aetherxown dockerx:dockerx $(echo $DIR_BUILD) -fi diff --git a/root/etc/s6-overlay/s6-rc.d/init-adduser/type b/root/etc/s6-overlay/s6-rc.d/init-adduser/type deleted file mode 100644 index bdd22a18..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-adduser/type +++ /dev/null @@ -1 +0,0 @@ -oneshot diff --git a/root/etc/s6-overlay/s6-rc.d/init-adduser/up b/root/etc/s6-overlay/s6-rc.d/init-adduser/up deleted file mode 100644 index b8522da3..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-adduser/up +++ /dev/null @@ -1 +0,0 @@ -/etc/s6-overlay/s6-rc.d/init-adduser/run diff --git a/root/etc/s6-overlay/s6-rc.d/init-config-end/dependencies.d/init-config b/root/etc/s6-overlay/s6-rc.d/init-config-end/dependencies.d/init-config deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/init-config-end/type b/root/etc/s6-overlay/s6-rc.d/init-config-end/type deleted file mode 100644 index bdd22a18..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-config-end/type +++ /dev/null @@ -1 +0,0 @@ -oneshot diff --git a/root/etc/s6-overlay/s6-rc.d/init-config-end/up b/root/etc/s6-overlay/s6-rc.d/init-config-end/up deleted file mode 100644 index c329423e..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-config-end/up +++ /dev/null @@ -1 +0,0 @@ -# This file doesn't do anything, it's just the end of the downstream image init process diff --git a/root/etc/s6-overlay/s6-rc.d/init-crontab-config/dependencies.d/init-os-end b/root/etc/s6-overlay/s6-rc.d/init-crontab-config/dependencies.d/init-os-end deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/init-crontab-config/run b/root/etc/s6-overlay/s6-rc.d/init-crontab-config/run deleted file mode 100755 index 0238c5ba..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-crontab-config/run +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/with-contenv bash -# shellcheck shell=bash - -for cron_user in dockerx root; do - if [[ -z ${TVAPP_READ_ONLY_FS} ]] && [[ -z ${TVAPP_NON_ROOT_USER} ]]; then - if [[ -f "/etc/crontabs/${cron_user}" ]]; then - aetherxown "${cron_user}":"${cron_user}" "/etc/crontabs/${cron_user}" - crontab -u "${cron_user}" "/etc/crontabs/${cron_user}" - fi - fi - - if [[ -f "/defaults/crontabs/${cron_user}" ]]; then - mkdir -p /config/crontabs - - # # - # if crontabs do not exist in config - # # - - if [[ ! -f "/config/crontabs/${cron_user}" ]]; then - - # # - # copy crontab from system - # # - - if crontab -l -u "${cron_user}" >/dev/null 2>&1; then - crontab -l -u "${cron_user}" >"/config/crontabs/${cron_user}" - fi - - # # - # if crontabs still do not exist in config (were not copied from system) - # copy crontab from image defaults (using -n, do not overwrite an existing file) - # # - - cp -n "/defaults/crontabs/${cron_user}" /config/crontabs/ - fi - - # # - # set perms and import user crontabs - # # - - aetherxown "${cron_user}":"${cron_user}" "/config/crontabs/${cron_user}" - crontab -u "${cron_user}" "/config/crontabs/${cron_user}" - fi -done - diff --git a/root/etc/s6-overlay/s6-rc.d/init-crontab-config/type b/root/etc/s6-overlay/s6-rc.d/init-crontab-config/type deleted file mode 100644 index bdd22a18..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-crontab-config/type +++ /dev/null @@ -1 +0,0 @@ -oneshot diff --git a/root/etc/s6-overlay/s6-rc.d/init-crontab-config/up b/root/etc/s6-overlay/s6-rc.d/init-crontab-config/up deleted file mode 100644 index d3541118..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-crontab-config/up +++ /dev/null @@ -1 +0,0 @@ -/etc/s6-overlay/s6-rc.d/init-crontab-config/run diff --git a/root/etc/s6-overlay/s6-rc.d/init-custom-files/dependencies.d/init-mods-end b/root/etc/s6-overlay/s6-rc.d/init-custom-files/dependencies.d/init-mods-end deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/init-custom-files/run b/root/etc/s6-overlay/s6-rc.d/init-custom-files/run deleted file mode 100755 index 81265df0..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-custom-files/run +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/with-contenv bash -# shellcheck shell=bash - -# # -# define > colors -# -# Use the color table at: -# - https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797 -# # - -declare -A c=( - [end]=$'\e[0m' - [white]=$'\e[97m' - [bold]=$'\e[1m' - [dim]=$'\e[2m' - [underline]=$'\e[4m' - [strike]=$'\e[9m' - [blink]=$'\e[5m' - [inverted]=$'\e[7m' - [hidden]=$'\e[8m' - [black]=$'\e[0;30m' - [redl]=$'\e[0;91m' - [redd]=$'\e[0;31m' - [magental]=$'\e[0;95m' - [magentad]=$'\e[0;35mm' - [bluel]=$'\e[0;94m' - [blued]=$'\e[0;34m' - [cyanl]=$'\e[0;96m' - [cyand]=$'\e[0;36m' - [greenl]=$'\e[0;92m' - [greend]=$'\e[0;32m' - [yellowl]=$'\e[0;93m' - [yellowd]=$'\e[0;33m' - [greyl]=$'\e[0;37m' - [greyd]=$'\e[0;90m' - [navy]=$'\e[38;5;62m' - [olive]=$'\e[38;5;144m' - [peach]=$'\e[38;5;210m' -) - -# # -# unicode for emojis -# https://apps.timwhitlock.info/emoji/tables/unicode -# # - -declare -A icon=( - ["symbolic link"]=$'\xF0\x9F\x94\x97' # πŸ”— - ["regular file"]=$'\xF0\x9F\x93\x84' # πŸ“„ - ["directory"]=$'\xF0\x9F\x93\x81' # πŸ“ - ["regular empty file"]=$'\xe2\xad\x95' # β­• - ["log"]=$'\xF0\x9F\x93\x9C' # πŸ“œ - ["1"]=$'\xF0\x9F\x93\x9C' # πŸ“œ - ["2"]=$'\xF0\x9F\x93\x9C' # πŸ“œ - ["3"]=$'\xF0\x9F\x93\x9C' # πŸ“œ - ["4"]=$'\xF0\x9F\x93\x9C' # πŸ“œ - ["5"]=$'\xF0\x9F\x93\x9C' # πŸ“œ - ["pem"]=$'\xF0\x9F\x94\x92' # πŸ”‘ - ["pub"]=$'\xF0\x9F\x94\x91' # πŸ”’ - ["pfx"]=$'\xF0\x9F\x94\x92' # πŸ”‘ - ["p12"]=$'\xF0\x9F\x94\x92' # πŸ”‘ - ["key"]=$'\xF0\x9F\x94\x91' # πŸ”’ - ["crt"]=$'\xF0\x9F\xAA\xAA ' # πŸͺͺ - ["gz"]=$'\xF0\x9F\x93\xA6' # πŸ“¦ - ["zip"]=$'\xF0\x9F\x93\xA6' # πŸ“¦ - ["gzip"]=$'\xF0\x9F\x93\xA6' # πŸ“¦ - ["deb"]=$'\xF0\x9F\x93\xA6' # πŸ“¦ - ["sh"]=$'\xF0\x9F\x97\x94' # πŸ—” -) - -# Directories -SCRIPTS_DIR="/custom-cont-init.d" - -# Make sure custom init directory exists and has files in it -if [[ -e "${SCRIPTS_DIR}" ]] && [[ -n "$(/bin/ls -A ${SCRIPTS_DIR} 2>/dev/null)" ]]; then - printf '%-29s %-65s\n' " ${c[bluel]}Loader${c[end]}" "${c[end]}Loading any found plugins${c[end]}" - for SCRIPT in "${SCRIPTS_DIR}"/*; do - NAME="$(basename "${SCRIPT}")" - if [[ -f "${SCRIPT}" ]]; then - printf '%-29s %-65s\n' " ${c[bluel]}Loader${c[end]}" "${c[end]}Executing${c[end]}" - /bin/bash "${SCRIPT}" - printf '%-29s %-65s\n' " ${c[bluel]}Loader${c[end]}" "${c[end]}Successfully ran with code ${c[bluel]}[$?]${c[end]}" - elif [[ ! -f "${SCRIPT}" ]]; then - printf '%-29s %-65s\n' " ${c[bluel]}Loader${c[end]}" "${c[end]}${c[bluel]}${NAME}${c[end]} is not a valid file${c[end]}" - fi - done -else - printf '%-29s %-65s\n' " ${c[bluel]}Loader${c[end]}" "${c[end]}No plugins found; skipping${c[end]}" -fi diff --git a/root/etc/s6-overlay/s6-rc.d/init-custom-files/type b/root/etc/s6-overlay/s6-rc.d/init-custom-files/type deleted file mode 100644 index bdd22a18..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-custom-files/type +++ /dev/null @@ -1 +0,0 @@ -oneshot diff --git a/root/etc/s6-overlay/s6-rc.d/init-custom-files/up b/root/etc/s6-overlay/s6-rc.d/init-custom-files/up deleted file mode 100644 index 28bf3185..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-custom-files/up +++ /dev/null @@ -1 +0,0 @@ -/etc/s6-overlay/s6-rc.d/init-custom-files/run diff --git a/root/etc/s6-overlay/s6-rc.d/init-envfile/run b/root/etc/s6-overlay/s6-rc.d/init-envfile/run deleted file mode 100755 index 592df527..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-envfile/run +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/with-contenv bash -# shellcheck shell=bash - -if find /run/s6/container_environment/FILE__* -maxdepth 1 > /dev/null 2>&1; then - for FILENAME in /run/s6/container_environment/FILE__*; do - SECRETFILE=$(cat "${FILENAME}") - if [[ -f ${SECRETFILE} ]]; then - FILESTRIP=${FILENAME//FILE__/} - if [[ $(tail -n1 "${SECRETFILE}" | wc -l) != 0 ]]; then - echo "[env-init] Your secret: ${FILENAME##*/}" - echo " contains a trailing newline and may not work as expected" - fi - cat "${SECRETFILE}" >"${FILESTRIP}" - echo "[env-init] ${FILESTRIP##*/} set from ${FILENAME##*/}" - else - echo "[env-init] cannot find secret in ${FILENAME##*/}" - fi - done -fi diff --git a/root/etc/s6-overlay/s6-rc.d/init-envfile/type b/root/etc/s6-overlay/s6-rc.d/init-envfile/type deleted file mode 100644 index bdd22a18..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-envfile/type +++ /dev/null @@ -1 +0,0 @@ -oneshot diff --git a/root/etc/s6-overlay/s6-rc.d/init-envfile/up b/root/etc/s6-overlay/s6-rc.d/init-envfile/up deleted file mode 100644 index b2b4fb8c..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-envfile/up +++ /dev/null @@ -1 +0,0 @@ -/etc/s6-overlay/s6-rc.d/init-envfile/run diff --git a/root/etc/s6-overlay/s6-rc.d/init-folders/dependencies.d/init-os-end b/root/etc/s6-overlay/s6-rc.d/init-folders/dependencies.d/init-os-end deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/init-folders/run b/root/etc/s6-overlay/s6-rc.d/init-folders/run deleted file mode 100755 index 35e4b640..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-folders/run +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/with-contenv bash -# shellcheck shell=bash - -#Β make folders -mkdir -p \ - /config/keys \ - /run \ diff --git a/root/etc/s6-overlay/s6-rc.d/init-folders/type b/root/etc/s6-overlay/s6-rc.d/init-folders/type deleted file mode 100644 index bdd22a18..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-folders/type +++ /dev/null @@ -1 +0,0 @@ -oneshot diff --git a/root/etc/s6-overlay/s6-rc.d/init-folders/up b/root/etc/s6-overlay/s6-rc.d/init-folders/up deleted file mode 100644 index 0fb7dc7d..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-folders/up +++ /dev/null @@ -1 +0,0 @@ -/etc/s6-overlay/s6-rc.d/init-folders/run diff --git a/root/etc/s6-overlay/s6-rc.d/init-keygen/run b/root/etc/s6-overlay/s6-rc.d/init-keygen/run deleted file mode 100755 index 8b0c603f..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-keygen/run +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/with-contenv bash -# shellcheck shell=bash - -# # -# @project TVApp2 -# @usage Automatic m3u and xml guide updater for TheTvApp, TVPass, and MoveOnJoy utilized within your IPTV client. -# @file run -# @repo.1 https://github.com/TheBinaryNinja/tvapp2 -# @repo.2 https://git.binaryninja.net/BinaryNinja/tvapp2 -# @repo.3 https://github.com/aetherinox/docker-base-alpine -# # - -SUBJECT="/C=NA/ST=NA/L=NA/O=BinaryNinja/OU=TVApp2 Docker Image/CN=*" -if [[ -f /config/keys/cert.key && -f /config/keys/cert.crt ]]; then - echo -e " SSL : Using existing keys found in /config/keys" -else - echo -e " SSL : Generating self-signed keys in folder/config/keys. Replace if needed." - rm -f \ - /config/keys/cert.key \ - /config/keys/cert.crt || true - - mkdir -p /config/keys - - OUT=$(openssl req -new -x509 -days 3650 -nodes -out /config/keys/cert.crt -keyout /config/keys/cert.key -subj "$SUBJECT" 2>/dev/null) -fi diff --git a/root/etc/s6-overlay/s6-rc.d/init-keygen/type b/root/etc/s6-overlay/s6-rc.d/init-keygen/type deleted file mode 100644 index bdd22a18..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-keygen/type +++ /dev/null @@ -1 +0,0 @@ -oneshot diff --git a/root/etc/s6-overlay/s6-rc.d/init-keygen/up b/root/etc/s6-overlay/s6-rc.d/init-keygen/up deleted file mode 100644 index cacd3ec7..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-keygen/up +++ /dev/null @@ -1 +0,0 @@ -/etc/s6-overlay/s6-rc.d/init-keygen/run diff --git a/root/etc/s6-overlay/s6-rc.d/init-migrations/run b/root/etc/s6-overlay/s6-rc.d/init-migrations/run deleted file mode 100755 index c4679265..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-migrations/run +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/with-contenv bash -# shellcheck shell=bash - -MIGRATIONS_DIR="/migrations" -MIGRATIONS_HISTORY="/config/.migrations" - -echo -e " Migrations : Started" - -if [[ ! -d ${MIGRATIONS_DIR} ]]; then - echo -e " Migrations : No migrations found" - exit -fi - -for MIGRATION in $(find ${MIGRATIONS_DIR}/* | sort -n); do - NAME="$(basename "${MIGRATION}")" - if [[ -f ${MIGRATIONS_HISTORY} ]] && grep -Fxq "${NAME}" ${MIGRATIONS_HISTORY}; then - echo -e " Migrations : ${NAME} β€Ί Skipped" - continue - fi - - echo -e " Migrations : ${NAME} β€Ί Executing" - chmod +x "${MIGRATION}" - - # # - # Execute migration script in a subshell to prevent it from modifying the current environment - # # - - ("${MIGRATION}") - EXIT_CODE=$? - if [[ ${EXIT_CODE} -ne 0 ]]; then - echo -e " Migrations : ${NAME} β€Ί Failed with exit code ${EXIT_CODE}" - exit "${EXIT_CODE}" - fi - - echo "${NAME}" >>${MIGRATIONS_HISTORY} - echo -e " Migrations : ${NAME} β€Ί Success" -done - -echo -e " Migrations : Complete" diff --git a/root/etc/s6-overlay/s6-rc.d/init-migrations/type b/root/etc/s6-overlay/s6-rc.d/init-migrations/type deleted file mode 100644 index bdd22a18..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-migrations/type +++ /dev/null @@ -1 +0,0 @@ -oneshot diff --git a/root/etc/s6-overlay/s6-rc.d/init-migrations/up b/root/etc/s6-overlay/s6-rc.d/init-migrations/up deleted file mode 100644 index 7c4cbcf6..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-migrations/up +++ /dev/null @@ -1 +0,0 @@ -/etc/s6-overlay/s6-rc.d/init-migrations/run diff --git a/root/etc/s6-overlay/s6-rc.d/init-mods-end/dependencies.d/init-mods b/root/etc/s6-overlay/s6-rc.d/init-mods-end/dependencies.d/init-mods deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/init-mods-end/dependencies.d/init-mods-package-install b/root/etc/s6-overlay/s6-rc.d/init-mods-end/dependencies.d/init-mods-package-install deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/init-mods-end/type b/root/etc/s6-overlay/s6-rc.d/init-mods-end/type deleted file mode 100644 index bdd22a18..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-mods-end/type +++ /dev/null @@ -1 +0,0 @@ -oneshot diff --git a/root/etc/s6-overlay/s6-rc.d/init-mods-end/up b/root/etc/s6-overlay/s6-rc.d/init-mods-end/up deleted file mode 100644 index 2f9d66ac..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-mods-end/up +++ /dev/null @@ -1 +0,0 @@ -# Empty placeholder for end of mod init process diff --git a/root/etc/s6-overlay/s6-rc.d/init-mods-package-install/dependencies.d/init-mods b/root/etc/s6-overlay/s6-rc.d/init-mods-package-install/dependencies.d/init-mods deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/init-mods-package-install/type b/root/etc/s6-overlay/s6-rc.d/init-mods-package-install/type deleted file mode 100644 index bdd22a18..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-mods-package-install/type +++ /dev/null @@ -1 +0,0 @@ -oneshot diff --git a/root/etc/s6-overlay/s6-rc.d/init-mods-package-install/up b/root/etc/s6-overlay/s6-rc.d/init-mods-package-install/up deleted file mode 100644 index fb633014..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-mods-package-install/up +++ /dev/null @@ -1 +0,0 @@ -/etc/s6-overlay/s6-rc.d/init-mods-package-install/run diff --git a/root/etc/s6-overlay/s6-rc.d/init-mods/dependencies.d/init-version-checks b/root/etc/s6-overlay/s6-rc.d/init-mods/dependencies.d/init-version-checks deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/init-os-end/dependencies.d/base b/root/etc/s6-overlay/s6-rc.d/init-os-end/dependencies.d/base deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/init-os-end/dependencies.d/init-adduser b/root/etc/s6-overlay/s6-rc.d/init-os-end/dependencies.d/init-adduser deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/init-os-end/dependencies.d/init-envfile b/root/etc/s6-overlay/s6-rc.d/init-os-end/dependencies.d/init-envfile deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/init-os-end/dependencies.d/init-migrations b/root/etc/s6-overlay/s6-rc.d/init-os-end/dependencies.d/init-migrations deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/init-os-end/type b/root/etc/s6-overlay/s6-rc.d/init-os-end/type deleted file mode 100644 index bdd22a18..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-os-end/type +++ /dev/null @@ -1 +0,0 @@ -oneshot diff --git a/root/etc/s6-overlay/s6-rc.d/init-os-end/up b/root/etc/s6-overlay/s6-rc.d/init-os-end/up deleted file mode 100644 index 092149d5..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-os-end/up +++ /dev/null @@ -1 +0,0 @@ -# This file doesn't do anything, it's just the end of the mod init process diff --git a/root/etc/s6-overlay/s6-rc.d/init-permissions/dependencies.d/init-keygen b/root/etc/s6-overlay/s6-rc.d/init-permissions/dependencies.d/init-keygen deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/init-permissions/run b/root/etc/s6-overlay/s6-rc.d/init-permissions/run deleted file mode 100755 index 0db76481..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-permissions/run +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/with-contenv bash -# shellcheck shell=bash - -#Β permissions -aetherxown -R dockerx:dockerx \ - /config/keys diff --git a/root/etc/s6-overlay/s6-rc.d/init-permissions/type b/root/etc/s6-overlay/s6-rc.d/init-permissions/type deleted file mode 100644 index bdd22a18..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-permissions/type +++ /dev/null @@ -1 +0,0 @@ -oneshot diff --git a/root/etc/s6-overlay/s6-rc.d/init-permissions/up b/root/etc/s6-overlay/s6-rc.d/init-permissions/up deleted file mode 100644 index 81bfce3a..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-permissions/up +++ /dev/null @@ -1 +0,0 @@ -/etc/s6-overlay/s6-rc.d/init-permissions/run diff --git a/root/etc/s6-overlay/s6-rc.d/init-samples/dependencies.d/init-folders b/root/etc/s6-overlay/s6-rc.d/init-samples/dependencies.d/init-folders deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/init-samples/run b/root/etc/s6-overlay/s6-rc.d/init-samples/run deleted file mode 100755 index dfe32ae9..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-samples/run +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/with-contenv bash -# shellcheck shell=bash - -# # -# @project TVApp2 -# @usage Automatic m3u and xml guide updater for TheTvApp, TVPass, and MoveOnJoy utilized within your IPTV client. -# @file run -# @repo.1 https://github.com/TheBinaryNinja/tvapp2 -# @repo.2 https://git.binaryninja.net/BinaryNinja/tvapp2 -# @repo.3 https://github.com/aetherinox/docker-base-alpine -# # diff --git a/root/etc/s6-overlay/s6-rc.d/init-samples/type b/root/etc/s6-overlay/s6-rc.d/init-samples/type deleted file mode 100644 index bdd22a18..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-samples/type +++ /dev/null @@ -1 +0,0 @@ -oneshot diff --git a/root/etc/s6-overlay/s6-rc.d/init-samples/up b/root/etc/s6-overlay/s6-rc.d/init-samples/up deleted file mode 100644 index 05ae8e5f..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-samples/up +++ /dev/null @@ -1 +0,0 @@ -/etc/s6-overlay/s6-rc.d/init-samples/run diff --git a/root/etc/s6-overlay/s6-rc.d/init-services/dependencies.d/init-custom-files b/root/etc/s6-overlay/s6-rc.d/init-services/dependencies.d/init-custom-files deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/init-services/dependencies.d/init-mods-end b/root/etc/s6-overlay/s6-rc.d/init-services/dependencies.d/init-mods-end deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/init-services/type b/root/etc/s6-overlay/s6-rc.d/init-services/type deleted file mode 100644 index bdd22a18..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-services/type +++ /dev/null @@ -1 +0,0 @@ -oneshot diff --git a/root/etc/s6-overlay/s6-rc.d/init-services/up b/root/etc/s6-overlay/s6-rc.d/init-services/up deleted file mode 100644 index a7c3905b..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-services/up +++ /dev/null @@ -1 +0,0 @@ -# This file doesn't do anything, it just signals that services can start diff --git a/root/etc/s6-overlay/s6-rc.d/init-version-checks/dependencies.d/init-config-end b/root/etc/s6-overlay/s6-rc.d/init-version-checks/dependencies.d/init-config-end deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/init-version-checks/run b/root/etc/s6-overlay/s6-rc.d/init-version-checks/run deleted file mode 100755 index c3f66072..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-version-checks/run +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/with-contenv bash -# shellcheck shell=bash - -# detect nginx configs with dates not matching the provided sample files -# active_confs=$(find /config/nginx/ -name "*.conf" -type f 2>/dev/null) - -# for i in ${active_confs}; do -# if [ -f "${i}.sample" ]; then -# if [ "$(sed -nE 's|^## Version ([0-9]{4}\/[0-9]{2}\/[0-9]{2}).*|\1|p' "${i}")" != "$(sed -nE 's|^## Version ([0-9]{4}\/[0-9]{2}\/[0-9]{2}).*|\1|p' "${i}.sample")" ]; then -# active_confs_changed="β”‚ $(printf '%10s' "$(sed -nE 's|^## Version ([0-9]{4}\/[0-9]{2}\/[0-9]{2}).*|\1|p' "${i}" | tr / -)") β”‚ $(printf '%10s' "$(sed -nE 's|^## Version ([0-9]{4}\/[0-9]{2}\/[0-9]{2}).*|\1|p' "${i}.sample" | tr / -)") β”‚ $(printf '%-70s' "${i}") β”‚\n${active_confs_changed}" -# fi -# fi -# done - -# detect site-confs with wrong extension -# site_confs_wrong_ext=$(find /config/nginx/site-confs/ -type f -not -name "*.conf" -not -name "*.conf.sample" 2>/dev/null) - -# if [ -n "${site_confs_wrong_ext}" ]; then -# echo "**** The following site-confs have extensions other than .conf ****" -# echo "**** This may be due to user customization. ****" -# echo "**** You should review the files and rename them to use the .conf extension or remove them. ****" -# echo "**** nginx.conf will only include site-confs with the .conf extension. ****" -# echo -e "${site_confs_wrong_ext}" -# fi diff --git a/root/etc/s6-overlay/s6-rc.d/init-version-checks/type b/root/etc/s6-overlay/s6-rc.d/init-version-checks/type deleted file mode 100644 index bdd22a18..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-version-checks/type +++ /dev/null @@ -1 +0,0 @@ -oneshot diff --git a/root/etc/s6-overlay/s6-rc.d/init-version-checks/up b/root/etc/s6-overlay/s6-rc.d/init-version-checks/up deleted file mode 100644 index 2b7c0e6e..00000000 --- a/root/etc/s6-overlay/s6-rc.d/init-version-checks/up +++ /dev/null @@ -1 +0,0 @@ -/etc/s6-overlay/s6-rc.d/init-version-checks/run diff --git a/root/etc/s6-overlay/s6-rc.d/svc-cron/dependencies.d/init-services b/root/etc/s6-overlay/s6-rc.d/svc-cron/dependencies.d/init-services deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/svc-cron/run b/root/etc/s6-overlay/s6-rc.d/svc-cron/run deleted file mode 100755 index 70c5ea63..00000000 --- a/root/etc/s6-overlay/s6-rc.d/svc-cron/run +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/with-contenv bash -# shellcheck shell=bash - -if builtin command -v crontab >/dev/null 2>&1 && [[ -n "$(crontab -l -u dockerx 2>/dev/null || true)" || -n "$(crontab -l -u root 2>/dev/null || true)" ]]; then - if builtin command -v busybox >/dev/null 2>&1 && [[ $(busybox || true) =~ [[:space:]](crond)([,]|$) ]]; then - exec busybox crond -f -S -l 5 - elif [[ -f /usr/bin/apt ]] && [[ -f /usr/sbin/cron ]]; then - exec /usr/sbin/cron -f -L 5 - else - echo "**** cron not found ****" - sleep infinity - fi -else - sleep infinity -fi diff --git a/root/etc/s6-overlay/s6-rc.d/svc-cron/type b/root/etc/s6-overlay/s6-rc.d/svc-cron/type deleted file mode 100644 index 5883cff0..00000000 --- a/root/etc/s6-overlay/s6-rc.d/svc-cron/type +++ /dev/null @@ -1 +0,0 @@ -longrun diff --git a/root/etc/s6-overlay/s6-rc.d/user/contents.d/init-folders b/root/etc/s6-overlay/s6-rc.d/user/contents.d/init-folders deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/user/contents.d/init-keygen b/root/etc/s6-overlay/s6-rc.d/user/contents.d/init-keygen deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/user/contents.d/init-permissions b/root/etc/s6-overlay/s6-rc.d/user/contents.d/init-permissions deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/user/contents.d/init-samples b/root/etc/s6-overlay/s6-rc.d/user/contents.d/init-samples deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/user/contents.d/init-version-checks b/root/etc/s6-overlay/s6-rc.d/user/contents.d/init-version-checks deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/s6-overlay/s6-rc.d/user2/contents.d/ci-service-check b/root/etc/s6-overlay/s6-rc.d/user2/contents.d/ci-service-check deleted file mode 100644 index e69de29b..00000000 diff --git a/root/etc/services.d/tvapp2/run b/root/etc/services.d/tvapp2/run deleted file mode 100755 index af1f07d3..00000000 --- a/root/etc/services.d/tvapp2/run +++ /dev/null @@ -1,195 +0,0 @@ -#!/usr/bin/with-contenv bash -# shellcheck shell=bash - -# # -# defaults -# # - -PUID=${PUID:-911} -PGID=${PGID:-911} -DIR_BUILD=${DIR_BUILD:-/usr/src/app} -DIR_RUN=${DIR_RUN:-/usr/bin/app} -bHasError=false - -# # -# define > colors -# -# Use the color table at: -# - https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797 -# # - -declare -A c=( - [end]=$'\e[0m' - [white]=$'\e[97m' - [bold]=$'\e[1m' - [dim]=$'\e[2m' - [underline]=$'\e[4m' - [strike]=$'\e[9m' - [blink]=$'\e[5m' - [inverted]=$'\e[7m' - [hidden]=$'\e[8m' - [black]=$'\e[0;30m' - [redl]=$'\e[0;91m' - [redd]=$'\e[0;31m' - [magental]=$'\e[0;95m' - [magentad]=$'\e[0;35mm' - [bluel]=$'\e[0;94m' - [blued]=$'\e[0;34m' - [cyanl]=$'\e[0;96m' - [cyand]=$'\e[0;36m' - [greenl]=$'\e[0;92m' - [greend]=$'\e[0;32m' - [yellowl]=$'\e[0;93m' - [yellowd]=$'\e[0;33m' - [greyl]=$'\e[0;37m' - [greyd]=$'\e[0;90m' - [navy]=$'\e[38;5;62m' - [olive]=$'\e[38;5;144m' - [peach]=$'\e[38;5;210m' -) - -# # -# unicode for emojis -# https://apps.timwhitlock.info/emoji/tables/unicode -# # - -declare -A icon=( - ["symbolic link"]=$'\xF0\x9F\x94\x97' # πŸ”— - ["regular file"]=$'\xF0\x9F\x93\x84' # πŸ“„ - ["directory"]=$'\xF0\x9F\x93\x81' # πŸ“ - ["regular empty file"]=$'\xe2\xad\x95' # β­• - ["log"]=$'\xF0\x9F\x93\x9C' # πŸ“œ - ["1"]=$'\xF0\x9F\x93\x9C' # πŸ“œ - ["2"]=$'\xF0\x9F\x93\x9C' # πŸ“œ - ["3"]=$'\xF0\x9F\x93\x9C' # πŸ“œ - ["4"]=$'\xF0\x9F\x93\x9C' # πŸ“œ - ["5"]=$'\xF0\x9F\x93\x9C' # πŸ“œ - ["pem"]=$'\xF0\x9F\x94\x92' # πŸ”‘ - ["pub"]=$'\xF0\x9F\x94\x91' # πŸ”’ - ["pfx"]=$'\xF0\x9F\x94\x92' # πŸ”‘ - ["p12"]=$'\xF0\x9F\x94\x92' # πŸ”‘ - ["key"]=$'\xF0\x9F\x94\x91' # πŸ”’ - ["crt"]=$'\xF0\x9F\xAA\xAA ' # πŸͺͺ - ["gz"]=$'\xF0\x9F\x93\xA6' # πŸ“¦ - ["zip"]=$'\xF0\x9F\x93\xA6' # πŸ“¦ - ["gzip"]=$'\xF0\x9F\x93\xA6' # πŸ“¦ - ["deb"]=$'\xF0\x9F\x93\xA6' # πŸ“¦ - ["sh"]=$'\xF0\x9F\x97\x94' # πŸ—” -) - -# # -# distro info -# # - -sys_os_name="Unknown" -sys_os_ver="1.0.0" - -if [ -e /etc/alpine-release ]; then - sys_os_name="Alpine" - sys_os_ver="$(cat /etc/alpine-release)" -fi - -# # -# s6 > store env variables -# # - -printf '%-29s %-65s\n' " ${c[bluel]}STATUS${c[end]}" "${c[end]}Fetching docker container and gateway addresses${c[end]}" - -# # -# get container ips -# # - -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') - -if [ -d "/var/run/s6/container_environment/" ]; then - printf "$ip_gateway" > /var/run/s6/container_environment/IP_GATEWAY - printf "$ip_container" > /var/run/s6/container_environment/IP_CONTAINER -else - printf '%-29s %-65s\n' " ${c[redl]}ERROR${c[end]}" "${c[end]}Cannot generate s6-overlay env files; folder ${c[redl]}/var/run/s6/container_environment/${c[end]} does not exist${c[end]}" - bHasError=true -fi - -# # -# s6 > export env vars -# # - -export IP_GATEWAY=$ip_gateway -export IP_GATEWAY=$ip_container - -# # -# install and startup for tvapp2 -# # - -printf '%-29s %-65s\n' " ${c[bluel]}STATUS${c[end]}" "${c[end]}Copying ${c[bluel]}${DIR_BUILD}${c[end]} to ${c[bluel]}${DIR_RUN}${c[end]}" -if [ -z "${DIR_BUILD}" ]; then - printf '%-29s %-65s\n' " ${c[redl]}ERROR${c[end]}" "${c[end]}Cannot copy; env var ${c[redl]}\${DIR_BUILD}${c[end]} missing${c[end]}" - bHasError=true -else - if [ -d "${DIR_BUILD}/" ]; then - cp -r ${DIR_BUILD}/* ${DIR_RUN} - else - printf '%-29s %-65s\n' " ${c[redl]}ERROR${c[end]}" "${c[end]}Cannot copy folder ${c[redl]}${DIR_BUILD}${c[end]} to ${c[redl]}${DIR_RUN}${c[end]}; build folder ${c[redl]}${DIR_BUILD}${c[end]} does not exist${c[end]}" - bHasError=true - fi -fi - -# # -# remove build directory -# # - -printf '%-29s %-65s\n' " ${c[bluel]}STATUS${c[end]}" "${c[end]}Remove ${c[bluel]}${DIR_BUILD}/${c[end]}" -if [ -z "${DIR_BUILD}" ]; then - printf '%-29s %-65s\n' " ${c[redl]}ERROR${c[end]}" "${c[end]}Cannot remove; env var ${c[redl]}\${DIR_BUILD}${c[end]} missing${c[end]}" -else - if [ -d "${DIR_BUILD}" ]; then - rm -rf "${DIR_BUILD}/" - else - printf '%-29s %-65s\n' " ${c[redl]}ERROR${c[end]}" "${c[end]}Cannot remove; build folder ${c[redl]}${DIR_BUILD}${c[end]} does not exist. Restart the container to re-initialize build folder.${c[end]}" - fi -fi - -# # -# cd to BUILD_RUN directory -# # - -printf '%-29s %-65s\n' " ${c[bluel]}STATUS${c[end]}" "${c[end]}Changing to run directory ${c[bluel]}${DIR_RUN}/${c[end]}" -if [ -z "${DIR_RUN}" ]; then - printf '%-29s %-65s\n' " ${c[redl]}ERROR${c[end]}" "${c[end]}Cannot cd; env var ${c[redl]}\${DIR_RUN}${c[end]} missing${c[end]}" - bHasError=true -else - if [ -d "${DIR_RUN}" ]; then - cd ${DIR_RUN} - else - printf '%-29s %-65s\n' " ${c[redl]}ERROR${c[end]}" "${c[end]}Cannot cd; run folder ${c[redl]}${DIR_RUN}${c[end]} does not exist${c[end]}" - bHasError=true - fi -fi - -# # -# install tvapp2 via npm -# # - -printf '%-29s %-65s\n' " ${c[bluel]}STATUS${c[end]}" "${c[end]}Running command ${c[bluel]}npm install --omit=dev${c[end]}" -if ! command -v npm; then - printf '%-29s %-65s\n' " ${c[redl]}ERROR${c[end]}" "${c[end]}Cannot install TVApp2 with npm because package ${c[redl]}npm${c[end]} not installed${c[end]}" - bHasError=true -else - npm install --omit=dev - - printf '%-29s %-65s\n' " ${c[bluel]}STATUS${c[end]}" "${c[end]}Running command ${c[bluel]}npm start${c[end]}" - npm start -fi - -# # -# finished run script -# # - -printf '%-29s %-65s\n' " ${c[greenl]}OK${c[end]}" "${c[end]}Finished initializing script${c[end]}" -if [ "$bHasError" = true ] ; then - printf '%-29s %-65s\n' "" "" - printf '%-29s %-65s\n' " ${c[redl]}ERROR${c[end]}" "${c[end]}Fatal errors were detected${c[end]}" - printf '%-29s %-65s\n' " ${c[redl]}${c[end]}" "${c[end]}The run script detected that certain steps failed. This app may not${c[end]}" - printf '%-29s %-65s\n' " ${c[redl]}${c[end]}" "${c[end]}work properly. Try restarting the container.${c[end]}" - printf '%-29s %-65s\n' "" "" -fi diff --git a/tvapp2/classes/CLib.js b/tvapp2/classes/CLib.js deleted file mode 100644 index 009d32c7..00000000 --- a/tvapp2/classes/CLib.js +++ /dev/null @@ -1,169 +0,0 @@ -/* - Compress / Uncompress String with base64 - - these functions use a unique character table. moving the letters around will cause strings to not - be in the correct order once uncompressed. - - @usage new CLib().compress( 'https://daddylive.mp/' ) - new CLib().uncompress( 'burS7u6FvUHhZfrhkfJoYz8CswTD=' ) - new CLib().translate( '=', plugin.defTrans, plugin.tvaTrans ) - - a custom character set can be specified with two additional parameters. however, anything prior - that was encoded will not be decoded by the new character set. - - const strCompress = new CLib().compress( 'test.com' ); - const strUncompress = new CLib().uncompress( strCompress ); - - new CLib().compress( 'test.com', 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', 'rXzxP9ZdvehYlstwiTuV1c07j45Abo2Ama6k3gqpyf8n+/NMSEIUHBQRJDLFCGKO' ) - new CLib().uncompress( 'oZcUozDkAQH=', 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', 'rXzxP9ZdvehYlstwiTuV1c07j45Abo2Ama6k3gqpyf8n+/NMSEIUHBQRJDLFCGKO' ) -*/ - -import chalk from 'chalk'; -import Log from './Log.js'; - -/* - Class > CLib -*/ - -class CLib -{ - constructor() - { - this.defTrans = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; - this.tvaTrans = 'TVAPp29uqXiv6g5adr1j8nfwZ0bs7Ykm3xl4hczAtoey+/CDKJULSEMBQRFGIHNO'; - } - - compress( data, defTrans, tvaTrans ) - { - if ( typeof data === 'string' ) - data = Buffer.from( data, 'utf8' ); - - const transDef = defTrans || this.defTrans; - const transTva = tvaTrans || this.tvaTrans; - - try - { - const dataCompress = this.translate( data.toString( 'base64' ), transDef, transTva ); - - Log.ok( `clib`, chalk.yellow( `[compress]` ), chalk.white( `βš™οΈ` ), - chalk.blueBright( `` ), chalk.gray( `Compress string` ), - chalk.blueBright( `` ), chalk.gray( `${ data }` ), - chalk.blueBright( `` ), chalk.gray( `${ dataCompress }` ) ); - - return dataCompress; - } - catch ( err ) - { - Log.error( `clib`, chalk.redBright( `[compress]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `Could not compress string; bad string ${ data }` ), - chalk.redBright( `` ), chalk.gray( `${ err.message }` ), - chalk.redBright( `` ), chalk.gray( `${ data }` ) ); - - return null; - } - } - - uncompress( data, defTrans, tvaTrans ) - { - if ( Buffer.isBuffer( data ) ) - data = data.toString(); - - const transDef = defTrans || this.defTrans; - const transTva = tvaTrans || this.tvaTrans; - - try - { - const dataTranslated = this.translate( data, transTva, transDef ); - const dataUncompress = Buffer.from( dataTranslated, 'base64' ).toString( 'utf8' ); - - Log.ok( `clib`, chalk.yellow( `[decompss]` ), chalk.white( `βš™οΈ` ), - chalk.blueBright( `` ), chalk.gray( `Uncompress string` ), - chalk.blueBright( `` ), chalk.gray( `${ data }` ), - chalk.blueBright( `` ), chalk.gray( `${ dataUncompress }` ) ); - - return dataUncompress; - } - catch ( err ) - { - Log.error( `clib`, chalk.redBright( `[decompss]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `Could not uncompress string; bad string ${ data }` ), - chalk.redBright( `` ), chalk.gray( `${ err.message }` ), - chalk.redBright( `` ), chalk.gray( `${ data }` ) ); - - return null; - } - } - - /* - Translate - - compresses or decompresses encoded strings for the functions: - - compress - - uncompress - */ - - translate( str, fromChars, toChars ) - { - let res = ''; - for ( let i = 0;i < str.length;i++ ) - { - const char = str[i]; - const index = fromChars.indexOf( char ); - if ( index !== -1 ) - res += toChars[index]; - else - res += char; - } - - return res; - } - - /* - Encode: String > Hex > Base64 - - encodes a human-readable string into a hex value, and then to base64 - - @usage const clib = new CLib() - const encoded = clib.encodeToHexBase64('hello'); // Njg2NTZjNmM2Zg== - const decoded = clib.decodeFromHexBase64(`${ encoded }`); // hello - */ - - encodeToHexBase64( str ) - { - const hex = [...str].map( ( char ) => - { - const code = char.charCodeAt( 0 ).toString( 16 ); - return code.padStart( 2, '0' ); - }).join( '' ); - - const base64 = btoa( hex ); - return base64; - } - - /* - Decode: Base64 > Hex > String - - decodes a base64 value to hex, and then back into a human readable string - - @usage const clib = new CLib() - const encoded = clib.encodeToHexBase64('hello'); // Njg2NTZjNmM2Zg== - const decoded = clib.decodeFromHexBase64(`${ encoded }`); // hello - */ - - decodeFromHexBase64( base64Str ) - { - const hex = atob( base64Str ); - const chars = hex.match( /.{1,2}/g ); // every 2 hex chars = 1 byte - - return chars.map( ( byte ) => String.fromCharCode( parseInt( byte, 16 ) ) ).join( '' ); - } -} - -/* - export class - - @usage import CLib from './classes/CLib.js'; -*/ - -// eslint-disable-next-line no-restricted-syntax -export default CLib; diff --git a/tvapp2/classes/Log.js b/tvapp2/classes/Log.js deleted file mode 100644 index b802b0ed..00000000 --- a/tvapp2/classes/Log.js +++ /dev/null @@ -1,121 +0,0 @@ -/* - Define > Logs - - When assigning text colors, terminals and the windows command prompt can display any color; however apps - such as Portainer console cannot. If you use 16 million colors and are viewing console in Portainer, colors will - not be the same as the rgb value. It's best to just stick to Chalk's default colors. - - Various levels of logs with the following usage: - Log.verbose(`This is verbose`) - Log.debug(`This is debug`) - Log.info(`This is info`) - Log.ok(`This is ok`) - Log.notice(`This is notice`) - Log.warn(`This is warn`) - Log.error( - `Error fetching sports data with error:`, - chalk.white(`β†’`), - chalk.grey(`This is the error message`) - ); - - Level Type - ----------------------------------- - 6 Trace - 5 Debug - 4 Info - 3 Notice - 2 Warn - 1 Error -*/ - -import fs from 'fs'; -import chalk from 'chalk'; - -/* - chalk.level - - @ref https://npmjs.com/package/chalk - - 0 All colors disabled - - 1 Basic color support (16 colors) - - 2 256 color support - - 3 Truecolor support (16 million colors) - - When assigning text colors, terminals and the windows command prompt can display any color; however apps - such as Portainer console cannot. If you use 16 million colors and are viewing console in Portainer, colors will - not be the same as the rgb value. It's best to just stick to Chalk's default colors. -*/ - -chalk.level = 3; - -/* - Define -*/ - -const LOG_LEVEL = process.env.LOG_LEVEL || 4; -const { name } = JSON.parse( fs.readFileSync( './package.json' ) ); - -/* - Class > Log -*/ - -class Log -{ - static now() - { - const now = new Date(); - return chalk.gray( `[${ now.toLocaleTimeString() }]` ); - } - - static verbose( ...msg ) - { - if ( LOG_LEVEL >= 6 ) - console.debug( chalk.white.bgBlack.blackBright.bold( ` ${ name } ` ), chalk.white( `βš™οΈ` ), this.now(), chalk.gray( msg.join( ' ' ) ) ); - } - - static debug( ...msg ) - { - if ( LOG_LEVEL >= 7 ) - console.trace( chalk.white.bgMagenta.bold( ` ${ name } ` ), chalk.white( `βš™οΈ` ), this.now(), chalk.magentaBright( msg.join( ' ' ) ) ); - else if ( LOG_LEVEL >= 5 ) - console.debug( chalk.white.bgGray.bold( ` ${ name } ` ), chalk.white( `βš™οΈ` ), this.now(), chalk.gray( msg.join( ' ' ) ) ); - } - - static info( ...msg ) - { - if ( LOG_LEVEL >= 4 ) - console.info( chalk.white.bgBlueBright.bold( ` ${ name } ` ), chalk.white( `ℹ️` ), this.now(), chalk.blueBright( msg.join( ' ' ) ) ); - } - - static ok( ...msg ) - { - if ( LOG_LEVEL >= 4 ) - console.log( chalk.white.bgGreen.bold( ` ${ name } ` ), chalk.white( `βœ…` ), this.now(), chalk.greenBright( msg.join( ' ' ) ) ); - } - - static notice( ...msg ) - { - if ( LOG_LEVEL >= 3 ) - console.log( chalk.white.bgYellow.bold( ` ${ name } ` ), chalk.white( `πŸ“Œ` ), this.now(), chalk.yellowBright( msg.join( ' ' ) ) ); - } - - static warn( ...msg ) - { - if ( LOG_LEVEL >= 2 ) - console.warn( chalk.white.bgYellow.bold( ` ${ name } ` ), chalk.white( `⚠️` ), this.now(), chalk.yellowBright( msg.join( ' ' ) ) ); - } - - static error( ...msg ) - { - if ( LOG_LEVEL >= 1 ) - console.error( chalk.white.bgRedBright.bold( ` ${ name } ` ), chalk.white( `❌` ), this.now(), chalk.redBright( msg.join( ' ' ) ) ); - } -} - -/* - export class - - @usage import Log from './classes/Log.js'; -*/ - -// eslint-disable-next-line no-restricted-syntax -export default Log; diff --git a/tvapp2/classes/Semaphore.js b/tvapp2/classes/Semaphore.js deleted file mode 100644 index c89fff3b..00000000 --- a/tvapp2/classes/Semaphore.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - Semaphore > Declare - - allows multiple threads to work with the same shared resources -*/ - -class Semaphore -{ - constructor( max ) - { - this.max = max; - this.queue = []; - this.active = 0; - } - - async acquire() - { - if ( this.active < this.max ) - { - this.active++; - return; - } - - return new Promise( ( resolve ) => this.queue.push( resolve ) ); - } - - release() - { - this.active--; - if ( this.queue.length > 0 ) - { - const resolve = this.queue.shift(); - this.active++; - resolve(); - } - } -} - -/* - export class - - @usage import Log from './classes/Log.js'; -*/ - -// eslint-disable-next-line no-restricted-syntax -export default Semaphore; - diff --git a/tvapp2/classes/Storage.js b/tvapp2/classes/Storage.js deleted file mode 100644 index 8ac60439..00000000 --- a/tvapp2/classes/Storage.js +++ /dev/null @@ -1,520 +0,0 @@ -/* - Class β€Ί Storage - - The storage classes allows you to save specific settings into a json file. These settings are better off being stored in - a local file, instead of using up the resources being saved in a database. - - Class supports multiple storage files, but by default, it will save settings in `www/config.json`. - - Settings include Tuner / HDHomeRun device information, etc. - - @usage - const storage = new Storage( envWebFolder, FILE_CFG ); -*/ - -import chalk from 'chalk'; -import path from 'path'; -import nconf from 'nconf'; -import fs from 'fs'; -import Log from './Log.js'; -import Utils from './Utils.js'; -import { fileURLToPath } from 'url'; - -/* - CJS β€Ί ESM -*/ - -const __filename = fileURLToPath( import.meta.url ); // get resolved path to file -const __dirname = path.dirname( __filename ); // get name of directory - -/* - Class β€Ί Storage - - constructor ( str:folder, str:file ) - Initialize ( bool:bForceNew ) - Setup ( bool:bForceNew ) - Get ( str:key ) - Set ( str:key, any:value ) - Save ( ) - GetConfig ( ) - isJsonString ( json:str ) - isJsonEmpty ( obj:json ) -*/ - -class Storage -{ - /* - Constructor β€Ί Storage - - Initializes a Storage instance for managing the config.json file. - Determines the full path to the config file based on folder and file arguments, - or uses the default static fileConfig if none are provided. - - Handles Node.js packaged apps (process.pkg) by adjusting paths accordingly. - - @args - folder (str) Optional folder where config.json will be stored. Defaults to 'www'. - file (str) Optional config file name. Defaults to static Storage.fileConfig. - - @usage - const storage = new Storage(envWebFolder, FILE_CFG); - */ - - static fileConfig = path.resolve( process.cwd( ), 'www', 'config.json' ); - - constructor( folder, file ) - { - this.folderWeb = folder || 'www'; - this.fileConfig = file ? path.resolve( folder, file ) : Storage.fileConfig; - - if ( process.pkg ) - this.fileConfig = path.join( path.dirname( process.execPath ), this.folderWeb, this.fileConfig ); - else - this.fileConfig = path.resolve( process.cwd( ), this.folderWeb, this.fileConfig ); - } - - /* - Initialize β€Ί Activate Config Setup with Logging - - Activates the Storage.Setup( ) function while providing detailed logging. - Ensures the user's config.json file exists, is valid, and is initialized - with default values if missing or corrupt. - - Steps: - - Logs the start of initialization. - - Calls Setup( ) with optional force flag to recreate config. - - Catches and logs any errors during setup. - - @args - bForceNew (bool) Optional. If true, forces the config file to be removed - and regenerated from defaults. - - @returns - (Promise) Resolves when initialization completes, or logs an error if setup fails. - - @usage - const storage = new Storage(envWebFolder, FILE_CFG); - await storage.Initialize(false); - */ - - async Initialize( bForceNew ) - { - Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `πŸ“£` ), chalk.blueBright( `` ), chalk.gray( `${ Utils.getFuncName( ) }` ) ); - - const bForce = bForceNew || false; - - try - { - Log.info( `conf`, chalk.yellow( `[initiate]` ), chalk.white( `ℹ️` ), - chalk.blueBright( `` ), chalk.gray( `Initializing config file` ), - chalk.blueBright( `` ), chalk.gray( `${ this.fileConfig }` ) ); - - await new Storage( ).Setup( bForce ); - } - catch ( err ) - { - console.log( 'Error writing Metadata.json:' + err.message ); - } - } - - /* - Initialize β€Ί Setup User Config File - - Sets up a user's config.json file, ensuring it exists and is valid JSON. - If the file is missing, empty, or invalid, it will be created or replaced. - Typically, you should call this via Storage( ).Initialize( ) rather than Setup( ) directly. - - Steps: - - Creates parent directory if it doesn't exist. - - Removes existing config if bForceNew is true. - - Validates existing JSON; backs up invalid files. - - Creates default config if missing. - - Wires up nconf with argv, env, file, and default values. - - @args - bForceNew (bool) Optional flag to force recreate the config file, wiping all existing data. - - @returns - (Promise) Resolves true when initialization completes successfully. - - @usage - const storage = new Storage(envWebFolder, FILE_CFG); - await storage.Initialize(false); - */ - - async Setup( bForceNew ) - { - Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `πŸ“£` ), chalk.blueBright( `` ), chalk.gray( `${ Utils.getFuncName( ) }` ) ); - - return new Promise( ( resolve, reject ) => - { - try - { - Log.info( `conf`, chalk.yellow( `[generate]` ), chalk.white( `ℹ️` ), - chalk.blueBright( `` ), chalk.gray( `Initializing storage setup` ), - chalk.blueBright( `` ), chalk.gray( `${ bForceNew }` ), - chalk.blueBright( `` ), chalk.gray( `${ this.fileConfig }` ) ); - - /* - ensure parent directory exists - */ - const dirPath = path.dirname( this.fileConfig ); - - if ( !fs.existsSync( dirPath ) ) - { - fs.mkdirSync( dirPath, { recursive: true }); - } - - /* - if force flag is true, remove existing config file (force) - */ - - if ( bForceNew === true && fs.existsSync( this.fileConfig ) ) - { - Log.ok( `conf`, chalk.yellow( `[generate]` ), chalk.white( `βœ…` ), - chalk.greenBright( `` ), chalk.gray( `Remove original config; force new` ), - chalk.greenBright( `` ), chalk.gray( `${ this.fileConfig }` ) ); - - try - { - fs.unlinkSync( this.fileConfig ); - } - catch ( e ) - { - Log.error( `conf`, chalk.redBright( `[generate]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `Failed to unlink existing config` ), - chalk.redBright( `` ), chalk.gray( `${ e.message }` ), - chalk.redBright( `` ), chalk.gray( `${ this.fileConfig }` ) ); - } - } - - /* - if config exists, validate JSON; if invalid, move to backup and recreate - */ - - if ( fs.existsSync( this.fileConfig ) ) - { - let raw = null; - let parsed = null; - - try - { - raw = fs.readFileSync( this.fileConfig, { encoding: 'utf8' }); - - if ( typeof raw !== 'string' || raw.trim( ).length === 0 ) - { - throw new Error( 'Empty config file' ); - } - - parsed = JSON.parse( raw ); - } - catch ( e ) - { - const backupPath = `${ this.fileConfig }.corrupt.${ Date.now( ) }`; - - try - { - fs.renameSync( this.fileConfig, backupPath ); - Log.error( `conf`, chalk.redBright( `[generate]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `Config file invalid; moved to backup` ), - chalk.redBright( `` ), chalk.gray( `${ backupPath }` ), - chalk.redBright( `` ), chalk.gray( `${ this.fileConfig }` ) ); - } - catch ( renameErr ) - { - Log.error( `conf`, chalk.redBright( `[generate]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `Unable to backup invalid config file` ), - chalk.redBright( `` ), chalk.gray( `${ renameErr.message }` ), - chalk.redBright( `` ), chalk.gray( `${ this.fileConfig }` ) ); - if ( this.rejected ) - { - reject( renameErr ); - return; - } - } - } - } - - /* - if config does not exist (or was just moved because it was corrupt), create it atomically - */ - - if ( !fs.existsSync( this.fileConfig ) ) - { - const defaults = - { - deviceId: 'FFFFFFFF' - }; - - const tempPath = `${ this.fileConfig }.tmp`; - - try - { - fs.writeFileSync( tempPath, JSON.stringify( defaults, null, 4 ), { encoding: 'utf8' }); - fs.renameSync( tempPath, this.fileConfig ); - - Log.ok( `conf`, chalk.yellow( `[generate]` ), chalk.white( `βœ…` ), - chalk.greenBright( `` ), chalk.gray( `Created new config file with defaults` ), - chalk.greenBright( `` ), chalk.gray( `${ this.fileConfig }` ) ); - } - catch ( writeErr ) - { - Log.error( `conf`, chalk.redBright( `[generate]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `Failed to create config file` ), - chalk.redBright( `` ), chalk.gray( `${ writeErr.message }` ), - chalk.redBright( `` ), chalk.gray( `${ this.fileConfig }` ) ); - - if ( this.rejected ) - { - reject( writeErr ); - return; - } - } - } - - /* - now that file exists and is valid JSON, wire up nconf - */ - - nconf.argv( ).env({ parseValues: true }).file({ file: this.fileConfig }).defaults( - { - deviceId: 'FFFFFFFF' - }); - } - catch ( err ) - { - Log.error( `conf`, chalk.redBright( `[generate]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `Could not generate and write to new config file` ), - chalk.redBright( `` ), chalk.gray( `${ err.message }` ), - chalk.redBright( `` ), chalk.gray( `${ this.fileConfig }` ) ); - - if ( this.rejected ) - { - reject( err ); - return; - } - } - - resolve( true ); - }); - } - - /* - Get β€Ί Retrieve Configuration Value - - Fetches a stored value from the application's persistent configuration - using the provided key via the nconf module. - - This function is static, so it can be called without creating a Storage instance. - - @args - key (str) The configuration key to retrieve. - - @returns - (any) The value associated with the key, or undefined if the key does not exist. - - @usage - const deviceId = Storage.Get('deviceId'); - */ - - static Get( key ) - { - Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `πŸ“£` ), chalk.blueBright( `` ), chalk.gray( `${ Utils.getFuncName( ) }` ) ); - - return nconf.get( key ); - } - - /* - Set β€Ί Store Configuration Value - - Stores a value in the application's persistent configuration using - the provided key via the nconf module. Automatically saves the - updated configuration to disk by calling Storage.Save( ). - - This function is static, so it can be called without creating a Storage instance. - - @args - key (str) The configuration key to set. - value (any) The value to store under the specified key. - - @returns - (void) No return value. - - @usage - Storage.Set('deviceId', '105B35EF'); - */ - - static Set( key, value ) - { - Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `πŸ“£` ), - chalk.blueBright( `` ), chalk.gray( `${ Utils.getFuncName( ) }` ) ); - - nconf.set( key, value ); - Storage.Save( ); - } - - /* - Save β€Ί Persist Configuration to Disk - - Saves the current configuration stored in nconf to disk. - After saving, the method reads back the file to verify it is valid JSON - and logs detailed status messages about success or errors. - - @purpose - - Calls nconf.save() to write the current configuration. - - Reads back the saved file. - - Parses the file as JSON to confirm validity. - - Logs success or detailed error messages for failures. - - @args - none - - @returns - (void) Logs success or error; does not return a value. - - @usage - Storage.Save(); - */ - - static Save( ) - { - const filePath = this.fileConfig; - - Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `πŸ“£` ), - chalk.blueBright( `` ), chalk.gray( `${ Utils.getFuncName( ) }` ) ); - - nconf.save( ( err ) => - { - if ( err ) - { - Log.error( `conf`, chalk.redBright( `[snapshot]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `Could not save config` ), - chalk.redBright( `` ), chalk.gray( `${ err }` ), - chalk.redBright( `` ), chalk.gray( `${ filePath }` ) ); - return; - } - - fs.readFile( filePath, ( err, data ) => - { - if ( err ) - { - Log.error( `conf`, chalk.redBright( `[snapshot]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `Unable to read config file` ), - chalk.redBright( `` ), chalk.gray( `${ err }` ), - chalk.redBright( `` ), chalk.gray( `${ filePath }` ) ); - return; - } - - try - { - const parsed = JSON.parse( data.toString( ) ); - - Log.ok( `conf`, chalk.yellow( `[snapshot]` ), chalk.white( `βœ…` ), - chalk.greenBright( `` ), chalk.gray( `Save to config file successful` ), - chalk.greenBright( `` ), chalk.gray( `${ filePath }` ) ); - - Log.debug( `conf`, chalk.yellow( `[snapshot]` ), chalk.white( `βš™οΈ` ), - chalk.blueBright( `` ), chalk.gray( `Read values from saved config file` ), - chalk.blueBright( `` ), chalk.gray( `${ filePath }` ), - chalk.blueBright( `` ), chalk.gray( `${ JSON.stringify( parsed ) }` ) ); - } - catch ( parseErr ) - { - Log.error( `conf`, chalk.redBright( `[snapshot]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `Config file is not valid JSON` ), - chalk.redBright( `` ), chalk.gray( `${ parseErr.message }` ), - chalk.redBright( `` ), chalk.gray( `${ filePath }` ) ); - } - }); - }); - } - - /* - GetConfig β€Ί Return Full Path to Config File - - Returns the full path to the currently used config.json file for this Storage instance. - This is useful when you need to know the exact file location without reading its contents. - - @args - none - - @returns - (str) Absolute path to the config.json file. - - @usage - const storage_config = Storage.GetConfig(); - */ - - static GetConfig( ) - { - Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `πŸ“£` ), chalk.blueBright( `` ), chalk.gray( `${ Utils.getFuncName( ) }` ) ); - - return this.fileConfig; - } - - /* - isJsonString β€Ί Check if Input is Valid JSON - - Determines whether a given string is valid JSON by attempting - to parse it. Returns true if parsing succeeds, false if it throws - an error. - - @args - json (str) The string to test for valid JSON. - - @returns - (bool) True if input is valid JSON, false otherwise. - - @usage - const valid = Storage.isJsonString('{"key":"value"}'); // returns true - */ - - static isJsonString( json ) - { - Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `πŸ“£` ), chalk.blueBright( `` ), chalk.gray( `${ Utils.getFuncName( ) }` ) ); - - try - { - JSON.parse( json ); - } - catch ( e ) - { - return false; - } - - return true; - } - - /* - helper β€Ί json object empty - */ - - static isJsonEmpty( json ) - { - Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `πŸ“£` ), chalk.blueBright( `` ), chalk.gray( `${ Utils.getFuncName( ) }` ) ); - - if ( Object.keys( json ).length === 0 ) - return true; - - if ( JSON.stringify( json ) === '\"{}\"' ) - return true; - - for ( const key in json ) - { - if ( ! Object.prototype.hasOwnProperty.call( json, key ) ) - return true; - } - - return false; - } -} - -/* - export class - - @import - import Storage from './classes/Storage.js'; -*/ - -// eslint-disable-next-line no-restricted-syntax -export default Storage; diff --git a/tvapp2/classes/Tuner.js b/tvapp2/classes/Tuner.js deleted file mode 100644 index 944f50aa..00000000 --- a/tvapp2/classes/Tuner.js +++ /dev/null @@ -1,455 +0,0 @@ -/* - Class β€Ί Tuner - - Handles HDHomeRun device management and deviceId lifecycle. - - @purpose - - Generate / format HDHomeRun device IDs. - - Validate device IDs against HDHomeRun rules (length, hex chars, checksum). - - Persist device IDs using Storage class. - - Automatically generate new device ID if missing, invalid, or uninitialized (FFFFFFFF). - - Initialize tuner instances with validated device IDs. - - @usage - await new Tuner( Storage.Get( 'deviceId' ) ).Initialize( ); - const tuner = new Tuner( ); - await tuner.Initialize( ); - const validId = await tuner.VerifyDeviceId( ); - - @notes - - Device IDs are persisted via the Storage class (config.json). - - User's device id must be valid before HDHomeRun will initialize. -*/ - - -import chalk from 'chalk'; -import Storage from './Storage.js'; -import Utils from './Utils.js'; -import Log from './Log.js'; - -/* - Class β€Ί Tuner - - constructor ( str:deviceId ) - Initialize ( ) - Start ( ) - _GenerateDeviceId ( int:len ) - GenerateDeviceId ( ) - GetDeviceId ( ) - FormatDeviceId ( str:deviceid ) - IsDeviceIdValid ( ) - VerifyDeviceId ( ) -*/ - -class Tuner -{ - constructor( deviceId ) - { - Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `πŸ“£` ), chalk.blueBright( `` ), chalk.gray( `${ Utils.getConstructorName( ) }` ) ); - - this.Name = `HDHomeRun`; - this.FriendlyName = `TVApp2`; - this.ModelNumber = `HDHR5-4US`; - this.FirmwareName = `hdhomerun5_atsc`; - this.FirmwareVersion = `0.9.15.00-RC04`; - this.SlotsConnected = 0; - this.SlotsMax = 10; - this.DeviceId = deviceId || Storage.Get( 'deviceId' ); - } - - /* - Initialize β€Ί Setup and Start Tuner - - Initializes the tuner by calling the Start( ) method. - Catches and logs any errors encountered during startup. - - @args - none - - @returns - (void) Logs status; does not return a value. - - @usage - await tuner.Initialize( ); - */ - - async Initialize( ) - { - Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `πŸ“£` ), chalk.blueBright( `` ), chalk.gray( `${ Utils.getFuncName( ) }` ) ); - - try - { - await this.Start( ); - } - catch ( err ) - { - Log.error( `hdhr`, chalk.redBright( `[initiate]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `Failure initializing tuner` ), - chalk.redBright( `` ), chalk.gray( `${ err.message }` ) ); - } - } - - /* - Start β€Ί Initialize and Verify Device ID - - Starts the tuner by verifying the current deviceId. - If the deviceId is missing or invalid, it will be regenerated and validated. - Logs the status of the deviceId once verification completes. - - @args - none - - @returns - (bool) true if deviceId is valid after verification, false otherwise. - - @usage - await tuner.Start( ); - */ - - async Start( ) - { - Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `πŸ“£` ), chalk.blueBright( `` ), chalk.gray( `${ Utils.getFuncName( ) }` ) ); - - const verifiedId = await new Tuner( ).VerifyDeviceId( this.DeviceId ); - - if ( await this.IsDeviceIdValid( verifiedId ) ) - { - Log.ok( `conf`, chalk.yellow( `[validate]` ), chalk.white( `βœ…` ), - chalk.greenBright( `` ), chalk.gray( `User has valid deviceId` ), - chalk.greenBright( `` ), chalk.gray( `${ verifiedId }` ) ); - } - } - - /* - _GenerateDeviceId β€Ί Generate Raw Random Hexadecimal String - - Generates a raw random hexadecimal string using Node.js crypto module. - This is typically used as the random portion of a deviceId. - - @args - len (int) Optional number of bytes to generate. Defaults to 4 bytes. - - @returns - (str) Uppercase hexadecimal string, length = len * 2 characters. - - @usage - const randomHex = Tuner._GenerateDeviceId( 4 ); // 8-character hex string - */ - - static _GenerateDeviceId( len ) - { - Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `πŸ“£` ), chalk.blueBright( `` ), chalk.gray( `${ Utils.getFuncName( ) }` ) ); - - return crypto.randomBytes( len || 4 ).toString( 'hex' ).toUpperCase( ); - } - - /* - GenerateDeviceId β€Ί Generate New HDHomeRun Device ID - - Generates a new, properly formatted HDHomeRun deviceId. - - Steps: - - Generates 4 random hexadecimal characters. - - Prepends '105' and appends '0' to form base deviceId. - - Passes baseId to Tuner.FormatDeviceId( ) to ensure correct checksum and 8-character format. - - @args - None - - @returns - (str) A valid, 8-character HDHomeRun deviceId in uppercase hexadecimal. - - @usage - const newDeviceId = Tuner.GenerateDeviceId( ); - */ - - static GenerateDeviceId( ) - { - Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `πŸ“£` ), chalk.blueBright( `` ), chalk.gray( `${ Utils.getFuncName( ) }` ) ); - - const chars = '0123456789ABCDEF'; - let randomHex = ''; - - // generate 4 random hexadecimal chars - for ( let i = 0;i < 4;i++ ) - { - randomHex += chars[Math.floor( Math.random( ) * chars.length )]; - } - - const baseId = '105' + randomHex + '0'; - return this.FormatDeviceId( baseId ); - } - - /* - GetDeviceId β€Ί Retrieve Stored HDHomeRun Device ID - - Fetches the current deviceId from persistent storage (via Storage.Get). - - @args - None - - @returns - (str) The current deviceId stored in configuration. - - @usage - const deviceId = await tuner.GetDeviceId( ); - */ - - GetDeviceId( ) - { - Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `πŸ“£` ), chalk.blueBright( `` ), chalk.gray( `${ Utils.getFuncName( ) }` ) ); - - return Storage.Get( 'deviceId' ); - } - - /* - FormatDeviceId β€Ί Validate and Format HDHomeRun Device ID - - Fetches the provided deviceId (or instance default) and ensures it is valid - according to HDHomeRun rules, then returns a properly formatted ID. - - Steps: - - Input must be exactly 8 hexadecimal characters. - - All characters must be 0-9 or A-F/a-f. - - Computes checksum using HDHomeRun-specific lookup table. - - Generates a new deviceId integer with checksum applied. - - Converts back to 8-character uppercase hexadecimal string. - - Logs detailed errors if the input deviceId is invalid. - - @args - deviceid (str) Optional deviceId to format. Defaults to instance deviceId. - - @returns - (str|int) Formatted 8-character hex deviceId, or 0 if input invalid. - - @usage - const formattedId = Tuner.FormatDeviceId( someDeviceId ); - */ - - static FormatDeviceId( deviceid ) - { - Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `πŸ“£` ), chalk.blueBright( `` ), chalk.gray( `${ Utils.getFuncName( ) }` ) ); - - const deviceId = deviceid || this.DeviceId; - - /* - Validate input length - */ - - if ( !deviceId || deviceId.length !== 8 ) - { - Log.error( `hdhr`, chalk.redBright( `[validate]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `HDHomeRun deviceId must be 8 hexadecimals` ), - chalk.redBright( `` ), chalk.gray( `${ deviceId }` ) ); - - return 0; - } - - /* - All chars should be valid hexadecimal - */ - - const hexPattern = /^[0-9A-Fa-f]+$/; - if ( !hexPattern.test( deviceId ) ) - { - Log.error( `hdhr`, chalk.redBright( `[validate]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `HDHomeRun deviceId must contain all hex (0-9, A-F, a-f)` ), - chalk.redBright( `` ), chalk.gray( `${ deviceId }` ) ); - - return 0; - } - - /* - Hex string to integer - */ - - const deviceIdInt = parseInt( deviceId, 16 ); - - /* - Checksum lookup table - */ - - const checksumLookup = - [ - 0xA, 0x5, 0xF, 0x6, 0x7, 0xC, 0x1, 0xB, 0x9, 0x2, 0x8, 0xD, 0x4, 0x3, 0xE, 0x0 - ]; - - /* - Calc checksum - */ - - let checksum = 0; - checksum ^= checksumLookup[( deviceIdInt >> 28 ) & 0x0F]; - checksum ^= ( deviceIdInt >> 24 ) & 0x0F; - checksum ^= checksumLookup[( deviceIdInt >> 20 ) & 0x0F]; - checksum ^= ( deviceIdInt >> 16 ) & 0x0F; - checksum ^= checksumLookup[( deviceIdInt >> 12 ) & 0x0F]; - checksum ^= ( deviceIdInt >> 8 ) & 0x0F; - checksum ^= checksumLookup[( deviceIdInt >> 4 ) & 0x0F]; - - /* - Calc new device ID - */ - - const newDevId = ( deviceIdInt & 0xFFFFFFF0 ) + checksum; - - /* - Convert back to hex string; ensure we get 8 characters with leading zeros; convert to uppercase - */ - - return newDevId.toString( 16 ).toUpperCase( ).padStart( 8, '0' ); - } - - /* - IsDeviceIdValid β€Ί Validate HDHomeRun Device ID - - Checks if the current deviceId on this instance is valid according to HDHomeRun rules. - - Validation steps: - - Must be exactly 8 characters long. - - All characters must be hexadecimal (0-9, A-F, a-f). - - Computes checksum using HDHomeRun-specific lookup table; must equal 0. - - Logs detailed errors if the deviceId fails any validation step. - - @returns - (bool) true if deviceId is valid, false otherwise. - - @usage - const isValid = await tuner.IsDeviceIdValid( ); - */ - - async IsDeviceIdValid( ) - { - Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `πŸ“£` ), chalk.blueBright( `` ), chalk.gray( `${ Utils.getFuncName( ) }` ) ); - - /* - Define Hexadecimal charset (0-9, A-F, a-f) - */ - - const hexDigits = new Set( '0123456789ABCDEFabcdef' ); - const deviceId = this.DeviceId; - - /* - Check if device ID is exactly 8 characters - */ - - if ( !deviceId || deviceId.length !== 8 ) - { - Log.error( `hdhr`, chalk.redBright( `[validate]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `HDHomeRun deviceId must be 8 hexadecimals` ), - chalk.redBright( `` ), chalk.gray( `${ deviceId }` ) ); - - return false; - } - - /* - Check if all characters are hexadecimal - */ - - if ( !Array.from( deviceId ).every( ( c ) => hexDigits.has( c ) ) ) - { - Log.error( `hdhr`, chalk.redBright( `[validate]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `HDHomeRun deviceId must contain all hex (0-A)` ), - chalk.redBright( `` ), chalk.gray( `${ deviceId }` ) ); - - return false; - } - - /* - Convert hex string to integer (equivalent to int.from_bytes with big endian) - */ - - const deviceIdInt = parseInt( deviceId, 16 ); - - /* - Checksum lookup table - */ - - const checksumLookup = - [ - 0xA, 0x5, 0xF, 0x6, 0x7, 0xC, 0x1, 0xB, 0x9, 0x2, 0x8, 0xD, 0x4, 0x3, 0xE, 0x0 - ]; - - /* - Calc checksum - */ - - let checksum = 0; - checksum ^= checksumLookup[( deviceIdInt >>> 28 ) & 0x0F]; - checksum ^= ( deviceIdInt >>> 24 ) & 0x0F; - checksum ^= checksumLookup[( deviceIdInt >>> 20 ) & 0x0F]; - checksum ^= ( deviceIdInt >>> 16 ) & 0x0F; - checksum ^= checksumLookup[( deviceIdInt >>> 12 ) & 0x0F]; - checksum ^= ( deviceIdInt >>> 8 ) & 0x0F; - checksum ^= checksumLookup[( deviceIdInt >>> 4 ) & 0x0F]; - checksum ^= ( deviceIdInt >>> 0 ) & 0x0F; - - return checksum === 0; - } - - /* - VerifyDeviceId β€Ί Validate / Generate Device ID - - Checks if the current deviceId on this instance is valid. - - If missing, uninitialized ('FFFFFFFF'), or fails validation: - a new deviceId is generated via the static Tuner.GenerateDeviceId( ) method. - - New deviceId is saved to persistent storage via Storage.Set( ) and - updated on the instance. - - Function also recursively verifies until a valid deviceId is established. - - @returns - (str) A valid deviceId for this tuner instance. - - @usage - const validId = await tuner.VerifyDeviceId( ); - */ - - async VerifyDeviceId( ) - { - Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `πŸ“£` ), chalk.blueBright( `` ), chalk.gray( `${ Utils.getFuncName( ) }` ) ); - - const deviceId = this.DeviceId; - - if ( !deviceId || deviceId === 'FFFFFFFF' || !await this.IsDeviceIdValid( ) ) - { - const deviceIdNew = Tuner.GenerateDeviceId( ); // static generates a properly formatted ID - if ( deviceId === 'FFFFFFFF' ) - { - Log.info( `conf`, chalk.yellow( `[generate]` ), chalk.white( `πŸ“£` ), - chalk.yellow( `` ), chalk.gray( `Generating HDHomeRun deviceId for the first time` ), - chalk.yellow( `` ), chalk.gray( `${ deviceIdNew }` ) ); - } - else - { - Log.error( `conf`, chalk.redBright( `[generate]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `Invalid deviceId; generating new` ), - chalk.redBright( `` ), chalk.gray( `${ deviceId }` ), - chalk.redBright( `` ), chalk.gray( `${ deviceIdNew }` ) ); - } - - Storage.Set( 'deviceId', deviceIdNew ); // save to JSON via nconf - this.DeviceId = deviceIdNew; // update the instance so validation works - - // verify recursively until valid - const verifiedId = await this.VerifyDeviceId( ); - return verifiedId; - } - - return deviceId; - } -} - -/* - export class - - @image - import Tuner from './classes/Tuner.js'; -*/ - -// eslint-disable-next-line no-restricted-syntax -export default Tuner; diff --git a/tvapp2/classes/Utils.js b/tvapp2/classes/Utils.js deleted file mode 100644 index f0e0def3..00000000 --- a/tvapp2/classes/Utils.js +++ /dev/null @@ -1,47 +0,0 @@ -class Utils -{ - /* - Returns the name of the function that this function was called from. - used for Log.verbose - */ - - static getFuncName() - { - return ( new Error() ).stack.match( /at (\S+)/g )[1].slice( 3 ); - } - - /* - Returns the name of the constructor that this function was called from. - used for Log.verbose - */ - - static getConstructorName() - { - return ( new Error() ).stack.match( /new\s+(\w+)/g )[0]; - } - - /* - helper > str2bool - */ - - static str2bool( str ) - { - if ( typeof str === 'string' ) - { - const lower = str.toLowerCase(); - if ([ - '1', 'true', 'yes', 'y', 't' - ].includes( lower ) ) - str = true; - if ([ - '0', 'false', 'no', 'n', 'f' - ].includes( lower ) ) - str = false; - return str; - } - else return Boolean( str ); - } -} - -// eslint-disable-next-line no-restricted-syntax -export default Utils; diff --git a/tvapp2/eslint.config.mjs b/tvapp2/eslint.config.mjs deleted file mode 100644 index c4073f13..00000000 --- a/tvapp2/eslint.config.mjs +++ /dev/null @@ -1,273 +0,0 @@ -/* - Eslint 9 Flat Config - - old eslint < 8 .rc files are no longer supported! do not place .eslintrc files in subfolders. - eslint developers are currently working on an experimental feature to allow for sub-folder - override rules - @ref https://github.com/eslint/eslint/discussions/18574#discussioncomment-9729092 - https://eslint.org/docs/latest/use/configure/configuration-files#experimental-configuration-file-resolution - - eslint config migration docs - @ref https://eslint.org/docs/latest/use/configure/migration-guide -*/ - -import path from 'path'; -import globals from 'globals'; -import js from '@eslint/js'; -import { FlatCompat } from '@eslint/eslintrc'; - -/* - Plugins -*/ - -import pluginImport from 'eslint-plugin-import'; -import pluginNode from 'eslint-plugin-n' -import pluginChaiFriendly from 'eslint-plugin-chai-friendly'; -import pluginStylistic from '@stylistic/eslint-plugin' - -/* - Globals -*/ - -const customGlobals = -{ - guid: 'readable', - uuid: 'readable', - Buffer: "readonly", - BufferEncoding: "readonly" -}; - -/* - Compatibility -*/ - -import { fileURLToPath } from 'url'; -const __filename = fileURLToPath(import.meta.url); // get resolved path to file -const __dirname = path.dirname(__filename); // get name of directory - -const compat = new FlatCompat({ - baseDirectory: __dirname, // optional; default: process.cwd() - resolvePluginsRelativeTo: __dirname, // optional - recommendedConfig: js.configs.recommended, // optional unless using 'eslint:recommended' - allConfig: js.configs.all, // optional unless using 'eslint:all' -}); - -/* - Eslint > Flat Config -*/ - -export default -[ - { - ignores: [ - 'coverage/**', - 'node_modules/**', - '**/dist/**/*', - '**/__tmp__/**/*', - 'eslint.config.mjs', - 'eslint.config.cjs', - "root.js", - "www/**/*" - ] - }, - ...compat.extends('eslint:recommended'), - { - plugins: { - 'n': pluginNode, - 'import': pluginImport, - '@stylistic': pluginStylistic, - 'chai-friendly': pluginChaiFriendly - }, - linterOptions: { - reportUnusedDisableDirectives: false - }, - languageOptions: { - globals: { - ...customGlobals, - ...globals.browser, - process: true, // Node.js global - _: true, - $: true - }, - sourceType: 'module', - ecmaVersion: 'latest', - parserOptions: { - requireConfigFile: false - } - }, - rules: { - // eslint/js rules - 'one-var': 'off', - 'no-throw-literal': 'off', - - 'camelcase': [ - 'error', - { - 'properties': 'always' - } - ], - - 'no-unused-vars': 'off', - 'no-console': 'off', - 'no-alert': 'error', - 'no-debugger': 'error', - 'prefer-arrow-callback': 'error', - 'no-useless-escape': 'off', - 'no-var': 'error', - 'prefer-const': 'error', - 'no-unused-expressions': 0, - 'chai-friendly/no-unused-expressions': 'off', - 'strict': ['error', 'never'], - 'prefer-promise-reject-errors': 'off', - 'no-object-constructor': 'error', - 'object-shorthand': 'off', - 'no-array-constructor': 'error', - 'array-callback-return': 'error', - 'no-eval': 'error', - 'no-new-func': 'error', - 'prefer-rest-params': 'error', - 'prefer-spread': 'error', - 'no-useless-constructor': 'error', - 'no-dupe-class-members': 'error', - 'no-duplicate-imports': 'error', - 'eqeqeq': 'error', - 'no-unneeded-ternary': 'error', - 'curly': 'off', - - 'no-empty': 'off', - 'no-restricted-syntax': [ - 'error', - { - 'selector': 'ExportDefaultDeclaration', - 'message': 'Prefer named exports' - } - ], - 'import/no-webpack-loader-syntax': 'off', - 'import/no-relative-parent-imports': 'error', - 'import/first': 'error', - 'import/no-default-export': 'off', - 'node/no-callback-literal': 0, - - /* - @plugin eslint-plugin-n - */ - - 'n/no-callback-literal': 0, - 'n/no-deprecated-api': 'error', - 'n/no-exports-assign': 'error', - 'n/no-extraneous-import': 'error', - 'n/no-extraneous-require': [ - 'error', - { - 'allowModules': ['electron', 'electron-notarize'], - 'resolvePaths': [], - 'tryExtensions': [] - } - ], - 'n/no-missing-import': 'off', - 'n/no-missing-require': 'off', - 'n/no-mixed-requires': 'error', - 'n/no-new-require': 'error', - 'n/no-path-concat': 'error', - 'n/no-process-env': 'off', - 'n/no-process-exit': 'off', - 'n/no-restricted-import': 'error', - 'n/no-restricted-require': 'error', - 'n/no-sync': 'off', - 'n/no-unpublished-bin': 'error', - 'n/no-unpublished-import': 'error', - 'n/no-unpublished-require': 'error', - 'n/no-unsupported-features/es-builtins': 'error', - 'n/no-unsupported-features/es-syntax': 'error', - 'n/no-unsupported-features/node-builtins': 'off', - 'n/prefer-global/buffer': 'error', - 'n/prefer-global/console': 'error', - 'n/prefer-global/process': 'error', - 'n/prefer-global/text-decoder': 'error', - 'n/prefer-global/text-encoder': 'error', - 'n/prefer-global/url': 'error', - 'n/prefer-global/url-search-params': 'error', - 'n/prefer-node-protocol': 'off', - 'n/prefer-promises/dns': 'off', - 'n/prefer-promises/fs': 'off', - 'n/process-exit-as-throw': 'error', - '@stylistic/object-property-newline': 'off', - '@stylistic/no-multi-spaces': [ 0, { ignoreEOLComments: true } ], - '@stylistic/arrow-spacing': [ 'error', { before: true, after: true } ], - '@stylistic/semi-spacing': ['error', { - before: false, - after: false, - }], - "@stylistic/space-before-function-paren": ["error", { - anonymous: "always", - asyncArrow: "never", - named: "never" - }], - '@stylistic/padded-blocks': ['error', { - blocks: 'never', - switches: 'never', - classes: 'never', - }], - '@stylistic/arrow-parens': [ 'error', 'always' ], - '@stylistic/block-spacing': [ 'error', 'always' ], - '@stylistic/comma-dangle': [ 'error', 'never' ], - '@stylistic/comma-spacing': [ 'error', { before: false, after: true }], - '@stylistic/computed-property-spacing': ['error', 'never'], - '@stylistic/no-mixed-operators': ['off'], - '@stylistic/eol-last': ['error', 'always'], - '@stylistic/jsx-quotes': ['error', 'prefer-single'], - '@stylistic/linebreak-style': ['error', 'unix'], - '@stylistic/no-mixed-spaces-and-tabs': ['error'], - '@stylistic/no-tabs': ['error'], - '@stylistic/no-trailing-spaces': ['error', { skipBlankLines: true, ignoreComments: true }], - '@stylistic/no-whitespace-before-property': ['error'], - '@stylistic/object-curly-spacing': ['error', 'always'], - '@stylistic/quote-props': ['error', 'as-needed'], - '@stylistic/quotes': ['error', 'single', { allowTemplateLiterals: 'always' }], - '@stylistic/semi': ['error', 'always'], - '@stylistic/space-infix-ops': ['error'], - '@stylistic/template-curly-spacing': ['error', 'always'], - '@stylistic/template-tag-spacing': ['error', 'always'], - '@stylistic/space-in-parens': [ 'error', 'always', - { - exceptions: ["{}", "[]"] - }], - '@stylistic/spaced-comment': [ 'error', 'always', - { - markers: ['/'] - }], - '@stylistic/array-bracket-newline': [ 'warn', - { - multiline: true, - minItems: 5, - }], - '@stylistic/brace-style': [ 'error', 'allman', - { - allowSingleLine: true, - }], - '@stylistic/array-bracket-spacing': [ 'error', 'always', - { - arraysInArrays: false, - objectsInArrays: false, - singleValue: false, - }], - '@stylistic/wrap-iife': [2, 'inside', { functionPrototypeMethods: true }], - '@stylistic/keyword-spacing': [ 'error', - { - before: true, - after: true, - overrides: - { - return: { before: true, after: true }, - throw: { before: true, after: true }, - case: { before: true, after: true }, - as: { before: true, after: true }, - if: { before: true, after: true }, - for: { before: true, after: true }, - while: { before: true, after: true }, - static: { before: true, after: true } - } - }], - }, - } -]; diff --git a/tvapp2/index.js b/tvapp2/index.js deleted file mode 100755 index a5092bab..00000000 --- a/tvapp2/index.js +++ /dev/null @@ -1,2842 +0,0 @@ -#!/usr/bin/env node - -/* - Import Packages -*/ - -import fs from 'fs'; -import path from 'path'; -import http from 'http'; -import https from 'https'; -import os from 'node:os'; -import osName from 'os-name'; -import getos from 'getos'; -import zlib from 'zlib'; -import chalk from 'chalk'; -import ejs from 'ejs'; -import moment from 'moment'; -import TimeAgo from 'javascript-time-ago'; -import en from 'javascript-time-ago/locale/en'; -import Log from './classes/Log.js'; -import Storage from './classes/Storage.js'; -import Utils from './classes/Utils.js'; -import CLib from './classes/CLib.js'; -import Semaphore from './classes/Semaphore.js'; -import Tuner from './classes/Tuner.js'; -import cron, { schedule } from 'node-cron'; -import * as child from 'child_process'; -import * as crons from 'cron'; - -/* - Old CJS variables converted to ESM -*/ - -import { fileURLToPath } from 'url'; - -/* - Initialize classes -*/ - -const cache = new Map(); -const clib = new CLib(); - -const encoded = clib.encodeToHexBase64( 'tvapp2' ); -const decoded = clib.decodeFromHexBase64( `${ encoded }` ); - -/* - Import package.json values -*/ - -const { name, author, version, repository, discord, docs } = JSON.parse( fs.readFileSync( './package.json' ) ); -const __filename = fileURLToPath( import.meta.url ); // get resolved path to file -const __dirname = path.dirname( __filename ); // get name of directory - -/* - const gitHash = child.execSync( 'git rev-parse HEAD' ).toString().trim(); -*/ - -/* - chalk.level - - @ref https://npmjs.com/package/chalk - - 0 All colors disabled - - 1 Basic color support (16 colors) - - 2 256 color support - - 3 Truecolor support (16 million colors) - - When assigning text colors, terminals and the windows command prompt can display any color; however apps - such as Portainer console cannot. If you use 16 million colors and are viewing console in Portainer, colors will - not be the same as the rgb value. It's best to just stick to Chalk's default colors. -*/ - -chalk.level = 3; - -/* - timeAgo -*/ - -TimeAgo.addDefaultLocale( en ); -const timeAgo = new TimeAgo( ); - -/* - Define β€Ί General - - @note if you change `envWebFolder`; ensure you re-name the folder where the - website assets are stored. -*/ - -let FILE_CFG; -let FILE_URL; -let FILE_M3U; -let FILE_XML; -let FILE_GZP; -let FILE_M3U_SIZE = 0; -let FILE_XML_SIZE = 0; -let FILE_GZP_SIZE = 0; -let FILE_M3U_MODIFIED = 0; -let FILE_XML_MODIFIED = 0; -let FILE_GZP_MODIFIED = 0; - -/* - Define β€Ί Environment Variables || Defaults -*/ - -const envAppRelease = process.env.RELEASE || 'stable'; -const envUrlRepo = process.env.URL_REPO || 'https://git.binaryninja.net/binaryninja'; -const envXmlEpg = process.env.URL_EPG || 'https://epg.binaryninja.net/XMLTV-EPG'; -const envStreamQuality = process.env.STREAM_QUALITY || 'hd'; -const envFileURL = process.env.FILE_URL || 'urls.txt'; -const envFileM3U = process.env.FILE_M3U || 'playlist.m3u8'; -const envFileXML = process.env.FILE_EPG || 'xmltv.xml'; -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 envWebFolder = process.env.WEB_FOLDER || 'www'; -const envHdhrPort = process.env.HDHR_PORT || `6077`; -const envWebEncoding = process.env.WEB_ENCODING || 'deflate, br'; -const envProxyHeader = process.env.WEB_PROXY_HEADER || 'x-forwarded-for'; -const envHealthTimer = process.env.HEALTH_TIMER || 600000; -const envTaskCronSync = process.env.TASK_CRON_SYNC || '0 0 */3 * *'; -const envGitSHA1 = process.env.GIT_SHA1 || '0000000000000000000000000000000000000000'; -const LOG_LEVEL = process.env.LOG_LEVEL || 4; - -/* - Server -*/ - -let serverOs = 'Unknown'; -let serverStartup = 0; - -/* - Define β€Ί Externals -*/ - -const extURL = `${ envUrlRepo }/tvapp2-externals/raw/branch/main/urls.txt`; -const extXML = `${ envXmlEpg }/xmltv_v2.0.0.xml`; -const extM3U = `${ envXmlEpg }/formatted_v2.0.0.dat`; -//const extM3U = `${ envUrlRepo }/tvapp2-externals/raw/branch/main/formatted.dat`; - -/* - Define β€Ί Defaults -*/ - -let urls = []; -const gCookies = {}; -const USERAGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:138.0) Gecko/20100101 Firefox/138.0'; - -/* - Web url shortcuts - - using any of the following subdomains / subpaths will trigger the download for that specific file - - @example http://127.0.0.1:4124/gzip - http://127.0.0.1:4124/gz - http://127.0.0.1:4124/playlist - http://127.0.0.1:4124/key - http://127.0.0.1:4124/channel?url=https://thetvapp.to/tv/bbc-america-live-stream/ - http://127.0.0.1:4124/api/health -*/ - -const subdomainGZP = [ 'gzip', 'gz' ]; -const subdomainM3U = [ 'playlist', 'm3u', 'm3u8' ]; -const subdomainEPG = [ 'guide', 'epg', 'xml' ]; -const subdomainKey = [ 'key', 'keys' ]; -const subdomainChan = [ 'channels', 'channel', 'chan' ]; -const subdomainHealth = [ 'api/status', 'api/health' ]; -const subdomainRestart = [ 'api/restart', 'api/sync', 'api/resync' ]; - -/* - 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`; - -/* - Hosts -*/ - -const hosts = -[ - { name: 'TVPass.org', url: 'https://tvpass.org' }, - { name: 'TheTVApp.to', url: 'https://thetvapp.to' }, - { name: 'MoveOnJoy.com', url: 'http://moveonjoy.com' }, - { name: 'Daddylive.dad', url: 'https://daddylivestream.com' }, - { name: 'git.binaryninja.net', url: envUrlRepo } -]; - -/* - Get Server OS - - attempts to get the OS of a server a few different ways; and not just show "Linux". - - Windows machines will show Windows 11 - Linux machines will show Linux Alpine (3.22.0) -*/ - -getos( ( e, json ) => -{ - if ( e ) - return osName( os.platform(), os.release() ); - - if ( json.os === 'win32' ) - serverOs = osName( os.platform(), os.release() ); - - if ( json.os === 'linux' ) - { - if ( json.dist ) - serverOs = json.dist; - - if ( json.release ) - serverOs = serverOs.concat( ' ', '(' + json.release + ')' ); - } - - return serverOs; -}); - -/* - Process -*/ - -if ( process.pkg ) -{ - Log.info( `core`, chalk.yellow( `[initiate]` ), chalk.white( `ℹ️` ), - chalk.blueBright( `` ), chalk.gray( `Starting server utilizing process.execPath` ) ); - - const basePath = path.dirname( process.execPath ); - FILE_CFG = path.join( basePath, envWebFolder, `config.json` ); - FILE_URL = path.join( basePath, envWebFolder, `${ envFileURL }` ); - FILE_M3U = path.join( basePath, envWebFolder, `${ envFileM3U }` ); - FILE_XML = path.join( basePath, envWebFolder, `${ envFileXML }` ); - FILE_XML.length; - FILE_GZP = path.join( basePath, envWebFolder, `${ envFileGZP }` ); -} -else -{ - Log.info( `core`, chalk.yellow( `[initiate]` ), chalk.white( `ℹ️` ), - chalk.blueBright( `` ), chalk.gray( `Starting server utilizing processed locals` ) ); - - FILE_CFG = path.resolve( __dirname, envWebFolder, `config.json` ); - FILE_URL = path.resolve( __dirname, envWebFolder, `${ envFileURL }` ); - FILE_M3U = path.resolve( __dirname, envWebFolder, `${ envFileM3U }` ); - FILE_XML = path.resolve( __dirname, envWebFolder, `${ envFileXML }` ); - FILE_GZP = path.resolve( __dirname, envWebFolder, `${ envFileGZP }` ); -} - -/* - helper β€Ί sleep -*/ - -function sleep( ms ) -{ - return new Promise( ( resolve ) => - { - setTimeout( resolve, ms ); - }); -} - -/* - Semaphore β€Ί Initialize - - @arg int threads_max -*/ - -const semaphore = new Semaphore( 5 ); - -/* - Get Client IP - - prioritize header. -*/ - -const clientIp = ( req ) => - ( req.headers && ( - req.headers[envProxyHeader]?.split( ',' )?.shift() || - req.headers['X-Forwarded-For']?.split( ',' )?.shift() || - req.headers['x-forwarded-for']?.split( ',' )?.shift() || - req.headers['cf-connecting-ip']?.split( ',' )?.shift() || - req.headers['x-real-ip']?.split( ',' )?.shift() || - req.headers['X-Real-IP']?.split( ',' )?.shift() || - req.socket?.remoteAddress ) || - envIpContainer ); - -/* - Check Service Status - - this function attempts to see if a specified domain is up. - will first start with the URL you provide. - if try 1 fails, it will determine if that URL used protocol https or https and then flip to the other - if try 2 fails with the opposite protocol; domain is considered down -*/ - -async function hostCheck( service, uri ) -{ - /* try 1 */ - try - { - const resp = await fetch( uri ); - - /* try 1 β€Ί domain down */ - if ( resp.status !== 200 ) - { - Log.error( `ping`, chalk.redBright( `[response]` ), chalk.white( `❌` ), chalk.redBright( `` ), chalk.gray( `Try: Service Offline; failed to communicate with service, possibly down` ), chalk.redBright( `` ), chalk.gray( `${ resp.status }` ), chalk.redBright( `` ), chalk.gray( `${ service }` ), chalk.redBright( `
` ), chalk.gray( `${ uri }` ) ); - return false; - } - - /* try 1 β€Ί domain up */ - Log.ok( `ping`, chalk.yellow( `[response]` ), chalk.white( `βœ…` ), chalk.greenBright( `` ), chalk.gray( `Domain Online` ), chalk.greenBright( `` ), chalk.gray( `${ resp.status }` ), chalk.greenBright( `` ), chalk.gray( `${ service }` ), chalk.greenBright( `
` ), chalk.gray( `${ uri }` ) ); - return true; - } - catch ( err ) - { - /* - try 2 β€Ί https - */ - - if ( /^https:\/\//i.test( uri ) ) - { - const uriRetry = uri.replace( /^https:\/\//ig, 'http://' ); - Log.info( `ping`, chalk.yellow( `[response]` ), chalk.white( `⚠️` ), chalk.yellowBright( `` ), chalk.gray( `Try: Failed via HTTPS; trying HTTP protocol` ), chalk.yellowBright( `` ), chalk.gray( `${ service }` ), chalk.yellowBright( `` ), chalk.gray( `${ uri }` ), chalk.redBright( `(failed)` ), chalk.yellowBright( `` ), chalk.gray( `${ uriRetry }` ), chalk.blueBright( `(pending)` ) ); - - try - { - const resp = await fetch( uriRetry ); - - /* try 2 β€Ί https β€Ί domain down */ - if ( resp.status !== 200 ) - { - Log.error( `ping`, chalk.redBright( `[response]` ), chalk.white( `❌` ), chalk.redBright( `` ), chalk.gray( `Try: Domain Offline; failed to communicate with domain, possibly down` ), chalk.redBright( `` ), chalk.gray( `${ resp.status }` ), chalk.redBright( `` ), chalk.gray( `${ service }` ), chalk.redBright( `
` ), chalk.gray( `${ uriRetry }` ) ); - return false; - } - - /* try 2 β€Ί https β€Ί domain up */ - Log.ok( `ping`, chalk.yellow( `[response]` ), chalk.white( `βœ…` ), chalk.greenBright( `` ), chalk.gray( `Domain Online` ), chalk.greenBright( `` ), chalk.gray( `${ resp.status }` ), chalk.greenBright( `` ), chalk.gray( `${ service }` ), chalk.greenBright( `
` ), chalk.gray( `${ uriRetry }` ) ); - return true; - } - catch ( err ) - { - /* try 2 β€Ί https β€Ί domain not exist */ - Log.error( `ping`, chalk.redBright( `[response]` ), chalk.white( `❌` ), chalk.redBright( `` ), chalk.gray( `Try: Domain Offline; failed to communicate with domain, address does not exist` ), chalk.redBright( `` ), chalk.gray( `${ service }` ), chalk.redBright( `
` ), chalk.gray( `${ uri }` ), chalk.redBright( `` ), chalk.gray( `${ err }` ) ); - return false; - } - } - - /* - try 2 β€Ί http - */ - - else if ( /^http:\/\//i.test( uri ) ) - { - const uriRetry = uri.replace( /^http:\/\//ig, 'https://' ); - Log.info( `ping`, chalk.yellow( `[response]` ), chalk.white( `⚠️` ), chalk.yellowBright( `` ), chalk.gray( `Try: Failed via HTTP; trying HTTPS protocol` ), chalk.yellowBright( `` ), chalk.gray( `${ service }` ), chalk.yellowBright( `` ), chalk.gray( `${ uri }` ), chalk.redBright( `(failed)` ), chalk.yellowBright( `` ), chalk.gray( `${ uriRetry }` ), chalk.blueBright( `(pending)` ) ); - - try - { - const resp = await fetch( uriRetry ); - - /* try 2 β€Ί http β€Ί domain down */ - if ( resp.status !== 200 ) - { - Log.error( `ping`, chalk.redBright( `[response]` ), chalk.white( `❌` ), chalk.redBright( `` ), chalk.gray( `Domain Offline; failed to communicate with domain, possibly down` ), chalk.redBright( `` ), chalk.gray( `${ resp.status }` ), chalk.redBright( `` ), chalk.gray( `${ service }` ), chalk.redBright( `
` ), chalk.gray( `${ uriRetry }` ) ); - return false; - } - - /* try 2 β€Ί http β€Ί domain up */ - Log.ok( `ping`, chalk.yellow( `[response]` ), chalk.white( `βœ…` ), chalk.greenBright( `` ), chalk.gray( `Domain Online` ), chalk.greenBright( `` ), chalk.gray( `${ resp.status }` ), chalk.greenBright( `` ), chalk.gray( `${ service }` ), chalk.greenBright( `
` ), chalk.gray( `${ uriRetry }` ) ); - return true; - } - catch ( err ) - { - /* try 2 β€Ί http β€Ί domain not exist */ - Log.error( `ping`, chalk.redBright( `[response]` ), chalk.white( `❌` ), chalk.redBright( `` ), chalk.gray( `Domain Offline; failed to communicate with domain, address does not exist` ), chalk.redBright( `` ), chalk.gray( `${ service }` ), chalk.redBright( `
` ), chalk.gray( `${ uri }` ), chalk.redBright( `` ), chalk.gray( `${ err }` ) ); - return false; - } - } - } -} - -/* - Func β€Ί Download File - - @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 Promise<> -*/ - -async function downloadFile( url, filePath ) -{ - return new Promise( ( resolve, reject ) => - { - Log.info( `file`, chalk.yellow( `[download]` ), chalk.white( `ℹ️` ), - chalk.blueBright( `` ), chalk.gray( `Preparing to download external file` ), - chalk.blueBright( `` ), chalk.gray( `${ url }` ), - chalk.blueBright( `` ), chalk.gray( `${ filePath }` ) ); - - const isHttps = new URL( url ).protocol === 'https:'; - const httpModule = isHttps ? https : http; - const file = fs.createWriteStream( filePath ); - httpModule - .get( url, ( res ) => - { - Log.info( `file`, chalk.yellow( `[retrieve]` ), chalk.white( `ℹ️` ), - chalk.blueBright( `` ), chalk.gray( `Getting response from file download request` ), - chalk.blueBright( `` ), chalk.gray( `${ res.statusCode }` ), - chalk.blueBright( `` ), chalk.gray( `${ url }` ), - chalk.blueBright( `` ), chalk.gray( `${ filePath }` ) ); - - if ( res.statusCode !== 200 ) - { - Log.error( `file`, chalk.redBright( `[download]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `Attempt to download external file returned non-200 status` ), - chalk.redBright( `` ), chalk.gray( `${ res.statusCode }` ), - chalk.redBright( `` ), chalk.gray( `${ url }` ), - chalk.redBright( `` ), chalk.gray( `${ filePath }` ) ); - - return reject( new Error( `Failed to download file: ${ url }. Status code: ${ res.statusCode }` ) ); - } - res.pipe( file ); - file.on( 'finish', () => - { - Log.ok( `file`, chalk.yellow( `[download]` ), chalk.white( `βœ…` ), - chalk.greenBright( `` ), chalk.gray( `Successfully downloaded external file` ), - chalk.greenBright( `` ), chalk.gray( `${ res.statusCode }` ), - chalk.greenBright( `` ), chalk.gray( `${ url }` ), - chalk.greenBright( `` ), chalk.gray( `${ filePath }` ) ); - - file.close( () => resolve( true ) ); - }); - }) - .on( 'error', ( err ) => - { - Log.error( `file`, chalk.redBright( `[download]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `Failed to download external source file` ), - chalk.redBright( `` ), chalk.gray( `${ err.message }` ), - chalk.redBright( `` ), chalk.gray( `${ url }` ), - chalk.redBright( `` ), chalk.gray( `${ filePath }` ) ); - - fs.unlink( filePath, () => reject( err ) ); - }); - }); -} - -/* - Get Filesize and convert to human readable format - - @arg str filename filename to get size in bytes for - @ret str 2025-03-23 04:11 am -*/ - -function getFileModified( filename ) -{ - return moment( fs.statSync( filename ).mtime ).format( 'YYYY-MM-DD h:mm a' ); -} - -/* - Func > Get Human Readable Filesize - - Takes the total number of bytes in a file's size and converts it into - a human readable format. - - @arg str filename filename to get size in bytes for - @arg bool si divides the bytes of a file by 1000 instead of 2024 - @arg int decimal specifies the decimal point - @ret str 111.9 KB - -*/ - -function getFileSizeHuman( filename, si = true, decimal = 1 ) -{ - let stats = []; - stats.size = 0; - if ( fs.existsSync( filename ) ) - stats = fs.statSync( filename ); - - let bytes = stats.size; - const thresh = si ? 1000 : 1024; - - if ( Math.abs( bytes ) < thresh ) - return bytes + ' B'; - - const units = si - ? [ - 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' - ] - : [ - 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB' - ]; - - let u = -1; - const r = 10 ** decimal; - - do - { - bytes /= thresh; - ++u; - } while ( Math.round( Math.abs( bytes ) * r ) / r >= thresh && u < units.length - 1 ); - - return bytes.toFixed( decimal ) + ' ' + units[u]; -} - -/* - Func > Get Files - - 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. - - 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 getFile( url, filePath ) -{ - try - { - Log.debug( `file`, chalk.yellow( `[requests]` ), chalk.white( `βš™οΈ` ), - chalk.blueBright( `` ), chalk.gray( `Requesting to download external file` ), - chalk.blueBright( `` ), chalk.gray( `${ url }` ), - chalk.blueBright( `` ), chalk.gray( `${ filePath }` ) ); - - const ok = await hostCheck( 'git.binaryninja.com', `${ envUrlRepo }` ); - - if ( ok ) - { - try - { - await downloadFile( url, filePath ); - return true; - } - catch ( err ) - { - Log.error( `file`, chalk.redBright( `[download]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `Download attempt failed after service check succeeded` ), - chalk.redBright( `` ), chalk.gray( `${ err.message }` ), - chalk.redBright( `` ), chalk.gray( `${ url }` ), - chalk.redBright( `` ), chalk.gray( `${ filePath }` ) ); - - return false; - } - } - else - { - Log.info( `file`, chalk.yellow( `[download]` ), chalk.white( `ℹ️` ), - chalk.yellowBright( `` ), chalk.gray( `Skipping download because service is offline; using existing local file` ), - chalk.yellowBright( `` ), chalk.gray( `${ url }` ), - chalk.yellowBright( `` ), chalk.gray( `${ filePath }` ) ); - - return false; - } - } - catch ( err ) - { - if ( fs.existsSync( filePath ) ) - { - Log.warn( `file`, chalk.yellow( `[requests]` ), chalk.white( `⚠️` ), - chalk.yellowBright( `` ), chalk.gray( `Download failed - Using existing local file ${ filePath }` ), - chalk.yellowBright( `` ), chalk.gray( `${ url }` ), - chalk.yellowBright( `` ), chalk.gray( `${ filePath }` ) ); - } - else - { - Log.error( `file`, chalk.redBright( `[requests]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `Download filed and no local backup exists, aborting` ), - chalk.redBright( `` ), chalk.gray( `${ err.message }` ), - chalk.redBright( `` ), chalk.gray( `${ url }` ), - chalk.redBright( `` ), chalk.gray( `${ filePath }` ) ); - - throw err; - } - } -} - -/* - Func > Create GZip - - locates the xmltv.xml and packages it into a xmltv.gz archive -*/ - -async function createGzip( ) -{ - return new Promise( ( resolve, reject ) => - { - Log.info( `.gzp`, chalk.yellow( `[generate]` ), chalk.white( `ℹ️` ), - chalk.blueBright( `` ), chalk.gray( `Preparing to create compressed XML gz file` ), - chalk.blueBright( `` ), chalk.gray( `${ FILE_XML }` ), - chalk.blueBright( `` ), chalk.gray( `${ FILE_GZP }` ) ); - - fs.readFile( FILE_XML, ( err, buf ) => - { - Log.debug( `.gzp`, chalk.yellow( `[generate]` ), chalk.white( `βš™οΈ` ), - chalk.blueBright( `` ), chalk.gray( `Reading source XML file` ), - chalk.blueBright( `` ), chalk.gray( `${ FILE_XML }` ), - chalk.blueBright( `` ), chalk.gray( `${ FILE_GZP }` ) ); - - if ( err ) - { - Log.error( `.gzp`, chalk.redBright( `[generate]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `Could not read source XML file` ), - chalk.redBright( `` ), chalk.gray( `${ err }` ), - chalk.redBright( `` ), chalk.gray( `${ FILE_XML }` ), - chalk.redBright( `` ), chalk.gray( `${ FILE_GZP }` ) ); - - return reject( new Error( `Could not read file ${ envFileXML }. Error: ${ err }` ) ); - } - - zlib.gzip( buf, ( err, buf ) => - { - Log.debug( `.gzp`, chalk.yellow( `[generate]` ), chalk.white( `βš™οΈ` ), - chalk.blueBright( `` ), chalk.gray( `Starting zlib.gzip` ), - chalk.blueBright( `` ), chalk.gray( `${ FILE_XML }` ), - chalk.blueBright( `` ), chalk.gray( `${ FILE_GZP }` ) ); - - if ( err ) - { - Log.error( `.gzp`, chalk.redBright( `[generate]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `Could not create gz archive` ), - chalk.redBright( `` ), chalk.gray( `${ err }` ), - chalk.redBright( `` ), chalk.gray( `${ FILE_XML }` ), - chalk.redBright( `` ), chalk.gray( `${ FILE_GZP }` ) ); - - return reject( new Error( `Could not create ${ envFileGZP }. Error: ${ err }` ) ); - } - - Log.info( `.gzp`, chalk.yellow( `[generate]` ), chalk.white( `ℹ️` ), - chalk.blueBright( `` ), chalk.gray( `Started creating gz archive from XML source` ), - chalk.blueBright( `` ), chalk.gray( `${ FILE_XML }` ), - chalk.blueBright( `` ), chalk.gray( `${ FILE_GZP }` ) ); - - fs.writeFile( `${ FILE_GZP }`, buf, ( err ) => - { - if ( err ) - { - Log.error( `.gzp`, chalk.redBright( `[generate]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `Could not write to and create gz archive` ), - chalk.redBright( `` ), chalk.gray( `${ err }` ), - chalk.redBright( `` ), chalk.gray( `${ FILE_XML }` ), - chalk.redBright( `` ), chalk.gray( `${ FILE_GZP }` ) ); - - return reject( new Error( `Could not write XML file ${ envFileXML } to ${ envFileGZP }. Error: ${ err }` ) ); - } - - Log.ok( `.gzp`, chalk.yellow( `[generate]` ), chalk.white( `βœ…` ), - chalk.greenBright( `` ), chalk.gray( `Successfully created compressed gz archive from XML source file` ), - chalk.greenBright( `` ), chalk.gray( `${ FILE_XML }` ), - chalk.greenBright( `` ), chalk.gray( `${ FILE_GZP }` ) ); - - resolve( true ); - }); - }); - }); - }); -} - -/* - Func > Get Gzip - - try; catch to create a .gz compressed file from the .xml guide data -*/ - -async function getGzip( ) -{ - try - { - Log.debug( `.gzp`, chalk.yellow( `[requests]` ), chalk.white( `βš™οΈ` ), - chalk.blueBright( `` ), chalk.gray( `Requesting to create compressed gzip from uncompressed XML data` ), - chalk.blueBright( `` ), chalk.gray( `${ FILE_XML }` ), - chalk.blueBright( `` ), chalk.gray( `${ FILE_GZP }` ) ); - - await createGzip( ); - } - catch ( err ) - { - if ( fs.existsSync( FILE_XML ) ) - { - Log.warn( `.gzp`, chalk.yellow( `[requests]` ), chalk.white( `⚠️` ), - chalk.yellowBright( `` ), chalk.yellowBright( `Cannot get compressed gzip; but source XML file found and can be used` ), - chalk.yellowBright( `` ), chalk.gray( `${ FILE_XML }` ), - chalk.yellowBright( `` ), chalk.gray( `${ FILE_GZP }` ) ); - } - else - { - Log.error( `.gzp`, chalk.redBright( `[requests]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `Failed to get compressed gzip, and source XML file not found` ), - chalk.redBright( `` ), chalk.gray( `${ err.message }` ), - chalk.redBright( `` ), chalk.gray( `${ FILE_XML }` ), - chalk.redBright( `` ), chalk.gray( `${ FILE_GZP }` ) ); - - 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, req ) -{ - return new Promise( ( resolve, reject ) => - { - Log.info( `live`, chalk.yellow( `[generate]` ), chalk.white( `ℹ️` ), - chalk.blueBright( `` ), chalk.gray( `Preparing to fetch remote request` ), - chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.blueBright( `` ), chalk.gray( `${ url }` ) ); - - const mod = url.startsWith( 'https' ) ? https : http; - mod - .get( url, { - headers: { - 'Accept-Encoding': envWebEncoding - } - }, ( resp ) => - { - Log.info( `live`, chalk.yellow( `[retrieve]` ), chalk.white( `ℹ️` ), - chalk.blueBright( `` ), chalk.gray( `Getting response from remote fetch request` ), - chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.blueBright( `` ), chalk.gray( `${ resp.statusCode }` ), - chalk.blueBright( `` ), chalk.gray( `${ url }` ) ); - - if ( resp.statusCode !== 200 ) - { - Log.error( `live`, chalk.redBright( `[retrieve]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `Remote fetch returned status code other than 200` ), - chalk.redBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.redBright( `` ), chalk.gray( `${ resp.statusCode }` ), - chalk.redBright( `` ), chalk.gray( `${ url }` ) ); - - return reject( new Error( `HTTP ${ resp.statusCode } for ${ url }` ) ); - } - - const chunks = []; - - resp.on( 'data', ( chunk ) => chunks.push( chunk ) ); - resp.on( 'end', () => - { - const buffer = Buffer.concat( chunks ); - const encoding = resp.headers['content-encoding']; - - if ( encoding === 'gzip' ) - { - zlib.gunzip( buffer, ( err, decoded ) => - { - if ( err ) - { - Log.error( `live`, chalk.redBright( `[retrieve]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `Remote fetch could not complete encoding type ${ encoding }` ), - chalk.redBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.redBright( `` ), chalk.gray( `${ err }` ), - chalk.redBright( `` ), chalk.gray( `${ encoding }` ), - chalk.redBright( `` ), chalk.gray( `${ resp.statusCode }` ), - chalk.redBright( `` ), chalk.gray( `${ url }` ) ); - - return reject( err ); - } - - Log.debug( `live`, chalk.yellow( `[retrieve]` ), chalk.white( `βš™οΈ` ), - chalk.blueBright( `` ), chalk.gray( `Remote fetch detected encoding type ${ encoding }; decoding` ), - chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.blueBright( `` ), chalk.gray( `${ encoding }` ), - chalk.blueBright( `` ), chalk.gray( `${ resp.statusCode }` ), - chalk.blueBright( `` ), chalk.gray( `${ url }` ) ); - - resolve( decoded ); - }); - } - else if ( encoding === 'deflate' ) - { - zlib.inflate( buffer, ( err, decoded ) => - { - if ( err ) - { - Log.error( `live`, chalk.redBright( `[retrieve]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `Remote fetch could not complete encoding type ${ encoding }` ), - chalk.redBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.redBright( `` ), chalk.gray( `${ err }` ), - chalk.redBright( `` ), chalk.gray( `${ encoding }` ), - chalk.redBright( `` ), chalk.gray( `${ resp.statusCode }` ), - chalk.redBright( `` ), chalk.gray( `${ url }` ) ); - - return reject( err ); - } - - Log.debug( `live`, chalk.yellow( `[retrieve]` ), chalk.white( `βš™οΈ` ), - chalk.blueBright( `` ), chalk.gray( `Remote fetch detected encoding type ${ encoding }; decoding` ), - chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.blueBright( `` ), chalk.gray( `${ encoding }` ), - chalk.blueBright( `` ), chalk.gray( `${ resp.statusCode }` ), - chalk.blueBright( `` ), chalk.gray( `${ url }` ) ); - - resolve( decoded ); - }); - } - else if ( encoding === 'br' ) - { - zlib.brotliDecompress( buffer, ( err, decoded ) => - { - if ( err ) - { - Log.error( `live`, chalk.redBright( `[retrieve]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `Remote fetch could not complete encoding type ${ encoding } (brotli decompress)` ), - chalk.redBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.redBright( `` ), chalk.gray( `${ err }` ), - chalk.redBright( `` ), chalk.gray( `${ encoding }` ), - chalk.redBright( `` ), chalk.gray( `${ resp.statusCode }` ), - chalk.redBright( `` ), chalk.gray( `${ url }` ) ); - - return reject( err ); - } - - Log.debug( `live`, chalk.yellow( `[retrieve]` ), chalk.white( `βš™οΈ` ), - chalk.blueBright( `` ), chalk.gray( `Remote fetch detected encoding type ${ encoding } (brotli decompress); decoding` ), - chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.blueBright( `` ), chalk.gray( `${ encoding }` ), - chalk.blueBright( `` ), chalk.gray( `${ resp.statusCode }` ), - chalk.blueBright( `` ), chalk.gray( `${ url }` ) ); - - resolve( decoded ); - }); - } - else - { - Log.debug( `live`, chalk.yellow( `[retrieve]` ), chalk.white( `βš™οΈ` ), - chalk.blueBright( `` ), chalk.gray( `Remote fetch contains no headers to decode; resolving buffer` ), - chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.blueBright( `` ), chalk.gray( `${ encoding }` ), - chalk.blueBright( `` ), chalk.gray( `${ resp.statusCode }` ), - chalk.blueBright( `` ), chalk.gray( `${ url }` ) ); - - resolve( buffer ); - } - }); - }) - .on( 'error', reject ); - }); -} - -/* - Serve Keys - - @url https://tvapp2.domain.lan/keys?uri=https://v16.thetvapp.to/hls/WABCDT1/tracks-v2a1/mono.m3u8?token=a0b2C-1ae-qaxAV5iKAd8g&expires=1746394920&user_id=EjLZVsIiJphafFxXRVWRdVWPvzTqpWBZbchvsTwpAlrQZzFuZMpdSn== -*/ - -async function serveKey( req, res ) -{ - try - { - const paramUrl = new URL( req.url, `http://${ req.headers.host }` ).searchParams.get( 'uri' ); - if ( !paramUrl ) - { - const statusCheck = - { - ip: envIpContainer, - gateway: envIpGateway, - client: clientIp( req ), - message: 'Error: Missing "uri" parameter for key download.', - status: 'unhealthy', - ref: req.url, - method: req.method || 'GET', - code: 400, - uptime: Math.round( process.uptime() ), - uptimeShort: timeAgo.format( Date.now() - process.uptime() * 1000, 'twitter' ), - uptimeLong: timeAgo.format( Date.now() - process.uptime() * 1000, 'round' ), - timestamp: Date.now() - }; - - res.writeHead( statusCheck.code, { - 'Content-Type': 'application/json' - }); - - Log.error( `keys`, chalk.redBright( `[response]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `${ statusCheck.message }` ), - chalk.redBright( `` ), chalk.gray( `serveKey` ), - chalk.redBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.redBright( `` ), chalk.gray( `${ statusCheck.code }` ), - chalk.redBright( `` ), chalk.gray( `${ req.url }` ), - chalk.redBright( `` ), chalk.gray( `empty; missing var` ) ); - - return res.end( JSON.stringify( statusCheck ) ); - } - - Log.debug( `keys`, chalk.yellow( `[response]` ), chalk.white( `βš™οΈ` ), - chalk.blueBright( `` ), chalk.gray( `Valid paramUrl specified; establishing connection to serve key to client` ), - chalk.blueBright( `` ), chalk.gray( `serveKey` ), - chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.blueBright( `` ), chalk.gray( `${ req.url }` ), - chalk.blueBright( `` ), chalk.gray( `${ paramUrl }` ) ); - - const keyData = await fetchRemote( paramUrl, req ); - res.writeHead( 200, { - 'Content-Type': 'application/octet-stream' - }); - - Log.ok( `keys`, chalk.yellow( `[response]` ), chalk.white( `βœ…` ), - chalk.greenBright( `` ), chalk.gray( `Serving key to client` ), - chalk.greenBright( `` ), chalk.gray( `serveKey` ), - chalk.greenBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.greenBright( `` ), chalk.gray( `200` ), - chalk.greenBright( `` ), chalk.gray( `${ req.url }` ), - chalk.greenBright( `` ), chalk.gray( `${ paramUrl }` ), - chalk.greenBright( `` ), chalk.gray( `${ keyData }` ) ); - - res.end( keyData ); - } - catch ( err ) - { - const statusCheck = - { - ip: envIpContainer, - gateway: envIpGateway, - client: clientIp( req ), - message: `Failed to serve key; try{} failed. Ensure you specify a valid uri to tvapp / tvpass`, - error: `${ err.message }`, - status: 'unhealthy', - ref: req.url, - method: req.method || 'GET', - code: 500, - uptime: Math.round( process.uptime() ), - uptimeShort: timeAgo.format( Date.now() - process.uptime() * 1000, 'twitter' ), - uptimeLong: timeAgo.format( Date.now() - process.uptime() * 1000, 'round' ), - timestamp: Date.now() - }; - - res.writeHead( statusCheck.code, { - 'Content-Type': 'application/json' - }); - - Log.error( `keys`, chalk.yellow( `[response]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `${ statusCheck.message }` ), - chalk.redBright( `` ), chalk.gray( `serveKey` ), - chalk.redBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.redBright( `` ), chalk.gray( `${ statusCheck.code }` ), - chalk.redBright( `` ), chalk.gray( `${ statusCheck.error }` ), - chalk.redBright( `` ), chalk.gray( `${ req.url }` ) ); - - res.end( JSON.stringify( statusCheck ) ); - } -} - -/* - cookies > headers > parse -*/ - -function cookieHeadersSetParse( values ) -{ - if ( !Array.isArray( values ) ) return; - values.forEach( ( line ) => - { - const [cookiePair] = line.split( ';' ); - if ( cookiePair ) - { - const [ key, val ] = cookiePair.split( '=' ); - if ( key && val ) - { - gCookies[key.trim()] = val.trim(); - } - } - }); -} - -/* - cookies > headers > build -*/ - -function cookieHeadersBuild() -{ - const pairs = []; - for ( const [ k, v ] of Object.entries( gCookies ) ) - { - pairs.push( `${ k }=${ v }` ); - } - return pairs.join( '; ' ); -} - -/* - fetch > page -*/ - -function fetchPage( url, req ) -{ - return new Promise( ( resolve, reject ) => - { - Log.info( `http`, chalk.yellow( `[generate]` ), chalk.white( `ℹ️` ), - chalk.blueBright( `` ), chalk.gray( `Preparing to fetch remote page` ), - chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.blueBright( `` ), chalk.gray( `${ url }` ) ); - - const opts = { - method: 'GET', - headers: { - 'User-Agent': USERAGENT, - Accept: '*/*', - Cookie: cookieHeadersBuild() - } - }; - https - .get( url, opts, ( res ) => - { - Log.info( `http`, chalk.yellow( `[retrieve]` ), chalk.white( `ℹ️` ), - chalk.blueBright( `` ), chalk.gray( `Status code returned` ), - chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.blueBright( `` ), chalk.gray( `${ res.statusCode }` ), - chalk.blueBright( `` ), chalk.gray( `${ url }` ) ); - - if ( res.statusCode !== 200 ) - { - Log.debug( `http`, chalk.yellow( `[retrieve]` ), chalk.white( `βš™οΈ` ), - chalk.blueBright( `` ), chalk.gray( `Failed to load url; status 200 was not returned` ), - chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.blueBright( `` ), chalk.gray( `${ res.statusCode }` ), - chalk.blueBright( `` ), chalk.gray( `${ url }` ) ); - - return reject( new Error( `page did not return code 200 status ${ res.statusCode } => ${ url }` ) ); - } - - if ( res.headers['set-cookie']) - { - Log.debug( `http`, chalk.yellow( `[retrieve]` ), chalk.white( `βš™οΈ` ), - chalk.blueBright( `` ), chalk.gray( `Setting headers` ), - chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.blueBright( `` ), chalk.gray( `${ res.statusCode }` ), - chalk.blueBright( `` ), chalk.gray( `${ url }` ), - chalk.blueBright( `
` ), chalk.gray( `set-cookie` ) ); - - cookieHeadersSetParse( res.headers['set-cookie']); - } - - let data = ''; - res.on( 'data', ( chunk ) => ( data += chunk ) ); - res.on( 'end', () => resolve( data ) ); - }) - .on( 'error', reject ); - }); -} - -/* - tokenized url > get -*/ - -async function getTokenizedUrl( channelUrl, req ) -{ - try - { - const html = await fetchPage( channelUrl, req ); - let streamName; - let streamHost; - - Log.debug( `play`, chalk.yellow( `[tokenize]` ), chalk.white( `βš™οΈ` ), - chalk.blueBright( `` ), chalk.gray( `Requesting to get tokenize url` ), - chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.blueBright( `` ), chalk.gray( `${ channelUrl }` ) ); - - if ( channelUrl.includes( 'espn-' ) ) - { - streamName = 'ESPN'; - } - else if ( channelUrl.includes( 'espn2-' ) ) - { - streamName = 'ESPN2'; - } - else - { - const streamNameMatch = html.match( /id="stream_name" name="([^"]+)"/ ); - if ( !streamNameMatch ) - { - Log.error( `play`, chalk.yellow( `[tokenize]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `Cannot find streamNameMatch; returned empty` ), - chalk.redBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.redBright( `` ), chalk.grey( `${ channelUrl }` ), - chalk.redBright( `` ), chalk.grey( `missing / var empty` ) ); - - return null; - } - - streamName = streamNameMatch[1]; - - Log.debug( `play`, chalk.yellow( `[tokenize]` ), chalk.white( `βš™οΈ` ), - chalk.blueBright( `` ), chalk.gray( `streamName found` ), - chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.blueBright( `` ), chalk.gray( `${ channelUrl }` ), - chalk.blueBright( `` ), chalk.gray( `not yet assigned` ), - chalk.blueBright( `` ), chalk.gray( `${ streamName }` ) ); - } - - if ( channelUrl.match( 'tvpass\.org' ) ) - { - streamHost = 'tvpass.org'; - }; - - if ( channelUrl.match( 'thetvapp\.to' ) ) - { - streamHost = 'thetvapp.to'; - }; - - const tokenUrl = `https://${ streamHost }/token/${ streamName }?quality=${ envStreamQuality.toLowerCase() }`; - const tokenResponse = await fetchPage( tokenUrl, req ); - let tokenizedUrl; - - Log.debug( `play`, chalk.yellow( `[tokenize]` ), chalk.white( `βš™οΈ` ), - chalk.blueBright( `` ), chalk.gray( `Generating tokenized final stream URL` ), - chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.blueBright( `` ), chalk.gray( `${ channelUrl }` ), - chalk.blueBright( `` ), chalk.gray( `${ streamHost }` ), - chalk.blueBright( `` ), chalk.gray( `${ streamName }` ), - chalk.blueBright( `` ), chalk.gray( `${ envStreamQuality.toLowerCase() }` ), - chalk.blueBright( `` ), chalk.gray( `${ tokenUrl }` ), - chalk.blueBright( `` ), chalk.gray( `not yet assigned` ) ); - - try - { - const json = JSON.parse( tokenResponse ); - tokenizedUrl = json.url; - - Log.debug( `play`, chalk.yellow( `[tokenize]` ), chalk.white( `βš™οΈ` ), - chalk.blueBright( `` ), chalk.gray( `Returned token response in json format` ), - chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.blueBright( `` ), chalk.gray( `${ channelUrl }` ), - chalk.blueBright( `` ), chalk.gray( `${ streamHost }` ), - chalk.blueBright( `` ), chalk.gray( `${ streamName }` ), - chalk.blueBright( `` ), chalk.gray( `${ envStreamQuality.toLowerCase() }` ), - chalk.blueBright( `` ), chalk.gray( `${ tokenUrl }` ), - chalk.blueBright( `` ), chalk.gray( `${ json }` ), - chalk.blueBright( `` ), chalk.gray( `${ tokenizedUrl }` ) ); - } - catch ( err ) - { - Log.error( `play`, chalk.redBright( `[tokenize]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `Failed to parse token JSON for channel` ), - chalk.redBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.redBright( `` ), chalk.gray( `${ channelUrl }` ), - chalk.redBright( `` ), chalk.gray( `${ streamHost }` ), - chalk.redBright( `` ), chalk.gray( `${ streamName }` ), - chalk.redBright( `` ), chalk.gray( `${ envStreamQuality.toLowerCase() }` ), - chalk.redBright( `` ), chalk.gray( `${ tokenUrl }` ), - chalk.redBright( `` ), chalk.gray( `not yet assigned` ) ); - - return null; - } - - if ( !tokenizedUrl ) - { - Log.error( `play`, chalk.redBright( `[tokenize]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `No URL found in token JSON for channel` ), - chalk.redBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.redBright( `` ), chalk.gray( `${ channelUrl }` ), - chalk.redBright( `` ), chalk.gray( `${ streamHost }` ), - chalk.redBright( `` ), chalk.gray( `${ streamName }` ), - chalk.redBright( `` ), chalk.gray( `${ envStreamQuality.toLowerCase() }` ), - chalk.redBright( `` ), chalk.gray( `${ tokenUrl }` ), - chalk.redBright( `` ), chalk.gray( `missing` ) ); - - return null; - } - - Log.ok( `play`, chalk.yellow( `[tokenize]` ), chalk.white( `βœ…` ), - chalk.greenBright( `` ), chalk.gray( `Successfully generated token for stream` ), - chalk.greenBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.greenBright( `` ), chalk.gray( `${ channelUrl }` ), - chalk.greenBright( `` ), chalk.gray( `${ streamHost }` ), - chalk.greenBright( `` ), chalk.gray( `${ streamName }` ), - chalk.greenBright( `` ), chalk.gray( `${ envStreamQuality.toLowerCase() }` ), - chalk.greenBright( `` ), chalk.gray( `${ streamHost }` ), - chalk.greenBright( `` ), chalk.gray( `${ tokenizedUrl }` ), - chalk.greenBright( `` ), chalk.gray( `${ tokenUrl }` ) ); - - return tokenizedUrl; - } - catch ( err ) - { - Log.error( `play`, chalk.redBright( `[tokenize]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `Fatal error fetching token` ), - chalk.redBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.redBright( `` ), chalk.gray( `${ err.message }` ), - chalk.redBright( `` ), chalk.gray( `${ channelUrl }` ), - chalk.redBright( `` ), chalk.gray( `not defined` ), - chalk.redBright( `` ), chalk.gray( `not defined` ), - chalk.redBright( `` ), chalk.gray( `${ envStreamQuality.toLowerCase() }` ), - chalk.redBright( `` ), chalk.gray( `not defined` ) ); - - return null; - } -} - -/* - serve > m3u playlist -*/ - -async function serveM3UPlaylist( req, res ) -{ - await semaphore.acquire(); - try - { - const method = req.method || 'GET'; - Log.debug( `plst`, chalk.yellow( `[requests]` ), chalk.white( `βš™οΈ` ), - chalk.blueBright( `` ), chalk.gray( `Requesting to serve M3U playlist` ), - chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.blueBright( `` ), chalk.gray( `${ req.url }` ), - chalk.blueBright( `` ), chalk.gray( `${ method }` ) ); - - /* - paramUrl > decodedUrl > tokenizedUrl - */ - - const paramUrl = new URL( req.url, `http://${ req.headers.host }` ).searchParams.get( 'url' ); - if ( !paramUrl ) - { - const statusCheck = - { - ip: envIpContainer, - gateway: envIpGateway, - client: clientIp( req ), - message: `Missing ?url= parameter for var paramUrl`, - status: `unhealthy`, - ref: req.url, - method: req.method || 'GET', - code: 404, - uptime: Math.round( process.uptime() ), - uptimeShort: timeAgo.format( Date.now() - process.uptime() * 1000, 'twitter' ), - uptimeLong: timeAgo.format( Date.now() - process.uptime() * 1000, 'round' ), - timestamp: Date.now() - }; - - res.writeHead( statusCheck.code, { - 'Content-Type': 'application/json' - }); - - Log.error( `plst`, chalk.redBright( `[response]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `${ statusCheck.message }` ), - chalk.redBright( `` ), chalk.gray( `serveM3UPlaylist` ), - chalk.redBright( `` ), chalk.gray( `${ statusCheck.code }` ), - chalk.redBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.redBright( `` ), chalk.gray( `http://${ req.headers.host }/channel?url=XXXX` ), - chalk.redBright( `` ), chalk.gray( `${ method }` ) ); - - res.end( JSON.stringify( statusCheck ) ); - return; - } - - const decodedUrl = decodeURIComponent( paramUrl ); - if ( decodedUrl.endsWith( '.ts' ) ) - { - res.writeHead( 302, { - Location: decodedUrl - }); - - Log.notice( `plst`, chalk.yellow( `[response]` ), chalk.white( `πŸ“Œ` ), - chalk.yellowBright( `` ), chalk.gray( `decodedUrl ends with .ts script; (302) redirecting` ), - chalk.yellowBright( `` ), chalk.gray( `serveM3UPlaylist` ), - chalk.yellowBright( `` ), chalk.gray( `302` ), - chalk.yellowBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.yellowBright( `` ), chalk.gray( `${ paramUrl }` ), - chalk.yellowBright( `` ), chalk.gray( `${ decodedUrl }` ), - chalk.yellowBright( `` ), chalk.gray( `${ method }` ) ); - - res.end(); - return; - } - - const cachedUrl = getCache( req, decodedUrl ); - if ( cachedUrl ) - { - const rewrittenPlaylist = await rewriteM3U( cachedUrl, req ); - res.writeHead( 200, - { - 'Content-Type': 'application/vnd.apple.mpegurl', - 'Content-Disposition': 'inline; filename="' + envFileM3U - }); - - Log.debug( `plst`, chalk.yellow( `[response]` ), chalk.white( `βš™οΈ` ), - chalk.blueBright( `` ), chalk.gray( `Serving cachedUrl m3u playlist to client` ), - chalk.blueBright( `` ), chalk.gray( `serveM3UPlaylist` ), - chalk.blueBright( `` ), chalk.gray( `200` ), - chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.blueBright( `` ), chalk.gray( `${ paramUrl }` ), - chalk.blueBright( `` ), chalk.gray( `${ decodedUrl }` ), - chalk.blueBright( `` ), chalk.gray( `${ cachedUrl }` ), - chalk.blueBright( `` ), chalk.gray( `${ method }` ) ); - - res.end( rewrittenPlaylist ); - return; - } - - Log.info( `plst`, chalk.yellow( `[response]` ), chalk.white( `ℹ️` ), - chalk.blueBright( `` ), chalk.gray( `Request not cached; generating new tokenizedUrl for m3u playlist to client` ), - chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.blueBright( `` ), chalk.gray( `serveM3UPlaylist` ), - chalk.blueBright( `` ), chalk.gray( `${ paramUrl }` ), - chalk.blueBright( `` ), chalk.gray( `${ decodedUrl }` ), - chalk.blueBright( `` ), chalk.gray( `${ cachedUrl }` ), - chalk.blueBright( `` ), chalk.gray( `${ method }` ) ); - - /* - get tokenized url - */ - - const tokenizedUrl = await getTokenizedUrl( decodedUrl, req ); - if ( !tokenizedUrl ) - { - const statusCheck = - { - ip: envIpContainer, - gateway: envIpGateway, - client: clientIp( req ), - message: `Failed to retrieve tokenized URL.`, - status: `unhealthy`, - ref: req.url, - method: req.method || 'GET', - code: 500, - uptime: Math.round( process.uptime() ), - uptimeShort: timeAgo.format( Date.now() - process.uptime() * 1000, 'twitter' ), - uptimeLong: timeAgo.format( Date.now() - process.uptime() * 1000, 'round' ), - timestamp: Date.now() - }; - - res.writeHead( statusCheck.code, { - 'Content-Type': 'application/json' - }); - - Log.error( `plst`, chalk.redBright( `[response]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `${ statusCheck.message }` ), - chalk.redBright( `` ), chalk.gray( `serveM3UPlaylist` ), - chalk.redBright( `` ), chalk.gray( `${ statusCheck.code }` ), - chalk.redBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.redBright( `` ), chalk.gray( `${ decodedUrl }` ), - chalk.redBright( `` ), chalk.gray( `${ tokenizedUrl }` ), - chalk.redBright( `` ), chalk.gray( `${ method }` ) ); - - res.end( JSON.stringify( statusCheck ) ); - return; - } - - setCache( req, decodedUrl, tokenizedUrl, 4 * 60 * 60 * 1000 ); - const hdUrl = tokenizedUrl.replace( 'tracks-v2a1', 'tracks-v1a1' ); - const rewrittenPlaylist = await rewriteM3U( hdUrl, req ); - - res.writeHead( 200, { - 'Content-Type': 'application/vnd.apple.mpegurl', - 'Content-Disposition': 'inline; filename="' + envFileM3U - }); - - Log.ok( `plst`, chalk.yellow( `[response]` ), chalk.white( `βœ…` ), - chalk.greenBright( `` ), chalk.gray( `Serving new tokenizedUrl with m3u playlist to client` ), - chalk.greenBright( `` ), chalk.gray( `serveM3UPlaylist` ), - chalk.greenBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.greenBright( `` ), chalk.gray( `200` ), - chalk.greenBright( `` ), chalk.gray( `${ envFileM3U }` ), - chalk.greenBright( `` ), chalk.gray( `${ paramUrl }` ), - chalk.greenBright( `` ), chalk.gray( `${ decodedUrl }` ), - chalk.greenBright( `` ), chalk.gray( `${ tokenizedUrl }` ), - chalk.greenBright( `` ), chalk.gray( `${ method }` ) ); - - res.end( rewrittenPlaylist ); - } - catch ( err ) - { - if ( !res.headersSent ) - { - const statusCheck = - { - ip: envIpContainer, - gateway: envIpGateway, - client: clientIp( req ), - message: `Cannot process request when fetching channel playlist`, - error: `${ err.message }`, - status: 'unhealthy', - ref: req.url, - method: req.method || 'GET', - code: 500, - uptime: Math.round( process.uptime() ), - uptimeShort: timeAgo.format( Date.now() - process.uptime() * 1000, 'twitter' ), - uptimeLong: timeAgo.format( Date.now() - process.uptime() * 1000, 'round' ), - timestamp: Date.now() - }; - - res.writeHead( statusCheck.code, { - 'Content-Type': 'application/json' - }); - - Log.error( `plst`, chalk.redBright( `[response]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `Failed to serve m3u playlist` ), - chalk.redBright( `` ), chalk.gray( `serveM3UPlaylist` ), - chalk.redBright( `` ), chalk.gray( `${ statusCheck.message }` ), - chalk.redBright( `` ), chalk.gray( `${ statusCheck.code }` ) ); - - res.end( JSON.stringify( statusCheck ) ); - } - } - finally - { - semaphore.release(); - } -} - -/* - serve > health check -*/ - -async function serveHealthCheck( req, res ) -{ - await semaphore.acquire(); - try - { - const paramUrl = new URL( req.url, `http://${ req.headers.host }` ).searchParams.get( 'api' ); - const paramSilent = new URL( req.url, `http://${ req.headers.host }` ).searchParams.get( 'silent' ); - - if ( !paramUrl ) - { - if ( Utils.str2bool( paramSilent ) !== true ) - { - Log.debug( `/api`, chalk.yellow( `[health]` ), chalk.white( `βš™οΈ` ), - chalk.blueBright( `` ), chalk.gray( `No api-key passed to health check` ) ); - } - } - - const statusCheck = - { - ip: envIpContainer, - gateway: envIpGateway, - client: clientIp( req ), - message: `healthy`, - status: `healthy`, - ref: req.url, - method: req.method || 'GET', - code: 200, - uptime: Math.round( process.uptime() ), - uptimeShort: timeAgo.format( Date.now() - process.uptime() * 1000, 'twitter' ), - uptimeLong: timeAgo.format( Date.now() - process.uptime() * 1000, 'round' ), - timestamp: Date.now() - }; - - res.writeHead( statusCheck.code, { - 'Content-Type': 'application/json' - }); - - if ( Utils.str2bool( paramSilent ) !== true ) - { - Log.ok( `/api`, chalk.yellow( `[health]` ), chalk.white( `βœ…` ), - chalk.greenBright( `` ), chalk.gray( `Response` ), - chalk.greenBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.greenBright( `` ), chalk.gray( `${ statusCheck.code }` ), - chalk.greenBright( `` ), chalk.gray( `${ statusCheck.status }` ), - chalk.greenBright( `` ), chalk.gray( `${ process.uptime() }` ) ); - } - - res.end( JSON.stringify( statusCheck ) ); - return; - } - catch ( err ) - { - if ( !res.headersSent ) - { - const statusCheck = - { - ip: envIpContainer, - gateway: envIpGateway, - client: clientIp( req ), - message: `health check failed`, - error: `${ err.message }`, - status: `unhealthy`, - ref: req.url, - method: req.method || 'GET', - code: 503, - uptime: Math.round( process.uptime() ), - uptimeShort: timeAgo.format( Date.now() - process.uptime() * 1000, 'twitter' ), - uptimeLong: timeAgo.format( Date.now() - process.uptime() * 1000, 'round' ), - timestamp: Date.now() - }; - - res.writeHead( statusCheck.code, { - 'Content-Type': 'application/json' - }); - - Log.error( `/api`, chalk.redBright( `[health]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `${ statusCheck.message } response` ), - chalk.redBright( `` ), chalk.gray( `${ statusCheck.code }` ), - chalk.redBright( `` ), chalk.gray( `${ statusCheck.status }` ), - chalk.redBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.redBright( `` ), chalk.gray( `${ err.message }` ), - chalk.redBright( `` ), chalk.gray( `${ process.uptime() }` ) ); - - res.end( JSON.stringify( statusCheck ) ); - } - } - finally - { - semaphore.release(); - } -} - -/* - Rewrites the URLs -*/ - -async function rewriteM3U( originalUrl, req ) -{ - const rawData = await fetchRemote( originalUrl, req ); - const protocol = req.headers['x-forwarded-proto']?.split( ',' )[0] || ( req.socket.encrypted ? 'https' : 'http' ); - const host = req.headers.host; - const baseUrl = `${ protocol }://${ host }`; - const playlistContent = rawData.toString( 'utf8' ); - return playlistContent - .replace( /URI="([^"]+)"/g, ( match, uri ) => - { - const resolvedUri = new URL( uri, originalUrl ).href; - return `URI="${ baseUrl }/key?uri=${ encodeURIComponent( resolvedUri ) }"`; - }) - .replace( /^([^#].*\.m3u8)(\?.*)?$/gm, ( match, uri ) => - { - const resolvedUri = new URL( uri, originalUrl ).href; - return `${ baseUrl }/channel?url=${ encodeURIComponent( resolvedUri ) }`; - }) - .replace( /^([^#].*\.ts)(\?.*)?$/gm, ( match, uri ) => - { - const resolvedUri = new URL( uri, originalUrl ).href; - return `${ baseUrl }/channel?url=${ encodeURIComponent( resolvedUri ) }`; - }); -} - -/* - serve > m3u - - Serves IPTV .m3u playlist -*/ - -async function serveM3U( res, req ) -{ - try - { - const protocol = req.headers['x-forwarded-proto']?.split( ',' )[0] || ( req.socket.encrypted ? 'https' : 'http' ); - const host = req.headers.host; - const baseUrl = `${ protocol }://${ host }`; - const formattedContent = fs.readFileSync( FILE_M3U, 'utf-8' ); - const updatedContent = formattedContent - .replace( /(https?:\/\/[^\s]*thetvapp[^\s]*)/g, ( fullUrl ) => - { - Log.debug( `.m3u`, chalk.yellow( `[rewriter]` ), chalk.white( `βš™οΈ` ), - chalk.blueBright( `` ), chalk.gray( `Rewriting url for keyword` ), - chalk.blueBright( `` ), chalk.gray( `*thetvapp` ), - chalk.blueBright( `` ), chalk.gray( `${ fullUrl }` ), - chalk.blueBright( `` ), chalk.gray( `${ baseUrl }/channel?url=${ encodeURIComponent( fullUrl ) }` ) ); - - return `${ baseUrl }/channel?url=${ encodeURIComponent( fullUrl ) }`; - }) - .replace( /(https?:\/\/[^\s]*tvpass[^\s]*)/g, ( fullUrl ) => - { - Log.debug( `.m3u`, chalk.yellow( `[rewriter]` ), chalk.white( `βš™οΈ` ), - chalk.blueBright( `` ), chalk.gray( `Rewriting url for keyword` ), - chalk.blueBright( `` ), chalk.gray( `*tvpass` ), - chalk.blueBright( `` ), chalk.gray( `${ fullUrl }` ), - chalk.blueBright( `` ), chalk.gray( `${ baseUrl }/channel?url=${ encodeURIComponent( fullUrl ) }` ) ); - - return `${ baseUrl }/channel?url=${ encodeURIComponent( fullUrl ) }`; - }); - /* - .replace( /(https?:\/\/fl\d+\.moveonjoy\.com[^\s]*)/g, ( fullUrl ) => - { - const urlRewrite = fullUrl.replace( /fl\d+\.moveonjoy\.com/, 'fl25.moveonjoy.com' ); - Log.debug( `.m3u`, chalk.yellow( `[rewriter]` ), chalk.white( `βš™οΈ` ), - chalk.blueBright( `` ), chalk.gray( `Rewriting url for keyword` ), - chalk.blueBright( `` ), chalk.gray( `*fl1.moveonjoy` ), - chalk.blueBright( `` ), chalk.gray( `${ fullUrl }` ), - chalk.blueBright( `` ), chalk.gray( `${ urlRewrite }` ) ); - - return `${ urlRewrite }`; - }); - */ - - res.writeHead( 200, { - 'Content-Type': 'application/x-mpegURL', - 'Content-Disposition': 'inline; filename="' + envFileM3U - }); - - Log.ok( `.m3u`, chalk.yellow( `[response]` ), chalk.white( `βœ…` ), - chalk.greenBright( `` ), chalk.gray( `Successfully served m3u8 channel playlist data` ), - chalk.greenBright( `` ), chalk.gray( `serveM3U` ), - chalk.greenBright( `` ), chalk.gray( `200` ), - chalk.greenBright( `` ), chalk.gray( `${ host }` ), - chalk.greenBright( `` ), chalk.gray( `${ req.url }` ), - chalk.greenBright( `` ), chalk.gray( `${ FILE_M3U }` ), - chalk.greenBright( `` ), chalk.gray( `${ envFileM3U }` ) ); - - res.end( updatedContent ); - } - catch ( err ) - { - const statusCheck = - { - ip: envIpContainer, - gateway: envIpGateway, - client: clientIp( req ), - message: `Fatal serving m3u8 channel playlist data`, - error: `${ err.message }`, - status: 'unhealthy', - ref: req.url, - method: req.method || 'GET', - code: 500, - uptime: Math.round( process.uptime() ), - uptimeShort: timeAgo.format( Date.now() - process.uptime() * 1000, 'twitter' ), - uptimeLong: timeAgo.format( Date.now() - process.uptime() * 1000, 'round' ), - timestamp: Date.now() - }; - - res.writeHead( statusCheck.code, { - 'Content-Type': 'application/json' - }); - - Log.error( `.m3u`, chalk.yellow( `[response]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `${ statusCheck.message }` ), - chalk.redBright( `` ), chalk.gray( `serveM3U` ), - chalk.redBright( `` ), chalk.gray( `${ statusCheck.code }` ), - chalk.redBright( `` ), chalk.gray( `${ err.message }` ), - chalk.redBright( `` ), chalk.gray( `${ req.url }` ), - chalk.redBright( `` ), chalk.gray( `${ FILE_XML }` ), - chalk.redBright( `` ), chalk.gray( `${ envFileXML }` ) ); - - res.end( JSON.stringify( statusCheck ) ); - } -} - -/* - serve > xml - - Serves IPTV uncompressed .xml guide data -*/ - -async function serveXML( res, req ) -{ - try - { - const protocol = req.headers['x-forwarded-proto']?.split( ',' )[0] || ( req.socket.encrypted ? 'https' : 'http' ); - const host = req.headers.host; - const baseUrl = `${ protocol }://${ host }`; - const formattedContent = fs.readFileSync( FILE_XML, 'utf-8' ); - - res.writeHead( 200, { - 'Content-Type': 'application/xml', - 'Content-Disposition': 'inline; filename="' + envFileXML - }); - - Log.ok( `.xml`, chalk.yellow( `[response]` ), chalk.white( `βœ…` ), - chalk.greenBright( `` ), chalk.gray( `Successfully served uncompressed xml / epg guide data` ), - chalk.greenBright( `` ), chalk.gray( `serveXML` ), - chalk.greenBright( `` ), chalk.gray( `200` ), - chalk.greenBright( `` ), chalk.gray( `${ host }` ), - chalk.greenBright( `` ), chalk.gray( `${ req.url }` ), - chalk.greenBright( `` ), chalk.gray( `${ FILE_XML }` ), - chalk.greenBright( `` ), chalk.gray( `${ envFileXML }` ) ); - - res.end( formattedContent ); - } - catch ( err ) - { - const statusCheck = - { - ip: envIpContainer, - gateway: envIpGateway, - client: clientIp( req ), - message: `Fatal serving uncompressed xml / epg guide data`, - error: `${ err.message }`, - status: 'unhealthy', - ref: req.url, - method: req.method || 'GET', - code: 500, - uptime: Math.round( process.uptime() ), - uptimeShort: timeAgo.format( Date.now() - process.uptime() * 1000, 'twitter' ), - uptimeLong: timeAgo.format( Date.now() - process.uptime() * 1000, 'round' ), - timestamp: Date.now() - }; - - res.writeHead( statusCheck.code, { - 'Content-Type': 'text/plain' - }); - - Log.error( `.xml`, chalk.yellow( `[response]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `${ statusCheck.message }` ), - chalk.redBright( `` ), chalk.gray( `serveXML` ), - chalk.redBright( `` ), chalk.gray( `${ statusCheck.code }` ), - chalk.redBright( `` ), chalk.gray( `${ err.message }` ), - chalk.redBright( `` ), chalk.gray( `${ req.url }` ), - chalk.redBright( `` ), chalk.gray( `${ FILE_XML }` ), - chalk.redBright( `` ), chalk.gray( `${ envFileXML }` ) ); - - res.end( JSON.stringify( statusCheck ) ); - } -}; - -/* - serve > gzip - - Serves IPTV compressed .gz guide data -*/ - -async function serveGZP( res, req ) -{ - try - { - const protocol = req.headers['x-forwarded-proto']?.split( ',' )[0] || ( req.socket.encrypted ? 'https' : 'http' ); - const host = req.headers.host; - const baseUrl = `${ protocol }://${ host }`; - const formattedContent = fs.readFileSync( FILE_GZP ); - - res.writeHead( 200, { - 'Content-Type': 'application/gzip', - 'Content-Disposition': 'inline; filename="' + envFileGZP - }); - - Log.ok( `.gzp`, chalk.yellow( `[response]` ), chalk.white( `βœ…` ), - chalk.greenBright( `` ), chalk.gray( `Successfully served compressed gzip xml/epg guide data` ), - chalk.greenBright( `` ), chalk.gray( `serveGZP` ), - chalk.greenBright( `` ), chalk.gray( `200` ), - chalk.greenBright( `` ), chalk.gray( `${ host }` ), - chalk.greenBright( `` ), chalk.gray( `${ req.url }` ), - chalk.greenBright( `` ), chalk.gray( `${ FILE_GZP }` ), - chalk.greenBright( `` ), chalk.gray( `${ envFileGZP }` ) ); - - res.end( formattedContent ); - } - catch ( err ) - { - const statusCheck = - { - ip: envIpContainer, - gateway: envIpGateway, - client: clientIp( req ), - message: `Fatal serving compressed gzip xml/epg guide data`, - error: `${ err.message }`, - status: 'unhealthy', - ref: req.url, - method: req.method || 'GET', - code: 500, - uptime: Math.round( process.uptime() ), - uptimeShort: timeAgo.format( Date.now() - process.uptime() * 1000, 'twitter' ), - uptimeLong: timeAgo.format( Date.now() - process.uptime() * 1000, 'round' ), - timestamp: Date.now() - }; - - res.writeHead( statusCheck.code, { - 'Content-Type': 'text/plain' - }); - - Log.error( `.gzp`, chalk.yellow( `[response]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `${ statusCheck.message }` ), - chalk.redBright( `` ), chalk.gray( `serveGZP` ), - chalk.redBright( `` ), chalk.gray( `${ statusCheck.code }` ), - chalk.redBright( `` ), chalk.gray( `${ err.message }` ), - chalk.redBright( `` ), chalk.gray( `${ req.url }` ), - chalk.redBright( `` ), chalk.gray( `${ FILE_GZP }` ), - chalk.redBright( `` ), chalk.gray( `${ envFileGZP }` ) ); - - res.end( JSON.stringify( statusCheck ) ); - } -}; - -/* - cache > set -*/ - -function setCache( req, key, value, ttl ) -{ - const expiry = Date.now() + ttl; - cache.set( key, { - value, - expiry - }); - - Log.debug( `cache`, chalk.yellow( `[assigner]` ), chalk.white( `βš™οΈ` ), - chalk.blueBright( `` ), chalk.gray( `New key created` ), - chalk.blueBright( `` ), chalk.gray( `setCache` ), - chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.blueBright( `` ), chalk.gray( `${ key }` ), - chalk.blueBright( `` ), chalk.gray( `${ ttl / 1000 } seconds` ) ); -} - -/* - cache > get -*/ - -function getCache( req, key ) -{ - const cached = cache.get( key ); - if ( cached && cached.expiry > Date.now() ) - { - return cached.value; - } - else - { - if ( cached ) - Log.debug( `cache`, chalk.yellow( `[get]` ), chalk.white( `βš™οΈ` ), - chalk.blueBright( `` ), chalk.gray( `Key has expired, marked for deletion` ), - chalk.blueBright( `` ), chalk.gray( `getCache` ), - chalk.blueBright( `` ), chalk.gray( `${ clientIp( req ) }` ), - chalk.blueBright( `` ), chalk.gray( `${ key }` ) ); - - cache.delete( key ); - return null; - } -} - -/* - Initialization - - this is the starting method to prepare tvapp2 -*/ - -async function initialize() -{ - const start = performance.now(); - try - { - const validation = crons.validateCronExpression( envTaskCronSync ); - if ( !validation.valid ) - { - Log.error( `core`, chalk.yellow( `[schedule]` ), chalk.white( `❌` ), - chalk.redBright( `` ), chalk.gray( `Specified cron schedule is not valid` ), - chalk.redBright( `` ), chalk.whiteBright.bgBlack( ` ${ envTaskCronSync } ` ) ); - } - else - { - const cronNextRunDt = new Date( crons.sendAt( envTaskCronSync ) ); - const cronNextRun = moment( cronNextRunDt ).format( 'MM-DD-YYYY h:mm A' ); - - Log.info( `core`, chalk.yellow( `[schedule]` ), chalk.white( `ℹ️` ), - chalk.blueBright( `` ), chalk.gray( `TVApp2 will refresh channel and guide data at` ), - chalk.blueBright( `` ), chalk.whiteBright.gray( ` ${ envTaskCronSync } ` ), - chalk.blueBright( `` ), chalk.whiteBright.gray( ` ${ cronNextRun } ` ), - chalk.blueBright( `` ), chalk.whiteBright.gray( ` ${ cronNextRunDt } ` ) ); - } - - Log.info( `core`, chalk.yellow( `[initiate]` ), chalk.white( `ℹ️` ), - chalk.blueBright( `` ), chalk.gray( `Starting TVApp2 container. Assigning bound IP to host network adapter` ), - chalk.blueBright( `` ), chalk.gray( `${ envWebIP }` ), - chalk.blueBright( `` ), chalk.gray( `${ envIpContainer }` ), - chalk.blueBright( `` ), chalk.gray( `${ envWebPort }` ) ); - - /* - Debug > network - */ - - Log.debug( `.net`, chalk.yellow( `[assigner]` ), chalk.white( `βš™οΈ` ), chalk.blueBright( `` ), chalk.gray( `IP_CONTAINER` ), chalk.blueBright( `` ), chalk.gray( `${ envIpContainer }` ) ); - Log.debug( `.net`, chalk.yellow( `[assigner]` ), chalk.white( `βš™οΈ` ), chalk.blueBright( `` ), chalk.gray( `IP_GATEWAY` ), chalk.blueBright( `` ), chalk.gray( `${ envIpGateway }` ) ); - Log.debug( `.env`, chalk.yellow( `[assigner]` ), chalk.white( `βš™οΈ` ), chalk.blueBright( `` ), chalk.gray( `RELEASE` ), chalk.blueBright( `` ), chalk.gray( `${ envAppRelease }` ) ); - - /* - Debug > Verbose > environment vars - */ - - const env = process.env; - Object.keys( env ).forEach( ( key ) => - { - Log.verbose( `.env`, chalk.yellow( `[assigner]` ), chalk.white( `πŸ“£` ), chalk.blueBright( `` ), chalk.gray( `${ key }` ), chalk.blueBright( `` ), chalk.gray( `${ env[key] }` ) ); - }); - - /* - Debug > environment vars - - we could just loop process.env; but that will show every container env var. We just want this app - */ - - Log.debug( `.env`, chalk.yellow( `[assigner]` ), chalk.white( `βš™οΈ` ), chalk.blueBright( `` ), chalk.gray( `URL_REPO` ), chalk.blueBright( `` ), chalk.gray( `${ envUrlRepo }` ) ); - Log.debug( `.env`, chalk.yellow( `[assigner]` ), chalk.white( `βš™οΈ` ), chalk.blueBright( `` ), chalk.gray( `WEB_IP` ), chalk.blueBright( `` ), chalk.gray( `${ envWebIP }` ) ); - Log.debug( `.env`, chalk.yellow( `[assigner]` ), chalk.white( `βš™οΈ` ), chalk.blueBright( `` ), chalk.gray( `WEB_PORT` ), chalk.blueBright( `` ), chalk.gray( `${ envWebPort }` ) ); - Log.debug( `.env`, chalk.yellow( `[assigner]` ), chalk.white( `βš™οΈ` ), chalk.blueBright( `` ), chalk.gray( `WEB_FOLDER` ), chalk.blueBright( `` ), chalk.gray( `${ envWebFolder }` ) ); - Log.debug( `.env`, chalk.yellow( `[assigner]` ), chalk.white( `βš™οΈ` ), chalk.blueBright( `` ), chalk.gray( `WEB_ENCODING` ), chalk.blueBright( `` ), chalk.gray( `${ envWebEncoding }` ) ); - Log.debug( `.env`, chalk.yellow( `[assigner]` ), chalk.white( `βš™οΈ` ), chalk.blueBright( `` ), chalk.gray( `WEB_PROXY_HEADER` ), chalk.blueBright( `` ), chalk.gray( `${ envProxyHeader }` ) ); - Log.debug( `.env`, chalk.yellow( `[assigner]` ), chalk.white( `βš™οΈ` ), chalk.blueBright( `` ), chalk.gray( `STREAM_QUALITY` ), chalk.blueBright( `` ), chalk.gray( `${ envStreamQuality }` ) ); - Log.debug( `.env`, chalk.yellow( `[assigner]` ), chalk.white( `βš™οΈ` ), chalk.blueBright( `` ), chalk.gray( `API_KEY` ), chalk.blueBright( `` ), chalk.gray( `${ envApiKey }` ) ); - Log.debug( `.env`, chalk.yellow( `[assigner]` ), chalk.white( `βš™οΈ` ), chalk.blueBright( `` ), chalk.gray( `FILE_URL` ), chalk.blueBright( `` ), chalk.gray( `${ envFileURL }` ) ); - Log.debug( `.env`, chalk.yellow( `[assigner]` ), chalk.white( `βš™οΈ` ), chalk.blueBright( `` ), chalk.gray( `FILE_M3U` ), chalk.blueBright( `` ), chalk.gray( `${ envFileM3U }` ) ); - Log.debug( `.env`, chalk.yellow( `[assigner]` ), chalk.white( `βš™οΈ` ), chalk.blueBright( `` ), chalk.gray( `FILE_EPG` ), chalk.blueBright( `` ), chalk.gray( `${ envFileXML }` ) ); - Log.debug( `.env`, chalk.yellow( `[assigner]` ), chalk.white( `βš™οΈ` ), chalk.blueBright( `` ), chalk.gray( `FILE_GZP` ), chalk.blueBright( `` ), chalk.gray( `${ envFileGZP }` ) ); - Log.debug( `.env`, chalk.yellow( `[assigner]` ), chalk.white( `βš™οΈ` ), chalk.blueBright( `` ), chalk.gray( `HEALTH_TIMER` ), chalk.blueBright( `` ), chalk.gray( `${ envHealthTimer }` ) ); - Log.debug( `.env`, chalk.yellow( `[assigner]` ), chalk.white( `βš™οΈ` ), chalk.blueBright( `` ), chalk.gray( `LOG_LEVEL` ), chalk.blueBright( `` ), chalk.gray( `${ LOG_LEVEL }` ) ); - Log.debug( `.env`, chalk.yellow( `[assigner]` ), chalk.white( `βš™οΈ` ), chalk.blueBright( `` ), chalk.gray( `USERAGENT` ), chalk.blueBright( `` ), chalk.gray( `${ USERAGENT }` ) ); - - /* - Debug > vars > external urls - */ - - Log.debug( `.var`, chalk.yellow( `[assigner]` ), chalk.white( `βš™οΈ` ), chalk.blueBright( `` ), chalk.gray( `extURL` ), chalk.blueBright( `` ), chalk.gray( `${ extURL }` ) ); - Log.debug( `.var`, chalk.yellow( `[assigner]` ), chalk.white( `βš™οΈ` ), chalk.blueBright( `` ), chalk.gray( `extXML` ), chalk.blueBright( `` ), chalk.gray( `${ extXML }` ) ); - Log.debug( `.var`, chalk.yellow( `[assigner]` ), chalk.white( `βš™οΈ` ), chalk.blueBright( `` ), chalk.gray( `extM3U` ), chalk.blueBright( `` ), chalk.gray( `${ extM3U }` ) ); - - /* - Debug > vars > subdomain keywords - */ - - Log.debug( `.var`, chalk.yellow( `[assigner]` ), chalk.white( `βš™οΈ` ), chalk.blueBright( `` ), chalk.gray( `subdomainGZP` ), chalk.blueBright( `` ), chalk.gray( `${ subdomainGZP.join() }` ) ); - Log.debug( `.var`, chalk.yellow( `[assigner]` ), chalk.white( `βš™οΈ` ), chalk.blueBright( `` ), chalk.gray( `subdomainM3U` ), chalk.blueBright( `` ), chalk.gray( `${ subdomainM3U.join() }` ) ); - Log.debug( `.var`, chalk.yellow( `[assigner]` ), chalk.white( `βš™οΈ` ), chalk.blueBright( `` ), chalk.gray( `subdomainEPG` ), chalk.blueBright( `` ), chalk.gray( `${ subdomainEPG.join() }` ) ); - Log.debug( `.var`, chalk.yellow( `[assigner]` ), chalk.white( `βš™οΈ` ), chalk.blueBright( `` ), chalk.gray( `subdomainKey` ), chalk.blueBright( `` ), chalk.gray( `${ subdomainKey.join() }` ) ); - Log.debug( `.var`, chalk.yellow( `[assigner]` ), chalk.white( `βš™οΈ` ), chalk.blueBright( `` ), chalk.gray( `subdomainChan` ), chalk.blueBright( `` ), chalk.gray( `${ subdomainChan.join() }` ) ); - Log.debug( `.var`, chalk.yellow( `[assigner]` ), chalk.white( `βš™οΈ` ), chalk.blueBright( `` ), chalk.gray( `subdomainHealth` ), chalk.blueBright( `` ), chalk.gray( `${ subdomainHealth.join() }` ) ); - Log.debug( `.var`, chalk.yellow( `[assigner]` ), chalk.white( `βš™οΈ` ), chalk.blueBright( `` ), chalk.gray( `subdomainRestart` ), chalk.blueBright( `` ), chalk.gray( `${ subdomainRestart.join() }` ) ); - - /* - get files - */ - - await getFile( extURL, FILE_URL ); - await getFile( extXML, FILE_XML ); - await getFile( extM3U, FILE_M3U ); - await getGzip(); - - urls = fs.readFileSync( FILE_URL, 'utf-8' ).split( '\n' ).filter( Boolean ); - if ( urls.length === 0 ) - throw new Error( `No valid URLs found in ${ FILE_URL }` ); - - /* - Calculate Sizes - */ - - FILE_M3U_SIZE = getFileSizeHuman( FILE_M3U ); - FILE_XML_SIZE = getFileSizeHuman( FILE_XML ); - FILE_GZP_SIZE = getFileSizeHuman( FILE_GZP ); - - FILE_M3U_MODIFIED = getFileModified( FILE_M3U ); - FILE_XML_MODIFIED = getFileModified( FILE_XML ); - FILE_GZP_MODIFIED = getFileModified( FILE_GZP ); - - const end = performance.now(); - serverStartup = `${ end - start }`; - Log.info( `core`, chalk.yellow( `[initiate]` ), chalk.white( `ℹ️` ), - chalk.blueBright( `` ), chalk.gray( `TVApp2 container is ready` ), - chalk.blueBright( `