import { parseICSDate } from "@/lib/agenda/agenda-date" import type { AgendaApiEvent } from "@/lib/agenda/agenda-types" function unfoldIcsLines(raw: string): string[] { const physical = raw.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n") const lines: string[] = [] for (const line of physical) { if ((line.startsWith(" ") || line.startsWith("\t")) && lines.length > 0) { lines[lines.length - 1] += line.slice(1) } else { lines.push(line) } } return lines } function parseIcsProperty(line: string): { name: string; value: string } | null { const idx = line.indexOf(":") if (idx <= 0) return null const rawName = line.slice(0, idx) const value = line.slice(idx + 1).trim() const name = rawName.split(";")[0]?.toUpperCase() ?? "" if (!name) return null return { name, value } } function isAllDayProperty(rawName: string, value: string): boolean { if (/VALUE=DATE/i.test(rawName)) return true return /^\d{8}$/.test(value) } function extractVEventBlocks(lines: string[]): string[][] { const blocks: string[][] = [] let current: string[] | null = null for (const line of lines) { const upper = line.trim().toUpperCase() if (upper === "BEGIN:VEVENT") { current = [] continue } if (upper === "END:VEVENT") { if (current) blocks.push(current) current = null continue } if (current) current.push(line) } return blocks } function parseVEventBlock(lines: string[], index: number): AgendaApiEvent | null { let uid = "" let summary = "" let description = "" let location = "" let start = "" let end = "" let allDay = false for (const line of lines) { const idx = line.indexOf(":") if (idx <= 0) continue const rawName = line.slice(0, idx) const value = line.slice(idx + 1).trim() const prop = parseIcsProperty(line) if (!prop) continue switch (prop.name) { case "UID": uid = value break case "SUMMARY": summary = value break case "DESCRIPTION": description = value.replace(/\\n/g, "\n").replace(/\\,/g, ",") break case "LOCATION": location = value.replace(/\\,/g, ",") break case "DTSTART": start = value allDay = isAllDayProperty(rawName, value) break case "DTEND": end = value break default: break } } if (!start) return null if (!uid) uid = `ext-${index}-${start}` return { uid, summary: summary || "(Sans titre)", description, location, start, end: end || start, all_day: allDay, path: uid, } } /** Parse un flux ICS en événements API agenda. */ export function parseIcsFeed(raw: string): AgendaApiEvent[] { const lines = unfoldIcsLines(raw) const blocks = extractVEventBlocks(lines) const events: AgendaApiEvent[] = [] blocks.forEach((block, index) => { const event = parseVEventBlock(block, index) if (!event) return if (!parseICSDate(event.start)) return events.push(event) }) return events }