feat(integrations): add ICal (#3980)
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
This commit is contained in:
@@ -18,6 +18,7 @@ import { GitHubContainerRegistryIntegration } from "../github-container-registry
|
||||
import { GithubIntegration } from "../github/github-integration";
|
||||
import { GitlabIntegration } from "../gitlab/gitlab-integration";
|
||||
import { HomeAssistantIntegration } from "../homeassistant/homeassistant-integration";
|
||||
import { ICalIntegration } from "../ical/ical-integration";
|
||||
import { JellyfinIntegration } from "../jellyfin/jellyfin-integration";
|
||||
import { JellyseerrIntegration } from "../jellyseerr/jellyseerr-integration";
|
||||
import { LinuxServerIOIntegration } from "../linuxserverio/linuxserverio-integration";
|
||||
@@ -112,6 +113,7 @@ export const integrationCreators = {
|
||||
codeberg: CodebergIntegration,
|
||||
linuxServerIO: LinuxServerIOIntegration,
|
||||
gitHubContainerRegistry: GitHubContainerRegistryIntegration,
|
||||
ical: ICalIntegration,
|
||||
quay: QuayIntegration,
|
||||
ntfy: NTFYIntegration,
|
||||
mock: MockIntegration,
|
||||
|
||||
67
packages/integrations/src/ical/ical-integration.ts
Normal file
67
packages/integrations/src/ical/ical-integration.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import ICAL from "ical.js";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
|
||||
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 { CalendarEvent } from "../interfaces/calendar/calendar-types";
|
||||
|
||||
export class ICalIntegration extends Integration implements ICalendarIntegration {
|
||||
async getCalendarEventsAsync(start: Date, end: Date): Promise<CalendarEvent[]> {
|
||||
const response = await fetchWithTrustedCertificatesAsync(super.getSecretValue("url"));
|
||||
const result = await response.text();
|
||||
const jcal = ICAL.parse(result) as unknown[];
|
||||
const comp = new ICAL.Component(jcal);
|
||||
|
||||
return comp.getAllSubcomponents("vevent").reduce((prev, vevent) => {
|
||||
const event = new ICAL.Event(vevent);
|
||||
const startDate = event.startDate.toJSDate();
|
||||
const endDate = event.endDate.toJSDate();
|
||||
|
||||
if (startDate > end) return prev;
|
||||
if (endDate < start) return prev;
|
||||
|
||||
return prev.concat({
|
||||
title: event.summary,
|
||||
subTitle: null,
|
||||
description: event.description,
|
||||
startDate,
|
||||
endDate,
|
||||
image: null,
|
||||
location: event.location,
|
||||
indicatorColor: "red",
|
||||
links: [],
|
||||
});
|
||||
}, [] as CalendarEvent[]);
|
||||
}
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const response = await input.fetchAsync(super.getSecretValue("url"));
|
||||
if (!response.ok) return TestConnectionError.StatusResult(response);
|
||||
|
||||
const result = await response.text();
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const jcal = ICAL.parse(result);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
const comp = new ICAL.Component(jcal);
|
||||
return comp.getAllSubcomponents("vevent").length > 0
|
||||
? { success: true }
|
||||
: TestConnectionError.ParseResult({
|
||||
name: "Calendar parse error",
|
||||
message: "No events found",
|
||||
cause: new Error("No events found"),
|
||||
});
|
||||
} catch (error) {
|
||||
return TestConnectionError.ParseResult({
|
||||
name: "Calendar parse error",
|
||||
message: "Failed to parse calendar",
|
||||
cause: error as Error,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ export { PlexIntegration } from "./plex/plex-integration";
|
||||
export { ProwlarrIntegration } from "./prowlarr/prowlarr-integration";
|
||||
export { TrueNasIntegration } from "./truenas/truenas-integration";
|
||||
export { OPNsenseIntegration } from "./opnsense/opnsense-integration";
|
||||
export { ICalIntegration } from "./ical/ical-integration";
|
||||
|
||||
// Types
|
||||
export type { IntegrationInput } from "./base/integration";
|
||||
|
||||
@@ -1,24 +1,41 @@
|
||||
export const radarrReleaseTypes = ["inCinemas", "digitalRelease", "physicalRelease"] as const;
|
||||
export type RadarrReleaseType = (typeof radarrReleaseTypes)[number];
|
||||
|
||||
export interface CalendarEvent {
|
||||
name: string;
|
||||
subName: string;
|
||||
date: Date;
|
||||
dates?: { type: RadarrReleaseType; date: Date }[];
|
||||
description?: string;
|
||||
thumbnail?: string;
|
||||
mediaInformation?: {
|
||||
type: "audio" | "video" | "tv" | "movie";
|
||||
seasonNumber?: number;
|
||||
episodeNumber?: number;
|
||||
};
|
||||
links: {
|
||||
href: string;
|
||||
name: string;
|
||||
color: string | undefined;
|
||||
notificationColor?: string | undefined;
|
||||
isDark: boolean | undefined;
|
||||
logo: string;
|
||||
}[];
|
||||
export interface RadarrMetadata {
|
||||
type: "radarr";
|
||||
releaseType: RadarrReleaseType;
|
||||
}
|
||||
|
||||
export type CalendarMetadata = RadarrMetadata;
|
||||
|
||||
export interface CalendarLink {
|
||||
name: string;
|
||||
isDark: boolean;
|
||||
href: string;
|
||||
color?: string;
|
||||
logo?: string;
|
||||
}
|
||||
|
||||
export interface CalendarImageBadge {
|
||||
content: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
export interface CalendarImage {
|
||||
src: string;
|
||||
badge?: CalendarImageBadge;
|
||||
aspectRatio?: { width: number; height: number };
|
||||
}
|
||||
|
||||
export interface CalendarEvent {
|
||||
title: string;
|
||||
subTitle: string | null;
|
||||
description: string | null;
|
||||
startDate: Date;
|
||||
endDate: Date | null;
|
||||
image: CalendarImage | null;
|
||||
location: string | null;
|
||||
metadata?: CalendarMetadata;
|
||||
indicatorColor: string;
|
||||
links: CalendarLink[];
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import type { IntegrationTestingInput } 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 { CalendarEvent } from "../../interfaces/calendar/calendar-types";
|
||||
import type { CalendarEvent, CalendarLink } from "../../interfaces/calendar/calendar-types";
|
||||
import { mediaOrganizerPriorities } from "../media-organizer";
|
||||
|
||||
export class LidarrIntegration extends Integration implements ICalendarIntegration {
|
||||
@@ -44,22 +44,28 @@ export class LidarrIntegration extends Integration implements ICalendarIntegrati
|
||||
const lidarrCalendarEvents = await z.array(lidarrCalendarEventSchema).parseAsync(await response.json());
|
||||
|
||||
return lidarrCalendarEvents.map((lidarrCalendarEvent): CalendarEvent => {
|
||||
const imageSrc = this.chooseBestImage(lidarrCalendarEvent);
|
||||
return {
|
||||
name: lidarrCalendarEvent.title,
|
||||
subName: lidarrCalendarEvent.artist.artistName,
|
||||
description: lidarrCalendarEvent.overview,
|
||||
thumbnail: this.chooseBestImageAsURL(lidarrCalendarEvent),
|
||||
date: lidarrCalendarEvent.releaseDate,
|
||||
mediaInformation: {
|
||||
type: "audio",
|
||||
},
|
||||
title: lidarrCalendarEvent.title,
|
||||
subTitle: lidarrCalendarEvent.artist.artistName,
|
||||
description: lidarrCalendarEvent.overview ?? null,
|
||||
startDate: lidarrCalendarEvent.releaseDate,
|
||||
endDate: null,
|
||||
image: imageSrc
|
||||
? {
|
||||
src: imageSrc.remoteUrl,
|
||||
aspectRatio: { width: 7, height: 12 },
|
||||
}
|
||||
: null,
|
||||
location: null,
|
||||
indicatorColor: "cyan",
|
||||
links: this.getLinksForLidarrCalendarEvent(lidarrCalendarEvent),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private getLinksForLidarrCalendarEvent = (event: z.infer<typeof lidarrCalendarEventSchema>) => {
|
||||
const links: CalendarEvent["links"] = [];
|
||||
const links: CalendarLink[] = [];
|
||||
|
||||
for (const link of event.artist.links) {
|
||||
switch (link.name) {
|
||||
@@ -70,7 +76,6 @@ export class LidarrIntegration extends Integration implements ICalendarIntegrati
|
||||
color: "#f5c518",
|
||||
isDark: false,
|
||||
logo: "/images/apps/vgmdb.svg",
|
||||
notificationColor: "cyan",
|
||||
});
|
||||
break;
|
||||
case "imdb":
|
||||
@@ -80,7 +85,6 @@ export class LidarrIntegration extends Integration implements ICalendarIntegrati
|
||||
color: "#f5c518",
|
||||
isDark: false,
|
||||
logo: "/images/apps/imdb.png",
|
||||
notificationColor: "cyan",
|
||||
});
|
||||
break;
|
||||
case "last":
|
||||
@@ -90,7 +94,6 @@ export class LidarrIntegration extends Integration implements ICalendarIntegrati
|
||||
color: "#cf222a",
|
||||
isDark: false,
|
||||
logo: "/images/apps/lastfm.svg",
|
||||
notificationColor: "cyan",
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import type { AtLeastOneOf } from "@homarr/common/types";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import { Integration } from "../../base/integration";
|
||||
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 { CalendarEvent } from "../../interfaces/calendar/calendar-types";
|
||||
import type { CalendarEvent, CalendarLink } from "../../interfaces/calendar/calendar-types";
|
||||
import { radarrReleaseTypes } from "../../interfaces/calendar/calendar-types";
|
||||
import { mediaOrganizerPriorities } from "../media-organizer";
|
||||
|
||||
@@ -34,33 +33,44 @@ export class RadarrIntegration extends Integration implements ICalendarIntegrati
|
||||
});
|
||||
const radarrCalendarEvents = await z.array(radarrCalendarEventSchema).parseAsync(await response.json());
|
||||
|
||||
return radarrCalendarEvents.map((radarrCalendarEvent): CalendarEvent => {
|
||||
const dates = radarrReleaseTypes
|
||||
.map((type) => (radarrCalendarEvent[type] ? { type, date: radarrCalendarEvent[type] } : undefined))
|
||||
.filter((date) => date) as AtLeastOneOf<Exclude<CalendarEvent["dates"], undefined>[number]>;
|
||||
return {
|
||||
name: radarrCalendarEvent.title,
|
||||
subName: radarrCalendarEvent.originalTitle,
|
||||
description: radarrCalendarEvent.overview,
|
||||
thumbnail: this.chooseBestImageAsURL(radarrCalendarEvent),
|
||||
date: dates[0].date,
|
||||
dates,
|
||||
mediaInformation: {
|
||||
type: "movie",
|
||||
},
|
||||
links: this.getLinksForRadarrCalendarEvent(radarrCalendarEvent),
|
||||
};
|
||||
return radarrCalendarEvents.flatMap((radarrCalendarEvent): CalendarEvent[] => {
|
||||
const imageSrc = this.chooseBestImageAsURL(radarrCalendarEvent);
|
||||
|
||||
return radarrReleaseTypes
|
||||
.map((releaseType) => ({ type: releaseType, date: radarrCalendarEvent[releaseType] }))
|
||||
.filter((item) => item.date !== undefined)
|
||||
.map((item) => ({
|
||||
title: radarrCalendarEvent.title,
|
||||
subTitle: radarrCalendarEvent.originalTitle,
|
||||
description: radarrCalendarEvent.overview ?? null,
|
||||
// Check is done above in the filter
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
startDate: item.date!,
|
||||
endDate: null,
|
||||
image: imageSrc
|
||||
? {
|
||||
src: imageSrc,
|
||||
aspectRatio: { width: 7, height: 12 },
|
||||
}
|
||||
: null,
|
||||
location: null,
|
||||
metadata: {
|
||||
type: "radarr",
|
||||
releaseType: item.type,
|
||||
},
|
||||
indicatorColor: "yellow",
|
||||
links: this.getLinksForRadarrCalendarEvent(radarrCalendarEvent),
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
private getLinksForRadarrCalendarEvent = (event: z.infer<typeof radarrCalendarEventSchema>) => {
|
||||
const links: CalendarEvent["links"] = [
|
||||
const links: CalendarLink[] = [
|
||||
{
|
||||
href: this.url(`/movie/${event.titleSlug}`).toString(),
|
||||
name: "Radarr",
|
||||
logo: "/images/apps/radarr.svg",
|
||||
color: undefined,
|
||||
notificationColor: "yellow",
|
||||
isDark: true,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -8,7 +8,7 @@ import type { IntegrationTestingInput } 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 { CalendarEvent } from "../../interfaces/calendar/calendar-types";
|
||||
import type { CalendarEvent, CalendarLink } from "../../interfaces/calendar/calendar-types";
|
||||
import { mediaOrganizerPriorities } from "../media-organizer";
|
||||
|
||||
export class ReadarrIntegration extends Integration implements ICalendarIntegration {
|
||||
@@ -50,15 +50,22 @@ export class ReadarrIntegration extends Integration implements ICalendarIntegrat
|
||||
const readarrCalendarEvents = await z.array(readarrCalendarEventSchema).parseAsync(await response.json());
|
||||
|
||||
return readarrCalendarEvents.map((readarrCalendarEvent): CalendarEvent => {
|
||||
const imageSrc = this.chooseBestImageAsURL(readarrCalendarEvent);
|
||||
|
||||
return {
|
||||
name: readarrCalendarEvent.title,
|
||||
subName: readarrCalendarEvent.author.authorName,
|
||||
description: readarrCalendarEvent.overview,
|
||||
thumbnail: this.chooseBestImageAsURL(readarrCalendarEvent),
|
||||
date: readarrCalendarEvent.releaseDate,
|
||||
mediaInformation: {
|
||||
type: "audio",
|
||||
},
|
||||
title: readarrCalendarEvent.title,
|
||||
subTitle: readarrCalendarEvent.author.authorName,
|
||||
description: readarrCalendarEvent.overview ?? null,
|
||||
startDate: readarrCalendarEvent.releaseDate,
|
||||
endDate: null,
|
||||
image: imageSrc
|
||||
? {
|
||||
src: imageSrc,
|
||||
aspectRatio: { width: 7, height: 12 },
|
||||
}
|
||||
: null,
|
||||
location: null,
|
||||
indicatorColor: "#f5c518",
|
||||
links: this.getLinksForReadarrCalendarEvent(readarrCalendarEvent),
|
||||
};
|
||||
});
|
||||
@@ -72,9 +79,8 @@ export class ReadarrIntegration extends Integration implements ICalendarIntegrat
|
||||
isDark: false,
|
||||
logo: "/images/apps/readarr.svg",
|
||||
name: "Readarr",
|
||||
notificationColor: "#f5c518",
|
||||
},
|
||||
] satisfies CalendarEvent["links"];
|
||||
] satisfies CalendarLink[];
|
||||
};
|
||||
|
||||
private chooseBestImage = (
|
||||
|
||||
@@ -8,7 +8,7 @@ import type { IntegrationTestingInput } 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 { CalendarEvent } from "../../interfaces/calendar/calendar-types";
|
||||
import type { CalendarEvent, CalendarLink } from "../../interfaces/calendar/calendar-types";
|
||||
import { mediaOrganizerPriorities } from "../media-organizer";
|
||||
|
||||
export class SonarrIntegration extends Integration implements ICalendarIntegration {
|
||||
@@ -33,33 +33,36 @@ export class SonarrIntegration extends Integration implements ICalendarIntegrati
|
||||
"X-Api-Key": super.getSecretValue("apiKey"),
|
||||
},
|
||||
});
|
||||
const sonarCalendarEvents = await z.array(sonarrCalendarEventSchema).parseAsync(await response.json());
|
||||
const sonarrCalendarEvents = await z.array(sonarrCalendarEventSchema).parseAsync(await response.json());
|
||||
|
||||
return sonarCalendarEvents.map(
|
||||
(sonarCalendarEvent): CalendarEvent => ({
|
||||
name: sonarCalendarEvent.title,
|
||||
subName: sonarCalendarEvent.series.title,
|
||||
description: sonarCalendarEvent.series.overview,
|
||||
thumbnail: this.chooseBestImageAsURL(sonarCalendarEvent),
|
||||
date: sonarCalendarEvent.airDateUtc,
|
||||
mediaInformation: {
|
||||
type: "tv",
|
||||
episodeNumber: sonarCalendarEvent.episodeNumber,
|
||||
seasonNumber: sonarCalendarEvent.seasonNumber,
|
||||
},
|
||||
links: this.getLinksForSonarCalendarEvent(sonarCalendarEvent),
|
||||
}),
|
||||
);
|
||||
return sonarrCalendarEvents.map((event): CalendarEvent => {
|
||||
const imageSrc = this.chooseBestImageAsURL(event);
|
||||
return {
|
||||
title: event.title,
|
||||
subTitle: event.series.title,
|
||||
description: event.series.overview ?? null,
|
||||
startDate: event.airDateUtc,
|
||||
endDate: null,
|
||||
image: imageSrc
|
||||
? {
|
||||
src: imageSrc,
|
||||
aspectRatio: { width: 7, height: 12 },
|
||||
}
|
||||
: null,
|
||||
location: null,
|
||||
indicatorColor: "blue",
|
||||
links: this.getLinksForSonarrCalendarEvent(event),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private getLinksForSonarCalendarEvent = (event: z.infer<typeof sonarrCalendarEventSchema>) => {
|
||||
const links: CalendarEvent["links"] = [
|
||||
private getLinksForSonarrCalendarEvent = (event: z.infer<typeof sonarrCalendarEventSchema>) => {
|
||||
const links: CalendarLink[] = [
|
||||
{
|
||||
href: this.url(`/series/${event.series.titleSlug}`).toString(),
|
||||
name: "Sonarr",
|
||||
logo: "/images/apps/sonarr.svg",
|
||||
color: undefined,
|
||||
notificationColor: "blue",
|
||||
isDark: true,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -8,32 +8,46 @@ export class CalendarMockService implements ICalendarIntegration {
|
||||
}
|
||||
}
|
||||
|
||||
const homarrMeetup = (start: Date, end: Date): CalendarEvent => ({
|
||||
name: "Homarr Meetup",
|
||||
subName: "",
|
||||
description: "Yearly meetup of the Homarr community",
|
||||
date: randomDateBetween(start, end),
|
||||
links: [
|
||||
{
|
||||
href: "https://homarr.dev",
|
||||
name: "Homarr",
|
||||
logo: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/homarr.svg",
|
||||
color: "#000000",
|
||||
notificationColor: "#fa5252",
|
||||
isDark: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
const homarrMeetup = (start: Date, end: Date): CalendarEvent => {
|
||||
const startDate = randomDateBetween(start, end);
|
||||
const endDate = new Date(startDate.getTime() + 2 * 60 * 60 * 1000); // 2 hours later
|
||||
return {
|
||||
title: "Homarr Meetup",
|
||||
subTitle: "",
|
||||
description: "Yearly meetup of the Homarr community",
|
||||
startDate,
|
||||
endDate,
|
||||
image: null,
|
||||
location: "Mountains",
|
||||
indicatorColor: "#fa5252",
|
||||
links: [
|
||||
{
|
||||
href: "https://homarr.dev",
|
||||
name: "Homarr",
|
||||
logo: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/homarr.svg",
|
||||
color: "#000000",
|
||||
isDark: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
const titanicRelease = (start: Date, end: Date): CalendarEvent => ({
|
||||
name: "Titanic",
|
||||
subName: "A classic movie",
|
||||
title: "Titanic",
|
||||
subTitle: "A classic movie",
|
||||
description: "A tragic love story set on the ill-fated RMS Titanic.",
|
||||
date: randomDateBetween(start, end),
|
||||
thumbnail: "https://image.tmdb.org/t/p/original/5bTWA20cL9LCIGNpde4Epc2Ijzn.jpg",
|
||||
mediaInformation: {
|
||||
type: "movie",
|
||||
startDate: randomDateBetween(start, end),
|
||||
endDate: null,
|
||||
image: {
|
||||
src: "https://image.tmdb.org/t/p/original/5bTWA20cL9LCIGNpde4Epc2Ijzn.jpg",
|
||||
aspectRatio: { width: 7, height: 12 },
|
||||
},
|
||||
location: null,
|
||||
metadata: {
|
||||
type: "radarr",
|
||||
releaseType: "inCinemas",
|
||||
},
|
||||
indicatorColor: "cyan",
|
||||
links: [
|
||||
{
|
||||
href: "https://www.imdb.com/title/tt0120338/",
|
||||
@@ -41,22 +55,26 @@ const titanicRelease = (start: Date, end: Date): CalendarEvent => ({
|
||||
color: "#f5c518",
|
||||
isDark: false,
|
||||
logo: "/images/apps/imdb.svg",
|
||||
notificationColor: "cyan",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const seriesRelease = (start: Date, end: Date): CalendarEvent => ({
|
||||
name: "The Mandalorian",
|
||||
subName: "A Star Wars Series",
|
||||
title: "The Mandalorian",
|
||||
subTitle: "A Star Wars Series",
|
||||
description: "A lone bounty hunter in the outer reaches of the galaxy.",
|
||||
date: randomDateBetween(start, end),
|
||||
thumbnail: "https://image.tmdb.org/t/p/original/ztvm7C7hiUpS3CZRXFmJxljICzK.jpg",
|
||||
mediaInformation: {
|
||||
type: "tv",
|
||||
seasonNumber: 1,
|
||||
episodeNumber: 1,
|
||||
startDate: randomDateBetween(start, end),
|
||||
endDate: null,
|
||||
image: {
|
||||
src: "https://image.tmdb.org/t/p/original/sWgBv7LV2PRoQgkxwlibdGXKz1S.jpg",
|
||||
aspectRatio: { width: 7, height: 12 },
|
||||
badge: {
|
||||
content: "S1:E1",
|
||||
color: "red",
|
||||
},
|
||||
},
|
||||
location: null,
|
||||
indicatorColor: "blue",
|
||||
links: [
|
||||
{
|
||||
href: "https://www.imdb.com/title/tt8111088/",
|
||||
@@ -64,7 +82,6 @@ const seriesRelease = (start: Date, end: Date): CalendarEvent => ({
|
||||
color: "#f5c518",
|
||||
isDark: false,
|
||||
logo: "/images/apps/imdb.svg",
|
||||
notificationColor: "blue",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -63,17 +63,20 @@ export class NextcloudIntegration extends Integration implements ICalendarIntegr
|
||||
);
|
||||
|
||||
return {
|
||||
name: veventObject.summary,
|
||||
date,
|
||||
subName: "",
|
||||
title: veventObject.summary,
|
||||
subTitle: null,
|
||||
description: veventObject.description,
|
||||
startDate: date,
|
||||
endDate: veventObject.end,
|
||||
image: null,
|
||||
location: veventObject.location || null,
|
||||
indicatorColor: "#ff8600",
|
||||
links: [
|
||||
{
|
||||
href: url.toString(),
|
||||
name: "Nextcloud",
|
||||
logo: "/images/apps/nextcloud.svg",
|
||||
color: undefined,
|
||||
notificationColor: "#ff8600",
|
||||
isDark: true,
|
||||
},
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user