UNPKG

n8n-nodes-japi

Version:

Node comunitário para integrar a J-API (WhatsApp) ao n8n

611 lines (587 loc) 17 kB
import { IExecuteFunctions, NodeApiError, } from 'n8n-workflow'; export class JApi { description = { displayName: 'J-API', name: 'japi', group: ['output'], version: 1, icon: 'file:japi.svg', 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 ──────────*/ async execute(this: IExecuteFunctions) { const creds = await this.getCredentials('japiApi'); const items = this.getInputData(); const out = []; for (let i = 0; i < items.length; i++) { const action = this.getNodeParameter('action', i) as string; const endpoint = `/send/${action}`; // mapeia 1:1 com a API let body: Record<string, any> = {}; /* 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) as boolean, delay: this.getNodeParameter('delay', i) as number, }; 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) as boolean, delay: this.getNodeParameter('delay', i) as number, }; 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) as boolean, delay: this.getNodeParameter('delay', i) as number, }; 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; } const options: Record<string, any> = { method: 'POST', uri: `${creds.baseUrl}${endpoint}`, headers: { token: creds.apiKey }, body, json: true, }; try { const res = await this.helpers.request(options); out.push({ json: res }); } catch (err) { throw new NodeApiError(this.getNode(), err); } } return this.prepareOutputData(out); } }