n8n-nodes-japi
Version:
Node comunitário para integrar a J-API (WhatsApp) ao n8n
647 lines (646 loc) • 30.3 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.JApi = void 0;
var n8n_workflow_1 = require("n8n-workflow");
var JApi = /** @class */ (function () {
function JApi() {
this.description = {
displayName: 'J-API',
name: 'japi',
group: ['output'],
version: 1,
icon: 'file:japi.png',
description: 'Interage com a API J-API (WhatsApp)',
defaults: { name: 'J-API' },
inputs: ['main'],
outputs: ['main'],
credentials: [{ name: 'japiApi', required: true }],
/*────────── UI fields ──────────*/
properties: [
/* 1. seletor da ação */
{
displayName: 'Tipo de envio',
name: 'action',
type: 'options',
options: [
{ name: 'Texto', value: 'text' },
{ name: 'Mídia', value: 'media' },
{ name: 'Contato', value: 'contact' },
{ name: 'Localização', value: 'location' },
{ name: 'Status', value: 'status' },
{ name: 'Menu', value: 'menu' },
{ name: 'Carousel', value: 'carousel' },
],
default: 'text',
},
/* 2. campos compartilhados */
{
displayName: 'Telefone (E.164)',
name: 'phone',
type: 'string',
displayOptions: {
show: {
action: [
'text',
'media',
'contact',
'location',
'menu',
'carousel',
],
},
},
default: '',
required: true,
},
/* 3. campos específicos */
/* Texto */
{
displayName: 'Mensagem de texto',
name: 'text',
type: 'string',
displayOptions: { show: { action: ['text', 'status'] } },
default: '',
required: true,
},
/* Mídia */
/*{
displayName: 'URL da mídia',
name: 'mediaUrl',
type: 'string',
displayOptions: { show: { action: ['media'] } },
default: '',
required: true,
},*/
{
displayName: 'Legenda (opcional)',
name: 'caption',
type: 'string',
displayOptions: { show: { action: ['media'] } },
default: '',
},
{
displayName: 'Tipo de mídia',
name: 'mediaType',
type: 'options',
displayOptions: { show: { action: ['media'] } },
options: [
{ name: 'Imagem', value: 'image' },
{ name: 'Vídeo', value: 'video' },
{ name: 'Documento', value: 'document' },
{ name: 'Áudio', value: 'audio' },
{ name: 'MyAudio', value: 'myaudio' },
{ name: 'PTT', value: 'ptt' },
{ name: 'Sticker', value: 'sticker' },
],
default: 'image',
required: true,
},
{
displayName: 'Arquivo (URL ou base64)',
name: 'file',
type: 'string',
displayOptions: { show: { action: ['media'] } },
default: '',
required: true,
},
{
displayName: 'Nome do doc (apenas documents)',
name: 'docName',
type: 'string',
displayOptions: { show: { action: ['media'] } },
default: '',
},
{
displayName: 'MIME type (opcional)',
name: 'mimetype',
type: 'string',
displayOptions: { show: { action: ['media'] } },
default: '',
},
/* Campos compartilhados */
{
displayName: 'Reply ID',
name: 'replyid',
type: 'string',
displayOptions: {
show: {
action: ['media', 'contact']
}
},
default: '',
description: 'ID da mensagem para responder',
},
{
displayName: 'Menções',
name: 'mentions',
type: 'string',
displayOptions: {
show: {
action: ['media', 'contact']
}
},
default: '',
description: 'Números para mencionar (separados por vírgula)',
placeholder: '5511999999999,5511888888888',
},
{
displayName: 'Marcar chat como lido',
name: 'readchat',
type: 'boolean',
displayOptions: {
show: {
action: ['media', 'contact']
}
},
default: false,
description: 'Marca conversa como lida após envio',
},
{
displayName: 'Atraso (ms)',
name: 'delay',
type: 'number',
displayOptions: {
show: {
action: ['media', 'contact']
}
},
default: 0,
description: 'Atraso em milissegundos antes do envio, durante o atraso aparecerá "Digitando..."',
},
/* Contato (vCard) */
{
displayName: 'Nome completo',
name: 'fullName',
type: 'string',
displayOptions: { show: { action: ['contact'] } },
default: '',
required: true,
description: 'Nome completo do contato',
},
{
displayName: 'Números de telefone',
name: 'phoneNumber',
type: 'string',
displayOptions: { show: { action: ['contact'] } },
default: '',
required: true,
description: 'Números de telefone (separados por vírgula)',
placeholder: '5511999999999,5511888888888',
},
{
displayName: 'Organização',
name: 'organization',
type: 'string',
displayOptions: { show: { action: ['contact'] } },
default: '',
description: 'Nome da organização/empresa',
},
{
displayName: 'Email',
name: 'email',
type: 'string',
displayOptions: { show: { action: ['contact'] } },
default: '',
description: 'Endereço de email',
placeholder: 'joao@empresa.com',
},
{
displayName: 'URL',
name: 'url',
type: 'string',
displayOptions: { show: { action: ['contact'] } },
default: '',
description: 'URL pessoal ou da empresa',
placeholder: 'https://empresa.com/joao',
},
/* Localização */
{
displayName: 'Nome do local',
name: 'name',
type: 'string',
displayOptions: { show: { action: ['location'] } },
default: '',
description: 'Nome do local',
},
{
displayName: 'Endereço',
name: 'address',
type: 'string',
displayOptions: { show: { action: ['location'] } },
default: '',
description: 'Endereço completo do local',
},
{
displayName: 'Latitude',
name: 'latitude',
type: 'number',
displayOptions: { show: { action: ['location'] } },
default: 0,
required: true,
description: 'Latitude (-90 a 90)',
},
{
displayName: 'Longitude',
name: 'longitude',
type: 'number',
displayOptions: { show: { action: ['location'] } },
default: 0,
required: true,
description: 'Longitude (-180 a 180)',
},
{
displayName: 'Reply ID',
name: 'replyid',
type: 'string',
displayOptions: { show: { action: ['location'] } },
default: '',
description: 'ID da mensagem para responder',
},
{
displayName: 'Menções',
name: 'mentions',
type: 'string',
displayOptions: { show: { action: ['location'] } },
default: '',
description: 'Números para mencionar (separados por vírgula)',
placeholder: '5511999999999,5511888888888',
},
{
displayName: 'Marcar chat como lido',
name: 'readchat',
type: 'boolean',
displayOptions: { show: { action: ['location'] } },
default: false,
description: 'Marca conversa como lida após envio',
},
{
displayName: 'Atraso (ms)',
name: 'delay',
type: 'number',
displayOptions: { show: { action: ['location'] } },
default: 0,
description: 'Atraso em milissegundos antes do envio, durante o atraso aparecerá "Digitando..."',
},
/* Menu */
{
displayName: 'Tipo do menu',
name: 'menuType',
type: 'options',
displayOptions: { show: { action: ['menu'] } },
options: [
{ name: 'Botões', value: 'button' },
{ name: 'Lista', value: 'list' },
{ name: 'Enquete', value: 'poll' },
{ name: 'Carrossel', value: 'carousel' },
],
default: 'button',
required: true,
description: 'Tipo do menu interativo',
},
{
displayName: 'Texto principal',
name: 'menuText',
type: 'string',
displayOptions: { show: { action: ['menu'] } },
default: '',
required: true,
description: 'Texto principal da mensagem (aceita placeholders)',
},
{
displayName: 'Texto do rodapé',
name: 'footerText',
type: 'string',
displayOptions: { show: { action: ['menu'] } },
default: '',
description: 'Texto do rodapé (opcional para botões e listas)',
},
{
displayName: 'Texto do botão principal',
name: 'listButton',
type: 'string',
displayOptions: { show: { action: ['menu'] } },
default: '',
description: 'Texto do botão principal (para listas)',
},
{
displayName: 'Número de opções selecionáveis',
name: 'sen8nasd',
type: 'number',
displayOptions: { show: { action: ['menu'] } },
default: 1,
description: 'Número máximo de opções selecionáveis (para enquetes)',
},
{
displayName: 'Opções',
name: 'choices',
type: 'json',
displayOptions: { show: { action: ['menu'] } },
default: '["Opção 1|id1","Opção 2|id2"]',
required: true,
description: 'Lista de opções. Use [Título] para seções em listas',
},
{
displayName: 'Reply ID',
name: 'replyid',
type: 'string',
displayOptions: { show: { action: ['menu'] } },
default: '',
description: 'ID da mensagem para responder',
},
{
displayName: 'Menções',
name: 'mentions',
type: 'string',
displayOptions: { show: { action: ['menu'] } },
default: '',
description: 'Números para mencionar (separados por vírgula)',
},
{
displayName: 'Marcar chat como lido',
name: 'readchat',
type: 'boolean',
displayOptions: { show: { action: ['menu'] } },
default: false,
description: 'Marca conversa como lida após envio',
},
{
displayName: 'Atraso (ms)',
name: 'delay',
type: 'number',
displayOptions: { show: { action: ['menu'] } },
default: 0,
description: 'Atraso em milissegundos antes do envio',
},
/* Carousel */
{
displayName: 'Texto principal',
name: 'carouselText',
type: 'string',
displayOptions: { show: { action: ['carousel'] } },
default: '',
required: true,
description: 'Texto principal da mensagem',
},
{
displayName: 'Carrossel (JSON)',
name: 'carousel',
type: 'json',
displayOptions: { show: { action: ['carousel'] } },
default: '[{"text":"Texto do cartão","image":"https://exemplo.com/imagem.jpg","buttons":[{"id":"resposta1","text":"Texto do botão","type":"REPLY"}]}]',
required: true,
description: 'Array de cartões do carrossel com text, image e buttons',
},
{
displayName: 'Atraso (ms)',
name: 'delay',
type: 'number',
displayOptions: { show: { action: ['carousel'] } },
default: 0,
description: 'Atraso em milissegundos antes do envio',
},
{
displayName: 'Marcar chat como lido',
name: 'readchat',
type: 'boolean',
displayOptions: { show: { action: ['carousel'] } },
default: false,
description: 'Marca conversa como lida após envio',
},
/* Status */
{
displayName: 'Tipo do status',
name: 'type',
type: 'options',
displayOptions: { show: { action: ['status'] } },
options: [
{ name: 'Texto', value: 'text' },
{ name: 'Imagem', value: 'image' },
{ name: 'Vídeo', value: 'video' },
{ name: 'Áudio', value: 'audio' },
{ name: 'MyAudio', value: 'myaudio' },
{ name: 'PTT', value: 'ptt' },
],
default: 'text',
required: true,
description: 'Tipo do status',
},
{
displayName: 'Texto',
name: 'text',
type: 'string',
displayOptions: { show: { action: ['status'] } },
default: '',
description: 'Texto principal ou legenda',
},
{
displayName: 'Cor de fundo',
name: 'background_color',
type: 'number',
displayOptions: { show: { action: ['status'] } },
default: 1,
description: 'Código da cor de fundo (1 – 19)',
},
{
displayName: 'Fonte',
name: 'font',
type: 'number',
displayOptions: { show: { action: ['status'] } },
default: 0,
description: 'Estilo da fonte (somente quando type = text) • (0 – 8)',
},
{
displayName: 'Arquivo',
name: 'file',
type: 'string',
displayOptions: { show: { action: ['status'] } },
default: '',
description: 'URL ou Base64 do arquivo de mídia',
},
{
displayName: 'Miniatura',
name: 'thumbnail',
type: 'string',
displayOptions: { show: { action: ['status'] } },
default: '',
description: 'URL ou Base64 da miniatura (opcional para vídeos)',
},
{
displayName: 'MIME type',
name: 'mimetype',
type: 'string',
displayOptions: { show: { action: ['status'] } },
default: '',
description: 'MIME type do arquivo (opcional)',
},
],
}; /* end description */
}
/*────────── EXECUTE ──────────*/
JApi.prototype.execute = function () {
return __awaiter(this, void 0, void 0, function () {
var creds, items, out, i, action, endpoint, body, options, res, err_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.getCredentials('japiApi')];
case 1:
creds = _a.sent();
items = this.getInputData();
out = [];
i = 0;
_a.label = 2;
case 2:
if (!(i < items.length)) return [3 /*break*/, 7];
action = this.getNodeParameter('action', i);
endpoint = "/send/".concat(action);
body = {};
/* switch monta o corpo conforme o tipo */
switch (action) {
case 'text':
body = {
number: this.getNodeParameter('phone', i),
text: this.getNodeParameter('text', i),
};
break;
case 'media':
body = {
number: this.getNodeParameter('phone', i),
type: this.getNodeParameter('mediaType', i),
file: this.getNodeParameter('file', i),
text: this.getNodeParameter('caption', i) || undefined,
docName: this.getNodeParameter('docName', i) || undefined,
mimetype: this.getNodeParameter('mimetype', i) || undefined,
replyid: this.getNodeParameter('replyid', i) || undefined,
mentions: this.getNodeParameter('mentions', i) || undefined,
readchat: this.getNodeParameter('readchat', i),
delay: this.getNodeParameter('delay', i),
};
break;
case 'contact':
body = {
number: this.getNodeParameter('phone', i),
fullName: this.getNodeParameter('fullName', i),
phoneNumber: this.getNodeParameter('phoneNumber', i),
organization: this.getNodeParameter('organization', i),
email: this.getNodeParameter('email', i),
url: this.getNodeParameter('url', i),
replyid: this.getNodeParameter('replyid', i),
mentions: this.getNodeParameter('mentions', i),
readchat: this.getNodeParameter('readchat', i),
delay: this.getNodeParameter('delay', i),
};
break;
case 'location':
body = {
number: this.getNodeParameter('phone', i),
name: this.getNodeParameter('name', i),
address: this.getNodeParameter('address', i),
latitude: this.getNodeParameter('latitude', i),
longitude: this.getNodeParameter('longitude', i),
replyid: this.getNodeParameter('replyid', i),
mentions: this.getNodeParameter('mentions', i),
readchat: this.getNodeParameter('readchat', i),
delay: this.getNodeParameter('delay', i),
};
break;
case 'status':
body = {
type: this.getNodeParameter('type', i),
text: this.getNodeParameter('text', i),
background_color: this.getNodeParameter('background_color', i),
font: this.getNodeParameter('font', i),
file: this.getNodeParameter('file', i),
thumbnail: this.getNodeParameter('thumbnail', i),
mimetype: this.getNodeParameter('mimetype', i),
};
break;
case 'menu':
body = {
number: this.getNodeParameter('phone', i),
type: this.getNodeParameter('menuType', i),
text: this.getNodeParameter('menuText', i),
footerText: this.getNodeParameter('footerText', i),
listButton: this.getNodeParameter('listButton', i),
selectableCount: this.getNodeParameter('selectableCount', i),
choices: this.getNodeParameter('choices', i),
replyid: this.getNodeParameter('replyid', i),
mentions: this.getNodeParameter('mentions', i),
readchat: this.getNodeParameter('readchat', i),
delay: this.getNodeParameter('delay', i),
};
break;
case 'carousel':
body = {
number: this.getNodeParameter('phone', i),
text: this.getNodeParameter('carouselText', i),
carousel: this.getNodeParameter('carousel', i),
delay: this.getNodeParameter('delay', i),
readchat: this.getNodeParameter('readchat', i),
};
break;
}
options = {
method: 'POST',
uri: "".concat(creds.baseUrl).concat(endpoint),
headers: { token: creds.apiKey },
body: body,
json: true,
};
_a.label = 3;
case 3:
_a.trys.push([3, 5, , 6]);
return [4 /*yield*/, this.helpers.request(options)];
case 4:
res = _a.sent();
out.push({ json: res });
return [3 /*break*/, 6];
case 5:
err_1 = _a.sent();
throw new n8n_workflow_1.NodeApiError(this.getNode(), err_1);
case 6:
i++;
return [3 /*break*/, 2];
case 7: return [2 /*return*/, this.prepareOutputData(out)];
}
});
});
};
return JApi;
}());
exports.JApi = JApi;