iptv-doctor 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 IPTV Star contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # IPTV Doctor
2
+
3
+ Flagship CLI for checking, cleaning, and reporting on IPTV M3U/HLS playlists.
4
+
5
+ ## Run
6
+
7
+ Published package:
8
+
9
+ ```bash
10
+ npx iptv-doctor check playlist.m3u --report report.html --json report.json --csv report.csv
11
+ npx iptv-doctor report report.json --html report.html --csv report.csv --badge badge.json
12
+ npx iptv-doctor clean playlist.m3u --out clean.m3u
13
+ npx iptv-doctor epg check playlist.m3u guide.xml --json epg.json
14
+ npx iptv-doctor epg fix playlist.m3u guide.xml --out fixed.m3u
15
+ npx iptv-doctor badge report.json --out badge.json
16
+ npx iptv-doctor mcp --list-tools
17
+ npx iptv-doctor worldcup --country US --format xmltv --out worldcup.xml
18
+ npx iptv-doctor worldcup --country US --format html --out worldcup-guide.html
19
+ ```
20
+
21
+ Source checkout:
22
+
23
+ ```bash
24
+ pnpm --filter iptv-doctor doctor check playlist.m3u --report report.html --json report.json --csv report.csv
25
+ ```
26
+
27
+ `check` can also write `--badge badge.json` directly for Shields endpoint usage.
28
+ `mcp` starts a stdio MCP server; `--list-tools` prints the bundled tool names for verification.
29
+
30
+ IPTV Doctor does not provide streams, paid channel lists, Xtream credentials, Stalker portals, MAC lists, or DRM bypass tools.
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ const { runIptvDoctorCli } = await import("../dist/cli.js");
3
+
4
+ runIptvDoctorCli().catch((error) => {
5
+ console.error(error instanceof Error ? error.message : error);
6
+ process.exit(1);
7
+ });
package/dist/cli.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import { type ProbeOptions } from "iptv-doctor-core";
3
+ export interface IptvDoctorCliOptions extends ProbeOptions {
4
+ stdout?: (value: string) => void;
5
+ }
6
+ export declare function runIptvDoctorCli(argv?: string[], options?: IptvDoctorCliOptions): Promise<void>;
7
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAKA,OAAO,EAWL,KAAK,YAAY,EAClB,MAAM,kBAAkB,CAAC;AAc1B,MAAM,WAAW,oBAAqB,SAAQ,YAAY;IACxD,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC;AAED,wBAAsB,gBAAgB,CAAC,IAAI,WAAe,EAAE,OAAO,GAAE,oBAAyB,GAAG,OAAO,CAAC,IAAI,CAAC,CA4C7G"}
package/dist/cli.js ADDED
@@ -0,0 +1,191 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync, writeFileSync } from "node:fs";
3
+ import { generateICalendar, generateM3UPlaceholder, generateWorldCupGuideHtml, generateXMLTV } from "@bjia666/match2epg";
4
+ import { getWorldCup2026Dataset } from "iptv-sports-data";
5
+ import { IPTV_DOCTOR_MCP_TOOLS, runIptvDoctorMcpServer } from "./mcp.js";
6
+ import { parseM3U, probePlaylist, analyzeEpgMatches, renderShieldsBadgeJson, renderCleanM3U, renderCsvReport, renderDiagnosticsHtml, renderFixedEpgM3U, renderJsonReport } from "iptv-doctor-core";
7
+ function usage() {
8
+ return `Usage:
9
+ iptv-doctor check <playlist.m3u> --report report.html [--json report.json] [--csv report.csv] [--badge badge.json]
10
+ iptv-doctor clean <playlist.m3u> --out clean.m3u
11
+ iptv-doctor epg check <playlist.m3u> <guide.xml> --json epg.json
12
+ iptv-doctor epg fix <playlist.m3u> <guide.xml> --out fixed.m3u
13
+ iptv-doctor report <report.json> [--html report.html] [--csv report.csv] [--badge badge.json]
14
+ iptv-doctor badge <report.json> --out badge.json
15
+ iptv-doctor mcp [--list-tools]
16
+ iptv-doctor worldcup --country US --format xmltv|ics|m3u|html --out worldcup.xml`;
17
+ }
18
+ export async function runIptvDoctorCli(argv = process.argv, options = {}) {
19
+ const command = argv[2];
20
+ if (!command || command === "--help" || command === "-h" || command === "help") {
21
+ writeStdout(options, `${usage()}\n`);
22
+ return;
23
+ }
24
+ if (command === "check") {
25
+ await runCheck(argv.slice(3), options);
26
+ return;
27
+ }
28
+ if (command === "clean") {
29
+ await runClean(argv.slice(3), options);
30
+ return;
31
+ }
32
+ if (command === "worldcup") {
33
+ runWorldCup(argv.slice(3));
34
+ return;
35
+ }
36
+ if (command === "epg") {
37
+ runEpg(argv.slice(3));
38
+ return;
39
+ }
40
+ if (command === "badge") {
41
+ runBadge(argv.slice(3), options);
42
+ return;
43
+ }
44
+ if (command === "report") {
45
+ runReport(argv.slice(3), options);
46
+ return;
47
+ }
48
+ if (command === "mcp") {
49
+ await runMcp(argv.slice(3), options);
50
+ return;
51
+ }
52
+ throw new Error(usage());
53
+ }
54
+ async function runCheck(args, options) {
55
+ const input = args[0];
56
+ if (!input)
57
+ throw new Error(usage());
58
+ const report = readFlag(args, "--report") ?? "iptv-doctor-report.html";
59
+ const json = readFlag(args, "--json");
60
+ const csv = readFlag(args, "--csv");
61
+ const badge = readFlag(args, "--badge");
62
+ const playlist = readFileSync(input, "utf8");
63
+ const channels = parseM3U(playlist);
64
+ const diagnostics = await probePlaylist(channels, options);
65
+ writeFileSync(report, renderDiagnosticsHtml(diagnostics));
66
+ if (json)
67
+ writeFileSync(json, renderJsonReport(diagnostics));
68
+ if (csv)
69
+ writeFileSync(csv, renderCsvReport(diagnostics));
70
+ if (badge)
71
+ writeFileSync(badge, renderShieldsBadgeJson(diagnostics));
72
+ process.stdout.write(`Checked ${channels.length} channels. Report written to ${report}\n`);
73
+ }
74
+ async function runClean(args, options) {
75
+ const input = args[0];
76
+ const output = readFlag(args, "--out");
77
+ if (!input || !output)
78
+ throw new Error(usage());
79
+ const playlist = readFileSync(input, "utf8");
80
+ const channels = parseM3U(playlist);
81
+ const diagnostics = await probePlaylist(channels, options);
82
+ writeFileSync(output, renderCleanM3U(diagnostics));
83
+ process.stdout.write(`Wrote clean playlist to ${output}\n`);
84
+ }
85
+ function runEpg(args) {
86
+ const subcommand = args[0];
87
+ const playlistPath = args[1];
88
+ const guidePath = args[2];
89
+ if (!subcommand || !playlistPath || !guidePath)
90
+ throw new Error(usage());
91
+ const playlist = readFileSync(playlistPath, "utf8");
92
+ const guide = readFileSync(guidePath, "utf8");
93
+ if (subcommand === "check") {
94
+ const json = readFlag(args, "--json");
95
+ const result = `${JSON.stringify(analyzeEpgMatches(playlist, guide), null, 2)}\n`;
96
+ if (json) {
97
+ writeFileSync(json, result);
98
+ }
99
+ else {
100
+ process.stdout.write(result);
101
+ }
102
+ return;
103
+ }
104
+ if (subcommand === "fix") {
105
+ const output = readFlag(args, "--out");
106
+ if (!output)
107
+ throw new Error(usage());
108
+ writeFileSync(output, renderFixedEpgM3U(playlist, guide));
109
+ process.stdout.write(`Wrote EPG-fixed playlist to ${output}\n`);
110
+ return;
111
+ }
112
+ throw new Error(usage());
113
+ }
114
+ function runBadge(args, options) {
115
+ const input = args[0];
116
+ const output = readFlag(args, "--out");
117
+ if (!input || !output)
118
+ throw new Error(usage());
119
+ const diagnostics = readDiagnosticsReport(input);
120
+ writeFileSync(output, renderShieldsBadgeJson(diagnostics));
121
+ writeStdout(options, `Wrote health badge JSON to ${output}\n`);
122
+ }
123
+ function runReport(args, options) {
124
+ const input = args[0];
125
+ if (!input)
126
+ throw new Error(usage());
127
+ const html = readFlag(args, "--html");
128
+ const csv = readFlag(args, "--csv");
129
+ const badge = readFlag(args, "--badge");
130
+ if (!html && !csv && !badge)
131
+ throw new Error(usage());
132
+ const diagnostics = readDiagnosticsReport(input);
133
+ if (html)
134
+ writeFileSync(html, renderDiagnosticsHtml(diagnostics));
135
+ if (csv)
136
+ writeFileSync(csv, renderCsvReport(diagnostics));
137
+ if (badge)
138
+ writeFileSync(badge, renderShieldsBadgeJson(diagnostics));
139
+ writeStdout(options, `Rendered report assets from ${input}\n`);
140
+ }
141
+ async function runMcp(args, options) {
142
+ if (args.includes("--list-tools")) {
143
+ writeStdout(options, `${IPTV_DOCTOR_MCP_TOOLS.join("\n")}\n`);
144
+ return;
145
+ }
146
+ await runIptvDoctorMcpServer();
147
+ }
148
+ function runWorldCup(args) {
149
+ const country = (readFlag(args, "--country") ?? "US");
150
+ const format = readFlag(args, "--format") ?? "xmltv";
151
+ const output = readFlag(args, "--out");
152
+ if (!output)
153
+ throw new Error(usage());
154
+ if (!["US", "CA", "MX"].includes(country)) {
155
+ throw new Error("World Cup country must be one of US, CA, MX.");
156
+ }
157
+ const dataset = getWorldCup2026Dataset();
158
+ const content = format === "xmltv"
159
+ ? generateXMLTV(dataset, country)
160
+ : format === "ics"
161
+ ? generateICalendar(dataset)
162
+ : format === "m3u"
163
+ ? generateM3UPlaceholder(dataset, country)
164
+ : format === "html"
165
+ ? generateWorldCupGuideHtml(dataset, country)
166
+ : undefined;
167
+ if (!content)
168
+ throw new Error("World Cup format must be one of xmltv, ics, m3u, html.");
169
+ writeFileSync(output, content);
170
+ process.stdout.write(`Wrote World Cup ${format} metadata to ${output}\n`);
171
+ }
172
+ function readFlag(args, flag) {
173
+ const index = args.indexOf(flag);
174
+ return index >= 0 ? args[index + 1] : undefined;
175
+ }
176
+ function readDiagnosticsReport(input) {
177
+ const parsed = JSON.parse(readFileSync(input, "utf8"));
178
+ if (!Array.isArray(parsed.diagnostics)) {
179
+ throw new Error("Input must be a JSON diagnostics report generated by iptv-doctor check --json.");
180
+ }
181
+ return parsed.diagnostics;
182
+ }
183
+ function writeStdout(options, value) {
184
+ (options.stdout ?? process.stdout.write.bind(process.stdout))(value);
185
+ }
186
+ if (import.meta.url === `file://${process.argv[1]}`) {
187
+ runIptvDoctorCli().catch((error) => {
188
+ console.error(error instanceof Error ? error.message : error);
189
+ process.exit(1);
190
+ });
191
+ }
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare const IPTV_DOCTOR_MCP_TOOLS: readonly ["list_worldcup_matches", "us_legal_viewing_paths", "generate_us_xmltv"];
3
+ export declare function createIptvDoctorMcpServer(): McpServer;
4
+ export declare function runIptvDoctorMcpServer(): Promise<void>;
5
+ //# sourceMappingURL=mcp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAKpE,eAAO,MAAM,qBAAqB,mFAAoF,CAAC;AAEvH,wBAAgB,yBAAyB,IAAI,SAAS,CAuDrD;AAED,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC,CAG5D"}
package/dist/mcp.js ADDED
@@ -0,0 +1,49 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { generateXMLTV } from "@bjia666/match2epg";
4
+ import { getWorldCup2026Dataset } from "iptv-sports-data";
5
+ export const IPTV_DOCTOR_MCP_TOOLS = ["list_worldcup_matches", "us_legal_viewing_paths", "generate_us_xmltv"];
6
+ export function createIptvDoctorMcpServer() {
7
+ const dataset = getWorldCup2026Dataset();
8
+ const server = new McpServer({
9
+ name: "iptv-doctor",
10
+ version: "0.1.0"
11
+ });
12
+ server.registerTool("list_worldcup_matches", {
13
+ description: "List sample FIFA World Cup 2026 matches from the local metadata dataset."
14
+ }, async () => ({
15
+ content: [
16
+ {
17
+ type: "text",
18
+ text: dataset.fixtures
19
+ .map((fixture) => `${fixture.id}: ${fixture.homeTeam} vs ${fixture.awayTeam} at ${fixture.kickoffUtc} (${fixture.status})`)
20
+ .join("\n")
21
+ }
22
+ ]
23
+ }));
24
+ server.registerTool("us_legal_viewing_paths", {
25
+ description: "Return US legal viewing metadata stored by this project."
26
+ }, async () => ({
27
+ content: [
28
+ {
29
+ type: "text",
30
+ text: JSON.stringify(dataset.broadcasters.US?.channels ?? [], null, 2)
31
+ }
32
+ ]
33
+ }));
34
+ server.registerTool("generate_us_xmltv", {
35
+ description: "Generate XMLTV metadata for the US World Cup channel mapping."
36
+ }, async () => ({
37
+ content: [
38
+ {
39
+ type: "text",
40
+ text: generateXMLTV(dataset, "US")
41
+ }
42
+ ]
43
+ }));
44
+ return server;
45
+ }
46
+ export async function runIptvDoctorMcpServer() {
47
+ const server = createIptvDoctorMcpServer();
48
+ await server.connect(new StdioServerTransport());
49
+ }
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "iptv-doctor",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "bin": {
7
+ "iptv-doctor": "./bin/iptv-doctor.mjs"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "dist",
12
+ "README.md",
13
+ "package.json"
14
+ ],
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "dependencies": {
19
+ "@modelcontextprotocol/sdk": "^1.18.0",
20
+ "iptv-doctor-core": "0.1.0",
21
+ "@bjia666/match2epg": "0.1.0",
22
+ "iptv-sports-data": "0.1.0"
23
+ },
24
+ "scripts": {
25
+ "build": "tsc -b",
26
+ "doctor": "node --conditions=source --import tsx ./src/cli.ts"
27
+ }
28
+ }