From 1ce19de200f790a7382794e651f9d64c3e3fa889 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Tue, 10 Nov 2020 13:08:48 -0500 Subject: [PATCH] :sparkles: Add Line-Node (#1137) * :sparkles: Line-Node * :zap: Improvements --- .../LineNotifyOAuth2Api.credentials.ts | 47 +++++ .../nodes-base/nodes/Line/GenericFunctions.ts | 50 +++++ packages/nodes-base/nodes/Line/Line.node.ts | 144 ++++++++++++++ .../nodes/Line/NotificationDescription.ts | 176 ++++++++++++++++++ packages/nodes-base/nodes/Line/line.png | Bin 0 -> 4529 bytes packages/nodes-base/package.json | 2 + 6 files changed, 419 insertions(+) create mode 100644 packages/nodes-base/credentials/LineNotifyOAuth2Api.credentials.ts create mode 100644 packages/nodes-base/nodes/Line/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/Line/Line.node.ts create mode 100644 packages/nodes-base/nodes/Line/NotificationDescription.ts create mode 100644 packages/nodes-base/nodes/Line/line.png diff --git a/packages/nodes-base/credentials/LineNotifyOAuth2Api.credentials.ts b/packages/nodes-base/credentials/LineNotifyOAuth2Api.credentials.ts new file mode 100644 index 0000000000..f97af2e872 --- /dev/null +++ b/packages/nodes-base/credentials/LineNotifyOAuth2Api.credentials.ts @@ -0,0 +1,47 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class LineNotifyOAuth2Api implements ICredentialType { + name = 'lineNotifyOAuth2Api'; + extends = [ + 'oAuth2Api', + ]; + displayName = 'Line Notify OAuth2 API'; + properties = [ + { + displayName: 'Authorization URL', + name: 'authUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://notify-bot.line.me/oauth/authorize', + required: true, + }, + { + displayName: 'Access Token URL', + name: 'accessTokenUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://notify-bot.line.me/oauth/token', + required: true, + }, + { + displayName: 'Scope', + name: 'scope', + type: 'hidden' as NodePropertyTypes, + default: 'notify', + required: true, + }, + { + displayName: 'Auth URI Query Parameters', + name: 'authQueryParameters', + type: 'hidden' as NodePropertyTypes, + default: '', + }, + { + displayName: 'Authentication', + name: 'authentication', + type: 'hidden' as NodePropertyTypes, + default: 'body', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Line/GenericFunctions.ts b/packages/nodes-base/nodes/Line/GenericFunctions.ts new file mode 100644 index 0000000000..cc95fadd37 --- /dev/null +++ b/packages/nodes-base/nodes/Line/GenericFunctions.ts @@ -0,0 +1,50 @@ +import { + OptionsWithUri, +} from 'request'; + +import { + IExecuteFunctions, + IExecuteSingleFunctions, + IHookFunctions, + ILoadOptionsFunctions, +} from 'n8n-core'; + +import { + IDataObject, +} from 'n8n-workflow'; + +export async function lineApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IHookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any + + let options: OptionsWithUri = { + headers: { + 'Content-Type': 'application/json', + }, + method, + body, + qs, + uri: uri || ``, + json: true, + }; + options = Object.assign({}, options, option); + + try { + if (Object.keys(body).length === 0) { + delete options.body; + } + + //@ts-ignore + return await this.helpers.requestOAuth2.call(this, 'lineNotifyOAuth2Api', options, { tokenType: 'Bearer' }); + + } catch (error) { + + let errorMessage; + + if (error.response && error.response.body && error.response.body.message) { + + errorMessage = error.response.body.message; + + throw new Error(`Line error response [${error.statusCode}]: ${errorMessage}`); + } + throw error; + } +} diff --git a/packages/nodes-base/nodes/Line/Line.node.ts b/packages/nodes-base/nodes/Line/Line.node.ts new file mode 100644 index 0000000000..970429a17f --- /dev/null +++ b/packages/nodes-base/nodes/Line/Line.node.ts @@ -0,0 +1,144 @@ +import { + BINARY_ENCODING, + IExecuteFunctions, +} from 'n8n-core'; + +import { + IBinaryKeyData, + IDataObject, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +import { + lineApiRequest, +} from './GenericFunctions'; + +import { + notificationFields, + notificationOperations, +} from './NotificationDescription'; + +export class Line implements INodeType { + description: INodeTypeDescription = { + displayName: 'Line', + name: 'line', + icon: 'file:line.png', + group: ['input'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume Line API.', + defaults: { + name: 'Line', + color: '#00b900', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'lineNotifyOAuth2Api', + required: true, + displayOptions: { + show: { + resource: [ + 'notification', + ], + }, + }, + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Notification', + value: 'notification', + }, + ], + default: 'notification', + description: 'The resource to operate on.', + }, + ...notificationOperations, + ...notificationFields, + ], + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = (items.length as unknown) as number; + const qs: IDataObject = {}; + let responseData; + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + for (let i = 0; i < length; i++) { + + if (resource === 'notification') { + //https://notify-bot.line.me/doc/en/ + if (operation === 'send') { + const message = this.getNodeParameter('message', i) as string; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + const body: IDataObject = { + message, + }; + + Object.assign(body, additionalFields); + + if (body.hasOwnProperty('notificationDisabled')) { + body.notificationDisabled = (body.notificationDisabled) ? 'true' : 'false'; + } + + if (body.stickerUi) { + const sticker = (body.stickerUi as IDataObject).stickerValue as IDataObject; + if (sticker) { + body.stickerId = sticker.stickerId; + body.stickerPackageId = sticker.stickerPackageId; + } + delete body.stickerUi; + } + + if (body.imageUi) { + const image = (body.imageUi as IDataObject).imageValue as IDataObject; + + if (image && image.binaryData === true) { + if (items[i].binary === undefined) { + throw new Error('No binary data exists on item!'); + } + //@ts-ignore + if (items[i].binary[image.binaryProperty] === undefined) { + throw new Error(`No binary data property "${image.binaryProperty}" does not exists on item!`); + } + + const binaryData = (items[i].binary as IBinaryKeyData)[image.binaryProperty as string]; + + body.imageFile = { + value: Buffer.from(binaryData.data, BINARY_ENCODING), + options: { + filename: binaryData.fileName, + }, + }; + } else { + body.imageFullsize = image.imageFullsize; + body.imageThumbnail = image.imageThumbnail; + } + delete body.imageUi; + } + responseData = await lineApiRequest.call(this, 'POST', '', {}, {}, 'https://notify-api.line.me/api/notify', { formData: body }); + } + } + } + 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/Line/NotificationDescription.ts b/packages/nodes-base/nodes/Line/NotificationDescription.ts new file mode 100644 index 0000000000..bcc053499f --- /dev/null +++ b/packages/nodes-base/nodes/Line/NotificationDescription.ts @@ -0,0 +1,176 @@ +import { + INodeProperties, + } from 'n8n-workflow'; + +export const notificationOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'notification', + ], + }, + }, + options: [ + { + name: 'Send', + value: 'send', + description: 'Sends notifications to users or groups', + }, + ], + default: 'send', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const notificationFields = [ + +/* -------------------------------------------------------------------------- */ +/* notification:send */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Message', + name: 'message', + required: true, + type: 'string', + displayOptions: { + show: { + operation: [ + 'send', + ], + resource: [ + 'notification', + ], + }, + }, + default: '', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + operation: [ + 'send', + ], + resource: [ + 'notification', + ], + }, + }, + options: [ + { + displayName: 'Image', + name: 'imageUi', + placeholder: 'Add Image', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'imageValue', + displayName: 'image', + values: [ + { + displayName: 'Binary Data', + name: 'binaryData', + type: 'boolean', + default: false, + }, + { + displayName: 'Image Full Size', + name: 'imageFullsize', + type: 'string', + default: '', + displayOptions: { + show: { + binaryData: [ + false, + ], + }, + }, + description: 'HTTP/HTTPS URL. Maximum size of 2048×2048px JPEG', + }, + { + displayName: 'Image Thumbnail', + name: 'imageThumbnail', + type: 'string', + displayOptions: { + show: { + binaryData: [ + false, + ], + }, + }, + default: '', + description: 'HTTP/HTTPS URL. Maximum size of 240×240px JPEG', + }, + { + displayName: 'Binary Property', + name: 'binaryProperty', + type: 'string', + displayOptions: { + show: { + binaryData: [ + true, + ], + }, + }, + default: 'data', + description: `Name of the property that holds the binary data.
`, + }, + ], + }, + ], + }, + { + displayName: 'Notification Disabled', + name: 'notificationDisabled', + type: 'boolean', + default: false, + description: `true: The user doesn't receive a push notification when the message is sent.
+ false: The user receives a push notification when the message is sent`, + }, + { + displayName: 'Sticker', + name: 'stickerUi', + placeholder: 'Add Sticker', + type: 'fixedCollection', + typeOptions: { + multipleValues: false, + }, + default: {}, + options: [ + { + name: 'stickerValue', + displayName: 'Sticker', + values: [ + { + displayName: 'Sticker ID', + name: 'stickerId', + type: 'number', + default: '', + description: 'Sticker ID', + }, + { + displayName: 'Sticker Package ID', + name: 'stickerPackageId', + type: 'number', + default: '', + description: 'Package ID', + }, + ], + }, + ], + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Line/line.png b/packages/nodes-base/nodes/Line/line.png new file mode 100644 index 0000000000000000000000000000000000000000..41753ce3104a2c3a6cd50a9c5caedbab6281b393 GIT binary patch literal 4529 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 literal 0 HcmV?d00001 diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index b88362d29f..c695f8c001 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -107,6 +107,7 @@ "dist/credentials/JotFormApi.credentials.js", "dist/credentials/Kafka.credentials.js", "dist/credentials/KeapOAuth2Api.credentials.js", + "dist/credentials/LineNotifyOAuth2Api.credentials.js", "dist/credentials/LinkedInOAuth2Api.credentials.js", "dist/credentials/MailerLiteApi.credentials.js", "dist/credentials/MailchimpApi.credentials.js", @@ -315,6 +316,7 @@ "dist/nodes/Kafka/Kafka.node.js", "dist/nodes/Keap/Keap.node.js", "dist/nodes/Keap/KeapTrigger.node.js", + "dist/nodes/Line/Line.node.js", "dist/nodes/LinkedIn/LinkedIn.node.js", "dist/nodes/MailerLite/MailerLite.node.js", "dist/nodes/MailerLite/MailerLiteTrigger.node.js",