mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-14 00:24:07 -08:00
266 lines
7.1 KiB
TypeScript
266 lines
7.1 KiB
TypeScript
import set from 'lodash.set';
|
||
import { BinaryDataManager } from 'n8n-core';
|
||
import {
|
||
IDataObject,
|
||
IExecuteSingleFunctions,
|
||
IHttpRequestOptions,
|
||
IN8nHttpFullResponse,
|
||
INodeExecutionData,
|
||
JsonObject,
|
||
NodeApiError,
|
||
NodeOperationError,
|
||
} from 'n8n-workflow';
|
||
|
||
import FormData from 'form-data';
|
||
|
||
interface WhatsAppApiError {
|
||
error: {
|
||
message: string;
|
||
type: string;
|
||
code: number;
|
||
fbtrace_id: string;
|
||
};
|
||
}
|
||
|
||
export async function addTemplateComponents(
|
||
this: IExecuteSingleFunctions,
|
||
requestOptions: IHttpRequestOptions,
|
||
) {
|
||
const params = this.getNodeParameter('templateParameters') as IDataObject;
|
||
if (!params?.parameter) {
|
||
return requestOptions;
|
||
}
|
||
const components = [
|
||
{
|
||
type: 'body',
|
||
parameters: params.parameter,
|
||
},
|
||
];
|
||
if (!requestOptions.body) {
|
||
requestOptions.body = {};
|
||
}
|
||
set(requestOptions.body as IDataObject, 'template.components', components);
|
||
return requestOptions;
|
||
}
|
||
|
||
export async function setType(this: IExecuteSingleFunctions, requestOptions: IHttpRequestOptions) {
|
||
const operation = this.getNodeParameter('operation') as string;
|
||
const messageType = this.getNodeParameter('messageType', null) as string | null;
|
||
let actualType = messageType;
|
||
|
||
if (operation === 'sendTemplate') {
|
||
actualType = 'template';
|
||
}
|
||
|
||
if (requestOptions.body) {
|
||
Object.assign(requestOptions.body, { type: actualType });
|
||
}
|
||
|
||
return requestOptions;
|
||
}
|
||
|
||
export async function mediaUploadFromItem(
|
||
this: IExecuteSingleFunctions,
|
||
requestOptions: IHttpRequestOptions,
|
||
) {
|
||
const mediaPropertyName = this.getNodeParameter('mediaPropertyName') as string;
|
||
if (this.getInputData().binary?.[mediaPropertyName] === undefined) {
|
||
throw new NodeOperationError(
|
||
this.getNode(),
|
||
`The binary property "${mediaPropertyName}" does not exist. So no file can be written!`,
|
||
);
|
||
}
|
||
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 data = new FormData();
|
||
data.append('file', await BinaryDataManager.getInstance().retrieveBinaryData(binaryFile), {
|
||
contentType: mimeType,
|
||
filename: mediaFileName ?? binaryFileName,
|
||
});
|
||
data.append('messaging_product', 'whatsapp');
|
||
|
||
const phoneNumberId = this.getNodeParameter('phoneNumberId') as string;
|
||
|
||
const result = (await this.helpers.httpRequestWithAuthentication.call(this, 'whatsAppApi', {
|
||
url: `/${phoneNumberId}/media`,
|
||
baseURL: requestOptions.baseURL,
|
||
method: 'POST',
|
||
body: data,
|
||
})) as IDataObject;
|
||
|
||
const operation = this.getNodeParameter('messageType') as string;
|
||
if (!requestOptions.body) {
|
||
requestOptions.body = {};
|
||
}
|
||
set(requestOptions.body as IDataObject, `${operation}.id`, result.id);
|
||
if (operation === 'document') {
|
||
set(
|
||
requestOptions.body as IDataObject,
|
||
`${operation}.filename`,
|
||
mediaFileName ?? binaryFileName,
|
||
);
|
||
}
|
||
|
||
return requestOptions;
|
||
}
|
||
|
||
export async function templateInfo(
|
||
this: IExecuteSingleFunctions,
|
||
requestOptions: IHttpRequestOptions,
|
||
): Promise<IHttpRequestOptions> {
|
||
const template = this.getNodeParameter('template') as string;
|
||
const [name, language] = template.split('|');
|
||
if (!requestOptions.body) {
|
||
requestOptions.body = {};
|
||
}
|
||
set(requestOptions.body as IDataObject, 'template.name', name);
|
||
set(requestOptions.body as IDataObject, 'template.language.code', language);
|
||
return requestOptions;
|
||
}
|
||
|
||
export async function componentsRequest(
|
||
this: IExecuteSingleFunctions,
|
||
requestOptions: IHttpRequestOptions,
|
||
): Promise<IHttpRequestOptions> {
|
||
const components = this.getNodeParameter('components') as IDataObject;
|
||
const componentsRet: object[] = [];
|
||
|
||
if (!components?.component) {
|
||
return requestOptions;
|
||
}
|
||
|
||
for (const component of components.component as IDataObject[]) {
|
||
const comp: IDataObject = {
|
||
type: component.type,
|
||
};
|
||
|
||
if (component.type === 'body') {
|
||
comp.parameters = (((component.bodyParameters as IDataObject)!.parameter as IDataObject[]) ||
|
||
[])!.map((i: IDataObject) => {
|
||
if (i.type === 'text') {
|
||
return i;
|
||
} else if (i.type === 'currency') {
|
||
return {
|
||
type: 'currency',
|
||
currency: {
|
||
code: i.code,
|
||
fallback_value: i.fallback_value,
|
||
amount_1000: (i.amount_1000 as number) * 1000,
|
||
},
|
||
};
|
||
} else if (i.type === 'date_time') {
|
||
return {
|
||
type: 'date_time',
|
||
date_time: {
|
||
fallback_value: i.date_time,
|
||
},
|
||
};
|
||
}
|
||
});
|
||
} else if (component.type === 'button') {
|
||
comp.index = component.index?.toString();
|
||
comp.sub_type = component.sub_type;
|
||
comp.parameters = [(component.buttonParameters as IDataObject).parameter];
|
||
} else if (component.type === 'header') {
|
||
comp.parameters = (
|
||
(component.headerParameters as IDataObject).parameter as IDataObject[]
|
||
).map((i: IDataObject) => {
|
||
if (i.type === 'image') {
|
||
return {
|
||
type: 'image',
|
||
image: {
|
||
link: i.imageLink,
|
||
},
|
||
};
|
||
}
|
||
return i;
|
||
});
|
||
}
|
||
componentsRet.push(comp);
|
||
}
|
||
|
||
if (!requestOptions.body) {
|
||
requestOptions.body = {};
|
||
}
|
||
|
||
set(requestOptions.body as IDataObject, 'template.components', componentsRet);
|
||
|
||
return requestOptions;
|
||
}
|
||
|
||
export async function cleanPhoneNumber(
|
||
this: IExecuteSingleFunctions,
|
||
requestOptions: IHttpRequestOptions,
|
||
): Promise<IHttpRequestOptions> {
|
||
let phoneNumber = this.getNodeParameter('recipientPhoneNumber') as string;
|
||
phoneNumber = phoneNumber.replace(/[\-\(\)\+]/g, '');
|
||
|
||
if (!requestOptions.body) {
|
||
requestOptions.body = {};
|
||
}
|
||
|
||
set(requestOptions.body as IDataObject, 'to', phoneNumber);
|
||
|
||
return requestOptions;
|
||
}
|
||
|
||
export async function sendErrorPostReceive(
|
||
this: IExecuteSingleFunctions,
|
||
data: INodeExecutionData[],
|
||
response: IN8nHttpFullResponse,
|
||
): Promise<INodeExecutionData[]> {
|
||
if (response.statusCode === 500) {
|
||
throw new NodeApiError(
|
||
this.getNode(),
|
||
{},
|
||
{
|
||
message: 'Sending failed',
|
||
description:
|
||
'If you’re sending to a new test number, try sending a message to it from within the Meta developer portal first.',
|
||
httpCode: '500',
|
||
},
|
||
);
|
||
} else if (response.statusCode === 400) {
|
||
const error = { ...(response.body as WhatsAppApiError).error };
|
||
error.message = error.message.replace(/^\(#\d+\) /, '');
|
||
const messageType = this.getNodeParameter('messageType', 'media');
|
||
if (error.message.endsWith('is not a valid whatsapp business account media attachment ID')) {
|
||
throw new NodeApiError(
|
||
this.getNode(),
|
||
{ error },
|
||
{
|
||
message: `Invalid ${messageType} ID`,
|
||
description: error.message,
|
||
httpCode: '400',
|
||
},
|
||
);
|
||
} else if (error.message.endsWith('is not a valid URI.')) {
|
||
throw new NodeApiError(
|
||
this.getNode(),
|
||
{ error },
|
||
{
|
||
message: `Invalid ${messageType} URL`,
|
||
description: error.message,
|
||
httpCode: '400',
|
||
},
|
||
);
|
||
}
|
||
throw new NodeApiError(
|
||
this.getNode(),
|
||
{ ...(response as unknown as JsonObject), body: { error } },
|
||
{},
|
||
);
|
||
} else if (response.statusCode > 399) {
|
||
throw new NodeApiError(this.getNode(), response as unknown as JsonObject);
|
||
}
|
||
return data;
|
||
}
|