Compare commits

...

18 Commits

Author SHA1 Message Date
iFlip721
82fb6c3bcd update api 2026-06-11 18:10:09 -04:00
iFlip721
a697acc0f3 drop old sources 2026-06-11 17:00:32 -04:00
7046428246 Delete Screenshots directory 2026-06-11 16:50:39 -04:00
iFlip721
245034a43a initial push from external dev branches 2026-06-11 16:40:21 -04:00
iFlip721
986e83632b cleanup prep 2026-06-11 16:37:09 -04:00
iFlip721
30aa901b4d update ignore 2026-06-11 16:36:57 -04:00
renovate[bot]
321a0e8540 chore(deps): lock file maintenance (#166)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-20 04:55:03 +00:00
renovate[bot]
cb8f769e34 chore(deps): lock file maintenance (#164)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-13 04:24:45 +00:00
renovate[bot]
38ff77a04e chore(deps): lock file maintenance (#162)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-06 02:01:35 +00:00
renovate[bot]
292cd8dd94 chore(deps): lock file maintenance (#161)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-30 04:52:58 +00:00
renovate[bot]
3878059314 chore(deps): lock file maintenance (#160)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-23 02:14:45 +00:00
renovate[bot]
7a7e50c7ba chore(deps): lock file maintenance (#158)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-16 01:49:18 +00:00
renovate[bot]
564dd536fc chore(deps): lock file maintenance (#157)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-09 05:28:15 +00:00
renovate[bot]
0e29805351 chore(deps): lock file maintenance (#152)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-02 09:38:43 +00:00
dc76267da3 Merge pull request #151 from TheBinaryNinja/m3u_xmltv_feature
PR 151: bump: version 1.5.9
2026-02-27 14:32:17 -05:00
147b11b22d bump: version 1.5.9 2026-02-27 14:30:03 -05:00
renovate[bot]
b992e4ff01 chore(deps): lock file maintenance (#149)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-23 05:25:33 +00:00
renovate[bot]
b46a922464 chore(deps): lock file maintenance (#147)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-16 04:56:43 +00:00
272 changed files with 15514 additions and 13325 deletions

335
.gitignore vendored
View File

@@ -1,323 +1,94 @@
# #
# Dependency directories
# #
node_modules/
tvapp2/node_modules/
jspm_packages/
# #
# Snowpack dependency directory (https://snowpack.dev/)
# #
web_modules/
# #
# TypeScript cache
# #
\*.tsbuildinfo
# #
# Optional npm cache directory
# #
.npm
# #
# npm files
# #
.npmrc
# #
# Optional eslint cache
# #
.eslintcache
# #
# Optional stylelint cache
# #
.stylelintcache
# #
# Microbundle cache
# #
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# #
# Optional REPL history
# #
.node_repl_history
# #
# Output of 'npm pack'
# #
\*.tgz
# #
# Yarn Integrity file
# #
.yarn-integrity
# #
# Binaries
# #
*.exe
*.exe~
*.dll
*.so
*.dylib
# #
# TVApp2 Specific
# #
*.dat
*.xml
*.txt
# #
# Test binary
# build with `go test -c`
# #
*.test
# #
# Coverage directory used by tools like istanbul
# #
coverage
\*.lcov
*.out
# #
# nyc test coverage
# #
.nyc_output
# #
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
# #
.grunt
# #
# Bower dependency directory (https://bower.io/)
# #
bower_components
# #
# node-waf configuration
# #
.lock-wscript
# #
# Compiled binary addons (https://nodejs.org/api/addons.html)
# #
build/Release
# #
# Dependency directories (remove the comment below to include it)
# vendor/
# #
# #
# Go workspace file
# #
go.work
# #
# Mac
# #
.DS_STORE
# #
# Visual Studio Code
# #
.vscode/
# #
# Temp folders
# #
.temp/
temp/
work/
# #
# Python Cache
# #
__pycache__/
# #
# Logs
# #
logs
_.log
coverage
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
temp/
work/
logs
keys
tmp
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
pids
lib-cov
_.log
_.pid
_.seed
__pycache__/
\*.tsbuildinfo
\*.tgz
\*.lcov
\*.pid.lock
.npm
.npmrc
.eslintcache
.stylelintcache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
.node_repl_history
.yarn-integrity
.DS_STORE
.vscode/
.temp/
.nyc_output
.grunt
.lock-wscript
.pnpm-debug.log*
# #
# yarn v2
# #
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.\*
# #
# wrangler project
# #
.dev.vars
.wrangler/
# #
# sources
# #
.src
# #
# dist
# #
.dist
# #
# Private Files
# #
.dev
keys
# #
# dotenv environment variable files
# #
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# #
# parcel-bundler cache (https://parceljs.org/)
# #
.cache
.parcel-cache
# #
# Next.js build output
# #
.next
# #
# Nuxt.js build / generate output
# #
.nuxt
# #
# vuepress build output
# #
.vuepress/dist
# #
# Docusaurus cache and generated files
# #
.docusaurus
# #
# Serverless directories
# #
.serverless/
# #
# FuseBox cache
# #
.fusebox/
# #
# DynamoDB Local files
# #
.dynamodb/
# #
# TernJS port file
# #
.tern-port
# #
# Gatsby files
# #
.cache/
# #
# Misc
# #
tmp
*.user
# #
# Python files
# #
.opt-*
# #
# Diagnostic reports (https://nodejs.org/api/report.html)
# #
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# #
# Runtime data
# #
pids
_.pid
_.seed
\*.pid.lock
# #
# Directory for instrumented libs generated by jscoverage/JSCover
# #
lib-cov
*.user
*.exe
*.exe~
*.dll
*.so
*.dylib
*c*de*
*.dat
*.xml
*.txt
*.test
*.out

View File

View File

@@ -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"]

View File

@@ -0,0 +1,5 @@
{
"mongoUri": "mongodb://MONGO_ROOT_USER:MONGO_ROOT_PASS@mongo:27017/tvapp2?authSource=admin",
"port": 3000,
"logLevel": "info"
}

5
config/config.json Normal file
View File

@@ -0,0 +1,5 @@
{
"mongoUri": "mongodb://tvapp:tvapp@mongo:27017/tvapp2?authSource=admin",
"port": 3000,
"logLevel": "info"
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
import{v as g,r,o as e,u as n,X as o,e as d,F as y,K as p,O as u,d as b,H as l,t as a,y as h,f as c,N as i,g as k,p as C,_ as m,q as S,Q as $,j as x}from"./index-CQPQcDLN.js";const E={class:"col"},z={class:"card flush"},N={class:"toolbar"},w=["onClick"],B={class:"src-name"},G={class:"src-url"},P={class:"stat-mini"},V={class:"stat-mini"},F={class:"stat-mini",style:{"min-width":"110px"}},L={style:{"font-size":"12px","font-weight":"500",color:"var(--text-1)"}},A=g({__name:"EPGSourcesScreen",emits:["add"],setup(O,{emit:_}){const v=_,f=$();return(R,s)=>(l(),r("div",E,[e("div",z,[e("div",N,[n(x,{value:"",onChange:()=>{},placeholder:"Search EPG sources"}),s[3]||(s[3]=e("span",{class:"spacer"},null,-1)),n(d,{variant:"ghost",icon:"refresh"},{default:o(()=>[...s[1]||(s[1]=[a("Sync all",-1)])]),_:1}),n(d,{variant:"primary",icon:"plus",onClick:s[0]||(s[0]=t=>v("add","epg"))},{default:o(()=>[...s[2]||(s[2]=[a("Add EPG source",-1)])]),_:1})]),(l(!0),r(y,null,p(u(b),t=>(l(),r("div",{key:t.id,class:"src-row",onClick:j=>u(f).push(`/epg-sources/${t.id}`)},[e("div",{class:h(["src-ico",{builtin:t.builtin,"epg-builtin":t.builtin}]),style:{color:"var(--good)"}},[n(c,{name:t.builtin?"tv":"epg",size:18},null,8,["name"])],2),e("div",null,[e("div",B,[a(i(t.name)+" ",1),n(k,{status:t.status,pulse:t.status==="good"},null,8,["status","pulse"]),t.builtin?(l(),C(m,{key:0,tone:"system"},{default:o(()=>[n(c,{name:"check",size:10}),s[4]||(s[4]=a("built-in",-1))]),_:1})):S("",!0),n(m,{tone:"cyan"},{default:o(()=>[a(i(t.interval),1)]),_:2},1024)]),e("div",G,i(t.url),1)]),e("div",P,[e("b",null,i(t.channels),1),s[5]||(s[5]=a("channels",-1))]),e("div",V,[e("b",null,i(t.programs.toLocaleString()),1),s[6]||(s[6]=a("programs",-1))]),e("div",F,[e("b",L,i(t.lastSync),1),s[7]||(s[7]=a(" last sync ",-1))]),n(d,{variant:"ghost",size:"sm",icon:"more"})],8,w))),128))])]))}});export{A as default};

File diff suppressed because one or more lines are too long

1
dist/assets/ImportScreen-D7vLRk6-.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/assets/MappingScreen-BdiMBcth.js vendored Normal file
View File

@@ -0,0 +1 @@
import{v as V,C as f,r,o as n,u as i,O as z,z as A,X as m,e as w,t as u,y as c,F as g,K as N,q as $,_ as k,I as D,J as B,n as E,H as l,N as p,p as y,f as v,i as G}from"./index-CQPQcDLN.js";import{_ as U}from"./Stat.vue_vue_type_script_setup_true_lang-BLQk8QX-.js";const K={class:"col"},T={class:"card",style:{display:"flex","align-items":"center",gap:"18px"}},j={style:{width:"180px",height:"6px",background:"var(--bg-2)","border-radius":"999px",overflow:"hidden"}},F={class:"map-grid"},O={class:"map-col"},P={class:"segmented",style:{padding:"2px"}},H={class:"map-list"},J=["onClick"],L={class:"nm"},q={class:"id"},X={key:0,class:"empty"},Q={class:"map-link",style:{"align-self":"center"}},R={class:"map-col"},W={class:"map-list"},Y=["onClick"],Z={class:"nm"},ee={class:"id"},ae=V({__name:"MappingScreen",setup(ne){const _=[{id:"bbc.one.uk",name:"BBC One"},{id:"bbc.two.uk",name:"BBC Two"},{id:"bbc.news.uk",name:"BBC News"},{id:"sky.sports.main.uk",name:"Sky Sports Main Event"},{id:"sky.sports.f1.uk",name:"Sky Sports F1"},{id:"itv1.uk",name:"ITV1"},{id:"channel4.uk",name:"Channel 4"},{id:"film4.uk",name:"Film4"},{id:"discovery.uk",name:"Discovery Channel UK"},{id:"natgeo.uk",name:"National Geographic UK"},{id:"cnn.int",name:"CNN International"},{id:"aljazeera.en",name:"Al Jazeera English"},{id:"hgtv.uk",name:"HGTV UK"},{id:"nickjr.uk",name:"Nick Jr UK"},{id:"tcm.uk",name:"TCM Movies"},{id:"eurosport1.uk",name:"Eurosport 1"}],a=D({});f.forEach(s=>{s.epg==="matched"&&s.tvg_id&&(a[s.id]=s.tvg_id)});const o=B(null),d=B("unmatched"),x=E(()=>f.filter(s=>d.value==="all"||(d.value==="unmatched"?!a[s.id]:!!a[s.id])));function S(s,e){a[s]=e}function I(s){delete a[s]}const C=E(()=>Object.keys(a).length),b=f.length;function h(s){return Object.entries(a).find(([,e])=>e===s)}return(s,e)=>(l(),r("div",K,[n("div",T,[i(v,{name:"map",size:20}),e[4]||(e[4]=n("div",{style:{flex:"1"}},[n("div",{style:{"font-weight":"600","font-size":"15px"}},"Channel ↔ EPG mapping"),n("div",{class:"muted",style:{"font-size":"var(--fs-xs)","margin-top":"2px"}}," Drag from left to right, or pick a channel and click the EPG ID. Auto-match runs nightly. ")],-1)),i(U,{label:"Matched",value:`${C.value} / ${z(b)}`},null,8,["value"]),n("div",j,[n("div",{style:A({width:C.value/z(b)*100+"%",height:"100%",background:"var(--accent)",boxShadow:"0 0 12px var(--accent)"})},null,4)]),i(w,{variant:"primary",icon:"refresh"},{default:m(()=>[...e[3]||(e[3]=[u("Auto-match",-1)])]),_:1})]),n("div",F,[n("div",O,[n("h3",null,[i(v,{name:"playlist",size:14}),e[5]||(e[5]=u(" M3U Channels ",-1)),e[6]||(e[6]=n("span",{class:"spacer"},null,-1)),n("div",P,[n("button",{class:c(d.value==="unmatched"?"active":""),onClick:e[0]||(e[0]=t=>d.value="unmatched"),style:{"font-size":"10.5px",padding:"3px 8px"}},"Unmatched",2),n("button",{class:c(d.value==="matched"?"active":""),onClick:e[1]||(e[1]=t=>d.value="matched"),style:{"font-size":"10.5px",padding:"3px 8px"}},"Matched",2),n("button",{class:c(d.value==="all"?"active":""),onClick:e[2]||(e[2]=t=>d.value="all"),style:{"font-size":"10.5px",padding:"3px 8px"}},"All",2)])]),n("div",H,[(l(!0),r(g,null,N(x.value,t=>(l(),r("div",{key:t.id,class:c(["map-item",{selected:o.value===t.id,matched:!!a[t.id]}]),onClick:M=>o.value=t.id},[i(G,{ch:t},null,8,["ch"]),n("div",L,p(t.tvg_name),1),a[t.id]?(l(),r(g,{key:0},[n("span",q,p(a[t.id]),1),i(w,{variant:"ghost",size:"sm",icon:"x",onClick:M=>I(t.id)},null,8,["onClick"])],64)):(l(),y(k,{key:1,tone:"warn"},{default:m(()=>[...e[7]||(e[7]=[u("unmatched",-1)])]),_:1}))],10,J))),128)),x.value.length===0?(l(),r("div",X,[...e[8]||(e[8]=[n("h3",null,"All matched 🎉",-1),n("p",null,"Every channel in this view has an EPG ID assigned.",-1)])])):$("",!0)])]),n("div",Q,[i(v,{name:"chevron-r",size:22})]),n("div",R,[n("h3",null,[i(v,{name:"epg",size:14}),e[9]||(e[9]=u(" EPG channel IDs ",-1)),e[10]||(e[10]=n("span",{class:"spacer"},null,-1)),i(k,null,{default:m(()=>[u(p(_.length),1)]),_:1})]),n("div",W,[(l(),r(g,null,N(_,t=>n("div",{key:t.id,class:c(["map-item",{selected:o.value&&!h(t.id),matched:!!h(t.id)}]),onClick:()=>{o.value&&!h(t.id)&&(S(o.value,t.id),o.value=null)}},[n("div",Z,p(t.name),1),n("span",ee,p(t.id),1),h(t.id)?(l(),y(k,{key:0,tone:"good"},{default:m(()=>[i(v,{name:"check",size:10}),e[11]||(e[11]=u("linked",-1))]),_:1})):o.value?(l(),y(k,{key:1,tone:"cyan"},{default:m(()=>[...e[12]||(e[12]=[u("click to link",-1)])]),_:1})):$("",!0)],10,Y)),64))])])])]))}});export{ae as default};

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
import{v as k,D as g,r as d,o as a,u as i,X as l,e as _,F as h,K as C,J as $,H as o,t as e,O as u,y as x,f as v,N as n,g as w,p as S,_ as c,q as N,Q as z,j as B}from"./index-CQPQcDLN.js";import{u as f}from"./useSettings-CPUgOpin.js";const V={class:"col"},j={class:"card flush"},A={class:"toolbar"},D=["onClick"],F={class:"src-name"},P={class:"src-url"},q={class:"stat-mini"},E={class:"stat-mini"},H={class:"stat-mini",style:{"min-width":"110px"}},I={style:{"font-size":"12px","font-weight":"500",color:"var(--text-1)"}},O=k({__name:"PlaylistsScreen",emits:["add"],setup(J,{emit:y}){const p=y,b=z(),m=$([]);return g(async()=>{const r=await fetch("/api/playlists");r.ok&&(m.value=await r.json())}),(r,s)=>(o(),d("div",V,[a("div",j,[a("div",A,[i(B,{value:"",onChange:()=>{},placeholder:"Search playlists"}),s[3]||(s[3]=a("span",{class:"spacer"},null,-1)),i(_,{variant:"ghost",icon:"refresh"},{default:l(()=>[...s[1]||(s[1]=[e("Sync all",-1)])]),_:1}),i(_,{variant:"primary",icon:"plus",onClick:s[0]||(s[0]=t=>p("add","playlist"))},{default:l(()=>[...s[2]||(s[2]=[e("Add playlist",-1)])]),_:1})]),(o(!0),d(h,null,C(m.value,t=>(o(),d("div",{key:t.id,class:"src-row",onClick:K=>u(b).push(`/playlists/${t.id}`)},[a("div",{class:x(["src-ico",{builtin:t.builtin}])},[i(v,{name:t.builtin?"tv":"playlist",size:18},null,8,["name"])],2),a("div",null,[a("div",F,[e(n(t.name)+" ",1),i(w,{status:t.status,pulse:t.status==="good"},null,8,["status","pulse"]),t.builtin?(o(),S(c,{key:0,tone:"system"},{default:l(()=>[i(v,{name:"check",size:10}),s[4]||(s[4]=e("built-in",-1))]),_:1})):N("",!0),i(c,{tone:"cyan"},{default:l(()=>[e(n(t.interval),1)]),_:2},1024)]),a("div",P,n(t.url),1)]),i(c,{tone:u(f)(t.id).active?"active":"disabled"},{default:l(()=>[e(n(u(f)(t.id).active?"Active":"Inactive"),1)]),_:2},1032,["tone"]),a("div",q,[a("b",null,n(t.channels),1),s[5]||(s[5]=e("channels",-1))]),a("div",E,[a("b",null,n(t.groups),1),s[6]||(s[6]=e("groups",-1))]),a("div",H,[a("b",I,n(t.lastSync),1),s[7]||(s[7]=e(" last sync ",-1))])],8,D))),128))])]))}});export{O as default};

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
import{v as l,r as o,o as t,N as a,z as n,L as r,H as i,t as m}from"./index-CQPQcDLN.js";const c={style:{"text-align":"right"}},d={class:"muted",style:{"font-size":"var(--fs-xs)","text-transform":"uppercase","letter-spacing":"0.06em"}},u=l({__name:"Stat",props:{label:{},value:{},small:{type:Boolean}},setup(e){return(s,f)=>(i(),o("div",c,[t("div",d,a(e.label),1),t("div",{style:n({fontWeight:600,fontSize:e.small?"var(--fs-sm)":"18px",fontVariantNumeric:"tabular-nums",marginTop:"2px"})},[r(s.$slots,"default",{},()=>[m(a(e.value),1)])],4)]))}});export{u as _};

1
dist/assets/index-BzPI8e-F.css vendored Normal file

File diff suppressed because one or more lines are too long

26
dist/assets/index-CQPQcDLN.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/assets/useSettings-CPUgOpin.js vendored Normal file
View File

@@ -0,0 +1 @@
import{n as c,J as s,I as l}from"./index-CQPQcDLN.js";const h=s("TVApp2 Workspace"),u=s("https://tvapp2.example.com"),o=s("/m3u/playlist.m3u8"),$=s("/epg/guide.xml.gz"),m=c(()=>`${u.value.replace(/\/$/,"")}${o.value.startsWith("/")?"":"/"}${o.value}`),e=l({});function i(t){return e[t]||(e[t]={active:!0,endpointMode:"global",customPath:`/playlists/${t}.m3u`}),e[t]}function d(t){const a=i(t),n=u.value.replace(/\/$/,"");if(a.endpointMode==="custom"){const p=a.customPath.startsWith("/")?a.customPath:`/${a.customPath}`;return`${n}${p}`}return m.value}export{u as a,o as b,h as d,$ as e,m,d as p,i as u};

19
dist/index.html vendored Normal file
View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en" data-theme="dark" data-density="regular">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>TVApp2 — M3U &amp; EPG Manager</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
<style>
@keyframes slidein { from { transform: translateX(20px); opacity: 0; } to { transform: none; opacity: 1; } }
</style>
<script type="module" crossorigin src="/assets/index-CQPQcDLN.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BzPI8e-F.css">
</head>
<body>
<div id="app"></div>
</body>
</html>

2
dist/types-node/vite.config.d.ts vendored Normal file
View File

@@ -0,0 +1,2 @@
declare const _default: import("vite").UserConfig;
export default _default;

14
dist/types-node/vite.config.js vendored Normal file
View File

@@ -0,0 +1,14 @@
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue()],
server: {
port: 5173,
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
},
},
},
});

126
docker-compose.yml Executable file → Normal file
View File

@@ -1,50 +1,84 @@
# #
# 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
# #
# Pinned image versions — bump in lockstep with docker/*.Dockerfile.
# MONGO = 7.0.15
# Requires Docker Engine >= 24.0 and Docker Compose >= 2.20 (Compose Spec).
# The legacy top-level `version:` key is intentionally omitted (deprecated in Compose v2).
services:
# One-shot init: writes ${TVAPP2_CONFIG_DIR}/config.json from the MONGO_ROOT_USER /
# MONGO_ROOT_PASS values in .env. The JSON template is embedded below — NO host file is
# bind-mounted — so a deployment host needs only this compose file + .env, nothing else to
# ship. (Bind-mounting a host template is a footgun: if the file is missing Docker silently
# creates it as an empty *directory*, and config.json ends up empty.) Left untouched if an
# existing config.json already targets the compose "mongo" host; regenerated otherwise
# (e.g. a stale @127.0.0.1: config left by an all-in-one run).
config-init:
image: alpine:3.20
environment:
MONGO_ROOT_USER: ${MONGO_ROOT_USER}
MONGO_ROOT_PASS: ${MONGO_ROOT_PASS}
volumes:
- ${TVAPP2_CONFIG_DIR}:/out
command:
- sh
- -c
- |
set -eu
if [ -s /out/config.json ] && grep -q '@mongo:' /out/config.json; then
echo "[config-init] /out/config.json already targets @mongo: — leaving alone"
exit 0
fi
if [ -s /out/config.json ]; then
echo "[config-init] /out/config.json does not target @mongo: — regenerating"
fi
printf '{\n "mongoUri": "mongodb://%s:%s@mongo:27017/tvapp2?authSource=admin",\n "port": 3000,\n "logLevel": "info"\n}\n' \
"$$MONGO_ROOT_USER" "$$MONGO_ROOT_PASS" > /out/config.json
echo "[config-init] wrote /out/config.json"
restart: "no"
# #
# Service > TVApp2
# #
app:
image: iflip721/tvapp2-app-stack:2.0.5-dev
build:
context: .
dockerfile: docker/app.Dockerfile
ports:
- "3000:3000"
volumes:
# config.json is generated by the config-init service from .env (embedded template).
# Set TVAPP2_CONFIG_DIR in .env to the host directory that holds it.
- ${TVAPP2_CONFIG_DIR}:/etc/tvapp2:rw
depends_on:
config-init:
condition: service_completed_successfully
mongo:
condition: service_healthy
networks: [tvnet]
restart: unless-stopped
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
mongo:
image: mongo:7.0.15
environment:
MONGO_INITDB_ROOT_USERNAME: ${MONGO_ROOT_USER}
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_ROOT_PASS}
ports:
# Expose mongod on the host. Override the host-side port with
# MONGO_HOST_PORT in .env (defaults to 27017).
- "${MONGO_HOST_PORT:-27017}:27017"
volumes:
# Persistent MongoDB data. Set MONGO_DATA_PATH in .env to an absolute
# host path (e.g. /srv/tvapp2/mongo) for a bind mount; leave unset to
# use the named volume defined below.
- ${MONGO_DATA_PATH:-mongo-data}:/data/db
healthcheck:
test: ["CMD", "mongosh", "--quiet", "--eval", "db.adminCommand('ping').ok"]
interval: 10s
timeout: 5s
retries: 5
networks: [tvnet]
restart: unless-stopped
volumes:
# Fallback named volume — used only when MONGO_DATA_PATH is unset.
mongo-data:
networks:
tvnet:

View File

@@ -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 "$@"

65
docker/app.Dockerfile Normal file
View File

@@ -0,0 +1,65 @@
# syntax=docker/dockerfile:1.7
# -----------------------------------------------------------------------------
# docker/app.Dockerfile — TVApp2 "app stack" image (iflip721/tvapp2-app-stack)
#
# Three-stage build:
# 1) spa-build — Vue 3 + Vite SPA → /spa/dist (root package.json)
# 2) server-build — Express API (tsc) → /server/dist (server/package.json)
# 3) runtime — prod-only Node; serves API + built SPA on :3000
#
# Runtime layout (must match server/src/index.ts publicDir and sources/paths.ts SEED_SOURCES_DIR):
# /app/dist/ compiled server (dist/index.js, dist/sources/paths.js)
# /app/public/ built SPA (resolve(<dist>,'..','public') => /app/public)
# /app/seed-data/ source bundles (resolve(<dist>,'..','..','seed-data','sources'))
# /app/package.json server pkg (type:module) + node_modules (express, mongoose only)
#
# Node pin: 22.11.0 LTS "Jod" on alpine 3.20 — keep in lockstep with CLAUDE.md.
# -----------------------------------------------------------------------------
ARG NODE_IMAGE=node:22.11.0-alpine3.20
# ---- Stage 1: build the SPA (root package) ----------------------------------
FROM ${NODE_IMAGE} AS spa-build
WORKDIR /spa
COPY package.json package-lock.json ./
RUN npm ci
COPY tsconfig.json tsconfig.node.json vite.config.ts index.html ./
COPY public/ ./public/
COPY src/ ./src/
RUN npm run build # vue-tsc -b && vite build -> /spa/dist
# ---- Stage 2: build the server (server package) -----------------------------
FROM ${NODE_IMAGE} AS server-build
WORKDIR /server
COPY server/package.json server/package-lock.json ./
RUN npm ci # devDeps (typescript) needed to compile
COPY server/tsconfig.json ./
COPY server/src/ ./src/
RUN npm run build # tsc -p . -> /server/dist
# ---- Stage 3: runtime -------------------------------------------------------
FROM ${NODE_IMAGE} AS runtime
ENV NODE_ENV=production \
TVAPP2_CONFIG=/etc/tvapp2/config.json
WORKDIR /app
# tini = correct PID 1 (forwards SIGTERM/SIGINT to the graceful-shutdown handler in index.ts).
RUN apk add --no-cache tini
# Prod-only server deps (express, mongoose).
COPY server/package.json server/package-lock.json ./
RUN npm ci --omit=dev && npm cache clean --force
# Compiled server + built SPA + committed seed bundles.
COPY --from=server-build /server/dist ./dist
COPY --from=spa-build /spa/dist ./public
COPY server/seed-data ./seed-data
USER node
EXPOSE 3000
# Liveness: HTTP server up (body also reports mongo connected/disconnected).
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
CMD wget -qO- http://127.0.0.1:3000/api/health || exit 1
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "dist/index.js"]

18
index.html Normal file
View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en" data-theme="dark" data-density="regular">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>TVApp2 — M3U &amp; EPG Manager</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
<style>
@keyframes slidein { from { transform: translateX(20px); opacity: 0; } to { transform: none; opacity: 1; } }
</style>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

1575
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

23
package.json Normal file
View File

@@ -0,0 +1,23 @@
{
"name": "tvapp2",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc -b && vite build",
"preview": "vite preview"
},
"dependencies": {
"hls.js": "^1.6.16",
"mitt": "^3.0.1",
"vue": "^3.5.13",
"vue-router": "^4.5.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.1",
"typescript": "^5.7.2",
"vite": "^6.0.5",
"vue-tsc": "^2.2.0"
}
}

View File

@@ -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]}"

View File

@@ -1 +0,0 @@
oneshot

View File

@@ -1 +0,0 @@
echo -e " Completed loading container"

View File

@@ -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.

View File

@@ -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

View File

@@ -1 +0,0 @@
oneshot

View File

@@ -1 +0,0 @@
/etc/s6-overlay/s6-rc.d/init-adduser/run

View File

@@ -1 +0,0 @@
oneshot

View File

@@ -1 +0,0 @@
# This file doesn't do anything, it's just the end of the downstream image init process

View File

@@ -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

View File

@@ -1 +0,0 @@
oneshot

View File

@@ -1 +0,0 @@
/etc/s6-overlay/s6-rc.d/init-crontab-config/run

View File

@@ -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

View File

@@ -1 +0,0 @@
oneshot

View File

@@ -1 +0,0 @@
/etc/s6-overlay/s6-rc.d/init-custom-files/run

View File

@@ -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

View File

@@ -1 +0,0 @@
oneshot

View File

@@ -1 +0,0 @@
/etc/s6-overlay/s6-rc.d/init-envfile/run

View File

@@ -1,7 +0,0 @@
#!/usr/bin/with-contenv bash
# shellcheck shell=bash
# make folders
mkdir -p \
/config/keys \
/run \

View File

@@ -1 +0,0 @@
oneshot

View File

@@ -1 +0,0 @@
/etc/s6-overlay/s6-rc.d/init-folders/run

View File

@@ -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

View File

@@ -1 +0,0 @@
oneshot

View File

@@ -1 +0,0 @@
/etc/s6-overlay/s6-rc.d/init-keygen/run

View File

@@ -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"

View File

@@ -1 +0,0 @@
oneshot

View File

@@ -1 +0,0 @@
/etc/s6-overlay/s6-rc.d/init-migrations/run

View File

@@ -1 +0,0 @@
oneshot

View File

@@ -1 +0,0 @@
# Empty placeholder for end of mod init process

View File

@@ -1 +0,0 @@
/etc/s6-overlay/s6-rc.d/init-mods-package-install/run

View File

@@ -1 +0,0 @@
oneshot

View File

@@ -1 +0,0 @@
# This file doesn't do anything, it's just the end of the mod init process

View File

@@ -1,6 +0,0 @@
#!/usr/bin/with-contenv bash
# shellcheck shell=bash
# permissions
aetherxown -R dockerx:dockerx \
/config/keys

View File

@@ -1 +0,0 @@
oneshot

View File

@@ -1 +0,0 @@
/etc/s6-overlay/s6-rc.d/init-permissions/run

View File

@@ -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
# #

View File

@@ -1 +0,0 @@
oneshot

View File

@@ -1 +0,0 @@
/etc/s6-overlay/s6-rc.d/init-samples/run

View File

@@ -1 +0,0 @@
oneshot

View File

@@ -1 +0,0 @@
# This file doesn't do anything, it just signals that services can start

View File

@@ -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

View File

@@ -1 +0,0 @@
oneshot

View File

@@ -1 +0,0 @@
/etc/s6-overlay/s6-rc.d/init-version-checks/run

View File

@@ -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

View File

@@ -1 +0,0 @@
longrun

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