diff --git a/packages/nodes-base/credentials/HunterApi.credentials.ts b/packages/nodes-base/credentials/HunterApi.credentials.ts new file mode 100644 index 0000000000..802ff5159b --- /dev/null +++ b/packages/nodes-base/credentials/HunterApi.credentials.ts @@ -0,0 +1,17 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class HunterApi implements ICredentialType { + name = 'hunterApi'; + displayName = 'Hunter API'; + properties = [ + { + displayName: 'API Key', + name: 'apiKey', + type: 'string' as NodePropertyTypes, + default: '', + }, + ]; +} diff --git a/packages/nodes-base/nodes/Hunter/GenericFunctions.ts b/packages/nodes-base/nodes/Hunter/GenericFunctions.ts new file mode 100644 index 0000000000..02fafdd482 --- /dev/null +++ b/packages/nodes-base/nodes/Hunter/GenericFunctions.ts @@ -0,0 +1,56 @@ +import { OptionsWithUri } from 'request'; +import { + IExecuteFunctions, + IExecuteSingleFunctions, + IHookFunctions, + ILoadOptionsFunctions, +} from 'n8n-core'; +import { IDataObject } from 'n8n-workflow'; + +export async function hunterApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any + const credentials = this.getCredentials('hunterApi'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + qs = Object.assign({ api_key: credentials.apiKey }, qs); + let options: OptionsWithUri = { + method, + qs, + body, + uri: uri ||`https://api.hunter.io/v2${resource}`, + json: true + }; + options = Object.assign({}, options, option); + if (Object.keys(options.body).length === 0) { + delete options.body; + } + try { + return await this.helpers.request!(options); + } catch (err) { + throw new Error(err); + } +} + +/** + * Make an API request to paginated flow endpoint + * and return all results + */ +export async function hunterApiRequestAllItems(this: IHookFunctions | IExecuteFunctions| ILoadOptionsFunctions, propertyName: string, method: string, resource: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + query.offset = 0; + query.limit = 100; + + do { + responseData = await hunterApiRequest.call(this, method, resource, body, query); + returnData.push.apply(returnData, responseData[propertyName]); + query.offset += query.limit; + } while ( + responseData.meta !== undefined && + responseData.meta.results !== undefined && + responseData.meta.offset <= responseData.meta.results + ); + return returnData; +} diff --git a/packages/nodes-base/nodes/Hunter/Hunter.node.ts b/packages/nodes-base/nodes/Hunter/Hunter.node.ts new file mode 100644 index 0000000000..b4df2e1595 --- /dev/null +++ b/packages/nodes-base/nodes/Hunter/Hunter.node.ts @@ -0,0 +1,335 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; +import { + IDataObject, + INodeTypeDescription, + INodeExecutionData, + INodeType, +} from 'n8n-workflow'; +import { + hunterApiRequest, + hunterApiRequestAllItems, +} from './GenericFunctions'; + +export class Hunter implements INodeType { + description: INodeTypeDescription = { + displayName: 'Hunter', + name: 'hunter', + icon: 'file:hunter.png', + group: ['output'], + version: 1, + subtitle: '={{$parameter["operation"]}}', + description: 'Consume Hunter API', + defaults: { + name: 'Hunter', + color: '#ff3807', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'hunterApi', + required: true, + } + ], + properties: [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + options: [ + { + name: ' Domain Search', + value: 'domainSearch', + description: 'Get every email address found on the internet using a given domain name, with sources.', + }, + { + name: ' Email Finder', + value: 'emailFinder', + description: 'Generates or retrieves the most likely email address from a domain name, a first name and a last name.', + }, + { + name: 'Email Verifier', + value: 'emailVerifier', + description: 'Allows you to verify the deliverability of an email address.', + }, + ], + default: 'domainSearch', + description: 'operation to consume.', + }, + { + displayName: 'Domain', + name: 'domain', + type: 'string', + displayOptions: { + show: { + operation: [ + 'domainSearch', + ], + returnAll: [ + false, + ], + }, + }, + default: '', + required: true, + description: 'Domain name from which you want to find the email addresses. For example, "stripe.com".', + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'domainSearch', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'domainSearch', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 100, + }, + default: 50, + description: 'How many results to return.', + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + default: {}, + displayOptions: { + show: { + operation: [ + 'domainSearch', + ], + }, + }, + options: [ + { + displayName: 'Type', + name: 'type', + type: 'options', + default: '', + options: [ + { + name: 'Personal', + value: 'personal', + }, + { + name: 'Generic', + value: 'generic', + }, + ] + }, + { + displayName: 'Seniority', + name: 'seniority', + type: 'multiOptions', + default: [], + options: [ + { + name: 'Junior', + value: 'junior', + }, + { + name: 'Senior', + value: 'senior', + }, + { + name: 'Executive', + value: 'executive', + }, + ] + }, + { + displayName: 'Department', + name: 'department', + type: 'multiOptions', + default: [], + options: [ + { + name: 'Executive', + value: 'executive', + }, + { + name: 'IT', + value: 'it', + }, + { + name: 'Finance', + value: 'finance', + }, + { + name: 'Management', + value: 'management', + }, + { + name: 'Sales', + value: 'sales', + }, + { + name: 'Legal', + value: 'legal', + }, + { + name: 'Support', + value: 'support', + }, + { + name: 'HR', + value: 'hr', + }, + { + name: 'Marketing', + value: 'marketing', + }, + { + name: 'Communication', + value: 'communication', + }, + ] + }, + ], + }, + { + displayName: 'Domain', + name: 'domain', + type: 'string', + default: '', + displayOptions: { + show: { + operation: [ + 'emailFinder' + ], + }, + }, + required: true, + description: 'Domain name from which you want to find the email addresses. For example, "stripe.com".', + }, + { + displayName: 'First Name', + name: 'firstname', + type: 'string', + displayOptions: { + show: { + operation: [ + 'emailFinder' + ], + }, + }, + default: '', + required: true, + description: `The person's first name. It doesn't need to be in lowercase..`, + }, + { + displayName: 'Last Name', + name: 'lastname', + type: 'string', + displayOptions: { + show: { + operation: [ + 'emailFinder' + ], + }, + }, + default: '', + required: true, + description: `The person's last name. It doesn't need to be in lowercase..`, + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + displayOptions: { + show: { + operation: [ + 'emailVerifier' + ], + }, + }, + default: '', + required: true, + description: 'The email address you want to verify', + }, + ], + }; + + 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; + for (let i = 0; i < length; i++) { + const operation = this.getNodeParameter('operation', 0) as string; + //https://hunter.io/api-documentation/v2#domain-search + if (operation === 'domainSearch') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const filters = this.getNodeParameter('filters', i) as IDataObject; + const domain = this.getNodeParameter('domain', i) as string; + qs.domain = domain; + if (filters.type){ + qs.type = filters.type; + } + if (filters.seniority){ + qs.seniority = (filters.seniority as string[]).join(','); + } + if (filters.department){ + qs.department = (filters.department as string[]).join(','); + } + if (returnAll) { + responseData = await hunterApiRequestAllItems.call(this, 'data', 'GET', '/domain-search', {}, qs); + } else { + const limit = this.getNodeParameter('limit', i) as number; + qs.limit = limit; + responseData = await hunterApiRequest.call(this, 'GET', '/domain-search', {}, qs); + responseData = responseData.data; + } + } + //https://hunter.io/api-documentation/v2#email-finder + if (operation === 'emailFinder') { + const domain = this.getNodeParameter('domain', i) as string; + const firstname = this.getNodeParameter('firstname', i) as string; + const lastname = this.getNodeParameter('lastname', i) as string; + qs.first_name = firstname; + qs.last_name = lastname; + qs.domain = domain; + responseData = await hunterApiRequest.call(this, 'GET', '/email-finder', {}, qs); + responseData = responseData.data; + } + //https://hunter.io/api-documentation/v2#email-verifier + if (operation === 'emailVerifier') { + const email = this.getNodeParameter('email', i) as string; + qs.email = email; + responseData = await hunterApiRequest.call(this, 'GET', '/email-verifier', {}, qs); + responseData = responseData.data; + } + 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/Hunter/hunter.png b/packages/nodes-base/nodes/Hunter/hunter.png new file mode 100644 index 0000000000..fb214f80ee Binary files /dev/null and b/packages/nodes-base/nodes/Hunter/hunter.png differ diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 2717c48b29..f7d13755c6 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -51,6 +51,7 @@ "dist/credentials/HttpDigestAuth.credentials.js", "dist/credentials/HttpHeaderAuth.credentials.js", "dist/credentials/HubspotApi.credentials.js", + "dist/credentials/HunterApi.credentials.js", "dist/credentials/Imap.credentials.js", "dist/credentials/IntercomApi.credentials.js", "dist/credentials/JiraSoftwareCloudApi.credentials.js", @@ -133,6 +134,7 @@ "dist/nodes/HtmlExtract/HtmlExtract.node.js", "dist/nodes/HttpRequest.node.js", "dist/nodes/Hubspot/Hubspot.node.js", + "dist/nodes/Hunter/Hunter.node.js", "dist/nodes/If.node.js", "dist/nodes/Intercom/Intercom.node.js", "dist/nodes/Interval.node.js",