From 5195004f4917c01dbd1c648cc31d5bdc9d2db43c Mon Sep 17 00:00:00 2001 From: Jean-Francois Arseneau Date: Sat, 30 Apr 2022 23:35:46 -0700 Subject: [PATCH 1/7] Add digest auth once more, using Axios this time --- package-lock.json | 57 +++++++++++++++++++++++++++++++++++++++++++-- package.json | 1 + src/tvheadendApi.js | 12 +++++++++- 3 files changed, 67 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5235924..ff653e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,16 @@ { "name": "antennas", - "version": "4.1.1", + "version": "4.1.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "antennas", - "version": "4.1.1", + "version": "4.1.2", "license": "MIT", "dependencies": { "axios": "^0.24.0", + "axios-digest": "^0.3.0", "js-yaml": "^3.13.1", "koa": "^2.5.0", "koa-logger": "^3.2.0", @@ -992,6 +993,17 @@ "follow-redirects": "^1.14.4" } }, + "node_modules/axios-digest": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/axios-digest/-/axios-digest-0.3.0.tgz", + "integrity": "sha512-zl7zThkh+YLSDUYwDqY1hVPndpDn4ghbB59JVhLIj19X5GJBaIts9+SI82O6D0P2wxz9uXLz+Mwnh1WkNDuXgQ==", + "dependencies": { + "axios": "^0.24.0", + "js-md5": "^0.7.3", + "js-sha256": "^0.9.0", + "js-sha512": "^0.8.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3622,6 +3634,21 @@ "node": ">=8" } }, + "node_modules/js-md5": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.7.3.tgz", + "integrity": "sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ==" + }, + "node_modules/js-sha256": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", + "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==" + }, + "node_modules/js-sha512": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha512/-/js-sha512-0.8.0.tgz", + "integrity": "sha512-PWsmefG6Jkodqt+ePTvBZCSMFgN7Clckjd0O7su3I0+BW2QWUTJNzjktHsztGLhncP2h8mcF9V9Y2Ha59pAViQ==" + }, "node_modules/js-string-escape": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", @@ -6833,6 +6860,17 @@ "follow-redirects": "^1.14.4" } }, + "axios-digest": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/axios-digest/-/axios-digest-0.3.0.tgz", + "integrity": "sha512-zl7zThkh+YLSDUYwDqY1hVPndpDn4ghbB59JVhLIj19X5GJBaIts9+SI82O6D0P2wxz9uXLz+Mwnh1WkNDuXgQ==", + "requires": { + "axios": "^0.24.0", + "js-md5": "^0.7.3", + "js-sha256": "^0.9.0", + "js-sha512": "^0.8.0" + } + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -8765,6 +8803,21 @@ "istanbul-lib-report": "^3.0.0" } }, + "js-md5": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.7.3.tgz", + "integrity": "sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ==" + }, + "js-sha256": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", + "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==" + }, + "js-sha512": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha512/-/js-sha512-0.8.0.tgz", + "integrity": "sha512-PWsmefG6Jkodqt+ePTvBZCSMFgN7Clckjd0O7su3I0+BW2QWUTJNzjktHsztGLhncP2h8mcF9V9Y2Ha59pAViQ==" + }, "js-string-escape": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", diff --git a/package.json b/package.json index c465b56..2023c33 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "axios": "^0.24.0", + "axios-digest": "^0.3.0", "js-yaml": "^3.13.1", "koa": "^2.5.0", "koa-logger": "^3.2.0", diff --git a/src/tvheadendApi.js b/src/tvheadendApi.js index fc42324..171a629 100644 --- a/src/tvheadendApi.js +++ b/src/tvheadendApi.js @@ -1,4 +1,5 @@ const axios = require('axios'); +const AxiosDigest = require('axios-digest').default; async function get(apiPath, config) { const options = { @@ -13,7 +14,16 @@ async function get(apiPath, config) { }; } - return axios.get(`${config.tvheadend_parsed_uri}${apiPath}`, options); + try { + return await axios.get(`${config.tvheadend_parsed_uri}${apiPath}`, options); + } catch (err) { + if (err?.response?.status === 401) { + const axiosDigest = new AxiosDigest(config.tvheadend_username, config.tvheadend_password); + return axiosDigest.get(`${config.tvheadend_parsed_uri}${apiPath}`); + } + + return err; + } } module.exports = { get }; From cff6a49aa53f3c1130fdd52a3fa48fd97fac16e5 Mon Sep 17 00:00:00 2001 From: Jean-Francois Arseneau Date: Sat, 30 Apr 2022 23:36:13 -0700 Subject: [PATCH 2/7] Show channel count and more error cases --- public/index.html | 4 ++++ public/js/index.js | 1 + src/lineup.js | 17 ++++++++++------- src/router.js | 30 ++++++++++++++++++++++-------- 4 files changed, 37 insertions(+), 15 deletions(-) diff --git a/public/index.html b/public/index.html index f92c035..bbeb8fd 100644 --- a/public/index.html +++ b/public/index.html @@ -63,6 +63,10 @@

