feat(calendar): add homeassistant support (#4152)
This commit is contained in:
@@ -161,7 +161,7 @@ export const integrationDefs = {
|
||||
name: "Home Assistant",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/home-assistant.svg",
|
||||
category: ["smartHomeServer"],
|
||||
category: ["smartHomeServer", "calendar"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/home-assistant"),
|
||||
},
|
||||
openmediavault: {
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import z from "zod";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { ResponseError } from "@homarr/common/server";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||
import type { TestingResult } from "../base/test-connection/test-connection-service";
|
||||
import type { ICalendarIntegration } from "../interfaces/calendar/calendar-integration";
|
||||
import type { ISmartHomeIntegration } from "../interfaces/smart-home/smart-home-integration";
|
||||
import { entityStateSchema } from "./homeassistant-types";
|
||||
import type { CalendarEvent } from "../types";
|
||||
import { calendarEventSchema, calendarsSchema, entityStateSchema } from "./homeassistant-types";
|
||||
|
||||
export class HomeAssistantIntegration extends Integration implements ISmartHomeIntegration {
|
||||
export class HomeAssistantIntegration extends Integration implements ISmartHomeIntegration, ICalendarIntegration {
|
||||
public async getEntityStateAsync(entityId: string) {
|
||||
try {
|
||||
const response = await this.getAsync(`/api/states/${entityId}`);
|
||||
@@ -62,6 +67,35 @@ export class HomeAssistantIntegration extends Integration implements ISmartHomeI
|
||||
}
|
||||
}
|
||||
|
||||
public async getCalendarEventsAsync(start: Date, end: Date): Promise<CalendarEvent[]> {
|
||||
const calendarsResponse = await this.getAsync("/api/calendars");
|
||||
if (!calendarsResponse.ok) throw new ResponseError(calendarsResponse);
|
||||
const calendars = await calendarsSchema.parseAsync(await calendarsResponse.json());
|
||||
|
||||
return await Promise.all(
|
||||
calendars.map(async (calendar) => {
|
||||
const response = await this.getAsync(`/api/calendars/${calendar.entity_id}`, { start, end });
|
||||
if (!response.ok) throw new ResponseError(response);
|
||||
return await z.array(calendarEventSchema).parseAsync(await response.json());
|
||||
}),
|
||||
).then((events) =>
|
||||
events.flat().map(
|
||||
(event): CalendarEvent => ({
|
||||
title: event.summary,
|
||||
subTitle: null,
|
||||
description: event.description,
|
||||
// If not reseting it to 0 o'clock it uses utc time and therefore shows as 2 o'clock
|
||||
startDate: "date" in event.start ? new Date(`${event.start.date}T00:00:00`) : new Date(event.start.dateTime),
|
||||
endDate: "date" in event.end ? new Date(`${event.end.date}T00:00:00`) : new Date(event.end.dateTime),
|
||||
image: null,
|
||||
indicatorColor: "#18bcf2",
|
||||
links: [],
|
||||
location: event.location,
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const response = await input.fetchAsync(this.url("/api/config"), {
|
||||
headers: this.getAuthHeaders(),
|
||||
@@ -82,8 +116,8 @@ export class HomeAssistantIntegration extends Integration implements ISmartHomeI
|
||||
* @param path full path to the API endpoint
|
||||
* @returns the response from the API
|
||||
*/
|
||||
private async getAsync(path: `/api/${string}`) {
|
||||
return await fetchWithTrustedCertificatesAsync(this.url(path), {
|
||||
private async getAsync(path: `/api/${string}`, queryParams?: Record<string, string | Date | number | boolean>) {
|
||||
return await fetchWithTrustedCertificatesAsync(this.url(path, queryParams), {
|
||||
headers: this.getAuthHeaders(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -12,3 +12,27 @@ export const entityStateSchema = z.object({
|
||||
});
|
||||
|
||||
export type EntityState = z.infer<typeof entityStateSchema>;
|
||||
|
||||
export const calendarsSchema = z.array(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
entity_id: z.string(),
|
||||
}),
|
||||
);
|
||||
|
||||
const calendarMomentSchema = z
|
||||
.object({
|
||||
date: z.string(),
|
||||
})
|
||||
.or(
|
||||
z.object({
|
||||
dateTime: z.string(),
|
||||
}),
|
||||
);
|
||||
export const calendarEventSchema = z.object({
|
||||
start: calendarMomentSchema,
|
||||
end: calendarMomentSchema,
|
||||
summary: z.string(),
|
||||
description: z.string().nullable(),
|
||||
location: z.string().nullable(),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user