-
Notifications
You must be signed in to change notification settings - Fork 7.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(WhatsApp Business node): WhatsApp node (#3659)
* feat: base structure for whatsapp node with credentials * feat: messages operation * feat: create generic api call with credentials and test first operation * fix: add missing template params * fix: language code for template * feat: media type and start of template components * fix: remove provider name from media type * lintfix * fix: format * feat: media operations w/o upload media type * ♻️ Convert WhatsApp Business node to declarative style * 🐛 form data not being sent with boundary in header * ✨ add media operations to WhatsApp * ✨ add credentials test to WhatsApp credentials * ♻️ move preview url to optional collection in whatsapp message * ♻️ renamed media operations in whatsapp node * :refactor: move media file name to optional fields in whatsapp node * ✨ add upload from n8n for whatsapp node message resource * 🔥 remove other template component types in whatsapp node * :speech_bubble: add specialised text for media types in WhatsApp node * ⚡ Load dinamically phone number and template name * ⚡ Add action property to all operations * 🔥 Remove unnecessary imports * ⚡ Use getBinaryDataBuffer helper * ⚡ Add components property * ✨ send components for whatsapp templates and template language * 🏷️ fix WhatsApp node message function types * 🏷️ fix any in whatsapp message functions * 🔥 remove unused import * ⚡ Improvements * ⚡ Add send location * ⚡ Add send contact * ⚡ Small improvement * ♻️ changes for review * 🐛 fix presend error * ♻️ change lat/long to numbers with proper clamping * fix: bad merge * refactor: changes for review * update package-lock.json * update package.-lock.json * update Co-authored-by: cxgarcia <schlaubitzcristobal@gmail.com> Co-authored-by: ricardo <ricardoespinoza105@gmail.com>
- Loading branch information
1 parent
f37d6ba
commit f63710a
Showing
10 changed files
with
2,126 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
58 changes: 58 additions & 0 deletions
58
packages/nodes-base/credentials/WhatsAppApi.credentials.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { | ||
IAuthenticateGeneric, | ||
ICredentialDataDecryptedObject, | ||
ICredentialTestRequest, | ||
ICredentialType, | ||
IHttpRequestOptions, | ||
INodeProperties, | ||
NodePropertyTypes, | ||
} from 'n8n-workflow'; | ||
|
||
export class WhatsAppApi implements ICredentialType { | ||
name = 'whatsAppApi'; | ||
displayName = 'WhatsApp API'; | ||
documentationUrl = 'whatsApp'; | ||
properties: INodeProperties[] = [ | ||
{ | ||
displayName: 'Access Token', | ||
type: 'string', | ||
name: 'accessToken', | ||
default: '', | ||
required: true, | ||
}, | ||
{ | ||
displayName: 'Bussiness Account ID', | ||
type: 'string', | ||
name: 'businessAccountId', | ||
default: '', | ||
required: true, | ||
}, | ||
]; | ||
|
||
authenticate: IAuthenticateGeneric = { | ||
type: 'generic', | ||
properties: { | ||
headers: { | ||
Authorization: '=Bearer {{$credentials.accessToken}}', | ||
}, | ||
}, | ||
}; | ||
|
||
test: ICredentialTestRequest = { | ||
request: { | ||
baseURL: 'https://graph.facebook.com/v13.0', | ||
url: '/', | ||
ignoreHttpStatusErrors: true, | ||
}, | ||
rules: [ | ||
{ | ||
type: 'responseSuccessBody', | ||
properties: { | ||
key: 'error.type', | ||
value: 'OAuthException', | ||
message: 'Invalid access token', | ||
}, | ||
}, | ||
], | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
import { INodeProperties } from 'n8n-workflow'; | ||
import { setupUpload } from './MediaFunctions'; | ||
|
||
export const mediaFields: INodeProperties[] = [ | ||
{ | ||
displayName: 'Operation', | ||
name: 'operation', | ||
noDataExpression: true, | ||
type: 'options', | ||
placeholder: '', | ||
options: [ | ||
{ | ||
name: 'Upload', | ||
value: 'mediaUpload', | ||
action: 'Upload media', | ||
}, | ||
{ | ||
name: 'Download', | ||
value: 'mediaUrlGet', | ||
action: 'Download media', | ||
}, | ||
{ | ||
name: 'Delete', | ||
value: 'mediaDelete', | ||
action: 'Delete media', | ||
}, | ||
], | ||
default: 'mediaUpload', | ||
displayOptions: { | ||
show: { | ||
resource: ['media'], | ||
}, | ||
}, | ||
// eslint-disable-next-line n8n-nodes-base/node-param-description-weak | ||
description: 'The operation to perform on the media', | ||
}, | ||
]; | ||
|
||
export const mediaTypeFields: INodeProperties[] = [ | ||
// ---------------------------------- | ||
// operation: mediaUpload | ||
// ---------------------------------- | ||
{ | ||
displayName: 'Sender Phone Number (or ID)', | ||
name: 'phoneNumberId', | ||
type: 'options', | ||
typeOptions: { | ||
loadOptions: { | ||
routing: { | ||
request: { | ||
url: '={{$credentials.businessAccountId}}/phone_numbers', | ||
method: 'GET', | ||
}, | ||
output: { | ||
postReceive: [ | ||
{ | ||
type: 'rootProperty', | ||
properties: { | ||
property: 'data', | ||
}, | ||
}, | ||
{ | ||
type: 'setKeyValue', | ||
properties: { | ||
name: '={{$responseItem.display_phone_number}} - {{$responseItem.verified_name}}', | ||
value: '={{$responseItem.id}}', | ||
}, | ||
}, | ||
{ | ||
type: 'sort', | ||
properties: { | ||
key: 'name', | ||
}, | ||
}, | ||
], | ||
}, | ||
}, | ||
}, | ||
}, | ||
default: '', | ||
placeholder: '', | ||
routing: { | ||
request: { | ||
method: 'POST', | ||
url: '={{$value}}/media', | ||
}, | ||
}, | ||
displayOptions: { | ||
show: { | ||
operation: ['mediaUpload'], | ||
resource: ['media'], | ||
}, | ||
}, | ||
required: true, | ||
description: "The ID of the business account's phone number to store the media", | ||
}, | ||
{ | ||
displayName: 'Property Name', | ||
name: 'mediaPropertyName', | ||
type: 'string', | ||
default: 'data', | ||
displayOptions: { | ||
show: { | ||
operation: ['mediaUpload'], | ||
resource: ['media'], | ||
}, | ||
}, | ||
required: true, | ||
description: 'Name of the binary property which contains the data for the file to be uploaded', | ||
routing: { | ||
send: { | ||
preSend: [setupUpload], | ||
}, | ||
}, | ||
}, | ||
// ---------------------------------- | ||
// type: mediaUrlGet | ||
// ---------------------------------- | ||
{ | ||
displayName: 'Media ID', | ||
name: 'mediaGetId', | ||
type: 'string', | ||
default: '', | ||
displayOptions: { | ||
show: { | ||
operation: ['mediaUrlGet'], | ||
resource: ['media'], | ||
}, | ||
}, | ||
routing: { | ||
request: { | ||
method: 'GET', | ||
url: '=/{{$value}}', | ||
}, | ||
}, | ||
required: true, | ||
description: 'The ID of the media', | ||
}, | ||
// ---------------------------------- | ||
// type: mediaUrlGet | ||
// ---------------------------------- | ||
{ | ||
displayName: 'Media ID', | ||
name: 'mediaDeleteId', | ||
type: 'string', | ||
default: '', | ||
displayOptions: { | ||
show: { | ||
operation: ['mediaDelete'], | ||
resource: ['media'], | ||
}, | ||
}, | ||
routing: { | ||
request: { | ||
method: 'DELETE', | ||
url: '=/{{$value}}', | ||
}, | ||
}, | ||
required: true, | ||
description: 'The ID of the media', | ||
}, | ||
{ | ||
displayName: 'Additional Fields', | ||
name: 'additionalFields', | ||
type: 'collection', | ||
placeholder: 'Add Field', | ||
default: {}, | ||
displayOptions: { | ||
show: { | ||
resource: ['media'], | ||
operation: ['mediaUpload'], | ||
}, | ||
}, | ||
options: [ | ||
{ | ||
displayName: 'Filename', | ||
name: 'mediaFileName', | ||
type: 'string', | ||
default: '', | ||
description: 'The name to use for the file', | ||
}, | ||
], | ||
}, | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { | ||
IDataObject, | ||
IExecuteSingleFunctions, | ||
IHttpRequestOptions, | ||
NodeOperationError, | ||
} from 'n8n-workflow'; | ||
|
||
import FormData from 'form-data'; | ||
|
||
export async function setupUpload( | ||
this: IExecuteSingleFunctions, | ||
requestOptions: IHttpRequestOptions, | ||
) { | ||
const mediaPropertyName = this.getNodeParameter('mediaPropertyName') as string; | ||
if (!mediaPropertyName) { | ||
return requestOptions; | ||
} | ||
if (this.getInputData().binary?.[mediaPropertyName] === undefined || !mediaPropertyName.trim()) { | ||
throw new NodeOperationError(this.getNode(), 'Could not find file in node input data', { | ||
description: `There’s no key called '${mediaPropertyName}' with binary data in it`, | ||
}); | ||
} | ||
const binaryFile = this.getInputData().binary![mediaPropertyName]!; | ||
const mediaFileName = (this.getNodeParameter('additionalFields') as IDataObject).mediaFileName as | ||
| string | ||
| undefined; | ||
const binaryFileName = binaryFile.fileName; | ||
if (!mediaFileName && !binaryFileName) { | ||
throw new NodeOperationError(this.getNode(), 'No file name given for media upload.'); | ||
} | ||
const mimeType = binaryFile.mimeType; | ||
|
||
const buffer = await this.helpers.getBinaryDataBuffer(mediaPropertyName); | ||
|
||
const data = new FormData(); | ||
data.append('file', buffer, { | ||
contentType: mimeType, | ||
filename: mediaFileName || binaryFileName, | ||
}); | ||
data.append('messaging_product', 'whatsapp'); | ||
|
||
requestOptions.body = data; | ||
return requestOptions; | ||
} |
Oops, something went wrong.