This commit is contained in:
1 parent
74df7780fd
commit
d266a8258d
9 files changed
+32
-15
No files matched your search
@@ -55,7 +55,8 @@ View BE| DAZN 03 [Standard de Liège - SK Beveren | Jupiler Pro League (nl) (202
|
|||||||
| `EVENT_DURATION_MIN` | `150` | nee | Duur waarna een event het slot verlaat |
|
| `EVENT_DURATION_MIN` | `150` | nee | Duur waarna een event het slot verlaat |
|
||||||
| `START_GRACE_MIN` | `10` | nee | Hoeveel minuten voor aanvang een event eligibel wordt |
|
| `START_GRACE_MIN` | `10` | nee | Hoeveel minuten voor aanvang een event eligibel wordt |
|
||||||
| `REFRESH_INTERVAL_SEC` | `60` | nee | Interval voor upstream-refresh |
|
| `REFRESH_INTERVAL_SEC` | `60` | nee | Interval voor upstream-refresh |
|
||||||
| `TIMEZONE` | `Europe/Brussels` | nee | Tijdzone van de tijdstempels in kanaalnamen |
|
| `SOURCE_TIMEZONE` | `UTC` | nee | Tijdzone van de tijdstempels in kanaalnamen |
|
||||||
|
| `TIMEZONE` | `Europe/Brussels` | nee | Tijdzone voor weergave in playlist en EPG |
|
||||||
| `LISTEN_PORT` | `8080` | nee | HTTP-poort |
|
| `LISTEN_PORT` | `8080` | nee | HTTP-poort |
|
||||||
| `UPSTREAM_TIMEOUT_MS` | `15000` | nee | Timeout voor upstream-verzoeken |
|
| `UPSTREAM_TIMEOUT_MS` | `15000` | nee | Timeout voor upstream-verzoeken |
|
||||||
| `PROXY_INSECURE_TLS` | `0` | nee | `1` = self-signed upstream-certificaten accepteren |
|
| `PROXY_INSECURE_TLS` | `0` | nee | `1` = self-signed upstream-certificaten accepteren |
|
||||||
@@ -95,8 +96,9 @@ Ondersteun minimaal het opgegeven DAZN-formaat:
|
|||||||
- Neem de laatste `[...]` als inhoud; splits op de eerste `|` in `title` en `rest`.
|
- Neem de laatste `[...]` als inhoud; splits op de eerste `|` in `title` en `rest`.
|
||||||
- Parse uit `rest`: league, optionele taalcode en verplichte starttijd.
|
- Parse uit `rest`: league, optionele taalcode en verplichte starttijd.
|
||||||
- `title`, `league`, `language`, `startAt` (UTC) en `endAt`.
|
- `title`, `league`, `language`, `startAt` (UTC) en `endAt`.
|
||||||
- Tijdzone via `TIMEZONE`, correct omgaan met zomertijd (Intl-offsetberekening, geen
|
- Tijdstempels worden standaard als UTC gelezen (`SOURCE_TIMEZONE`), correct omgaan met
|
||||||
afhankelijkheid van de proces-TZ).
|
zomertijd (Intl-offsetberekening, geen afhankelijkheid van de proces-TZ). Weergave in
|
||||||
|
playlist/EPG gebeurt in `TIMEZONE`.
|
||||||
- Als een matchende naam niet parsebaar is: stream overslaan, één keer loggen en in
|
- Als een matchende naam niet parsebaar is: stream overslaan, één keer loggen en in
|
||||||
`/status` als waarschuwing tonen. Geen gokken met "nu actief", om toekomstige streams
|
`/status` als waarschuwing tonen. Geen gokken met "nu actief", om toekomstige streams
|
||||||
niet te vroeg te tonen.
|
niet te vroeg te tonen.
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ ENV LISTEN_PORT=8080 \
|
|||||||
EVENT_DURATION_MIN=150 \
|
EVENT_DURATION_MIN=150 \
|
||||||
START_GRACE_MIN=10 \
|
START_GRACE_MIN=10 \
|
||||||
REFRESH_INTERVAL_SEC=60 \
|
REFRESH_INTERVAL_SEC=60 \
|
||||||
|
SOURCE_TIMEZONE=UTC \
|
||||||
TIMEZONE=Europe/Brussels \
|
TIMEZONE=Europe/Brussels \
|
||||||
UPSTREAM_TIMEOUT_MS=15000 \
|
UPSTREAM_TIMEOUT_MS=15000 \
|
||||||
PROXY_INSECURE_TLS=0 \
|
PROXY_INSECURE_TLS=0 \
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ competitie, taal en starttijd, bijvoorbeeld:
|
|||||||
View BE| DAZN 03 [Standard de Liège - SK Beveren | Jupiler Pro League (nl) (2026-11-06 19:25:00)]
|
View BE| DAZN 03 [Standard de Liège - SK Beveren | Jupiler Pro League (nl) (2026-11-06 19:25:00)]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
De tijdstempels in de kanaalnamen worden standaard als **UTC** gelezen. De app toont ze
|
||||||
|
vervolgens in `TIMEZONE` (standaard `Europe/Brussels`), dus `19:25:00` UTC wordt in de
|
||||||
|
playlist en EPG als `20:25` getoond.
|
||||||
|
|
||||||
Alleen streams met taalcode `(nl)` of zonder taalcode worden gebruikt. Een expliciete
|
Alleen streams met taalcode `(nl)` of zonder taalcode worden gebruikt. Een expliciete
|
||||||
`(fr)`- of andere taalcode wordt overgeslagen.
|
`(fr)`- of andere taalcode wordt overgeslagen.
|
||||||
|
|
||||||
@@ -68,7 +72,8 @@ staat.
|
|||||||
| `EVENT_DURATION_MIN` | `150` | nee | Duur waarna een event het slot verlaat |
|
| `EVENT_DURATION_MIN` | `150` | nee | Duur waarna een event het slot verlaat |
|
||||||
| `START_GRACE_MIN` | `10` | nee | Minuten voor aanvang dat een event eligibel wordt |
|
| `START_GRACE_MIN` | `10` | nee | Minuten voor aanvang dat een event eligibel wordt |
|
||||||
| `REFRESH_INTERVAL_SEC` | `60` | nee | Hoe vaak kanaalnamen opnieuw worden gelezen |
|
| `REFRESH_INTERVAL_SEC` | `60` | nee | Hoe vaak kanaalnamen opnieuw worden gelezen |
|
||||||
| `TIMEZONE` | `Europe/Brussels` | nee | Tijdzone van de tijdstempels in kanaalnamen |
|
| `SOURCE_TIMEZONE` | `UTC` | nee | Tijdzone van de tijdstempels in de kanaalnamen |
|
||||||
|
| `TIMEZONE` | `Europe/Brussels` | nee | Tijdzone voor weergave in playlist en EPG |
|
||||||
| `LISTEN_PORT` | `8080` | nee | HTTP-poort |
|
| `LISTEN_PORT` | `8080` | nee | HTTP-poort |
|
||||||
| `UPSTREAM_TIMEOUT_MS` | `15000` | nee | Timeout voor upstream-verzoeken |
|
| `UPSTREAM_TIMEOUT_MS` | `15000` | nee | Timeout voor upstream-verzoeken |
|
||||||
| `PROXY_INSECURE_TLS` | `0` | nee | `1` = self-signed upstream-certificaten accepteren |
|
| `PROXY_INSECURE_TLS` | `0` | nee | `1` = self-signed upstream-certificaten accepteren |
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ services:
|
|||||||
EVENT_DURATION_MIN: "150"
|
EVENT_DURATION_MIN: "150"
|
||||||
START_GRACE_MIN: "10"
|
START_GRACE_MIN: "10"
|
||||||
REFRESH_INTERVAL_SEC: "600"
|
REFRESH_INTERVAL_SEC: "600"
|
||||||
|
SOURCE_TIMEZONE: "UTC"
|
||||||
TIMEZONE: "Europe/Brussels"
|
TIMEZONE: "Europe/Brussels"
|
||||||
# PROXY_INSECURE_TLS: "1" # alleen nodig bij self-signed upstream-certificaten
|
# PROXY_INSECURE_TLS: "1" # alleen nodig bij self-signed upstream-certificaten
|
||||||
# LOG_LEVEL: "debug"
|
# LOG_LEVEL: "debug"
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ function statusPayload() {
|
|||||||
eventDurationMin: settings.eventDurationMin,
|
eventDurationMin: settings.eventDurationMin,
|
||||||
startGraceMin: settings.startGraceMin,
|
startGraceMin: settings.startGraceMin,
|
||||||
refreshIntervalSec: settings.refreshIntervalSec,
|
refreshIntervalSec: settings.refreshIntervalSec,
|
||||||
|
sourceTimezone: settings.sourceTimezone,
|
||||||
timezone: settings.timezone,
|
timezone: settings.timezone,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ function createScheduler() {
|
|||||||
const { events, warnings } = buildEvents(streams, {
|
const { events, warnings } = buildEvents(streams, {
|
||||||
searchTerm: settings.searchTerm,
|
searchTerm: settings.searchTerm,
|
||||||
language: settings.language,
|
language: settings.language,
|
||||||
timezone: settings.timezone,
|
timezone: settings.sourceTimezone,
|
||||||
eventDurationMs: settings.eventDurationMs,
|
eventDurationMs: settings.eventDurationMs,
|
||||||
});
|
});
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ const settings = {
|
|||||||
eventDurationMin: readNumber(process.env.EVENT_DURATION_MIN, 150, 1, 1440),
|
eventDurationMin: readNumber(process.env.EVENT_DURATION_MIN, 150, 1, 1440),
|
||||||
startGraceMin: readNumber(process.env.START_GRACE_MIN, 10, 0, 1440),
|
startGraceMin: readNumber(process.env.START_GRACE_MIN, 10, 0, 1440),
|
||||||
refreshIntervalSec: readNumber(process.env.REFRESH_INTERVAL_SEC, 60, 10, 3600),
|
refreshIntervalSec: readNumber(process.env.REFRESH_INTERVAL_SEC, 60, 10, 3600),
|
||||||
|
sourceTimezone: (process.env.SOURCE_TIMEZONE || 'UTC').trim() || 'UTC',
|
||||||
timezone: (process.env.TIMEZONE || 'Europe/Brussels').trim() || 'Europe/Brussels',
|
timezone: (process.env.TIMEZONE || 'Europe/Brussels').trim() || 'Europe/Brussels',
|
||||||
listenPort: Math.round(readNumber(process.env.LISTEN_PORT, 8080, 1, 65535)),
|
listenPort: Math.round(readNumber(process.env.LISTEN_PORT, 8080, 1, 65535)),
|
||||||
upstreamTimeoutMs: readNumber(process.env.UPSTREAM_TIMEOUT_MS, 15000, 1000, 120000),
|
upstreamTimeoutMs: readNumber(process.env.UPSTREAM_TIMEOUT_MS, 15000, 1000, 120000),
|
||||||
@@ -32,11 +33,11 @@ const settings = {
|
|||||||
settings.eventDurationMs = settings.eventDurationMin * 60 * 1000;
|
settings.eventDurationMs = settings.eventDurationMin * 60 * 1000;
|
||||||
settings.startGraceMs = settings.startGraceMin * 60 * 1000;
|
settings.startGraceMs = settings.startGraceMin * 60 * 1000;
|
||||||
|
|
||||||
function validateTimezone(timezone) {
|
function validateTimezone(timezone, envName) {
|
||||||
try {
|
try {
|
||||||
new Intl.DateTimeFormat('en-US', { timeZone: timezone });
|
new Intl.DateTimeFormat('en-US', { timeZone: timezone });
|
||||||
} catch {
|
} catch {
|
||||||
throw new Error(`Invalid TIMEZONE: ${timezone}`);
|
throw new Error(`Invalid ${envName}: ${timezone}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +61,8 @@ function validate() {
|
|||||||
throw new Error('XTREAM_URL must use http or https');
|
throw new Error('XTREAM_URL must use http or https');
|
||||||
}
|
}
|
||||||
|
|
||||||
validateTimezone(settings.timezone);
|
validateTimezone(settings.sourceTimezone, 'SOURCE_TIMEZONE');
|
||||||
|
validateTimezone(settings.timezone, 'TIMEZONE');
|
||||||
|
|
||||||
if (!settings.language) {
|
if (!settings.language) {
|
||||||
throw new Error('LANGUAGE must not be empty');
|
throw new Error('LANGUAGE must not be empty');
|
||||||
|
|||||||
@@ -7,18 +7,23 @@ const { parseChannelName, normalizeText } = require('../src/channel-name');
|
|||||||
const sample =
|
const sample =
|
||||||
'View BE| DAZN 03 [Standard de Liège - SK Beveren | Jupiler Pro League (nl) (2026-11-06 19:25:00)]';
|
'View BE| DAZN 03 [Standard de Liège - SK Beveren | Jupiler Pro League (nl) (2026-11-06 19:25:00)]';
|
||||||
|
|
||||||
test('parses the DAZN sample', () => {
|
test('parses the DAZN sample with UTC source timestamps', () => {
|
||||||
const parsed = parseChannelName(sample, { timezone: 'Europe/Brussels' });
|
const parsed = parseChannelName(sample, { timezone: 'UTC' });
|
||||||
assert.equal(parsed.title, 'Standard de Liège - SK Beveren');
|
assert.equal(parsed.title, 'Standard de Liège - SK Beveren');
|
||||||
assert.equal(parsed.league, 'Jupiler Pro League');
|
assert.equal(parsed.league, 'Jupiler Pro League');
|
||||||
assert.equal(parsed.language, 'nl');
|
assert.equal(parsed.language, 'nl');
|
||||||
|
assert.equal(parsed.startAt.toISOString(), '2026-11-06T19:25:00.000Z');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can parse the same timestamp in another source timezone', () => {
|
||||||
|
const parsed = parseChannelName(sample, { timezone: 'Europe/Brussels' });
|
||||||
assert.equal(parsed.startAt.toISOString(), '2026-11-06T18:25:00.000Z');
|
assert.equal(parsed.startAt.toISOString(), '2026-11-06T18:25:00.000Z');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('parses a French stream', () => {
|
test('parses a French stream', () => {
|
||||||
const parsed = parseChannelName(
|
const parsed = parseChannelName(
|
||||||
'View BE| DAZN 03 [Standard de Liège - SK Beveren | Jupiler Pro League (fr) (2026-11-06 19:25:00)]',
|
'View BE| DAZN 03 [Standard de Liège - SK Beveren | Jupiler Pro League (fr) (2026-11-06 19:25:00)]',
|
||||||
{ timezone: 'Europe/Brussels' }
|
{ timezone: 'UTC' }
|
||||||
);
|
);
|
||||||
assert.equal(parsed.language, 'fr');
|
assert.equal(parsed.language, 'fr');
|
||||||
});
|
});
|
||||||
@@ -26,14 +31,14 @@ test('parses a French stream', () => {
|
|||||||
test('allows a missing language code', () => {
|
test('allows a missing language code', () => {
|
||||||
const parsed = parseChannelName(
|
const parsed = parseChannelName(
|
||||||
'DAZN 03 [Standard de Liège - SK Beveren | Jupiler Pro League (2026-11-06 19:25:00)]',
|
'DAZN 03 [Standard de Liège - SK Beveren | Jupiler Pro League (2026-11-06 19:25:00)]',
|
||||||
{ timezone: 'Europe/Brussels' }
|
{ timezone: 'UTC' }
|
||||||
);
|
);
|
||||||
assert.equal(parsed.language, null);
|
assert.equal(parsed.language, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('returns null for unparseable names', () => {
|
test('returns null for unparseable names', () => {
|
||||||
assert.equal(parseChannelName('DAZN 03', { timezone: 'Europe/Brussels' }), null);
|
assert.equal(parseChannelName('DAZN 03', { timezone: 'UTC' }), null);
|
||||||
assert.equal(parseChannelName('', { timezone: 'Europe/Brussels' }), null);
|
assert.equal(parseChannelName('', { timezone: 'UTC' }), null);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('normalizes diacritics and whitespace', () => {
|
test('normalizes diacritics and whitespace', () => {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ const test = require('node:test');
|
|||||||
const assert = require('node:assert/strict');
|
const assert = require('node:assert/strict');
|
||||||
const { buildEvents, compareEvents, isEligible } = require('../src/events');
|
const { buildEvents, compareEvents, isEligible } = require('../src/events');
|
||||||
|
|
||||||
const timezone = 'Europe/Brussels';
|
const timezone = 'UTC';
|
||||||
|
|
||||||
function stream(id, name) {
|
function stream(id, name) {
|
||||||
return { stream_id: id, name };
|
return { stream_id: id, name };
|
||||||
|
|||||||
Reference in new issue
Block a user