export function safeHttpUrl(input: string) { try { const url = new URL(input); if (!["http:", "https:"].includes(url.protocol)) return null; return url; } catch { return null; } } export function extractXhsPublishUrl(input: string) { const candidates = input.match(/https?:\/\/[^\s<>"',。!?;:、【】()]+/gi) ?? []; for (const candidate of candidates) { const cleaned = candidate.replace(/[.,!?;:~)\]}]+$/g, ""); const url = safeHttpUrl(cleaned); if (!url) continue; const hostname = url.hostname.toLowerCase(); const isXhs = hostname === "xiaohongshu.com" || hostname.endsWith(".xiaohongshu.com") || hostname === "xhslink.cn" || hostname.endsWith(".xhslink.cn"); if (isXhs) return url.toString(); } return ""; } export type SupportedPlatform = "小红书" | "抖音"; function platformMatches(url: URL, platform: SupportedPlatform) { const hostname = url.hostname.toLowerCase(); if (platform === "抖音") { return hostname === "douyin.com" || hostname.endsWith(".douyin.com"); } return ( hostname === "xiaohongshu.com" || hostname.endsWith(".xiaohongshu.com") || hostname === "xhslink.cn" || hostname.endsWith(".xhslink.cn") ); } export function extractPublishUrl( input: string, platform: SupportedPlatform = "小红书", ) { const candidates = input.match(/https?:\/\/[^\s<>"',。!?;:、【】()]+/gi) ?? []; for (const candidate of candidates) { const cleaned = candidate.replace(/[.,!?;:~)\]}]+$/g, ""); const url = safeHttpUrl(cleaned); if (url && platformMatches(url, platform)) return url.toString(); } return ""; } export function extractAnyPublishUrl(input: string) { return extractPublishUrl(input, "小红书") || extractPublishUrl(input, "抖音"); } export function platformFromPublishUrl(input: string): SupportedPlatform | "" { const url = safeHttpUrl(input); if (!url) return ""; if (platformMatches(url, "小红书")) return "小红书"; if (platformMatches(url, "抖音")) return "抖音"; return ""; }