mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-09 22:24:05 -08:00
✨ Add Dropcontact node (#2394)
* Add a new dropcontact node * Improvements to #2389 * ⚡ Add credentials verification * ⚡ Small improvement * ⚡ set default time to 45 seconds * ⚡ Improvements * ⚡ Improvements * ⚡ Improvements * ⚡ Improvements * ⚡ Improvements * 🐛 Set siren and language correctly Co-authored-by: PaulineDropcontact <pauline@dropcontact.io> Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
This commit is contained in:
parent
8c4040dc5b
commit
18597808f3
|
@ -0,0 +1,18 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class DropcontactApi implements ICredentialType {
|
||||
name = 'dropcontactApi';
|
||||
displayName = 'Dropcontact API';
|
||||
documentationUrl = 'dropcontact';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'API Key',
|
||||
name: 'apiKey',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
}
|
370
packages/nodes-base/nodes/Dropcontact/Dropcontact.node.ts
Normal file
370
packages/nodes-base/nodes/Dropcontact/Dropcontact.node.ts
Normal file
|
@ -0,0 +1,370 @@
|
|||
import {
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
ICredentialDataDecryptedObject,
|
||||
ICredentialsDecrypted,
|
||||
ICredentialTestFunctions,
|
||||
IDataObject,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeApiError,
|
||||
NodeCredentialTestResult,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
dropcontactApiRequest,
|
||||
validateCrendetials,
|
||||
} from './GenericFunction';
|
||||
|
||||
export class Dropcontact implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Dropcontact',
|
||||
name: 'dropcontact',
|
||||
icon: 'file:dropcontact.svg',
|
||||
group: ['transform'],
|
||||
version: 1,
|
||||
description: 'Find B2B emails and enrich contacts',
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
defaults: {
|
||||
name: 'Dropcontact',
|
||||
color: '#0ABA9F',
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
credentials: [
|
||||
{
|
||||
name: 'dropcontactApi',
|
||||
required: true,
|
||||
testedBy: 'dropcontactApiCredentialTest',
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Resource',
|
||||
noDataExpression: true,
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Contact',
|
||||
value: 'contact',
|
||||
},
|
||||
],
|
||||
default: 'contact',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Operation',
|
||||
noDataExpression: true,
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Enrich',
|
||||
value: 'enrich',
|
||||
description: 'Find B2B emails and enrich your contact from his name and his website',
|
||||
},
|
||||
{
|
||||
name: 'Fetch Request',
|
||||
value: 'fetchRequest',
|
||||
},
|
||||
],
|
||||
default: 'enrich',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Request ID',
|
||||
name: 'requestId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
operation: [
|
||||
'fetchRequest',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Email',
|
||||
name: 'email',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
operation: [
|
||||
'enrich',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Simplify Output (Faster)',
|
||||
name: 'simplify',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
operation: [
|
||||
'enrich',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'When off, waits for the contact data before completing. Waiting time can be adjusted with Extend Wait Time option. When on, returns a request_id that can be used later in the Fetch Request operation.',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
operation: [
|
||||
'enrich',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Company SIREN Number',
|
||||
name: 'num_siren',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Company SIRET Code',
|
||||
name: 'siret',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Company Name',
|
||||
name: 'company',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Country',
|
||||
name: 'country',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'First Name',
|
||||
name: 'first_name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Full Name',
|
||||
name: 'full_name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Last Name',
|
||||
name: 'last_name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'LinkedIn Profile',
|
||||
name: 'linkedin',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Phone Number',
|
||||
name: 'phone',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Website',
|
||||
name: 'website',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
operation: [
|
||||
'enrich',
|
||||
],
|
||||
},
|
||||
},
|
||||
placeholder: 'Add Option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Data Fetch Wait Time',
|
||||
name: 'waitTime',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/simplify': [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: 45,
|
||||
description: 'When not simplifying the response, data will be fetched in two steps. This parameter controls how long to wait (in seconds) before trying the second step',
|
||||
},
|
||||
{
|
||||
displayName: 'French Company Enrich',
|
||||
name: 'siren',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `Whether you want the <a href="https://en.wikipedia.org/wiki/SIREN_code" target="_blank">SIREN number</a>, NAF code, TVA number, company address and informations about the company leader.</br>
|
||||
Only applies to french companies`,
|
||||
},
|
||||
{
|
||||
displayName: 'Language',
|
||||
name: 'language',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'English',
|
||||
value: 'en',
|
||||
},
|
||||
{
|
||||
name: 'French',
|
||||
value: 'fr',
|
||||
},
|
||||
],
|
||||
default: 'en',
|
||||
description: 'Whether the response is in English or French',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
methods = {
|
||||
credentialTest: {
|
||||
async dropcontactApiCredentialTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<NodeCredentialTestResult> {
|
||||
try {
|
||||
await validateCrendetials.call(this, credential.data as ICredentialDataDecryptedObject);
|
||||
} catch (error) {
|
||||
return {
|
||||
status: 'Error',
|
||||
message: 'The API Key included in the request is invalid',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'OK',
|
||||
message: 'Connection successful!',
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const entryData = this.getInputData();
|
||||
const resource = this.getNodeParameter('resource', 0) as string;
|
||||
const operation = this.getNodeParameter('operation', 0) as string;
|
||||
// tslint:disable-next-line: no-any
|
||||
let responseData: any;
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
if (resource === 'contact') {
|
||||
if (operation === 'enrich') {
|
||||
const options = this.getNodeParameter('options', 0) as IDataObject;
|
||||
const data = [];
|
||||
const simplify = this.getNodeParameter('simplify', 0) as boolean;
|
||||
|
||||
const siren = options.siren === true ? true : false;
|
||||
const language = options.language ? options.language : 'en';
|
||||
|
||||
for (let i = 0; i < entryData.length; i++) {
|
||||
const email = this.getNodeParameter('email', i) as string;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i);
|
||||
const body: IDataObject = {};
|
||||
if (email !== '') {
|
||||
body.email = email;
|
||||
}
|
||||
Object.assign(body, additionalFields);
|
||||
data.push(body);
|
||||
}
|
||||
|
||||
responseData = await dropcontactApiRequest.call(this, 'POST', '/batch', { data, siren, language }, {}) as { request_id: string, error: string, success: boolean };
|
||||
|
||||
if (!responseData.success) {
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({ error: responseData.reason || 'invalid request' });
|
||||
} else {
|
||||
throw new NodeApiError(this.getNode(), { error: responseData.reason || 'invalid request' });
|
||||
}
|
||||
}
|
||||
|
||||
if (simplify === false) {
|
||||
const waitTime = this.getNodeParameter('options.waitTime', 0, 45) as number;
|
||||
// tslint:disable-next-line: no-any
|
||||
const delay = (ms: any) => new Promise(res => setTimeout(res, ms * 1000));
|
||||
await delay(waitTime);
|
||||
responseData = await dropcontactApiRequest.call(this, 'GET', `/batch/${responseData.request_id}`, {}, {});
|
||||
if (!responseData.success) {
|
||||
if (this.continueOnFail()) {
|
||||
responseData.push({ error: responseData.reason });
|
||||
} else {
|
||||
throw new NodeApiError(this.getNode(), {
|
||||
error: responseData.reason,
|
||||
description: 'Hint: Increase the Wait Time to avoid this error',
|
||||
});
|
||||
}
|
||||
} else {
|
||||
returnData.push(...responseData.data);
|
||||
}
|
||||
} else {
|
||||
returnData.push(responseData);
|
||||
}
|
||||
}
|
||||
|
||||
if (operation === 'fetchRequest') {
|
||||
for (let i = 0; i < entryData.length; i++) {
|
||||
const requestId = this.getNodeParameter('requestId', i) as string;
|
||||
responseData = await dropcontactApiRequest.call(this, 'GET', `/batch/${requestId}`, {}, {}) as { request_id: string, error: string, success: boolean };
|
||||
if (!responseData.success) {
|
||||
if (this.continueOnFail()) {
|
||||
responseData.push({ error: responseData.reason || 'invalid request' });
|
||||
} else {
|
||||
throw new NodeApiError(this.getNode(), { error: responseData.reason || 'invalid request' });
|
||||
}
|
||||
}
|
||||
returnData.push(...responseData.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
}
|
||||
}
|
82
packages/nodes-base/nodes/Dropcontact/GenericFunction.ts
Normal file
82
packages/nodes-base/nodes/Dropcontact/GenericFunction.ts
Normal file
|
@ -0,0 +1,82 @@
|
|||
import {
|
||||
IExecuteFunctions,
|
||||
IHookFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
ICredentialDataDecryptedObject,
|
||||
ICredentialTestFunctions,
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
NodeApiError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
OptionsWithUri,
|
||||
} from 'request';
|
||||
|
||||
/**
|
||||
* Make an authenticated API request to Bubble.
|
||||
*/
|
||||
export async function dropcontactApiRequest(
|
||||
this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions,
|
||||
method: string,
|
||||
endpoint: string,
|
||||
body: IDataObject,
|
||||
qs: IDataObject,
|
||||
) {
|
||||
|
||||
const { apiKey } = await this.getCredentials('dropcontactApi') as {
|
||||
apiKey: string,
|
||||
};
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
headers: {
|
||||
'user-agent': 'n8n',
|
||||
'X-Access-Token': apiKey,
|
||||
},
|
||||
method,
|
||||
uri: `https://api.dropcontact.io${endpoint}`,
|
||||
qs,
|
||||
body,
|
||||
json: true,
|
||||
};
|
||||
|
||||
if (!Object.keys(body).length) {
|
||||
delete options.body;
|
||||
}
|
||||
|
||||
if (!Object.keys(qs).length) {
|
||||
delete options.qs;
|
||||
}
|
||||
|
||||
try {
|
||||
return await this.helpers.request!(options);
|
||||
} catch (error) {
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function validateCrendetials(this: ICredentialTestFunctions, decryptedCredentials: ICredentialDataDecryptedObject): Promise<any> { // tslint:disable-line:no-any
|
||||
const credentials = decryptedCredentials;
|
||||
|
||||
const { apiKey } = credentials as {
|
||||
apiKey: string,
|
||||
};
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
headers: {
|
||||
'user-agent': 'n8n',
|
||||
'X-Access-Token': apiKey,
|
||||
},
|
||||
method: 'POST',
|
||||
body: {
|
||||
data: [{ email: '' }],
|
||||
},
|
||||
uri: `https://api.dropcontact.io/batch`,
|
||||
json: true,
|
||||
};
|
||||
|
||||
return this.helpers.request!(options);
|
||||
}
|
||||
|
3
packages/nodes-base/nodes/Dropcontact/dropcontact.svg
Normal file
3
packages/nodes-base/nodes/Dropcontact/dropcontact.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M29.5692 16.219C28.1469 15.5371 26.5535 15.1552 24.8709 15.1552C18.8615 15.1552 13.9899 20.0268 13.9899 26.0362C13.9899 32.0456 18.8615 36.9172 24.8709 36.9172C28.9621 36.9172 32.526 34.6592 34.3843 31.3215L31.7614 29.8643C30.4154 32.2818 27.8341 33.9172 24.8709 33.9172C20.5183 33.9172 16.9899 30.3888 16.9899 26.0362C16.9899 21.6836 20.5183 18.1552 24.8709 18.1552C26.0896 18.1552 27.2437 18.4318 28.2738 18.9257L29.5692 16.219ZM34.7779 1.98444V27.7461H31.669V0.899363C29.5459 0.313171 27.3095 0 25 0C11.1929 0 0 11.1929 0 25C0 38.8071 11.1929 50 25 50C38.8071 50 50 38.8071 50 25C50 14.6626 43.7259 5.7907 34.7779 1.98444Z" fill="#0ABA9F"/>
|
||||
</svg>
|
After Width: | Height: | Size: 773 B |
|
@ -78,6 +78,7 @@
|
|||
"dist/credentials/DriftOAuth2Api.credentials.js",
|
||||
"dist/credentials/DropboxApi.credentials.js",
|
||||
"dist/credentials/DropboxOAuth2Api.credentials.js",
|
||||
"dist/credentials/DropcontactApi.credentials.js",
|
||||
"dist/credentials/EgoiApi.credentials.js",
|
||||
"dist/credentials/ElasticsearchApi.credentials.js",
|
||||
"dist/credentials/ElasticSecurityApi.credentials.js",
|
||||
|
@ -379,6 +380,7 @@
|
|||
"dist/nodes/Disqus/Disqus.node.js",
|
||||
"dist/nodes/Drift/Drift.node.js",
|
||||
"dist/nodes/Dropbox/Dropbox.node.js",
|
||||
"dist/nodes/Dropcontact/Dropcontact.node.js",
|
||||
"dist/nodes/EditImage.node.js",
|
||||
"dist/nodes/Egoi/Egoi.node.js",
|
||||
"dist/nodes/Elastic/ElasticSecurity/ElasticSecurity.node.js",
|
||||
|
|
Loading…
Reference in a new issue