mirror of
https://github.com/ajnart/homarr.git
synced 2026-02-26 16:30:57 +01:00
99 lines
3.8 KiB
TypeScript
99 lines
3.8 KiB
TypeScript
import * as ical from "node-ical";
|
|
import { DAVClient } from "tsdav";
|
|
import type { Dispatcher, RequestInit as UndiciFetchRequestInit } from "undici";
|
|
|
|
import { createCertificateAgentAsync } from "@homarr/certificates/server";
|
|
import { logger } from "@homarr/log";
|
|
|
|
import { HandleIntegrationErrors } from "../base/errors/decorator";
|
|
import { integrationTsdavHttpErrorHandler } from "../base/errors/http";
|
|
import type { IntegrationTestingInput } from "../base/integration";
|
|
import { Integration } from "../base/integration";
|
|
import type { TestingResult } from "../base/test-connection/test-connection-service";
|
|
import type { ICalendarIntegration } from "../interfaces/calendar/calendar-integration";
|
|
import type { CalendarEvent } from "../interfaces/calendar/calendar-types";
|
|
|
|
@HandleIntegrationErrors([integrationTsdavHttpErrorHandler])
|
|
export class NextcloudIntegration extends Integration implements ICalendarIntegration {
|
|
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
|
const client = await this.createCalendarClientAsync(input.dispatcher);
|
|
await client.login();
|
|
|
|
return { success: true };
|
|
}
|
|
|
|
public async getCalendarEventsAsync(start: Date, end: Date, _showUnmonitored?: boolean): Promise<CalendarEvent[]> {
|
|
const client = await this.createCalendarClientAsync();
|
|
await client.login();
|
|
|
|
const calendars = await client.fetchCalendars();
|
|
// Parameters must be in ISO-8601, See https://tsdav.vercel.app/docs/caldav/fetchCalendarObjects#arguments
|
|
const calendarEvents = (
|
|
await Promise.all(
|
|
calendars.map(
|
|
async (calendar) =>
|
|
await client.fetchCalendarObjects({
|
|
calendar,
|
|
timeRange: { start: start.toISOString(), end: end.toISOString() },
|
|
}),
|
|
),
|
|
)
|
|
).flat();
|
|
|
|
return calendarEvents.map((event): CalendarEvent => {
|
|
// @ts-expect-error the typescript definitions for this package are wrong
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
|
|
const icalData = ical.default.parseICS(event.data) as ical.CalendarResponse;
|
|
const veventObject = Object.values(icalData).find((data) => data.type === "VEVENT");
|
|
|
|
if (!veventObject) {
|
|
throw new Error(`Invalid event data object: ${JSON.stringify(event.data)}. Unable to process the calendar.`);
|
|
}
|
|
|
|
logger.debug(`Converting VEVENT event to ${event.etag} from Nextcloud: ${JSON.stringify(veventObject)}`);
|
|
|
|
const date = veventObject.start;
|
|
|
|
const eventUrlWithoutHost = new URL(event.url).pathname;
|
|
const dateInMillis = veventObject.start.valueOf();
|
|
|
|
const url = this.url(
|
|
`/apps/calendar/timeGridWeek/now/edit/sidebar/${Buffer.from(eventUrlWithoutHost).toString("base64url")}/${dateInMillis / 1000}`,
|
|
);
|
|
|
|
return {
|
|
name: veventObject.summary,
|
|
date,
|
|
subName: "",
|
|
description: veventObject.description,
|
|
links: [
|
|
{
|
|
href: url.toString(),
|
|
name: "Nextcloud",
|
|
logo: "/images/apps/nextcloud.svg",
|
|
color: undefined,
|
|
notificationColor: "#ff8600",
|
|
isDark: true,
|
|
},
|
|
],
|
|
};
|
|
});
|
|
}
|
|
|
|
private async createCalendarClientAsync(dispatcher?: Dispatcher) {
|
|
return new DAVClient({
|
|
serverUrl: this.integration.url,
|
|
credentials: {
|
|
username: this.getSecretValue("username"),
|
|
password: this.getSecretValue("password"),
|
|
},
|
|
authMethod: "Basic",
|
|
defaultAccountType: "caldav",
|
|
fetchOptions: {
|
|
// We can use the undici options as the global fetch is used instead of the polyfilled.
|
|
dispatcher: dispatcher ?? (await createCertificateAgentAsync()),
|
|
} satisfies UndiciFetchRequestInit as RequestInit,
|
|
});
|
|
}
|
|
}
|