@@ -73,3 +73,96 @@ export async function addOneEventToCalendar(token, event) {
7373
7474 return res . json ( ) ;
7575}
76+
77+ function normalizeBatchText ( raw ) {
78+ return raw . replace ( / \r ? \n / g, "\r\n" ) ;
79+ }
80+
81+ function generateBatchRequestBody ( events , boundary = "batch_boundary" ) {
82+ const CRLF = "\r\n" ;
83+ let body = "" ;
84+
85+ events . forEach ( ( event , i ) => {
86+ body += `--${ boundary } ${ CRLF } ` ;
87+ body += `Content-Type: application/http${ CRLF } ` ;
88+ body += `Content-ID: <item-${ i + 1 } >${ CRLF } ${ CRLF } ` ;
89+
90+ body += `POST /calendar/v3/calendars/primary/events HTTP/1.1${ CRLF } ` ;
91+ body += `Content-Type: application/json${ CRLF } ${ CRLF } ` ;
92+
93+ const eventPayload = {
94+ summary : event . title ,
95+ description : event . description ,
96+ start : {
97+ dateTime : event . startDate . toISOString ( ) ,
98+ timeZone : "Asia/Taipei" ,
99+ } ,
100+ end : {
101+ dateTime : event . endDate . toISOString ( ) ,
102+ timeZone : "Asia/Taipei" ,
103+ } ,
104+ } ;
105+
106+ body += JSON . stringify ( eventPayload ) + CRLF + CRLF ;
107+ } ) ;
108+
109+ body += `--${ boundary } --${ CRLF } ` ;
110+ return body ;
111+ }
112+
113+ function parseGoogleBatchResponse ( responseText ) {
114+ const boundary = responseText . match ( / - - b a t c h _ [ ^ \r \n - ] + / ) ?. [ 0 ] ?. slice ( 2 ) ;
115+ const parts = responseText . split ( `--${ boundary } ` ) ;
116+ const results = [ ] ;
117+
118+ for ( const part of parts ) {
119+ const trimmed = part . trim ( ) ;
120+ if ( ! trimmed || ! trimmed . includes ( "HTTP/1.1" ) ) continue ;
121+
122+ const statusMatch = trimmed . match ( / H T T P \/ 1 .1 ( \d { 3 } ) ( .+ ) / ) ;
123+ const status = statusMatch ? parseInt ( statusMatch [ 1 ] , 10 ) : null ;
124+ const statusText = statusMatch ? statusMatch [ 2 ] : "" ;
125+
126+ const sections = trimmed . split ( / \n { 2 , } | \r \n { 2 , } / ) ;
127+ const rawBody = sections [ sections . length - 1 ] ;
128+
129+ const jsonStart = rawBody . indexOf ( "{" ) ;
130+ const jsonEnd = rawBody . lastIndexOf ( "}" ) ;
131+
132+ let body = null ;
133+ if ( jsonStart !== - 1 && jsonEnd !== - 1 && jsonEnd > jsonStart ) {
134+ const jsonStr = rawBody . slice ( jsonStart , jsonEnd + 1 ) ;
135+ try {
136+ body = JSON . parse ( jsonStr ) ;
137+ } catch ( e ) {
138+ console . warn ( "JSON parse failed:" , e ) ;
139+ }
140+ }
141+
142+ results . push ( { status, ok : status >= 200 && status < 300 , statusText, body } ) ;
143+ }
144+
145+ return results ;
146+ }
147+
148+ // Google Calendar API batch add Event
149+ export async function addBatchEventsToCalendar ( token , events ) {
150+ const boundary = "batch_boundary_" + Date . now ( ) ;
151+ const body = generateBatchRequestBody ( events , boundary ) ;
152+
153+ const res = await fetch ( "https://www.googleapis.com/batch/calendar/v3" , {
154+ method : "POST" ,
155+ headers : {
156+ Authorization : `Bearer ${ token } ` ,
157+ "Content-Type" : `multipart/mixed; boundary=${ boundary } ` ,
158+ } ,
159+ body,
160+ } ) ;
161+
162+ const text = await res . text ( ) ;
163+ if ( ! res . ok ) {
164+ throw new Error ( `Batch API Error: ${ res . status } \n${ text } ` ) ;
165+ }
166+
167+ return parseGoogleBatchResponse ( normalizeBatchText ( text ) ) ;
168+ }
0 commit comments