/** * @copyright 2021 Christoph Wurst * * @author 2021 Christoph Wurst * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ import { getClient } from '../dav/client' import ICAL from 'ical.js' import logger from './logger' import { parseXML } from 'webdav/dist/node/tools/dav' import { v4 as uuidv4 } from 'uuid' export function getEmptySlots() { return { MO: [], TU: [], WE: [], TH: [], FR: [], SA: [], SU: [], } } export async function findScheduleInboxAvailability() { const client = getClient('calendars') const response = await client.customRequest('inbox', { method: 'PROPFIND', data: ` ` }) const xml = await parseXML(response.data) if (!xml) { return undefined } const availability = xml?.multistatus?.response[0]?.propstat?.prop['calendar-availability'] if (!availability) { return undefined } const parsedIcal = ICAL.parse(availability) const vcalendarComp = new ICAL.Component(parsedIcal) const vavailabilityComp = vcalendarComp.getFirstSubcomponent('vavailability') const availableComps = vavailabilityComp.getAllSubcomponents('available') // Combine all AVAILABLE blocks into a week of slots const slots = getEmptySlots() availableComps.forEach((availableComp) => { const start = availableComp.getFirstProperty('dtstart').getFirstValue().toJSDate() const end = availableComp.getFirstProperty('dtend').getFirstValue().toJSDate() const rrule = availableComp.getFirstProperty('rrule') if (rrule.getFirstValue().freq !== 'WEEKLY') { logger.warn('rrule not supported', { rrule: rrule.toICALString(), }) return } rrule.getFirstValue().getComponent('BYDAY').forEach(day => { slots[day].push({ start, end, }) }) }) return { slots, } } export async function saveScheduleInboxAvailability(slots, timezoneId) { const all = [...Object.keys(slots).flatMap(dayId => slots[dayId].map(slot => ({ ...slot, day: dayId, })))] const vavailabilityComp = new ICAL.Component('vavailability') // TODO: deduplicate slots that occur on more than one day all.map(slot => { const availableComp = new ICAL.Component('available') // Define DTSTART and DTEND // TODO: tz? moment.tz(dateTime, timezone).toDate() const startTimeProp = availableComp.addPropertyWithValue('dtstart', ICAL.Time.fromJSDate(slot.start, false)) startTimeProp.setParameter('tzid', timezoneId) const endTimeProp = availableComp.addPropertyWithValue('dtend', ICAL.Time.fromJSDate(slot.end, false)) endTimeProp.setParameter('tzid', timezoneId) // Add mandatory UID availableComp.addPropertyWithValue('uid', uuidv4()) // TODO: add optional summary // Define RRULE availableComp.addPropertyWithValue('rrule', { freq: 'WEEKLY', byday: slot.day, }) return availableComp }).map(vavailabilityComp.addSubcomponent.bind(vavailabilityComp)) const vcalendarComp = new ICAL.Component('vcalendar') vcalendarComp.addSubcomponent(vavailabilityComp) logger.debug('New availability ical created', { asObject: vcalendarComp, asString: vcalendarComp.toString(), }) const client = getClient('calendars') await client.customRequest('inbox', { method: 'PROPPATCH', data: ` ${vcalendarComp.toString()} ` }) }