diff --git a/packages/nodes-base/credentials/SpontitApi.credentials.ts b/packages/nodes-base/credentials/SpontitApi.credentials.ts new file mode 100644 index 0000000000..510093aeda --- /dev/null +++ b/packages/nodes-base/credentials/SpontitApi.credentials.ts @@ -0,0 +1,24 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class SpontitApi implements ICredentialType { + name = 'spontitApi'; + displayName = 'Spontit API'; + documentationUrl = 'spontit'; + properties = [ + { + displayName: 'API Key', + name: 'apiKey', + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Username', + name: 'username', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Spontit/GenericFunctions.ts b/packages/nodes-base/nodes/Spontit/GenericFunctions.ts new file mode 100644 index 0000000000..993ede1b96 --- /dev/null +++ b/packages/nodes-base/nodes/Spontit/GenericFunctions.ts @@ -0,0 +1,49 @@ +import { + OptionsWithUri, +} from 'request'; + +import { + IExecuteFunctions, + IHookFunctions, + ILoadOptionsFunctions, + IWebhookFunctions, +} from 'n8n-core'; + +import { + IDataObject, +} from 'n8n-workflow'; + +export async function spontitApiRequest(this: IExecuteFunctions | ILoadOptionsFunctions | IHookFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const credentials = this.getCredentials('spontitApi') as IDataObject; + + try { + const options: OptionsWithUri = { + headers: { + 'X-Authorization': credentials.apiKey as string, + 'X-UserId': credentials.username as string, + }, + method, + body, + qs, + uri: `https://api.spontit.com/v3${resource}`, + json: true, + }; + if (Object.keys(body).length === 0) { + delete options.body; + } + //@ts-ignore + return await this.helpers?.request(options); + } catch (error) { + + if (error.response && error.response.body && error.response.body.message) { + + const messages = error.response.body.message; + // Try to return the error prettier + throw new Error( + `Spontit error response [${error.statusCode}]: ${messages}`, + ); + } + throw error; + } +} \ No newline at end of file diff --git a/packages/nodes-base/nodes/Spontit/PushDescription.ts b/packages/nodes-base/nodes/Spontit/PushDescription.ts new file mode 100644 index 0000000000..bbb6075e6b --- /dev/null +++ b/packages/nodes-base/nodes/Spontit/PushDescription.ts @@ -0,0 +1,170 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const pushOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'push', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a push notification', + }, + ], + default: 'push', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const pushFields = [ + /* -------------------------------------------------------------------------- */ + /* push:create */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Content', + name: 'content', + type: 'string', + default: '', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'push', + ], + }, + }, + description: `To provide text in a push, supply one of either "content" or "pushContent" (or both). + Limited to 2500 characters. (Required if a value for "pushContent" is not provided).`, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'push', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Channel Name', + name: 'channelName', + type: 'string', + default: '', + description: 'The name of a channel you created. If you have not yet created a channel, simply don\'t provide this value and the push will be sent to your main account.', + }, + { + displayName: 'Expiration Stamp', + name: 'expirationStamp', + type: 'dateTime', + default: '', + description: 'A Unix timestamp. When to automatically expire your push notification. The default is 10 days after pushing. The push will become unaccessible within 15-30 minutes of the selected time, but will remain on all device screens until dismissed or clicked.', + }, + { + displayName: 'iOS DeepLink', + name: 'iOSDeepLink', + type: 'string', + default: '', + description: 'An iOS deep link. Use this to deep link into other apps. Alternatively, you can provide a universal link in the link attribute and set openLinkInApp to false.', + }, + { + displayName: 'Link', + name: 'link', + type: 'string', + default: '', + description: 'A link that can be attached to your push. Must be a valid URL.', + }, + { + displayName: 'Open In Home Feed', + name: 'openInHomeFeed', + type: 'boolean', + default: false, + description: 'Control whether the notification opens to the home feed or to a standalone page with the notification. The default (openInHomeFeed=False) is to open the notification on a standalone page.', + }, + { + displayName: 'Open Link In App', + name: 'openLinkInApp', + type: 'boolean', + default: false, + description: 'Whether to open the provided link within the iOS app or in Safari. Android PWA opens all links in the default web browser.', + }, + { + displayName: 'Push To Emails', + name: 'pushToEmails', + type: 'string', + default: '', + required: false, + description: `Emails (strings) to whom to send the notification.
+ If all three attributes 'pushToFollowers', 'pushToPhoneNumbers' and 'pushToEmails'
+ are not supplied, then everyone who follows the channel will receive the push notification.
+ If 'pushToFollowers' is supplied, only those listed in the array will receive the push notification.
+ If one of the userIds supplied does not follow the specified channel, then that userId value will be ignored.
+ See the "Followers" section to learn how to list the userIds of those who follow one of your channels.`, + }, + { + displayName: 'Push To Followers', + name: 'pushToFollowers', + type: 'string', + default: '', + description: `User IDs (strings) to whom to send the notification.
+ If all three attributes 'pushToFollowers', 'pushToPhoneNumbers' and 'pushToEmails'
+ are not supplied, then everyone who follows the channel will receive the push notification.
+ If 'pushToFollowers' is supplied, only those listed in the array will receive the push notification.
+ If one of the userIds supplied does not follow the specified channel, then that userId value will be ignored.
+ See the "Followers" section to learn how to list the userIds of those who follow one of your channels.`, + }, + { + displayName: 'Push To Phone Numbers', + name: 'pushToPhoneNumbers', + type: 'string', + default: '', + description: `Phone numbers (strings) to whom to send the notification.
+ If all three attributes 'pushToFollowers', 'pushToPhoneNumbers' and 'pushToEmails'
+ are not supplied, then everyone who follows the channel will receive the push notification.
+ If 'pushToFollowers' is supplied, only those listed in the array will receive the push notification.
+ If one of the userIds supplied does not follow the specified channel, then that userId value will be ignored.
+ See the "Followers" section to learn how to list the userIds of those who follow one of your channels.`, + }, + { + displayName: 'Subtitle', + name: 'subtitle', + type: 'string', + default: '', + description: 'The subtitle of your push. Limited to 20 characters. Only appears on iOS devices.', + }, + { + displayName: 'Title', + name: 'pushTitle', + type: 'string', + default: '', + description: 'The title of push. Appears in bold at the top. Limited to 100 characters.', + }, + { + displayName: 'Schedule', + name: 'schedule', + type: 'dateTime', + default: '', + description: 'A Unix timestamp. Schedule a push to be sent at a later date and time.', + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Spontit/Spontit.node.ts b/packages/nodes-base/nodes/Spontit/Spontit.node.ts new file mode 100644 index 0000000000..72fd75d596 --- /dev/null +++ b/packages/nodes-base/nodes/Spontit/Spontit.node.ts @@ -0,0 +1,116 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; +import { + IDataObject, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +import { + spontitApiRequest, +} from './GenericFunctions'; + +import { + pushFields, + pushOperations, +} from './PushDescription'; + +import * as moment from 'moment'; + +export class Spontit implements INodeType { + description: INodeTypeDescription = { + displayName: 'Spontit', + name: 'spontit', + icon: 'file:spontit.png', + group: ['output'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume Spontit API', + defaults: { + name: 'Spontit', + color: '#00deff', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'spontitApi', + required: true, + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Push', + value: 'push', + }, + ], + default: 'push', + description: 'The resource to operate on.', + }, + ...pushOperations, + ...pushFields, + ], + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const timezone = this.getTimezone(); + let responseData; + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + for (let i = 0; i < items.length; i++) { + if (resource === 'push') { + if (operation === 'create') { + const content = this.getNodeParameter('content', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + const body: IDataObject = { + content, + }; + + Object.assign(body, additionalFields); + + if (body.pushToFollowers) { + body.pushToFollowers = (body.pushToFollowers as string).split(','); + } + + if (body.pushToPhoneNumbers) { + body.pushToPhoneNumbers = (body.pushToPhoneNumbers as string).split(','); + } + + if (body.pushToEmails) { + body.pushToEmails = (body.pushToEmails as string).split(','); + } + + if (body.schedule) { + body.scheduled = moment.tz(body.schedule, timezone).unix(); + } + + if (body.expirationStamp) { + body.expirationStamp = moment.tz(body.expirationStamp, timezone).unix(); + } + + responseData = await spontitApiRequest.call(this, 'POST', '/push', body); + + responseData = responseData.data; + } + } + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + + } else if (responseData !== undefined) { + returnData.push(responseData as IDataObject); + } + } + + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/Spontit/spontit.png b/packages/nodes-base/nodes/Spontit/spontit.png new file mode 100644 index 0000000000..ef3981b614 Binary files /dev/null and b/packages/nodes-base/nodes/Spontit/spontit.png differ diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index df04dbf5ed..e0e0bb3f51 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -182,6 +182,7 @@ "dist/credentials/StripeApi.credentials.js", "dist/credentials/Sftp.credentials.js", "dist/credentials/Signl4Api.credentials.js", + "dist/credentials/SpontitApi.credentials.js", "dist/credentials/SpotifyOAuth2Api.credentials.js", "dist/credentials/StoryblokContentApi.credentials.js", "dist/credentials/StoryblokManagementApi.credentials.js", @@ -394,6 +395,7 @@ "dist/nodes/Slack/Slack.node.js", "dist/nodes/Sms77/Sms77.node.js", "dist/nodes/SplitInBatches.node.js", + "dist/nodes/Spontit/Spontit.node.js", "dist/nodes/Spotify/Spotify.node.js", "dist/nodes/SpreadsheetFile.node.js", "dist/nodes/SseTrigger.node.js",