Tasks tab: NE Hub lists plus iCloud reminder lists where Apple exposes them
build-container / build (push) Successful in 1m43s
build-container / build (push) Successful in 1m43s
- Lists, quick add, due dates, notes, complete/reopen, delete; Home card for overdue/due today - iCloud VTODO lists read and written over CalDAV (If-Match, edits patch the latest copy so priority/alarms survive); NE Hub lists work regardless - Fix: text inside a nested VALARM was read as the task's / event's own description and then written back on edit; nested components are now skipped (regression tests added) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5c7917e57c
commit
916882e55d
9 files changed
+307
-15
No files matched your search
@@ -70,19 +70,22 @@ async function connect(appleId, password) {
|
||||
const rt = tag(r, 'resourcetype') || '';
|
||||
if (!/<(?:\w+:)?calendar\s*\/?>/i.test(rt)) continue;
|
||||
const comps = tag(r, 'supported-calendar-component-set');
|
||||
if (comps && !/name="VEVENT"/i.test(comps)) continue; // reminders lists etc.
|
||||
const events = !comps || /name="VEVENT"/i.test(comps), todos = !!comps && /name="VTODO"/i.test(comps);
|
||||
if (!events && !todos) continue;
|
||||
const href = abs(hrefOf(r), c.url);
|
||||
const color = (tag(r, 'calendar-color') || '').trim().slice(0, 7);
|
||||
calendars.push({ id: calId(href), href, name: decode(tag(r, 'displayname') || 'Calendar').trim() || 'Calendar', color: /^#[0-9a-f]{6}$/i.test(color) ? color : '#d9453a', enabled: true });
|
||||
calendars.push({ id: calId(href), href, name: decode(tag(r, 'displayname') || 'Calendar').trim() || 'Calendar', color: /^#[0-9a-f]{6}$/i.test(color) ? color : '#d9453a', enabled: true, events, todos });
|
||||
}
|
||||
if (!calendars.length) throw new Error('No calendars found on this account');
|
||||
save({ user: o.user, pass: o.pass, calendars });
|
||||
return status();
|
||||
}
|
||||
|
||||
const status = () => { const s = load(); return s ? { connected: true, appleId: s.user, calendars: s.calendars.map(({ id, name, color, enabled }) => ({ id, name, color, enabled })) } : { connected: false }; };
|
||||
const status = () => { const s = load(); return s ? { connected: true, appleId: s.user, calendars: s.calendars.filter(c => c.events !== false).map(({ id, name, color, enabled }) => ({ id, name, color, enabled })), reminderLists: s.calendars.filter(c => c.todos).length } : { connected: false }; };
|
||||
function toggle(id, enabled) { const s = load(); if (!s) throw new Error('Not connected'); const c = s.calendars.find(x => x.id === id); if (!c) throw new Error('Calendar not found'); c.enabled = !!enabled; save(s); return status(); }
|
||||
const enabled = () => { const s = load(); return s ? s.calendars.filter(c => c.enabled).map(({ id, name, color }) => ({ id, name, color })) : []; };
|
||||
const enabled = () => { const s = load(); return s ? s.calendars.filter(c => c.enabled && c.events !== false).map(({ id, name, color }) => ({ id, name, color })) : []; };
|
||||
/** Task lists (VTODO collections) the account exposes. Apple hides upgraded Reminders lists from CalDAV, so this may be empty. */
|
||||
const todoLists = () => { const s = load(); return s ? s.calendars.filter(c => c.todos).map(({ id, name, color }) => ({ id, name, color })) : []; };
|
||||
const inside = (c, href) => { if (!String(href || '').startsWith(c.href.replace(/\/?$/, '/'))) throw new Error('That event is not in this calendar'); };
|
||||
const cred = id => { const s = load(); if (!s) throw new Error('iCloud is not connected'); const c = s.calendars.find(x => x.id === id); if (!c) throw new Error('Calendar not found'); return { s, c, o: { user: s.user, pass: s.pass } }; };
|
||||
|
||||
@@ -90,7 +93,7 @@ const cred = id => { const s = load(); if (!s) throw new Error('iCloud is not co
|
||||
async function listRaw(from, to) {
|
||||
const s = load(); if (!s) return [];
|
||||
const out = [];
|
||||
await Promise.all(s.calendars.filter(c => c.enabled).map(async c => {
|
||||
await Promise.all(s.calendars.filter(c => c.enabled && c.events !== false).map(async c => {
|
||||
const r = await dav('REPORT', c.href, { user: s.user, pass: s.pass, depth: 1, body: REPORT(from - 864e5, to + 864e5) });
|
||||
if (r.status === 401) throw new Error('iCloud sign-in no longer works. Reconnect with a new app-specific password.');
|
||||
if (r.status >= 400) throw new Error(`${c.name}: iCloud answered ${r.status}`);
|
||||
@@ -102,6 +105,16 @@ async function listRaw(from, to) {
|
||||
return out;
|
||||
}
|
||||
|
||||
const REPORT_TODO = '<?xml version="1.0"?><c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav"><d:prop><d:getetag/><c:calendar-data/></d:prop><c:filter><c:comp-filter name="VCALENDAR"><c:comp-filter name="VTODO"/></c:comp-filter></c:filter></c:calendar-query>';
|
||||
/** Raw task entries in one list: [{ href, etag, ics }] */
|
||||
async function listTodoRaw(id) {
|
||||
const { c, o } = cred(id);
|
||||
const r = await dav('REPORT', c.href, { ...o, depth: 1, body: REPORT_TODO });
|
||||
if (r.status === 401) throw new Error('iCloud sign-in no longer works. Reconnect with a new app-specific password.');
|
||||
if (r.status >= 400) throw new Error(`${c.name}: iCloud answered ${r.status}`);
|
||||
return responses(r.text).map(blk => ({ href: abs(hrefOf(blk), r.url), etag: decode(tag(blk, 'getetag') || '').trim(), ics: decode(tag(blk, 'calendar-data') || '') })).filter(x => x.ics);
|
||||
}
|
||||
|
||||
async function getResource(id, href) {
|
||||
const { c, o } = cred(id); inside(c, href);
|
||||
const r = await dav('GET', href, o);
|
||||
@@ -126,4 +139,4 @@ async function deleteEvent(id, href, etag) {
|
||||
if (r.status >= 400 && r.status !== 404) throw new Error('iCloud refused the delete (' + r.status + ')');
|
||||
}
|
||||
|
||||
module.exports = { connect, status, toggle, enabled, listRaw, getResource, putEvent, deleteEvent, wipe, isCal: id => /^icloud:[0-9a-f]{10}$/.test(id || '') };
|
||||
module.exports = { connect, status, toggle, enabled, todoLists, listTodoRaw, listRaw, getResource, putEvent, deleteEvent, wipe, isCal: id => /^icloud:[0-9a-f]{10}$/.test(id || '') };
|
||||
Reference in new issue
Block a user