From 463d9273e29cd3399c79cff072b1f3ed91ec35df Mon Sep 17 00:00:00 2001 From: as6325400 Date: Fri, 4 Apr 2025 00:02:35 +0800 Subject: [PATCH] add batch insert event to calendar --- src/js/popup.js | 33 +++++++++++++----- src/js/utils.js | 93 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 9 deletions(-) diff --git a/src/js/popup.js b/src/js/popup.js index 6b0334d..3d2bf8c 100644 --- a/src/js/popup.js +++ b/src/js/popup.js @@ -1,4 +1,9 @@ -import { getCurrentYearMonth, downloadICS, addOneEventToCalendar } from "./utils.js"; +import { + getCurrentYearMonth, + downloadICS, + addOneEventToCalendar, + addBatchEventsToCalendar, +} from "./utils.js"; function initializeInputs() { const { year, month } = getCurrentYearMonth(); @@ -112,18 +117,28 @@ function setupEventListeners() { async function insertEventsToGCal(events) { // Google OAuth token const token = await getGoogleAuthToken(); - - // add event Google Calendar - for (const event of events) { + const batchAddEvents = true; + + if (!batchAddEvents) { + // add event Google Calendar + for (const event of events) { + try { + await addOneEventToCalendar(token, event); + console.log("add Event Success", event.title); + } catch (err) { + console.error("add Event error:", event.title, err); + } + } + } else if (batchAddEvents) { + // batch API,done after... try { - await addOneEventToCalendar(token, event); - console.log("add Event Success", event.title); + const batchResponse = await addBatchEventsToCalendar(token, events); + console.log("Batch request sent!"); + console.log(batchResponse); } catch (err) { - console.error("add Event error:", event.title, err); + console.error("Batch request failed:", err); } } - - // batch API,done after... } async function launchWebAuthFlowForGoogle() { diff --git a/src/js/utils.js b/src/js/utils.js index 26b1b8f..31e0695 100644 --- a/src/js/utils.js +++ b/src/js/utils.js @@ -73,3 +73,96 @@ export async function addOneEventToCalendar(token, event) { return res.json(); } + +function normalizeBatchText(raw) { + return raw.replace(/\r?\n/g, "\r\n"); +} + +function generateBatchRequestBody(events, boundary = "batch_boundary") { + const CRLF = "\r\n"; + let body = ""; + + events.forEach((event, i) => { + body += `--${boundary}${CRLF}`; + body += `Content-Type: application/http${CRLF}`; + body += `Content-ID: ${CRLF}${CRLF}`; + + body += `POST /calendar/v3/calendars/primary/events HTTP/1.1${CRLF}`; + body += `Content-Type: application/json${CRLF}${CRLF}`; + + const eventPayload = { + summary: event.title, + description: event.description, + start: { + dateTime: event.startDate.toISOString(), + timeZone: "Asia/Taipei", + }, + end: { + dateTime: event.endDate.toISOString(), + timeZone: "Asia/Taipei", + }, + }; + + body += JSON.stringify(eventPayload) + CRLF + CRLF; + }); + + body += `--${boundary}--${CRLF}`; + return body; +} + +function parseGoogleBatchResponse(responseText) { + const boundary = responseText.match(/--batch_[^\r\n-]+/)?.[0]?.slice(2); + const parts = responseText.split(`--${boundary}`); + const results = []; + + for (const part of parts) { + const trimmed = part.trim(); + if (!trimmed || !trimmed.includes("HTTP/1.1")) continue; + + const statusMatch = trimmed.match(/HTTP\/1.1 (\d{3}) (.+)/); + const status = statusMatch ? parseInt(statusMatch[1], 10) : null; + const statusText = statusMatch ? statusMatch[2] : ""; + + const sections = trimmed.split(/\n{2,}|\r\n{2,}/); + const rawBody = sections[sections.length - 1]; + + const jsonStart = rawBody.indexOf("{"); + const jsonEnd = rawBody.lastIndexOf("}"); + + let body = null; + if (jsonStart !== -1 && jsonEnd !== -1 && jsonEnd > jsonStart) { + const jsonStr = rawBody.slice(jsonStart, jsonEnd + 1); + try { + body = JSON.parse(jsonStr); + } catch (e) { + console.warn("JSON parse failed:", e); + } + } + + results.push({ status, ok: status >= 200 && status < 300, statusText, body }); + } + + return results; +} + +// Google Calendar API batch add Event +export async function addBatchEventsToCalendar(token, events) { + const boundary = "batch_boundary_" + Date.now(); + const body = generateBatchRequestBody(events, boundary); + + const res = await fetch("https://www.googleapis.com/batch/calendar/v3", { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": `multipart/mixed; boundary=${boundary}`, + }, + body, + }); + + const text = await res.text(); + if (!res.ok) { + throw new Error(`Batch API Error: ${res.status}\n${text}`); + } + + return parseGoogleBatchResponse(normalizeBatchText(text)); +}