feat: add nextcloud integration (#2501)

This commit is contained in:
Manuel
2025-03-08 22:13:45 +00:00
committed by GitHub
parent 0b07f227ee
commit 2e62a61f4d
8 changed files with 193 additions and 5 deletions

View File

@@ -19,6 +19,7 @@ import { RadarrIntegration } from "../media-organizer/radarr/radarr-integration"
import { ReadarrIntegration } from "../media-organizer/readarr/readarr-integration";
import { SonarrIntegration } from "../media-organizer/sonarr/sonarr-integration";
import { TdarrIntegration } from "../media-transcoding/tdarr-integration";
import { NextcloudIntegration } from "../nextcloud/nextcloud.integration";
import { OpenMediaVaultIntegration } from "../openmediavault/openmediavault-integration";
import { OverseerrIntegration } from "../overseerr/overseerr-integration";
import { createPiHoleIntegrationAsync } from "../pi-hole/pi-hole-integration-factory";
@@ -86,6 +87,7 @@ export const integrationCreators = {
tdarr: TdarrIntegration,
proxmox: ProxmoxIntegration,
emby: EmbyIntegration,
nextcloud: NextcloudIntegration,
} satisfies Record<IntegrationKind, IntegrationInstance | [(input: IntegrationInput) => Promise<Integration>]>;
type IntegrationInstanceOfKind<TKind extends keyof typeof integrationCreators> = {

View File

@@ -19,6 +19,7 @@ export { PlexIntegration } from "./plex/plex-integration";
export { ProwlarrIntegration } from "./prowlarr/prowlarr-integration";
export { LidarrIntegration } from "./media-organizer/lidarr/lidarr-integration";
export { ReadarrIntegration } from "./media-organizer/readarr/readarr-integration";
export { NextcloudIntegration } from "./nextcloud/nextcloud.integration";
// Types
export type { IntegrationInput } from "./base/integration";

View File

@@ -0,0 +1,97 @@
import dayjs from "dayjs";
import objectSupport from "dayjs/plugin/objectSupport";
import utc from "dayjs/plugin/utc";
import * as ical from "node-ical";
import { DAVClient } from "tsdav";
import { logger } from "@homarr/log";
import { Integration } from "../base/integration";
import type { CalendarEvent } from "../calendar-types";
dayjs.extend(utc);
dayjs.extend(objectSupport);
export class NextcloudIntegration extends Integration {
public async testConnectionAsync(): Promise<void> {
const client = this.createCalendarClient();
await client.login();
}
public async getCalendarEventsAsync(start: Date, end: Date): Promise<CalendarEvent[]> {
const client = this.createCalendarClient();
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 = dayjs.utc({
days: veventObject.start.getDay(),
month: veventObject.start.getMonth(),
year: veventObject.start.getFullYear(),
hours: veventObject.start.getHours(),
minutes: veventObject.start.getMinutes(),
seconds: veventObject.start.getSeconds(),
});
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: date.toDate(),
subName: "",
description: veventObject.description,
links: [
{
href: url.toString(),
name: "Nextcloud",
logo: "/images/apps/nextcloud.svg",
color: undefined,
notificationColor: "#ff8600",
isDark: true,
},
],
};
});
}
private createCalendarClient() {
return new DAVClient({
serverUrl: this.integration.url,
credentials: {
username: this.getSecretValue("username"),
password: this.getSecretValue("password"),
},
authMethod: "Basic",
defaultAccountType: "caldav",
});
}
}