Node-Red to join Telegram messages

Good day,

Very recently I embarked on finding a way to ping network devices from a database or list. While pinging and a device appears offline, i then wanted to post that "offline" device to Telegram.

A member on the group was brilliant at assisting me and I was able to achieve exactly what was described above. Truly magic member.

Getting to the point: Each failed device will send a separate message and the Bot does not like being bombarded with messages and at some point give up and does not report all failures.

So now I am looking for a way to join or wait a certain time and then send as one combined message.

An example of the offline devices attached picture and the bottom part is the result that I am after, one combined message.

Thank you for looking.

[
    {
        "id": "337047ad.fcb858",
        "type": "tab",
        "label": "Flow 1",
        "disabled": false,
        "info": ""
    },
    {
        "id": "d901eeaa.d0d01",
        "type": "debug",
        "z": "337047ad.fcb858",
        "name": "Query DB",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 600,
        "y": 180,
        "wires": []
    },
    {
        "id": "dc501b75.0d31d8",
        "type": "ping",
        "z": "337047ad.fcb858",
        "mode": "triggered",
        "name": "",
        "host": "",
        "timer": "1",
        "inputs": 1,
        "x": 590,
        "y": 220,
        "wires": [
            [
                "d901eeaa.d0d01",
                "2e4bb18e.82e07e"
            ]
        ]
    },
    {
        "id": "db694dbc.960ca",
        "type": "function",
        "z": "337047ad.fcb858",
        "name": "Prepare telegram message (if offline)",
        "func": "if(msg.payload===false)\n{\n    msg.payload = {};\n    msg.payload.type = 'message';\n    msg.payload.content = `${msg.ping.name} (${msg.ping.host}) seems to be OFFLINE`;\n    msg.payload.chatId = xxxxxxx\n    return msg;\n}\n",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "x": 1010,
        "y": 400,
        "wires": [
            [
                "37286037.e03f9"
            ]
        ]
    },
    {
        "id": "33fa1e6a.529bf2",
        "type": "debug",
        "z": "337047ad.fcb858",
        "name": "Query DB",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 580,
        "y": 360,
        "wires": []
    },
    {
        "id": "2e4bb18e.82e07e",
        "type": "function",
        "z": "337047ad.fcb858",
        "name": "Prepare telegram message",
        "func": "const chatId = xxxxxxxx; // << update this\n\nif (!msg.ping.host) return null; //ensure host has a value\n\nvar pingMonitor = flow.get(\"pingMonitor\") || {};//get lookup object ||or|| a new empty object\nvar pingPrev = pingMonitor[msg.ping.host] || {};//this ping monitor object ||or|| a new empty object\nvar sendTelegram = false;\n\n//if ping is good, you get a number in payload...\nif (typeof msg.payload == \"number\" && pingPrev.status === \"bad\" ) {\n    pingPrev.status = \"good\";\n    msg.payload = {};\n    msg.payload.type = 'message';\n    msg.payload.content = `${msg.ping.name} (${msg.ping.host}) is now back online & operational!`;\n    msg.payload.chatId = chatId;\n    sendTelegram = true;\n} else if (msg.payload === false && (pingPrev.status === \"good\" || !pingPrev.status)) {\n    pingPrev.status = \"bad\";\n    msg.payload = {};\n    msg.payload.type = 'message';\n    msg.payload.content = `${msg.ping.name} (${msg.ping.host}) seems to be OFFLINE`;\n    msg.payload.chatId = chatId;\n    sendTelegram = true;\n}\n\n//update context\npingPrev.datetime = new Date();\npingPrev.host = msg.ping.host;\npingPrev.name = msg.ping.name;\npingMonitor[msg.ping.host] = pingPrev;\nflow.set(\"pingMonitor\", pingMonitor);\n\nif (sendTelegram) return msg;\n\nreturn null;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "x": 900,
        "y": 220,
        "wires": [
            [
                "37286037.e03f9"
            ]
        ]
    },
    {
        "id": "fb4b47cd.fa82a8",
        "type": "ping",
        "z": "337047ad.fcb858",
        "mode": "triggered",
        "name": "",
        "host": "",
        "timer": "20",
        "inputs": 1,
        "x": 750,
        "y": 400,
        "wires": [
            [
                "db694dbc.960ca"
            ]
        ]
    },
    {
        "id": "7e6ffb57.605f54",
        "type": "telegram command",
        "z": "337047ad.fcb858",
        "name": "",
        "command": "/ping",
        "bot": "d918e20d.f9968",
        "strict": false,
        "hasresponse": true,
        "useregex": true,
        "removeregexcommand": false,
        "outputs": 2,
        "x": 210,
        "y": 400,
        "wires": [
            [
                "f52ae2a8.a3715"
            ],
            []
        ]
    },
    {
        "id": "4b93459e.23e3cc",
        "type": "comment",
        "z": "337047ad.fcb858",
        "name": "Network Offline Devices",
        "info": "",
        "x": 260,
        "y": 320,
        "wires": []
    },
    {
        "id": "8e959977.43e508",
        "type": "Stackhero-MySQL",
        "z": "337047ad.fcb858",
        "server": "2fad9f54.9ef47",
        "name": "",
        "x": 450,
        "y": 220,
        "wires": [
            [
                "dc501b75.0d31d8"
            ]
        ]
    },
    {
        "id": "721b196e.c93328",
        "type": "function",
        "z": "337047ad.fcb858",
        "name": "",
        "func": "msg.topic = 'SELECT * FROM host WHERE active = 1';\nmsg.payload = {\"action\": \"Q\", \"query\" : \"SELECT * FROM host WHERE active = 1\"};\nreturn msg;\n\n",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "x": 280,
        "y": 220,
        "wires": [
            [
                "8e959977.43e508"
            ]
        ]
    },
    {
        "id": "13abd7df.36a758",
        "type": "inject",
        "z": "337047ad.fcb858",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "30",
        "crontab": "",
        "once": true,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 250,
        "y": 140,
        "wires": [
            [
                "721b196e.c93328"
            ]
        ]
    },
    {
        "id": "f52ae2a8.a3715",
        "type": "function",
        "z": "337047ad.fcb858",
        "name": "",
        "func": "msg.topic = 'SELECT * FROM host WHERE active = 1';\nmsg.payload = {\"action\": \"Q\", \"query\" : \"SELECT * FROM host WHERE active = 1\"};\nreturn msg;\n\n",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "x": 420,
        "y": 400,
        "wires": [
            [
                "296abbe8.a435d4"
            ]
        ]
    },
    {
        "id": "296abbe8.a435d4",
        "type": "Stackhero-MySQL",
        "z": "337047ad.fcb858",
        "server": "2fad9f54.9ef47",
        "name": "",
        "x": 570,
        "y": 400,
        "wires": [
            [
                "fb4b47cd.fa82a8",
                "33fa1e6a.529bf2"
            ]
        ]
    },
    {
        "id": "595da70a.e1c328",
        "type": "inject",
        "z": "337047ad.fcb858",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": true,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 250,
        "y": 360,
        "wires": [
            [
                "f52ae2a8.a3715"
            ]
        ]
    },
    {
        "id": "679da82f.ed7be8",
        "type": "comment",
        "z": "337047ad.fcb858",
        "name": "Set ping timer",
        "info": "",
        "x": 230,
        "y": 100,
        "wires": []
    },
    {
        "id": "37286037.e03f9",
        "type": "telegram sender",
        "z": "337047ad.fcb858",
        "name": "",
        "bot": "",
        "haserroroutput": false,
        "outputs": 1,
        "x": 1420,
        "y": 320,
        "wires": [
            []
        ]
    },
    {
        "id": "d918e20d.f9968",
        "type": "telegram bot",
        "botname": "@xxxxxxbot",
        "usernames": "",
        "chatids": "xxxxxxxxxx",
        "baseapiurl": "",
        "updatemode": "polling",
        "pollinterval": "300",
        "usesocks": false,
        "sockshost": "",
        "socksport": "",
        "socksusername": "",
        "sockspassword": "",
        "bothost": "",
        "botpath": "",
        "localbotport": "",
        "publicbotport": "",
        "privatekey": "",
        "certificate": "",
        "useselfsignedcertificate": false,
        "sslterminated": false,
        "verboselogging": false
    },
    {
        "id": "2fad9f54.9ef47",
        "type": "Stackhero-MySQL-Server",
        "name": "",
        "host": "192.168.1.250",
        "port": "3308",
        "tls": false,
        "database": "pinger"
    }
]

