Files
gitlab-instance-0a899031_do…/web-console/lib/youtubeProChannels.ts
2026-03-29 03:35:05 -05:00

117 lines
3.7 KiB
TypeScript

export type YoutubeProChannel = {
id: string;
name: string;
/** 备忘尾码(旧版) */
streamKeySuffix?: string;
/** 完整串流密钥或 rtmp(s) URL */
streamKey?: string;
/** 本线路 URL_config 草稿(多路本地编排) */
urlLines?: string;
/** 建议 PM2 名,如 youtube2__xxx */
pm2Name?: string;
notes?: string;
};
const STORAGE_KEY = "live-hub-youtube-pro-v2";
export function parseYoutubeProChannelList(raw: unknown): YoutubeProChannel[] {
if (!Array.isArray(raw)) return [];
return raw
.map((x) => migrateRow(x as Record<string, unknown>))
.filter((c): c is YoutubeProChannel => c !== null);
}
function migrateRow(x: Record<string, unknown>): YoutubeProChannel | null {
if (!x || typeof x !== "object") return null;
const id = String(x.id || "").trim();
if (!id) return null;
const name = String(x.name || "").trim() || id;
return {
id,
name,
streamKeySuffix: typeof x.streamKeySuffix === "string" ? x.streamKeySuffix : undefined,
streamKey: typeof x.streamKey === "string" ? x.streamKey : undefined,
urlLines: typeof x.urlLines === "string" ? x.urlLines : undefined,
pm2Name: typeof x.pm2Name === "string" ? x.pm2Name : undefined,
notes: typeof x.notes === "string" ? x.notes : undefined,
};
}
function safeParse(raw: string | null): YoutubeProChannel[] {
if (!raw) return [];
try {
const v = JSON.parse(raw) as unknown;
if (!Array.isArray(v)) return [];
return v.map((x) => migrateRow(x as Record<string, unknown>)).filter((c): c is YoutubeProChannel => c !== null);
} catch {
return [];
}
}
export function loadYoutubeProChannels(): YoutubeProChannel[] {
if (typeof window === "undefined") return [];
let raw = window.localStorage.getItem(STORAGE_KEY);
if (!raw) {
raw = window.localStorage.getItem("live-hub-youtube-pro-v1");
if (raw) {
const legacy = safeParse(raw);
if (legacy.length) {
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(legacy));
return legacy;
}
}
}
return safeParse(raw);
}
export function saveYoutubeProChannels(channels: YoutubeProChannel[]): void {
if (typeof window === "undefined") return;
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(channels));
}
const YOUTUBE_PRO_SYNC_PATHS = ["/hub/youtube_pro_channels", "/youtube_pro_channels"] as const;
/** 从服务器拉多频道列表(依次尝试 /hub 与根路径,兼容反代未转发 /hub 的情况) */
export async function fetchYoutubeProChannelsRemote(
fetchJson: <T,>(path: string, init?: RequestInit) => Promise<T>,
): Promise<YoutubeProChannel[]> {
for (const path of YOUTUBE_PRO_SYNC_PATHS) {
try {
const data = await fetchJson<{ channels?: unknown }>(path);
const fromServer = parseYoutubeProChannelList(data.channels);
if (fromServer.length) return fromServer;
} catch {
/* try next */
}
}
return [];
}
export async function pushYoutubeProChannelsRemote(
fetchJson: <T,>(path: string, init?: RequestInit) => Promise<T>,
channels: YoutubeProChannel[],
): Promise<void> {
let last: Error | null = null;
for (const path of YOUTUBE_PRO_SYNC_PATHS) {
try {
await fetchJson(path, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ channels }),
});
return;
} catch (e) {
last = e instanceof Error ? e : new Error(String(e));
}
}
throw last ?? new Error("youtube_pro_channels sync failed");
}
export function newChannelId(): string {
return `ch_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 7)}`;
}
export function defaultPm2NameForChannel(channelId: string): string {
return `youtube2__${channelId}`;
}