From 9a7b19f9384c8cc6ff5b1c7bcdaa9496a8beb0b1 Mon Sep 17 00:00:00 2001 From: Daniel Gradman-Svendsen Date: Mon, 17 Nov 2025 13:58:43 +0100 Subject: [PATCH] refactor --- chats-out.js | 109 ++++--------------------------- group-out.js | 124 +++++------------------------------ whatsapp-utils.js | 163 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 192 insertions(+), 204 deletions(-) create mode 100644 whatsapp-utils.js diff --git a/chats-out.js b/chats-out.js index dab6779..331124f 100644 --- a/chats-out.js +++ b/chats-out.js @@ -1,3 +1,5 @@ +const { formatChatNumber, formatChatNumberSocket } = require('./whatsapp-utils'); + module.exports = function(RED) { function WhatsappOut(config) { RED.nodes.createNode(this,config); @@ -13,103 +15,14 @@ module.exports = function(RED) { }; const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); - async function webNubmerSeteing(numb){ - // Validate and clean the number - if (!numb) { - throw new Error('Number is required'); - } - - // Convert to string if number - numb = typeof numb === 'number' ? numb.toString() : numb; - - // Check if it's already a formatted ID (contains @) - if (numb.includes('@')) { - return numb; - } - - // For group IDs, preserve the hyphen (format: NUMBER-TIMESTAMP@g.us) - // Only strip non-digits and non-hyphens for cleaning - numb = numb.replace(/[^\d-]/g, ''); - - // Check if number is valid after cleaning - if (!numb || numb.length === 0) { - throw new Error('Invalid number format'); - } - - // Check if it looks like a group ID (contains hyphen) - if (numb.includes('-')) { - // It's a group ID, format it directly without validation - return `${numb}@g.us`; - } - - // It's a regular phone number, validate with getNumberId - try { - var numbID = await node.waClient.getNumberId(numb); - if(numbID) { - return `${numbID.user}@${numbID.server}`; - } else { - return `${numb}@g.us` - } - } catch (e) { - node.error(`Error getting number ID for ${numb}: ${e.message}`); - throw e; - } - } - - async function socNubmerSeteing(numb){ - if (numb && numb.remoteJid){ - return numb.remoteJid; - } - - // Validate and clean the number - if (!numb) { - throw new Error('Number is required'); - } - - // Convert to string if number - numb = typeof numb === 'number' ? numb.toString() : numb; - - // Check if it's already a formatted ID (contains @) - if (numb.includes('@')) { - return numb; - } - - // For group IDs, preserve the hyphen (format: NUMBER-TIMESTAMP@g.us) - // Only strip non-digits and non-hyphens for cleaning - numb = numb.replace(/[^\d-]/g, ''); - - // Check if number is valid after cleaning - if (!numb || numb.length === 0) { - throw new Error('Invalid number format'); - } - - // Check if it looks like a group ID (contains hyphen) - if (numb.includes('-')) { - // It's a group ID, format it directly - return `${numb}@g.us`; - } - - // It's a regular phone number, validate with onWhatsApp - try { - const [result] = await (await node.waClient).onWhatsApp(numb) - if (result?.exists){ - return result.jid - } - return numb = `${numb}@g.us`; - } catch (e) { - node.error(`Error checking WhatsApp for ${numb}: ${e.message}`); - throw e; - } - } - async function whatsappMessage(numb , inputMessage){ if (node.waClient.clientType === "waWebClient"){ try { - numb = await webNubmerSeteing(numb); + numb = await formatChatNumber(numb, node.waClient, node); if(typeof inputMessage === "object"){ inputMessage = new Buttons(inputMessage.text, inputMessage.buttons, "text" ,inputMessage.footer); } - node.waClient.sendMessage(numb, inputMessage); + await node.waClient.sendMessage(numb, inputMessage); } catch(e){ node.error(`Error Sending Msg: ${e}`); @@ -118,18 +31,18 @@ module.exports = function(RED) { else if (node.waClient.clientType === "waSocketClient"){ try { let client = await node.waClient; - numb = await socNubmerSeteing(numb) + numb = await formatChatNumberSocket(numb, node.waClient, node); if (typeof inputMessage ==="string"){ inputMessage = {text : inputMessage}; } - const msgStatus = await client.sendMessage(numb, inputMessage); + await client.sendMessage(numb, inputMessage); } catch(e) { - node.error(`Error Sending Msg:: ${e}`); + node.error(`Error Sending Msg: ${e}`); } } else { - node.error(`Error Sending Msg: ${e}`) + node.error(`Error Sending Msg: Unknown client type`) } SetStatus("Message Send.", "green"); setTimeout(()=>{ @@ -142,12 +55,12 @@ module.exports = function(RED) { var whatsappImageBase64 = whatsappImage.split(',')[1] || whatsappImage; try { if (node.waClient.clientType === "waWebClient"){ - numb = await webNubmerSeteing(numb) + numb = await formatChatNumber(numb, node.waClient, node); var myMessage = new MessageMedia('image/png', whatsappImageBase64, null, null); - node.waClient.sendMessage(numb, myMessage, {caption : whatsappCaption }); + await node.waClient.sendMessage(numb, myMessage, {caption : whatsappCaption }); } else { - numb = await socNubmerSeteing(numb) + numb = await formatChatNumberSocket(numb, node.waClient, node); let imageToSend = Buffer.from(whatsappImageBase64, "base64"); const imageMessage = { diff --git a/group-out.js b/group-out.js index 63116bb..0e46117 100644 --- a/group-out.js +++ b/group-out.js @@ -1,3 +1,5 @@ +const { formatGroupId, formatGroupIdSocket } = require('./whatsapp-utils'); + module.exports = function(RED) { function WhatsappGroupOut(config) { RED.nodes.createNode(this,config); @@ -13,105 +15,16 @@ module.exports = function(RED) { }; const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); - - async function webNubmerSeteing(numb){ - // Validate and clean the number - if (!numb) { - throw new Error('Number is required'); - } - - // Convert to string if number - numb = typeof numb === 'number' ? numb.toString() : numb; - - // Check if it's already a formatted ID (contains @) - if (numb.includes('@')) { - return numb; - } - - // For group IDs, preserve the hyphen (format: NUMBER-TIMESTAMP@g.us) - // Only strip non-digits and non-hyphens for cleaning - numb = numb.replace(/[^\d-]/g, ''); - - // Check if number is valid after cleaning - if (!numb || numb.length === 0) { - throw new Error('Invalid number format'); - } - - // Check if it looks like a group ID (contains hyphen) - if (numb.includes('-')) { - // It's a group ID, format it directly without validation - return `${numb}@g.us`; - } - - // It's a regular phone number, validate with getNumberId - try { - var numbID = await node.waClient.getNumberId(numb); - if(numbID) { - return `${numbID.user}@${numbID.server}`; - } else { - return `${numb}@g.us` - } - } catch (e) { - node.error(`Error getting number ID for ${numb}: ${e.message}`); - throw e; - } - } - - async function socNubmerSeteing(numb){ - if (numb && numb.remoteJid){ - return numb.remoteJid; - } - - // Validate and clean the number - if (!numb) { - throw new Error('Number is required'); - } - - // Convert to string if number - numb = typeof numb === 'number' ? numb.toString() : numb; - - // Check if it's already a formatted ID (contains @) - if (numb.includes('@')) { - return numb; - } - - // For group IDs, preserve the hyphen (format: NUMBER-TIMESTAMP@g.us) - // Only strip non-digits and non-hyphens for cleaning - numb = numb.replace(/[^\d-]/g, ''); - - // Check if number is valid after cleaning - if (!numb || numb.length === 0) { - throw new Error('Invalid number format'); - } - - // Check if it looks like a group ID (contains hyphen) - if (numb.includes('-')) { - // It's a group ID, format it directly - return `${numb}@g.us`; - } - - // It's a regular phone number, validate with onWhatsApp - try { - const [result] = await (await node.waClient).onWhatsApp(numb) - if (result?.exists){ - return result.jid - } - return numb = `${numb}@g.us`; - } catch (e) { - node.error(`Error checking WhatsApp for ${numb}: ${e.message}`); - throw e; - } - } - - async function whatsappMessage(numb , inputMessage){ + async function whatsappMessage(gID , inputMessage){ if (node.waClient.clientType === "waWebClient"){ try { - numb = await webNubmerSeteing(numb); + gID = await formatGroupId(gID, node.waClient, node); if(typeof inputMessage === "object"){ inputMessage = new Buttons(inputMessage.text, inputMessage.buttons, "text" ,inputMessage.footer); - node.waClient.sendMessage(numb, inputMessage); + await node.waClient.sendMessage(gID, inputMessage); + } else { + await node.waClient.sendMessage(gID, inputMessage); } - node.waClient.sendMessage(numb, inputMessage); } catch(e){ node.error(`Error Sending Msg: ${e}`); @@ -120,18 +33,18 @@ module.exports = function(RED) { else if (node.waClient.clientType === "waSocketClient"){ try { let client = await node.waClient; - numb = await socNubmerSeteing(numb) + gID = await formatGroupIdSocket(gID, node.waClient, node); if (typeof inputMessage ==="string"){ inputMessage = {text : inputMessage}; } - const msgStatus = await client.sendMessage(numb, inputMessage); + await client.sendMessage(gID, inputMessage); } catch(e) { - node.error(`Error Sending Msg:: ${e}`); + node.error(`Error Sending Msg: ${e}`); } } else { - node.error(`Error Sending Msg: ${e}`) + node.error(`Error Sending Msg: Unknown client type`) } SetStatus("Message Send.", "green"); setTimeout(()=>{ @@ -139,26 +52,25 @@ module.exports = function(RED) { }, 2000) }; - async function whatsappMultiMediaMessage(numb, whatsappImage, whatsappCaption){ + async function whatsappMultiMediaMessage(gID, whatsappImage, whatsappCaption){ try { if (node.waClient.clientType === "waWebClient"){ - numb = await webNubmerSeteing(node.number) + gID = await formatGroupId(gID, node.waClient, node); var whatsappImageBase64 = whatsappImage.split(',')[1] || whatsappImage; var myMessage = new MessageMedia('image/png', whatsappImageBase64, null, null); - node.waClient.sendMessage(numb, myMessage, {caption : whatsappCaption || "Image from Node-Red"}); + await node.waClient.sendMessage(gID, myMessage, {caption : whatsappCaption || "Image from Node-Red"}); } else { - numb = await socNubmerSeteing(node.number); - + gID = await formatGroupIdSocket(gID, node.waClient, node); + var whatsappImageBase64 = whatsappImage.split(',')[1] || whatsappImage; let imageToSend = Buffer.from(whatsappImageBase64, "base64"); const imageMessage = { - // image: {url : whatsappImage}, image: imageToSend, - caption: whatsappCaption + caption: whatsappCaption || "Image from Node-Red" } let client = await node.waClient; - const msgStatus = await client.sendMessage(numb, imageMessage); + await client.sendMessage(gID, imageMessage); } SetStatus("Message Send.", "green"); setTimeout(()=>{ diff --git a/whatsapp-utils.js b/whatsapp-utils.js new file mode 100644 index 0000000..30b976a --- /dev/null +++ b/whatsapp-utils.js @@ -0,0 +1,163 @@ +// Shared utility functions for WhatsApp nodes + +/** + * Format a phone number for WhatsApp Web client (chat) + * Assumes the recipient is a chat (@c.us) + */ +async function formatChatNumber(numb, waClient, nodeLogger) { + // Validate input + if (!numb) { + throw new Error('Number is required'); + } + + // Convert to string if number + numb = typeof numb === 'number' ? numb.toString() : numb; + + // If already formatted, return as-is + if (numb.includes('@')) { + return numb; + } + + // Clean: remove everything except digits + numb = numb.replace(/\D/g, ''); + + // Validate cleaned number + if (!numb || numb.length === 0) { + throw new Error('Invalid number format'); + } + + // Validate with getNumberId + try { + const numbID = await waClient.getNumberId(numb); + if (numbID) { + return `${numbID.user}@${numbID.server}`; + } else { + // If validation fails, assume it's a chat + nodeLogger.warn(`getNumberId returned null for ${numb}, using @c.us`); + return `${numb}@c.us`; + } + } catch (e) { + // If error, assume it's a chat + nodeLogger.warn(`getNumberId failed for ${numb}, using @c.us: ${e.message}`); + return `${numb}@c.us`; + } +} + +/** + * Format a group ID for WhatsApp Web client + * Assumes the recipient is a group (@g.us) + */ +async function formatGroupId(gID, waClient, nodeLogger) { + // Validate input + if (!gID) { + throw new Error('Group ID is required'); + } + + // Convert to string if number + gID = typeof gID === 'number' ? gID.toString() : gID; + + // If already formatted, return as-is + if (gID.includes('@')) { + return gID; + } + + // Clean: preserve hyphens (group format: NUMBER-TIMESTAMP) + gID = gID.replace(/[^\d-]/g, ''); + + // Validate cleaned group ID + if (!gID || gID.length === 0) { + throw new Error('Invalid group ID format'); + } + + // Groups always use @g.us + return `${gID}@g.us`; +} + +/** + * Format a phone number for Socket client (chat) + * Assumes the recipient is a chat + */ +async function formatChatNumberSocket(numb, waClient, nodeLogger) { + // Handle message object with remoteJid + if (numb && numb.remoteJid) { + return numb.remoteJid; + } + + // Validate input + if (!numb) { + throw new Error('Number is required'); + } + + // Convert to string if number + numb = typeof numb === 'number' ? numb.toString() : numb; + + // If already formatted, return as-is + if (numb.includes('@')) { + return numb; + } + + // Clean: remove everything except digits + numb = numb.replace(/\D/g, ''); + + // Validate cleaned number + if (!numb || numb.length === 0) { + throw new Error('Invalid number format'); + } + + // Check if number exists on WhatsApp + try { + const client = await waClient; + const [result] = await client.onWhatsApp(numb); + if (result?.exists) { + return result.jid; + } + // If not found, assume chat + nodeLogger.warn(`onWhatsApp returned no results for ${numb}, using @s.whatsapp.net`); + return `${numb}@s.whatsapp.net`; + } catch (e) { + nodeLogger.warn(`onWhatsApp failed for ${numb}, using @s.whatsapp.net: ${e.message}`); + return `${numb}@s.whatsapp.net`; + } +} + +/** + * Format a group ID for Socket client + * Assumes the recipient is a group (@g.us) + */ +async function formatGroupIdSocket(gID, waClient, nodeLogger) { + // Handle message object with remoteJid + if (gID && gID.remoteJid) { + return gID.remoteJid; + } + + // Validate input + if (!gID) { + throw new Error('Group ID is required'); + } + + // Convert to string if number + gID = typeof gID === 'number' ? gID.toString() : gID; + + // If already formatted, return as-is + if (gID.includes('@')) { + return gID; + } + + // Clean: preserve hyphens (group format: NUMBER-TIMESTAMP) + gID = gID.replace(/[^\d-]/g, ''); + + // Validate cleaned group ID + if (!gID || gID.length === 0) { + throw new Error('Invalid group ID format'); + } + + // Groups always use @g.us + return `${gID}@g.us`; +} + +module.exports = { + formatChatNumber, + formatGroupId, + formatChatNumberSocket, + formatGroupIdSocket +};