This script is listing out zero VTODO items from a 'calendar' hosted on fastmail:
import dotenv from 'dotenv';
import { v4 as uuidv4 } from 'uuid';
import { DAVClient } from 'tsdav';
import ICAL from 'ical.js';
// Load environment variables
dotenv.config();
const caldavUrl = process.env.CALDAV_SERVER;
const username = process.env.CALDAV_EMAIL;
const password = process.env.CALDAV_PASS;
const targetCalendarName = process.env.CALDAV_CALNAME;
async function main() {
try {
// Create the DAV client
const client = new DAVClient({
serverUrl: caldavUrl,
credentials: {
username: username,
password: password,
},
authMethod: 'Basic',
defaultAccountType: 'caldav',
});
// Login to the server
await client.login();
// Get the list of calendars
const calendars = await client.fetchCalendars();
if (!calendars.length) {
console.log("No calendars found.");
process.exit(1);
}
// Find the target calendar
let mycal = null;
for (let calendar of calendars) {
const displayName = calendar.displayName;
console.log(`Calendar: ${displayName}`);
if (displayName === targetCalendarName) {
mycal = calendar;
break;
}
}
if (!mycal) {
console.log(`Target calendar '${targetCalendarName}' not found.`);
process.exit(1);
}
// Check for VTODO support
const supportedComponents = mycal.components;
console.log("Supported components:", supportedComponents);
if (!supportedComponents.includes('VTODO')) {
console.log("This calendar does not support tasks (VTODO).");
process.exit(1);
}
// Fetch calendar objects with depth: 1
const calendarObjects = await client.fetchCalendarObjects({
calendar: mycal,
depth: 'infinity',
});
if (!calendarObjects.length) {
console.log("No calendar objects found.");
} else {
// Filter and display VTODOs
const vtodos = calendarObjects.filter(obj => {
const icalComponent = ICAL.Component.fromString(obj.data);
return icalComponent.getFirstSubcomponent('vtodo') !== null;
});
if (vtodos.length === 0) {
console.log("No VTODOs found.");
} else {
console.log("VTODOs:");
vtodos.forEach(vtodoObj => {
const icalComponent = ICAL.Component.fromString(vtodoObj.data);
const vtodoComponent = icalComponent.getFirstSubcomponent('vtodo');
const summary = vtodoComponent.getFirstPropertyValue('summary');
const description = vtodoComponent.getFirstPropertyValue('description');
const dueDate = vtodoComponent.getFirstPropertyValue('due');
console.log(`Summary: ${summary}`);
console.log(`Description: ${description}`);
if (dueDate) {
console.log(`Due Date: ${dueDate.toString()}`);
} else {
console.log("Due Date: Not set");
}
console.log('---');
});
}
}
// Create a VTODO using ical.js
const vcalendar = new ICAL.Component(['vcalendar', [], []]);
vcalendar.addPropertyWithValue('prodid', '-//My Product//EN');
vcalendar.addPropertyWithValue('version', '2.0');
const vtodo = new ICAL.Component('vtodo');
const now = ICAL.Time.now();
const dueTime = now.clone();
dueTime.adjust(0, 0, 1, 0); // Set due time to one day from now
vtodo.addPropertyWithValue('uid', uuidv4());
vtodo.addPropertyWithValue('summary', 'another VTODO');
vtodo.addPropertyWithValue('description', 'This is another VTODO.');
vtodo.addPropertyWithValue('due', dueTime.toString());
vtodo.addPropertyWithValue('status', 'NEEDS-ACTION');
vtodo.addPropertyWithValue('created', now.toString());
vcalendar.addSubcomponent(vtodo);
const vtodoStr = vcalendar.toString();
// Create and save the VTODO
await client.createCalendarObject({
calendar: mycal,
iCalString: vtodoStr,
});
console.log(`VTODO created successfully in calendar '${mycal.displayName}!'`);
} catch (err) {
console.log(`An error occurred: ${err.message}`);
}
}
main();
However, this script in Python works correctly, and lists the items. I think there is an error in tsdav, or else some special handling/approach is needed for Fastmail?
from datetime import datetime, timedelta
from dotenv import load_dotenv
import os, sys
import caldav
import vobject
# Load environment variables
load_dotenv()
caldav_url = os.environ['CALDAV_SERVER']
username = os.environ['CALDAV_EMAIL']
password = os.environ['CALDAV_PASS']
target_calendar_name = os.environ['CALDAV_CALNAME']
# Connect to the CalDAV server
client = caldav.DAVClient (url=caldav_url, username=username, password=password)
# Get the principal (account)
principal = client.principal()
# Get the list of calendars
calendars = principal.calendars()
if not calendars:
print("No calendars found.")
exit(1)
# Find the target calendar
mycal = None
for calendar in calendars:
displayname = calendar.get_display_name()
print(f"Calendar: {displayname}")
if displayname == target_calendar_name:
mycal = calendar
if not mycal:
print(f"Target calendar '{target_calendar_name}' not found.")
sys.exit(1)
# check for VTODO support.
print(f"Calendar: {calendar.name}")
# Fetch properties
properties = mycal.get_properties([caldav.elements.dav.DisplayName(), caldav.elements.cdav.SupportedCalendarComponentSet()])
# Check for VTODO support
supported = mycal.get_supported_components()
print("supported:",supported)
if 'VTODO' not in supported:
print(" This calendar does not support tasks (VTODO).")
exit(1)
# List all VTODOs in the selected calendar
vtodos = mycal.todos()
if not vtodos:
print("No VTODOs found.")
else:
print("VTODOs:")
for vtodo in vtodos:
todo_data = vobject.readOne(vtodo.data)
summary = todo_data.vtodo.summary.value if 'summary' in todo_data.vtodo.contents else 'No summary'
description = todo_data.vtodo.description.value if 'description' in todo_data.vtodo.contents else 'No description'
due = todo_data.vtodo.due.value.strftime('%Y-%m-%d %H:%M:%S') if 'due' in todo_data.vtodo.contents else 'No due date'
print(f"Summary: {summary}")
print(f"Description: {description}")
print(f"Due Date: {due}")
print('---')
# Create a VTODO using vobject
vtodo = vobject.iCalendar()
vtodo.add('VTODO')
vtodo.vtodo.add('UID').value = datetime.now().strftime('%Y%m%dT%H%M%S')
vtodo.vtodo.add('DTSTAMP').value = datetime.utcnow()
vtodo.vtodo.add('SUMMARY').value = 'Sample VTODO'
vtodo.vtodo.add('DESCRIPTION').value = 'This is a another VTODO.'
vtodo.vtodo.add('DUE').value = datetime.utcnow() + timedelta(days=1)
vtodo.vtodo.add('STATUS').value = 'NEEDS-ACTION'
# Convert VTODO to a string
vtodo_str = vtodo.serialize()
# Create and save the VTODO using the correct method
try:
mycal.add_todo(vtodo_str)
print(f"VTODO created successfully in calendar '{mycal.get_display_name()}!'")
except caldav.error.AuthorizationError as e:
print(f"AuthorizationError for calendar '{mycal.get_display_name()}': {e}")
except Exception as e:
print(f"An error occurred: {e}")
Hi there
This script is listing out zero VTODO items from a 'calendar' hosted on fastmail:
However, this script in Python works correctly, and lists the items. I think there is an error in tsdav, or else some special handling/approach is needed for Fastmail?