n8n-nodes-zuckzapgo
Version:
n8n community nodes for ZuckZapGo - WhatsApp Multi-Device REST API
1,155 lines (1,154 loc) • 56.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ZuckZapGoMessage = void 0;
const GenericFunctions_1 = require("../GenericFunctions");
class ZuckZapGoMessage {
constructor() {
this.description = {
displayName: 'ZuckZapGo Message',
name: 'zuckzapgoMessage',
icon: 'file:zuckzapgo.svg',
group: ['transform'],
version: 1,
subtitle: '={{$parameter["messageType"]}}',
description: 'Send messages through ZuckZapGo WhatsApp API',
defaults: {
name: 'ZuckZapGo Message',
},
inputs: ["main" /* NodeConnectionType.Main */],
outputs: ["main" /* NodeConnectionType.Main */],
credentials: [
{
name: 'zuckzapgoApi',
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',
},
{
name: 'Link',
value: 'link',
description: 'Send a link with automatic preview',
action: 'Send link with preview',
},
],
default: 'text',
},
// Common fields for all message types
{
displayName: 'Recipient',
name: 'phone',
type: 'string',
default: '',
placeholder: 'e.g. 5491155553934 or 120363312246943103@g.us or 120363025246943103@newsletter or user@lid',
description: 'Phone number, Group ID (@g.us), Newsletter ID (@newsletter), or LID (@lid)',
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'],
},
},
},
// Link message fields
{
displayName: 'Link URL',
name: 'link',
type: 'string',
default: '',
placeholder: 'https://example.com or example.com',
description: 'URL to send with automatic preview generation',
required: true,
displayOptions: {
show: {
messageType: ['link'],
},
},
},
{
displayName: 'Caption',
name: 'linkCaption',
type: 'string',
default: '',
placeholder: 'Check out this amazing website!',
description: 'Optional text to accompany the link',
displayOptions: {
show: {
messageType: ['link'],
},
},
},
// 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: 'Presence Simulation',
name: 'presence',
type: 'number',
default: 0,
placeholder: 'e.g. 2000 for 2 seconds',
description: 'Milliseconds to simulate typing (text/documents) or recording (audio). 0 = disabled, max 30000ms.',
typeOptions: {
minValue: 0,
maxValue: 30000,
},
},
{
displayName: 'Message Duration',
name: 'duration',
type: 'options',
default: 0,
options: [
{
name: 'No Expiration',
value: 0,
description: 'Message never expires',
},
{
name: '24 Hours',
value: 86400,
description: 'Message expires after 24 hours',
},
{
name: '7 Days',
value: 604800,
description: 'Message expires after 7 days',
},
{
name: '90 Days',
value: 7776000,
description: 'Message expires after 90 days',
},
],
description: 'Time until message expires (WhatsApp disappearing messages)',
},
{
displayName: 'View Once',
name: 'viewOnce',
type: 'boolean',
default: false,
description: 'Whether message can only be viewed once (image/video/audio only)',
displayOptions: {
show: {
'/messageType': ['image', 'video', 'audio'],
},
},
},
{
displayName: 'Link Preview',
name: 'linkPreview',
type: 'boolean',
default: false,
description: 'Whether to enable automatic link preview for URLs in text',
displayOptions: {
show: {
'/messageType': ['text'],
},
},
},
{
displayName: 'Context Information',
name: 'contextInfo',
type: 'fixedCollection',
default: {},
description: 'Message context for replies, forwards, and mentions',
options: [
{
name: 'context',
displayName: 'Context',
values: [
{
displayName: 'Forward Message',
name: 'isForwarded',
type: 'boolean',
default: false,
description: 'Whether to mark this message as forwarded',
},
{
displayName: 'Mention All',
name: 'mentionAll',
type: 'boolean',
default: false,
description: 'Whether to mention all group members',
},
{
displayName: 'Mentioned Users',
name: 'mentionedJID',
type: 'string',
default: '',
placeholder: 'e.g. 5491155553934@s.whatsapp.net,5491155553935@s.whatsapp.net',
description: 'Comma-separated list of user JIDs to mention',
},
{
displayName: 'Participant',
name: 'participant',
type: 'string',
default: '',
placeholder: 'e.g. 5491155553935@s.whatsapp.net',
description: 'JID of the original message sender (required for replies)',
},
{
displayName: 'Reply To Message ID',
name: 'stanzaId',
type: 'string',
default: '',
description: 'ID of the message to reply to',
},
],
},
],
},
],
},
],
};
}
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 = '';
// Format recipient properly based on type
let formattedPhone;
if (phone.includes('@')) {
// Already has @ format (groups @g.us, newsletters @newsletter, LIDs @lid) - use as is
formattedPhone = phone;
}
else {
// Plain phone number - add @s.whatsapp.net for user
formattedPhone = `${phone}@s.whatsapp.net`;
}
let body = {
Phone: formattedPhone,
};
// Add common fields
if (additionalOptions.id) {
body.Id = additionalOptions.id;
}
// Add enhanced features
if (additionalOptions.presence && Number(additionalOptions.presence) > 0) {
const presence = Number(additionalOptions.presence);
if (presence <= 30000) { // Max 30 seconds
body.Presence = presence;
}
}
if (additionalOptions.duration && Number(additionalOptions.duration) > 0) {
const validDurations = [86400, 604800, 7776000]; // 24h, 7d, 90d
const duration = Number(additionalOptions.duration);
if (validDurations.includes(duration)) {
body.Duration = duration;
}
}
if (additionalOptions.viewOnce && ['image', 'video', 'audio'].includes(messageType)) {
body.ViewOnce = true;
}
if (additionalOptions.linkPreview && messageType === 'text') {
body.LinkPreview = true;
}
// Initialize ContextInfo - completely optional and flexible like Postman
if (additionalOptions.contextInfo && additionalOptions.contextInfo.context) {
const context = additionalOptions.contextInfo.context;
// Get context from array or direct object
const ctx = Array.isArray(context) && context.length > 0 ? context[0] : context;
const contextInfo = {};
// Only add fields that are actually set by user (exactly like Postman)
// Forward flag - only if explicitly set to true
if (ctx.isForwarded === true) {
contextInfo.IsForwarded = true;
}
// Reply context - only if both fields are provided
if (ctx.stanzaId && ctx.participant) {
contextInfo.StanzaId = ctx.stanzaId;
contextInfo.Participant = ctx.participant;
}
// Mention all flag - only if explicitly set to true
if (ctx.mentionAll === true) {
contextInfo.MentionAll = true;
}
// Mentioned users list - only if there are actual JIDs
if (ctx.mentionedJID) {
const mentionedJIDs = ctx.mentionedJID.split(',').map((jid) => jid.trim()).filter(Boolean);
if (mentionedJIDs.length > 0) {
contextInfo.MentionedJID = mentionedJIDs;
}
}
// Only add ContextInfo to body if at least one field was set
if (Object.keys(contextInfo).length > 0) {
body.ContextInfo = contextInfo;
}
}
// Handle different message types
if (messageType === 'text') {
endpoint = '/chat/send/text';
body.Body = this.getNodeParameter('body', i);
}
else if (messageType === 'link') {
endpoint = '/chat/send/link';
let link = this.getNodeParameter('link', i);
// Validate and format URL
if (!link.match(/^https?:\/\//i)) {
link = `https://${link}`;
}
body.Link = link;
const linkCaption = this.getNodeParameter('linkCaption', i);
if (linkCaption) {
body.Caption = linkCaption;
}
}
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.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.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.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') {