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 0000000000..41753ce310 Binary files /dev/null and b/packages/nodes-base/nodes/Line/line.png differ 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",