Config Settings

Tuner Count + + Channel Count + + diff --git a/public/js/index.js b/public/js/index.js index 8042dc9..b329d4a 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -27,6 +27,7 @@ fetch('/antennas_config.json').then((result) => { urlReplace('#tvheadendStreamUrl')(config.tvheadend_parsed_stream_uri); urlReplace('#antennasUrl')(config.antennas_url); replace('#tunerCount')(config.tuner_count); + replace('#channelCount')(config.channel_count); replace('#status')(config.status); }); diff --git a/src/lineup.js b/src/lineup.js index 4fdca5f..8036711 100644 --- a/src/lineup.js +++ b/src/lineup.js @@ -5,13 +5,16 @@ module.exports = async (config) => { const { data } = response; // TODO: Check if there's a Plex permission problem const lineup = []; - for (const channel of data.entries) { - if (channel.enabled) { - lineup.push({ - GuideNumber: String(channel.number), - GuideName: channel.name, - URL: `${config.tvheadend_stream_url}/stream/channel/${channel.uuid}`, - }); + + if (data?.entries) { + for (const channel of data.entries) { + if (channel.enabled) { + lineup.push({ + GuideNumber: String(channel.number), + GuideName: channel.name, + URL: `${config.tvheadend_stream_url}/stream/channel/${channel.uuid}`, + }); + } } } diff --git a/src/router.js b/src/router.js index 206efd8..e89e53c 100644 --- a/src/router.js +++ b/src/router.js @@ -6,10 +6,14 @@ const tvheadendApi = require('./tvheadendApi'); async function getConnectionStatus(config) { try { const channels = await tvheadendApi.get('/api/channel/grid?start=0&limit=999999', config); - if (channels.data.total === 0) { - return 'Connected but no channels found from Tvheadend'; - } - return 'All systems go'; + let status = 'All systems go'; + if (channels?.response?.status === 403) { throw new Error('Username and password not accepted by Tvheadend'); } + if (channels?.code === 'ECONNREFUSED') { throw new Error('Unable to connect to Tvheadend'); } + if (channels?.data?.total === 0) { status = 'Connected but no channels found from Tvheadend'; } + return { + status, + channelCount: channels?.data?.total, + }; } catch (err) { console.log(` Antennas failed to connect to Tvheadend! @@ -21,9 +25,17 @@ async function getConnectionStatus(config) { Here's a dump of the error: ${err}`); - if (err.response.status === 401) { return 'Failed to authenticate with Tvheadend'; } - if (err.code === 'ECONNABORTED') { return 'Unable to find Tvheadend server, make sure the server is up and the configuration is pointing to the right spot'; } - return 'Unknown error, check the logs for more details'; + let status = 'Unknown error, check the logs for more details'; + + if (err?.response?.status === 401) { status = 'Failed to authenticate with Tvheadend'; } + if (err?.code === 'ECONNABORTED') { status = 'Unable to find Tvheadend server, make sure the server is up and the configuration is pointing to the right spot'; } + if (err?.message === 'Auth params error.' || err?.message === 'Username and password not accepted by Tvheadend' ) { status = 'Access denied to Tvheadend; check the username, password, and access rights'; } + if (err?.message === 'Unable to connect to Tvheadend') { status = 'Unable to connect to Tvheadend; is it running?'; } + + return { + status, + channelCount: 0, + }; } } @@ -32,7 +44,9 @@ module.exports = (config, device) => { router.get('/antennas_config.json', async (ctx) => { ctx.type = 'application/json'; - config.status = await getConnectionStatus(config); + const connectionStatus = await getConnectionStatus(config); + config.status = connectionStatus.status; + config.channel_count = connectionStatus.channelCount; ctx.body = config; }); From 2ef14fc7ab33fe861a9baf60e923911eeb51f242 Mon Sep 17 00:00:00 2001 From: Jean-Francois Arseneau Date: Sun, 1 May 2022 13:25:33 -0700 Subject: [PATCH 3/7] Test if the expected response is empty --- src/lineup.test.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/lineup.test.js b/src/lineup.test.js index 45d5892..5041bb5 100644 --- a/src/lineup.test.js +++ b/src/lineup.test.js @@ -36,7 +36,7 @@ test.serial('calls the API with the right options', async (t) => { remote_timeshift: false, services: ['test-service'], tags: [], - bouquet: '' + bouquet: '', }], total: 1, }, @@ -62,4 +62,13 @@ test.serial('returns empty when Tvheadend has no channel', async (t) => { const actual = await lineup({ tvheadend_stream_url: 'https://stream.test' }); t.deepEqual(actual, []); -}); \ No newline at end of file +}); + +test.serial('returns empty when Tvheadend returns nothing', async (t) => { + const expectedResponse = {}; + tvheadendApiStub.resolves(expectedResponse); + + const actual = await lineup({ tvheadend_stream_url: 'https://stream.test' }); + + t.deepEqual(actual, []); +}); From 5f85b655e14f60d28c2dd167ec72e0aea9bc14d8 Mon Sep 17 00:00:00 2001 From: Jean-Francois Arseneau Date: Sun, 1 May 2022 13:28:08 -0700 Subject: [PATCH 4/7] Add undefined tests --- src/lineup.js | 24 ++++++++++++++---------- src/lineup.test.js | 13 +++++++++++-- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/lineup.js b/src/lineup.js index 8036711..21fe31a 100644 --- a/src/lineup.js +++ b/src/lineup.js @@ -2,21 +2,25 @@ const tvheadendApi = require('./tvheadendApi'); module.exports = async (config) => { const response = await tvheadendApi.get('/api/channel/grid?start=0&limit=999999', config); - const { data } = response; - // TODO: Check if there's a Plex permission problem const lineup = []; + + if (response) { + const { data } = response; + // TODO: Check if there's a Plex permission problem - if (data?.entries) { - for (const channel of data.entries) { - if (channel.enabled) { - lineup.push({ - GuideNumber: String(channel.number), - GuideName: channel.name, - URL: `${config.tvheadend_stream_url}/stream/channel/${channel.uuid}`, - }); + if (data?.entries) { + for (const channel of data.entries) { + if (channel.enabled) { + lineup.push({ + GuideNumber: String(channel.number), + GuideName: channel.name, + URL: `${config.tvheadend_stream_url}/stream/channel/${channel.uuid}`, + }); + } } } } + return lineup; }; diff --git a/src/lineup.test.js b/src/lineup.test.js index 5041bb5..ca831ed 100644 --- a/src/lineup.test.js +++ b/src/lineup.test.js @@ -64,11 +64,20 @@ test.serial('returns empty when Tvheadend has no channel', async (t) => { t.deepEqual(actual, []); }); -test.serial('returns empty when Tvheadend returns nothing', async (t) => { - const expectedResponse = {}; +test.serial('returns empty when Tvheadend returns undefined', async (t) => { + const expectedResponse = undefined; tvheadendApiStub.resolves(expectedResponse); const actual = await lineup({ tvheadend_stream_url: 'https://stream.test' }); t.deepEqual(actual, []); }); + +test.serial('returns empty when Tvheadend returns no data', async (t) => { + const expectedResponse = { data: undefined }; + tvheadendApiStub.resolves(expectedResponse); + + const actual = await lineup({ tvheadend_stream_url: 'https://stream.test' }); + + t.deepEqual(actual, []); +}); \ No newline at end of file From d795994b5e27a22fecdf891074bc1a9dce498c58 Mon Sep 17 00:00:00 2001 From: Jean-Francois Arseneau Date: Sun, 1 May 2022 13:36:10 -0700 Subject: [PATCH 5/7] Make sure Node 12 can run this --- src/lineup.js | 2 +- src/router.js | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lineup.js b/src/lineup.js index 21fe31a..527b549 100644 --- a/src/lineup.js +++ b/src/lineup.js @@ -8,7 +8,7 @@ module.exports = async (config) => { const { data } = response; // TODO: Check if there's a Plex permission problem - if (data?.entries) { + if (data && data.entries) { for (const channel of data.entries) { if (channel.enabled) { lineup.push({ diff --git a/src/router.js b/src/router.js index e89e53c..3fb6a6a 100644 --- a/src/router.js +++ b/src/router.js @@ -27,10 +27,10 @@ async function getConnectionStatus(config) { let status = 'Unknown error, check the logs for more details'; - if (err?.response?.status === 401) { status = 'Failed to authenticate with Tvheadend'; } - if (err?.code === 'ECONNABORTED') { status = 'Unable to find Tvheadend server, make sure the server is up and the configuration is pointing to the right spot'; } - if (err?.message === 'Auth params error.' || err?.message === 'Username and password not accepted by Tvheadend' ) { status = 'Access denied to Tvheadend; check the username, password, and access rights'; } - if (err?.message === 'Unable to connect to Tvheadend') { status = 'Unable to connect to Tvheadend; is it running?'; } + if (err && err.response && err.response.status === 401) { status = 'Failed to authenticate with Tvheadend'; } + if (err && err.code === 'ECONNABORTED') { status = 'Unable to find Tvheadend server, make sure the server is up and the configuration is pointing to the right spot'; } + if (err && err.message === 'Auth params error.' || err?.message === 'Username and password not accepted by Tvheadend' ) { status = 'Access denied to Tvheadend; check the username, password, and access rights'; } + if (err && err.message === 'Unable to connect to Tvheadend') { status = 'Unable to connect to Tvheadend; is it running?'; } return { status, @@ -44,9 +44,9 @@ module.exports = (config, device) => { router.get('/antennas_config.json', async (ctx) => { ctx.type = 'application/json'; - const connectionStatus = await getConnectionStatus(config); - config.status = connectionStatus.status; - config.channel_count = connectionStatus.channelCount; + const { status, channelCount } = await getConnectionStatus(config); + config.status = status; + config.channel_count = channelCount; ctx.body = config; }); From 0b98289b7053f52a1ce7f24b90aaa2dc337e38db Mon Sep 17 00:00:00 2001 From: Jean-Francois Arseneau Date: Sun, 1 May 2022 13:37:54 -0700 Subject: [PATCH 6/7] Add Node 12 support to tvheadendApi --- src/tvheadendApi.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tvheadendApi.js b/src/tvheadendApi.js index 171a629..2b6132f 100644 --- a/src/tvheadendApi.js +++ b/src/tvheadendApi.js @@ -17,7 +17,7 @@ async function get(apiPath, config) { try { return await axios.get(`${config.tvheadend_parsed_uri}${apiPath}`, options); } catch (err) { - if (err?.response?.status === 401) { + if (err && err.response && err.response.status === 401) { const axiosDigest = new AxiosDigest(config.tvheadend_username, config.tvheadend_password); return axiosDigest.get(`${config.tvheadend_parsed_uri}${apiPath}`); } From 98b99dcf79c567513950338b514ffd91d3b420bf Mon Sep 17 00:00:00 2001 From: Jean-Francois Arseneau Date: Sun, 1 May 2022 13:41:57 -0700 Subject: [PATCH 7/7] Bump version to 4.2.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ff653e6..5c80f95 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "antennas", - "version": "4.1.2", + "version": "4.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "antennas", - "version": "4.1.2", + "version": "4.2.0", "license": "MIT", "dependencies": { "axios": "^0.24.0", diff --git a/package.json b/package.json index 2023c33..ecb5aa5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "antennas", - "version": "4.1.2", + "version": "4.2.0", "description": "HDHomeRun emulator for Plex DVR to connect to Tvheadend.", "main": "index.js", "repository": "https://github.com/jfarseneau/antennas",