Compare commits

...

7 Commits
1.5.0 ... 1.5.1

Author SHA1 Message Date
998d706bf7 ci: update release workflow 2025-05-13 01:02:01 -07:00
f4a394bd3b feat: add notice every 30 minutes for next data refresh
every 30 minutes, console will print date on when next data refresh is
2025-05-12 16:57:48 -07:00
898bbe4827 docs(readme): update env variable list 2025-05-12 13:33:52 -07:00
66b69d5629 build: bump version from 1.5.0 to 1.5.1 2025-05-12 12:55:35 -07:00
818729d6ed feat: add automatic cron support; resync every 3 days
refresh iptv data every 3rd day at midnight
2025-05-12 12:55:08 -07:00
33a2a90eb1 build(deps): bump user-agents from 1.1.529 to 1.1.537 2025-05-12 12:54:04 -07:00
2ff6c193c9 build(deps): add package node-cron 2025-05-12 12:51:33 -07:00
6 changed files with 146 additions and 17 deletions

View File

@@ -355,13 +355,28 @@ jobs:
if: ${{ startsWith( inputs.PRERELEASE, false ) }}
run: |
filename_zip="${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}.zip"
sha256="$(shasum --algorithm 256 ${filename_zip} | awk '{ print $1 }')"
shasum --algorithm 256 ${filename_zip} > SHA256SUMS.txt
# import gpg key (base64)
echo '${{ secrets.ADMINSERV_GPG_KEY_B64 }}' | base64 -d | gpg --import
# get sha1 and sha256 for .zip and .gz files
find . -maxdepth 1 \( -name '*.zip' -o -name '*.gz' \) -printf '%P\n' | xargs -r sha1sum | gpg --digest-algo 256 --clearsign > sha1sum.txt.asc
find . -maxdepth 1 \( -name '*.zip' -o -name '*.gz' \) -printf '%P\n' | xargs -r sha256sum | gpg --digest-algo sha256 --clearsign > sha256sum.txt.asc
# get sha1sum; assign to variable
sha1sum="$(shasum --algorithm 1 ${filename_zip} | awk '{ print $1 }')"
echo "SHA1SUM=${sha1sum}" >> $GITHUB_ENV
# get sha256sum; assign to variable
sha256sum="$(shasum --algorithm 256 ${filename_zip} | awk '{ print $1 }')"
echo "SHA256SUM=${sha256sum}" >> $GITHUB_ENV
# no longer needed, replaced by find . command
# shasum --algorithm 256 ${filename_zip} > SHA256SUMS.txt
echo "FILE_ZIP=${filename_zip}" >> $GITHUB_ENV
echo "SHA256SUM=${sha256}" >> $GITHUB_ENV
filename_compose_zip="${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}-docker-compose.zip"
sha256_compose="$(shasum --algorithm 256 ${filename_compose_zip} | awk '{ print $1 }')"
sha256sum_compose="$(shasum --algorithm 256 ${filename_compose_zip} | awk '{ print $1 }')"
echo "FILE_COMPOSE_ZIP=${filename_compose_zip}" >> $GITHUB_ENV
# #
@@ -372,8 +387,12 @@ jobs:
id: task_release_checksum_rc_set
if: ${{ startsWith( inputs.PRERELEASE, true ) }}
run: |
# get filename
filename_zip="${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}-rc.${{ inputs.VERSION_RC }}.zip"
# get sha256 checksum
sha256="$(shasum --algorithm 256 ${filename_zip} | awk '{ print $1 }')"
# write sha256sum to file
shasum --algorithm 256 ${filename_zip} > SHA256SUMS.txt
echo "FILE_ZIP=${filename_zip}" >> $GITHUB_ENV
echo "SHA256SUM=${sha256}" >> $GITHUB_ENV
@@ -412,7 +431,7 @@ jobs:
if: ${{ startsWith( inputs.PRERELEASE, false ) }}
run: |
echo Zipping STABLE Package .zip ${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}.zip
zip -jr ${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}.zip SHA256SUMS.txt
zip -jr ${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}.zip sha1sum.txt.asc sha256sum.txt.asc
ls
# #
@@ -424,7 +443,7 @@ jobs:
if: ${{ startsWith( inputs.PRERELEASE, true ) }}
run: |
echo Zipping PRE-RELEASE Package .zip ${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}-rc.${{ inputs.VERSION_RC }}.zip
zip -jr ${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}-rc.${{ inputs.VERSION_RC }}.zip SHA256SUMS.txt
zip -jr ${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}-rc.${{ inputs.VERSION_RC }}.zip sha1sum.txt.asc sha256sum.txt.asc
ls
# #
@@ -578,7 +597,8 @@ jobs:
files: |
${{ env.PROJECT_NAME }}-v${{ env.PACKAGE_VERSION }}-docker-compose.zip
${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}.zip
SHA256SUMS.txt
sha1sum.txt.asc
sha256sum.txt.asc
prerelease: false
body: |
${{ steps.task_release_changelog_categorized.outputs.changelog }}
@@ -611,7 +631,8 @@ jobs:
files: |
${{ env.PROJECT_NAME }}-v${{ env.PACKAGE_VERSION }}-rc.${{ inputs.VERSION_RC }}-docker-compose.zip
${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}-rc.${{ inputs.VERSION_RC }}.zip
SHA256SUMS.txt
sha1sum.txt.asc
sha256sum.txt.asc
prerelease: false
body: |
> [!WARNING]

