export type QuickLinkMap = Record; export type NekoBrowserLink = { id: number; key: string; label: string; url: string; port: string; }; const NEKO_KEY_RE = /^neko_browser(?:_(\d+))?$/i; function parsePort(url: string): string { try { return new URL(url, "http://127.0.0.1").port || ""; } catch { return ""; } } export function extractNekoBrowserLinks(quickLinks?: QuickLinkMap | null): NekoBrowserLink[] { const picked = new Map(); for (const [key, rawUrl] of Object.entries(quickLinks ?? {})) { const match = key.match(NEKO_KEY_RE); const url = rawUrl.trim(); if (!match || !url) continue; const id = key === "neko_browser" ? 1 : Number(match[1] || 1); const next: NekoBrowserLink = { id, key, label: `Neko ${id}`, url, port: parsePort(url), }; const current = picked.get(id); const preferNext = key !== "neko_browser"; if (!current || preferNext) picked.set(id, next); } return Array.from(picked.values()).sort((a, b) => a.id - b.id); } export function resolveNekoBrowserLink( links: NekoBrowserLink[], preferredIndex: number, ): { link: NekoBrowserLink | null; fallback: boolean } { if (!links.length) return { link: null, fallback: false }; if (!Number.isInteger(preferredIndex) || preferredIndex < 0) { return { link: links[0], fallback: false }; } if (preferredIndex < links.length) { return { link: links[preferredIndex], fallback: false }; } return { link: links[0], fallback: true }; } export function formatNekoPorts(links: NekoBrowserLink[]): string { const ports = links.map((item) => item.port).filter(Boolean); return ports.length ? ports.map((port) => `:${port}`).join(" / ") : ""; }