require('dotenv').config(); const fetch = require('node-fetch'); const fs = require('fs'); const path = require('path'); const calendarUid = process.env.ZOHO_CALENDAR_UID; let accessToken = process.env.ZOHO_ACCESS_TOKEN; const refreshToken = process.env.ZOHO_REFRESH_TOKEN; const clientId = process.env.ZOHO_CLIENT_ID; const clientSecret = process.env.ZOHO_CLIENT_SECRET; // Dates for current month const currentDate = new Date(); const currentYear = currentDate.getFullYear(); const currentMonth = (currentDate.getMonth() + 1).toString().padStart(2, '0'); const lastDay = new Date(currentYear, currentDate.getMonth() + 1, 0).getDate(); const startDate = `${currentYear}${currentMonth}01`; const endDate = `${currentYear}${currentMonth}${lastDay}`; const zohoApiUrl = `https://calendar.zoho.com/api/v1/calendars/${calendarUid}`; async function refreshAccessToken() { const params = new URLSearchParams(); params.append('grant_type', 'refresh_token'); params.append('client_id', clientId); params.append('client_secret', clientSecret); params.append('refresh_token', refreshToken); const response = await fetch('https://accounts.zoho.com/oauth/v2/token', { method: 'POST', body: params }); const data = await response.json(); if (!data.access_token) { throw new Error('Failed to refresh access token: ' + JSON.stringify(data)); } const envPath = path.resolve(process.cwd(), '.env'); const envContents = fs.readFileSync(envPath, 'utf-8') .split('\n') .map(line => line.startsWith('ZOHO_ACCESS_TOKEN=') ? `ZOHO_ACCESS_TOKEN=${data.access_token}` : line) .join('\n'); fs.writeFileSync(envPath, envContents, 'utf-8'); accessToken = data.access_token; } function parseZohoTimestamp(start, isAllDay) { if (isAllDay) { const year = parseInt(start.slice(0, 4)); const month = parseInt(start.slice(4, 6)) - 1; const day = parseInt(start.slice(6, 8)); return new Date(year, month, day); } else { const year = parseInt(start.slice(0, 4)); const month = parseInt(start.slice(4, 6)) - 1; const day = parseInt(start.slice(6, 8)); const hour = parseInt(start.slice(9, 11)); const minute = parseInt(start.slice(11, 13)); const second = parseInt(start.slice(13, 15)); return new Date(year, month, day, hour, minute, second); } } function formatEventDate(startDate, endDate, isAllDay) { const start = parseZohoTimestamp(startDate, isAllDay); if (isAllDay) { return start.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }); } else { const end = parseZohoTimestamp(endDate, isAllDay); const optionsDate = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }; const optionsTime = { hour: 'numeric', minute: '2-digit', hour12: true }; const dateStr = start.toLocaleDateString('en-US', optionsDate); const startTimeStr = start.toLocaleTimeString('en-US', optionsTime); const endTimeStr = end.toLocaleTimeString('en-US', optionsTime); return `${dateStr} • ${startTimeStr} – ${endTimeStr}`; } } module.exports = async function() { try { const eventsListUrl = `${zohoApiUrl}/events?range={"start":"${startDate}","end":"${endDate}"}`; console.log(`Making initial API call to: ${eventsListUrl}`); console.log(`Using access token`); let response = await fetch(eventsListUrl, { headers: { Authorization: `Zoho-oauthtoken ${accessToken}` } }); if (response.status === 401) { console.log("Access token is expired, refreshing..."); await refreshAccessToken(); response = await fetch(eventsListUrl, { headers: { Authorization: `Zoho-oauthtoken ${accessToken}` } }); } if (!response.ok) { const text = await response.text(); throw new Error(`API call failed: ${response.status} ${response.statusText} - ${text}`); } const data = await response.json(); console.log("Initial API call successful. Received data:"); console.dir(data, { depth: null }); const normalizedEventsPromises = data.events.map(async (event) => { const basicEvent = { calid: event.calid, title: event.title, uid: event.uid, start: event.dateandtime.start, end: event.dateandtime.end, isallday: event.isallday, location: event.location, displayDate: formatEventDate(event.dateandtime.start, event.dateandtime.end, event.isallday), color: event.color, }; // This logic is necessary to get the description from a second API call if (basicEvent.uid) { const eventDetailsUrl = `${zohoApiUrl}/events/${basicEvent.uid}`; console.log(`Making details API call to: ${eventDetailsUrl}`); const detailsResponse = await fetch(eventDetailsUrl, { headers: { Authorization: `Zoho-oauthtoken ${accessToken}` } }); if (detailsResponse.ok) { const detailsData = await detailsResponse.json(); if (detailsData.events && detailsData.events.length > 0) { basicEvent.description = detailsData.events[0].description; console.log(`Description added for event UID: ${basicEvent.uid}`); } } else { console.warn(`Failed to fetch details for event UID: ${basicEvent.uid}`); } } // Assign category based on color switch (basicEvent.color) { case '#7CAA56': basicEvent.category = 'sales'; break; case '#FC6060': basicEvent.category = 'workshop'; break; case '#FFC464': basicEvent.category = 'community'; break; default: basicEvent.category = 'uncategorized'; } return basicEvent; }); const normalizedEvents = await Promise.all(normalizedEventsPromises); console.log("All events processed. Final normalized events array:", normalizedEvents); const filePath = path.resolve(__dirname, '..', '_data', 'eventsJson.js'); const fileContents = `module.exports = { normalizedEvents: ${JSON.stringify(normalizedEvents, null, 2)} };`; fs.writeFileSync(filePath, fileContents, 'utf-8'); console.log(`Data successfully written to ${filePath}`); return normalizedEvents; } catch (error) { console.error("Error fetching Zoho events:", error); return []; } };