@bnhf/prismcast 1.3.4-2026.2.19
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.md +7 -0
- package/README.md +347 -0
- package/bin/prismcast +6 -0
- package/dist/app.d.ts +6 -0
- package/dist/app.js +315 -0
- package/dist/app.js.map +1 -0
- package/dist/browser/cdp.d.ts +38 -0
- package/dist/browser/cdp.js +155 -0
- package/dist/browser/cdp.js.map +1 -0
- package/dist/browser/channelSelection.d.ts +65 -0
- package/dist/browser/channelSelection.js +202 -0
- package/dist/browser/channelSelection.js.map +1 -0
- package/dist/browser/display.d.ts +34 -0
- package/dist/browser/display.js +54 -0
- package/dist/browser/display.js.map +1 -0
- package/dist/browser/index.d.ts +205 -0
- package/dist/browser/index.js +1205 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/tuning/fox.d.ts +2 -0
- package/dist/browser/tuning/fox.js +83 -0
- package/dist/browser/tuning/fox.js.map +1 -0
- package/dist/browser/tuning/hbo.d.ts +2 -0
- package/dist/browser/tuning/hbo.js +237 -0
- package/dist/browser/tuning/hbo.js.map +1 -0
- package/dist/browser/tuning/hulu.d.ts +2 -0
- package/dist/browser/tuning/hulu.js +550 -0
- package/dist/browser/tuning/hulu.js.map +1 -0
- package/dist/browser/tuning/sling.d.ts +2 -0
- package/dist/browser/tuning/sling.js +518 -0
- package/dist/browser/tuning/sling.js.map +1 -0
- package/dist/browser/tuning/thumbnailRow.d.ts +2 -0
- package/dist/browser/tuning/thumbnailRow.js +108 -0
- package/dist/browser/tuning/thumbnailRow.js.map +1 -0
- package/dist/browser/tuning/tileClick.d.ts +2 -0
- package/dist/browser/tuning/tileClick.js +103 -0
- package/dist/browser/tuning/tileClick.js.map +1 -0
- package/dist/browser/tuning/youtubeTv.d.ts +2 -0
- package/dist/browser/tuning/youtubeTv.js +182 -0
- package/dist/browser/tuning/youtubeTv.js.map +1 -0
- package/dist/browser/video.d.ts +289 -0
- package/dist/browser/video.js +996 -0
- package/dist/browser/video.js.map +1 -0
- package/dist/channels/index.d.ts +3 -0
- package/dist/channels/index.js +392 -0
- package/dist/channels/index.js.map +1 -0
- package/dist/config/index.d.ts +53 -0
- package/dist/config/index.js +233 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/presets.d.ts +98 -0
- package/dist/config/presets.js +241 -0
- package/dist/config/presets.js.map +1 -0
- package/dist/config/profiles.d.ts +79 -0
- package/dist/config/profiles.js +245 -0
- package/dist/config/profiles.js.map +1 -0
- package/dist/config/providers.d.ts +120 -0
- package/dist/config/providers.js +450 -0
- package/dist/config/providers.js.map +1 -0
- package/dist/config/sites.d.ts +22 -0
- package/dist/config/sites.js +377 -0
- package/dist/config/sites.js.map +1 -0
- package/dist/config/userChannels.d.ts +178 -0
- package/dist/config/userChannels.js +543 -0
- package/dist/config/userChannels.js.map +1 -0
- package/dist/config/userConfig.d.ts +235 -0
- package/dist/config/userConfig.js +913 -0
- package/dist/config/userConfig.js.map +1 -0
- package/dist/hdhr/channelMap.d.ts +21 -0
- package/dist/hdhr/channelMap.js +82 -0
- package/dist/hdhr/channelMap.js.map +1 -0
- package/dist/hdhr/deviceId.d.ts +11 -0
- package/dist/hdhr/deviceId.js +84 -0
- package/dist/hdhr/deviceId.js.map +1 -0
- package/dist/hdhr/discover.d.ts +6 -0
- package/dist/hdhr/discover.js +155 -0
- package/dist/hdhr/discover.js.map +1 -0
- package/dist/hdhr/index.d.ts +9 -0
- package/dist/hdhr/index.js +87 -0
- package/dist/hdhr/index.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +144 -0
- package/dist/index.js.map +1 -0
- package/dist/routes/assets.d.ts +6 -0
- package/dist/routes/assets.js +79 -0
- package/dist/routes/assets.js.map +1 -0
- package/dist/routes/auth.d.ts +6 -0
- package/dist/routes/auth.js +77 -0
- package/dist/routes/auth.js.map +1 -0
- package/dist/routes/channels.d.ts +6 -0
- package/dist/routes/channels.js +40 -0
- package/dist/routes/channels.js.map +1 -0
- package/dist/routes/components.d.ts +138 -0
- package/dist/routes/components.js +210 -0
- package/dist/routes/components.js.map +1 -0
- package/dist/routes/config.d.ts +72 -0
- package/dist/routes/config.js +1977 -0
- package/dist/routes/config.js.map +1 -0
- package/dist/routes/debug.d.ts +6 -0
- package/dist/routes/debug.js +274 -0
- package/dist/routes/debug.js.map +1 -0
- package/dist/routes/health.d.ts +6 -0
- package/dist/routes/health.js +85 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/hls.d.ts +6 -0
- package/dist/routes/hls.js +25 -0
- package/dist/routes/hls.js.map +1 -0
- package/dist/routes/index.d.ts +19 -0
- package/dist/routes/index.js +49 -0
- package/dist/routes/index.js.map +1 -0
- package/dist/routes/logs.d.ts +6 -0
- package/dist/routes/logs.js +164 -0
- package/dist/routes/logs.js.map +1 -0
- package/dist/routes/mpegts.d.ts +6 -0
- package/dist/routes/mpegts.js +19 -0
- package/dist/routes/mpegts.js.map +1 -0
- package/dist/routes/play.d.ts +6 -0
- package/dist/routes/play.js +18 -0
- package/dist/routes/play.js.map +1 -0
- package/dist/routes/playlist.d.ts +36 -0
- package/dist/routes/playlist.js +134 -0
- package/dist/routes/playlist.js.map +1 -0
- package/dist/routes/root.d.ts +6 -0
- package/dist/routes/root.js +2920 -0
- package/dist/routes/root.js.map +1 -0
- package/dist/routes/streams.d.ts +6 -0
- package/dist/routes/streams.js +88 -0
- package/dist/routes/streams.js.map +1 -0
- package/dist/routes/theme.d.ts +15 -0
- package/dist/routes/theme.js +275 -0
- package/dist/routes/theme.js.map +1 -0
- package/dist/routes/ui.d.ts +56 -0
- package/dist/routes/ui.js +354 -0
- package/dist/routes/ui.js.map +1 -0
- package/dist/service/commands.d.ts +41 -0
- package/dist/service/commands.js +391 -0
- package/dist/service/commands.js.map +1 -0
- package/dist/service/generators.d.ts +33 -0
- package/dist/service/generators.js +432 -0
- package/dist/service/generators.js.map +1 -0
- package/dist/service/index.d.ts +2 -0
- package/dist/service/index.js +7 -0
- package/dist/service/index.js.map +1 -0
- package/dist/streaming/clients.d.ts +48 -0
- package/dist/streaming/clients.js +114 -0
- package/dist/streaming/clients.js.map +1 -0
- package/dist/streaming/fmp4Segmenter.d.ts +61 -0
- package/dist/streaming/fmp4Segmenter.js +461 -0
- package/dist/streaming/fmp4Segmenter.js.map +1 -0
- package/dist/streaming/hls.d.ts +120 -0
- package/dist/streaming/hls.js +722 -0
- package/dist/streaming/hls.js.map +1 -0
- package/dist/streaming/hlsSegments.d.ts +54 -0
- package/dist/streaming/hlsSegments.js +162 -0
- package/dist/streaming/hlsSegments.js.map +1 -0
- package/dist/streaming/lifecycle.d.ts +33 -0
- package/dist/streaming/lifecycle.js +185 -0
- package/dist/streaming/lifecycle.js.map +1 -0
- package/dist/streaming/monitor.d.ts +74 -0
- package/dist/streaming/monitor.js +1310 -0
- package/dist/streaming/monitor.js.map +1 -0
- package/dist/streaming/mp4Parser.d.ts +74 -0
- package/dist/streaming/mp4Parser.js +566 -0
- package/dist/streaming/mp4Parser.js.map +1 -0
- package/dist/streaming/mpegts.d.ts +14 -0
- package/dist/streaming/mpegts.js +248 -0
- package/dist/streaming/mpegts.js.map +1 -0
- package/dist/streaming/registry.d.ts +119 -0
- package/dist/streaming/registry.js +127 -0
- package/dist/streaming/registry.js.map +1 -0
- package/dist/streaming/setup.d.ts +135 -0
- package/dist/streaming/setup.js +670 -0
- package/dist/streaming/setup.js.map +1 -0
- package/dist/streaming/showInfo.d.ts +30 -0
- package/dist/streaming/showInfo.js +362 -0
- package/dist/streaming/showInfo.js.map +1 -0
- package/dist/streaming/statusEmitter.d.ts +125 -0
- package/dist/streaming/statusEmitter.js +139 -0
- package/dist/streaming/statusEmitter.js.map +1 -0
- package/dist/types/index.d.ts +403 -0
- package/dist/types/index.js +6 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/debugFilter.d.ts +38 -0
- package/dist/utils/debugFilter.js +157 -0
- package/dist/utils/debugFilter.js.map +1 -0
- package/dist/utils/delay.d.ts +6 -0
- package/dist/utils/delay.js +15 -0
- package/dist/utils/delay.js.map +1 -0
- package/dist/utils/errors.d.ts +15 -0
- package/dist/utils/errors.js +40 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/evaluate.d.ts +51 -0
- package/dist/utils/evaluate.js +124 -0
- package/dist/utils/evaluate.js.map +1 -0
- package/dist/utils/ffmpeg.d.ts +65 -0
- package/dist/utils/ffmpeg.js +317 -0
- package/dist/utils/ffmpeg.js.map +1 -0
- package/dist/utils/fileLogger.d.ts +25 -0
- package/dist/utils/fileLogger.js +248 -0
- package/dist/utils/fileLogger.js.map +1 -0
- package/dist/utils/format.d.ts +16 -0
- package/dist/utils/format.js +46 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/html.d.ts +6 -0
- package/dist/utils/html.js +24 -0
- package/dist/utils/html.js.map +1 -0
- package/dist/utils/index.d.ts +15 -0
- package/dist/utils/index.js +20 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logEmitter.d.ts +17 -0
- package/dist/utils/logEmitter.js +30 -0
- package/dist/utils/logEmitter.js.map +1 -0
- package/dist/utils/logger.d.ts +82 -0
- package/dist/utils/logger.js +219 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/m3u.d.ts +32 -0
- package/dist/utils/m3u.js +148 -0
- package/dist/utils/m3u.js.map +1 -0
- package/dist/utils/morganStream.d.ts +7 -0
- package/dist/utils/morganStream.js +33 -0
- package/dist/utils/morganStream.js.map +1 -0
- package/dist/utils/platform.d.ts +64 -0
- package/dist/utils/platform.js +157 -0
- package/dist/utils/platform.js.map +1 -0
- package/dist/utils/retry.d.ts +15 -0
- package/dist/utils/retry.js +82 -0
- package/dist/utils/retry.js.map +1 -0
- package/dist/utils/streamContext.d.ts +28 -0
- package/dist/utils/streamContext.js +33 -0
- package/dist/utils/streamContext.js.map +1 -0
- package/dist/utils/version.d.ts +37 -0
- package/dist/utils/version.js +228 -0
- package/dist/utils/version.js.map +1 -0
- package/package.json +92 -0
- package/prismcast.png +0 -0
- package/prismcast.svg +74 -0
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
import { buildProviderGroups, getAllProviderTags, getProviderSelections, isChannelAvailableByProvider, isProviderVariant, setEnabledProviders, setProviderSelections } from "./providers.js";
|
|
2
|
+
import { CONFIG } from "./index.js";
|
|
3
|
+
import { LOG } from "../utils/index.js";
|
|
4
|
+
import { PREDEFINED_CHANNELS } from "../channels/index.js";
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
const { promises: fsPromises } = fs;
|
|
9
|
+
/* The channels file is stored in the same data directory as the config file (~/.prismcast).
|
|
10
|
+
*/
|
|
11
|
+
const dataDir = path.join(os.homedir(), ".prismcast");
|
|
12
|
+
const channelsFilePath = path.join(dataDir, "channels.json");
|
|
13
|
+
/**
|
|
14
|
+
* Returns the path to the user channels file.
|
|
15
|
+
* @returns The absolute path to ~/.prismcast/channels.json.
|
|
16
|
+
*/
|
|
17
|
+
export function getUserChannelsFilePath() {
|
|
18
|
+
return channelsFilePath;
|
|
19
|
+
}
|
|
20
|
+
/* These functions handle reading and writing the channels file. All operations are async and handle errors gracefully.
|
|
21
|
+
*/
|
|
22
|
+
// Module-level storage for loaded user channels. This is populated at startup and used by getAllChannels().
|
|
23
|
+
let loadedUserChannels = {};
|
|
24
|
+
let userChannelsParseError = false;
|
|
25
|
+
let userChannelsParseErrorMessage;
|
|
26
|
+
/**
|
|
27
|
+
* Returns whether the user channels file had a parse error.
|
|
28
|
+
* @returns True if the channels file exists but contains invalid JSON.
|
|
29
|
+
*/
|
|
30
|
+
export function hasChannelsParseError() {
|
|
31
|
+
return userChannelsParseError;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Returns the parse error message if the channels file had a parse error.
|
|
35
|
+
* @returns The error message or undefined.
|
|
36
|
+
*/
|
|
37
|
+
export function getChannelsParseErrorMessage() {
|
|
38
|
+
return userChannelsParseErrorMessage;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Loads user channels from the channels file. Returns an empty map if the file doesn't exist, and sets parseError if the file exists but contains invalid JSON.
|
|
42
|
+
* The file can contain a special `providerSelections` key with user's provider preferences, which is extracted separately from channels.
|
|
43
|
+
* @returns The loaded channels with parse status and provider selections.
|
|
44
|
+
*/
|
|
45
|
+
export async function loadUserChannels() {
|
|
46
|
+
try {
|
|
47
|
+
const content = await fsPromises.readFile(channelsFilePath, "utf-8");
|
|
48
|
+
try {
|
|
49
|
+
const parsed = JSON.parse(content);
|
|
50
|
+
// Extract providerSelections if present — it's not a channel, it's metadata.
|
|
51
|
+
const providerSelections = {};
|
|
52
|
+
const channels = {};
|
|
53
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
54
|
+
if (key === "providerSelections") {
|
|
55
|
+
// Copy provider selections if it's an object.
|
|
56
|
+
if ((typeof value === "object") && (value !== null) && !Array.isArray(value)) {
|
|
57
|
+
for (const [selKey, selValue] of Object.entries(value)) {
|
|
58
|
+
if (typeof selValue === "string") {
|
|
59
|
+
providerSelections[selKey] = selValue;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else if ((typeof value === "object") && (value !== null) && !Array.isArray(value)) {
|
|
65
|
+
// It's a channel definition.
|
|
66
|
+
channels[key] = value;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return { channels, parseError: false, providerSelections };
|
|
70
|
+
}
|
|
71
|
+
catch (parseError) {
|
|
72
|
+
const message = (parseError instanceof Error) ? parseError.message : String(parseError);
|
|
73
|
+
LOG.warn("Invalid JSON in channels file %s: %s. Using predefined channels only.", channelsFilePath, message);
|
|
74
|
+
return { channels: {}, parseError: true, parseErrorMessage: message, providerSelections: {} };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
// File doesn't exist - this is normal, use predefined channels only.
|
|
79
|
+
if (error.code === "ENOENT") {
|
|
80
|
+
return { channels: {}, parseError: false, providerSelections: {} };
|
|
81
|
+
}
|
|
82
|
+
// Other read errors - log and use predefined channels.
|
|
83
|
+
LOG.warn("Failed to read channels file %s: %s. Using predefined channels only.", channelsFilePath, (error instanceof Error) ? error.message : String(error));
|
|
84
|
+
return { channels: {}, parseError: false, providerSelections: {} };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Saves user channels to the channels file and updates the in-memory cache. Changes take effect immediately for new stream requests without requiring a server
|
|
89
|
+
* restart. Creates the data directory if it doesn't exist. Provider selections are also saved if any exist.
|
|
90
|
+
* @param channels - The channels to save.
|
|
91
|
+
* @throws If the file cannot be written.
|
|
92
|
+
*/
|
|
93
|
+
export async function saveUserChannels(channels) {
|
|
94
|
+
// Ensure data directory exists.
|
|
95
|
+
await fsPromises.mkdir(dataDir, { recursive: true });
|
|
96
|
+
// Sort channels by key for consistent output.
|
|
97
|
+
const sortedChannels = {};
|
|
98
|
+
const sortedKeys = Object.keys(channels).sort();
|
|
99
|
+
for (const key of sortedKeys) {
|
|
100
|
+
sortedChannels[key] = channels[key];
|
|
101
|
+
}
|
|
102
|
+
// Include provider selections if any exist.
|
|
103
|
+
const selections = getProviderSelections();
|
|
104
|
+
if (Object.keys(selections).length > 0) {
|
|
105
|
+
// Sort provider selections for consistent output.
|
|
106
|
+
const sortedSelections = {};
|
|
107
|
+
const selectionKeys = Object.keys(selections).sort();
|
|
108
|
+
for (const key of selectionKeys) {
|
|
109
|
+
sortedSelections[key] = selections[key];
|
|
110
|
+
}
|
|
111
|
+
sortedChannels.providerSelections = sortedSelections;
|
|
112
|
+
}
|
|
113
|
+
// Write channels with pretty formatting for readability.
|
|
114
|
+
const content = JSON.stringify(sortedChannels, null, 2);
|
|
115
|
+
await fsPromises.writeFile(channelsFilePath, content + "\n", "utf-8");
|
|
116
|
+
// Update in-memory cache so changes take effect immediately for new stream requests.
|
|
117
|
+
loadedUserChannels = { ...channels };
|
|
118
|
+
// Refresh provider groups so channelsRef reflects the new channel data. This ensures getResolvedChannel() returns correct data after modifications.
|
|
119
|
+
buildProviderGroups(getMergedChannelMap());
|
|
120
|
+
// Clear any previous parse error since we're writing valid data.
|
|
121
|
+
userChannelsParseError = false;
|
|
122
|
+
userChannelsParseErrorMessage = undefined;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Deletes a user channel by key.
|
|
126
|
+
* @param key - The channel key to delete.
|
|
127
|
+
* @throws If the file cannot be read or written.
|
|
128
|
+
*/
|
|
129
|
+
export async function deleteUserChannel(key) {
|
|
130
|
+
const result = await loadUserChannels();
|
|
131
|
+
// If parse error, we can't modify - just log a warning.
|
|
132
|
+
if (result.parseError) {
|
|
133
|
+
throw new Error("Cannot delete channel: channels file contains invalid JSON.");
|
|
134
|
+
}
|
|
135
|
+
// Remove the channel.
|
|
136
|
+
Reflect.deleteProperty(result.channels, key);
|
|
137
|
+
// Save the modified channels.
|
|
138
|
+
await saveUserChannels(result.channels);
|
|
139
|
+
LOG.info("User channel '%s' deleted.", key);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Resets all user channels by deleting the channels file.
|
|
143
|
+
* @throws If the file exists but cannot be deleted.
|
|
144
|
+
*/
|
|
145
|
+
export async function resetUserChannels() {
|
|
146
|
+
try {
|
|
147
|
+
await fsPromises.unlink(channelsFilePath);
|
|
148
|
+
LOG.info("Channels file deleted, using predefined channels only.");
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
// File doesn't exist - already using predefined channels.
|
|
152
|
+
if (error.code === "ENOENT") {
|
|
153
|
+
LOG.info("Channels file does not exist, already using predefined channels only.");
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/* User channels are loaded at server startup and stored in module-level state. This avoids repeated file reads during request handling.
|
|
160
|
+
*/
|
|
161
|
+
/**
|
|
162
|
+
* Initializes user channels by loading them from the file. This should be called once at server startup. Also builds provider groups and loads provider selections.
|
|
163
|
+
*/
|
|
164
|
+
export async function initializeUserChannels() {
|
|
165
|
+
const result = await loadUserChannels();
|
|
166
|
+
loadedUserChannels = result.channels;
|
|
167
|
+
userChannelsParseError = result.parseError;
|
|
168
|
+
userChannelsParseErrorMessage = result.parseErrorMessage;
|
|
169
|
+
// Load provider selections from the file.
|
|
170
|
+
setProviderSelections(result.providerSelections);
|
|
171
|
+
// Load enabled providers from the configuration, validating that each tag is recognized. Invalid tags (e.g., from hand-edited config.json typos) are stripped
|
|
172
|
+
// silently after logging a warning. Validation must happen after buildProviderGroups() because getAllProviderTags() depends on the groups being built.
|
|
173
|
+
const configuredProviders = CONFIG.channels.enabledProviders;
|
|
174
|
+
// Build the merged channels map and then build provider groups.
|
|
175
|
+
const mergedChannels = getMergedChannelMap();
|
|
176
|
+
buildProviderGroups(mergedChannels);
|
|
177
|
+
// Now that provider groups are built, validate the configured provider tags. Strip any unrecognized tags and warn.
|
|
178
|
+
if (configuredProviders.length > 0) {
|
|
179
|
+
const knownTags = new Set(getAllProviderTags().map((t) => t.tag));
|
|
180
|
+
const validTags = configuredProviders.filter((tag) => knownTags.has(tag));
|
|
181
|
+
const invalidTags = configuredProviders.filter((tag) => !knownTags.has(tag));
|
|
182
|
+
if (invalidTags.length > 0) {
|
|
183
|
+
LOG.warn("Ignoring unrecognized provider tags in configuration: %s.", invalidTags.join(", "));
|
|
184
|
+
}
|
|
185
|
+
setEnabledProviders(validTags);
|
|
186
|
+
CONFIG.channels.enabledProviders = validTags;
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
setEnabledProviders(configuredProviders);
|
|
190
|
+
}
|
|
191
|
+
const userCount = Object.keys(loadedUserChannels).length;
|
|
192
|
+
const predefinedCount = Object.keys(PREDEFINED_CHANNELS).length;
|
|
193
|
+
const totalCount = userCount + predefinedCount;
|
|
194
|
+
if (userCount > 0) {
|
|
195
|
+
LOG.info("Loaded %d channels (%d user, %d predefined).", totalCount, userCount, predefinedCount);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
LOG.info("Loaded %d channels.", totalCount);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Returns the merged channel map (predefined + user) without filtering by enabled status or provider variants. Used internally for building provider groups.
|
|
203
|
+
* @returns The complete merged channel map.
|
|
204
|
+
*/
|
|
205
|
+
function getMergedChannelMap() {
|
|
206
|
+
const result = { ...PREDEFINED_CHANNELS };
|
|
207
|
+
for (const [key, channel] of Object.entries(loadedUserChannels)) {
|
|
208
|
+
result[key] = channel;
|
|
209
|
+
}
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
212
|
+
/* The getChannelListing() function is the single source of truth for merging predefined channels with user channels. It returns enriched entries with source
|
|
213
|
+
* classification and enabled status. All other channel retrieval functions that need merged data build on top of it.
|
|
214
|
+
*/
|
|
215
|
+
/**
|
|
216
|
+
* Returns the full channel listing with source classification and enabled status. This is the authoritative merge point for predefined and user channels — all
|
|
217
|
+
* code that needs a merged view of channels should use this function (or getAllChannels() which delegates to it).
|
|
218
|
+
*
|
|
219
|
+
* For each channel key, the source is classified as:
|
|
220
|
+
* - "predefined": exists only in predefined channels
|
|
221
|
+
* - "user": exists only in user channels
|
|
222
|
+
* - "override": exists in both (user channel data takes precedence)
|
|
223
|
+
*
|
|
224
|
+
* The enabled field reflects whether the channel is available for streaming. Predefined-only channels can be disabled via configuration; user and override
|
|
225
|
+
* channels are always enabled.
|
|
226
|
+
*
|
|
227
|
+
* Provider variants (non-canonical keys in provider groups) are filtered out from this listing — they are accessed via the provider selection mechanism instead.
|
|
228
|
+
*
|
|
229
|
+
* IMPORTANT: This function preserves object references from PREDEFINED_CHANNELS and loadedUserChannels. The provider system (providers.ts) relies on this behavior
|
|
230
|
+
* to detect user overrides via reference comparison. Do not clone channel objects when building the listing.
|
|
231
|
+
* @returns Sorted array of channel listing entries.
|
|
232
|
+
*/
|
|
233
|
+
export function getChannelListing() {
|
|
234
|
+
const allKeys = new Set([...Object.keys(PREDEFINED_CHANNELS), ...Object.keys(loadedUserChannels)]);
|
|
235
|
+
const listing = [];
|
|
236
|
+
for (const key of allKeys) {
|
|
237
|
+
// Skip provider variants — they're accessed via provider selection, not as separate channels.
|
|
238
|
+
if (isProviderVariant(key)) {
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
const isPredefined = key in PREDEFINED_CHANNELS;
|
|
242
|
+
const isUser = key in loadedUserChannels;
|
|
243
|
+
// Determine source classification. User channel data takes precedence on key conflicts.
|
|
244
|
+
let source;
|
|
245
|
+
if (isPredefined && isUser) {
|
|
246
|
+
source = "override";
|
|
247
|
+
}
|
|
248
|
+
else if (isUser) {
|
|
249
|
+
source = "user";
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
source = "predefined";
|
|
253
|
+
}
|
|
254
|
+
listing.push({
|
|
255
|
+
availableByProvider: isChannelAvailableByProvider(key),
|
|
256
|
+
channel: isUser ? loadedUserChannels[key] : PREDEFINED_CHANNELS[key],
|
|
257
|
+
enabled: !isPredefinedChannelDisabled(key),
|
|
258
|
+
key,
|
|
259
|
+
source
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
// Sort alphabetically by key for consistent ordering across all callers.
|
|
263
|
+
listing.sort((a, b) => a.key.localeCompare(b.key));
|
|
264
|
+
return listing;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Returns all available channels (predefined + user), with user channels taking precedence on key conflicts. Disabled predefined channels are excluded unless they
|
|
268
|
+
* have a user override. Built on top of getChannelListing() to ensure a single merging code path.
|
|
269
|
+
* @returns The merged channel map with disabled predefined channels filtered out.
|
|
270
|
+
*/
|
|
271
|
+
export function getAllChannels() {
|
|
272
|
+
const result = {};
|
|
273
|
+
for (const entry of getChannelListing()) {
|
|
274
|
+
if (entry.enabled && entry.availableByProvider) {
|
|
275
|
+
result[entry.key] = entry.channel;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return result;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Returns the loaded user channels (without predefined channels).
|
|
282
|
+
* @returns The user channel map.
|
|
283
|
+
*/
|
|
284
|
+
export function getUserChannels() {
|
|
285
|
+
return { ...loadedUserChannels };
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Checks if a channel key exists in the predefined channels.
|
|
289
|
+
* @param key - The channel key to check.
|
|
290
|
+
* @returns True if the channel is predefined.
|
|
291
|
+
*/
|
|
292
|
+
export function isPredefinedChannel(key) {
|
|
293
|
+
return key in PREDEFINED_CHANNELS;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Checks if a channel key exists in the user channels.
|
|
297
|
+
* @param key - The channel key to check.
|
|
298
|
+
* @returns True if the channel is user-defined.
|
|
299
|
+
*/
|
|
300
|
+
export function isUserChannel(key) {
|
|
301
|
+
return key in loadedUserChannels;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Checks if a user channel overrides a predefined channel (same key exists in both).
|
|
305
|
+
* @param key - The channel key to check.
|
|
306
|
+
* @returns True if the user channel overrides a predefined channel.
|
|
307
|
+
*/
|
|
308
|
+
export function isOverrideChannel(key) {
|
|
309
|
+
return isPredefinedChannel(key) && isUserChannel(key);
|
|
310
|
+
}
|
|
311
|
+
/* Users can disable predefined channels to exclude them from the playlist and block streaming. Disabled channels appear grayed out in the UI with an option to
|
|
312
|
+
* re-enable. This is useful for users who don't want certain predefined channels cluttering their channel list.
|
|
313
|
+
*/
|
|
314
|
+
/**
|
|
315
|
+
* Checks if a predefined channel is disabled.
|
|
316
|
+
* @param key - The channel key to check.
|
|
317
|
+
* @returns True if the channel is predefined and disabled.
|
|
318
|
+
*/
|
|
319
|
+
export function isPredefinedChannelDisabled(key) {
|
|
320
|
+
// Only predefined channels can be disabled via this mechanism.
|
|
321
|
+
if (!isPredefinedChannel(key)) {
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
// If a user channel overrides this predefined channel, the predefined channel's disabled state is irrelevant.
|
|
325
|
+
if (isUserChannel(key)) {
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
return CONFIG.channels.disabledPredefined.includes(key);
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Returns the list of disabled predefined channel keys.
|
|
332
|
+
* @returns Array of disabled channel keys.
|
|
333
|
+
*/
|
|
334
|
+
export function getDisabledPredefinedChannels() {
|
|
335
|
+
return [...CONFIG.channels.disabledPredefined];
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Returns all predefined channels regardless of disabled state. Used by the UI to show all predefined channels including disabled ones.
|
|
339
|
+
* @returns The predefined channel map.
|
|
340
|
+
*/
|
|
341
|
+
export function getPredefinedChannels() {
|
|
342
|
+
return { ...PREDEFINED_CHANNELS };
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Checks if a channel is available for streaming. A channel is available if it exists in the merged channel map returned by getAllChannels(), which already
|
|
346
|
+
* excludes disabled predefined channels (unless overridden by a user channel).
|
|
347
|
+
* @param key - The channel key to check.
|
|
348
|
+
* @returns True if the channel can be streamed.
|
|
349
|
+
*/
|
|
350
|
+
export function isChannelAvailable(key) {
|
|
351
|
+
return key in getAllChannels();
|
|
352
|
+
}
|
|
353
|
+
/* These functions validate channel data before saving.
|
|
354
|
+
*/
|
|
355
|
+
/**
|
|
356
|
+
* Validates a channel key for format and uniqueness.
|
|
357
|
+
* @param key - The channel key to validate.
|
|
358
|
+
* @param isNew - True if this is a new channel (checks for duplicates among user channels).
|
|
359
|
+
* @returns Error message if invalid, undefined if valid.
|
|
360
|
+
*/
|
|
361
|
+
export function validateChannelKey(key, isNew) {
|
|
362
|
+
// Check for empty key.
|
|
363
|
+
if (!key || (key.trim() === "")) {
|
|
364
|
+
return "Channel key is required.";
|
|
365
|
+
}
|
|
366
|
+
// Check format: lowercase alphanumeric and hyphens only.
|
|
367
|
+
if (!/^[a-z0-9-]+$/.test(key)) {
|
|
368
|
+
return "Channel key must contain only lowercase letters, numbers, and hyphens.";
|
|
369
|
+
}
|
|
370
|
+
// Check length.
|
|
371
|
+
if (key.length > 50) {
|
|
372
|
+
return "Channel key must be 50 characters or less.";
|
|
373
|
+
}
|
|
374
|
+
// Check for duplicates when adding new channel.
|
|
375
|
+
if (isNew && isUserChannel(key)) {
|
|
376
|
+
return "A user channel with this key already exists.";
|
|
377
|
+
}
|
|
378
|
+
return undefined;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Validates a channel URL.
|
|
382
|
+
* @param url - The URL to validate.
|
|
383
|
+
* @returns Error message if invalid, undefined if valid.
|
|
384
|
+
*/
|
|
385
|
+
export function validateChannelUrl(url) {
|
|
386
|
+
// Check for empty URL.
|
|
387
|
+
if (!url || (url.trim() === "")) {
|
|
388
|
+
return "URL is required.";
|
|
389
|
+
}
|
|
390
|
+
// Check URL format.
|
|
391
|
+
try {
|
|
392
|
+
const parsed = new URL(url);
|
|
393
|
+
// Only allow http and https protocols.
|
|
394
|
+
if ((parsed.protocol !== "http:") && (parsed.protocol !== "https:")) {
|
|
395
|
+
return "URL must use http or https protocol.";
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
catch {
|
|
399
|
+
return "Invalid URL format.";
|
|
400
|
+
}
|
|
401
|
+
return undefined;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Validates a channel name.
|
|
405
|
+
* @param name - The name to validate.
|
|
406
|
+
* @returns Error message if invalid, undefined if valid.
|
|
407
|
+
*/
|
|
408
|
+
export function validateChannelName(name) {
|
|
409
|
+
// Check for empty name.
|
|
410
|
+
if (!name || (name.trim() === "")) {
|
|
411
|
+
return "Channel name is required.";
|
|
412
|
+
}
|
|
413
|
+
// Check length.
|
|
414
|
+
if (name.length > 100) {
|
|
415
|
+
return "Channel name must be 100 characters or less.";
|
|
416
|
+
}
|
|
417
|
+
return undefined;
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Validates a profile name.
|
|
421
|
+
* @param profile - The profile name to validate (can be empty for autodetect).
|
|
422
|
+
* @param validProfiles - Array of valid profile names.
|
|
423
|
+
* @returns Error message if invalid, undefined if valid.
|
|
424
|
+
*/
|
|
425
|
+
export function validateChannelProfile(profile, validProfiles) {
|
|
426
|
+
// Empty profile means autodetect, which is valid.
|
|
427
|
+
if (!profile || (profile.trim() === "")) {
|
|
428
|
+
return undefined;
|
|
429
|
+
}
|
|
430
|
+
// Check if profile exists.
|
|
431
|
+
if (!validProfiles.includes(profile)) {
|
|
432
|
+
return ["Unknown profile: ", profile, ". Valid profiles: ", validProfiles.join(", "), "."].join("");
|
|
433
|
+
}
|
|
434
|
+
return undefined;
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Validates an imported channels object for structure and content.
|
|
438
|
+
* @param data - The raw imported data to validate.
|
|
439
|
+
* @param validProfiles - Array of valid profile names.
|
|
440
|
+
* @returns Validation result with errors if invalid.
|
|
441
|
+
*/
|
|
442
|
+
export function validateImportedChannels(data, validProfiles) {
|
|
443
|
+
const errors = [];
|
|
444
|
+
// Check that input is an object.
|
|
445
|
+
if ((typeof data !== "object") || (data === null) || Array.isArray(data)) {
|
|
446
|
+
return { channels: {}, errors: ["Invalid format: expected an object with channel definitions."], valid: false };
|
|
447
|
+
}
|
|
448
|
+
const channels = {};
|
|
449
|
+
const entries = Object.entries(data);
|
|
450
|
+
for (const [key, value] of entries) {
|
|
451
|
+
// Validate key format.
|
|
452
|
+
const keyError = validateChannelKey(key, false);
|
|
453
|
+
if (keyError) {
|
|
454
|
+
errors.push("Channel '" + key + "': " + keyError);
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
// Check that value is an object.
|
|
458
|
+
if ((typeof value !== "object") || (value === null) || Array.isArray(value)) {
|
|
459
|
+
errors.push("Channel '" + key + "': expected an object with channel properties.");
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
const channelData = value;
|
|
463
|
+
// Validate required name field.
|
|
464
|
+
if ((typeof channelData.name !== "string") || (channelData.name.trim() === "")) {
|
|
465
|
+
errors.push("Channel '" + key + "': name is required.");
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
const nameError = validateChannelName(channelData.name);
|
|
469
|
+
if (nameError) {
|
|
470
|
+
errors.push("Channel '" + key + "': " + nameError);
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
// Validate required url field.
|
|
474
|
+
if ((typeof channelData.url !== "string") || (channelData.url.trim() === "")) {
|
|
475
|
+
errors.push("Channel '" + key + "': url is required.");
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
const urlError = validateChannelUrl(channelData.url);
|
|
479
|
+
if (urlError) {
|
|
480
|
+
errors.push("Channel '" + key + "': " + urlError);
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
// Validate optional profile field.
|
|
484
|
+
const profile = (typeof channelData.profile === "string") ? channelData.profile : undefined;
|
|
485
|
+
const profileError = validateChannelProfile(profile, validProfiles);
|
|
486
|
+
if (profileError) {
|
|
487
|
+
errors.push("Channel '" + key + "': " + profileError);
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
// Build validated channel.
|
|
491
|
+
const channel = {
|
|
492
|
+
name: channelData.name,
|
|
493
|
+
url: channelData.url
|
|
494
|
+
};
|
|
495
|
+
if (profile) {
|
|
496
|
+
channel.profile = profile;
|
|
497
|
+
}
|
|
498
|
+
// Include optional fields if present.
|
|
499
|
+
if (typeof channelData.stationId === "string") {
|
|
500
|
+
channel.stationId = channelData.stationId;
|
|
501
|
+
}
|
|
502
|
+
if (typeof channelData.channelSelector === "string") {
|
|
503
|
+
channel.channelSelector = channelData.channelSelector;
|
|
504
|
+
}
|
|
505
|
+
// Validate optional channelNumber field (range and type).
|
|
506
|
+
if (channelData.channelNumber !== undefined) {
|
|
507
|
+
const num = Number(channelData.channelNumber);
|
|
508
|
+
if (!Number.isInteger(num) || (num < 1) || (num > 99999)) {
|
|
509
|
+
errors.push("Channel '" + key + "': channelNumber must be an integer between 1 and 99999.");
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
channel.channelNumber = num;
|
|
513
|
+
}
|
|
514
|
+
channels[key] = channel;
|
|
515
|
+
}
|
|
516
|
+
// Validate channelNumber uniqueness across all imported channels. We check after building the full map so that all duplicates are reported.
|
|
517
|
+
const numberToKey = new Map();
|
|
518
|
+
for (const [key, channel] of Object.entries(channels)) {
|
|
519
|
+
if (channel.channelNumber === undefined) {
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
const existing = numberToKey.get(channel.channelNumber);
|
|
523
|
+
if (existing) {
|
|
524
|
+
errors.push("Channel '" + key + "': channelNumber " + String(channel.channelNumber) + " is already used by '" + existing + "'.");
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
numberToKey.set(channel.channelNumber, key);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return { channels, errors, valid: errors.length === 0 };
|
|
531
|
+
}
|
|
532
|
+
/* Provider selections are stored in the channels.json file alongside user channels. When a selection changes, we save the entire file (channels + selections)
|
|
533
|
+
* to persist the change.
|
|
534
|
+
*/
|
|
535
|
+
/**
|
|
536
|
+
* Saves the current provider selections to the channels file. This triggers a full file save including all user channels.
|
|
537
|
+
* @throws If the file cannot be written.
|
|
538
|
+
*/
|
|
539
|
+
export async function saveProviderSelections() {
|
|
540
|
+
// Simply save the user channels — the saveUserChannels function includes provider selections automatically.
|
|
541
|
+
await saveUserChannels(loadedUserChannels);
|
|
542
|
+
}
|
|
543
|
+
//# sourceMappingURL=userChannels.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"userChannels.js","sourceRoot":"","sources":["../../src/config/userChannels.ts"],"names":[],"mappings":"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"}
|