n8n-nodes-wuzapi
Version:
n8n community nodes for Wuzapi - WhatsApp Multi-Device REST API
1,142 lines (1,141 loc) • 50.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WuzapiMessage = void 0;
const GenericFunctions_1 = require("../GenericFunctions");
class WuzapiMessage {
constructor() {
this.description = {
displayName: 'Wuzapi Message',
name: 'wuzapiMessage',
icon: 'file:wuzapi.svg',
group: ['transform'],
version: 1,
subtitle: '={{$parameter["messageType"]}}',
description: 'Send messages through Wuzapi WhatsApp API',
defaults: {
name: 'Wuzapi Message',
},
inputs: ["main" /* NodeConnectionType.Main */],
outputs: ["main" /* NodeConnectionType.Main */],
credentials: [
{
name: 'wuzapiApi',
required: true,
},
],
properties: [
{
displayName: 'Message Type',
name: 'messageType',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Text',
value: 'text',
description: 'Send a text message',
action: 'Send text message',
},
{
name: 'Image',
value: 'image',
description: 'Send an image',
action: 'Send image message',
},
{
name: 'Audio',
value: 'audio',
description: 'Send an audio message',
action: 'Send audio message',
},
{
name: 'Video',
value: 'video',
description: 'Send a video',
action: 'Send video message',
},
{
name: 'Document',
value: 'document',
description: 'Send a document',
action: 'Send document',
},
{
name: 'Sticker',
value: 'sticker',
description: 'Send a sticker',
action: 'Send sticker',
},
{
name: 'Location',
value: 'location',
description: 'Send a location',
action: 'Send location',
},
{
name: 'Contact',
value: 'contact',
description: 'Send a contact card',
action: 'Send contact',
},
{
name: 'Template',
value: 'template',
description: 'Send a template message with buttons',
action: 'Send template message',
},
{
name: 'Buttons',
value: 'buttons',
description: 'Send a message with buttons',
action: 'Send buttons message',
},
{
name: 'List',
value: 'list',
description: 'Send a list message',
action: 'Send list message',
},
{
name: 'Poll',
value: 'poll',
description: 'Send a poll (groups only)',
action: 'Send poll',
},
],
default: 'text',
},
// Common fields for all message types
{
displayName: 'Phone Number or Group ID',
name: 'phone',
type: 'string',
default: '',
placeholder: 'e.g. 5491155553934 or 120363312246943103@g.us',
description: 'Phone number (without + or spaces) or Group ID',
required: true,
},
// Text message fields
{
displayName: 'Message',
name: 'body',
type: 'string',
default: '',
placeholder: 'Type your message here',
description: 'The text message to send',
required: true,
displayOptions: {
show: {
messageType: ['text', 'buttons'],
},
},
},
// Image fields
{
displayName: 'Image Source',
name: 'imageSource',
type: 'options',
default: 'binary',
options: [
{
name: 'Binary Data',
value: 'binary',
description: 'Use binary data from previous node',
},
{
name: 'Base64',
value: 'base64',
description: 'Provide base64 encoded image',
},
{
name: 'URL',
value: 'url',
description: 'Provide URL to image file',
},
],
displayOptions: {
show: {
messageType: ['image'],
},
},
},
{
displayName: 'Binary Property',
name: 'binaryProperty',
type: 'string',
default: 'data',
description: 'Name of the binary property containing the image',
displayOptions: {
show: {
messageType: ['image'],
imageSource: ['binary'],
},
},
},
{
displayName: 'Base64 Image',
name: 'imageBase64',
type: 'string',
default: '',
placeholder: 'data:image/jpeg;base64,iVBORw0...',
description: 'Base64 encoded image with data URL prefix',
displayOptions: {
show: {
messageType: ['image'],
imageSource: ['base64'],
},
},
},
{
displayName: 'Image URL',
name: 'imageUrl',
type: 'string',
default: '',
placeholder: 'https://example.com/image.jpg',
description: 'URL to the image file',
displayOptions: {
show: {
messageType: ['image'],
imageSource: ['url'],
},
},
},
{
displayName: 'Caption',
name: 'caption',
type: 'string',
default: '',
description: 'Caption for the image',
displayOptions: {
show: {
messageType: ['image', 'video'],
},
},
},
// Audio fields
{
displayName: 'Audio Source',
name: 'audioSource',
type: 'options',
default: 'binary',
options: [
{
name: 'Binary Data',
value: 'binary',
description: 'Use binary data from previous node',
},
{
name: 'Base64',
value: 'base64',
description: 'Provide base64 encoded audio',
},
{
name: 'URL',
value: 'url',
description: 'Provide URL to audio file',
},
],
displayOptions: {
show: {
messageType: ['audio'],
},
},
},
{
displayName: 'Binary Property',
name: 'audioBinaryProperty',
type: 'string',
default: 'data',
description: 'Name of the binary property containing the audio',
displayOptions: {
show: {
messageType: ['audio'],
audioSource: ['binary'],
},
},
},
{
displayName: 'Base64 Audio',
name: 'audioBase64',
type: 'string',
default: '',
placeholder: 'data:audio/ogg;base64,iVBORw0...',
description: 'Base64 encoded audio in opus format',
displayOptions: {
show: {
messageType: ['audio'],
audioSource: ['base64'],
},
},
},
{
displayName: 'Audio URL',
name: 'audioUrl',
type: 'string',
default: '',
placeholder: 'https://example.com/audio.ogg',
description: 'URL to the audio file',
displayOptions: {
show: {
messageType: ['audio'],
audioSource: ['url'],
},
},
},
// Video fields
{
displayName: 'Video Source',
name: 'videoSource',
type: 'options',
default: 'binary',
options: [
{
name: 'Binary Data',
value: 'binary',
description: 'Use binary data from previous node',
},
{
name: 'Base64',
value: 'base64',
description: 'Provide base64 encoded video',
},
{
name: 'URL',
value: 'url',
description: 'Provide URL to video file',
},
],
displayOptions: {
show: {
messageType: ['video'],
},
},
},
{
displayName: 'Binary Property',
name: 'videoBinaryProperty',
type: 'string',
default: 'data',
description: 'Name of the binary property containing the video',
displayOptions: {
show: {
messageType: ['video'],
videoSource: ['binary'],
},
},
},
{
displayName: 'Base64 Video',
name: 'videoBase64',
type: 'string',
default: '',
placeholder: 'data:video/mp4;base64,iVBORw0...',
description: 'Base64 encoded video (H.264 codec, AAC audio)',
displayOptions: {
show: {
messageType: ['video'],
videoSource: ['base64'],
},
},
},
{
displayName: 'Video URL',
name: 'videoUrl',
type: 'string',
default: '',
placeholder: 'https://example.com/video.mp4',
description: 'URL to the video file',
displayOptions: {
show: {
messageType: ['video'],
videoSource: ['url'],
},
},
},
// Document fields
{
displayName: 'Document Source',
name: 'documentSource',
type: 'options',
default: 'binary',
options: [
{
name: 'Binary Data',
value: 'binary',
description: 'Use binary data from previous node',
},
{
name: 'Base64',
value: 'base64',
description: 'Provide base64 encoded document',
},
{
name: 'URL',
value: 'url',
description: 'Provide URL to document file',
},
],
displayOptions: {
show: {
messageType: ['document'],
},
},
},
{
displayName: 'Binary Property',
name: 'documentBinaryProperty',
type: 'string',
default: 'data',
description: 'Name of the binary property containing the document',
displayOptions: {
show: {
messageType: ['document'],
documentSource: ['binary'],
},
},
},
{
displayName: 'Base64 Document',
name: 'documentBase64',
type: 'string',
default: '',
placeholder: 'data:application/pdf;base64,iVBORw0...',
description: 'Base64 encoded document',
displayOptions: {
show: {
messageType: ['document'],
documentSource: ['base64'],
},
},
},
{
displayName: 'Document URL',
name: 'documentUrl',
type: 'string',
default: '',
placeholder: 'https://example.com/document.pdf',
description: 'URL to the document file',
displayOptions: {
show: {
messageType: ['document'],
documentSource: ['url'],
},
},
},
{
displayName: 'File Name',
name: 'fileName',
type: 'string',
default: '',
placeholder: 'document.pdf',
description: 'Name of the document file',
required: true,
displayOptions: {
show: {
messageType: ['document'],
},
},
},
// Sticker fields
{
displayName: 'Sticker Source',
name: 'stickerSource',
type: 'options',
default: 'binary',
options: [
{
name: 'Binary Data',
value: 'binary',
description: 'Use binary data from previous node',
},
{
name: 'Base64',
value: 'base64',
description: 'Provide base64 encoded sticker',
},
{
name: 'URL',
value: 'url',
description: 'Provide URL to sticker file',
},
],
displayOptions: {
show: {
messageType: ['sticker'],
},
},
},
{
displayName: 'Binary Property',
name: 'stickerBinaryProperty',
type: 'string',
default: 'data',
description: 'Name of the binary property containing the sticker',
displayOptions: {
show: {
messageType: ['sticker'],
stickerSource: ['binary'],
},
},
},
{
displayName: 'Base64 Sticker',
name: 'stickerBase64',
type: 'string',
default: '',
placeholder: 'data:image/webp;base64,iVBORw0...',
description: 'Base64 encoded sticker in WebP format',
displayOptions: {
show: {
messageType: ['sticker'],
stickerSource: ['base64'],
},
},
},
{
displayName: 'Sticker URL',
name: 'stickerUrl',
type: 'string',
default: '',
placeholder: 'https://example.com/sticker.webp',
description: 'URL to the sticker file',
displayOptions: {
show: {
messageType: ['sticker'],
stickerSource: ['url'],
},
},
},
// Location fields
{
displayName: 'Latitude',
name: 'latitude',
type: 'number',
default: 0,
placeholder: '48.858370',
description: 'Latitude coordinate',
required: true,
displayOptions: {
show: {
messageType: ['location'],
},
},
},
{
displayName: 'Longitude',
name: 'longitude',
type: 'number',
default: 0,
placeholder: '2.294481',
description: 'Longitude coordinate',
required: true,
displayOptions: {
show: {
messageType: ['location'],
},
},
},
{
displayName: 'Location Name',
name: 'locationName',
type: 'string',
default: '',
placeholder: 'Eiffel Tower',
description: 'Name of the location',
displayOptions: {
show: {
messageType: ['location'],
},
},
},
// Contact fields
{
displayName: 'Contact Name',
name: 'contactName',
type: 'string',
default: '',
placeholder: 'John Doe',
description: 'Full name of the contact',
required: true,
displayOptions: {
show: {
messageType: ['contact'],
},
},
},
{
displayName: 'VCard',
name: 'vcard',
type: 'string',
default: '',
placeholder: 'BEGIN:VCARD\nVERSION:3.0\nFN:John Doe\nTEL:+1234567890\nEND:VCARD',
description: 'VCard data for the contact',
required: true,
displayOptions: {
show: {
messageType: ['contact'],
},
},
},
// Template fields
{
displayName: 'Content',
name: 'content',
type: 'string',
default: '',
placeholder: 'Message content',
description: 'Main content of the template message',
required: true,
displayOptions: {
show: {
messageType: ['template'],
},
},
},
{
displayName: 'Footer',
name: 'footer',
type: 'string',
default: '',
placeholder: 'Footer text',
description: 'Footer text for the template',
displayOptions: {
show: {
messageType: ['template'],
},
},
},
{
displayName: 'Buttons',
name: 'templateButtons',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
default: {},
description: 'Buttons to add to the template',
displayOptions: {
show: {
messageType: ['template'],
},
},
options: [
{
name: 'button',
displayName: 'Button',
values: [
{
displayName: 'Display Text',
name: 'displayText',
type: 'string',
default: '',
description: 'Text to display on the button',
required: true,
},
{
displayName: 'Type',
name: 'type',
type: 'options',
default: 'quickreply',
options: [
{
name: 'Quick Reply',
value: 'quickreply',
},
{
name: 'URL',
value: 'url',
},
{
name: 'Call',
value: 'call',
},
],
description: 'Type of button',
},
{
displayName: 'URL',
name: 'url',
type: 'string',
default: '',
placeholder: 'https://example.com',
description: 'URL to open when clicked',
displayOptions: {
show: {
type: ['url'],
},
},
required: true,
},
{
displayName: 'Phone Number',
name: 'phoneNumber',
type: 'string',
default: '',
placeholder: '1234567890',
description: 'Phone number to call',
displayOptions: {
show: {
type: ['call'],
},
},
required: true,
},
],
},
],
},
// List message fields
{
displayName: 'Button Text',
name: 'buttonText',
type: 'string',
default: '',
placeholder: 'Click here',
description: 'Text for the list button',
required: true,
displayOptions: {
show: {
messageType: ['list'],
},
},
},
{
displayName: 'Description',
name: 'description',
type: 'string',
default: '',
placeholder: 'Choose an option',
description: 'Description of the list',
required: true,
displayOptions: {
show: {
messageType: ['list'],
},
},
},
{
displayName: 'Top Text',
name: 'topText',
type: 'string',
default: '',
placeholder: 'Header text',
description: 'Text to show above the list',
required: true,
displayOptions: {
show: {
messageType: ['list'],
},
},
},
{
displayName: 'Footer Text',
name: 'footerText',
type: 'string',
default: '',
placeholder: 'Footer text',
description: 'Text to show below the list',
displayOptions: {
show: {
messageType: ['list'],
},
},
},
{
displayName: 'List Items',
name: 'listItems',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
default: {},
description: 'Items in the list',
required: true,
displayOptions: {
show: {
messageType: ['list'],
},
},
options: [
{
name: 'item',
displayName: 'Item',
values: [
{
displayName: 'Title',
name: 'title',
type: 'string',
default: '',
description: 'Title of the list item',
required: true,
},
{
displayName: 'Description',
name: 'desc',
type: 'string',
default: '',
description: 'Description of the list item',
},
{
displayName: 'Row ID',
name: 'rowId',
type: 'string',
default: '',
description: 'Unique identifier for the row',
required: true,
},
],
},
],
},
// Poll fields
{
displayName: 'Poll Question',
name: 'pollHeader',
type: 'string',
default: '',
placeholder: "What's your favorite color?",
required: true,
displayOptions: {
show: {
messageType: ['poll'],
},
},
},
{
displayName: 'Poll Options',
name: 'pollOptions',
type: 'string',
default: '',
placeholder: 'Red,Blue,Green,Yellow',
description: 'Comma-separated list of poll options',
required: true,
displayOptions: {
show: {
messageType: ['poll'],
},
},
},
// Additional options
{
displayName: 'Additional Options',
name: 'additionalOptions',
type: 'collection',
placeholder: 'Add Option',
default: {},
options: [
{
displayName: 'Message ID',
name: 'id',
type: 'string',
default: '',
description: 'Custom message ID (auto-generated if not provided)',
},
{
displayName: 'Reply To Message',
name: 'contextInfo',
type: 'fixedCollection',
default: {},
description: 'Reply to a specific message',
options: [
{
name: 'context',
displayName: 'Context',
values: [
{
displayName: 'Message ID',
name: 'stanzaId',
type: 'string',
default: '',
description: 'ID of the message to reply to',
required: true,
},
{
displayName: 'Participant',
name: 'participant',
type: 'string',
default: '',
placeholder: 'e.g. 5491155553935@s.whatsapp.net',
description: 'JID of the message sender',
required: true,
},
],
},
],
},
{
displayName: 'Mentions',
name: 'mentions',
type: 'fixedCollection',
default: {},
description: 'Mention specific users or all group members',
options: [
{
name: 'mentionConfig',
displayName: 'Mention Configuration',
values: [
{
displayName: 'Mention Type',
name: 'type',
type: 'options',
default: 'none',
options: [
{
name: 'None',
value: 'none',
description: 'No mentions',
},
{
name: 'Specific Users',
value: 'specific',
description: 'Mention specific users',
},
{
name: 'All Group Members',
value: 'all',
description: 'Mention all group members (groups only)',
},
],
},
{
displayName: 'User JIDs',
name: 'jids',
type: 'string',
default: '',
placeholder: 'e.g. 5491155553934@s.whatsapp.net,5491155553935@s.whatsapp.net',
description: 'Comma-separated list of user JIDs to mention',
displayOptions: {
show: {
type: ['specific'],
},
},
},
],
},
],
},
],
},
],
};
}
async execute() {
const items = this.getInputData();
const returnData = [];
for (let i = 0; i < items.length; i++) {
try {
const messageType = this.getNodeParameter('messageType', i);
const phone = this.getNodeParameter('phone', i);
const additionalOptions = this.getNodeParameter('additionalOptions', i);
let endpoint = '';
let body = {
Phone: phone,
};
// Add common fields
if (additionalOptions.id) {
body.Id = additionalOptions.id;
}
if (additionalOptions.contextInfo && additionalOptions.contextInfo.context) {
const context = additionalOptions.contextInfo.context;
if (context.stanzaId && context.participant) {
body.ContextInfo = {
StanzaId: context.stanzaId,
Participant: context.participant,
};
}
}
// Handle mentions
if (additionalOptions.mentions && additionalOptions.mentions.mentionConfig) {
const mentionConfig = additionalOptions.mentions.mentionConfig;
if (Array.isArray(mentionConfig) && mentionConfig.length > 0) {
const config = mentionConfig[0];
if (config.type === 'specific' && config.jids) {
const jids = config.jids.split(',').map((jid) => jid.trim());
body.mention_info = {
mentions: jids,
};
}
else if (config.type === 'all') {
body.mention_info = {
mention_all: true,
};
}
}
}
// Handle different message types
if (messageType === 'text') {
endpoint = '/chat/send/text';
body.Body = this.getNodeParameter('body', i);
}
else if (messageType === 'image') {
endpoint = '/chat/send/image';
const imageSource = this.getNodeParameter('imageSource', i);
if (imageSource === 'binary') {
const binaryProperty = this.getNodeParameter('binaryProperty', i);
const binaryData = items[i].binary?.[binaryProperty];
if (!binaryData) {
throw new Error(`Binary property "${binaryProperty}" not found`);
}
const buffer = Buffer.from(binaryData.data, 'base64');
body.Image = (0, GenericFunctions_1.prepareMediaData)(buffer, binaryData.mimeType || 'image/jpeg');
}
else if (imageSource === 'base64') {
body.Image = this.getNodeParameter('imageBase64', i);
}
else if (imageSource === 'url') {
body.Image = this.getNodeParameter('imageUrl', i);
}
const caption = this.getNodeParameter('caption', i);
if (caption) {
body.Caption = caption;
}
}
else if (messageType === 'audio') {
endpoint = '/chat/send/audio';
const audioSource = this.getNodeParameter('audioSource', i);
if (audioSource === 'binary') {
const binaryProperty = this.getNodeParameter('audioBinaryProperty', i);
const binaryData = items[i].binary?.[binaryProperty];
if (!binaryData) {
throw new Error(`Binary property "${binaryProperty}" not found`);
}
const buffer = Buffer.from(binaryData.data, 'base64');
body.Audio = (0, GenericFunctions_1.prepareMediaData)(buffer, binaryData.mimeType || 'audio/ogg');
}
else if (audioSource === 'base64') {
body.Audio = this.getNodeParameter('audioBase64', i);
}
else if (audioSource === 'url') {
body.Audio = this.getNodeParameter('audioUrl', i);
}
}
else if (messageType === 'video') {
endpoint = '/chat/send/video';
const videoSource = this.getNodeParameter('videoSource', i);
if (videoSource === 'binary') {
const binaryProperty = this.getNodeParameter('videoBinaryProperty', i);
const binaryData = items[i].binary?.[binaryProperty];
if (!binaryData) {
throw new Error(`Binary property "${binaryProperty}" not found`);
}
const buffer = Buffer.from(binaryData.data, 'base64');
body.Video = (0, GenericFunctions_1.prepareMediaData)(buffer, binaryData.mimeType || 'video/mp4');
}
else if (videoSource === 'base64') {
body.Video = this.getNodeParameter('videoBase64', i);
}
else if (videoSource === 'url') {
body.Video = this.getNodeParameter('videoUrl', i);
}
const caption = this.getNodeParameter('caption', i);
if (caption) {
body.Caption = caption;
}
}
else if (messageType === 'document') {
endpoint = '/chat/send/document';
const documentSource = this.getNodeParameter('documentSource', i);
if (documentSource === 'binary') {
const binaryProperty = this.getNodeParameter('documentBinaryProperty', i);
const binaryData = items[i].binary?.[binaryProperty];
if (!binaryData) {
throw new Error(`Binary property "${binaryProperty}" not found`);
}
const buffer = Buffer.from(binaryData.data, 'base64');
body.Document = (0, GenericFunctions_1.prepareMediaData)(buffer, binaryData.mimeType || 'application/octet-stream');
}
else if (documentSource === 'base64') {
body.Document = this.getNodeParameter('documentBase64', i);
}
else if (documentSource === 'url') {
body.Document = this.getNodeParameter('documentUrl', i);
}
body.FileName = this.getNodeParameter('fileName', i);
}
else if (messageType === 'sticker') {
endpoint = '/chat/send/sticker';
const stickerSource = this.getNodeParameter('stickerSource', i);
if (stickerSource === 'binary') {
const binaryProperty = this.getNodeParameter('stickerBinaryProperty', i);
const binaryData = items[i].binary?.[binaryProperty];
if (!binaryData) {
throw new Error(`Binary property "${binaryProperty}" not found`);
}
const buffer = Buffer.from(binaryData.data, 'base64');
body.Sticker = (0, GenericFunctions_1.prepareMediaData)(buffer, binaryData.mimeType || 'image/webp');
}
else if (stickerSource === 'base64') {
body.Sticker = this.getNodeParameter('stickerBase64', i);
}
else if (stickerSource === 'url') {
body.Sticker = this.getNodeParameter('stickerUrl', i);
}
}
else if (messageType === 'location') {
endpoint = '/chat/send/location';
body.Latitude = this.getNodeParameter('latitude', i);
body.Longitude = this.getNodeParameter('longitude', i);
const locationName = this.getNodeParameter('locationName', i);
if (locationName) {
body.Name = locationName;
}
}
else if (messageType === 'contact') {
endpoint = '/chat/send/contact';
body.Name = this.getNodeParameter('contactName', i);
body.Vcard = this.getNodeParameter('vcard', i);
}
else if (messageType === 'template') {
endpoint = '/chat/send/template';
body.Content = this.getNodeParameter('content', i);
const footer = this.getNodeParameter('footer', i);
if (footer) {
body.Footer = footer;
}
const buttons = this.getNodeParameter('templateButtons', i).button;
if (buttons && buttons.length > 0) {
body.Buttons = buttons.map((btn) => {
const button = {
DisplayText: btn.displayText,
Type: btn.type,
};
if (btn.type === 'url' && btn.url) {
button.Url = btn.url;
}
if (btn.type === 'call' && btn.phoneNumber) {
button.PhoneNumber = btn.phoneNumber;
}
return button;
});
}
}
else if (messageType === 'buttons') {
endpoint = '/chat/send/buttons';
body.Body = this.getNodeParameter('body', i);
}
else if (messageType === 'list') {
endpoint = '/chat/send/list';
body.ButtonText = this.getNodeParameter('buttonText', i);
body.Desc = this.getNodeParameter('description', i);
body.TopText = this.getNodeParameter('topText', i);
const footerText = this.getNodeParameter('footerText', i);
if (footerText) {
body.FooterText = footerText;
}
const listItems = this.getNodeParameter('listItems', i).item;
if (listItems && listItems.length > 0) {
body.List = listItems.map((item) => ({
title: item.title,
desc: item.desc || '',
RowId: item.rowId,
}));
}
}
else if (messageType === 'poll') {
endpoint = '/chat/send/poll';
body.Group = phone; // For polls, phone should be a group ID
body.Header = this.getNodeParameter('pollHeader', i);
const pollOptions = this.getNodeParameter('pollOptions', i);
body.Options = pollOptions.split(',').map(opt => opt.trim());
}
const responseData = await GenericFunctions_1.wuzapiApiRequest.call(this, 'POST', endpoint, body);