UNPKG

n8n-nodes-whatsapp-multi-session

Version:
1,061 lines 79.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.WhatsAppMultiSession = void 0; class WhatsAppMultiSession { constructor() { this.description = { displayName: 'WhatsApp Multi-Session', name: 'whatsAppMultiSession', icon: 'file:whatsapp.svg', group: ['communication'], version: 1, subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', description: 'Interact with WhatsApp Multi-Session API', defaults: { name: 'WhatsApp Multi-Session', }, inputs: [{ displayName: '', type: "main" /* NodeConnectionType.Main */ }], outputs: [{ displayName: '', type: "main" /* NodeConnectionType.Main */ }], credentials: [ { name: 'whatsAppMultiSessionApi', required: true, }, ], properties: [ { displayName: 'Resource', name: 'resource', type: 'options', noDataExpression: true, options: [ { name: 'Session', value: 'session', }, { name: 'Message', value: 'message', }, { name: 'Contact', value: 'contact', }, { name: 'Typing', value: 'typing', }, { name: 'Webhook', value: 'webhook', }, ], default: 'message', }, // Session Operations { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['session'], }, }, options: [ { name: 'Create', value: 'create', description: 'Create a new WhatsApp session', action: 'Create a session', }, { name: 'Get', value: 'get', description: 'Get session information', action: 'Get a session', }, { name: 'List', value: 'list', description: 'List all sessions', action: 'List sessions', }, { name: 'Login', value: 'login', description: 'Initiate login process. Use "Get QR Code" operation afterwards to retrieve the QR code for scanning with WhatsApp', action: 'Login to session', }, { name: 'Get QR Code', value: 'getQrCode', description: 'Get QR code for session authentication. Returns QR code as base64 image data URL. Scan with WhatsApp on your phone to authenticate', action: 'Get QR code', }, { name: 'Connect', value: 'connect', description: 'Connect a session (alias for Login). Use "Get QR Code" operation afterwards to retrieve the QR code', action: 'Connect a session', }, { name: 'Disconnect', value: 'disconnect', description: 'Disconnect a session', action: 'Disconnect a session', }, { name: 'Delete', value: 'delete', description: 'Delete a session', action: 'Delete a session', }, { name: 'Check Status', value: 'checkStatus', description: 'Check if session is active and ready', action: 'Check session status', }, ], default: 'list', }, // Message Operations { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['message'], }, }, options: [ { name: 'Send Text', value: 'sendText', description: 'Send a text message', action: 'Send a text message', }, { name: 'Send Image', value: 'sendImage', description: 'Send an image message', action: 'Send an image message', }, { name: 'Send Document', value: 'sendDocument', description: 'Send a document', action: 'Send a document', }, { name: 'Send Location', value: 'sendLocation', description: 'Send a location', action: 'Send a location', }, { name: 'Forward Message', value: 'forwardMessage', description: 'Forward an existing message', action: 'Forward a message', }, { name: 'Reply to Message', value: 'replyMessage', description: 'Reply to a specific message', action: 'Reply to a message', }, { name: 'Mark as Read', value: 'markAsRead', description: 'Mark a message as read (send blue tick)', action: 'Mark message as read', }, { name: 'Download Image', value: 'downloadImage', description: 'Download an image from a received message', action: 'Download an image', }, ], default: 'sendText', }, // Contact Operations { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['contact'], }, }, options: [ { name: 'List', value: 'list', description: 'List contacts', action: 'List contacts', }, { name: 'Check', value: 'check', description: 'Check if number is on WhatsApp', action: 'Check contact', }, ], default: 'list', }, // Typing Operations { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['typing'], }, }, options: [ { name: 'Start Typing', value: 'startTyping', description: 'Show typing indicator', action: 'Start typing indicator', }, { name: 'Stop Typing', value: 'stopTyping', description: 'Hide typing indicator', action: 'Stop typing indicator', }, ], default: 'startTyping', }, // Webhook Operations { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, displayOptions: { show: { resource: ['webhook'], }, }, options: [ { name: 'Set URL', value: 'setUrl', description: 'Set webhook URL for receiving messages', action: 'Set webhook URL', }, { name: 'Get URL', value: 'getUrl', description: 'Get current webhook URL', action: 'Get webhook URL', }, { name: 'Remove', value: 'remove', description: 'Remove webhook URL', action: 'Remove webhook', }, ], default: 'setUrl', }, // Session ID field (used by most operations) { displayName: 'Session ID', name: 'sessionId', type: 'string', required: true, displayOptions: { show: { resource: ['message', 'contact', 'typing', 'webhook'], }, }, default: '', placeholder: 'session_123', description: 'The WhatsApp session ID', }, // Session ID field for session operations (get, login, getQrCode, connect, disconnect, delete, checkStatus) { displayName: 'Session ID', name: 'sessionId', type: 'string', required: true, displayOptions: { show: { resource: ['session'], operation: ['get', 'login', 'getQrCode', 'connect', 'disconnect', 'delete', 'checkStatus'], }, }, default: '', placeholder: 'session_123', description: 'The WhatsApp session ID', hint: 'Use "Create" operation first to get a session ID, or use "List" to see existing sessions', }, // Session creation fields { displayName: 'Session Name', name: 'sessionName', type: 'string', displayOptions: { show: { resource: ['session'], operation: ['create'], }, }, default: '', placeholder: 'My WhatsApp Session', description: 'Display name for the session', }, // Message fields { displayName: 'Phone Number', name: 'phoneNumber', type: 'string', required: true, displayOptions: { show: { resource: ['message'], }, }, default: '', placeholder: '6281234567890', description: 'Recipient phone number (with country code, no +)', }, { displayName: 'Message Text', name: 'messageText', type: 'string', required: true, displayOptions: { show: { resource: ['message'], operation: ['sendText'], }, }, default: '', placeholder: 'Hello from n8n!', description: 'The text message to send', }, { displayName: 'Image URL', name: 'imageUrl', type: 'string', required: true, displayOptions: { show: { resource: ['message'], operation: ['sendImage'], }, }, default: '', placeholder: 'https://example.com/image.jpg', description: 'URL of the image to send', }, { displayName: 'Caption', name: 'caption', type: 'string', displayOptions: { show: { resource: ['message'], operation: ['sendImage'], }, }, default: '', placeholder: 'Image caption', description: 'Optional caption for the image', }, { displayName: 'Document URL', name: 'documentUrl', type: 'string', required: true, displayOptions: { show: { resource: ['message'], operation: ['sendDocument'], }, }, default: '', placeholder: 'https://example.com/document.pdf', description: 'URL of the document to send', }, { displayName: 'Filename', name: 'filename', type: 'string', displayOptions: { show: { resource: ['message'], operation: ['sendDocument'], }, }, default: '', placeholder: 'document.pdf', description: 'Optional filename for the document', }, { displayName: 'Latitude', name: 'latitude', type: 'number', required: true, displayOptions: { show: { resource: ['message'], operation: ['sendLocation'], }, }, default: 0, placeholder: '-6.2088', description: 'Latitude coordinate', }, { displayName: 'Longitude', name: 'longitude', type: 'number', required: true, displayOptions: { show: { resource: ['message'], operation: ['sendLocation'], }, }, default: 0, placeholder: '106.8456', description: 'Longitude coordinate', }, { displayName: 'Location Name', name: 'locationName', type: 'string', displayOptions: { show: { resource: ['message'], operation: ['sendLocation'], }, }, default: '', placeholder: 'Jakarta, Indonesia', description: 'Optional name for the location', }, // Forward message fields { displayName: 'Message ID to Forward', name: 'forwardMessageId', type: 'string', required: true, displayOptions: { show: { resource: ['message'], operation: ['forwardMessage'], }, }, default: '', placeholder: '3EB0D136B13F32830F7B88', description: 'ID of the message to forward (from webhook trigger data: {{$json.id}}). Must be from the same session.', }, { displayName: 'Message Text', name: 'forwardMessageText', type: 'string', required: true, displayOptions: { show: { resource: ['message'], operation: ['forwardMessage'], }, }, default: '', placeholder: 'Hello, this is the message content to forward', description: 'The text content of the message to forward (from webhook trigger data: {{$json.message.text || $json.message.conversation}})', }, // Reply message fields { displayName: 'Reply Text', name: 'replyText', type: 'string', required: true, displayOptions: { show: { resource: ['message'], operation: ['replyMessage'], }, }, default: '', placeholder: 'Thank you for your message!', description: 'The reply message text', }, { displayName: 'Quoted Message ID', name: 'quotedMessageId', type: 'string', required: true, displayOptions: { show: { resource: ['message'], operation: ['replyMessage'], }, }, default: '', placeholder: '3EB0D136B13F32830F7B88', description: 'ID of the message to reply to (from webhook trigger data: {{$json.id}}). Must be from the same session.', }, { displayName: 'Message ID', name: 'messageId', type: 'string', required: true, displayOptions: { show: { resource: ['message'], operation: ['markAsRead'], }, }, default: '', placeholder: '3EB0D136B13F32830F7B88', description: 'ID of the message to mark as read (from webhook trigger data: {{$json.id}}). Must be from the same session.', }, // Download Image fields { displayName: 'Media URL', name: 'mediaUrl', type: 'string', required: true, displayOptions: { show: { resource: ['message'], operation: ['downloadImage'], }, }, default: '', placeholder: '/api/media/temp/filename.jpg?expires=1234567890', description: 'Media URL from webhook trigger data ({{$json.media_url}}). This should be the temporary media URL.', }, { displayName: 'Output Format', name: 'outputFormat', type: 'options', displayOptions: { show: { resource: ['message'], operation: ['downloadImage'], }, }, options: [ { name: 'Base64', value: 'base64', description: 'Return image as base64 encoded string', }, { name: 'Binary', value: 'binary', description: 'Return image as binary data buffer', }, { name: 'File', value: 'file', description: 'Return image as downloadable file attachment', }, ], default: 'base64', description: 'Format for the downloaded image data', }, { displayName: 'Put Output File in Field', name: 'outputFileField', type: 'string', displayOptions: { show: { resource: ['message'], operation: ['downloadImage'], outputFormat: ['file'], }, }, default: 'data', placeholder: 'data', description: 'Name of the binary property to write the file to', }, // Contact check field { displayName: 'Phone Number', name: 'phoneNumber', type: 'string', required: true, displayOptions: { show: { resource: ['contact'], operation: ['check'], }, }, default: '', placeholder: '6281234567890', description: 'Phone number to check (with country code, no +)', }, // Typing fields { displayName: 'Phone Number', name: 'phoneNumber', type: 'string', required: true, displayOptions: { show: { resource: ['typing'], }, }, default: '', placeholder: '6281234567890', description: 'Phone number to send typing indicator to (with country code, no +)', }, // Webhook fields { displayName: 'Webhook URL', name: 'webhookUrl', type: 'string', required: true, displayOptions: { show: { resource: ['webhook'], operation: ['setUrl'], }, }, default: '', placeholder: 'https://your-server.com/webhook', description: 'URL to receive webhook notifications', }, ], }; } async execute() { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l; const items = this.getInputData(); const returnData = []; const credentials = await this.getCredentials('whatsAppMultiSessionApi'); const baseUrl = credentials.serverUrl; // Use API key for authentication const authHeaders = { 'Authorization': `Bearer ${credentials.apiKey}`, 'Content-Type': 'application/json', }; // Helper function for handling message sending with proper error handling const handleMessageSending = async (url, body, sessionId, operationType) => { var _a, _b; try { const response = await this.helpers.request({ method: 'POST', url: url, headers: authHeaders, body: body, json: true, resolveWithFullResponse: true, simple: false, }); // Handle successful response if (response.statusCode === 200 || response.statusCode === 201) { return { success: true, message: `${operationType} sent successfully`, sessionId: sessionId, operation: operationType, ...response.body, }; } // Handle error responses let errorMessage = `HTTP ${response.statusCode}`; let errorDetails = ''; let errorType = 'UNKNOWN_ERROR'; try { const errorBody = ((_a = response.body) === null || _a === void 0 ? void 0 : _a.toString()) || ''; // Try to parse as JSON first try { const errorJson = JSON.parse(errorBody); errorMessage = errorJson.message || errorJson.error || errorMessage; errorDetails = errorJson.details || ''; errorType = errorJson.code || errorType; } catch (jsonError) { // If not JSON, use plain text errorMessage = errorBody.trim() || errorMessage; } } catch (parseError) { // If can't parse body, use status message errorMessage = response.statusMessage || errorMessage; } // Handle specific error cases for message sending if (response.statusCode === 403) { errorType = 'SESSION_DISABLED'; errorMessage = 'Session is disabled or cannot perform operations'; errorDetails = errorDetails || 'The WhatsApp session may be disconnected, disabled, or not properly initialized. Please check the session status and try reconnecting.'; } else if (response.statusCode === 400) { errorType = 'BAD_REQUEST'; errorMessage = 'Bad request for message sending'; errorDetails = errorDetails || 'Check the message parameters and recipient phone number format.'; } else if (response.statusCode === 401) { errorType = 'AUTHENTICATION_FAILED'; errorMessage = 'Authentication failed'; errorDetails = errorDetails || 'Invalid API key or insufficient permissions.'; } else if (response.statusCode === 404) { errorType = 'SESSION_NOT_FOUND'; errorMessage = 'Session not found'; errorDetails = errorDetails || `The session ID '${sessionId}' was not found. Please check the session ID and ensure the session exists.`; } else if (response.statusCode === 429) { errorType = 'RATE_LIMITED'; errorMessage = 'Rate limit exceeded'; errorDetails = errorDetails || 'Too many messages sent too quickly. Please wait before sending more messages.'; } else if (response.statusCode === 500) { errorType = 'SERVER_ERROR'; errorMessage = 'Internal server error'; errorDetails = errorDetails || 'The WhatsApp API encountered an error. Please try again later.'; } else if (response.statusCode === 503) { errorType = 'SERVICE_UNAVAILABLE'; errorMessage = 'Service unavailable'; errorDetails = errorDetails || 'The WhatsApp service is temporarily unavailable. Please try again later.'; } // Return structured error response return { success: false, error: true, errorCode: response.statusCode, errorType: errorType, errorMessage: errorMessage, errorDetails: errorDetails, sessionId: sessionId, operation: operationType, requestBody: body, }; } catch (networkError) { // Handle network errors and other exceptions const error = networkError; let errorMessage = 'Network error or request failed'; let errorDetails = error.message || ''; let errorType = 'NETWORK_ERROR'; // Try to extract more specific error information if (error.code) { errorType = 'NETWORK_ERROR'; errorMessage = `Network error: ${error.code}`; errorDetails = errorDetails || error.code; } else if ((_b = error.response) === null || _b === void 0 ? void 0 : _b.statusCode) { errorMessage = `HTTP ${error.response.statusCode}`; errorType = 'HTTP_ERROR'; if (error.response.body) { try { const errorBody = error.response.body.toString(); const errorJson = JSON.parse(errorBody); errorDetails = errorJson.message || errorJson.error || errorBody; } catch { errorDetails = error.response.body.toString(); } } } return { success: false, error: true, errorType: errorType, errorMessage: errorMessage, errorDetails: errorDetails, sessionId: sessionId, operation: operationType, requestBody: body, }; } }; // Track processed requests to avoid duplicates const processedRequests = new Set(); for (let i = 0; i < items.length; i++) { const resource = this.getNodeParameter('resource', i); const operation = this.getNodeParameter('operation', i); // Create a unique key for this request to prevent duplicates let requestKey = ''; if (resource === 'message') { const sessionId = this.getNodeParameter('sessionId', i); const phoneNumber = this.getNodeParameter('phoneNumber', i); if (operation === 'sendText') { const messageText = this.getNodeParameter('messageText', i); requestKey = `${resource}-${operation}-${sessionId}-${phoneNumber}-${messageText}`; } else if (operation === 'sendImage') { const imageUrl = this.getNodeParameter('imageUrl', i); const caption = this.getNodeParameter('caption', i, ''); requestKey = `${resource}-${operation}-${sessionId}-${phoneNumber}-${imageUrl}-${caption}`; } else { requestKey = `${resource}-${operation}-${sessionId}-${phoneNumber}`; } } else if (resource === 'session') { // For session operations, only include sessionId if the operation requires it if (['get', 'login', 'getQrCode', 'connect', 'disconnect', 'delete', 'checkStatus'].includes(operation)) { const sessionId = this.getNodeParameter('sessionId', i); requestKey = `${resource}-${operation}-${sessionId}`; } else { // For operations like 'list' and 'create' that don't need sessionId requestKey = `${resource}-${operation}`; } } else { const sessionId = this.getNodeParameter('sessionId', i); requestKey = `${resource}-${operation}-${sessionId}`; } // Skip if this exact request has already been processed if (processedRequests.has(requestKey)) { continue; } processedRequests.add(requestKey); try { if (resource === 'session') { if (operation === 'list') { const response = await this.helpers.request({ method: 'GET', url: `${baseUrl}/api/sessions`, headers: authHeaders, json: true, }); // Handle both old format (response.sessions) and new format (response.data) const sessions = response.sessions || response.data || []; sessions.forEach((session) => { returnData.push({ json: session }); }); } else if (operation === 'create') { const sessionName = this.getNodeParameter('sessionName', i); const response = await this.helpers.request({ method: 'POST', url: `${baseUrl}/api/sessions`, headers: authHeaders, body: { name: sessionName, }, json: true, }); // Handle both old format (direct response) and new format (response.data) const sessionData = response.data || response; returnData.push({ json: sessionData }); } else if (operation === 'get') { const sessionId = this.getNodeParameter('sessionId', i); const response = await this.helpers.request({ method: 'GET', url: `${baseUrl}/api/sessions/${sessionId}`, headers: authHeaders, json: true, }); // Handle both old format (direct response) and new format (response.data) const sessionData = response.data || response; returnData.push({ json: sessionData }); } else if (operation === 'connect') { const sessionId = this.getNodeParameter('sessionId', i); const response = await this.helpers.request({ method: 'POST', url: `${baseUrl}/api/sessions/${sessionId}/connect`, headers: authHeaders, json: true, }); returnData.push({ json: response }); } else if (operation === 'disconnect') { const sessionId = this.getNodeParameter('sessionId', i); const response = await this.helpers.request({ method: 'POST', url: `${baseUrl}/api/sessions/${sessionId}/disconnect`, headers: authHeaders, json: true, }); returnData.push({ json: response }); } else if (operation === 'delete') { const sessionId = this.getNodeParameter('sessionId', i); const response = await this.helpers.request({ method: 'DELETE', url: `${baseUrl}/api/sessions/${sessionId}`, headers: authHeaders, json: true, }); returnData.push({ json: response }); } else if (operation === 'checkStatus') { const sessionId = this.getNodeParameter('sessionId', i); try { const response = await this.helpers.request({ method: 'GET', url: `${baseUrl}/api/sessions/${sessionId}`, headers: authHeaders, json: true, resolveWithFullResponse: true, simple: false, }); if (response.statusCode === 200) { // Handle both old format (direct response) and new format (response.data) const sessionData = response.data || response; // Check if session is active based on common session status fields const isActive = (sessionData.status === 'connected' || sessionData.status === 'active' || sessionData.state === 'CONNECTED' || sessionData.state === 'READY' || sessionData.connected === true || sessionData.ready === true); const statusInfo = { sessionId: sessionId, isActive: isActive, status: sessionData.status || sessionData.state || 'unknown', connected: sessionData.connected || sessionData.ready || false, phoneConnected: sessionData.phone_connected || false, lastActivity: sessionData.last_activity || sessionData.lastActivity || null, profilePicture: sessionData.profile_picture_url || sessionData.profilePicture || null, pushname: sessionData.pushname || null, wid: sessionData.wid || null, me: sessionData.me || null, rawSessionData: sessionData, }; returnData.push({ json: { success: true, message: isActive ? 'Session is active and ready' : 'Session is not active', ...statusInfo, } }); } else { // Handle error responses for non-200 status codes let errorMessage = `HTTP ${response.statusCode}`; let errorDetails = ''; let errorType = 'SESSION_CHECK_ERROR'; try { const errorBody = ((_a = response.body) === null || _a === void 0 ? void 0 : _a.toString()) || ''; // Try to parse as JSON first try { const errorJson = JSON.parse(errorBody); errorMessage = errorJson.message || errorJson.error || errorMessage; errorDetails = errorJson.details || ''; errorType = errorJson.code || errorType; } catch (jsonError) { // If not JSON, use plain text errorMessage = errorBody.trim() || errorMessage; } } catch (parseError) { // If can't parse body, use status message errorMessage = response.statusMessage || errorMessage; } // Handle specific error cases if (response.statusCode === 404) { errorType = 'SESSION_NOT_FOUND'; errorMessage = 'Session not found'; errorDetails = `The session ID '${sessionId}' was not found. Please check the session ID and ensure the session exists.`; } else if (response.statusCode === 401) { errorType = 'AUTHENTICATION_FAILED'; errorMessage = 'Authentication failed'; errorDetails = 'Invalid API key or insufficient permissions.'; } else if (response.statusCode === 403) { errorType = 'ACCESS_FORBIDDEN'; errorMessage = 'Access forbidden'; errorDetails = 'You do not have permission to access this session.'; } returnData.push({ json: { success: false, error: true, errorCode: response.statusCode, errorType: errorType, errorMessage: errorMessage, errorDetails: errorDetails, sessionId: sessionId, isActive: false, status: 'error', } }); } } catch (networkError) { // Handle network errors and other exceptions const error = networkError; let errorMessage = 'Network error or request failed'; let errorDetails = error.message || ''; let errorType = 'NETWORK_ERROR'; // Try to extract more specific error information if (error.code) { errorType = 'NETWORK_ERROR'; errorMessage = `Network error: ${error.code}`; errorDetails = errorDetails || error.code; } else if ((_b = error.response) === null || _b === void 0 ? void 0 : _b.statusCode) { errorMessage = `HTTP ${error.response.statusCode}`; errorType = 'HTTP_ERROR'; if (error.response.body) { try { const errorBody = error.response.body.toString(); const errorJson = JSON.parse(errorBody); errorDetails = errorJson.message || errorJson.error || errorBody; } catch { errorDetails = error.response.body.toString(); } } } returnData.push({ json: { success: false, error: true, errorType: errorType, errorMessage: errorMessage, errorDetails: errorDetails, sessionId: sessionId, isActive: false, status: 'error', } }); } } else if (operation === 'login') { // Initiate login for a session (triggers QR code generation) const sessionId = this.getNodeParameter('sessionId', i); try { const response = await this.helpers.request({ method: 'POST', url: `${baseUrl}/api/sessions/${sessionId}/login`, headers: authHeaders, json: true, resolveWithFullResponse: true, simple: false, }); if (response.statusCode !== 200) { let errorMessage = `HTTP ${response.statusCode}`; let errorDetails = ''; try { const errorBody = ((_c = response.body) === null || _c === void 0 ? void 0 : _c.toString()) || ''; const errorJson = JSON.parse(errorBody); errorMessage = errorJson.message || errorJson.error || errorMessage; errorDetails = errorJson.details || ''; } catch { errorMessage = response.statusMessage || errorMessage; } // Provide helpful error messages if (errorMessage.includes('already logged in')) { errorDetails = 'This session is already authe