/** * Source Import Utilities - Handle parsing and importing sources from various formats */ import type { VideoSource, SourceSubscription } from '@/lib/types'; /** * Simplified source format for import */ export interface ImportSourceFormat { id: string; name: string; baseUrl: string; group?: 'normal' | 'adult'; enabled?: boolean; priority?: number; } /** * Import result containing categorized sources */ export interface ImportResult { normalSources: VideoSource[]; adultSources: VideoSource[]; totalCount: number; } /** * Convert simplified import format to full VideoSource */ export function convertToVideoSource(source: ImportSourceFormat): VideoSource { return { id: source.id, name: source.name, baseUrl: source.baseUrl, searchPath: '', detailPath: '', enabled: source.enabled !== false, priority: source.priority || 1, group: source.group || 'normal', }; } /** * Validate if an object is a valid source format */ export function isValidSourceFormat(obj: unknown): obj is ImportSourceFormat { if (typeof obj !== 'object' || obj === null) return false; const source = obj as Record; return ( typeof source.id === 'string' && typeof source.name === 'string' && typeof source.baseUrl === 'string' && source.id.length > 0 && source.name.length > 0 && source.baseUrl.length > 0 ); } /** * Parse sources from JSON string * Supports both array format and wrapped object format */ export function parseSourcesFromJson(jsonString: string): ImportResult { const data = JSON.parse(jsonString); let sourcesArray: unknown[]; // Handle different JSON structures if (Array.isArray(data)) { sourcesArray = data; } else if (data.sources && Array.isArray(data.sources)) { sourcesArray = data.sources; } else if (data.list && Array.isArray(data.list)) { sourcesArray = data.list; } else { throw new Error('无法识别的JSON格式'); } const normalSources: VideoSource[] = []; const adultSources: VideoSource[] = []; for (const item of sourcesArray) { if (!isValidSourceFormat(item)) continue; const source = convertToVideoSource(item); if (item.group === 'adult') { adultSources.push(source); } else { normalSources.push(source); } } return { normalSources, adultSources, totalCount: normalSources.length + adultSources.length, }; } /** * Fetch and parse sources from a URL */ export async function fetchSourcesFromUrl(url: string): Promise { // If we're in the browser and it's an external URL, use our proxy to avoid CORS issues const isExternal = url.startsWith('http') && (typeof window !== 'undefined' && !url.includes(window.location.host)); const fetchUrl = isExternal ? `/api/proxy?url=${encodeURIComponent(url)}` : url; const response = await fetch(fetchUrl, { headers: { 'Accept': 'application/json', }, }); if (!response.ok) { throw new Error(`获取失败: ${response.status} ${response.statusText}`); } const text = await response.text(); return parseSourcesFromJson(text); } /** * Create a new subscription object */ export function createSubscription(name: string, url: string): SourceSubscription { return { id: `sub_${Date.now()}_${Math.random().toString(36).substring(7)}`, name: name.trim() || '未命名订阅', url: url.trim(), lastUpdated: Date.now(), autoRefresh: true, }; } /** * Merge new sources with existing sources, avoiding duplicates */ export function mergeSources( existing: VideoSource[], newSources: VideoSource[] ): VideoSource[] { const existingIds = new Set(existing.map(s => s.id)); const merged = [...existing]; for (const source of newSources) { if (existingIds.has(source.id)) { // Update existing source const idx = merged.findIndex(s => s.id === source.id); if (idx !== -1) { merged[idx] = { ...merged[idx], ...source }; } } else { // Add new source merged.push({ ...source, priority: merged.length + 1, }); existingIds.add(source.id); } } return merged; }