From a7845c92dffa44aa4f0943071e7d816557c2170b Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Wed, 26 Feb 2020 20:46:00 -0500 Subject: [PATCH 1/2] :sparkles: Zulip node done --- .../credentials/ZulipApi.credentials.ts | 30 ++ .../nodes/Zulip/GenericFunctions.ts | 54 +++ .../nodes/Zulip/MessageDescription.ts | 307 ++++++++++++++++++ .../nodes/Zulip/MessageInterface.ts | 7 + packages/nodes-base/nodes/Zulip/Zulip.node.ts | 211 ++++++++++++ packages/nodes-base/nodes/Zulip/zulip.png | Bin 0 -> 6178 bytes packages/nodes-base/package.json | 6 +- 7 files changed, 613 insertions(+), 2 deletions(-) create mode 100644 packages/nodes-base/credentials/ZulipApi.credentials.ts create mode 100644 packages/nodes-base/nodes/Zulip/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/Zulip/MessageDescription.ts create mode 100644 packages/nodes-base/nodes/Zulip/MessageInterface.ts create mode 100644 packages/nodes-base/nodes/Zulip/Zulip.node.ts create mode 100644 packages/nodes-base/nodes/Zulip/zulip.png diff --git a/packages/nodes-base/credentials/ZulipApi.credentials.ts b/packages/nodes-base/credentials/ZulipApi.credentials.ts new file mode 100644 index 000000000..2fff8fb1b --- /dev/null +++ b/packages/nodes-base/credentials/ZulipApi.credentials.ts @@ -0,0 +1,30 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class ZulipApi implements ICredentialType { + name = 'zulipApi'; + displayName = 'Zulip API'; + properties = [ + { + displayName: 'URL', + name: 'url', + type: 'string' as NodePropertyTypes, + default: '', + placeholder: 'https://yourZulipDomain.zulipchat.com', + }, + { + displayName: 'Email', + name: 'email', + type: 'string' as NodePropertyTypes, + default: '', + }, + { + displayName: 'API Key', + name: 'apiKey', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Zulip/GenericFunctions.ts b/packages/nodes-base/nodes/Zulip/GenericFunctions.ts new file mode 100644 index 000000000..5fc3c3f9e --- /dev/null +++ b/packages/nodes-base/nodes/Zulip/GenericFunctions.ts @@ -0,0 +1,54 @@ +import { OptionsWithUri } from 'request'; + +import { + IExecuteFunctions, + ILoadOptionsFunctions, + BINARY_ENCODING +} from 'n8n-core'; + +import { + IDataObject, + IHookFunctions, + IWebhookFunctions +} from 'n8n-workflow'; + +export async function zulipApiRequest(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const credentials = this.getCredentials('zulipApi'); + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + const base64Credentials = `${Buffer.from(`${credentials.email}:${credentials.apiKey}`).toString(BINARY_ENCODING)}`; + + const endpoint = `${credentials.url}/api/v1`; + + let options: OptionsWithUri = { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Authorization: `Basic ${base64Credentials}`, + }, + method, + form: body, + qs: query, + uri: uri || `${endpoint}${resource}`, + json: true + }; + if (!Object.keys(body).length) { + delete options.form; + } + if (!Object.keys(query).length) { + delete options.qs; + } + options = Object.assign({}, options, option); + try { + return await this.helpers.request!(options); + } catch (error) { + if (error.response) { + let errorMessage = error.response.body.message || error.response.body.description || error.message; + throw new Error(`Zulip error response [${error.statusCode}]: ${errorMessage}`); + } + throw error; + } +} diff --git a/packages/nodes-base/nodes/Zulip/MessageDescription.ts b/packages/nodes-base/nodes/Zulip/MessageDescription.ts new file mode 100644 index 000000000..0d06c7c84 --- /dev/null +++ b/packages/nodes-base/nodes/Zulip/MessageDescription.ts @@ -0,0 +1,307 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const messageOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'message', + ], + }, + }, + options: [ + { + name: 'Delete', + value: 'delete', + description: 'Delete a message', + }, + { + name: 'Get', + value: 'get', + description: 'Get a message', + }, + { + name: 'Send Private', + value: 'sendPrivate', + description: 'Send a private message', + }, + { + name: 'Send to Stream', + value: 'sendStream', + description: 'Send a message to stream', + }, + { + name: 'Update', + value: 'update', + description: 'Update a message', + }, + { + name: 'Upload a File', + value: 'updateFile', + description: 'Upload a file', + }, + ], + default: 'sendPrivate', + description: 'The operation to perform.', + }, +] as INodeProperties[]; + +export const messageFields = [ + +/* -------------------------------------------------------------------------- */ +/* message:sendPrivate */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'To', + name: 'to', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'message', + ], + operation: [ + 'sendPrivate', + ], + }, + }, + description: 'The destination stream, or a comma separated list containing the usernames (emails) of the recipients.', + }, + { + displayName: 'Content', + name: 'content', + type: 'string', + required: true, + default: '', + typeOptions: { + alwaysOpenEditWindow: true, + }, + displayOptions: { + show: { + resource: [ + 'message', + ], + operation: [ + 'sendPrivate', + ] + }, + }, + description: 'The content of the message.', + }, +/* -------------------------------------------------------------------------- */ +/* message:sendStream */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Stream', + name: 'stream', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getStreams', + }, + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'message', + ], + operation: [ + 'sendStream', + ], + }, + }, + description: 'The destination stream, or a comma separated list containing the usernames (emails) of the recipients.', + }, + { + displayName: 'Topic', + name: 'topic', + type: 'options', + typeOptions: { + loadOptionsDependsOn: 'stream', + loadOptionsMethod: 'getTopics', + }, + required: true, + displayOptions: { + show: { + resource: [ + 'message', + ], + operation: [ + 'sendStream', + ], + }, + }, + default: '', + description: 'The topic of the message. Only required if type is stream, ignored otherwise.', + }, + { + displayName: 'Content', + name: 'content', + type: 'string', + required: true, + default: '', + typeOptions: { + alwaysOpenEditWindow: true, + }, + displayOptions: { + show: { + resource: [ + 'message', + ], + operation: [ + 'sendStream', + ] + }, + }, + description: 'The content of the message.', + }, +/* -------------------------------------------------------------------------- */ +/* message:update */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Message ID', + name: 'messageId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'message', + ], + operation: [ + 'update', + ] + }, + }, + description: 'Unique identifier for the message.', + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'message', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + displayName: 'Content', + name: 'content', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + default: '', + description: 'The content of the message', + }, + { + displayName: 'Propagate Mode', + name: 'propagateMode', + type: 'options', + options: [ + { + name: 'Change One', + value: 'changeOne', + }, + { + name: 'Change Later', + value: 'changeLater', + }, + { + name: 'Change All', + value: 'changeAll', + }, + ], + default: 'changeOne', + description: 'Which message(s) should be edited: just the one indicated in message_id, messages in the same topic that had been sent after this one, or all of them', + }, + { + displayName: 'Topic', + name: 'topic', + type: 'string', + default: '', + description: 'The topic of the message. Only required for stream messages', + }, + ] + }, +/* -------------------------------------------------------------------------- */ +/* message:get */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Message ID', + name: 'messageId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'message', + ], + operation: [ + 'get', + ] + }, + }, + description: 'Unique identifier for the message.', + }, +/* -------------------------------------------------------------------------- */ +/* message:delete */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Message ID', + name: 'messageId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'message', + ], + operation: [ + 'delete', + ] + }, + }, + description: 'Unique identifier for the message.', + }, +/* -------------------------------------------------------------------------- */ +/* message:updateFile */ +/* -------------------------------------------------------------------------- */ + { + displayName: 'Binary Property', + name: 'dataBinaryProperty', + type: 'string', + required: true, + default: 'data', + displayOptions: { + show: { + resource: [ + 'message', + ], + operation: [ + 'updateFile', + ] + }, + }, + description: 'Name of the binary property to which to
write the data of the read file.', + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Zulip/MessageInterface.ts b/packages/nodes-base/nodes/Zulip/MessageInterface.ts new file mode 100644 index 000000000..694d4a602 --- /dev/null +++ b/packages/nodes-base/nodes/Zulip/MessageInterface.ts @@ -0,0 +1,7 @@ +export interface IMessage { + type?: string; + to?: string, + topic?: string; + content?: string; + propagat_mode?: string; +} diff --git a/packages/nodes-base/nodes/Zulip/Zulip.node.ts b/packages/nodes-base/nodes/Zulip/Zulip.node.ts new file mode 100644 index 000000000..af17e2329 --- /dev/null +++ b/packages/nodes-base/nodes/Zulip/Zulip.node.ts @@ -0,0 +1,211 @@ +import { + BINARY_ENCODING, + IExecuteFunctions, +} from 'n8n-core'; +import { + IDataObject, + ILoadOptionsFunctions, + INodeTypeDescription, + INodeExecutionData, + INodeType, + INodePropertyOptions, +} from 'n8n-workflow'; +import { + zulipApiRequest, +} from './GenericFunctions'; +import { + messageFields, + messageOperations, +} from './MessageDescription'; +import { + IMessage, +} from './MessageInterface'; +import { snakeCase } from 'change-case'; + +export class Zulip implements INodeType { + description: INodeTypeDescription = { + displayName: 'Zulip', + name: 'zulip', + icon: 'file:zulip.png', + group: ['output'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume Zulip API', + defaults: { + name: 'Zulip', + color: '#156742', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'zulipApi', + required: true, + } + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Message', + value: 'message', + }, + ], + default: 'message', + description: 'Resource to consume.', + }, + ...messageOperations, + ...messageFields, + ], + }; + + methods = { + loadOptions: { + // Get all the available streams to display them to user so that he can + // select them easily + async getStreams(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const { streams } = await zulipApiRequest.call(this, 'GET', '/streams'); + for (const stream of streams) { + const streamName = stream.name; + const streamId = stream.stream_id; + returnData.push({ + name: streamName, + value: streamId, + }); + } + return returnData; + }, + // Get all the available topics to display them to user so that he can + // select them easily + async getTopics(this: ILoadOptionsFunctions): Promise { + const streamId = this.getCurrentNodeParameter('stream') as string; + const returnData: INodePropertyOptions[] = []; + const { topics } = await zulipApiRequest.call(this, 'GET', `/users/me/${streamId}/topics`); + for (const topic of topics) { + const topicName = topic.name; + const topicId = topic.name; + returnData.push({ + name: topicName, + value: topicId, + }); + } + return returnData; + }, + // Get all the available users to display them to user so that he can + // select them easily + async getUsers(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const { members } = await zulipApiRequest.call(this, 'GET', '/users'); + for (const member of members) { + const memberName = member.full_name; + const memberId = member.email; + returnData.push({ + name: memberName, + value: memberId, + }); + } + return returnData; + }, + }, + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + const length = items.length as unknown as number; + let responseData; + const qs: IDataObject = {}; + 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 === 'message') { + //https://zulipchat.com/api/send-message + if (operation === 'sendPrivate') { + const to = (this.getNodeParameter('to', i) as string[]).join(','); + const content = this.getNodeParameter('content', i) as string; + const body: IMessage = { + type: 'private', + to, + content, + }; + responseData = await zulipApiRequest.call(this, 'POST', '/messages', body); + } + //https://zulipchat.com/api/send-message + if (operation === 'sendStream') { + const stream = this.getNodeParameter('stream', i) as string; + const topic = this.getNodeParameter('topic', i) as string; + const content = this.getNodeParameter('content', i) as string; + const body: IMessage = { + type: 'stream', + to: stream, + topic, + content, + }; + responseData = await zulipApiRequest.call(this, 'POST', '/messages', body); + } + //https://zulipchat.com/api/update-message + if (operation === 'update') { + const messageId = this.getNodeParameter('messageId', i) as string; + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + const body: IMessage = {}; + if (updateFields.content) { + body.content = updateFields.content as string; + } + if (updateFields.propagateMode) { + body.propagat_mode = snakeCase(updateFields.propagateMode as string); + } + if (updateFields.topic) { + body.topic = updateFields.topic as string; + } + responseData = await zulipApiRequest.call(this, 'PATCH', `/messages/${messageId}`, body); + } + //https://zulipchat.com/api/get-raw-message + if (operation === 'get') { + const messageId = this.getNodeParameter('messageId', i) as string; + responseData = await zulipApiRequest.call(this, 'GET', `/messages/${messageId}`); + } + //https://zulipchat.com/api/delete-message + if (operation === 'delete') { + const messageId = this.getNodeParameter('messageId', i) as string; + responseData = await zulipApiRequest.call(this, 'DELETE', `/messages/${messageId}`); + } + //https://zulipchat.com/api/upload-file + if (operation === 'updateFile') { + const credentials = this.getCredentials('zulipApi'); + const binaryProperty = this.getNodeParameter('dataBinaryProperty', i) as string; + if (items[i].binary === undefined) { + throw new Error('No binary data exists on item!'); + } + //@ts-ignore + if (items[i].binary[binaryProperty] === undefined) { + throw new Error(`No binary data property "${binaryProperty}" does not exists on item!`); + } + const formData = { + file: { + //@ts-ignore + value: Buffer.from(items[i].binary[binaryProperty].data, BINARY_ENCODING), + options: { + //@ts-ignore + filename: items[i].binary[binaryProperty].fileName, + //@ts-ignore + contentType: items[i].binary[binaryProperty].mimeType, + } + } + } + responseData = await zulipApiRequest.call(this, 'POST', '/user_uploads', {}, {}, undefined, { formData } ); + responseData.uri = `${credentials!.url}${responseData.uri}`; + } + } + if (Array.isArray(responseData)) { + returnData.push.apply(returnData, responseData as IDataObject[]); + } else { + returnData.push(responseData as IDataObject); + } + } + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/Zulip/zulip.png b/packages/nodes-base/nodes/Zulip/zulip.png new file mode 100644 index 0000000000000000000000000000000000000000..e40d60988c9cc097750a867ac30463959ac51582 GIT binary patch literal 6178 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 Date: Thu, 27 Feb 2020 23:27:21 +0100 Subject: [PATCH 2/2] :zap: Minor Zulip-Node fixes --- packages/nodes-base/nodes/Zulip/GenericFunctions.ts | 10 +++++----- packages/nodes-base/nodes/Zulip/MessageInterface.ts | 2 +- packages/nodes-base/nodes/Zulip/Zulip.node.ts | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/nodes-base/nodes/Zulip/GenericFunctions.ts b/packages/nodes-base/nodes/Zulip/GenericFunctions.ts index 5fc3c3f9e..6bac0b613 100644 --- a/packages/nodes-base/nodes/Zulip/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Zulip/GenericFunctions.ts @@ -3,7 +3,6 @@ import { OptionsWithUri } from 'request'; import { IExecuteFunctions, ILoadOptionsFunctions, - BINARY_ENCODING } from 'n8n-core'; import { @@ -20,14 +19,15 @@ export async function zulipApiRequest(this: IExecuteFunctions | IWebhookFunction throw new Error('No credentials got returned!'); } - const base64Credentials = `${Buffer.from(`${credentials.email}:${credentials.apiKey}`).toString(BINARY_ENCODING)}`; - const endpoint = `${credentials.url}/api/v1`; let options: OptionsWithUri = { + auth: { + user: credentials.email as string, + password: credentials.apiKey as string, + }, headers: { 'Content-Type': 'application/x-www-form-urlencoded', - Authorization: `Basic ${base64Credentials}`, }, method, form: body, @@ -46,7 +46,7 @@ export async function zulipApiRequest(this: IExecuteFunctions | IWebhookFunction return await this.helpers.request!(options); } catch (error) { if (error.response) { - let errorMessage = error.response.body.message || error.response.body.description || error.message; + const errorMessage = error.response.body.message || error.response.body.description || error.message; throw new Error(`Zulip error response [${error.statusCode}]: ${errorMessage}`); } throw error; diff --git a/packages/nodes-base/nodes/Zulip/MessageInterface.ts b/packages/nodes-base/nodes/Zulip/MessageInterface.ts index 694d4a602..b6a6da93f 100644 --- a/packages/nodes-base/nodes/Zulip/MessageInterface.ts +++ b/packages/nodes-base/nodes/Zulip/MessageInterface.ts @@ -1,6 +1,6 @@ export interface IMessage { type?: string; - to?: string, + to?: string; topic?: string; content?: string; propagat_mode?: string; diff --git a/packages/nodes-base/nodes/Zulip/Zulip.node.ts b/packages/nodes-base/nodes/Zulip/Zulip.node.ts index af17e2329..83e5c7602 100644 --- a/packages/nodes-base/nodes/Zulip/Zulip.node.ts +++ b/packages/nodes-base/nodes/Zulip/Zulip.node.ts @@ -195,7 +195,7 @@ export class Zulip implements INodeType { contentType: items[i].binary[binaryProperty].mimeType, } } - } + }; responseData = await zulipApiRequest.call(this, 'POST', '/user_uploads', {}, {}, undefined, { formData } ); responseData.uri = `${credentials!.url}${responseData.uri}`; }