From 760cc981299940ce815c8ae52a6d5f3816607d11 Mon Sep 17 00:00:00 2001
From: Your Name
Date: Sat, 19 Oct 2024 10:31:44 -0600
Subject: [PATCH 01/10] Moving to hono
---
README.md | 2 +-
index.ts => index.tsx | 135 ++++---
package-lock.json | 853 ++++--------------------------------------
package.json | 8 +-
tsconfig.json | 4 +-
5 files changed, 142 insertions(+), 860 deletions(-)
rename index.ts => index.tsx (74%)
diff --git a/README.md b/README.md
index 6c377ba..829605a 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
-Current version: **3.3.4**
+Current version: **3.3.5**
# About
This takes ESPN/ESPN+, FOX Sports, Paramount+, MSG+, NFL+, B1G+, NESN, Mountain West, FloSports, CBS Sports, or MLB.tv programming and transforms it into a "live TV" experience with virtual linear channels. It will discover what is on, and generate a schedule of channels that will give you M3U and XMLTV files that you can import into something like [Jellyfin](https://jellyfin.org) or [Channels](https://getchannels.com).
diff --git a/index.ts b/index.tsx
similarity index 74%
rename from index.ts
rename to index.tsx
index da6332b..d8f9990 100644
--- a/index.ts
+++ b/index.tsx
@@ -1,4 +1,6 @@
-import express from 'express';
+import {Context, Hono} from 'hono';
+import {serve} from '@hono/node-server';
+import {BlankEnv, BlankInput} from 'hono/types';
import moment from 'moment';
import {generateM3u} from './services/generate-m3u';
@@ -24,16 +26,26 @@ import {useLinear} from './services/channels';
import {version} from './package.json';
-const notFound = (_req, res) => {
- res.writeHead(404, {
+const notFound = (c: Context) => {
+ return c.text('404 not found', 404, {
'X-Tuner-Error': 'EPlusTV: Error getting content',
});
- res.write('404 not found');
- res.end();
};
const shutDown = () => process.exit(0);
+const getUri = (c: Context): string => {
+ if (process.env.BASE_URL) {
+ return process.env.BASE_URL;
+ }
+
+ const protocol = c.req.header('x-forwarded-proto') || 'http';
+ const host = c.req.header('host') || '';
+
+
+ return `${protocol}://${host}`;
+};
+
const schedule = async () => {
console.log('=== Getting events ===');
await espnHandler.getSchedule();
@@ -55,78 +67,66 @@ const schedule = async () => {
console.log('=== Done building the schedule ===');
};
-const app = express();
+const app = new Hono();
-app.get('/channels.m3u', (req, res) => {
- const uri = process.env.BASE_URL || `${req.protocol}://${req.headers.host}`;
- const m3uFile = generateM3u(uri);
+app.get('/channels.m3u', c => {
+ const m3uFile = generateM3u(getUri(c));
if (!m3uFile) {
- notFound(req, res);
- return;
+ return notFound(c);
}
- res.writeHead(200, {
+ return c.body(m3uFile, 200, {
'Content-Type': 'application/x-mpegurl',
});
- res.end(m3uFile, 'utf-8');
});
-app.get('/linear-channels.m3u', (req, res) => {
+app.get('/linear-channels.m3u', c => {
if (!useLinear) {
- notFound(req, res);
- return;
+ return notFound(c);
}
- const uri = process.env.BASE_URL || `${req.protocol}://${req.headers.host}`;
- const m3uFile = generateM3u(uri, true);
+ const m3uFile = generateM3u(getUri(c), true);
if (!m3uFile) {
- notFound(req, res);
- return;
+ return notFound(c);
}
- res.writeHead(200, {
+ return c.body(m3uFile, 200, {
'Content-Type': 'application/x-mpegurl',
});
- res.end(m3uFile, 'utf-8');
});
-app.get('/xmltv.xml', async (req, res) => {
+app.get('/xmltv.xml', async c => {
const xmlFile = await generateXml();
if (!xmlFile) {
- notFound(req, res);
- return;
+ return notFound(c);
}
- res.writeHead(200, {
+ return c.body(xmlFile, 200, {
'Content-Type': 'application/xml',
});
- res.end(xmlFile, 'utf-8');
});
-app.get('/linear-xmltv.xml', async (req, res) => {
+app.get('/linear-xmltv.xml', async c => {
if (!useLinear) {
- notFound(req, res);
- return;
+ return notFound(c);
}
const xmlFile = await generateXml(true);
if (!xmlFile) {
- notFound(req, res);
- return;
+ return notFound(c);
}
- res.writeHead(200, {
+ return c.body(xmlFile, 200, {
'Content-Type': 'application/xml',
});
- res.end(xmlFile, 'utf-8');
});
-app.get('/channels/:id.m3u8', async (req, res) => {
- const {id} = req.params;
+app.get('/channels/:id{.+\\.m3u8$}', async c => {
+ const id = c.req.param('id').split('.m3u8')[0];
let contents: string | undefined;
@@ -135,7 +135,7 @@ app.get('/channels/:id.m3u8', async (req, res) => {
appStatus.channels[id] = {};
}
- const uri = process.env.BASE_URL || `${req.protocol}://${req.headers.host}`;
+ const uri = getUri(c);
if (!appStatus.channels[id].player?.playlist) {
try {
@@ -154,27 +154,25 @@ app.get('/channels/:id.m3u8', async (req, res) => {
removeChannelStatus(id);
- notFound(req, res);
- return;
+ return notFound(c);
}
appStatus.channels[id].heartbeat = new Date();
- res.writeHead(200, {
+ return c.body(contents, 200, {
'Cache-Control': 'no-cache',
'Content-Type': 'application/vnd.apple.mpegurl',
});
- res.end(contents, 'utf-8');
});
-app.get('/chunklist/:id/:chunklistid.m3u8', async (req, res) => {
- const {id, chunklistid} = req.params;
+app.get('/chunklist/:id/:chunklistid{.+\\.m3u8$}', async c => {
+ const id = c.req.param('id');
+ const chunklistid = c.req.param('chunklistid').split('.m3u8')[0];
- let contents: string | null;
+ let contents: string | undefined;
if (!appStatus.channels[id]?.player?.playlist) {
- notFound(req, res);
- return;
+ return notFound(c);
}
try {
@@ -183,71 +181,65 @@ app.get('/chunklist/:id/:chunklistid.m3u8', async (req, res) => {
if (!contents) {
console.log(`Could not get chunklist for channel #${id}.`);
- notFound(req, res);
- return;
+ return notFound(c);
}
appStatus.channels[id].heartbeat = new Date();
- res.writeHead(200, {
+ return c.body(contents, 200, {
'Cache-Control': 'no-cache',
'Content-Type': 'application/vnd.apple.mpegurl',
});
- res.end(contents, 'utf-8');
});
-app.get('/channels/:id/:part.key', async (req, res) => {
- const {id, part} = req.params;
+app.get('/channels/:id/:part{.+\\.key$}', async c => {
+ const id = c.req.param('id');
+ const part = c.req.param('part').split('.key')[0];
let contents: ArrayBuffer | undefined;
try {
contents = await appStatus.channels[id].player?.getSegmentOrKey(part);
} catch (e) {
- notFound(req, res);
- return;
+ return notFound(c);
}
if (!contents) {
- notFound(req, res);
- return;
+ return notFound(c);
}
appStatus.channels[id].heartbeat = new Date();
- res.writeHead(200, {
+ return c.body(contents, 200, {
'Cache-Control': 'no-cache',
'Content-Type': 'application/octet-stream',
});
- res.end(contents, 'utf-8');
});
-app.get('/channels/:id/:part.ts', async (req, res) => {
- const {id, part} = req.params;
+app.get('/channels/:id/:part{.+\\.ts$}', async c => {
+ const id = c.req.param('id');
+ const part = c.req.param('part').split('.ts')[0];
let contents: ArrayBuffer | undefined;
try {
contents = await appStatus.channels[id].player?.getSegmentOrKey(part);
} catch (e) {
- notFound(req, res);
- return;
+ return notFound(c);
}
if (!contents) {
- notFound(req, res);
- return;
+ return notFound(c);
}
- res.writeHead(200, {
+ return c.body(contents, 200, {
'Cache-Control': 'no-cache',
'Content-Type': 'video/MP2T',
});
- res.end(contents, 'utf-8');
});
// 404 Handler
-app.use(notFound);
+app.notFound(notFound);
process.on('SIGTERM', shutDown);
process.on('SIGINT', shutDown);
@@ -288,8 +280,13 @@ process.on('SIGINT', shutDown);
await schedule();
- console.log('=== Starting Server ===');
- app.listen(SERVER_PORT, () => console.log(`Server started on port ${SERVER_PORT}`));
+ serve(
+ {
+ fetch: app.fetch,
+ port: SERVER_PORT,
+ },
+ () => console.log(`Server started on port ${SERVER_PORT}`),
+ );
})();
// Check for events every 4 hours and set the schedule
diff --git a/package-lock.json b/package-lock.json
index e9e2593..03175b6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,22 +1,23 @@
{
"name": "eplustv",
- "version": "3.3.4",
+ "version": "3.3.5",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "eplustv",
- "version": "3.3.4",
+ "version": "3.3.5",
"license": "MIT",
"dependencies": {
+ "@hono/node-server": "^1.13.1",
"axios": "^1.2.2",
"axios-curlirize": "^1.3.7",
"cheerio": "^1.0.0",
"crypto-js": "^4.2.0",
- "express": "^4.17.1",
"fast-xml-parser": "^4.5.0",
"fs-extra": "^10.0.0",
"hls-parser": "^0.10.6",
+ "hono": "^4.6.3",
"jwt-decode": "^3.1.2",
"lodash": "^4.17.21",
"moment": "^2.29.1",
@@ -39,6 +40,7 @@
"lint-staged": "^11.1.1",
"prettier": "2.3.2",
"ts-node": "^10.2.1",
+ "typed-htmx": "^0.3.1",
"typescript": "^4.4.3"
}
},
@@ -218,6 +220,17 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
+ "node_modules/@hono/node-server": {
+ "version": "1.13.1",
+ "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.13.1.tgz",
+ "integrity": "sha512-TSxE6cT5RHnawbjnveexVN7H2Dpn1YaLxQrCOLCUwD+hFbqbFsnJBgdWcYtASqtWVjA+Qgi8uqFug39GsHjo5A==",
+ "engines": {
+ "node": ">=18.14.1"
+ },
+ "peerDependencies": {
+ "hono": "^4"
+ }
+ },
"node_modules/@humanwhocodes/config-array": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz",
@@ -679,18 +692,6 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
- "node_modules/accepts": {
- "version": "1.3.7",
- "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
- "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
- "dependencies": {
- "mime-types": "~2.1.24",
- "negotiator": "0.6.2"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/acorn": {
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz",
@@ -825,11 +826,6 @@
"sprintf-js": "~1.0.2"
}
},
- "node_modules/array-flatten": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
- "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
- },
"node_modules/array-union": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
@@ -879,26 +875,6 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
- "node_modules/body-parser": {
- "version": "1.19.0",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
- "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
- "dependencies": {
- "bytes": "3.1.0",
- "content-type": "~1.0.4",
- "debug": "2.6.9",
- "depd": "~1.1.2",
- "http-errors": "1.7.2",
- "iconv-lite": "0.4.24",
- "on-finished": "~2.3.0",
- "qs": "6.7.0",
- "raw-body": "2.4.0",
- "type-is": "~1.6.17"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
"node_modules/boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
@@ -940,14 +916,6 @@
"node": ">=6.14.2"
}
},
- "node_modules/bytes": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
- "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==",
- "engines": {
- "node": ">= 0.8"
- }
- },
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -1100,38 +1068,6 @@
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
- "node_modules/content-disposition": {
- "version": "0.5.3",
- "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
- "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
- "dependencies": {
- "safe-buffer": "5.1.2"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/content-type": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
- "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/cookie": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
- "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/cookie-signature": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
- "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
- },
"node_modules/cosmiconfig": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
@@ -1199,14 +1135,6 @@
"url": "https://github.com/sponsors/fb55"
}
},
- "node_modules/debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dependencies": {
- "ms": "2.0.0"
- }
- },
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -1221,19 +1149,6 @@
"node": ">=0.4.0"
}
},
- "node_modules/depd": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
- "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/destroy": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
- "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
- },
"node_modules/diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
@@ -1318,25 +1233,12 @@
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
- "node_modules/ee-first": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
- "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
- },
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
- "node_modules/encodeurl": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
- "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
- "engines": {
- "node": ">= 0.8"
- }
- },
"node_modules/encoding-sniffer": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz",
@@ -1392,11 +1294,6 @@
"is-arrayish": "^0.2.1"
}
},
- "node_modules/escape-html": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
- "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
- },
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -1702,14 +1599,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/etag": {
- "version": "1.8.1",
- "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
- "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/execa": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
@@ -1733,46 +1622,6 @@
"url": "https://github.com/sindresorhus/execa?sponsor=1"
}
},
- "node_modules/express": {
- "version": "4.17.1",
- "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
- "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
- "dependencies": {
- "accepts": "~1.3.7",
- "array-flatten": "1.1.1",
- "body-parser": "1.19.0",
- "content-disposition": "0.5.3",
- "content-type": "~1.0.4",
- "cookie": "0.4.0",
- "cookie-signature": "1.0.6",
- "debug": "2.6.9",
- "depd": "~1.1.2",
- "encodeurl": "~1.0.2",
- "escape-html": "~1.0.3",
- "etag": "~1.8.1",
- "finalhandler": "~1.1.2",
- "fresh": "0.5.2",
- "merge-descriptors": "1.0.1",
- "methods": "~1.1.2",
- "on-finished": "~2.3.0",
- "parseurl": "~1.3.3",
- "path-to-regexp": "0.1.7",
- "proxy-addr": "~2.0.5",
- "qs": "6.7.0",
- "range-parser": "~1.2.1",
- "safe-buffer": "5.1.2",
- "send": "0.17.1",
- "serve-static": "1.14.1",
- "setprototypeof": "1.1.1",
- "statuses": "~1.5.0",
- "type-is": "~1.6.18",
- "utils-merge": "1.0.1",
- "vary": "~1.1.2"
- },
- "engines": {
- "node": ">= 0.10.0"
- }
- },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -1861,23 +1710,6 @@
"node": ">=8"
}
},
- "node_modules/finalhandler": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
- "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
- "dependencies": {
- "debug": "2.6.9",
- "encodeurl": "~1.0.2",
- "escape-html": "~1.0.3",
- "on-finished": "~2.3.0",
- "parseurl": "~1.3.3",
- "statuses": "~1.5.0",
- "unpipe": "~1.0.0"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
"node_modules/flat-cache": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
@@ -1929,22 +1761,6 @@
"node": ">= 6"
}
},
- "node_modules/forwarded": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
- "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/fresh": {
- "version": "0.5.2",
- "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
- "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=",
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/fs-extra": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz",
@@ -2074,6 +1890,14 @@
"resolved": "https://registry.npmjs.org/hls-parser/-/hls-parser-0.10.6.tgz",
"integrity": "sha512-t9bKB1fDDVajh5CY/qeZanNMnCm4hAWMroDlflnuboOskYR26z29bEyOYwQRP1MxP2Y83UC2J93DQCC1AMhBCA=="
},
+ "node_modules/hono": {
+ "version": "4.6.3",
+ "resolved": "https://registry.npmjs.org/hono/-/hono-4.6.3.tgz",
+ "integrity": "sha512-0LeEuBNFeSHGqZ9sNVVgZjB1V5fmhkBSB0hZrpqStSMLOWgfLy0dHOvrjbJh0H2khsjet6rbHfWTHY0kpYThKQ==",
+ "engines": {
+ "node": ">=16.9.0"
+ }
+ },
"node_modules/htmlparser2": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz",
@@ -2092,21 +1916,6 @@
"entities": "^4.5.0"
}
},
- "node_modules/http-errors": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
- "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
- "dependencies": {
- "depd": "~1.1.2",
- "inherits": "2.0.3",
- "setprototypeof": "1.1.1",
- "statuses": ">= 1.5.0 < 2",
- "toidentifier": "1.0.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/human-signals": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
@@ -2131,17 +1940,6 @@
"url": "https://github.com/sponsors/typicode"
}
},
- "node_modules/iconv-lite": {
- "version": "0.4.24",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
- "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
- "dependencies": {
- "safer-buffer": ">= 2.1.2 < 3"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/ignore": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz",
@@ -2203,15 +2001,8 @@
"node_modules/inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
- "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
- },
- "node_modules/ipaddr.js": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
- "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
- "engines": {
- "node": ">= 0.10"
- }
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+ "dev": true
},
"node_modules/is-arrayish": {
"version": "0.2.1",
@@ -2565,19 +2356,6 @@
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"dev": true
},
- "node_modules/media-typer": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
- "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/merge-descriptors": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
- "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
- },
"node_modules/merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
@@ -2593,14 +2371,6 @@
"node": ">= 8"
}
},
- "node_modules/methods": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
- "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/micromatch": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
@@ -2614,17 +2384,6 @@
"node": ">=8.6"
}
},
- "node_modules/mime": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
- "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
- "bin": {
- "mime": "cli.js"
- },
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/mime-db": {
"version": "1.49.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz",
@@ -2684,11 +2443,6 @@
"node": "*"
}
},
- "node_modules/ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
- },
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -2703,14 +2457,6 @@
"@seald-io/nedb": "^2.0.4"
}
},
- "node_modules/negotiator": {
- "version": "0.6.2",
- "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
- "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==",
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/node-gyp-build": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz",
@@ -2755,17 +2501,6 @@
"url": "https://github.com/fb55/nth-check?sponsor=1"
}
},
- "node_modules/on-finished": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
- "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
- "dependencies": {
- "ee-first": "1.1.1"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -2886,14 +2621,6 @@
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
- "node_modules/parseurl": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
- "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
- "engines": {
- "node": ">= 0.8"
- }
- },
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@@ -2912,11 +2639,6 @@
"node": ">=8"
}
},
- "node_modules/path-to-regexp": {
- "version": "0.1.7",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
- "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
- },
"node_modules/path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
@@ -2977,18 +2699,6 @@
"node": ">=0.4.0"
}
},
- "node_modules/proxy-addr": {
- "version": "2.0.7",
- "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
- "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
- "dependencies": {
- "forwarded": "0.2.0",
- "ipaddr.js": "1.9.1"
- },
- "engines": {
- "node": ">= 0.10"
- }
- },
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
@@ -3003,14 +2713,6 @@
"node": ">=6"
}
},
- "node_modules/qs": {
- "version": "6.7.0",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
- "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==",
- "engines": {
- "node": ">=0.6"
- }
- },
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -3031,28 +2733,6 @@
}
]
},
- "node_modules/range-parser": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
- "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/raw-body": {
- "version": "2.4.0",
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
- "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
- "dependencies": {
- "bytes": "3.1.0",
- "http-errors": "1.7.2",
- "iconv-lite": "0.4.24",
- "unpipe": "1.0.0"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
"node_modules/regexpp": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
@@ -3168,11 +2848,6 @@
"tslib": "^2.1.0"
}
},
- "node_modules/safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
- },
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -3199,53 +2874,6 @@
"integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==",
"dev": true
},
- "node_modules/send": {
- "version": "0.17.1",
- "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
- "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
- "dependencies": {
- "debug": "2.6.9",
- "depd": "~1.1.2",
- "destroy": "~1.0.4",
- "encodeurl": "~1.0.2",
- "escape-html": "~1.0.3",
- "etag": "~1.8.1",
- "fresh": "0.5.2",
- "http-errors": "~1.7.2",
- "mime": "1.6.0",
- "ms": "2.1.1",
- "on-finished": "~2.3.0",
- "range-parser": "~1.2.1",
- "statuses": "~1.5.0"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/send/node_modules/ms": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
- "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
- },
- "node_modules/serve-static": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
- "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
- "dependencies": {
- "encodeurl": "~1.0.2",
- "escape-html": "~1.0.3",
- "parseurl": "~1.3.3",
- "send": "0.17.1"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/setprototypeof": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
- "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
- },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -3307,14 +2935,6 @@
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
"dev": true
},
- "node_modules/statuses": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
- "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/string-argv": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz",
@@ -3481,14 +3101,6 @@
"node": ">=8.0"
}
},
- "node_modules/toidentifier": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
- "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==",
- "engines": {
- "node": ">=0.6"
- }
- },
"node_modules/ts-node": {
"version": "10.2.1",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.2.1.tgz",
@@ -3584,16 +3196,25 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/type-is": {
- "version": "1.6.18",
- "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
- "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "node_modules/typed-html": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/typed-html/-/typed-html-3.0.1.tgz",
+ "integrity": "sha512-JKCM9zTfPDuPqQqdGZBWSEiItShliKkBFg5c6yOR8zth43v763XkAzTWaOlVqc0Y6p9ee8AaAbipGfUnCsYZUA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/typed-htmx": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/typed-htmx/-/typed-htmx-0.3.1.tgz",
+ "integrity": "sha512-6WSPsukTIOEMsVbx5wzgVSvldLmgBUVcFIm2vJlBpRPtcbDOGC5y1IYrCWNX1yUlNsrv1Ngcw4gGM8jsPyNV7w==",
+ "dev": true,
"dependencies": {
- "media-typer": "0.3.0",
- "mime-types": "~2.1.24"
+ "typed-html": "^3.0.1"
},
"engines": {
- "node": ">= 0.6"
+ "node": ">=12"
}
},
"node_modules/typescript": {
@@ -3625,14 +3246,6 @@
"node": ">= 10.0.0"
}
},
- "node_modules/unpipe": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
- "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=",
- "engines": {
- "node": ">= 0.8"
- }
- },
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
@@ -3656,28 +3269,12 @@
"node": ">=6.14.2"
}
},
- "node_modules/utils-merge": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
- "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=",
- "engines": {
- "node": ">= 0.4.0"
- }
- },
"node_modules/v8-compile-cache": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
"integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
"dev": true
},
- "node_modules/vary": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
- "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=",
- "engines": {
- "node": ">= 0.8"
- }
- },
"node_modules/whatwg-encoding": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
@@ -3945,6 +3542,12 @@
}
}
},
+ "@hono/node-server": {
+ "version": "1.13.1",
+ "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.13.1.tgz",
+ "integrity": "sha512-TSxE6cT5RHnawbjnveexVN7H2Dpn1YaLxQrCOLCUwD+hFbqbFsnJBgdWcYtASqtWVjA+Qgi8uqFug39GsHjo5A==",
+ "requires": {}
+ },
"@humanwhocodes/config-array": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz",
@@ -4289,15 +3892,6 @@
"eslint-visitor-keys": "^2.0.0"
}
},
- "accepts": {
- "version": "1.3.7",
- "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
- "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
- "requires": {
- "mime-types": "~2.1.24",
- "negotiator": "0.6.2"
- }
- },
"acorn": {
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz",
@@ -4392,11 +3986,6 @@
"sprintf-js": "~1.0.2"
}
},
- "array-flatten": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
- "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
- },
"array-union": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
@@ -4440,23 +4029,6 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
- "body-parser": {
- "version": "1.19.0",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
- "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
- "requires": {
- "bytes": "3.1.0",
- "content-type": "~1.0.4",
- "debug": "2.6.9",
- "depd": "~1.1.2",
- "http-errors": "1.7.2",
- "iconv-lite": "0.4.24",
- "on-finished": "~2.3.0",
- "qs": "6.7.0",
- "raw-body": "2.4.0",
- "type-is": "~1.6.17"
- }
- },
"boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
@@ -4491,11 +4063,6 @@
"node-gyp-build": "^4.3.0"
}
},
- "bytes": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
- "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
- },
"callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -4609,29 +4176,6 @@
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
- "content-disposition": {
- "version": "0.5.3",
- "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
- "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
- "requires": {
- "safe-buffer": "5.1.2"
- }
- },
- "content-type": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
- "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
- },
- "cookie": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
- "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
- },
- "cookie-signature": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
- "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
- },
"cosmiconfig": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
@@ -4684,14 +4228,6 @@
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw=="
},
- "debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "requires": {
- "ms": "2.0.0"
- }
- },
"deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -4703,16 +4239,6 @@
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
},
- "depd": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
- "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
- },
- "destroy": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
- "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
- },
"diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
@@ -4770,22 +4296,12 @@
"domhandler": "^5.0.3"
}
},
- "ee-first": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
- "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
- },
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
- "encodeurl": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
- "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
- },
"encoding-sniffer": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz",
@@ -4828,11 +4344,6 @@
"is-arrayish": "^0.2.1"
}
},
- "escape-html": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
- "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
- },
"escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -5057,11 +4568,6 @@
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"dev": true
},
- "etag": {
- "version": "1.8.1",
- "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
- "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
- },
"execa": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
@@ -5079,43 +4585,6 @@
"strip-final-newline": "^2.0.0"
}
},
- "express": {
- "version": "4.17.1",
- "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
- "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
- "requires": {
- "accepts": "~1.3.7",
- "array-flatten": "1.1.1",
- "body-parser": "1.19.0",
- "content-disposition": "0.5.3",
- "content-type": "~1.0.4",
- "cookie": "0.4.0",
- "cookie-signature": "1.0.6",
- "debug": "2.6.9",
- "depd": "~1.1.2",
- "encodeurl": "~1.0.2",
- "escape-html": "~1.0.3",
- "etag": "~1.8.1",
- "finalhandler": "~1.1.2",
- "fresh": "0.5.2",
- "merge-descriptors": "1.0.1",
- "methods": "~1.1.2",
- "on-finished": "~2.3.0",
- "parseurl": "~1.3.3",
- "path-to-regexp": "0.1.7",
- "proxy-addr": "~2.0.5",
- "qs": "6.7.0",
- "range-parser": "~1.2.1",
- "safe-buffer": "5.1.2",
- "send": "0.17.1",
- "serve-static": "1.14.1",
- "setprototypeof": "1.1.1",
- "statuses": "~1.5.0",
- "type-is": "~1.6.18",
- "utils-merge": "1.0.1",
- "vary": "~1.1.2"
- }
- },
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -5182,20 +4651,6 @@
"to-regex-range": "^5.0.1"
}
},
- "finalhandler": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
- "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
- "requires": {
- "debug": "2.6.9",
- "encodeurl": "~1.0.2",
- "escape-html": "~1.0.3",
- "on-finished": "~2.3.0",
- "parseurl": "~1.3.3",
- "statuses": "~1.5.0",
- "unpipe": "~1.0.0"
- }
- },
"flat-cache": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
@@ -5227,16 +4682,6 @@
"mime-types": "^2.1.12"
}
},
- "forwarded": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
- "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="
- },
- "fresh": {
- "version": "0.5.2",
- "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
- "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
- },
"fs-extra": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz",
@@ -5333,6 +4778,11 @@
"resolved": "https://registry.npmjs.org/hls-parser/-/hls-parser-0.10.6.tgz",
"integrity": "sha512-t9bKB1fDDVajh5CY/qeZanNMnCm4hAWMroDlflnuboOskYR26z29bEyOYwQRP1MxP2Y83UC2J93DQCC1AMhBCA=="
},
+ "hono": {
+ "version": "4.6.3",
+ "resolved": "https://registry.npmjs.org/hono/-/hono-4.6.3.tgz",
+ "integrity": "sha512-0LeEuBNFeSHGqZ9sNVVgZjB1V5fmhkBSB0hZrpqStSMLOWgfLy0dHOvrjbJh0H2khsjet6rbHfWTHY0kpYThKQ=="
+ },
"htmlparser2": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz",
@@ -5344,18 +4794,6 @@
"entities": "^4.5.0"
}
},
- "http-errors": {
- "version": "1.7.2",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
- "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
- "requires": {
- "depd": "~1.1.2",
- "inherits": "2.0.3",
- "setprototypeof": "1.1.1",
- "statuses": ">= 1.5.0 < 2",
- "toidentifier": "1.0.0"
- }
- },
"human-signals": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
@@ -5368,14 +4806,6 @@
"integrity": "sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==",
"dev": true
},
- "iconv-lite": {
- "version": "0.4.24",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
- "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
- "requires": {
- "safer-buffer": ">= 2.1.2 < 3"
- }
- },
"ignore": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz",
@@ -5422,12 +4852,8 @@
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
- "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
- },
- "ipaddr.js": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
- "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+ "dev": true
},
"is-arrayish": {
"version": "0.2.1",
@@ -5706,16 +5132,6 @@
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"dev": true
},
- "media-typer": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
- "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
- },
- "merge-descriptors": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
- "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
- },
"merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
@@ -5728,11 +5144,6 @@
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
"dev": true
},
- "methods": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
- "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
- },
"micromatch": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
@@ -5743,11 +5154,6 @@
"picomatch": "^2.3.1"
}
},
- "mime": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
- "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
- },
"mime-db": {
"version": "1.49.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz",
@@ -5789,11 +5195,6 @@
"moment": "^2.29.4"
}
},
- "ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
- },
"natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -5808,11 +5209,6 @@
"@seald-io/nedb": "^2.0.4"
}
},
- "negotiator": {
- "version": "0.6.2",
- "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
- "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
- },
"node-gyp-build": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz",
@@ -5843,14 +5239,6 @@
"boolbase": "^1.0.0"
}
},
- "on-finished": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
- "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
- "requires": {
- "ee-first": "1.1.1"
- }
- },
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -5938,11 +5326,6 @@
"parse5": "^7.0.0"
}
},
- "parseurl": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
- "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
- },
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@@ -5955,11 +5338,6 @@
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true
},
- "path-to-regexp": {
- "version": "0.1.7",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
- "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
- },
"path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
@@ -5999,15 +5377,6 @@
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
"dev": true
},
- "proxy-addr": {
- "version": "2.0.7",
- "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
- "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
- "requires": {
- "forwarded": "0.2.0",
- "ipaddr.js": "1.9.1"
- }
- },
"proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
@@ -6019,33 +5388,12 @@
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
"dev": true
},
- "qs": {
- "version": "6.7.0",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
- "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
- },
"queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
"dev": true
},
- "range-parser": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
- "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
- },
- "raw-body": {
- "version": "2.4.0",
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
- "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
- "requires": {
- "bytes": "3.1.0",
- "http-errors": "1.7.2",
- "iconv-lite": "0.4.24",
- "unpipe": "1.0.0"
- }
- },
"regexpp": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
@@ -6119,11 +5467,6 @@
"tslib": "^2.1.0"
}
},
- "safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
- },
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -6144,49 +5487,6 @@
"integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==",
"dev": true
},
- "send": {
- "version": "0.17.1",
- "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
- "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
- "requires": {
- "debug": "2.6.9",
- "depd": "~1.1.2",
- "destroy": "~1.0.4",
- "encodeurl": "~1.0.2",
- "escape-html": "~1.0.3",
- "etag": "~1.8.1",
- "fresh": "0.5.2",
- "http-errors": "~1.7.2",
- "mime": "1.6.0",
- "ms": "2.1.1",
- "on-finished": "~2.3.0",
- "range-parser": "~1.2.1",
- "statuses": "~1.5.0"
- },
- "dependencies": {
- "ms": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
- "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
- }
- }
- },
- "serve-static": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
- "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
- "requires": {
- "encodeurl": "~1.0.2",
- "escape-html": "~1.0.3",
- "parseurl": "~1.3.3",
- "send": "0.17.1"
- }
- },
- "setprototypeof": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
- "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
- },
"shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -6236,11 +5536,6 @@
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
"dev": true
},
- "statuses": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
- "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
- },
"string-argv": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz",
@@ -6369,11 +5664,6 @@
"is-number": "^7.0.0"
}
},
- "toidentifier": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
- "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
- },
"ts-node": {
"version": "10.2.1",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.2.1.tgz",
@@ -6432,13 +5722,19 @@
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
"dev": true
},
- "type-is": {
- "version": "1.6.18",
- "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
- "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "typed-html": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/typed-html/-/typed-html-3.0.1.tgz",
+ "integrity": "sha512-JKCM9zTfPDuPqQqdGZBWSEiItShliKkBFg5c6yOR8zth43v763XkAzTWaOlVqc0Y6p9ee8AaAbipGfUnCsYZUA==",
+ "dev": true
+ },
+ "typed-htmx": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/typed-htmx/-/typed-htmx-0.3.1.tgz",
+ "integrity": "sha512-6WSPsukTIOEMsVbx5wzgVSvldLmgBUVcFIm2vJlBpRPtcbDOGC5y1IYrCWNX1yUlNsrv1Ngcw4gGM8jsPyNV7w==",
+ "dev": true,
"requires": {
- "media-typer": "0.3.0",
- "mime-types": "~2.1.24"
+ "typed-html": "^3.0.1"
}
},
"typescript": {
@@ -6457,11 +5753,6 @@
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ=="
},
- "unpipe": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
- "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
- },
"uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
@@ -6481,22 +5772,12 @@
"node-gyp-build": "^4.3.0"
}
},
- "utils-merge": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
- "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
- },
"v8-compile-cache": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
"integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
"dev": true
},
- "vary": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
- "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
- },
"whatwg-encoding": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
diff --git a/package.json b/package.json
index 0eb012b..ca08f42 100644
--- a/package.json
+++ b/package.json
@@ -1,23 +1,24 @@
{
"name": "eplustv",
- "version": "3.3.4",
+ "version": "3.3.5",
"description": "",
"scripts": {
- "start": "ts-node index.ts",
+ "start": "ts-node index.tsx",
"prepare": "husky install"
},
"keywords": [],
"author": "Joe Ipson ",
"license": "MIT",
"dependencies": {
+ "@hono/node-server": "^1.13.1",
"axios": "^1.2.2",
"axios-curlirize": "^1.3.7",
"cheerio": "^1.0.0",
"crypto-js": "^4.2.0",
- "express": "^4.17.1",
"fast-xml-parser": "^4.5.0",
"fs-extra": "^10.0.0",
"hls-parser": "^0.10.6",
+ "hono": "^4.6.3",
"jwt-decode": "^3.1.2",
"lodash": "^4.17.21",
"moment": "^2.29.1",
@@ -40,6 +41,7 @@
"lint-staged": "^11.1.1",
"prettier": "2.3.2",
"ts-node": "^10.2.1",
+ "typed-htmx": "^0.3.1",
"typescript": "^4.4.3"
},
"lint-staged": {
diff --git a/tsconfig.json b/tsconfig.json
index 077fad3..16c23b0 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,6 +1,8 @@
{
"compilerOptions": {
"target": "es6",
+ "jsx": "react-jsx",
+ "jsxImportSource": "hono/jsx",
"module": "commonjs",
"rootDir": "./",
"outDir": "./dist",
@@ -12,4 +14,4 @@
"tmp",
"slate"
]
-}
\ No newline at end of file
+}
From c11e8d60ae7935f2f42bea7d7fd0c24f5105a75d Mon Sep 17 00:00:00 2001
From: Your Name
Date: Sun, 20 Oct 2024 01:57:06 -0600
Subject: [PATCH 02/10] Most providers moved to UI
---
README.md | 55 +----
favicon.ico | Bin 0 -> 15406 bytes
index.tsx | 71 ++++++-
package-lock.json | 102 +++++++++
package.json | 6 +-
services/b1g-handler.ts | 102 +++++++--
services/build-schedule.ts | 4 +
services/cbs-handler.ts | 116 +++++-----
services/channels.ts | 61 ++++--
services/database.ts | 3 +
services/flo-handler.ts | 126 ++++++-----
services/fox-handler.ts | 199 +++++++++++-------
services/generate-m3u.ts | 18 +-
services/generate-xmltv.ts | 16 +-
services/init-directories.ts | 13 +-
services/mlb-handler.ts | 154 ++++++++------
services/mw-handler.ts | 28 ++-
services/nesn-handler.ts | 165 +++++++++------
services/networks.ts | 1 +
services/paramount-handler.ts | 176 ++++++++++------
services/providers/b1g/index.tsx | 63 ++++++
services/providers/b1g/views/CardBody.tsx | 34 +++
services/providers/b1g/views/Login.tsx | 57 +++++
services/providers/b1g/views/index.tsx | 42 ++++
services/providers/cbs-sports/index.tsx | 59 ++++++
.../providers/cbs-sports/views/CardBody.tsx | 34 +++
services/providers/cbs-sports/views/Login.tsx | 32 +++
services/providers/cbs-sports/views/index.tsx | 41 ++++
services/providers/flosports/index.tsx | 62 ++++++
.../providers/flosports/views/CardBody.tsx | 34 +++
services/providers/flosports/views/Login.tsx | 39 ++++
services/providers/flosports/views/index.tsx | 42 ++++
services/providers/fox/index.tsx | 104 +++++++++
services/providers/fox/views/CardBody.tsx | 58 +++++
services/providers/fox/views/Login.tsx | 35 +++
services/providers/fox/views/index.tsx | 77 +++++++
services/providers/index.ts | 21 ++
services/providers/mlb/index.tsx | 117 ++++++++++
services/providers/mlb/views/CardBody.tsx | 74 +++++++
services/providers/mlb/views/Login.tsx | 39 ++++
services/providers/mlb/views/index.tsx | 62 ++++++
services/providers/mw/index.tsx | 37 ++++
services/providers/mw/views/index.tsx | 36 ++++
services/providers/nesn/index.tsx | 61 ++++++
services/providers/nesn/views/CardBody.tsx | 58 +++++
services/providers/nesn/views/Login.tsx | 44 ++++
services/providers/nesn/views/index.tsx | 43 ++++
services/providers/paramount/index.tsx | 102 +++++++++
.../providers/paramount/views/CardBody.tsx | 73 +++++++
services/providers/paramount/views/Login.tsx | 36 ++++
services/providers/paramount/views/index.tsx | 43 ++++
services/shared-helpers.ts | 3 +-
services/shared-interfaces.ts | 22 ++
tsconfig.json | 10 +-
views/Header.tsx | 12 ++
views/Layout.tsx | 21 ++
views/Links.tsx | 75 +++++++
views/Main.tsx | 6 +
views/Providers.tsx | 12 ++
views/Script.tsx | 21 ++
views/Style.tsx | 35 +++
61 files changed, 2805 insertions(+), 487 deletions(-)
create mode 100644 favicon.ico
create mode 100644 services/providers/b1g/index.tsx
create mode 100644 services/providers/b1g/views/CardBody.tsx
create mode 100644 services/providers/b1g/views/Login.tsx
create mode 100644 services/providers/b1g/views/index.tsx
create mode 100644 services/providers/cbs-sports/index.tsx
create mode 100644 services/providers/cbs-sports/views/CardBody.tsx
create mode 100644 services/providers/cbs-sports/views/Login.tsx
create mode 100644 services/providers/cbs-sports/views/index.tsx
create mode 100644 services/providers/flosports/index.tsx
create mode 100644 services/providers/flosports/views/CardBody.tsx
create mode 100644 services/providers/flosports/views/Login.tsx
create mode 100644 services/providers/flosports/views/index.tsx
create mode 100644 services/providers/fox/index.tsx
create mode 100644 services/providers/fox/views/CardBody.tsx
create mode 100644 services/providers/fox/views/Login.tsx
create mode 100644 services/providers/fox/views/index.tsx
create mode 100644 services/providers/index.ts
create mode 100644 services/providers/mlb/index.tsx
create mode 100644 services/providers/mlb/views/CardBody.tsx
create mode 100644 services/providers/mlb/views/Login.tsx
create mode 100644 services/providers/mlb/views/index.tsx
create mode 100644 services/providers/mw/index.tsx
create mode 100644 services/providers/mw/views/index.tsx
create mode 100644 services/providers/nesn/index.tsx
create mode 100644 services/providers/nesn/views/CardBody.tsx
create mode 100644 services/providers/nesn/views/Login.tsx
create mode 100644 services/providers/nesn/views/index.tsx
create mode 100644 services/providers/paramount/index.tsx
create mode 100644 services/providers/paramount/views/CardBody.tsx
create mode 100644 services/providers/paramount/views/Login.tsx
create mode 100644 services/providers/paramount/views/index.tsx
create mode 100644 views/Header.tsx
create mode 100644 views/Layout.tsx
create mode 100644 views/Links.tsx
create mode 100644 views/Main.tsx
create mode 100644 views/Providers.tsx
create mode 100644 views/Script.tsx
create mode 100644 views/Style.tsx
diff --git a/README.md b/README.md
index 829605a..bfa6d61 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
-Current version: **3.3.5**
+Current version: **4.0.0**
# About
This takes ESPN/ESPN+, FOX Sports, Paramount+, MSG+, NFL+, B1G+, NESN, Mountain West, FloSports, CBS Sports, or MLB.tv programming and transforms it into a "live TV" experience with virtual linear channels. It will discover what is on, and generate a schedule of channels that will give you M3U and XMLTV files that you can import into something like [Jellyfin](https://jellyfin.org) or [Channels](https://getchannels.com).
@@ -12,7 +12,7 @@ This takes ESPN/ESPN+, FOX Sports, Paramount+, MSG+, NFL+, B1G+, NESN, Mountain
* The Mouse might not like it and it could be taken down at any minute. Enjoy it while it lasts. ¯\\_(ツ)_/¯
# Using
-The server exposes 2 main endpoints:
+The server exposes 4 main endpoints:
| Endpoint | Description |
|---|---|
@@ -55,26 +55,13 @@ Use if you would like to login with a TV provider or ESPN+ and access various ES
| ESPN_PPV | PPV: Set if you have purchased PPV events | False |
#### FOX Sports
-Use if you would like to login with a TV provider and access various FOX Sports events
-| Environment Variable | Description | Required? | Default |
-|---|---|---|---|
-| FOXSPORTS** | Set if your TV provider supports it | No | False |
-| MAX_RESOLUTION | Max resolution to use. Valid options are `UHD/HDR` and `720p` (Some events don't offer 4K and will attempt to play the highest framerate available for selected resolution). | No | 720p |
-| FOX_ONLY_4K | Only grab 4K events | No | False |
+Enable in your browser by going to the admin panel of the container
#### Paramount+
-Use if you would like to login with Paramount+
-| Environment Variable | Description | Required? | Default |
-|---|---|---|---|
-| PARAMOUNTPLUS | Set if you would like CBS Sports events | False | False |
-| CBSSPORTSHQ* | Set if you would like the CBS Sports HQ channel (only available with `LINEAR_CHANNELS`) | False | False |
-| GOLAZO* | Set if you would like the Golazo Network channel (only available with `LINEAR_CHANNELS`) | False | False |
+Enable in your browser by going to the admin panel of the container
#### CBS Sports
-Use if you would like to login with a TV provider and access various CBS Sports events
-| Environment Variable | Description | Required? | Default |
-|---|---|---|---|
-| CBSSPORTS | Set if you would like CBS Sports events | False | False |
+Enable in your browser by going to the admin panel of the container
#### NFL+
Use if you would like to login with NFL+.
@@ -94,20 +81,10 @@ Please note that if you only have an NFL account, you can still get events from
| NFLCHANNEL* | Set if you would like the NFL Channel (only available with `LINEAR_CHANNELS`) | False | False |
#### NESN
-Use if you would like to login with NESN. Will get NESN and NESN+ programming
-
-4K events (home Red Sox and Bruins games will be scheduled normally)
-| Environment Variable | Description | Default |
-|---|---|---|
-| NESN*** | Set if you would like to use NESN | False |
+Enable in your browser by going to the admin panel of the container
#### B1G+
-Use if you would like to login with your B1G+ account
-| Environment Variable | Description | Default |
-|---|---|---|
-| B1GPLUS | Set if you would like to use B1G+ | False |
-| B1GPLUS_USER | B1G+ Username | False |
-| B1GPLUS_PASS | B1G+ Password | False |
+Enable in your browser by going to the admin panel of the container
#### MSG+
Use if you would like to login with your MSG+ account
@@ -118,28 +95,16 @@ Use if you would like to login with your MSG+ account
| MSGPLUS_PASS | MSG+ Password | False |
#### FloSports
-Use if you would like to login with FloSports
-| Environment Variable | Description | Required? | Default |
-|---|---|---|---|
-| FLOSPORTS | Set if you would like FloSports events | False | False |
+Enable in your browser by going to the admin panel of the container
#### Mountain West
-Use if you would like to use Mountain West
-| Environment Variable | Description | Required? | Default |
-|---|---|---|---|
-| MTNWEST | Set if you would like Mountain West events | False | False |
+Enable in your browser by going to the admin panel of the container
#### MLB.tv
-Use if you would like to login with your MLB.tv account
+Enable in your browser by going to the admin panel of the container
*** If `LINEAR_CHANNELS` is set, Big Inning will be on its own channel
-| Environment Variable | Description | Default |
-|---|---|---|
-| MLBTV | Set if you would like to use MLB.tv | False |
-| MLBTV_ONLY_FREE | Only schedule free games | False |
-
-
### Notes
`*`: Dedicated linear channel - Will only schedule when `LINEAR_CHANNELS` is set
diff --git a/favicon.ico b/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..a8f2239298b58617c38162a61fd3a0c1a6841445
GIT binary patch
literal 15406
zcmeHNTZ~Re96#z>ij|O%RIEl+@PH;lRMM;v64!`C2=RatHjPBI2oG+p#O1*QSxL7%
zATCX@?1LwlNF?gPB@$taM6CO5EsOnr{r_jq%$b>U&Uemt*|l1eOwP>z`kVjEobP<|
zAJ1#$wf0I%JbIRTi`sbJaL@BfOJ#pRH_!W>XhVkh{QjPIzK7=xAQ@624N0d*WC0ps
z7q@S7rF_OE%6<$vT3A{V-n2{CfZI1L`8r{VFom@Os&?iZ3pm=kPm17Z5pD>6CcBi$
zHQUNihT?zVzya*qwF@UsoDk%D_wM1Jni?g7ojZY>w`BLq6`=elik&fx&6|HCrX<|C
za|e6&>=E{E-I7YvrvvBDOa1btK<}@C)2Fq0to-E{?f=oEM>uxun7~w9TPuW=enUee
z`t<|;{1ceJFwkGMY85JWSKwszNkL@2yLayjMCIi`uU^2&k-)NLz}&e&Lj%yiKTuaM
zZG51?eE9Hz#?&ZcYQ~AjNh~x{6;FTMOy|%`jQ~rirHuJ{ny=`Z;0kDlY>@w;Wb}GU
z(9$YePB%154n$j2GimU-NqR2iC{wmpSb`}gHS(p91Cie|rBr{Q%I~MRV&h8anE(?+
zZFb&@wwadJb0@xu@inKrV)^>iXFdskQk;_X^^!>x%qEb8LVJmCDDy=y74PJ~&WQ_N
z9KGelC;8-b
z`=2^>s!0m9^YQ=sHPF2~@WmGr!(YcjW(6V6KYxF1-MSSUHf+GmnKLnQ;zZHr{%bbw
zm$>mCIwburrSHqOZ5uIThSIQ~?7tU%5KfymO;`TjLJ*%{}%%ZrnKBzI|KQ7cX8|g0bj
z=+XYCrD6Q+*|W4xx?}k8;kxeMzu%Am-oJm3ckkX=w8e{oHf_Rn!USN{DCv(r`9K8$uf3$gA`Ho`1Xa`6uPREO|0h_*ka1a-T#M_3(S|97g7z&M
zF^aU9#E(>JN}}3bh52ZXVKs-3G*QUDcH&UR(bIU0kjY|B;&V_s-3W`tCB^xR#T};*
z4=Z<#cv}T=)R@#Wjj^naXL(m{xmj-!XI
zrk4!s8h;}qn4cJ_pg??LV^wfz%tae^l%W>$qE-Wrero6`@M_s;Q<9p(j#f`)P)irJ
z5$8G*z7keXMiMEQD@4;^uDUtOC*e)vC%Q{vAmwmF|G~2C)D&JNav_*nI&rj3DG(W^
zW`Q2*ImJ+$1)Pr~UyLb+k+(<=<99{fK4FL;<%cX!L&*`2k?_glk=D|%G|p0MxUooMJO`Q7IC8LAm}tU)6WC$jNeW4Pwci>G-gkT^&1b`p=^$CvCw}`0
zB_jBo)G7omLuH-JbMN#S|rbU=?V$SM@ozP
z9%N3O3G=z|#*G{&Q#R-XE#MhBC@U+&?Afz%^ypEmR##V3%qVD1VY@3nSvj8>oBow)zH`&Juax8bPPc9VpRp$ug@4_;bxER{&+*szRiHK3
zZq!oR%*Ag@$T^{_Md;Ki!0)w^IRkqRT+JZWQXb}koInx(F=cS^n>vht@7}!_Hf$IM
z4<0Nk=SMnGzy=K(q-mRe-9+n{E-o1*4Su?gh2Fg-W?uV0|19%VyoM+hqxX_wRyWo9
z8AA!J_3PJLb<3756u(;WzBza99M-N~3%-ZNb;O7fv|jsS<;sX+?|q%Dl6gkX1#Q~IxgrfW)KK_`4jqbl^X6H#oDzk6N6anO`j2~|_)4|3
z2eYmJ2Fk(mzUDp4>xN$!_wNG)N66*OK$7cN*TR!8B_w*D;z>L~o2Bjr8omX|MqYuA9QR~spp
z3d*HREZJFl94s(!AY;tn`|km|lBo;72AIb4!uKJa
z^s$tq(DCon`0?Y>rArr)bLIDns{QE#2#WzXaO1kFR|H
zfbUxN=pkk469BvyloK{vSt;|gZ0qph@H`7?66D-_eSLkX^7>Dcw`DtK;WbX?{F*V^
z0BtYPC)47afuAhPIo}`@602#4`N<`EjFw(F+!mWVDMUg#`4Lj!jWL|JrXHMb#CRwY
zxyjr#nc*T0+?*k^T_p1#$&5RaG_#O#l|B;ou-i1~!&*h9Gzo;LfH#Sw+(HorD>n-T
zNykROWSH%OyxG4aA9?8ckEEG^XAQf;kX0zf5L4-skn%?+m5M>K9mO&XWu>8vsXjlW
zXeK}3c2n8S2~P?T+2WUzua+*HA{J?eP$V;^tI!k_!$-)d>9LsYVt{q=#Dg!`8=0o-
NL7&U#2;?1se*yQ|nnwTt
literal 0
HcmV?d00001
diff --git a/index.tsx b/index.tsx
index d8f9990..e89facb 100644
--- a/index.tsx
+++ b/index.tsx
@@ -1,6 +1,8 @@
import {Context, Hono} from 'hono';
import {serve} from '@hono/node-server';
+import {serveStatic} from '@hono/node-server/serve-static';
import {BlankEnv, BlankInput} from 'hono/types';
+import {html} from 'hono/html';
import moment from 'moment';
import {generateM3u} from './services/generate-m3u';
@@ -23,9 +25,27 @@ import {cleanEntries, removeChannelStatus} from './services/shared-helpers';
import {appStatus} from './services/app-status';
import {SERVER_PORT} from './services/port';
import {useLinear} from './services/channels';
+import { providers } from './services/providers';
import {version} from './package.json';
+import { Layout } from './views/Layout';
+import { Header } from './views/Header';
+import { Main } from './views/Main';
+import { Links } from './views/Links';
+import { Style } from './views/Style';
+import { Providers } from './views/Providers';
+import { Script } from './views/Script';
+
+import {CBSSports} from './services/providers/cbs-sports/views';
+import { MntWest } from './services/providers/mw/views';
+import {Paramount} from './services/providers/paramount/views';
+import {FloSports} from './services/providers/flosports/views';
+import {MlbTv} from './services/providers/mlb/views';
+import {FoxSports} from './services/providers/fox/views';
+import {Nesn} from './services/providers/nesn/views';
+import {B1G} from './services/providers/b1g/views';
+
const notFound = (c: Context) => {
return c.text('404 not found', 404, {
'X-Tuner-Error': 'EPlusTV: Error getting content',
@@ -42,12 +62,12 @@ const getUri = (c: Context): string => {
const protocol = c.req.header('x-forwarded-proto') || 'http';
const host = c.req.header('host') || '';
-
return `${protocol}://${host}`;
};
const schedule = async () => {
console.log('=== Getting events ===');
+
await espnHandler.getSchedule();
await foxHandler.getSchedule();
await mlbHandler.getSchedule();
@@ -61,16 +81,48 @@ const schedule = async () => {
await cbsHandler.getSchedule();
console.log('=== Done getting events ===');
- await cleanEntries();
console.log('=== Building the schedule ===');
+
+ await cleanEntries();
await scheduleEntries();
+
console.log('=== Done building the schedule ===');
};
const app = new Hono();
-app.get('/channels.m3u', c => {
- const m3uFile = generateM3u(getUri(c));
+app.use('/node_modules/*', serveStatic({root: './'}));
+app.use('/favicon.ico', serveStatic({root: './'}));
+
+app.route('/', providers);
+
+app.get('/', async c => {
+ return c.html(
+ html`${(
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}`,
+ );
+});
+
+app.get('/channels.m3u', async c => {
+ const m3uFile = await generateM3u(getUri(c));
if (!m3uFile) {
return notFound(c);
@@ -81,12 +133,12 @@ app.get('/channels.m3u', c => {
});
});
-app.get('/linear-channels.m3u', c => {
+app.get('/linear-channels.m3u', async c => {
if (!useLinear) {
return notFound(c);
}
- const m3uFile = generateM3u(getUri(c), true);
+ const m3uFile = await generateM3u(getUri(c), true);
if (!m3uFile) {
return notFound(c);
@@ -278,14 +330,17 @@ process.on('SIGINT', shutDown);
await cbsHandler.initialize();
await cbsHandler.refreshTokens();
- await schedule();
+ await mwHandler.initialize();
serve(
{
fetch: app.fetch,
port: SERVER_PORT,
},
- () => console.log(`Server started on port ${SERVER_PORT}`),
+ () => {
+ console.log(`Server started on port ${SERVER_PORT}`);
+ schedule();
+ },
);
})();
diff --git a/package-lock.json b/package-lock.json
index 03175b6..1ccc5f8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,7 @@
"license": "MIT",
"dependencies": {
"@hono/node-server": "^1.13.1",
+ "@picocss/pico": "^2.0.6",
"axios": "^1.2.2",
"axios-curlirize": "^1.3.7",
"cheerio": "^1.0.0",
@@ -18,12 +19,15 @@
"fs-extra": "^10.0.0",
"hls-parser": "^0.10.6",
"hono": "^4.6.3",
+ "htmx-toaster": "^0.0.18",
+ "htmx.org": "^2.0.0",
"jwt-decode": "^3.1.2",
"lodash": "^4.17.21",
"moment": "^2.29.1",
"moment-timezone": "^0.5.45",
"nedb-promises": "^5.0.1",
"sockette": "^2.0.6",
+ "tsconfig-paths": "^4.2.0",
"ws": "^8.9.0",
"xml": "^1.0.1"
},
@@ -309,6 +313,14 @@
"node": ">= 8"
}
},
+ "node_modules/@picocss/pico": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@picocss/pico/-/pico-2.0.6.tgz",
+ "integrity": "sha512-/d8qsykowelD6g8k8JYgmCagOIulCPHMEc2NC4u7OjmpQLmtSetLhEbt0j1n3fPNJVcrT84dRp0RfJBn3wJROA==",
+ "engines": {
+ "node": ">=18.19.0"
+ }
+ },
"node_modules/@seald-io/binary-search-tree": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@seald-io/binary-search-tree/-/binary-search-tree-1.0.2.tgz",
@@ -1916,6 +1928,16 @@
"entities": "^4.5.0"
}
},
+ "node_modules/htmx-toaster": {
+ "version": "0.0.18",
+ "resolved": "https://registry.npmjs.org/htmx-toaster/-/htmx-toaster-0.0.18.tgz",
+ "integrity": "sha512-tziOdadXK8YtIyQlGHYsFl51XBQ5J28gBm/nP8g/s0BbvlIlUpuLTI7S2GliWDL3q1BagjcaKEDjfw9sGICz7w=="
+ },
+ "node_modules/htmx.org": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-2.0.3.tgz",
+ "integrity": "sha512-AeoJUAjkCVVajbfKX+3sVQBTCt8Ct4lif1T+z/tptTXo8+8yyq3QIMQQe/IT+R8ssfrO1I0DeX4CAronzCL6oA=="
+ },
"node_modules/human-signals": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
@@ -2122,6 +2144,17 @@
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
"dev": true
},
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
@@ -2424,6 +2457,14 @@
"node": "*"
}
},
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/moment": {
"version": "2.30.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
@@ -2984,6 +3025,14 @@
"node": ">=8"
}
},
+ "node_modules/strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/strip-final-newline": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
@@ -3145,6 +3194,19 @@
}
}
},
+ "node_modules/tsconfig-paths": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz",
+ "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==",
+ "dependencies": {
+ "json5": "^2.2.2",
+ "minimist": "^1.2.6",
+ "strip-bom": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/tslib": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
@@ -3608,6 +3670,11 @@
"fastq": "^1.6.0"
}
},
+ "@picocss/pico": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@picocss/pico/-/pico-2.0.6.tgz",
+ "integrity": "sha512-/d8qsykowelD6g8k8JYgmCagOIulCPHMEc2NC4u7OjmpQLmtSetLhEbt0j1n3fPNJVcrT84dRp0RfJBn3wJROA=="
+ },
"@seald-io/binary-search-tree": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@seald-io/binary-search-tree/-/binary-search-tree-1.0.2.tgz",
@@ -4794,6 +4861,16 @@
"entities": "^4.5.0"
}
},
+ "htmx-toaster": {
+ "version": "0.0.18",
+ "resolved": "https://registry.npmjs.org/htmx-toaster/-/htmx-toaster-0.0.18.tgz",
+ "integrity": "sha512-tziOdadXK8YtIyQlGHYsFl51XBQ5J28gBm/nP8g/s0BbvlIlUpuLTI7S2GliWDL3q1BagjcaKEDjfw9sGICz7w=="
+ },
+ "htmx.org": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-2.0.3.tgz",
+ "integrity": "sha512-AeoJUAjkCVVajbfKX+3sVQBTCt8Ct4lif1T+z/tptTXo8+8yyq3QIMQQe/IT+R8ssfrO1I0DeX4CAronzCL6oA=="
+ },
"human-signals": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
@@ -4946,6 +5023,11 @@
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
"dev": true
},
+ "json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="
+ },
"jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
@@ -5182,6 +5264,11 @@
"brace-expansion": "^1.1.7"
}
},
+ "minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="
+ },
"moment": {
"version": "2.30.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
@@ -5573,6 +5660,11 @@
"ansi-regex": "^5.0.1"
}
},
+ "strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="
+ },
"strip-final-newline": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
@@ -5684,6 +5776,16 @@
"yn": "3.1.1"
}
},
+ "tsconfig-paths": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz",
+ "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==",
+ "requires": {
+ "json5": "^2.2.2",
+ "minimist": "^1.2.6",
+ "strip-bom": "^3.0.0"
+ }
+ },
"tslib": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
diff --git a/package.json b/package.json
index ca08f42..f4a1fa3 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
"version": "3.3.5",
"description": "",
"scripts": {
- "start": "ts-node index.tsx",
+ "start": "ts-node -r tsconfig-paths/register index.tsx",
"prepare": "husky install"
},
"keywords": [],
@@ -11,6 +11,7 @@
"license": "MIT",
"dependencies": {
"@hono/node-server": "^1.13.1",
+ "@picocss/pico": "^2.0.6",
"axios": "^1.2.2",
"axios-curlirize": "^1.3.7",
"cheerio": "^1.0.0",
@@ -19,12 +20,15 @@
"fs-extra": "^10.0.0",
"hls-parser": "^0.10.6",
"hono": "^4.6.3",
+ "htmx-toaster": "^0.0.18",
+ "htmx.org": "^2.0.0",
"jwt-decode": "^3.1.2",
"lodash": "^4.17.21",
"moment": "^2.29.1",
"moment-timezone": "^0.5.45",
"nedb-promises": "^5.0.1",
"sockette": "^2.0.6",
+ "tsconfig-paths": "^4.2.0",
"ws": "^8.9.0",
"xml": "^1.0.1"
},
diff --git a/services/b1g-handler.ts b/services/b1g-handler.ts
index 9f015f6..cf1d5a9 100644
--- a/services/b1g-handler.ts
+++ b/services/b1g-handler.ts
@@ -8,7 +8,7 @@ import jwt_decode from 'jwt-decode';
import {b1gUserAgent, okHttpUserAgent} from './user-agent';
import {configPath} from './config';
import {useB1GPlus} from './networks';
-import {IEntry, IHeaders} from './shared-interfaces';
+import {ClassTypeWithoutMethods, IEntry, IHeaders, IProvider} from './shared-interfaces';
import {db} from './database';
interface IEventCategory {
@@ -58,6 +58,13 @@ interface IGameData {
categories: string[];
}
+interface IB1GMeta {
+ username: string;
+ password: string;
+}
+
+const b1gConfigPath = path.join(configPath, 'b1g_tokens.json');
+
const getEventData = (event: IB1GEvent): IGameData => {
let sport = 'B1G+ Event';
const categories: string[] = ['B1G+', 'B1G'];
@@ -142,20 +149,57 @@ class B1GHandler {
public expires_at?: number;
public initialize = async () => {
- if (!useB1GPlus) {
- return;
+ const setup = (await db.providers.count({name: 'b1g'})) > 0 ? true : false;
+
+ if (!setup) {
+ const data: TB1GTokens = {};
+
+ if (useB1GPlus) {
+ this.loadJSON();
+
+ data.access_token = this.access_token;
+ data.expires_at = this.expires_at;
+ }
+
+ await db.providers.insert>({
+ enabled: useB1GPlus,
+ meta: {
+ password: process.env.B1GPLUS_PASS,
+ username: process.env.B1GPLUS_USER,
+ },
+ name: 'b1g',
+ tokens: data,
+ });
+
+ if (fs.existsSync(b1gConfigPath)) {
+ fs.rmSync(b1gConfigPath);
+ }
}
- // Load tokens from local file and make sure they are valid
- this.load();
+ if (useB1GPlus) {
+ console.log('Using B1GPLUS variable is no longer needed. Please use the UI going forward');
+ }
+ if (process.env.B1GPLUS_USER) {
+ console.log('Using B1GPLUS_USER variable is no longer needed. Please use the UI going forward');
+ }
+ if (process.env.B1GPLUS_PASS) {
+ console.log('Using B1GPLUS_PASS variable is no longer needed. Please use the UI going forward');
+ }
- if (!this.expires_at || !this.access_token || moment(this.expires_at).isBefore(moment().add(100, 'days'))) {
- await this.login();
+ const {enabled} = await db.providers.findOne({name: 'b1g'});
+
+ if (!enabled) {
+ return;
}
+
+ // Load tokens from local file and make sure they are valid
+ await this.load();
};
public refreshTokens = async () => {
- if (!useB1GPlus) {
+ const {enabled} = await db.providers.findOne({name: 'b1g'});
+
+ if (!enabled) {
return;
}
@@ -165,7 +209,9 @@ class B1GHandler {
};
public getSchedule = async (): Promise => {
- if (!useB1GPlus) {
+ const {enabled} = await db.providers.findOne({name: 'b1g'});
+
+ if (!enabled) {
return;
}
@@ -231,7 +277,7 @@ class B1GHandler {
private extendToken = async (): Promise => {
try {
- const url = 'https://www.bigtenplus.com/api/v3/cleeng/extend_token';
+ const url = ['https://', 'www.bigtenplus.com', '/api/v3/cleeng/extend_token'].join('');
const headers = {
Authorization: `Bearer ${this.access_token}`,
'User-Agent': b1gUserAgent,
@@ -249,7 +295,7 @@ class B1GHandler {
this.access_token = data.token;
this.expires_at = moment().add(399, 'days').valueOf();
- this.save();
+ await this.save();
} catch (e) {
console.error(e);
console.log('Could not extend token for B1G+');
@@ -314,18 +360,20 @@ class B1GHandler {
}
};
- private login = async (): Promise => {
+ public login = async (username?: string, password?: string): Promise => {
try {
- const url = 'https://www.bigtenplus.com/api/v3/cleeng/login';
+ const url = ['https://', 'www.bigtenplus.com', '/api/v3/cleeng/login'].join('');
const headers = {
'User-Agent': b1gUserAgent,
accept: 'application/json',
'content-type': 'application/json',
};
+ const {meta} = await db.providers.findOne>({name: 'b1g'});
+
const params = {
- email: process.env.B1GPLUS_USER,
- password: process.env.B1GPLUS_PASS,
+ email: username || meta.username,
+ password: password || meta.password,
};
const {data} = await axios.post(url, params, {
@@ -335,19 +383,31 @@ class B1GHandler {
this.access_token = data.token;
this.expires_at = moment().add(399, 'days').valueOf();
- this.save();
+ await this.save();
+
+ return true;
} catch (e) {
console.error(e);
console.log('Could not login to B1G+');
+
+ return false;
}
};
- private save = () => {
- fsExtra.writeJSONSync(path.join(configPath, 'b1g_tokens.json'), this, {spaces: 2});
+ private save = async (): Promise => {
+ await db.providers.update({name: 'b1g'}, {$set: {tokens: this}});
};
- private load = () => {
- if (fs.existsSync(path.join(configPath, 'b1g_tokens.json'))) {
+ private load = async (): Promise => {
+ const {tokens} = await db.providers.findOne>({name: 'b1g'});
+ const {access_token, expires_at} = tokens;
+
+ this.access_token = access_token;
+ this.expires_at = expires_at;
+ };
+
+ private loadJSON = () => {
+ if (fs.existsSync(b1gConfigPath)) {
const {access_token, expires_at} = fsExtra.readJSONSync(path.join(configPath, 'b1g_tokens.json'));
this.access_token = access_token;
@@ -356,4 +416,6 @@ class B1GHandler {
};
}
+export type TB1GTokens = ClassTypeWithoutMethods;
+
export const b1gHandler = new B1GHandler();
diff --git a/services/build-schedule.ts b/services/build-schedule.ts
index 5471e30..bb605b9 100644
--- a/services/build-schedule.ts
+++ b/services/build-schedule.ts
@@ -2,6 +2,10 @@ import {NUM_OF_CHANNELS, START_CHANNEL, useLinear} from './channels';
import {db, IDocument} from './database';
import {IChannel, IEntry} from './shared-interfaces';
+export const removeEntriesProvider = async (providerName: string): Promise => {
+ await db.entries.remove({from: providerName}, {multi: true});
+};
+
const scheduleEntry = async (entry: IEntry & IDocument, startChannel: number): Promise => {
let channelNum: number;
diff --git a/services/cbs-handler.ts b/services/cbs-handler.ts
index 65829bf..bb29085 100644
--- a/services/cbs-handler.ts
+++ b/services/cbs-handler.ts
@@ -9,7 +9,7 @@ import crypto from 'crypto';
import {cbsSportsUserAgent, userAgent} from './user-agent';
import {configPath} from './config';
import {useCBSSports} from './networks';
-import {IEntry, IHeaders} from './shared-interfaces';
+import {ClassTypeWithoutMethods, IEntry, IHeaders, IProvider} from './shared-interfaces';
import {db} from './database';
import {getRandomUUID} from './shared-helpers';
import {createAdobeAuthHeader} from './adobe-helpers';
@@ -223,6 +223,8 @@ const CHANNEL_MAP = {
CBSSHQ: '9Lq0ERvoSR-z9AwvFS-xYA',
} as const;
+const cbsConfigPath = path.join(configPath, 'cbs_tokens.json');
+
const getEventData = (event: ICBSEvent): IGameData => {
const sport = event.video.properties.sport;
const categories: string[] = [
@@ -294,25 +296,49 @@ class CBSHandler {
public mvpd_id?: string;
public initialize = async () => {
- if (!useCBSSports) {
- return;
- }
+ const setup = (await db.providers.count({name: 'cbs'})) > 0 ? true : false;
- // Load tokens from local file and make sure they are valid
- this.load();
+ // First time setup
+ if (!setup) {
+ const data: TCBSTokens = {};
+
+ if (useCBSSports) {
+ this.loadJSON();
+
+ data.device_id = this.device_id;
+ data.user_id = this.user_id;
+ data.mvpd_id = this.mvpd_id;
+ }
+
+ await db.providers.insert>({
+ enabled: useCBSSports,
+ name: 'cbs',
+ tokens: data,
+ });
+
+ if (fs.existsSync(cbsConfigPath)) {
+ fs.rmSync(cbsConfigPath);
+ }
+ }
- if (!this.device_id) {
- this.device_id = getRandomUUID();
- this.save();
+ if (useCBSSports) {
+ console.log('Using CBSSPORTS variable is no longer needed. Please use the UI going forward');
}
- if (!this.user_id) {
- await this.startProviderAuthFlow();
+ const {enabled} = await db.providers.findOne({name: 'cbs'});
+
+ if (!enabled) {
+ return;
}
+
+ // Load tokens from local file and make sure they are valid
+ await this.load();
};
public refreshTokens = async () => {
- if (!useCBSSports) {
+ const {enabled} = await db.providers.findOne({name: 'cbs'});
+
+ if (!enabled) {
return;
}
@@ -320,7 +346,9 @@ class CBSHandler {
};
public getSchedule = async (): Promise => {
- if (!useCBSSports) {
+ const {enabled} = await db.providers.findOne({name: 'cbs'});
+
+ if (!enabled) {
return;
}
@@ -460,7 +488,9 @@ class CBSHandler {
}
};
- private startProviderAuthFlow = async (): Promise => {
+ public getAuthCode = async (): Promise => {
+ this.device_id = getRandomUUID();
+
try {
const url = [
'https://',
@@ -483,45 +513,14 @@ class CBSHandler {
},
});
- console.log(`=== CBS Sports Auth ===`);
- console.log('Please open a browser window and go to: https://www.cbssports.com/firetv/');
- console.log('Enter code: ', data.code);
- console.log('App will continue when login has completed...');
-
- return new Promise(async (resolve, reject) => {
- // Reg code expires in 5 minutes
- const maxNumOfReqs = 30;
-
- let numOfReqs = 0;
-
- const authenticate = async () => {
- if (numOfReqs < maxNumOfReqs) {
- const res = await this.authenticateRegCode(data.code);
- numOfReqs += 1;
-
- if (res) {
- clearInterval(regInterval);
- resolve();
- }
- } else {
- clearInterval(regInterval);
- reject();
- }
- };
-
- const regInterval = setInterval(() => {
- authenticate();
- }, 10 * 1000);
-
- await authenticate();
- });
+ return data.code;
} catch (e) {
console.error(e);
console.log('Could not login to CBS Sports');
}
};
- private authenticateRegCode = async (code: string): Promise => {
+ public authenticateRegCode = async (code: string): Promise => {
try {
const url = ['https://', 'video-api.cbssports.com', '/vms/shortcode/v1', '/status?shortcode=', code].join('');
@@ -568,26 +567,37 @@ class CBSHandler {
this.user_id = data.userId;
this.mvpd_id = data.mvpd;
- this.save();
+ await this.save();
} catch (e) {
console.error(e);
console.log('Could not lauthenticate with Adobe');
}
};
- private save = () => {
- fsExtra.writeJSONSync(path.join(configPath, 'cbs_tokens.json'), this, {spaces: 2});
+ private save = async (): Promise => {
+ await db.providers.update({name: 'cbs'}, {$set: {tokens: this}});
};
- private load = () => {
- if (fs.existsSync(path.join(configPath, 'cbs_tokens.json'))) {
- const {device_id, user_id, mvpd_id} = fsExtra.readJSONSync(path.join(configPath, 'cbs_tokens.json'));
+ private loadJSON = () => {
+ if (fs.existsSync(cbsConfigPath)) {
+ const {device_id, user_id, mvpd_id} = fsExtra.readJSONSync(cbsConfigPath);
this.device_id = device_id;
this.user_id = user_id;
this.mvpd_id = mvpd_id;
}
};
+
+ private load = async (): Promise => {
+ const {tokens} = await db.providers.findOne>({name: 'cbs'});
+ const {device_id, user_id, mvpd_id} = tokens || {};
+
+ this.device_id = device_id;
+ this.user_id = user_id;
+ this.mvpd_id = mvpd_id;
+ };
}
+export type TCBSTokens = ClassTypeWithoutMethods;
+
export const cbsHandler = new CBSHandler();
diff --git a/services/channels.ts b/services/channels.ts
index c1813d8..966e76f 100644
--- a/services/channels.ts
+++ b/services/channels.ts
@@ -1,18 +1,8 @@
import _ from 'lodash';
-import {
- useAccN,
- useEspn1,
- useEspn2,
- useEspnews,
- useEspnU,
- useFoxSports,
- useMLBtv,
- useMsgPlus,
- useNesn,
- useNfl,
- useParamount,
- useSec,
-} from './networks';
+
+import {useAccN, useEspn1, useEspn2, useEspnews, useEspnU, useMsgPlus, useNfl, useSec} from './networks';
+import {db} from './database';
+import {IProvider} from './shared-interfaces';
let startChannel = _.toNumber(process.env.START_CHANNEL);
if (_.isNaN(startChannel)) {
@@ -46,6 +36,18 @@ export const LINEAR_START_CHANNEL = nextStartChannel(startChannel + numOfChannel
export const useLinear = process.env.LINEAR_CHANNELS?.toLowerCase() === 'true' ? true : false;
+export const checkChannelEnabled = async (provider: string, channelId: string): Promise => {
+ const networkProvider = await db.providers.findOne({name: provider});
+
+ if (!networkProvider) {
+ throw new Error(`Could not find provider: ${provider}`);
+ }
+
+ const network = networkProvider.linear_channels.find(c => c.id === channelId);
+
+ return network.enabled;
+};
+
/* eslint-disable sort-keys-custom-order-fix/sort-keys-custom-order-fix */
export const CHANNELS = {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
@@ -100,7 +102,8 @@ export const CHANNELS = {
tvgName: 'ESPNWHD',
},
10: {
- canUse: useFoxSports,
+ canUse: undefined,
+ checkChannelEnabled: () => checkChannelEnabled('foxsports', 'fs1'),
id: 'fs1',
logo: 'https://tmsimg.fancybits.co/assets/s82547_ll_h15_aa.png?w=360&h=270',
name: 'FS1',
@@ -108,7 +111,8 @@ export const CHANNELS = {
tvgName: 'FS1HD',
},
11: {
- canUse: useFoxSports,
+ canUse: undefined,
+ checkChannelEnabled: () => checkChannelEnabled('foxsports', 'fs2'),
id: 'fs2',
logo: 'https://tmsimg.fancybits.co/assets/s59305_ll_h15_aa.png?w=360&h=270',
name: 'FS2',
@@ -116,7 +120,8 @@ export const CHANNELS = {
tvgName: 'FS2HD',
},
12: {
- canUse: useFoxSports,
+ canUse: undefined,
+ checkChannelEnabled: () => checkChannelEnabled('foxsports', 'btn'),
id: 'btn',
logo: 'https://tmsimg.fancybits.co/assets/s58321_ll_h15_ac.png?w=360&h=270',
name: 'B1G Network',
@@ -124,7 +129,8 @@ export const CHANNELS = {
tvgName: 'BIG10HD',
},
13: {
- canUse: useFoxSports,
+ canUse: undefined,
+ checkChannelEnabled: () => checkChannelEnabled('foxsports', 'fox-soccer-plus'),
id: 'fox-soccer-plus',
logo: 'https://tmsimg.fancybits.co/assets/s66880_ll_h15_aa.png?w=360&h=270',
name: 'FOX Soccer Plus',
@@ -132,7 +138,8 @@ export const CHANNELS = {
tvgName: 'FSCPLHD',
},
20: {
- canUse: useParamount.cbsSportsHq,
+ canUse: undefined,
+ checkChannelEnabled: () => checkChannelEnabled('paramount', 'cbssportshq'),
id: 'cbssportshq',
logo: 'https://tmsimg.fancybits.co/assets/s108919_ll_h15_aa.png?w=360&h=270',
name: 'CBS Sports HQ',
@@ -140,7 +147,8 @@ export const CHANNELS = {
tvgName: 'CBSSPHQ',
},
21: {
- canUse: useParamount.golazo,
+ canUse: undefined,
+ checkChannelEnabled: () => checkChannelEnabled('paramount', 'golazo'),
id: 'golazo',
logo: 'https://tmsimg.fancybits.co/assets/s133691_ll_h15_aa.png?w=360&h=270',
name: 'GOLAZO Network',
@@ -172,7 +180,12 @@ export const CHANNELS = {
tvgName: 'NFLDC1',
},
40: {
- canUse: useMLBtv,
+ canUse: undefined,
+ checkChannelEnabled: async (): Promise => {
+ const {linear_channels, meta} = await db.providers.findOne({name: 'mlbtv'});
+
+ return linear_channels[0].enabled && !meta.onlyFree;
+ },
id: 'MLBTVBI',
logo: 'https://tmsimg.fancybits.co/assets/s119153_ll_h15_aa.png?w=360&h=270',
name: 'MLB Big Inning',
@@ -180,7 +193,8 @@ export const CHANNELS = {
tvgName: 'MLBTVBI',
},
50: {
- canUse: useNesn,
+ canUse: undefined,
+ checkChannelEnabled: () => checkChannelEnabled('nesn', 'NESN'),
id: 'NESN',
logo: 'https://tmsimg.fancybits.co/assets/s35038_ll_h15_ac.png?w=360&h=270',
name: 'New England Sports Network HD',
@@ -188,7 +202,8 @@ export const CHANNELS = {
tvgName: 'NESNHD',
},
51: {
- canUse: useNesn,
+ canUse: undefined,
+ checkChannelEnabled: () => checkChannelEnabled('nesn', 'NESN+'),
id: 'NESN+',
logo: 'https://tmsimg.fancybits.co/assets/s63198_ll_h15_ac.png?w=360&h=270',
name: 'New England Sports Network Plus HD',
diff --git a/services/database.ts b/services/database.ts
index 69794f1..bb2c6e6 100644
--- a/services/database.ts
+++ b/services/database.ts
@@ -6,6 +6,7 @@ import {configPath} from './config';
export const entriesDb = path.join(configPath, 'entries.db');
export const scheduleDb = path.join(configPath, 'schedule.db');
+export const providersDb = path.join(configPath, 'providers.db');
export interface IDocument {
_id: string;
@@ -13,8 +14,10 @@ export interface IDocument {
export const db = {
entries: Datastore.create(entriesDb),
+ providers: Datastore.create(providersDb),
schedule: Datastore.create(scheduleDb),
};
export const initializeEntries = (): void => fs.writeFileSync(entriesDb, '');
export const initializeSchedule = (): void => fs.writeFileSync(scheduleDb, '');
+export const initializeProviders = (): void => fs.writeFileSync(providersDb, '');
diff --git a/services/flo-handler.ts b/services/flo-handler.ts
index 48efef1..2a34739 100644
--- a/services/flo-handler.ts
+++ b/services/flo-handler.ts
@@ -7,7 +7,7 @@ import moment from 'moment';
import {floSportsUserAgent} from './user-agent';
import {configPath} from './config';
import {useFloSports} from './networks';
-import {IEntry, IHeaders} from './shared-interfaces';
+import {ClassTypeWithoutMethods, IEntry, IHeaders, IProvider} from './shared-interfaces';
import {db} from './database';
import {getRandomUUID} from './shared-helpers';
@@ -83,6 +83,8 @@ const parseAirings = async (events: IFloEvent[]) => {
}
};
+const floSportsConfigPath = path.join(configPath, 'flo_tokens.json');
+
class FloSportsHandler {
public access_token?: string;
public refresh_token?: string;
@@ -91,25 +93,50 @@ class FloSportsHandler {
public device_id?: string;
public initialize = async () => {
- if (!useFloSports) {
- return;
- }
+ const setup = (await db.providers.count({name: 'flosports'})) > 0 ? true : false;
- // Load tokens from local file and make sure they are valid
- this.load();
+ if (!setup) {
+ const data: TFloSportsTokens = {};
+
+ if (useFloSports) {
+ this.loadJSON();
+
+ data.access_token = this.access_token;
+ data.expires_at = this.expires_at;
+ data.device_id = this.device_id;
+ data.refresh_token = this.refresh_token;
+ data.refresh_expires_at = this.refresh_expires_at;
+ }
+
+ await db.providers.insert>({
+ enabled: useFloSports,
+ name: 'flosports',
+ tokens: data,
+ });
- if (!this.device_id) {
- this.device_id = getRandomUUID();
- this.save();
+ if (fs.existsSync(floSportsConfigPath)) {
+ fs.rmSync(floSportsConfigPath);
+ }
+ }
+
+ if (useFloSports) {
+ console.log('Using FLOSPORTS variable is no longer needed. Please use the UI going forward');
}
- if (!this.expires_at || !this.access_token) {
- await this.startProviderAuthFlow();
+ const {enabled} = await db.providers.findOne>({name: 'flosports'});
+
+ if (!enabled) {
+ return;
}
+
+ // Load tokens from local file and make sure they are valid
+ await this.load();
};
public refreshTokens = async () => {
- if (!useFloSports) {
+ const {enabled} = await db.providers.findOne>({name: 'flosports'});
+
+ if (!enabled) {
return;
}
@@ -119,7 +146,9 @@ class FloSportsHandler {
};
public getSchedule = async (): Promise => {
- if (!useFloSports) {
+ const {enabled} = await db.providers.findOne>({name: 'flosports'});
+
+ if (!enabled) {
return;
}
@@ -231,14 +260,16 @@ class FloSportsHandler {
this.expires_at = data.exp * 1000;
this.refresh_token = data.refresh_token;
this.refresh_expires_at = data.refresh_token_exp * 1000;
- this.save();
+ await this.save();
} catch (e) {
console.error(e);
console.log('Could not extend token for FloSports');
}
};
- private startProviderAuthFlow = async (): Promise => {
+ public getAuthCode = async (): Promise => {
+ this.device_id = getRandomUUID();
+
try {
const url = ['https://', 'api.flosports.tv', '/api', '/activation-codes', '/new'].join('');
@@ -252,47 +283,14 @@ class FloSportsHandler {
},
);
- const code = data.activation_code;
-
- console.log('=== FloSports Auth ===');
- console.log('Please open a browser window and go to: https://www.flolive.tv/activate');
- console.log('Enter code: ', code);
- console.log('App will continue when login has completed...');
-
- return new Promise(async (resolve, reject) => {
- // Reg code expires in 5 minutes
- const maxNumOfReqs = 30;
-
- let numOfReqs = 0;
-
- const authenticate = async () => {
- if (numOfReqs < maxNumOfReqs) {
- const res = await this.authenticateRegCode(code);
- numOfReqs += 1;
-
- if (res) {
- clearInterval(regInterval);
- resolve();
- }
- } else {
- clearInterval(regInterval);
- reject();
- }
- };
-
- const regInterval = setInterval(() => {
- authenticate();
- }, 10 * 1000);
-
- await authenticate();
- });
+ return data.activation_code;
} catch (e) {
console.error(e);
console.log('Could not start the authentication process for Fox Sports!');
}
};
- private authenticateRegCode = async (code: string): Promise => {
+ public authenticateRegCode = async (code: string): Promise => {
try {
const url = ['https://', 'api.flosports.tv', '/api', '/activation-codes/', code].join('');
@@ -310,7 +308,7 @@ class FloSportsHandler {
this.expires_at = data.exp * 1000;
this.refresh_token = data.refresh_token;
this.refresh_expires_at = data.refresh_token_exp * 1000;
- this.save();
+ await this.save();
return true;
} catch (e) {
@@ -318,15 +316,25 @@ class FloSportsHandler {
}
};
- private save = () => {
- fsExtra.writeJSONSync(path.join(configPath, 'flo_tokens.json'), this, {spaces: 2});
+ private save = async () => {
+ await db.providers.update({name: 'flosports'}, {$set: {tokens: this}});
};
- private load = () => {
- if (fs.existsSync(path.join(configPath, 'flo_tokens.json'))) {
- const {device_id, access_token, expires_at, refresh_token, refresh_expires_at} = fsExtra.readJSONSync(
- path.join(configPath, 'flo_tokens.json'),
- );
+ private load = async () => {
+ const {tokens} = await db.providers.findOne>({name: 'flosports'});
+ const {device_id, access_token, expires_at, refresh_token, refresh_expires_at} = tokens;
+
+ this.device_id = device_id;
+ this.access_token = access_token;
+ this.expires_at = expires_at;
+ this.refresh_token = refresh_token;
+ this.refresh_expires_at = refresh_expires_at;
+ };
+
+ private loadJSON = () => {
+ if (fs.existsSync(floSportsConfigPath)) {
+ const {device_id, access_token, expires_at, refresh_token, refresh_expires_at} =
+ fsExtra.readJSONSync(floSportsConfigPath);
this.device_id = device_id;
this.access_token = access_token;
@@ -337,4 +345,6 @@ class FloSportsHandler {
};
}
+export type TFloSportsTokens = ClassTypeWithoutMethods;
+
export const floSportsHandler = new FloSportsHandler();
diff --git a/services/fox-handler.ts b/services/fox-handler.ts
index b3a8987..e315893 100644
--- a/services/fox-handler.ts
+++ b/services/fox-handler.ts
@@ -8,9 +8,9 @@ import moment from 'moment';
import {androidFoxUserAgent, userAgent} from './user-agent';
import {configPath} from './config';
import {useFoxOnly4k, useFoxSports} from './networks';
-import {IAdobeAuthFox, isAdobeFoxTokenValid} from './adobe-helpers';
+import {IAdobeAuthFox} from './adobe-helpers';
import {getRandomHex} from './shared-helpers';
-import {IEntry, IHeaders} from './shared-interfaces';
+import {ClassTypeWithoutMethods, IEntry, IHeaders, IProvider} from './shared-interfaces';
import {db} from './database';
import {useLinear} from './channels';
@@ -85,14 +85,21 @@ interface IFoxEventsData {
};
}
-const getMaxRes = _.memoize(() => {
- switch (process.env.MAX_RESOLUTION) {
+interface IFoxMeta {
+ only4k?: boolean;
+ uhd?: boolean;
+}
+
+const foxConfigPath = path.join(configPath, 'fox_tokens.json');
+
+const getMaxRes = (res: string) => {
+ switch (res) {
case 'UHD/HDR':
return 'UHD/HDR';
default:
return '720p';
}
-});
+};
const parseCategories = (event: IFoxEvent) => {
const categories = ['FOX Sports', 'FOX'];
@@ -117,6 +124,8 @@ const parseAirings = async (events: IFoxEvent[]) => {
const now = moment();
const inTwoDays = moment().add(2, 'days').endOf('day');
+ const {meta} = await db.providers.findOne>({name: 'foxsports'});
+
for (const event of events) {
const entryExists = await db.entries.findOne({id: event.id});
@@ -135,7 +144,7 @@ const parseAirings = async (events: IFoxEvent[]) => {
const categories = parseCategories(event);
- if (useFoxOnly4k && !_.some(categories, category => category === '4K')) {
+ if (meta.only4k && !_.some(categories, category => category === '4K')) {
continue;
}
@@ -163,8 +172,6 @@ const parseAirings = async (events: IFoxEvent[]) => {
}
};
-const maxRes = getMaxRes();
-
const FOX_APP_CONFIG = 'https://config.foxdcg.com/foxsports/androidtv-native/3.42/info.json';
// Will prelim token expire in the next month?
@@ -191,40 +198,86 @@ class FoxHandler {
private appConfig: IAppConfig;
public initialize = async () => {
- if (!useFoxSports) {
- return;
- }
+ const setup = (await db.providers.count({name: 'foxsports'})) > 0 ? true : false;
- // Load tokens from local file and make sure they are valid
- this.load();
+ if (!setup) {
+ const data: TFoxTokens = {};
- if (!this.adobe_device_id) {
- this.adobe_device_id = _.take(getRandomHex(), 16).join('');
- this.save();
- }
+ if (useFoxSports) {
+ this.loadJSON();
- if (!this.appConfig) {
- await this.getAppConfig();
- }
+ data.adobe_auth = this.adobe_auth;
+ data.adobe_device_id = this.adobe_device_id;
+ data.adobe_prelim_auth_token = this.adobe_prelim_auth_token;
+ }
- if (!this.adobe_prelim_auth_token || !this.adobe_prelim_auth_token?.accessToken) {
- await this.getPrelimToken();
+ await db.providers.insert>({
+ enabled: useFoxSports,
+ linear_channels: [
+ {
+ enabled: true,
+ id: 'fs1',
+ name: 'FS1',
+ tmsId: '82547',
+ },
+ {
+ enabled: true,
+ id: 'fs2',
+ name: 'FS2',
+ tmsId: '59305',
+ },
+ {
+ enabled: true,
+ id: 'btn',
+ name: 'B1G Network',
+ tmsId: '58321',
+ },
+ {
+ enabled: true,
+ id: 'fox-soccer-plus',
+ name: 'FOX Soccer Plus',
+ tmsId: '66880',
+ },
+ ],
+ meta: {
+ only4k: useFoxOnly4k,
+ uhd: getMaxRes(process.env.MAX_RESOLUTION) === 'UHD/HDR',
+ },
+ name: 'foxsports',
+ tokens: data,
+ });
+
+ if (fs.existsSync(foxConfigPath)) {
+ fs.rmSync(foxConfigPath);
+ }
}
- if (!isAdobeFoxTokenValid(this.adobe_auth)) {
- await this.startProviderAuthFlow();
+ if (useFoxSports) {
+ console.log('Using FOXSPORTS variable is no longer needed. Please use the UI going forward');
+ }
+ if (useFoxOnly4k) {
+ console.log('Using FOX_ONLY_4K variable is no longer needed. Please use the UI going forward');
+ }
+ if (process.env.MAX_RESOLUTION) {
+ console.log('Using MAX_RESOLUTION variable is no longer needed. Please use the UI going forward');
}
- if (willAuthTokenExpire(this.adobe_auth)) {
- console.log('Refreshing TV Provider token (FOX Sports)');
- await this.authenticateRegCode();
+ const {enabled} = await db.providers.findOne({name: 'foxsports'});
+
+ if (!enabled) {
+ return;
}
+ // Load tokens from local file and make sure they are valid
+ await this.load();
+
await this.getEntitlements();
};
public refreshTokens = async () => {
- if (!useFoxSports) {
+ const {enabled} = await db.providers.findOne({name: 'foxsports'});
+
+ if (!enabled) {
return;
}
@@ -240,7 +293,9 @@ class FoxHandler {
};
public getSchedule = async (): Promise => {
- if (!useFoxSports) {
+ const {enabled} = await db.providers.findOne({name: 'foxsports'});
+
+ if (!enabled) {
return;
}
@@ -298,9 +353,12 @@ class FoxHandler {
};
private getSteamData = async (eventId: string): Promise => {
+ const {meta} = await db.providers.findOne>({name: 'foxsports'});
+ const {uhd} = meta;
+
const streamOrder = ['UHD/HDR', '720p'];
- let resIndex = streamOrder.findIndex(i => i === maxRes);
+ let resIndex = streamOrder.findIndex(i => i === getMaxRes(uhd ? 'UHD/HDR' : ''));
if (resIndex < 0) {
resIndex = 1;
@@ -458,19 +516,24 @@ class FoxHandler {
);
this.adobe_prelim_auth_token = data;
- this.save();
+ await this.save();
} catch (e) {
console.error(e);
console.log('Could not get information to start Fox Sports login flow');
}
};
- private startProviderAuthFlow = async (): Promise => {
- try {
- if (!this.appConfig) {
- await this.getAppConfig();
- }
+ public getAuthCode = async (): Promise => {
+ this.adobe_device_id = _.take(getRandomHex(), 16).join('');
+ this.adobe_auth = undefined;
+
+ if (!this.appConfig) {
+ await this.getAppConfig();
+ }
+
+ await this.getPrelimToken();
+ try {
const {data} = await axios.post(
this.appConfig.api.auth.accountRegCode,
{
@@ -487,45 +550,14 @@ class FoxHandler {
},
);
- console.log('=== TV Provider Auth ===');
- console.log('Please open a browser window and go to: https://go.foxsports.com');
- console.log('Enter code: ', data.code);
- console.log('App will continue when login has completed...');
-
- return new Promise(async (resolve, reject) => {
- // Reg code expires in 5 minutes
- const maxNumOfReqs = 30;
-
- let numOfReqs = 0;
-
- const authenticate = async () => {
- if (numOfReqs < maxNumOfReqs) {
- const res = await this.authenticateRegCode(false);
- numOfReqs += 1;
-
- if (res) {
- clearInterval(regInterval);
- resolve();
- }
- } else {
- clearInterval(regInterval);
- reject();
- }
- };
-
- const regInterval = setInterval(() => {
- authenticate();
- }, 10 * 1000);
-
- await authenticate();
- });
+ return data.code;
} catch (e) {
console.error(e);
console.log('Could not start the authentication process for Fox Sports!');
}
};
- private authenticateRegCode = async (showAuthnError = true): Promise => {
+ public authenticateRegCode = async (showAuthnError = true): Promise => {
try {
if (!this.appConfig) {
await this.getAppConfig();
@@ -543,7 +575,9 @@ class FoxHandler {
});
this.adobe_auth = data;
- this.save();
+ await this.save();
+
+ await this.getEntitlements();
return true;
} catch (e) {
@@ -563,17 +597,22 @@ class FoxHandler {
}
};
- private save = () => {
- fsExtra.writeJSONSync(path.join(configPath, 'fox_tokens.json'), _.omit(this, 'appConfig', 'entitlements'), {
- spaces: 2,
- });
+ private save = async () => {
+ await db.providers.update({name: 'foxsports'}, {$set: {tokens: _.omit(this, 'appConfig', 'entitlements')}});
};
- private load = () => {
- if (fs.existsSync(path.join(configPath, 'fox_tokens.json'))) {
- const {adobe_device_id, adobe_auth, adobe_prelim_auth_token} = fsExtra.readJSONSync(
- path.join(configPath, 'fox_tokens.json'),
- );
+ private load = async (): Promise => {
+ const {tokens} = await db.providers.findOne>({name: 'foxsports'});
+ const {adobe_device_id, adobe_auth, adobe_prelim_auth_token} = tokens;
+
+ this.adobe_device_id = adobe_device_id;
+ this.adobe_auth = adobe_auth;
+ this.adobe_prelim_auth_token = adobe_prelim_auth_token;
+ };
+
+ private loadJSON = () => {
+ if (fs.existsSync(foxConfigPath)) {
+ const {adobe_device_id, adobe_auth, adobe_prelim_auth_token} = fsExtra.readJSONSync(foxConfigPath);
this.adobe_device_id = adobe_device_id;
this.adobe_auth = adobe_auth;
@@ -582,4 +621,6 @@ class FoxHandler {
};
}
+export type TFoxTokens = ClassTypeWithoutMethods;
+
export const foxHandler = new FoxHandler();
diff --git a/services/generate-m3u.ts b/services/generate-m3u.ts
index 1a73a23..6dd964a 100644
--- a/services/generate-m3u.ts
+++ b/services/generate-m3u.ts
@@ -2,19 +2,27 @@ import _ from 'lodash';
import {NUM_OF_CHANNELS, START_CHANNEL, CHANNELS, LINEAR_START_CHANNEL} from './channels';
-export const generateM3u = (uri: string, linear = false): string => {
+export const generateM3u = async (uri: string, linear = false): Promise => {
let m3uFile = '#EXTM3U';
if (linear) {
- _.forOwn(CHANNELS.MAP, (val, key) => {
- if (!val.canUse) {
- return;
+ for (const key in CHANNELS.MAP) {
+ const val = CHANNELS.MAP[key];
+
+ if (val.checkChannelEnabled) {
+ const enabled = await val.checkChannelEnabled();
+
+ if (!enabled) {
+ continue;
+ }
+ } else if (!val.canUse) {
+ continue;
}
const channelNum = parseInt(key, 10) + LINEAR_START_CHANNEL;
m3uFile = `${m3uFile}\n#EXTINF:0 tvg-id="${channelNum}.eplustv" channel-id="${val.name}" channel-number="${channelNum}" tvg-chno="${channelNum}" tvg-name="${val.tvgName}" tvc-guide-stationid="${val.stationId}" group-title="EPlusTV", ${val.name}`;
m3uFile = `${m3uFile}\n${uri}/channels/${channelNum}.m3u8\n`;
- });
+ }
} else {
_.times(NUM_OF_CHANNELS, i => {
const channelNum = START_CHANNEL + i;
diff --git a/services/generate-xmltv.ts b/services/generate-xmltv.ts
index 1a05651..02819b2 100644
--- a/services/generate-xmltv.ts
+++ b/services/generate-xmltv.ts
@@ -51,9 +51,17 @@ export const generateXml = async (linear = false): Promise => {
};
if (linear) {
- _.forOwn(CHANNELS.MAP, (val, key) => {
- if (!val.canUse) {
- return;
+ for (const key in CHANNELS.MAP) {
+ const val = CHANNELS.MAP[key];
+
+ if (val.checkChannelEnabled) {
+ const enabled = await val.checkChannelEnabled();
+
+ if (!enabled) {
+ continue;
+ }
+ } else if (!val.canUse) {
+ continue;
}
const channelNum = parseInt(key, 10) + LINEAR_START_CHANNEL;
@@ -86,7 +94,7 @@ export const generateXml = async (linear = false): Promise => {
},
],
});
- });
+ }
} else {
_.times(NUM_OF_CHANNELS, i => {
const channelNum = START_CHANNEL + i;
diff --git a/services/init-directories.ts b/services/init-directories.ts
index fc15f4e..859444f 100644
--- a/services/init-directories.ts
+++ b/services/init-directories.ts
@@ -1,7 +1,14 @@
import fs from 'fs';
import {configPath} from './config';
-import {entriesDb, initializeEntries, initializeSchedule, scheduleDb} from './database';
+import {
+ entriesDb,
+ initializeEntries,
+ initializeSchedule,
+ scheduleDb,
+ initializeProviders,
+ providersDb,
+} from './database';
export const initDirectories = (): void => {
if (!fs.existsSync(configPath)) {
@@ -15,4 +22,8 @@ export const initDirectories = (): void => {
if (!fs.existsSync(scheduleDb)) {
initializeSchedule();
}
+
+ if (!fs.existsSync(providersDb)) {
+ initializeProviders();
+ }
};
diff --git a/services/mlb-handler.ts b/services/mlb-handler.ts
index 0164d4b..59240f5 100644
--- a/services/mlb-handler.ts
+++ b/services/mlb-handler.ts
@@ -7,8 +7,8 @@ import moment, {Moment} from 'moment-timezone';
import {okHttpUserAgent, userAgent, androidMlbUserAgent} from './user-agent';
import {configPath} from './config';
-import {useMLBtv} from './networks';
-import {IEntry, IHeaders} from './shared-interfaces';
+import {useMLBtv, useMLBtvOnlyFree} from './networks';
+import {ClassTypeWithoutMethods, IEntry, IHeaders, IProvider} from './shared-interfaces';
import {db} from './database';
import {useLinear} from './channels';
@@ -80,6 +80,10 @@ interface ICombinedGame {
};
}
+interface IProviderMeta {
+ onlyFree?: boolean;
+}
+
const CLIENT_ID = [
'0',
'o',
@@ -125,6 +129,9 @@ const parseAirings = async (events: ICombinedGame) => {
const now = moment();
const endDate = moment().add(2, 'days').endOf('day');
+ const {meta} = await db.providers.findOne>({name: 'mlbtv'});
+ const onlyFree = meta?.onlyFree ?? false;
+
for (const pk in events) {
if (!events[pk].feed || !events[pk].entry) {
continue;
@@ -138,7 +145,7 @@ const parseAirings = async (events: ICombinedGame) => {
const entryExists = await db.entries.findOne({id: epg.mediaId});
if (!entryExists) {
- if (process.env.MLBTV_ONLY_FREE?.toLowerCase() === 'true' && !epg.freeGame) {
+ if (onlyFree && !epg.freeGame) {
continue;
}
@@ -221,6 +228,8 @@ const COMMON_HEADERS = {
'user-agent': userAgent,
};
+const mlbConfigPath = path.join(configPath, 'mlb_tokens.json');
+
class MLBHandler {
public device_id?: string;
public refresh_token?: string;
@@ -229,21 +238,64 @@ class MLBHandler {
public session_id?: string;
public initialize = async () => {
- if (!useMLBtv) {
- return;
+ const setup = (await db.providers.count({name: 'mlbtv'})) > 0 ? true : false;
+
+ if (!setup) {
+ const data: TMLBTokens = {};
+
+ if (useMLBtv) {
+ this.loadJSON();
+
+ data.access_token = this.access_token;
+ data.device_id = this.device_id;
+ data.expires_at = this.expires_at;
+ data.refresh_token = this.refresh_token;
+ data.session_id = this.session_id;
+ }
+
+ await db.providers.insert>({
+ enabled: useMLBtv,
+ linear_channels: [
+ {
+ enabled: useMLBtv && !useMLBtvOnlyFree,
+ id: 'MLBTVBI',
+ name: 'MLB Big Inning',
+ tmsId: '119153',
+ },
+ ],
+ meta: {
+ onlyFree: useMLBtvOnlyFree,
+ },
+ name: 'mlbtv',
+ tokens: data,
+ });
+
+ if (fs.existsSync(mlbConfigPath)) {
+ fs.rmSync(mlbConfigPath);
+ }
}
- // Load tokens from local file and make sure they are valid
- this.load();
+ if (useMLBtv) {
+ console.log('Using MLBTV variable is no longer needed. Please use the UI going forward');
+ }
+ if (useMLBtvOnlyFree) {
+ console.log('Using MLBTV_ONLY_FREE variable is no longer needed. Please use the UI going forward');
+ }
- if (!this.access_token || !this.expires_at) {
- await this.startProviderAuthFlow();
- await this.refreshToken();
+ const {enabled} = await db.providers.findOne>({name: 'mlbtv'});
+
+ if (!enabled) {
+ return;
}
+
+ // Load tokens from local file and make sure they are valid
+ await this.load();
};
public refreshTokens = async () => {
- if (!useMLBtv) {
+ const {enabled} = await db.providers.findOne>({name: 'mlbtv'});
+
+ if (!enabled) {
return;
}
@@ -253,7 +305,9 @@ class MLBHandler {
};
public getSchedule = async (): Promise => {
- if (!useMLBtv) {
+ const {meta, enabled} = await db.providers.findOne>({name: 'mlbtv'});
+
+ if (!enabled) {
return;
}
@@ -287,10 +341,12 @@ class MLBHandler {
};
}
- const bigInnings = await this.getBigInnings();
-
await parseAirings(combinedEntries);
- await parseBigInnings(bigInnings);
+
+ if (!meta.onlyFree) {
+ const bigInnings = await this.getBigInnings();
+ await parseBigInnings(bigInnings);
+ }
} catch (e) {
console.error(e);
console.log('Could not parse MLB.tv events');
@@ -513,14 +569,15 @@ class MLBHandler {
this.access_token = data.access_token;
this.expires_at = moment().add(data.expires_in, 'seconds').valueOf();
this.refresh_token = data.refresh_token;
- this.save();
+
+ await this.save();
} catch (e) {
console.error(e);
console.log('Could not get refresh token for MLB.tv');
}
};
- private authenticateRegCode = async (): Promise => {
+ public authenticateRegCode = async (): Promise => {
try {
const url = 'https://ids.mlb.com/oauth2/aus1m088yK07noBfh356/v1/token';
const headers = {
@@ -542,7 +599,8 @@ class MLBHandler {
this.access_token = data.access_token;
this.expires_at = moment().add(data.expires_in, 'seconds').valueOf();
this.refresh_token = data.refresh_token;
- this.save();
+
+ await this.save();
return true;
} catch (e) {
@@ -550,7 +608,7 @@ class MLBHandler {
}
};
- private startProviderAuthFlow = async (): Promise => {
+ public getAuthCode = async (): Promise => {
try {
const url = 'https://ids.mlb.com/oauth2/aus1m088yK07noBfh356/v1/device/authorize';
const headers = {
@@ -569,40 +627,8 @@ class MLBHandler {
});
this.device_id = data.device_code;
- this.save();
-
- console.log('=== MLB.tv Auth ===');
- console.log('Please open a browser window and go to: https://ids.mlb.com/activate');
- console.log('Enter code: ', data.user_code);
- console.log('App will continue when login has completed...');
-
- return new Promise(async (resolve, reject) => {
- // Reg code expires in 5 minutes
- const maxNumOfReqs = 30;
-
- let numOfReqs = 0;
-
- const authenticate = async () => {
- if (numOfReqs < maxNumOfReqs) {
- const res = await this.authenticateRegCode();
- numOfReqs += 1;
-
- if (res) {
- clearInterval(regInterval);
- resolve();
- }
- } else {
- clearInterval(regInterval);
- reject();
- }
- };
-
- const regInterval = setInterval(() => {
- authenticate();
- }, 10 * 1000);
- await authenticate();
- });
+ return data.user_code;
} catch (e) {
console.error(e);
console.log('Could not start the authentication process for MLB.tv');
@@ -656,15 +682,23 @@ class MLBHandler {
}
};
- private save = () => {
- fsExtra.writeJSONSync(path.join(configPath, 'mlb_tokens.json'), this, {spaces: 2});
+ private save = async () => {
+ await db.providers.update({name: 'mlbtv'}, {$set: {tokens: this}});
};
- private load = () => {
- if (fs.existsSync(path.join(configPath, 'mlb_tokens.json'))) {
- const {device_id, access_token, expires_at, refresh_token} = fsExtra.readJSONSync(
- path.join(configPath, 'mlb_tokens.json'),
- );
+ private load = async (): Promise => {
+ const {tokens} = await db.providers.findOne>({name: 'mlbtv'});
+ const {device_id, access_token, expires_at, refresh_token} = tokens;
+
+ this.device_id = device_id;
+ this.access_token = access_token;
+ this.expires_at = expires_at;
+ this.refresh_token = refresh_token;
+ };
+
+ private loadJSON = () => {
+ if (fs.existsSync(mlbConfigPath)) {
+ const {device_id, access_token, expires_at, refresh_token} = fsExtra.readJSONSync(mlbConfigPath);
this.device_id = device_id;
this.access_token = access_token;
@@ -674,4 +708,6 @@ class MLBHandler {
};
}
+export type TMLBTokens = ClassTypeWithoutMethods;
+
export const mlbHandler = new MLBHandler();
diff --git a/services/mw-handler.ts b/services/mw-handler.ts
index f4ef044..232de8d 100644
--- a/services/mw-handler.ts
+++ b/services/mw-handler.ts
@@ -3,7 +3,7 @@ import moment from 'moment';
import {userAgent} from './user-agent';
import {useMountainWest} from './networks';
-import {IEntry, IHeaders} from './shared-interfaces';
+import {IEntry, IHeaders, IProvider} from './shared-interfaces';
import {db} from './database';
interface IMWEvent {
@@ -58,8 +58,32 @@ const parseAirings = async (events: IMWEvent[]) => {
};
class MountainWestHandler {
+ public initialize = async () => {
+ const setup = (await db.providers.count({name: 'mw'})) > 0 ? true : false;
+
+ // First time setup
+ if (!setup) {
+ await db.providers.insert({
+ enabled: useMountainWest,
+ name: 'mw',
+ });
+ }
+
+ if (useMountainWest) {
+ console.log('Using MTNWEST variable is no longer needed. Please use the UI going forward');
+ }
+
+ const {enabled} = await db.providers.findOne({name: 'mw'});
+
+ if (!enabled) {
+ return;
+ }
+ };
+
public getSchedule = async (): Promise => {
- if (!useMountainWest) {
+ const {enabled} = await db.providers.findOne({name: 'mw'});
+
+ if (!enabled) {
return;
}
diff --git a/services/nesn-handler.ts b/services/nesn-handler.ts
index 45f5e32..a629b13 100644
--- a/services/nesn-handler.ts
+++ b/services/nesn-handler.ts
@@ -9,7 +9,7 @@ import jwt_decode from 'jwt-decode';
import {okHttpUserAgent, adobeNesnUserAgent} from './user-agent';
import {configPath} from './config';
import {useNesn} from './networks';
-import {IEntry, IHeaders, IJWToken} from './shared-interfaces';
+import {ClassTypeWithoutMethods, IEntry, IHeaders, IJWToken, IProvider} from './shared-interfaces';
import {db} from './database';
import {getRandomHex, getRandomUUID} from './shared-helpers';
import {useLinear} from './channels';
@@ -262,6 +262,8 @@ const parseAirings = async (events: INesnEvent[]) => {
}
};
+const nesnConfigPath = path.join(configPath, 'nesn_tokens.json');
+
class NesnHandler {
public device_id?: string;
public adobe_device_id?: string;
@@ -277,34 +279,73 @@ class NesnHandler {
private adobe_auth_token?: string;
public initialize = async () => {
- if (!useNesn) {
- return;
- }
+ const setup = (await db.providers.count({name: 'nesn'})) > 0 ? true : false;
+
+ if (!setup) {
+ const data: TNesnTokens = {};
+
+ if (useNesn) {
+ this.loadJSON();
+
+ data.adobe_device_id = this.adobe_device_id;
+ data.authz_token = this.authz_token;
+ data.cognito_access_token = this.cognito_access_token;
+ data.cognito_expires_at = this.cognito_expires_at;
+ data.cognito_id = this.cognito_id;
+ data.cognito_id_token = this.cognito_id_token;
+ data.cognito_refresh_token = this.cognito_refresh_token;
+ data.device_id = this.device_id;
+ data.mvpd_id = this.mvpd_id;
+ data.user_id = this.user_id;
+ }
- // Load tokens from local file and make sure they are valid
- this.load();
+ await db.providers.insert>({
+ enabled: useNesn,
+ linear_channels: [
+ {
+ enabled: true,
+ id: 'NESN',
+ name: 'New England Sports Network HD',
+ tmsId: '35038',
+ },
+ {
+ enabled: true,
+ id: 'NESN+',
+ name: 'New England Sports Network Plus HD',
+ tmsId: '63516',
+ },
+ ],
+ name: 'nesn',
+ tokens: data,
+ });
- if (!this.device_id) {
- this.device_id = getRandomUUID();
- this.save();
+ if (fs.existsSync(nesnConfigPath)) {
+ fs.rmSync(nesnConfigPath);
+ }
}
- if (!this.adobe_device_id) {
- this.adobe_device_id = `${getRandomHex()}${getRandomHex()}`;
- this.save();
+ if (useNesn) {
+ console.log('Using NESN variable is no longer needed. Please use the UI going forward');
}
- if (!this.cognito_expires_at || !this.cognito_access_token) {
- await this.startProviderAuthFlow();
+ const {enabled} = await db.providers.findOne({name: 'nesn'});
+
+ if (!enabled) {
+ return;
}
+ // Load tokens from local file and make sure they are valid
+ await this.load();
+
if (!this.user_id) {
await this.getUserId();
}
};
public refreshTokens = async () => {
- if (!useNesn) {
+ const {enabled} = await db.providers.findOne({name: 'nesn'});
+
+ if (!enabled) {
return;
}
@@ -314,7 +355,9 @@ class NesnHandler {
};
public getSchedule = async (): Promise => {
- if (!useNesn) {
+ const {enabled} = await db.providers.findOne({name: 'nesn'});
+
+ if (!enabled) {
return;
}
@@ -653,7 +696,7 @@ class NesnHandler {
this.cognito_id_token = data.AuthenticationResult.IdToken;
this.cognito_expires_at = moment().add(1, 'day').valueOf();
- this.save();
+ await this.save();
} catch (e) {
console.error(e);
console.log('Could not refresh NESN token');
@@ -677,14 +720,17 @@ class NesnHandler {
);
this.user_id = data.Username;
- this.save();
+ await this.save();
} catch (e) {
console.error(e);
console.log('Could not get user ID');
}
};
- private startProviderAuthFlow = async (): Promise => {
+ public getAuthCode = async (): Promise<[string, string, string]> => {
+ this.device_id = getRandomUUID();
+ this.adobe_device_id = `${getRandomHex()}${getRandomHex()}`;
+
try {
const codeUrl = ['https://', 'nesn.com', '/wp-json/nesn/v1/device'].join('');
@@ -720,45 +766,14 @@ class NesnHandler {
const authUrl = loginInfo.secureShortURL.toLowerCase();
- console.log('=== NESN Auth ===');
- console.log(`Please open a browser window and go to: ${authUrl}`);
- console.log('MAKE SURE THAT YOU DON\'T CLICK "SKIP THIS STEP FOR NOW"');
- console.log('App will continue when login has completed...');
-
- return new Promise(async (resolve, reject) => {
- // Reg code expires in 30 minutes
- const maxNumOfReqs = 180;
-
- let numOfReqs = 0;
-
- const authenticate = async () => {
- if (numOfReqs < maxNumOfReqs) {
- const res = await this.authenticateRegCode(code, adobeReggieCode);
- numOfReqs += 1;
-
- if (res) {
- clearInterval(regInterval);
- resolve();
- }
- } else {
- clearInterval(regInterval);
- reject();
- }
- };
-
- const regInterval = setInterval(() => {
- authenticate();
- }, 10 * 1000);
-
- await authenticate();
- });
+ return [authUrl, code, adobeReggieCode];
} catch (e) {
console.error(e);
console.log('Could not start the authentication process for Fox Sports!');
}
};
- private authenticateRegCode = async (code: string, adobeReggieCode: string): Promise => {
+ public authenticateRegCode = async (code: string, adobeReggieCode: string): Promise => {
try {
const url = ['https://', 'nesn.com', '/wp-json', '/nesn/v1/device/', code].join('');
@@ -780,30 +795,60 @@ class NesnHandler {
this.mvpd_id = data.mvpdId;
this.cognito_expires_at = moment().add(1, 'day').valueOf();
- this.save();
+ await this.save();
try {
const authnToken = await this.getAdobeSessionDevice(adobeReggieCode);
this.authz_token = await this.authorizeAdobeDevice(authnToken);
- this.save();
+
+ await this.save();
} catch (e) {
console.error(e);
console.log('Could not register adobe device');
}
+ await this.getUserId();
+
return true;
} catch (e) {
- console.log(e.response.status, e.config.url);
+ // console.log(e.response.status, e.config.url);
return false;
}
};
- private save = () => {
- fsExtra.writeJSONSync(path.join(configPath, 'nesn_tokens.json'), this, {spaces: 2});
+ private save = async (): Promise => {
+ await db.providers.update({name: 'nesn'}, {$set: {tokens: this}});
};
- private load = () => {
- if (fs.existsSync(path.join(configPath, 'nesn_tokens.json'))) {
+ private load = async (): Promise => {
+ const {tokens} = await db.providers.findOne>({name: 'nesn'});
+ const {
+ cognito_access_token,
+ cognito_id,
+ cognito_refresh_token,
+ cognito_id_token,
+ cognito_expires_at,
+ mvpd_id,
+ device_id,
+ user_id,
+ adobe_device_id,
+ authz_token,
+ } = tokens;
+
+ this.device_id = device_id;
+ this.adobe_device_id = adobe_device_id;
+ this.cognito_access_token = cognito_access_token;
+ this.cognito_id = cognito_id;
+ this.cognito_refresh_token = cognito_refresh_token;
+ this.cognito_expires_at = cognito_expires_at;
+ this.cognito_id_token = cognito_id_token;
+ this.mvpd_id = mvpd_id;
+ this.user_id = user_id;
+ this.authz_token = authz_token;
+ };
+
+ private loadJSON = () => {
+ if (fs.existsSync(nesnConfigPath)) {
const {
cognito_access_token,
cognito_id,
@@ -831,4 +876,6 @@ class NesnHandler {
};
}
+export type TNesnTokens = ClassTypeWithoutMethods;
+
export const nesnHandler = new NesnHandler();
diff --git a/services/networks.ts b/services/networks.ts
index 0043192..3ed3797 100644
--- a/services/networks.ts
+++ b/services/networks.ts
@@ -14,6 +14,7 @@ export const useFoxSports = process.env.FOXSPORTS?.toLowerCase() === 'true' ? tr
export const useFoxOnly4k = process.env.FOX_ONLY_4K?.toLowerCase() === 'true' ? true : false;
export const useMLBtv = process.env.MLBTV?.toLowerCase() === 'true' ? true : false;
+export const useMLBtvOnlyFree = process.env.MLBTV_ONLY_FREE?.toLowerCase() === 'true' ? true : false;
export const useB1GPlus = process.env.B1GPLUS?.toLowerCase() === 'true' ? true : false;
diff --git a/services/paramount-handler.ts b/services/paramount-handler.ts
index 50171c4..410198a 100644
--- a/services/paramount-handler.ts
+++ b/services/paramount-handler.ts
@@ -10,7 +10,7 @@ import {configPath} from './config';
import {useParamount} from './networks';
import {getRandomHex} from './shared-helpers';
import {db} from './database';
-import {IEntry, IHeaders} from './shared-interfaces';
+import {ClassTypeWithoutMethods, IEntry, IHeaders, IProvider} from './shared-interfaces';
import {useLinear} from './channels';
const BASE_THUMB_URL = 'https://wwwimage-us.pplusstatic.com/thumbnails/photos/w370-q80/';
@@ -126,6 +126,8 @@ interface IChannel {
local: boolean;
}
+const paramountConfigPath = path.join(configPath, 'paramount_tokens.json');
+
const ALLOWED_LOCAL_SPORTS = ['College Basketball', 'College Football', 'NFL Football', 'Super Bowl LVIII'];
const parseAirings = async (events: IParamountEvent[]) => {
@@ -188,40 +190,78 @@ class ParamountHandler {
private dma: IDma;
public initialize = async () => {
- if (!useParamount.plus || isParamountDisabled) {
- return;
- }
+ const setup = (await db.providers.count({name: 'paramount'})) > 0 ? true : false;
- // Load tokens from local file and make sure they are valid
- this.load();
+ if (!setup) {
+ const data: TParamountTokens = {};
- if (!this.device_id || !this.hashed_token) {
- this.device_id = _.take(getRandomHex(), 16).join('');
- this.hashed_token = crypto
- .createHmac('sha1', 'eplustv')
- .update(this.device_id)
- .digest()
- .toString('base64')
- .substring(0, 16);
+ if (useParamount.plus) {
+ this.loadJSON();
- this.save();
+ data.cookies = this.cookies;
+ data.device_id = this.device_id;
+ data.expires = this.expires;
+ data.hashed_token = this.hashed_token;
+ data.profileId = this.profileId;
+ }
+
+ await db.providers.insert>({
+ enabled: useParamount.plus,
+ linear_channels: [
+ {
+ enabled: useParamount.cbsSportsHq,
+ id: 'cbssportshq',
+ name: 'CBS Sports HQ',
+ tmsId: '108919',
+ },
+ {
+ enabled: useParamount.golazo,
+ id: 'golazo',
+ name: 'GOLAZO Network',
+ tmsId: '133691',
+ },
+ ],
+ name: 'paramount',
+ tokens: data,
+ });
+
+ if (fs.existsSync(paramountConfigPath)) {
+ fs.rmSync(paramountConfigPath);
+ }
}
- if (!this.cookies || !this.expires || moment().valueOf() >= this.expires) {
- await this.startProviderAuthFlow();
+ if (useParamount.plus) {
+ console.log('Using PARAMOUNTPLUS variable is no longer needed. Please use the UI going forward');
+ }
+ if (useParamount.golazo) {
+ console.log('Using GOLAZO variable is no longer needed. Please use the UI going forward');
+ }
+ if (useParamount.cbsSportsHq) {
+ console.log('Using CBSSPORTSHQ variable is no longer needed. Please use the UI going forward');
}
- if (!this.profileId) {
- await this.getUserProfile();
+ const {enabled} = await db.providers.findOne({name: 'paramount'});
+
+ if (!enabled || isParamountDisabled) {
+ return;
}
+ // Load tokens from local file and make sure they are valid
+ await this.load();
+
if (!this.appConfig) {
await this.getAppConfig();
}
+
+ if (!this.profileId) {
+ await this.getUserProfile();
+ }
};
public refreshTokens = async () => {
- if (!useParamount.plus || isParamountDisabled) {
+ const {enabled} = await db.providers.findOne({name: 'paramount'});
+
+ if (!enabled || isParamountDisabled) {
return;
}
@@ -231,7 +271,9 @@ class ParamountHandler {
};
public getSchedule = async () => {
- if (!useParamount.plus || isParamountDisabled) {
+ const {enabled} = await db.providers.findOne({name: 'paramount'});
+
+ if (!enabled || isParamountDisabled) {
return;
}
@@ -406,21 +448,26 @@ class ParamountHandler {
const channels: IChannel[] = [];
- data.carousel.forEach(c => {
+ for (const c of data.carousel) {
if (c.local) {
channels.push(c);
}
if (useLinear) {
- if (useParamount.cbsSportsHq && c.channelName === 'CBS Sports HQ') {
+ const {linear_channels} = await db.providers.findOne({name: 'paramount'});
+
+ const useCbsSportsHq = linear_channels.find(c => c.id === 'cbssportshq');
+ const useGolazo = linear_channels.find(c => c.id === 'golazo');
+
+ if (useCbsSportsHq && c.channelName === 'CBS Sports HQ') {
channels.push(c);
}
- if (useParamount.golazo && c.channelName === 'CBS Sports Golazo Network') {
+ if (useGolazo && c.channelName === 'CBS Sports Golazo Network') {
channels.push(c);
}
}
- });
+ }
return channels;
} catch (e) {
@@ -587,7 +634,15 @@ class ParamountHandler {
}
};
- private startProviderAuthFlow = async (): Promise => {
+ public getAuthCode = async (): Promise<[string, string]> => {
+ this.device_id = _.take(getRandomHex(), 16).join('');
+ this.hashed_token = crypto
+ .createHmac('sha1', 'eplustv')
+ .update(this.device_id)
+ .digest()
+ .toString('base64')
+ .substring(0, 16);
+
try {
const {data} = await instance.post(
`/apps-api/v2.0/androidtv/ott/auth/code.json?${new URLSearchParams({
@@ -596,45 +651,14 @@ class ParamountHandler {
}).toString()}`,
);
- console.log('=== TV Provider Auth ===');
- console.log('Please open a browser window and go to: https://www.paramountplus.com/activate/androidtv');
- console.log('Enter code: ', data.activationCode);
- console.log('App will continue when login has completed...');
-
- return new Promise(async (resolve, reject) => {
- // Reg code expires in 5 minutes
- const maxNumOfReqs = 30;
-
- let numOfReqs = 0;
-
- const authenticate = async () => {
- if (numOfReqs < maxNumOfReqs) {
- const res = await this.authenticateRegCode(data.activationCode, data.deviceToken);
- numOfReqs += 1;
-
- if (res) {
- clearInterval(regInterval);
- resolve();
- }
- } else {
- clearInterval(regInterval);
- reject();
- }
- };
-
- const regInterval = setInterval(() => {
- authenticate();
- }, 10 * 1000);
-
- await authenticate();
- });
+ return [data.activationCode, data.deviceToken];
} catch (e) {
console.error(e);
console.log('Could not start the authentication process for Paramount+!');
}
};
- private authenticateRegCode = async (activationCode: string, deviceToken: string): Promise => {
+ public authenticateRegCode = async (activationCode: string, deviceToken: string): Promise => {
const regUrl = [
'/apps-api/v2.0/androidtv/ott/auth/status.json?',
new URLSearchParams({
@@ -654,6 +678,14 @@ class ParamountHandler {
this.saveCookies(headers['set-cookie']);
+ if (!this.appConfig) {
+ await this.getAppConfig();
+ }
+
+ if (!this.profileId) {
+ await this.getUserProfile();
+ }
+
return true;
} catch (e) {
return false;
@@ -666,17 +698,23 @@ class ParamountHandler {
this.save();
};
- private save = () => {
- fsExtra.writeJSONSync(path.join(configPath, 'paramount_tokens.json'), _.omit(this, 'appConfig', 'ip', 'dma'), {
- spaces: 2,
- });
+ private save = async () => {
+ await db.providers.update({name: 'paramount'}, {$set: {tokens: _.omit(this, 'appConfig', 'ip', 'dma')}});
};
- private load = () => {
- if (fs.existsSync(path.join(configPath, 'paramount_tokens.json'))) {
- const {device_id, hashed_token, cookies, expires, profileId} = fsExtra.readJSONSync(
- path.join(configPath, 'paramount_tokens.json'),
- );
+ private load = async (): Promise => {
+ const {tokens} = await db.providers.findOne>({name: 'paramount'});
+ const {device_id, hashed_token, cookies, expires} = tokens || {};
+
+ this.device_id = device_id;
+ this.hashed_token = hashed_token;
+ this.cookies = cookies;
+ this.expires = expires;
+ };
+
+ private loadJSON = () => {
+ if (fs.existsSync(paramountConfigPath)) {
+ const {device_id, hashed_token, cookies, expires, profileId} = fsExtra.readJSONSync(paramountConfigPath);
this.device_id = device_id;
this.hashed_token = hashed_token;
@@ -687,4 +725,6 @@ class ParamountHandler {
};
}
+export type TParamountTokens = ClassTypeWithoutMethods;
+
export const paramountHandler = new ParamountHandler();
diff --git a/services/providers/b1g/index.tsx b/services/providers/b1g/index.tsx
new file mode 100644
index 0000000..c2ff71b
--- /dev/null
+++ b/services/providers/b1g/index.tsx
@@ -0,0 +1,63 @@
+import {Hono} from 'hono';
+
+import { db } from '@/services/database';
+
+import { Login } from './views/Login';
+import { B1GBody } from './views/CardBody';
+
+import { IProvider } from '@/services/shared-interfaces';
+import { removeEntriesProvider, scheduleEntries } from '@/services/build-schedule';
+import { b1gHandler, TB1GTokens } from '@/services/b1g-handler';
+
+export const b1g = new Hono().basePath('/b1g');
+
+const scheduleEvents = async () => {
+ await b1gHandler.getSchedule();
+ await scheduleEntries();
+};
+
+const removeEvents = async () => {
+ await removeEntriesProvider('b1g+');
+};
+
+b1g.put('/toggle', async c => {
+ const body = await c.req.parseBody();
+ const enabled = body['b1g-enabled'] === 'on';
+
+ if (!enabled) {
+ await db.providers.update({name: 'b1g'}, {$set: {enabled, tokens: {}}});
+ removeEvents();
+
+ return c.html(<>>);
+ }
+
+ return c.html();
+});
+
+b1g.post('/login', async c => {
+ const body = await c.req.parseBody();
+ const username = body.username as string;
+ const password = body.password as string;
+
+ const isAuthenticated = await b1gHandler.login(username, password);
+
+ if (!isAuthenticated) {
+ return c.html();
+ }
+
+ const {tokens} = await db.providers.update>({name: 'b1g'}, {$set: {enabled: true, meta: {
+ password,
+ username,
+ }}}, {returnUpdatedDocs: true});
+
+ // Kickoff event scheduler
+ scheduleEvents();
+
+ return c.html(, 200, {
+ 'HX-Trigger': `{"HXToast":{"type":"success","body":"Successfully enabled B1G+"}}`,
+ });
+});
+
+b1g.put('/reauth', async c => {
+ return c.html();
+});
diff --git a/services/providers/b1g/views/CardBody.tsx b/services/providers/b1g/views/CardBody.tsx
new file mode 100644
index 0000000..f4918c3
--- /dev/null
+++ b/services/providers/b1g/views/CardBody.tsx
@@ -0,0 +1,34 @@
+import {FC} from 'hono/jsx';
+
+import { TB1GTokens } from '@/services/b1g-handler';
+
+interface IB1GBodyProps {
+ enabled: boolean;
+ tokens?: TB1GTokens;
+ open?: boolean;
+}
+
+export const B1GBody: FC = ({enabled, tokens, open}) => {
+ const parsedTokens = JSON.stringify(tokens, undefined, 2);
+
+ if (!enabled) {
+ return null;
+ }
+
+ return (
+
+ );
+};
diff --git a/services/providers/b1g/views/Login.tsx b/services/providers/b1g/views/Login.tsx
new file mode 100644
index 0000000..2310a8b
--- /dev/null
+++ b/services/providers/b1g/views/Login.tsx
@@ -0,0 +1,57 @@
+import {FC} from 'hono/jsx';
+
+interface ILoginProps {
+ invalid?: boolean;
+}
+
+export const Login: FC = async ({invalid}) => {
+ return (
+
+
+
+
+ );
+};
diff --git a/services/providers/b1g/views/index.tsx b/services/providers/b1g/views/index.tsx
new file mode 100644
index 0000000..0885fcd
--- /dev/null
+++ b/services/providers/b1g/views/index.tsx
@@ -0,0 +1,42 @@
+import {FC} from 'hono/jsx';
+
+import { db } from '@/services/database';
+import { IProvider } from '@/services/shared-interfaces';
+import { TB1GTokens } from '@/services/b1g-handler';
+
+import { B1GBody } from './CardBody';
+
+export const B1G: FC = async () => {
+ const b1g = await db.providers.findOne>({name: 'b1g'});
+ const enabled = b1g?.enabled;
+ const tokens = b1g?.tokens;
+
+ return (
+
+ );
+};
diff --git a/services/providers/cbs-sports/index.tsx b/services/providers/cbs-sports/index.tsx
new file mode 100644
index 0000000..8d40724
--- /dev/null
+++ b/services/providers/cbs-sports/index.tsx
@@ -0,0 +1,59 @@
+import {Hono} from 'hono';
+
+import { Login } from './views/Login';
+import { CBSBody } from './views/CardBody';
+
+import { db } from '@/services/database';
+import { cbsHandler, TCBSTokens } from '@/services/cbs-handler';
+import { IProvider } from '@/services/shared-interfaces';
+import { removeEntriesProvider, scheduleEntries } from '@/services/build-schedule';
+
+export const cbs = new Hono().basePath('/cbs');
+
+const scheduleEvents = async () => {
+ await cbsHandler.getSchedule();
+ await scheduleEntries();
+};
+
+const removeEvents = async () => {
+ await removeEntriesProvider('cbssports');
+};
+
+cbs.put('/toggle', async c => {
+ const body = await c.req.parseBody();
+ const enabled = body['cbs-enabled'] === 'on';
+
+ if (!enabled) {
+ await db.providers.update({name: 'cbs'}, {$set: {enabled, tokens: {}}});
+ removeEvents();
+
+ return c.html(<>>);
+ }
+
+ return c.html(
+
+ );
+});
+
+cbs.get('/tve-login/:code', async c => {
+ const code = c.req.param('code');
+
+ const isAuthenticated = await cbsHandler.authenticateRegCode(code);
+
+ if (!isAuthenticated) {
+ return c.html();
+ }
+
+ const {tokens} = await db.providers.update>({name: 'cbs'}, {$set: {enabled: true}}, {returnUpdatedDocs: true});
+
+ // Kickoff event scheduler
+ scheduleEvents();
+
+ return c.html(, 200, {
+ 'HX-Trigger': `{"HXToast":{"type":"success","body":"Successfully enabled CBS Sports"}}`,
+ });
+});
+
+cbs.put('/reauth', async c => {
+ return c.html();
+});
diff --git a/services/providers/cbs-sports/views/CardBody.tsx b/services/providers/cbs-sports/views/CardBody.tsx
new file mode 100644
index 0000000..c82ac25
--- /dev/null
+++ b/services/providers/cbs-sports/views/CardBody.tsx
@@ -0,0 +1,34 @@
+import {FC} from 'hono/jsx';
+
+import { TCBSTokens } from '@/services/cbs-handler';
+
+interface ICBSBodyProps {
+ enabled: boolean;
+ tokens?: TCBSTokens;
+ open?: boolean;
+}
+
+export const CBSBody: FC = ({enabled, tokens, open}) => {
+ const parsedTokens = JSON.stringify(tokens, undefined, 2);
+
+ if (!enabled) {
+ return null;
+ }
+
+ return (
+
+ );
+};
diff --git a/services/providers/cbs-sports/views/Login.tsx b/services/providers/cbs-sports/views/Login.tsx
new file mode 100644
index 0000000..db3452f
--- /dev/null
+++ b/services/providers/cbs-sports/views/Login.tsx
@@ -0,0 +1,32 @@
+import {FC} from 'hono/jsx';
+
+import { cbsHandler } from '@/services/cbs-handler';
+
+interface ILogin {
+ code?: string;
+}
+
+export const Login: FC = async ({code}) => {
+ let shownCode = code;
+
+ if (!shownCode) {
+ shownCode = await cbsHandler.getAuthCode();
+ }
+
+ return (
+
+ );
+};
diff --git a/services/providers/cbs-sports/views/index.tsx b/services/providers/cbs-sports/views/index.tsx
new file mode 100644
index 0000000..997cfd8
--- /dev/null
+++ b/services/providers/cbs-sports/views/index.tsx
@@ -0,0 +1,41 @@
+import {FC} from 'hono/jsx';
+
+import { db } from '@/services/database';
+import { IProvider } from '@/services/shared-interfaces';
+import { TCBSTokens } from '@/services/cbs-handler';
+import { CBSBody } from './CardBody';
+
+export const CBSSports: FC = async () => {
+ const cbs = await db.providers.findOne>({name: 'cbs'});
+ const enabled = cbs?.enabled;
+ const tokens = cbs?.tokens || {};
+
+ return (
+
+ );
+};
diff --git a/services/providers/flosports/index.tsx b/services/providers/flosports/index.tsx
new file mode 100644
index 0000000..86c7b5b
--- /dev/null
+++ b/services/providers/flosports/index.tsx
@@ -0,0 +1,62 @@
+import {Hono} from 'hono';
+
+import { db } from '@/services/database';
+
+import { Login } from './views/Login';
+import { FloSportsBody } from './views/CardBody';
+
+import { IProvider } from '@/services/shared-interfaces';
+import { removeEntriesProvider, scheduleEntries } from '@/services/build-schedule';
+import { floSportsHandler, TFloSportsTokens } from '@/services/flo-handler';
+
+export const flosports = new Hono().basePath('/flosports');
+
+const scheduleEvents = async () => {
+ await floSportsHandler.getSchedule();
+ await scheduleEntries();
+};
+
+const removeEvents = async () => {
+ await removeEntriesProvider('flo');
+};
+
+flosports.put('/toggle', async c => {
+ const body = await c.req.parseBody();
+ const enabled = body['flosports-enabled'] === 'on';
+
+ if (!enabled) {
+ await db.providers.update({name: 'flosports'}, {$set: {enabled, tokens: {}}});
+ removeEvents();
+
+ return c.html(<>>);
+ }
+
+ return c.html();
+});
+
+flosports.get('/auth/:code', async c => {
+ const code = c.req.param('code');
+
+ const isAuthenticated = await floSportsHandler.authenticateRegCode(code);
+
+ if (!isAuthenticated) {
+ return c.html();
+ }
+
+ const {tokens} = await db.providers.update>(
+ {name: 'flosports'},
+ {$set: {enabled: true}},
+ {returnUpdatedDocs: true},
+ );
+
+ // Kickoff event scheduler
+ scheduleEvents();
+
+ return c.html(, 200, {
+ 'HX-Trigger': `{"HXToast":{"type":"success","body":"Successfully enabled FloSports"}}`,
+ });
+});
+
+flosports.put('/reauth', async c => {
+ return c.html();
+});
diff --git a/services/providers/flosports/views/CardBody.tsx b/services/providers/flosports/views/CardBody.tsx
new file mode 100644
index 0000000..1694ff7
--- /dev/null
+++ b/services/providers/flosports/views/CardBody.tsx
@@ -0,0 +1,34 @@
+import {FC} from 'hono/jsx';
+
+import { TFloSportsTokens } from '@/services/flo-handler';
+
+interface IFloSportsBodyProps {
+ enabled: boolean;
+ tokens?: TFloSportsTokens;
+ open?: boolean;
+}
+
+export const FloSportsBody: FC = ({enabled, tokens, open}) => {
+ const parsedTokens = JSON.stringify(tokens, undefined, 2);
+
+ if (!enabled) {
+ return null;
+ }
+
+ return (
+
+ );
+};
diff --git a/services/providers/flosports/views/Login.tsx b/services/providers/flosports/views/Login.tsx
new file mode 100644
index 0000000..5fe4070
--- /dev/null
+++ b/services/providers/flosports/views/Login.tsx
@@ -0,0 +1,39 @@
+import {FC} from 'hono/jsx';
+
+import { floSportsHandler } from '@/services/flo-handler';
+
+interface ILogin {
+ code?: string;
+}
+
+export const Login: FC = async ({code}) => {
+ let shownCode = code;
+
+ if (!shownCode) {
+ shownCode = await floSportsHandler.getAuthCode();
+ }
+
+ return (
+
+ );
+};
diff --git a/services/providers/flosports/views/index.tsx b/services/providers/flosports/views/index.tsx
new file mode 100644
index 0000000..68b3785
--- /dev/null
+++ b/services/providers/flosports/views/index.tsx
@@ -0,0 +1,42 @@
+import {FC} from 'hono/jsx';
+
+import { db } from '@/services/database';
+import { IProvider } from '@/services/shared-interfaces';
+import { TFloSportsTokens } from '@/services/flo-handler';
+
+import { FloSportsBody } from './CardBody';
+
+export const FloSports: FC = async () => {
+ const parmount = await db.providers.findOne>({name: 'flosports'});
+ const enabled = parmount?.enabled;
+ const tokens = parmount?.tokens;
+
+ return (
+
+ );
+};
diff --git a/services/providers/fox/index.tsx b/services/providers/fox/index.tsx
new file mode 100644
index 0000000..8056dcd
--- /dev/null
+++ b/services/providers/fox/index.tsx
@@ -0,0 +1,104 @@
+import {Hono} from 'hono';
+
+import { db } from '@/services/database';
+
+import { Login } from './views/Login';
+import { IProvider } from '@/services/shared-interfaces';
+import { removeEntriesProvider, scheduleEntries } from '@/services/build-schedule';
+import { foxHandler, TFoxTokens } from '@/services/fox-handler';
+import { FoxBody } from './views/CardBody';
+
+export const fox = new Hono().basePath('/fox');
+
+const scheduleEvents = async () => {
+ await foxHandler.getSchedule();
+ await scheduleEntries();
+};
+
+const removeEvents = async () => {
+ await removeEntriesProvider('foxsports');
+};
+
+const removeAndSchedule = async () => {
+ await removeEvents();
+ await scheduleEvents();
+};
+
+fox.put('/toggle', async c => {
+ const body = await c.req.parseBody();
+ const enabled = body['fox-enabled'] === 'on';
+
+ if (!enabled) {
+ await db.providers.update({name: 'foxsports'}, {$set: {enabled, tokens: {}}});
+ removeEvents();
+
+ return c.html(<>>);
+ }
+
+ return c.html();
+});
+
+fox.put('/toggle-4k-only', async c => {
+ const body = await c.req.parseBody();
+ const only4k = body['fox-enabled-4k-only'] === 'on';
+
+ const {meta} = await db.providers.findOne({name: 'foxsports'});
+
+ const {enabled, tokens, linear_channels} = await db.providers.update(
+ {name: 'foxsports'},
+ {
+ $set: {
+ meta: {
+ ...meta,
+ only4k,
+ },
+ },
+ },
+ {
+ returnUpdatedDocs: true,
+ },
+ );
+
+ removeAndSchedule();
+
+ return c.html();
+});
+
+fox.put('/toggle-uhd', async c => {
+ const body = await c.req.parseBody();
+ const uhd = body['fox-enabled-uhd'] === 'on';
+
+ const {meta} = await db.providers.findOne({name: 'foxsports'});
+
+ const {enabled, tokens, linear_channels} = await db.providers.update>({name: 'foxsports'}, {$set: {meta: {
+ ...meta,
+ uhd,
+ }}}, {
+ returnUpdatedDocs: true,
+ });
+
+ return c.html();
+});
+
+fox.get('/tve-login/:code', async c => {
+ const code = c.req.param('code');
+
+ const isAuthenticated = await foxHandler.authenticateRegCode(false);
+
+ if (!isAuthenticated) {
+ return c.html();
+ }
+
+ const {tokens, linear_channels} = await db.providers.update>({name: 'foxsports'}, {$set: {enabled: true}}, {returnUpdatedDocs: true});
+
+ // Kickoff event scheduler
+ scheduleEvents();
+
+ return c.html(, 200, {
+ 'HX-Trigger': `{"HXToast":{"type":"success","body":"Successfully enabled Fox Sports"}}`,
+ });
+});
+
+fox.put('/reauth', async c => {
+ return c.html();
+});
diff --git a/services/providers/fox/views/CardBody.tsx b/services/providers/fox/views/CardBody.tsx
new file mode 100644
index 0000000..f7f377b
--- /dev/null
+++ b/services/providers/fox/views/CardBody.tsx
@@ -0,0 +1,58 @@
+import {FC} from 'hono/jsx';
+
+import { TFoxTokens } from '@/services/fox-handler';
+import { IProviderChannel } from '@/services/shared-interfaces';
+
+interface IFoxBodyProps {
+ enabled: boolean;
+ tokens?: TFoxTokens;
+ open?: boolean;
+ channels: IProviderChannel[];
+}
+
+export const FoxBody: FC = ({enabled, tokens, open, channels}) => {
+ const parsedTokens = JSON.stringify(tokens, undefined, 2);
+
+ if (!enabled) {
+ return null;
+ }
+
+ return (
+
+
+
+ Linear Channels
+
+
+
+
+
+ Name |
+
+
+
+ {channels.map(c => (
+
+ {c.name} |
+
+ ))}
+
+
+
+ Tokens
+
+
+
+ );
+};
diff --git a/services/providers/fox/views/Login.tsx b/services/providers/fox/views/Login.tsx
new file mode 100644
index 0000000..abcae53
--- /dev/null
+++ b/services/providers/fox/views/Login.tsx
@@ -0,0 +1,35 @@
+import {FC} from 'hono/jsx';
+
+import { foxHandler } from '@/services/fox-handler';
+
+interface ILogin {
+ code?: string;
+ deviceToken?: string;
+}
+
+export const Login: FC = async ({code}) => {
+ let shownCode = code;
+
+ if (!shownCode) {
+ shownCode = await foxHandler.getAuthCode();
+ }
+
+ return (
+
+ );
+};
diff --git a/services/providers/fox/views/index.tsx b/services/providers/fox/views/index.tsx
new file mode 100644
index 0000000..a40c9b1
--- /dev/null
+++ b/services/providers/fox/views/index.tsx
@@ -0,0 +1,77 @@
+import {FC} from 'hono/jsx';
+
+import { db } from '@/services/database';
+import { IProvider } from '@/services/shared-interfaces';
+import { TFoxTokens } from '@/services/fox-handler';
+
+import { FoxBody } from './CardBody';
+
+export const FoxSports: FC = async () => {
+ const fox = await db.providers.findOne>({name: 'foxsports'});
+ const enabled = fox?.enabled;
+ const tokens = fox?.tokens;
+ const channels = fox?.linear_channels || [];
+ const only4k = fox?.meta?.only4k;
+ const uhd = fox?.meta?.uhd;
+
+ return (
+
+ );
+};
diff --git a/services/providers/index.ts b/services/providers/index.ts
new file mode 100644
index 0000000..46206ce
--- /dev/null
+++ b/services/providers/index.ts
@@ -0,0 +1,21 @@
+import {Hono} from 'hono';
+
+import {cbs} from './cbs-sports';
+import {mw} from './mw';
+import {paramount} from './paramount';
+import {flosports} from './flosports';
+import {mlbtv} from './mlb';
+import {fox} from './fox';
+import {nesn} from './nesn';
+import {b1g} from './b1g';
+
+export const providers = new Hono().basePath('/providers');
+
+providers.route('/', cbs);
+providers.route('/', mw);
+providers.route('/', paramount);
+providers.route('/', flosports);
+providers.route('/', mlbtv);
+providers.route('/', fox);
+providers.route('/', nesn);
+providers.route('/', b1g);
diff --git a/services/providers/mlb/index.tsx b/services/providers/mlb/index.tsx
new file mode 100644
index 0000000..dac6dbd
--- /dev/null
+++ b/services/providers/mlb/index.tsx
@@ -0,0 +1,117 @@
+import {Hono} from 'hono';
+
+import { db } from '@/services/database';
+
+import { Login } from './views/Login';
+import { MlbBody } from './views/CardBody';
+
+import { IProvider } from '@/services/shared-interfaces';
+import { removeEntriesProvider, scheduleEntries } from '@/services/build-schedule';
+import { mlbHandler, TMLBTokens } from '@/services/mlb-handler';
+
+export const mlbtv = new Hono().basePath('/mlbtv');
+
+const scheduleEvents = async () => {
+ await mlbHandler.getSchedule();
+ await scheduleEntries();
+};
+
+const removeEvents = async () => {
+ await removeEntriesProvider('mlbtvtv');
+};
+
+mlbtv.put('/toggle', async c => {
+ const body = await c.req.parseBody();
+ const enabled = body['mlbtv-enabled'] === 'on';
+
+ if (!enabled) {
+ await db.providers.update({name: 'mlbtv'}, {$set: {enabled, tokens: {}}});
+ removeEvents();
+
+ return c.html(<>>);
+ }
+
+ return c.html();
+});
+
+mlbtv.put('/toggle-free', async c => {
+ const body = await c.req.parseBody();
+ const onlyFree = body['mlbtv-onlyfree-enabled'] === 'on';
+
+ const {enabled, tokens, linear_channels} = await db.providers.update({name: 'mlbtv'}, {$set: {meta: {onlyFree}}}, {returnUpdatedDocs: true});
+
+ scheduleEvents();
+
+ return c.html();
+});
+
+mlbtv.get('/auth/:code', async c => {
+ const code = c.req.param('code');
+
+ const isAuthenticated = await mlbHandler.authenticateRegCode();
+
+ if (!isAuthenticated) {
+ return c.html();
+ }
+
+ const {tokens, linear_channels, meta} = await db.providers.update>(
+ {name: 'mlbtv'},
+ {$set: {enabled: true}},
+ {returnUpdatedDocs: true},
+ );
+
+ // Kickoff event scheduler
+ scheduleEvents();
+
+ return c.html(, 200, {
+ 'HX-Trigger': `{"HXToast":{"type":"success","body":"Successfully enabled MLB.tv"}}`,
+ });
+});
+
+mlbtv.put('/reauth', async c => {
+ return c.html();
+});
+
+mlbtv.put('/channels/toggle/:id', async c => {
+ const channelId = c.req.param('id');
+ const {linear_channels} = await db.providers.findOne({name: 'mlbtv'});
+
+ const body = await c.req.parseBody();
+ const enabled = body['channel-enabled'] === 'on';
+
+ let updatedChannel = channelId;
+
+ const updatedChannels = linear_channels.map(channel => {
+ if (channel.id === channelId) {
+ updatedChannel = channel.name;
+ return {...channel, enabled: !channel.enabled};
+ }
+ return channel;
+ });
+
+ if (updatedChannel !== channelId) {
+ await db.providers.update({name: 'mlbtv'}, {$set: {linear_channels: updatedChannels}});
+
+ // Kickoff event scheduler
+ scheduleEvents();
+
+ return c.html(
+ ,
+ 200,
+ {
+ ...(enabled && {
+ 'HX-Trigger': `{"HXToast":{"type":"success","body":"Successfully enabled ${updatedChannel}"}}`,
+ }),
+ },
+ );
+ }
+});
diff --git a/services/providers/mlb/views/CardBody.tsx b/services/providers/mlb/views/CardBody.tsx
new file mode 100644
index 0000000..8029b37
--- /dev/null
+++ b/services/providers/mlb/views/CardBody.tsx
@@ -0,0 +1,74 @@
+import {FC} from 'hono/jsx';
+
+import { TMLBTokens } from '@/services/mlb-handler';
+import { IProviderChannel } from '@/services/shared-interfaces';
+import { useLinear } from '@/services/channels';
+
+interface IMLBBodyProps {
+ enabled: boolean;
+ tokens?: TMLBTokens;
+ open?: boolean;
+ channels: IProviderChannel[];
+ onlyFree?: boolean;
+}
+
+export const MlbBody: FC = ({enabled, tokens, open, channels, onlyFree}) => {
+ const parsedTokens = JSON.stringify(tokens, undefined, 2);
+
+ if (!enabled) {
+ return null;
+ }
+
+ return (
+
+
+
+ Linear Channels
+
+
+
+
+ Tokens
+
+
+
+ );
+};
diff --git a/services/providers/mlb/views/Login.tsx b/services/providers/mlb/views/Login.tsx
new file mode 100644
index 0000000..629a710
--- /dev/null
+++ b/services/providers/mlb/views/Login.tsx
@@ -0,0 +1,39 @@
+import {FC} from 'hono/jsx';
+
+import { mlbHandler } from '@/services/mlb-handler';
+
+interface ILogin {
+ code?: string;
+}
+
+export const Login: FC = async ({code}) => {
+ let shownCode = code;
+
+ if (!shownCode) {
+ shownCode = await mlbHandler.getAuthCode();
+ }
+
+ return (
+
+ );
+};
diff --git a/services/providers/mlb/views/index.tsx b/services/providers/mlb/views/index.tsx
new file mode 100644
index 0000000..90eb986
--- /dev/null
+++ b/services/providers/mlb/views/index.tsx
@@ -0,0 +1,62 @@
+import {FC} from 'hono/jsx';
+
+import { db } from '@/services/database';
+import { IProvider } from '@/services/shared-interfaces';
+import { TMLBTokens } from '@/services/mlb-handler';
+
+import {MlbBody} from './CardBody';
+
+export const MlbTv: FC = async () => {
+ const mlbtv = await db.providers.findOne>({name: 'mlbtv'});
+ const enabled = mlbtv?.enabled;
+ const tokens = mlbtv?.tokens;
+ const channels = mlbtv?.linear_channels || [];
+ const onlyFree = mlbtv.meta?.onlyFree;
+
+ return (
+
+
+
+
MLB.tv
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/services/providers/mw/index.tsx b/services/providers/mw/index.tsx
new file mode 100644
index 0000000..cf904da
--- /dev/null
+++ b/services/providers/mw/index.tsx
@@ -0,0 +1,37 @@
+import {Hono} from 'hono';
+
+import { db } from '@/services/database';
+
+import { IProvider } from '@/services/shared-interfaces';
+import { removeEntriesProvider, scheduleEntries } from '@/services/build-schedule';
+import { mwHandler } from '@/services/mw-handler';
+
+export const mw = new Hono().basePath('/mw');
+
+const scheduleEvents = async () => {
+ await mwHandler.getSchedule();
+ await scheduleEntries();
+};
+
+const removeEvents = async () => {
+ await removeEntriesProvider('mountain-west');
+};
+
+mw.put('/toggle', async c => {
+ const body = await c.req.parseBody();
+ const enabled = body['mw-enabled'] === 'on';
+
+ await db.providers.update({name: 'mw'}, {$set: {enabled}});
+
+ if (enabled) {
+ scheduleEvents();
+ } else {
+ removeEvents();
+ }
+
+ return c.html(<>>, 200, {
+ ...(enabled && {
+ 'HX-Trigger': `{"HXToast":{"type":"success","body":"Successfully enabled Mountain West"}}`,
+ })
+ });
+});
diff --git a/services/providers/mw/views/index.tsx b/services/providers/mw/views/index.tsx
new file mode 100644
index 0000000..12ff052
--- /dev/null
+++ b/services/providers/mw/views/index.tsx
@@ -0,0 +1,36 @@
+import {FC} from 'hono/jsx';
+
+import { db } from '@/services/database';
+import { IProvider } from '@/services/shared-interfaces';
+
+export const MntWest: FC = async () => {
+ const mw = await db.providers.findOne({name: 'mw'});
+ const enabled = mw?.enabled;
+
+ return (
+
+ );
+};
diff --git a/services/providers/nesn/index.tsx b/services/providers/nesn/index.tsx
new file mode 100644
index 0000000..6835db8
--- /dev/null
+++ b/services/providers/nesn/index.tsx
@@ -0,0 +1,61 @@
+import {Hono} from 'hono';
+
+import { db } from '@/services/database';
+
+import { Login } from './views/Login';
+import { IProvider } from '@/services/shared-interfaces';
+import { removeEntriesProvider, scheduleEntries } from '@/services/build-schedule';
+import { nesnHandler, TNesnTokens } from '@/services/nesn-handler';
+import { NesnBody } from './views/CardBody';
+
+export const nesn = new Hono().basePath('/nesn');
+
+const scheduleEvents = async () => {
+ await nesnHandler.getSchedule();
+ await scheduleEntries();
+};
+
+const removeEvents = async () => {
+ await removeEntriesProvider('nesn+');
+};
+
+nesn.put('/toggle', async c => {
+ const body = await c.req.parseBody();
+ const enabled = body['nesn-enabled'] === 'on';
+
+ if (!enabled) {
+ await db.providers.update({name: 'nesn'}, {$set: {enabled, tokens: {}}});
+ removeEvents();
+
+ return c.html(<>>);
+ }
+
+ return c.html();
+});
+
+nesn.get('/tve-login/:code/:token/:hashedurl', async c => {
+ const code = c.req.param('code');
+ const adobeToken = c.req.param('token');
+ const url = c.req.param('hashedurl');
+
+ const decodedUrl = Buffer.from(url, 'base64').toString();
+
+ const isAuthenticated = await nesnHandler.authenticateRegCode(code, adobeToken);
+
+ if (!isAuthenticated) {
+ return c.html();
+ }
+
+ const {tokens, linear_channels} = await db.providers.update>({name: 'nesn'}, {$set: {enabled: true}}, {returnUpdatedDocs: true});
+
+ // Kickoff event scheduler
+ scheduleEvents();
+
+ return c.html(, 200, {
+ 'HX-Trigger': `{"HXToast":{"type":"success","body":"Successfully enabled NESN"}}`,
+ });
+});
+
+nesn.put('/reauth', async c => {
+ return c.html();
+});
diff --git a/services/providers/nesn/views/CardBody.tsx b/services/providers/nesn/views/CardBody.tsx
new file mode 100644
index 0000000..1d3de94
--- /dev/null
+++ b/services/providers/nesn/views/CardBody.tsx
@@ -0,0 +1,58 @@
+import {FC} from 'hono/jsx';
+
+import { TNesnTokens } from '@/services/nesn-handler';
+import { IProviderChannel } from '@/services/shared-interfaces';
+
+interface INesnBodyProps {
+ enabled: boolean;
+ tokens?: TNesnTokens;
+ open?: boolean;
+ channels: IProviderChannel[];
+}
+
+export const NesnBody: FC = ({enabled, tokens, open, channels}) => {
+ const parsedTokens = JSON.stringify(tokens, undefined, 2);
+
+ if (!enabled) {
+ return null;
+ }
+
+ return (
+
+
+
+ Linear Channels
+
+
+
+
+
+ Name |
+
+
+
+ {channels.map(c => (
+
+ {c.name} |
+
+ ))}
+
+
+
+ Tokens
+
+
+
+ );
+};
diff --git a/services/providers/nesn/views/Login.tsx b/services/providers/nesn/views/Login.tsx
new file mode 100644
index 0000000..365b148
--- /dev/null
+++ b/services/providers/nesn/views/Login.tsx
@@ -0,0 +1,44 @@
+import {FC} from 'hono/jsx';
+
+import { nesnHandler } from '@/services/nesn-handler';
+
+interface ILogin {
+ url?: string;
+ code?: string;
+ adobeCode?: string;
+}
+
+export const Login: FC = async ({url, code, adobeCode}) => {
+ let actualUrl = url;
+ let shownCode = code;
+ let adobeRegCode = adobeCode;
+
+ if (!shownCode || !actualUrl || !adobeCode) {
+ [actualUrl, shownCode, adobeRegCode] = await nesnHandler.getAuthCode();
+ }
+
+ const hashedUrl = Buffer.from(actualUrl).toString('base64');
+
+ return (
+
+
+
+
TVE Login:
+
+ Open this link and follow instructions:
+
+
+ {actualUrl}
+
+
+
+
+
+
+ );
+};
diff --git a/services/providers/nesn/views/index.tsx b/services/providers/nesn/views/index.tsx
new file mode 100644
index 0000000..1999356
--- /dev/null
+++ b/services/providers/nesn/views/index.tsx
@@ -0,0 +1,43 @@
+import {FC} from 'hono/jsx';
+
+import { db } from '@/services/database';
+import { IProvider } from '@/services/shared-interfaces';
+import { TNesnTokens } from '@/services/nesn-handler';
+
+import { NesnBody } from './CardBody';
+
+export const Nesn: FC = async () => {
+ const nesn = await db.providers.findOne>({name: 'nesn'});
+ const enabled = nesn?.enabled;
+ const tokens = nesn?.tokens;
+ const channels = nesn?.linear_channels || [];
+
+ return (
+
+ );
+};
diff --git a/services/providers/paramount/index.tsx b/services/providers/paramount/index.tsx
new file mode 100644
index 0000000..1483b3b
--- /dev/null
+++ b/services/providers/paramount/index.tsx
@@ -0,0 +1,102 @@
+import {Hono} from 'hono';
+
+import { db } from '@/services/database';
+
+import { Login } from './views/Login';
+import { IProvider } from '@/services/shared-interfaces';
+import { removeEntriesProvider, scheduleEntries } from '@/services/build-schedule';
+import { paramountHandler, TParamountTokens } from '@/services/paramount-handler';
+import { ParamountBody } from './views/CardBody';
+
+export const paramount = new Hono().basePath('/paramount');
+
+const scheduleEvents = async () => {
+ await paramountHandler.getSchedule();
+ await scheduleEntries();
+};
+
+const removeEvents = async () => {
+ await removeEntriesProvider('paramount+');
+};
+
+paramount.put('/toggle', async c => {
+ const body = await c.req.parseBody();
+ const enabled = body['paramount-enabled'] === 'on';
+
+ if (!enabled) {
+ await db.providers.update({name: 'paramount'}, {$set: {enabled, tokens: {}}});
+ removeEvents();
+
+ return c.html(<>>);
+ }
+
+ return c.html();
+});
+
+paramount.get('/tve-login/:code/:token', async c => {
+ const code = c.req.param('code');
+ const token = c.req.param('token');
+
+ const isAuthenticated = await paramountHandler.authenticateRegCode(code, token);
+
+ if (!isAuthenticated) {
+ return c.html();
+ }
+
+ const {tokens, linear_channels} = await db.providers.update>({name: 'paramount'}, {$set: {enabled: true}}, {returnUpdatedDocs: true});
+
+ // Kickoff event scheduler
+ scheduleEvents();
+
+ return c.html(, 200, {
+ 'HX-Trigger': `{"HXToast":{"type":"success","body":"Successfully enabled Paramount+"}}`,
+ });
+});
+
+paramount.put('/reauth', async c => {
+ return c.html();
+});
+
+paramount.put('/channels/toggle/:id', async c => {
+ const channelId = c.req.param('id');
+ const {linear_channels} = await db.providers.findOne({name: 'paramount'});
+
+ const body = await c.req.parseBody();
+ const enabled = body['channel-enabled'] === 'on';
+
+ let updatedChannel = channelId;
+
+ const updatedChannels = linear_channels.map(channel => {
+ if (channel.id === channelId) {
+ updatedChannel = channel.name;
+ return {...channel, enabled: !channel.enabled};
+ }
+ return channel;
+ });
+
+ if (updatedChannel !== channelId) {
+ await db.providers.update({name: 'paramount'}, {$set: {linear_channels: updatedChannels}});
+
+ // Kickoff event scheduler
+ scheduleEvents();
+
+ return c.html(
+ ,
+ 200,
+ {
+ ...(enabled && {
+ 'HX-Trigger': `{"HXToast":{"type":"success","body":"Successfully enabled ${updatedChannel}"}}`,
+ })
+ },
+ );
+ }
+});
diff --git a/services/providers/paramount/views/CardBody.tsx b/services/providers/paramount/views/CardBody.tsx
new file mode 100644
index 0000000..1e033e7
--- /dev/null
+++ b/services/providers/paramount/views/CardBody.tsx
@@ -0,0 +1,73 @@
+import {FC} from 'hono/jsx';
+
+import { TParamountTokens } from '@/services/paramount-handler';
+import { IProviderChannel } from '@/services/shared-interfaces';
+import { useLinear } from '@/services/channels';
+
+interface IParamountBodyProps {
+ enabled: boolean;
+ tokens?: TParamountTokens;
+ open?: boolean;
+ channels: IProviderChannel[];
+}
+
+export const ParamountBody: FC = ({enabled, tokens, open, channels}) => {
+ const parsedTokens = JSON.stringify(tokens, undefined, 2);
+
+ if (!enabled) {
+ return null;
+ }
+
+ return (
+
+
+
+ Linear Channels
+
+
+
+
+ Tokens
+
+
+
+ );
+};
diff --git a/services/providers/paramount/views/Login.tsx b/services/providers/paramount/views/Login.tsx
new file mode 100644
index 0000000..fc7df62
--- /dev/null
+++ b/services/providers/paramount/views/Login.tsx
@@ -0,0 +1,36 @@
+import {FC} from 'hono/jsx';
+
+import { paramountHandler } from '@/services/paramount-handler';
+
+interface ILogin {
+ code?: string;
+ deviceToken?: string;
+}
+
+export const Login: FC = async ({code, deviceToken}) => {
+ let shownCode = code;
+ let token = deviceToken;
+
+ if (!shownCode || !token) {
+ [shownCode, token] = await paramountHandler.getAuthCode();
+ }
+
+ return (
+
+ );
+};
diff --git a/services/providers/paramount/views/index.tsx b/services/providers/paramount/views/index.tsx
new file mode 100644
index 0000000..5d0a91b
--- /dev/null
+++ b/services/providers/paramount/views/index.tsx
@@ -0,0 +1,43 @@
+import {FC} from 'hono/jsx';
+
+import { db } from '@/services/database';
+import { IProvider } from '@/services/shared-interfaces';
+import { TParamountTokens } from '@/services/paramount-handler';
+
+import { ParamountBody } from './CardBody';
+
+export const Paramount: FC = async () => {
+ const paramount = await db.providers.findOne>({name: 'paramount'});
+ const enabled = paramount?.enabled;
+ const tokens = paramount?.tokens;
+ const channels = paramount?.linear_channels || [];
+
+ return (
+
+ );
+};
diff --git a/services/shared-helpers.ts b/services/shared-helpers.ts
index c8cafc1..96cae52 100644
--- a/services/shared-helpers.ts
+++ b/services/shared-helpers.ts
@@ -44,12 +44,13 @@ export const cleanEntries = async (): Promise => {
export const removeChannelStatus = (channelId: string | number): void => {
try {
- if (appStatus.channels[channelId].heartbeatTimer) {
+ if (appStatus.channels?.[channelId]?.heartbeatTimer) {
clearTimeout(appStatus.channels[channelId].heartbeatTimer);
}
delete appStatus.channels[channelId];
} catch (e) {
+ console.error(e);
console.log(`Failed to delete info for channel #${channelId}`);
}
};
diff --git a/services/shared-interfaces.ts b/services/shared-interfaces.ts
index e6080e1..0c327b0 100644
--- a/services/shared-interfaces.ts
+++ b/services/shared-interfaces.ts
@@ -55,3 +55,25 @@ export interface IChannel {
channel: string | number;
endsAt: number;
}
+
+export interface IProviderChannel {
+ enabled: boolean;
+ name: string;
+ tmsId?: string;
+ id: string;
+}
+
+export interface IProvider {
+ enabled: boolean;
+ tokens?: T;
+ linear_channels?: IProviderChannel[];
+ name: string;
+ meta?: M;
+}
+
+export type ClassTypeWithoutMethods = Omit<
+ T,
+ {
+ [K in keyof T]: T[K] extends (...args: any[]) => any ? K : never;
+ }[keyof T]
+>;
diff --git a/tsconfig.json b/tsconfig.json
index 16c23b0..c3c9d51 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -6,9 +6,17 @@
"module": "commonjs",
"rootDir": "./",
"outDir": "./dist",
+ "moduleResolution": "node",
"esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
"strict": false,
- "resolveJsonModule": true
+ "resolveJsonModule": true,
+ "baseUrl": "./",
+ "paths": {
+ "@/*": [
+ "*"
+ ]
+ }
},
"exclude": [
"tmp",
diff --git a/views/Header.tsx b/views/Header.tsx
new file mode 100644
index 0000000..5c8f1a0
--- /dev/null
+++ b/views/Header.tsx
@@ -0,0 +1,12 @@
+import type {FC} from 'hono/jsx';
+
+export const Header: FC = () => (
+
+);
diff --git a/views/Layout.tsx b/views/Layout.tsx
new file mode 100644
index 0000000..6c47fe5
--- /dev/null
+++ b/views/Layout.tsx
@@ -0,0 +1,21 @@
+import type {FC, ReactNode} from 'hono/jsx';
+
+export interface ILayoutProps {
+ children: ReactNode;
+}
+
+export const Layout: FC = ({children}: ILayoutProps) => (
+
+
+
+
+
+
+
+
+
+ E+TV
+
+ {children}
+
+);
diff --git a/views/Links.tsx b/views/Links.tsx
new file mode 100644
index 0000000..7223490
--- /dev/null
+++ b/views/Links.tsx
@@ -0,0 +1,75 @@
+import type {FC} from 'hono/jsx';
+
+import { useLinear } from '../services/channels';
+
+export interface ILinksProps {
+ baseUrl: string;
+}
+
+export const Links: FC = async ({baseUrl}) => {
+ const xmltvUrl = `${baseUrl}/xmltv.xml`;
+ const linearXmltvUrl = `${baseUrl}/linear-xmltv.xml`;
+ const channelsUrl = `${baseUrl}/channels.m3u`;
+ const linearChannelsUrl = `${baseUrl}/linear-channels.m3u`;
+
+ return (
+
+ );
+};
diff --git a/views/Main.tsx b/views/Main.tsx
new file mode 100644
index 0000000..df43a70
--- /dev/null
+++ b/views/Main.tsx
@@ -0,0 +1,6 @@
+import type {FC, ReactNode} from 'hono/jsx';
+
+export interface IMainProps {
+ children: ReactNode;
+}
+export const Main: FC = ({children}: IMainProps) => {children};
diff --git a/views/Providers.tsx b/views/Providers.tsx
new file mode 100644
index 0000000..098b3c5
--- /dev/null
+++ b/views/Providers.tsx
@@ -0,0 +1,12 @@
+import type {FC, ReactNode} from 'hono/jsx';
+
+export interface IProvidersProps {
+ children: ReactNode;
+}
+
+export const Providers: FC = ({children}: IProvidersProps) => (
+
+ Providers
+ {children}
+
+);
diff --git a/views/Script.tsx b/views/Script.tsx
new file mode 100644
index 0000000..b81e0eb
--- /dev/null
+++ b/views/Script.tsx
@@ -0,0 +1,21 @@
+import type {FC} from 'hono/jsx';
+
+export const Script: FC = () => {
+ return (
+