Skip to content

Instantly share code, notes, and snippets.

@sevenc-nanashi
Last active August 6, 2025 13:49
Show Gist options
  • Save sevenc-nanashi/4528fe0882bfdc096163aed5b14aa1db to your computer and use it in GitHub Desktop.
Save sevenc-nanashi/4528fe0882bfdc096163aed5b14aa1db to your computer and use it in GitHub Desktop.
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