From 5ec38d6136ab1349a137fd7fbb3a4d92d6f91d8a Mon Sep 17 00:00:00 2001 From: Manuel <30572287+manuel-rw@users.noreply.github.com> Date: Wed, 1 Jul 2026 13:56:27 +0200 Subject: [PATCH] feat: add pushover integration --- .../docs/docs/integrations/pushover/index.mdx | 42 +++++++++++++++++++ apps/docs/docs/integrations/pushover/index.ts | 8 ++++ .../docs/docs/widgets/notifications/index.mdx | 4 +- .../src/docs/homarr-docs-sitemap.ts | 2 + .../src/docs/integration-doc-slugs.ts | 1 + packages/definitions/src/integration.ts | 9 ++++ packages/integrations/src/base/creator.ts | 2 + packages/integrations/src/index.ts | 1 + .../src/pushover/pushover-integration.ts | 38 +++++++++++++++++ .../src/pushover/pushover-schema.ts | 9 ++++ 10 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 apps/docs/docs/integrations/pushover/index.mdx create mode 100644 apps/docs/docs/integrations/pushover/index.ts create mode 100644 packages/integrations/src/pushover/pushover-integration.ts create mode 100644 packages/integrations/src/pushover/pushover-schema.ts diff --git a/apps/docs/docs/integrations/pushover/index.mdx b/apps/docs/docs/integrations/pushover/index.mdx new file mode 100644 index 000000000..7266b7321 --- /dev/null +++ b/apps/docs/docs/integrations/pushover/index.mdx @@ -0,0 +1,42 @@ +--- +title: 'Pushover' +description: "Pushover is a service for receiving real-time push notifications on your Android, iPhone, iPad, and Desktop devices." +hide_title: true +--- + +import { notificationsWidget } from '@site/docs/widgets/notifications'; +import { AddingIntegration } from '@site/src/components/integrations/adding'; +import { IntegrationHeader } from '@site/src/components/integrations/header'; +import { IntegrationSecrets } from '@site/src/components/integrations/secrets'; +import { IntegrationCapabilites } from '@site/src/components/integrations/widgets'; +import { pushoverIntegration } from '.'; + + + +### Widgets & Capabilities + + +### Adding the integration + + +### Secrets + diff --git a/apps/docs/docs/integrations/pushover/index.ts b/apps/docs/docs/integrations/pushover/index.ts new file mode 100644 index 000000000..b4fe1193d --- /dev/null +++ b/apps/docs/docs/integrations/pushover/index.ts @@ -0,0 +1,8 @@ +import { IntegrationDefinition } from "@site/src/types"; + +export const pushoverIntegration: IntegrationDefinition = { + name: "Pushover", + description: "Pushover is a service for receiving real-time push notifications on your devices.", + iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/pushover.svg", + path: "../../integrations/pushover", +}; diff --git a/apps/docs/docs/widgets/notifications/index.mdx b/apps/docs/docs/widgets/notifications/index.mdx index 0a715ab3c..1f58d8ec6 100644 --- a/apps/docs/docs/widgets/notifications/index.mdx +++ b/apps/docs/docs/widgets/notifications/index.mdx @@ -15,6 +15,7 @@ import { notificationsWidget } from '.'; import { ntfyIntegration } from '@site/docs/integrations/ntfy'; import { nextcloudIntegration } from '@site/docs/integrations/nextcloud'; import { gotifyIntegration } from '@site/docs/integrations/gotify'; +import { pushoverIntegration } from '@site/docs/integrations/pushover'; diff --git a/packages/definitions/src/docs/homarr-docs-sitemap.ts b/packages/definitions/src/docs/homarr-docs-sitemap.ts index efc75332c..e150afc21 100644 --- a/packages/definitions/src/docs/homarr-docs-sitemap.ts +++ b/packages/definitions/src/docs/homarr-docs-sitemap.ts @@ -182,6 +182,7 @@ export type HomarrDocumentationPath = | "/docs/integrations/glances" | "/docs/integrations/gluetun" | "/docs/integrations/gotify" + | "/docs/integrations/pushover" | "/docs/integrations/home-assistant" | "/docs/integrations/ical" | "/docs/integrations/immich" @@ -326,6 +327,7 @@ export type HomarrDocumentationPath = | "/docs/integrations/quay" | "/docs/integrations/ntfy" | "/docs/integrations/gotify" + | "/docs/integrations/pushover" | "/docs/integrations/ical" | "/docs/integrations/anchor" | "/docs/integrations/truenas" diff --git a/packages/definitions/src/docs/integration-doc-slugs.ts b/packages/definitions/src/docs/integration-doc-slugs.ts index 90c3cae8b..fe430fc31 100644 --- a/packages/definitions/src/docs/integration-doc-slugs.ts +++ b/packages/definitions/src/docs/integration-doc-slugs.ts @@ -41,6 +41,7 @@ export const integrationDocSlugs: Record = { quay: "quay", ntfy: "ntfy", gotify: "gotify", + pushover: "pushover", ical: "ical", anchor: "anchor", truenas: "truenas", diff --git a/packages/definitions/src/integration.ts b/packages/definitions/src/integration.ts index d6c3a2f58..df17135c4 100644 --- a/packages/definitions/src/integration.ts +++ b/packages/definitions/src/integration.ts @@ -18,6 +18,7 @@ export const integrationSecretKindObject = { githubAppId: { isPublic: true, multiline: false }, githubInstallationId: { isPublic: true, multiline: false }, slug: { isPublic: true, multiline: false }, + userKey: { isPublic: false, multiline: false }, } satisfies Record; export const integrationSecretKinds = objectKeys(integrationSecretKindObject); @@ -362,6 +363,14 @@ export const integrationDefs = { category: ["notifications"], documentationUrl: createDocumentationLink("/docs/integrations/gotify"), }, + pushover: { + name: "Pushover", + secretKinds: [["apiKey", "userKey"]], + iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/pushover.svg", + category: ["notifications"], + defaultUrl: "https://api.pushover.net", + documentationUrl: createDocumentationLink("/docs/integrations/pushover"), + }, ical: { name: "iCal", secretKinds: [["url"]], diff --git a/packages/integrations/src/base/creator.ts b/packages/integrations/src/base/creator.ts index 13ed36537..b46cfddf0 100644 --- a/packages/integrations/src/base/creator.ts +++ b/packages/integrations/src/base/creator.ts @@ -39,6 +39,7 @@ import { NextcloudIntegration } from "../nextcloud/nextcloud.integration"; import { NPMIntegration } from "../npm/npm-integration"; import { GotifyIntegration } from "../gotify/gotify-integration"; import { NTFYIntegration } from "../ntfy/ntfy-integration"; +import { PushoverIntegration } from "../pushover/pushover-integration"; import { OpenMediaVaultIntegration } from "../openmediavault/openmediavault-integration"; import { OPNsenseIntegration } from "../opnsense/opnsense-integration"; import { OverseerrIntegration } from "../overseerr/overseerr-integration"; @@ -125,6 +126,7 @@ export const integrationCreators = { quay: QuayIntegration, ntfy: NTFYIntegration, gotify: GotifyIntegration, + pushover: PushoverIntegration, mock: MockIntegration, truenas: TrueNasIntegration, unraid: UnraidIntegration, diff --git a/packages/integrations/src/index.ts b/packages/integrations/src/index.ts index 66cf4be51..e65dca2e9 100644 --- a/packages/integrations/src/index.ts +++ b/packages/integrations/src/index.ts @@ -19,6 +19,7 @@ export { ReadarrIntegration } from "./media-organizer/readarr/readarr-integratio export { SonarrIntegration } from "./media-organizer/sonarr/sonarr-integration"; export { NextcloudIntegration } from "./nextcloud/nextcloud.integration"; export { GotifyIntegration } from "./gotify/gotify-integration"; +export { PushoverIntegration } from "./pushover/pushover-integration"; export { NTFYIntegration } from "./ntfy/ntfy-integration"; export { OpenMediaVaultIntegration } from "./openmediavault/openmediavault-integration"; export { GlancesIntegration } from "./glances/glances-integration"; diff --git a/packages/integrations/src/pushover/pushover-integration.ts b/packages/integrations/src/pushover/pushover-integration.ts new file mode 100644 index 000000000..6c17b7601 --- /dev/null +++ b/packages/integrations/src/pushover/pushover-integration.ts @@ -0,0 +1,38 @@ +import { ResponseError } from "@homarr/common/server"; +import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; + +import { Integration } from "../base/integration"; +import type { IntegrationTestingInput } from "../base/integration"; +import type { TestingResult } from "../base/test-connection/test-connection-service"; +import type { Notification } from "../interfaces/notifications/notification-types"; +import type { INotificationsIntegration } from "../interfaces/notifications/notifications-integration"; +import { pushoverLimitsResponseSchema } from "./pushover-schema"; + +export class PushoverIntegration extends Integration implements INotificationsIntegration { + public async testingAsync(input: IntegrationTestingInput): Promise { + const token = this.getSecretValue("apiKey"); + const url = this.url("/1/apps/limits.json", { token }); + const response = await input.fetchAsync(url); + + if (!response.ok) throw new ResponseError(response); + + const result = await pushoverLimitsResponseSchema.safeParseAsync(await response.json()); + if (!result.success) throw new Error(`Failed to parse Pushover response: ${result.error.message}`); + if (result.data.status !== 1) throw new Error("Pushover API returned non-success status"); + + return { success: true }; + } + + public async getNotificationsAsync(): Promise { + const token = this.getSecretValue("apiKey"); + const url = this.url("/1/apps/limits.json", { token }); + const response = await fetchWithTrustedCertificatesAsync(url); + + if (!response.ok) throw new ResponseError(response); + + const result = await pushoverLimitsResponseSchema.safeParseAsync(await response.json()); + if (!result.success) throw new Error(`Failed to parse Pushover response: ${result.error.message}`); + + return []; + } +} diff --git a/packages/integrations/src/pushover/pushover-schema.ts b/packages/integrations/src/pushover/pushover-schema.ts new file mode 100644 index 000000000..9d16453d4 --- /dev/null +++ b/packages/integrations/src/pushover/pushover-schema.ts @@ -0,0 +1,9 @@ +import { z } from "zod/v4"; + +export const pushoverLimitsResponseSchema = z.object({ + status: z.number(), + request: z.string(), + limit: z.number().optional(), + remaining: z.number().optional(), + reset: z.number().optional(), +});