View File

@@ -103,6 +103,7 @@ 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"

View File

@@ -168,6 +168,8 @@ For the [environment variables](#environment-variables), you may specify these i
| `FILE_EPG` | `xmltv.xml` | Filename for XML guide data file |
| `FILE_GZP` | `xmltv.xml.gz` | Filename for XML compressed as gzip .gz |
| `STREAM_QUALITY` | `hd` | Stream quality<br />Can be either `hd` or `sd` |
| `TASK_CRON_SYNC` | `0 0 */3 * *` | Defines how often to refresh the M3U and XML IPTV data |
| `HEALTH_TIMER` | `600000` | How often (in milliseconds) to run a health check |
| `DIR_BUILD` | `/usr/src/app` | Path inside container where TVApp2 will be built. <br /><br /> <sup>⚠️ This should not be used unless you know what you're doing</sup> |
| `DIR_RUN` | `/usr/bin/app` | Path inside container where TVApp2 will be placed after it is built <br /><br /> <sup>⚠️ This should not be used unless you know what you're doing</sup> |
| `LOG_LEVEL` | `4` | Level of logging to display in console<br/>`7` Trace <sup><sub>& below</sub></sup><br />`6` Verbose <sup><sub>& below</sub></sup><br />`5` Debug <sup><sub>& below</sub></sup><br />`4` Info <sup><sub>& below</sub></sup><br />`3` Notice <sup><sub>& below</sub></sup><br />`2` Warn <sup><sub>& below</sub></sup><br />`1` Error <sup><sub>only</sub></sup> |
@@ -1219,6 +1221,8 @@ This docker container contains the following env variables:
| `FILE_EPG` | `xmltv.xml` | Filename for XML guide data file |
| `FILE_GZP` | `xmltv.xml.gz` | Filename for XML compressed as gzip .gz |
| `STREAM_QUALITY` | `hd` | Stream quality<br />Can be either `hd` or `sd` |
| `TASK_CRON_SYNC` | `0 0 */3 * *` | Defines how often to refresh the M3U and XML IPTV data |
| `HEALTH_TIMER` | `600000` | How often (in milliseconds) to run a health check |
| `DIR_BUILD` | `/usr/src/app` | Path inside container where TVApp2 will be built. <br /><br /> <sup>⚠️ This should not be used unless you know what you're doing</sup> |
| `DIR_RUN` | `/usr/bin/app` | Path inside container where TVApp2 will be placed after it is built <br /><br /> <sup>⚠️ This should not be used unless you know what you're doing</sup> |
| `LOG_LEVEL` | `4` | Level of logging to display in console<br/>`7` Trace <sup><sub>& below</sub></sup><br />`6` Verbose <sup><sub>& below</sub></sup><br />`5` Debug <sup><sub>& below</sub></sup><br />`4` Info <sup><sub>& below</sub></sup><br />`3` Notice <sup><sub>& below</sub></sup><br />`2` Warn <sup><sub>& below</sub></sup><br />`1` Error <sup><sub>only</sub></sup> |

View File

@@ -12,6 +12,8 @@ import zlib from 'zlib';
import chalk from 'chalk';
import ejs from 'ejs';
import moment from 'moment';
import cron, { schedule } from 'node-cron';
import * as crons from 'cron';
/*
Old CJS variables converted to ESM
@@ -80,7 +82,8 @@ const envWebFolder = process.env.WEB_FOLDER || 'www';
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 LOG_LEVEL = process.env.LOG_LEVEL || 10;
const envTaskCronSync = process.env.TASK_CRON_SYNC || '0 0 */3 * *';
const LOG_LEVEL = process.env.LOG_LEVEL || 4;
/*
Define > Externals
@@ -1762,6 +1765,25 @@ 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( `<msg>` ), chalk.gray( `Specified cron schedule is not valid` ),
chalk.redBright( `<schedule>` ), 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( `<msg>` ), chalk.gray( `TVApp2 will refresh channel and guide data at` ),
chalk.blueBright( `<schedule>` ), chalk.whiteBright.gray( ` ${ envTaskCronSync } ` ),
chalk.blueBright( `<nextrun>` ), chalk.whiteBright.gray( ` ${ cronNextRun } ` ),
chalk.blueBright( `<nextrunIso>` ), chalk.whiteBright.gray( ` ${ cronNextRunDt } ` ) );
}
Log.info( `core`, chalk.yellow( `[initiate]` ), chalk.white( `` ),
chalk.blueBright( `<msg>` ), chalk.gray( `Starting TVApp2 container. Assigning bound IP to host network adapter` ),
chalk.blueBright( `<hostIp>` ), chalk.gray( `${ envWebIP }` ),
@@ -2305,3 +2327,43 @@ const server = http.createServer( ( request, response ) =>
});
})();
/*
Crons > Next Sync
*/
cron.schedule( envTaskCronSync, async() =>
{
Log.ok( `task`, chalk.yellow( `[resync]` ), chalk.white( `` ),
chalk.blueBright( `<msg>` ), chalk.gray( `Ran cron / task to re-sync IPTV data` ),
chalk.blueBright( `<schedule>` ), chalk.whiteBright.bgBlack( ` ${ envTaskCronSync } ` ) );
await initialize();
});
/*
Crons > Announce Next Sync
should show every 30 minutes
*/
cron.schedule( '*/30 * * * *', async() =>
{
const validation = crons.validateCronExpression( envTaskCronSync );
if ( !validation.valid )
{
Log.error( `core`, chalk.yellow( `[schedule]` ), chalk.white( `` ),
chalk.redBright( `<msg>` ), chalk.gray( `Specified cron schedule is not valid. Re-write the cron so that it is properly formatted` ),
chalk.redBright( `<env>` ), chalk.gray( `TASK_CRON_SYNC` ),
chalk.redBright( `<schedule>` ), 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( `<msg>` ), chalk.gray( `Next IPTV data refresh at` ),
chalk.blueBright( `<schedule>` ), chalk.whiteBright.gray( ` ${ envTaskCronSync } ` ),
chalk.blueBright( `<nextrun>` ), chalk.whiteBright.gray( ` ${ cronNextRun } ` ),
chalk.blueBright( `<nextrunIso>` ), chalk.whiteBright.gray( ` ${ cronNextRunDt } ` ) );
}
});

View File

@@ -1,20 +1,22 @@
{
"name": "tvapp2",
"version": "1.5.0",
"version": "1.5.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "tvapp2",
"version": "1.5.0",
"version": "1.5.1",
"license": "MIT",
"dependencies": {
"chalk": "^5.3.0",
"cron": "^4.3.0",
"ejs": "^3.1.10",
"express": "5.1.0",
"moment": "2.30.1",
"node-cron": "^4.0.3",
"playwright": "^1.52.0",
"user-agents": "^1.1.529"
"user-agents": "^1.1.537"
},
"devDependencies": {
"@stylistic/eslint-plugin-js": "^4.2.0",
@@ -297,6 +299,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/luxon": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.6.2.tgz",
"integrity": "sha512-R/BdP7OxEMc44l2Ex5lSXHoIXTB2JLNa3y2QISIbr58U/YcsffyQrYW//hZSdrfxrjRZj3GcUoxMPGdO8gSYuw==",
"license": "MIT"
},
"node_modules/@types/uuid": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
@@ -842,6 +850,19 @@
"node": ">=6.6.0"
}
},
"node_modules/cron": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/cron/-/cron-4.3.0.tgz",
"integrity": "sha512-ciiYNLfSlF9MrDqnbMdRWFiA6oizSF7kA1osPP9lRzNu0Uu+AWog1UKy7SkckiDY2irrNjeO6qLyKnXC8oxmrw==",
"license": "MIT",
"dependencies": {
"@types/luxon": "~3.6.0",
"luxon": "~3.6.0"
},
"engines": {
"node": ">=18.x"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -2929,6 +2950,15 @@
"dev": true,
"license": "MIT"
},
"node_modules/luxon": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.6.1.tgz",
"integrity": "sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==",
"license": "MIT",
"engines": {
"node": ">=12"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -3050,6 +3080,15 @@
"node": ">= 0.6"
}
},
"node_modules/node-cron": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/node-cron/-/node-cron-4.0.3.tgz",
"integrity": "sha512-YPEUlbYGgpfN44OtCm4KkK4TXjeIB4arERbDWP7kJgM+QHFK9AdM6/3bp4M3oB1QBrL5INyTRLAPei87j49foA==",
"license": "ISC",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
@@ -4326,9 +4365,9 @@
}
},
"node_modules/user-agents": {
"version": "1.1.529",
"resolved": "https://registry.npmjs.org/user-agents/-/user-agents-1.1.529.tgz",
"integrity": "sha512-8/ha9jnpBjUhC7+kpdAimo+mQVUMFhTJEkGVRSUsxK6YWuVZ6il+6ErVMjWpfX7q1Ft0m/4+XpPJhGuqZesFTQ==",
"version": "1.1.537",
"resolved": "https://registry.npmjs.org/user-agents/-/user-agents-1.1.537.tgz",
"integrity": "sha512-D3t8f1G3Kd3Ak0t9Md9oNsK59Ms1hs9FhdwkMafkaNpwpMcq/FHuHHT/ky64Dy8aKJkMFcEBeVN0zQmtoTKCKg==",
"license": "BSD-2-Clause",
"dependencies": {
"lodash.clonedeep": "^4.5.0"

View File

@@ -1,6 +1,6 @@
{
"name": "tvapp2",
"version": "1.5.0",
"version": "1.5.1",
"description": "This package allows you to generate M3U playlists and EPG guides from various online IPTV services.",
"author": "BinaryNinja",
"license": "MIT",
@@ -72,8 +72,10 @@
"iptv"
],
"dependencies": {
"cron": "^4.3.0",
"node-cron": "^4.0.3",
"playwright": "^1.52.0",
"user-agents": "^1.1.529",
"user-agents": "^1.1.537",
"chalk": "^5.3.0",
"ejs": "^3.1.10",
"moment": "2.30.1",