Last active
August 6, 2025 13:49
-
-
Save sevenc-nanashi/4528fe0882bfdc096163aed5b14aa1db to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { GoogleGenAI } from "npm:@google/genai"; | |
import ical from "npm:ical-generator"; | |
// 1. Load config from config.json | |
const config = JSON.parse(await Deno.readTextFile("config.json")); | |
const GEMINI_API_KEY = config.geminiApiKey; | |
const TARGET_URL = config.targetUrl; | |
const ENCODING: string = config.encoding || "utf-8"; | |
const DEFAULT_TIME = config.defaultTime || "09:00"; | |
const DURATION_MINUTES = config.eventDurationMinutes || 60; | |
const CALENDAR_NAME = config.calendarName || "Generated Calendar"; | |
const PROMPT = config.prompt || ""; | |
type IntermediateEvent = { | |
date: string; | |
time: string | null; | |
title: string; | |
description: string; | |
}; | |
type Event = { date: string; time: string; title: string; description: string }; | |
// 2. Fetch URL content | |
async function fetchContent( | |
url: string, | |
encoding: string, | |
): Promise<string> { | |
const response = await fetch(url); | |
if (!response.ok) { | |
throw new Error(`HTTP Failed, status ${response.status}`); | |
} | |
return new TextDecoder(ENCODING).decode(await response.arrayBuffer()); | |
} | |
// 3. Define structured schema | |
const structuredSchema = { | |
type: "array", | |
items: { | |
type: "object", | |
properties: { | |
date: { type: "string" }, | |
time: { type: "string", nullable: true }, | |
title: { type: "string" }, | |
description: { type: "string" }, | |
}, | |
required: ["date", "title", "description"], | |
}, | |
}; | |
// 4. Use Gemini with structured output | |
const ai = new GoogleGenAI({ apiKey: GEMINI_API_KEY }); | |
async function parseWithGemini(content: string) { | |
const result = await ai.models.generateContent({ | |
model: "gemini-2.5-flash", | |
contents: | |
`Extract events.\n${PROMPT}\ndate should follow this format: YYYY-MM-DD\ntime should follow this format: HH:MM\nContent:\n${content}`, | |
config: { | |
responseMimeType: "application/json", | |
responseSchema: structuredSchema, | |
}, | |
}); | |
if (!result.text) { | |
throw new Error("Gemini did not say anything"); | |
} | |
return (JSON.parse(result.text) as IntermediateEvent[]); | |
} | |
// 5. Fill missing times | |
function fillMissingTimes( | |
events: IntermediateEvent[], | |
): Event[] { | |
return events.map((ev) => { | |
if (!ev.time) { | |
ev.time = DEFAULT_TIME; | |
} | |
return ev as Event; | |
}); | |
} | |
// 6. Convert to ICS | |
function generateICS( | |
events: Event[], | |
): string { | |
const cal = ical({ name: CALENDAR_NAME }); | |
for (const ev of events) { | |
const start = new Date(`${ev.date}T${ev.time}:00`); | |
cal.createEvent({ | |
start, | |
end: new Date(start.getTime() + DURATION_MINUTES * 60 * 1000), | |
summary: ev.title, | |
description: ev.description, | |
}); | |
} | |
return cal.toString(); | |
} | |
// 7. Main function | |
const main = async () => { | |
try { | |
console.log("π₯ Fetching content..."); | |
const rawContent = await fetchContent(TARGET_URL, ENCODING); | |
console.log("π€ Parsing with Gemini..."); | |
const parsed = await parseWithGemini(rawContent); | |
console.log("π Filling missing times..."); | |
const filled = fillMissingTimes(parsed); | |
console.log("π Generating ICS..."); | |
const ics = generateICS(filled); | |
await Deno.writeTextFile("calendar.ics", ics); | |
console.log("β Saved to calendar.ics"); | |
} catch (error) { | |
console.error("β Error:", String(error)); | |
} | |
}; | |
main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment