diff --git a/.changes/deps-epg-parser-0-5-0.md b/.changes/deps-epg-parser-0-5-0.md
new file mode 100644
index 000000000..0eb47360f
--- /dev/null
+++ b/.changes/deps-epg-parser-0-5-0.md
@@ -0,0 +1,6 @@
+---
+type: internal
+area: deps
+---
+
+The web backend now uses the current structured XMLTV parser output, covered by a route-level contract test for future PWA EPG support.
diff --git a/apps/web-backend/src/app/web-backend-app.spec.ts b/apps/web-backend/src/app/web-backend-app.spec.ts
index 9c90818dd..bd271f1b4 100644
--- a/apps/web-backend/src/app/web-backend-app.spec.ts
+++ b/apps/web-backend/src/app/web-backend-app.spec.ts
@@ -210,6 +210,87 @@ https://stream.example/news.m3u8`);
);
});
+ it('parses XMLTV metadata into the current EPG shape', async () => {
+ const httpClient = new StubHttpClient();
+ httpClient.queueResponse(`
+
+
+ News
+
+ https://example.test/news
+
+
+ Morning News
+ Headlines
+
+
+`);
+
+ await withServer(
+ createWebBackendApp({
+ clientOrigins: ['http://localhost:4200'],
+ httpClient,
+ resolveHostname: resolvePublicHost,
+ }),
+ async (baseUrl) => {
+ const targetId = await registerProviderTarget(
+ baseUrl,
+ 'https://provider.example/guide.xml'
+ );
+ const response = await fetch(
+ `${baseUrl}/parse-xml?targetId=${targetId}`,
+ { headers: { Origin: 'http://localhost:4200' } }
+ );
+ const body = (await response.json()) as Record;
+
+ expect(response.status).toBe(200);
+ expect(body).toMatchObject({
+ sourceInfoName: 'Fixture Guide',
+ channels: [
+ {
+ id: 'news',
+ displayName: [{ lang: 'en', value: 'News' }],
+ icon: [
+ {
+ src: 'https://example.test/news.png',
+ width: '100',
+ height: '50',
+ },
+ ],
+ url: [
+ {
+ system: 'homepage',
+ value: 'https://example.test/news',
+ },
+ ],
+ },
+ ],
+ programs: [
+ {
+ channel: 'news',
+ start: '2026-08-11T01:00:00.000Z',
+ stop: '2026-08-11T02:00:00.000Z',
+ title: [{ lang: 'en', value: 'Morning News' }],
+ desc: [{ lang: 'en', value: 'Headlines' }],
+ icon: [
+ {
+ src: 'https://example.test/program.png',
+ },
+ ],
+ },
+ ],
+ });
+ expect(httpClient.requests).toEqual([
+ {
+ headers: undefined,
+ params: undefined,
+ url: 'https://provider.example/guide.xml',
+ },
+ ]);
+ }
+ );
+ });
+
it('extracts KODIPROP ClearKey DRM on the /parse URL-import path', async () => {
const httpClient = new StubHttpClient();
const kid = '00112233445566778899aabbccddeeff';
diff --git a/package.json b/package.json
index 36a19de49..58520fff8 100644
--- a/package.json
+++ b/package.json
@@ -110,7 +110,7 @@
"drizzle-orm": "0.45.2",
"electron-conf": "1.3.0",
"electron-updater": "6.8.9",
- "epg-parser": "^0.1.6",
+ "epg-parser": "^0.5.0",
"fix-path": "5.0.0",
"hls.js": "1.6.16",
"iptv-playlist-parser": "github:4gray/iptv-playlist-parser#v0.15.2-iptvnator.2",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2919bbc5f..a5aa38c1f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -122,8 +122,8 @@ importers:
specifier: 6.8.9
version: 6.8.9
epg-parser:
- specifier: ^0.1.6
- version: 0.1.6
+ specifier: ^0.5.0
+ version: 0.5.0
fix-path:
specifier: 5.0.0
version: 5.0.0
@@ -6049,6 +6049,9 @@ packages:
date-fns@4.4.0:
resolution: {integrity: sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==}
+ dayjs@1.11.21:
+ resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==}
+
debug@2.6.9:
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
peerDependencies:
@@ -6487,8 +6490,8 @@ packages:
resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}
engines: {node: '>=18'}
- epg-parser@0.1.6:
- resolution: {integrity: sha512-g6AxKOvs0E4bTGPdIUh8/FDKdrVjbf4DVK0jIFuChDt7wBRJmMVyqbLeS8NApf6M2wpCRLBpIenXOCS88w0Rqw==}
+ epg-parser@0.5.0:
+ resolution: {integrity: sha512-NK9vSev/KkCVcKilJd35PmB9tP4+tN45dhcsSxHagVoUTINh8KLZ1yvgtoPDVYr0IaSPJXIIIZWcu1JPjS5HMw==}
err-code@2.0.3:
resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==}
@@ -8091,6 +8094,9 @@ packages:
lodash.escaperegexp@4.1.2:
resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==}
+ lodash.groupby@4.6.0:
+ resolution: {integrity: sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==}
+
lodash.isequal@4.5.0:
resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==}
deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead.
@@ -17976,6 +17982,8 @@ snapshots:
date-fns@4.4.0: {}
+ dayjs@1.11.21: {}
+
debug@2.6.9:
dependencies:
ms: 2.0.0
@@ -18311,8 +18319,10 @@ snapshots:
environment@1.1.0: {}
- epg-parser@0.1.6:
+ epg-parser@0.5.0:
dependencies:
+ dayjs: 1.11.21
+ lodash.groupby: 4.6.0
xml-js: 1.6.11
err-code@2.0.3: {}
@@ -20552,6 +20562,8 @@ snapshots:
lodash.escaperegexp@4.1.2: {}
+ lodash.groupby@4.6.0: {}
+
lodash.isequal@4.5.0: {}
lodash.memoize@4.1.2: {}
@@ -24671,7 +24683,7 @@ snapshots:
xml-js@1.6.11:
dependencies:
- sax: 1.6.0
+ sax: 1.6.1
xml-name-validator@5.0.0: {}