Try something like this,
move the payload and ping so they are in the payload, then join all messages into an array, passing on the joined messages after a certain amount of time, in the example the join is set to 10 seconds. Then in the function loop through array of responses and create the texted for the chat.

[{"id":"13abd7df.36a758","type":"inject","z":"337047ad.fcb858","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[{\"host\":\"192.168.1.100\",\"name\":\"one\"},{\"host\":\"192.168.1.43\",\"name\":\"two\"},{\"host\":\"192.168.1.44\",\"name\":\"three\"}]","payloadType":"json","x":230,"y":180,"wires":[["dc501b75.0d31d8"]]},{"id":"dc501b75.0d31d8","type":"ping","z":"337047ad.fcb858","mode":"triggered","name":"","host":"","timer":"1","inputs":1,"x":350,"y":180,"wires":[["6c16cfc9.56cee8"]]},{"id":"6c16cfc9.56cee8","type":"change","z":"337047ad.fcb858","name":"","rules":[{"t":"set","p":"hold","pt":"msg","to":"{}","tot":"json"},{"t":"move","p":"payload","pt":"msg","to":"hold.time","tot":"msg"},{"t":"move","p":"ping","pt":"msg","to":"hold.ping","tot":"msg"},{"t":"move","p":"hold","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":530,"y":180,"wires":[["c1caf483.74453"]]},{"id":"c1caf483.74453","type":"join","z":"337047ad.fcb858","name":"","mode":"custom","build":"array","property":"payload","propertyType":"msg","key":"payload.ping.name","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"10","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":710,"y":180,"wires":[["2e4bb18e.82e07e"]]},{"id":"2e4bb18e.82e07e","type":"function","z":"337047ad.fcb858","name":"Prepare telegram message","func":"const chatId = \"xxxxxxxx\"; // << update this\nvar sendTelegram = false;\nvar text =\"\";\nfor (const element of msg.payload){\nif (!element.ping.host) return null; //ensure host has a value\n\nvar pingMonitor = flow.get(\"pingMonitor\") || {};//get lookup object ||or|| a new empty object\nvar pingPrev = pingMonitor[element.ping.host] || {};//this ping monitor object ||or|| a new empty object\n\n\n//if ping is good, you get a number in payload...\nif (typeof element.time == \"number\" && pingPrev.status === \"bad\" ) {\n    pingPrev.status = \"good\";\n    text += `${element.ping.name} (${element.ping.host}) is now back online & operational!\\n`;\n    sendTelegram = true;\n} else if (element.time === false && (pingPrev.status === \"good\" || !pingPrev.status)) {\n    pingPrev.status = \"bad\";\n    text += `${element.ping.name} (${element.ping.host}) seems to be OFFLINE\\n`;\n    sendTelegram = true;\n}\n\n//update context\npingPrev.datetime = new Date();\npingPrev.host = element.ping.host;\npingPrev.name = element.ping.name;\npingMonitor[element.ping.host] = pingPrev;\nflow.set(\"pingMonitor\", pingMonitor);\n}\nif (sendTelegram) {\n    msg.payload = {};\n    msg.payload.type = 'message';\n    msg.payload.content = text;\n    msg.payload.chatId = chatId;    \n    \n    return msg;\n}\n\nreturn null;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":920,"y":180,"wires":[["d901eeaa.d0d01"]]},{"id":"d901eeaa.d0d01","type":"debug","z":"337047ad.fcb858","name":"Query DB","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1050,"y":120,"wires":[]}]

1 Like

Thank you again for your assistance.

It works really well!

Just one snag, when I manually inject or Telegram command ask what is "offline or online / status" I get no reply. I would like the system to tell me in one message what is online and what is offline depending on the ask: A - Online B - Offline, if that makes any sense.

Thank you again for looking into this.

The pingMonitor Context store holds all the states. In the function you should grab the data and then create a message from it.

1 Like

LOL, thank you, really appreciate that.

I just have no idea of how to do it :slight_smile:

something like this

[{"id":"d901eeaa.d0d01","type":"debug","z":"bd180d69.a493b","name":"Query DB","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":770,"y":260,"wires":[]},{"id":"2d12ae50.e71a3a","type":"function","z":"bd180d69.a493b","name":"","func":"var text = \"\";\nfor (const element of msg.payload){\n    text +=  `${element.datetime} - ${element.name} is ${element.status}\\n`;\n}\nmsg.payload =text;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":500,"y":260,"wires":[["d901eeaa.d0d01"]]},{"id":"f91c22c9.8a239","type":"change","z":"bd180d69.a493b","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"$flowContext(\"pingMonitor\").[$.*]","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":300,"y":260,"wires":[["2d12ae50.e71a3a"]]},{"id":"ac13358a.68dbf8","type":"inject","z":"bd180d69.a493b","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":100,"y":260,"wires":[["f91c22c9.8a239"]]}]

[edit] you will need to set a status on all hosts so add else statment to add ping.ststus.

var sendTelegram = false;
var text ="";
for (const element of msg.payload){
if (!element.ping.host) return null; //ensure host has a value

var pingMonitor = flow.get("pingMonitor") || {};//get lookup object ||or|| a new empty object
var pingPrev = pingMonitor[element.ping.host] || {};//this ping monitor object ||or|| a new empty object


//if ping is good, you get a number in payload...
if (typeof element.time == "number" && pingPrev.status === "bad" ) {
    pingPrev.status = "good";
    text += `${element.ping.name} (${element.ping.host}) is now back online & operational!\n`;
    sendTelegram = true;
} else if (element.time === false && (pingPrev.status === "good" || !pingPrev.status)) {
    pingPrev.status = "bad";
    text += `${element.ping.name} (${element.ping.host}) seems to be OFFLINE\n`;
    sendTelegram = true;
} else{
    pingPrev.status = "good";
}

//update context
pingPrev.datetime = new Date();
pingPrev.host = element.ping.host;
pingPrev.name = element.ping.name;
pingMonitor[element.ping.host] = pingPrev;
flow.set("pingMonitor", pingMonitor);
}
if (sendTelegram) {
    msg.payload = {};
    msg.payload.type = 'message';
    msg.payload.content = text;
    msg.payload.chatId = chatId;    
    
    return msg;
}

return null;

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.