diff --git a/packages/backend/src/apps/pushover/actions/index.ts b/packages/backend/src/apps/pushover/actions/index.ts
new file mode 100644
index 00000000..8b423a8a
--- /dev/null
+++ b/packages/backend/src/apps/pushover/actions/index.ts
@@ -0,0 +1,3 @@
+import sendAPushoverNotification from './send-a-pushover-notification';
+
+export default [sendAPushoverNotification];
diff --git a/packages/backend/src/apps/pushover/actions/send-a-pushover-notification/index.ts b/packages/backend/src/apps/pushover/actions/send-a-pushover-notification/index.ts
new file mode 100644
index 00000000..504f7375
--- /dev/null
+++ b/packages/backend/src/apps/pushover/actions/send-a-pushover-notification/index.ts
@@ -0,0 +1,136 @@
+import { IJSONArray, IJSONObject } from '@automatisch/types';
+import defineAction from '../../../../helpers/define-action';
+
+export default defineAction({
+ name: 'Send a Pushover Notification',
+ key: 'sendPushoverNotification',
+ description:
+ 'Generates a Pushover notification on the devices you have subscribed to.',
+ arguments: [
+ {
+ label: 'Title',
+ key: 'title',
+ type: 'string' as const,
+ required: false,
+ description: 'An optional title displayed with the message.',
+ variables: true,
+ },
+ {
+ label: 'Message',
+ key: 'message',
+ type: 'string' as const,
+ required: true,
+ description: 'The main message text of your notification.',
+ variables: true,
+ },
+ {
+ label: 'Priority',
+ key: 'priority',
+ type: 'dropdown' as const,
+ required: false,
+ description: '',
+ variables: true,
+ options: [
+ { label: 'Lowest (no notification, just in-app message)', value: -2 },
+ { label: 'Low (no sound or vibration)', value: -1 },
+ { label: 'Normal', value: 0 },
+ { label: 'High (bypass quiet hours, highlight)', value: 1 },
+ {
+ label: 'Emergency (repeat every 30 seconds until acknowledged)',
+ value: 2,
+ },
+ ],
+ },
+ {
+ label: 'Sound',
+ key: 'sound',
+ type: 'dropdown' as const,
+ required: false,
+ description: 'Optional sound to override your default.',
+ variables: true,
+ source: {
+ type: 'query',
+ name: 'getDynamicData',
+ arguments: [
+ {
+ name: 'key',
+ value: 'listSounds',
+ },
+ ],
+ },
+ },
+ {
+ label: 'URL',
+ key: 'url',
+ type: 'string' as const,
+ required: false,
+ description: 'URL to display with message.',
+ variables: true,
+ },
+ {
+ label: 'URL Title',
+ key: 'urlTitle',
+ type: 'string' as const,
+ required: false,
+ description:
+ 'Title of URL to display, otherwise URL itself will be displayed.',
+ variables: true,
+ },
+ {
+ label: 'Devices',
+ key: 'devices',
+ type: 'dynamic' as const,
+ required: false,
+ description: '',
+ fields: [
+ {
+ label: 'Device',
+ key: 'device',
+ type: 'dropdown' as const,
+ required: false,
+ description:
+ 'Restrict sending to just these devices on your account.',
+ variables: true,
+ source: {
+ type: 'query',
+ name: 'getDynamicData',
+ arguments: [
+ {
+ name: 'key',
+ value: 'listDevices',
+ },
+ ],
+ },
+ },
+ ],
+ },
+ ],
+
+ async run($) {
+ const { title, message, priority, sound, url, urlTitle } =
+ $.step.parameters;
+
+ const devices = $.step.parameters.devices as IJSONArray;
+ const allDevices = devices
+ .map((device: IJSONObject) => device.device)
+ .join(',');
+
+ const payload = {
+ token: $.auth.data.apiToken,
+ user: $.auth.data.userKey,
+ title,
+ message,
+ priority,
+ sound,
+ url,
+ url_title: urlTitle,
+ device: allDevices,
+ };
+
+ const { data } = await $.http.post('/1/messages.json', payload);
+
+ $.setActionItem({
+ raw: data,
+ });
+ },
+});
diff --git a/packages/backend/src/apps/pushover/dynamic-data/index.ts b/packages/backend/src/apps/pushover/dynamic-data/index.ts
new file mode 100644
index 00000000..28aececc
--- /dev/null
+++ b/packages/backend/src/apps/pushover/dynamic-data/index.ts
@@ -0,0 +1,4 @@
+import listDevices from './list-devices';
+import listSounds from './list-sounds';
+
+export default [listDevices, listSounds];
diff --git a/packages/backend/src/apps/pushover/dynamic-data/list-devices/index.ts b/packages/backend/src/apps/pushover/dynamic-data/list-devices/index.ts
new file mode 100644
index 00000000..ef5d6180
--- /dev/null
+++ b/packages/backend/src/apps/pushover/dynamic-data/list-devices/index.ts
@@ -0,0 +1,32 @@
+import { IGlobalVariable, IJSONObject } from '@automatisch/types';
+
+export default {
+ name: 'List devices',
+ key: 'listDevices',
+
+ async run($: IGlobalVariable) {
+ const devices: {
+ data: IJSONObject[];
+ } = {
+ data: [],
+ };
+
+ const { data } = await $.http.post(`/1/users/validate.json`, {
+ token: $.auth.data.apiToken,
+ user: $.auth.data.userKey,
+ });
+
+ if (!data?.devices?.length) {
+ return;
+ }
+
+ for (const device of data.devices) {
+ devices.data.push({
+ value: device,
+ name: device,
+ });
+ }
+
+ return devices;
+ },
+};
diff --git a/packages/backend/src/apps/pushover/dynamic-data/list-sounds/index.ts b/packages/backend/src/apps/pushover/dynamic-data/list-sounds/index.ts
new file mode 100644
index 00000000..c45f1fe0
--- /dev/null
+++ b/packages/backend/src/apps/pushover/dynamic-data/list-sounds/index.ts
@@ -0,0 +1,30 @@
+import { IGlobalVariable, IJSONObject } from '@automatisch/types';
+
+export default {
+ name: 'List sounds',
+ key: 'listSounds',
+
+ async run($: IGlobalVariable) {
+ const sounds: {
+ data: IJSONObject[];
+ } = {
+ data: [],
+ };
+
+ const params = {
+ token: $.auth.data.apiToken,
+ };
+
+ const { data } = await $.http.get(`/1/sounds.json`, { params });
+ const soundEntries = Object.entries(data.sounds);
+
+ for (const [key, value] of soundEntries) {
+ sounds.data.push({
+ value: key,
+ name: value as string,
+ });
+ }
+
+ return sounds;
+ },
+};
diff --git a/packages/backend/src/apps/pushover/index.ts b/packages/backend/src/apps/pushover/index.ts
index 212df7d3..6a88e23d 100644
--- a/packages/backend/src/apps/pushover/index.ts
+++ b/packages/backend/src/apps/pushover/index.ts
@@ -1,5 +1,7 @@
import defineApp from '../../helpers/define-app';
import auth from './auth';
+import actions from './actions';
+import dynamicData from './dynamic-data';
export default defineApp({
name: 'Pushover',
@@ -11,4 +13,6 @@ export default defineApp({
primaryColor: '249DF1',
supportsConnections: true,
auth,
+ actions,
+ dynamicData,
});
diff --git a/packages/docs/pages/.vitepress/config.js b/packages/docs/pages/.vitepress/config.js
index 741200f3..8fe263df 100644
--- a/packages/docs/pages/.vitepress/config.js
+++ b/packages/docs/pages/.vitepress/config.js
@@ -266,7 +266,10 @@ export default defineConfig({
text: 'Pushover',
collapsible: true,
collapsed: true,
- items: [{ text: 'Connection', link: '/apps/pushover/connection' }],
+ items: [
+ { text: 'Actions', link: '/apps/pushover/actions' },
+ { text: 'Connection', link: '/apps/pushover/connection' },
+ ],
},
{
text: 'RSS',
diff --git a/packages/docs/pages/apps/pushover/actions.md b/packages/docs/pages/apps/pushover/actions.md
new file mode 100644
index 00000000..8e799a17
--- /dev/null
+++ b/packages/docs/pages/apps/pushover/actions.md
@@ -0,0 +1,12 @@
+---
+favicon: /favicons/pushover.svg
+items:
+ - name: Send a Pushover Notification
+ desc: Generates a Pushover notification on the devices you have subscribed to.
+---
+
+
+
+
diff --git a/packages/docs/pages/guide/available-apps.md b/packages/docs/pages/guide/available-apps.md
index 8b6a0d9c..b6f2d901 100644
--- a/packages/docs/pages/guide/available-apps.md
+++ b/packages/docs/pages/guide/available-apps.md
@@ -27,6 +27,7 @@ The following integrations are currently supported by Automatisch.
- [Pipedrive](/apps/pipedrive/triggers)
- [Placetel](/apps/placetel/triggers)
- [PostgreSQL](/apps/postgresql/actions)
+- [Pushover](/apps/pushover/actions)
- [RSS](/apps/rss/triggers)
- [Salesforce](/apps/salesforce/triggers)
- [Scheduler](/apps/scheduler/triggers)