diff --git a/README.md b/README.md index 494c4fc..dea25e9 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,7 @@ Admin Node generate QR Code just below the node for easy connection with whatsap 3. **Chats / Group Out** : As simple as mention on name, node will send `msg.payload` recived at input to the number mentioned in node. +A reply to All example is avilable in examples to import. MultiMedia Message: Requirments- | Input | Description | @@ -86,6 +87,7 @@ Admin Node generate QR Code just below the node for easy connection with whatsap ## Button, List and TemplateButton Supported in Whatsapp-Lite only, `TODO for Whatsapp-Web`. +A Complete Button-Bot example is avilable in Node examples. * Simple Button For simple 3 Bottons your `msg.paylod` should be... @@ -140,133 +142,10 @@ msg.payload = { ``` Yes its lot require for buttons, A node will come soon to minimize these effors. -You may direct import these test button with bellow code. +You may direct import these test buttons from the Node Examples. -```json -[ - { - "id": "6fe81f4418014185", - "type": "inject", - "z": "a133618d7af8d486", - "name": "Sample button Text", - "props": [ - { - "p": "payload" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "payload": "", - "payloadType": "date", - "x": 130, - "y": 180, - "wires": [ - [ - "ad52615eb46ed55b" - ] - ] - }, - { - "id": "cf275a62edbc347f", - "type": "inject", - "z": "a133618d7af8d486", - "name": "Smart Button Test", - "props": [ - { - "p": "topic", - "v": "", - "vt": "date" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "x": 130, - "y": 140, - "wires": [ - [ - "f5fbed87687de5b7" - ] - ] - }, - { - "id": "f5fbed87687de5b7", - "type": "function", - "z": "a133618d7af8d486", - "name": "TempButton", - "func": "msg.payload = {\n text: \"Hi it's a template message by Node-RED 👍 to Test\",\n footer: 'Hello I am footer of message.',\n templateButtons: [\n {\n index: 1,\n urlButton: {\n displayText: '⭐ Vist Node-RED',\n url: 'https://nodered.org/'\n }\n },\n {\n index: 2,\n callButton: {\n displayText: 'Call me!',\n phoneNumber: '+1 (234) 5678-901'\n }\n },\n {\n index: 3,\n quickReplyButton: {\n displayText: 'Click me I am Button',\n id: 'id-like-buttons-message'\n }\n },\n {\n index: 4,\n quickReplyButton: {\n displayText: '🖱️ Sample Button',\n id: 'button-id-no-space'\n }\n }] \n}\nreturn msg;", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 370, - "y": 140, - "wires": [ - [ - "9caf4dd912ca82f6" - ] - ] - }, - { - "id": "ad52615eb46ed55b", - "type": "function", - "z": "a133618d7af8d486", - "name": "Button", - "func": "msg.payload = {\n text: \"Hi it's button message\",\n footer: 'Hello World',\n headerType: 1,\n buttons: [\n {buttonId: 'id1', buttonText: {displayText: 'Button 1'}, type: 1},\n {buttonId: 'id2', buttonText: {displayText: 'Button 2'}, type: 1},\n {buttonId: 'id3', buttonText: {displayText: 'Button 3'}, type: 1}\n ]\n }\nreturn msg;", - "outputs": 1, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 350, - "y": 180, - "wires": [ - [ - "9caf4dd912ca82f6" - ] - ] - }, - { - "id": "670571eb76e4d237", - "type": "comment", - "z": "a133618d7af8d486", - "name": "Connect them to Chats-Out Node. ", - "info": "Dont foget mobile no.", - "x": 160, - "y": 100, - "wires": [] - }, - { - "id": "9caf4dd912ca82f6", - "type": "chats-out", - "z": "a133618d7af8d486", - "name": "Chats Out", - "whatsappLink": "whatsapp-web", - "number": "", - "x": 620, - "y": 160, - "wires": [] - }, - { - "id": "3e0550933ed06e84", - "type": "comment", - "z": "a133618d7af8d486", - "name": "Mention Your Phone No. in node.", - "info": "", - "x": 610, - "y": 120, - "wires": [] - } -] -``` -5. **Reply Node** : In Beta mode. +5. **Reply Node** : In Beta mode. (Chats-out Node can be used instead of reply node) Node will reply(the `payload`) on each message starting with string mentioned in instruction coloum or defaults `!red`. Avoid using it please. @@ -282,6 +161,7 @@ Issues and Suggestions are welcome [here.](https://github.com/raweee/node-red-co * `Ver-0.1.32` : Socket based `Whatsapp Lite` config node added in beta mode. Image message sending support added in chats-out node. * `Ver-0.1.33` : Button and list support added, Minnor bugs fixed. * `Ver-0.1.34` : Multiple Events reading options are added in Chats-In Node. +* `Ver-0.1.36` : Check box added to show status as `Online/Offline` in Whatsapp-Link Node, to get push notifications. Ping-Interval added to keep whatsapp alive for long time. Multiple examples added. ## Future Nodes Currently working on more Whatsapp Node and will be avilable soon - diff --git a/chats-in.js b/chats-in.js index c3fd660..194b850 100644 --- a/chats-in.js +++ b/chats-in.js @@ -72,6 +72,9 @@ module.exports = function(RED) { msg.from = msg.key.participant || msg.key.remoteJid; msg.from = msg.from.replace(/\D/g, '') || msg.from; msg.chatID = msg.key.remoteJid.replace(/\D/g, '') || msg.key.remoteJid ; + if(msg.message.extendedTextMessage){ + return null + } node.send(msg) }) }); diff --git a/chats-out.html b/chats-out.html index fa1cb9d..652f77a 100644 --- a/chats-out.html +++ b/chats-out.html @@ -28,7 +28,7 @@
Don't forget to mention international dialing code befor your number. diff --git a/examples/Button Bot.json b/examples/Button Bot.json new file mode 100644 index 0000000..3006caf --- /dev/null +++ b/examples/Button Bot.json @@ -0,0 +1,248 @@ +[ + { + "id": "595bb93a55a7385e", + "type": "tab", + "label": "Whatsapp-Button-Bot", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "d078d4c37dcddbfd", + "type": "chats-in", + "z": "595bb93a55a7385e", + "name": "Chats In", + "whatsappLink": "45549bbfeb38499a", + "whatsappLiteevent": "messages.upsert", + "whatsappWebevent": "", + "x": 80, + "y": 180, + "wires": [ + [ + "b6b28a9e9b416545", + "456aeee272c23848" + ] + ] + }, + { + "id": "b6b28a9e9b416545", + "type": "switch", + "z": "595bb93a55a7385e", + "name": "Get #hi", + "property": "payload", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "#hi", + "vt": "str" + }, + { + "t": "eq", + "v": "#Hi", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 2, + "x": 280, + "y": 120, + "wires": [ + [ + "8eb6e842afc48bf9" + ], + [ + "8eb6e842afc48bf9" + ] + ] + }, + { + "id": "8eb6e842afc48bf9", + "type": "function", + "z": "595bb93a55a7385e", + "name": "To get the #hi", + "func": "msg.payload = {\n text: \"Hi, This a test message for buttons, try selecting any option below.\", //String\n footer: `I'm just a footer.`, //String\n headerType: 1, //keep it \"1\" only.\n buttons: [ // Array of buttons.\n {buttonId: 'id1-string', buttonText: {displayText: 'Simple Reply'}, type: 1},\n {buttonId: 'id2-tempMessage', buttonText: {displayText: 'Temp. Button'}, type: 1},\n {buttonId: 'listMessage-id3', buttonText: {displayText: 'List Message'}, type: 1}\n ]\n}\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 480, + "y": 120, + "wires": [ + [ + "b6f984dbcae75a69", + "32b120bcdc2446d0" + ] + ] + }, + { + "id": "b6f984dbcae75a69", + "type": "chats-out", + "z": "595bb93a55a7385e", + "name": "Chats Out", + "whatsappLink": "45549bbfeb38499a", + "number": "", + "x": 700, + "y": 220, + "wires": [] + }, + { + "id": "456aeee272c23848", + "type": "switch", + "z": "595bb93a55a7385e", + "name": "Get Button Response", + "property": "message.buttonsResponseMessage.selectedButtonId", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "id1-string", + "vt": "str" + }, + { + "t": "eq", + "v": "id2-tempMessage", + "vt": "str" + }, + { + "t": "eq", + "v": "listMessage-id3", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 3, + "x": 260, + "y": 320, + "wires": [ + [ + "351cb37bbcb7d1dc" + ], + [ + "c8565e37aefa1654" + ], + [ + "2d81ceeaf0b1f07a" + ] + ] + }, + { + "id": "32b120bcdc2446d0", + "type": "debug", + "z": "595bb93a55a7385e", + "name": "", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 690, + "y": 400, + "wires": [] + }, + { + "id": "351cb37bbcb7d1dc", + "type": "function", + "z": "595bb93a55a7385e", + "name": "Mention Reply", + "func": "\nmsg.payload = {\n text: `Hi @${msg.from}, hello from Tomato Bot.`, \n mentions: [`${msg.key.remoteJid}`]\n}\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 480, + "y": 280, + "wires": [ + [ + "b6f984dbcae75a69", + "32b120bcdc2446d0" + ] + ] + }, + { + "id": "c8565e37aefa1654", + "type": "function", + "z": "595bb93a55a7385e", + "name": "Temp. Button", + "func": "msg.payload = {\n text: \"Hi it's a template message 👍 to Test\",\n footer: 'Hello I am footer of message.',\n templateButtons: [\n {index: 1, urlButton: {displayText: '⭐ Vist Node-RED', url: 'https://nodered.org/'}},\n {index: 2, callButton: {displayText: 'Call me!', phoneNumber: '+1 (234) 5678-901'}},\n {index: 3, quickReplyButton: {displayText: 'Click me I am Button', id: 'I-am-button-id-without-space'}},\n {index: 4, quickReplyButton: {displayText: '🖱️ Sample Button 2', id: 'button-2-was-clicked'}}\n ]\n}\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 470, + "y": 320, + "wires": [ + [ + "b6f984dbcae75a69", + "32b120bcdc2446d0" + ] + ] + }, + { + "id": "2d81ceeaf0b1f07a", + "type": "function", + "z": "595bb93a55a7385e", + "name": "List Button", + "func": "msg.payload = {\n text: \"This is a list\",\n footer: \"nice footer, link: https://google.com\",\n title: \"Amazing boldfaced list title\",\n buttonText: \"Required, Tap to see List\",\n sections : [{\n title: \"Section 1\",\n rows: [\n {title: \"Option 1\", rowId: \"option1\"},\n {title: \"Option 2\", rowId: \"option2\", description: \"This is a description\"}\n ]},\n {\n title: \"Section 2\",\n rows: [\n {title: \"Option 3\", rowId: \"option3\"},\n {title: \"Option 4\", rowId: \"option4\", description: \"This is a description V2\"}\n ]\n }]\n}\nreturn msg;", + "outputs": 1, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 470, + "y": 360, + "wires": [ + [ + "b6f984dbcae75a69", + "32b120bcdc2446d0" + ] + ] + }, + { + "id": "03ed98e4d8a123e1", + "type": "comment", + "z": "595bb93a55a7385e", + "name": "Socket Client Only", + "info": "Connect with whatsapp Lite (socket client) only", + "x": 110, + "y": 140, + "wires": [] + }, + { + "id": "ef67038c9dbff9d1", + "type": "comment", + "z": "595bb93a55a7385e", + "name": "To get #hi from user and reply a button", + "info": "Connect with whatsapp Lite (socket client) only", + "x": 390, + "y": 80, + "wires": [] + }, + { + "id": "08eb70757179304d", + "type": "comment", + "z": "595bb93a55a7385e", + "name": "Reply as per button response", + "info": "Connect with whatsapp Lite (socket client) only", + "x": 400, + "y": 240, + "wires": [] + }, + { + "id": "45549bbfeb38499a", + "type": "whatsappLink", + "cName": "whatsapp-web", + "name": "Web", + "clientType": "waSocketClient", + "onlineStatus": false, + "loopTime": "5" + } +] \ No newline at end of file diff --git a/examples/Reply to all.json b/examples/Reply to all.json new file mode 100644 index 0000000..9ac8bf8 --- /dev/null +++ b/examples/Reply to all.json @@ -0,0 +1,93 @@ +[ + { + "id": "595bb93a55a7385e", + "type": "tab", + "label": "Reply to All", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "b6f984dbcae75a69", + "type": "chats-out", + "z": "595bb93a55a7385e", + "name": "Chats Out", + "whatsappLink": "45549bbfeb38499a", + "number": "", + "x": 500, + "y": 180, + "wires": [] + }, + { + "id": "2ea4a9bd3d83e9e4", + "type": "template", + "z": "595bb93a55a7385e", + "name": "", + "field": "payload", + "fieldType": "msg", + "format": "handlebars", + "syntax": "mustache", + "template": "Hi, this is Node-Red reply for your message ( {{payload}} ) !", + "output": "str", + "x": 320, + "y": 180, + "wires": [ + [ + "b6f984dbcae75a69", + "befa79a6ea6ed85a" + ] + ] + }, + { + "id": "6eeee838055ff52c", + "type": "chats-in", + "z": "595bb93a55a7385e", + "name": "Chats In", + "whatsappLink": "45549bbfeb38499a", + "whatsappLiteevent": "messages.upsert", + "whatsappWebevent": "", + "x": 120, + "y": 180, + "wires": [ + [ + "2ea4a9bd3d83e9e4" + ] + ] + }, + { + "id": "befa79a6ea6ed85a", + "type": "debug", + "z": "595bb93a55a7385e", + "name": "", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 490, + "y": 280, + "wires": [] + }, + { + "id": "75123143fa9f3494", + "type": "comment", + "z": "595bb93a55a7385e", + "name": "Simple Ping Pong (Reply to all)", + "info": "", + "x": 350, + "y": 120, + "wires": [] + }, + { + "id": "45549bbfeb38499a", + "type": "whatsappLink", + "cName": "whatsapp-web", + "name": "Web", + "clientType": "waSocketClient", + "onlineStatus": false, + "loopTime": "5" + } +] \ No newline at end of file diff --git a/group-out.js b/group-out.js index ab3a23d..b7181d1 100644 --- a/group-out.js +++ b/group-out.js @@ -13,28 +13,39 @@ module.exports = function(RED) { }; const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); - function webNubmerSeteing(numb){ + + async function webNubmerSeteing(numb){ numb = typeof numb ==='number' ? numb : numb.replace(/\D/g, ''); - numb = `${numb}@g.us`; - return numb + // numb = `${numb}@c.us`; + var numbID = await node.waClient.getNumberId(numb); + if(numbID) { + return `${numbID.user}@${numbID.server}`; + } else { + return `${numb}@g.us` + } + // return numb } - function socNubmerSeteing(numb){ + async function socNubmerSeteing(numb){ + if (numb.remoteJid){ + return numb.remoteJid; + } numb = typeof numb ==='number' ? numb : numb.replace(/\D/g, ''); - numb = `${numb}@g.us` - return numb + const [result] = await (await node.waClient).onWhatsApp(numb) + if (result?.exists){ + console.log(result.exists) + return result.jid + } + return numb = `${numb}@g.us`; } async function whatsappMessage(numb , inputMessage){ if (node.waClient.clientType === "waWebClient"){ try { - numb = webNubmerSeteing(numb); + numb = await webNubmerSeteing(numb); if(typeof inputMessage === "object"){ - // inputMessage = new Buttons(inputMessage.text, inputMessage.buttons, "text" ,inputMessage.footer) - let myBtn = new Buttons('Button body',[{body:'bt1'},{body:'bt2'},{body:'bt3'}],'title','footer'); - console.log(myBtn) + inputMessage = new Buttons(inputMessage.text, inputMessage.buttons, "text" ,inputMessage.footer); node.waClient.sendMessage(numb, inputMessage); - inputMessage = myBtn } node.waClient.sendMessage(numb, inputMessage); } @@ -45,14 +56,14 @@ module.exports = function(RED) { else if (node.waClient.clientType === "waSocketClient"){ try { let client = await node.waClient; - numb = socNubmerSeteing(numb) + numb = await socNubmerSeteing(numb) if (typeof inputMessage ==="string"){ inputMessage = {text : inputMessage}; } const msgStatus = await client.sendMessage(numb, inputMessage); } catch(e) { - node.error(`Error Sending Msg: ${e}`); + node.error(`Error Sending Msg:: ${e}`); } } else { @@ -68,12 +79,12 @@ module.exports = function(RED) { var whatsappImageBase64 = whatsappImage.split(',')[1] || whatsappImage; try { if (node.waClient.clientType === "waWebClient"){ - numb = webNubmerSeteing(node.number) + numb = await webNubmerSeteing(node.number) var myMessage = new MessageMedia('image/png', whatsappImageBase64, null, null); node.waClient.sendMessage(numb, myMessage, {caption : whatsappCaption || "Image from Node-Red"}); } else { - numb = socNubmerSeteing(node.number) + numb = await socNubmerSeteing(node.number) const imageMessage = { text: whatsappCaption, footer: null, @@ -113,7 +124,10 @@ module.exports = function(RED) { delay(2000) } } - } else { + } else if(message.key.remoteJid){ + whatsappMessage(message.key, message.payload) + } + else { SetStatus("No number","red"); setTimeout(()=>{ SetStatus('Connected','green'); @@ -121,8 +135,6 @@ module.exports = function(RED) { } }); - - //whatsapp Status Parameters---- if (node.waClient.clientType === "waWebClient"){ node.waClient.on('qr', (qr) => { @@ -171,6 +183,7 @@ module.exports = function(RED) { } + } RED.nodes.registerType("group-out", WhatsappGroupOut); } diff --git a/package.json b/package.json index bf9ab95..a836732 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-red-contrib-whatsapp-link", - "version": "0.1.35", + "version": "0.1.36", "description": "Node to send and receive whatsapp messages in groups and chats. | No third party APIs", "repository": { "type": "git", diff --git a/whatsappLink.html b/whatsappLink.html index ba70194..559bada 100644 --- a/whatsappLink.html +++ b/whatsappLink.html @@ -4,7 +4,9 @@ defaults: { cName : {value:"whatsapp-web",required:true}, name : {value : "Web"}, - clientType: {} + clientType: {}, + onlineStatus : {value : true}, + loopTime : {value : 5, validate:RED.validators.number()} }, label: function() { return this.clientType; @@ -15,23 +17,35 @@ { value: "Select Client Type", options: [ - { value: "waWebClient", label: "Whatsapp Web"}, { value: "waSocketClient", label: "Whatsapp Lite"}, + { value: "waWebClient", label: "Whatsapp Web"}, ] } ] - }) + }); } }); \ No newline at end of file diff --git a/whatsappLink.js b/whatsappLink.js index a3f7285..0ec57e0 100644 --- a/whatsappLink.js +++ b/whatsappLink.js @@ -12,6 +12,10 @@ module.exports = function(RED) { RED.nodes.createNode(this,n); var WAnode = this; var clientType = n.clientType; + var loopTime = n.loopTime; + loopTime = loopTime + Math.random(); + loopTime = loopTime * 60 * 60 * 1000; + var onlineStatus = n.onlineStatus; var whatsappConnectionStatus; var client @@ -40,7 +44,20 @@ module.exports = function(RED) { }; client = WAConnect(); WAnode.connectionSetupID = setInterval(connectionSetup, 10000); - + + async function pressenceUpdate(OLS){ + try { + if (!OLS){ + await client.sendPresenceUnavailable(); + WAnode.log(`Whatsapp marked as Offline`) + } else { + await client.sendPresenceAvailable(); + } + } catch (e){ + WAnode.error("Error at pressence : " + e) + } + } + function WAClose(){ try { client.destroy(); @@ -49,12 +66,12 @@ module.exports = function(RED) { WAnode.err(`Error : Too many instructions! Try again.`) } }; - + var WARestart = function(){ WAClose(); WAConnect(); } - + async function connectionSetup(){ try { whatsappConnectionStatus = await client.getState(); @@ -69,7 +86,7 @@ module.exports = function(RED) { WAnode.log(`Error : Waiting for Initializion...`); } }; - + //QR-Code on Terminal and Ready Status. client.on("qr", (qr)=>{ clearInterval(WAnode.connectionSetupID); @@ -81,6 +98,7 @@ module.exports = function(RED) { }); client.on("ready", ()=>{ WAnode.log(`Status : Whatsapp Connected`); + pressenceUpdate(onlineStatus); }); //Whatsapp-Link Test Features (For Status and Testing Only.) @@ -130,7 +148,7 @@ Participants : ${chat.groupMetadata.size}` logger:pino({level: "silent"}), auth : state, browser: ["Node-RED", "Chrome", "4.0.0"], - markOnlineOnConnect: true, + markOnlineOnConnect: onlineStatus, patchMessageBeforeSending: (message) => { const requiresPatch = !!( message.buttonsMessage || message.templateMessage || message.listMessage @@ -153,8 +171,6 @@ Participants : ${chat.groupMetadata.size}` }) socketClient.ev.on('creds.update', saveCreds); - console.log(socketClient) - // socketClient.setMaxListeners(0); socketClient.ev.on('connection.update', (update) => { const { connection, lastDisconnect } = update @@ -180,21 +196,51 @@ Participants : ${chat.groupMetadata.size}` FS.rmSync(whatsappLinkDirSocket, {recursive : true, force: true}) connectSocketClient() } else { - WAnode.error(lastDisconnect?.error) + WAnode.log("Error : " + lastDisconnect?.error) } } } - }) + }) return socketClient }; client = connectSocketClient(); + client.onlineStatus = onlineStatus; client.clientType = clientType; client.clientStartFunction = connectSocketClient; WAnode.client = client }; - - + + async function loopStatusUpdate(){ + try { + if (clientType === "waSocketClient"){ + let myClient = await WAnode.client; + let id = myClient.user.id; + await myClient.sendPresenceUpdate("available", id) + if (!onlineStatus) { + setTimeout(()=> { + myClient.sendPresenceUpdate("unavailable", id) + },17000) + }; + } + else { + await WAnode.client.sendPresenceAvailable(); + if (!onlineStatus) { + setTimeout(()=> { + WAnode.client.sendPresenceUnavailable(); + },17000) + }; + }} + catch(e){ + WAnode.error("Error in whatsapp Ping.") + } + } + + var loopStatusUpdateID = setInterval(()=> { + loopStatusUpdate(); + }, loopTime) + this.on('close', (removed, done)=>{ + clearInterval(loopStatusUpdateID); if(removed){ if(clientType === "waWebClient"){ clearInterval(WAnode.connectionSetupID);