Buzz-In_bot (for your Christmas party)

I originally wrote this flow for my IoT students when we held informal "snap" revision-question sessions.

[{"id":"d1df51257f61bac2","type":"tab","label":"Buss_IN_bot","disabled":false,"info":"","env":[]},{"id":"9aa5bfe3fcb611c7","type":"switch","z":"d1df51257f61bac2","name":"Switch on message content","property":"payload.content","propertyType":"msg","rules":[{"t":"cont","v":"click-me","vt":"str"},{"t":"cont","v":"/click-me","vt":"str"},{"t":"cont","v":"/team","vt":"str"},{"t":"cont","v":"/start","vt":"str"},{"t":"cont","v":"/leave","vt":"str"},{"t":"cont","v":"/alexa","vt":"str"},{"t":"cont","v":"/","vt":"str"},{"t":"else"}],"checkall":"false","repair":false,"outputs":8,"x":200,"y":560,"wires":[["86cf7578d7b421f8"],["86cf7578d7b421f8"],["f1934b2a4480fab8"],["450c7f174d0366a6"],["2fcf4f21a0cb1b8c"],["103da8c4af7d2d36"],["8377b5808407f5a0"],["8288a4b249ef02c9"]]},{"id":"c5f023cf8caa8d41","type":"link out","z":"d1df51257f61bac2","name":"","mode":"link","links":["123a4d5dda2cef02"],"x":725,"y":640,"wires":[]},{"id":"18b25528393d196d","type":"inject","z":"d1df51257f61bac2","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":"0.5","topic":"","payload":"1","payloadType":"num","x":130,"y":140,"wires":[["7baf5f5dc045ab47"]]},{"id":"f1934b2a4480fab8","type":"function","z":"d1df51257f61bac2","name":"Show names of the team","func":"let players   = flow.get(\"players\");\nlet my_chatID = flow.get(\"chatID\");\n\nlet length = Object.keys(players).length;\n// let chatID = 0\nlet message = \"Names of team members...\\n\";\n\n// Loop through the array looking for \"active\" users\n// Make a list of users in the 'message' variable\nfor (let i=0; i < length; i++) {\n    let chatID = Object.keys(players)[i];\n    if (players[chatID].status == \"active\") {\n        let my_name = players[chatID].name; \n        message = message + my_name + \"\\n\";\n    }\n}\n\n// Format the message to send to the user via Telegram \nmsg.error = false;\nmsg.payload.content = message;\nmsg.payload.chatId = my_chatID;\nmsg.payload.type = \"message\";\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":510,"y":540,"wires":[["c3e44e07e924c808"]]},{"id":"450c7f174d0366a6","type":"function","z":"d1df51257f61bac2","name":"Welcome screen","func":"let chatID = flow.get(\"chatID\");\n\n// Assemble the content for the Welcome screen\nlet my_message = \"\\u{1F60A} \\u{1F60A} Welcome to Buzz-In bot \\u{1F60A} \\u{1F60A}\";\nmy_message = my_message + \"\\n\\nYour hi-tech personal voting panel\\nfor your 2023 Christmas Party.\\n\";\nmy_message = my_message + \"\\u{1F4C1} \\n \\u{1F4CA} \\n \\u{1F3AF} \\n \\u{1F40D}\";\nmy_message = my_message + \"\\n\\nEnter your name to register\"\nmy_message = my_message + \"\\nCommands are...\";\nmy_message = my_message + \"\\n/start - to restart your session\";\nmy_message = my_message + \"\\n/alexa,<device_name>set device\";\nmy_message = my_message + \"\\nDon't forget the comma after Alexa\";\nmy_message = my_message + \"\\n/team - see names of team members\";\nmy_message = my_message + \"\\n/leave - to leave the system\\n\\n\\n\";\n\n// Format the message to send to the user via Telegram \nmsg.payload = {\n        chatId: chatID, \n        type:\"message\",\n        content:my_message\n}\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":490,"y":460,"wires":[["8f9095813785d6ef","c3e44e07e924c808"]]},{"id":"86cf7578d7b421f8","type":"function","z":"d1df51257f61bac2","name":"Check if you are the first","func":"// If the triggered flag is false - let the message through, then set flag to true\n// If the triggered flag is true  - message doesn't go any further\nlet triggered = flow.get(\"triggered\") || false;\nif (triggered == false) {\n    flow.set(\"triggered\", true);\n    return msg;\n}\nreturn null;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":510,"y":500,"wires":[["35a61a013e5fcd98"]]},{"id":"7baf5f5dc045ab47","type":"function","z":"d1df51257f61bac2","name":"One phone","func":"// Array for team of players\n// New users will be dynamically added to the Javascript object \n// This is performed by the 'Add player to context' function node\n\n// Insert a dummy entry so the Javscript object exists\nlet players = {\n    1:{name: 'unknown', score: 0, status: 'inactive'}\n}\n\nflow.set(\"players\", players);\nflow.set(\"triggered\", false);\n\n// Set the default 'device_name' here for Alexa\nflow.set(\"AlexaLocation\", \"study\");\n\nreturn msg;\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":290,"y":140,"wires":[[]]},{"id":"2fcf4f21a0cb1b8c","type":"function","z":"d1df51257f61bac2","name":"Player /leave","func":"let chatID = msg.payload.chatId;\nlet players = flow.get(\"players\");\nlet AlexaLocation = flow.get(\"AlexaLocation\");\nmsg.device = AlexaLocation;\n\n// Check if the player is registered (i.e. their chatId is in the Javascript object )\n// Delete the row in the object if the player was found in the object\n// Send the appropriate message to Alexa\nif (players[chatID]) {\n    let my_name = players[chatID].name;\n    msg.payload = my_name + \"<break time='0.5s'/> has left the team\";\n    delete players[chatID];\n    flow.set(\"players\", players);\n    return msg;\n}\nelse{\n    msg.payload = \"This request has been ignored <break time='0.5s'/> as you are not registered <break time='0.5s'/> please enter your name\";\n    return msg;\n}\n\nreturn null;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":470,"y":580,"wires":[["c5f023cf8caa8d41"]]},{"id":"924d188917417787","type":"delay","z":"d1df51257f61bac2","name":"","pauseType":"delay","timeout":"8","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":880,"y":540,"wires":[["9305cf6b317d5f25"]]},{"id":"9939eb0010207359","type":"function","z":"d1df51257f61bac2","name":"Reset all keyboards","func":"let players = flow.get(\"players\");\n\n// Find out how many chatIds are in the Javascript object   \nlet length = Object.keys(players).length;\n// let chatID = 0\n\n// Loop around the player Javascript object\n// Send a Telegram keyboard to each \"active\" user\nfor (let i=0; i < length; i++) {\n    let chatID = Object.keys(players)[i];\n    if (players[chatID].status == \"active\") {\n        msg.payload = {};\n        let opts = {\n            reply_markup: JSON.stringify({\n                \"keyboard\": [[\"Click-me\"]],\n                'resize_keyboard' : true, \n                'one_time_keyboard' : true,\n                \"input_field_placeholder\": \"Enter name or click button\"\n            })\n        };\n        msg.error = false;\n        msg.payload.content = 'Get ready for the question';\n        msg.payload.options = opts;\n        msg.payload.chatId = chatID;\n        msg.payload.type = \"message\";\n        node.send(msg);\n    }\n}\n\nreturn null;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":1080,"y":500,"wires":[["c3e44e07e924c808"]]},{"id":"8dcfde95a4527737","type":"delay","z":"d1df51257f61bac2","name":"","pauseType":"rate","timeout":"101","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"5","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"allowrate":false,"outputs":1,"x":560,"y":300,"wires":[["9fa63f4669190648"]]},{"id":"20d609d7ea90b3fe","type":"comment","z":"d1df51257f61bac2","name":"Link to Alexa","info":"","x":770,"y":600,"wires":[]},{"id":"4eb659d40c2eb6a4","type":"comment","z":"d1df51257f61bac2","name":"Buzz-In bot v7 (18th Dec 2023) >>See inside for changes","info":"The nested Javascript object is now dynamic\nand will add a new row when a user enters their name.\n\nThe /leave command will now delete the row in the nested Javascript object.\n\nIf a player, who is not registered, does 'Click-me'\nthe system (i.e. Alexa) will ask them to register.\n\nChanged keyboard format so initial keyboard asks for username.\n\nAdded detailed comments so my IoT students should\nbe able to understand the code in the flow.","x":270,"y":80,"wires":[]},{"id":"103da8c4af7d2d36","type":"function","z":"d1df51257f61bac2","name":"Set Alexa,<device_name>","func":"// Split the response string\n// Use the second part as the 'device_name' for Alexa\nlet response = msg.payload.content;\nresponse = response.split(\",\");\nlet device_name = response[1];\n\nflow.set(\"AlexaLocation\",device_name);\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":510,"y":620,"wires":[[]]},{"id":"9fb2958d6ff2e1cb","type":"debug","z":"d1df51257f61bac2","name":"Catch all","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":280,"y":200,"wires":[]},{"id":"ef4c0373043add8e","type":"catch","z":"d1df51257f61bac2","name":"","scope":null,"uncaught":false,"x":120,"y":200,"wires":[["9fb2958d6ff2e1cb"]]},{"id":"8288a4b249ef02c9","type":"function","z":"d1df51257f61bac2","name":"Add player to context","func":"// get players from flow variable or set dummy entry\nlet players = flow.get(\"players\") ||  {1: {\"name\": \"unknown\", \"score\": 0, \"status\": \"inactive\"}};\n\n// get chatId of incoming msg\nlet id = msg.payload.chatId\n\n// if chatId not in players object then add an entry for it\nif (players[id] === undefined) {\n    players[id] = {}\n}\n\n// fill in the player info and activate it\nlet my_name   = msg.payload.content;\n//players[id].name = my_name\n//players[id].score = 0\n//players[id].status = \"active\"\n\nplayers[id] = {\"name\": my_name,\n               \"score\": 0, \n               \"status\": \"active\"\n};\n\n// Send a message to Alexa saying a new player has joined the Team\nlet AlexaLocation = flow.get(\"AlexaLocation\");\nmsg.device = AlexaLocation;\nmsg.payload = my_name + \"<break time='0.5s'/> has joined the team\";\n\n// write out players to context\nflow.set(\"players\", players);\n\nreturn msg;\n\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":500,"y":700,"wires":[["c5f023cf8caa8d41","3fe83f96f6c6c038"]]},{"id":"35a61a013e5fcd98","type":"function","z":"d1df51257f61bac2","name":"process","func":"let chatID = msg.payload.chatId;\n\nlet players = flow.get(\"players\");\nlet AlexaLocation = flow.get(\"AlexaLocation\");\nmsg.device = AlexaLocation;\n\n// Check if player is registered\nif (players[chatID]) {\n    let my_name = players[chatID].name;\n    msg.payload = \"Well done \"+ my_name + \"<break time='0.5s'/>you were first\";\n}\nelse {\n    msg.payload = \"Well done <break time='0.5s'/> you were first <break time='0.5s'/> however you are not registered <break time='0.5s'/> please enter your name\";\n}\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":720,"y":500,"wires":[["c5f023cf8caa8d41","924d188917417787","bf2444d75e55327b"]]},{"id":"9305cf6b317d5f25","type":"change","z":"d1df51257f61bac2","name":"Reset triggered flag","rules":[{"t":"set","p":"triggered","pt":"flow","to":"false","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":1080,"y":540,"wires":[[]]},{"id":"9fa63f4669190648","type":"change","z":"d1df51257f61bac2","name":"set msg.payload to lowercase","rules":[{"t":"set","p":"payload.content","pt":"msg","to":"$lowercase(payload.content)\t","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":210,"y":400,"wires":[["9aa5bfe3fcb611c7"]]},{"id":"3fe83f96f6c6c038","type":"function","z":"d1df51257f61bac2","name":"Send user registered keyboard","func":"// Send a Telegram keyboard to the newly registerd user\nlet chatID = flow.get(\"chatID\");\n\nmsg.payload = {};\nlet opts = {\n  reply_markup: JSON.stringify({\n    \"keyboard\": [[\"Click-me\"]],\n    'resize_keyboard' : true, \n    'one_time_keyboard' : true,\n    \"input_field_placeholder\": \"Enter name or click button\"\n  })\n};\nmsg.error = false;\nmsg.payload.content = 'Get ready for the question';\nmsg.payload.options = opts;\nmsg.payload.chatId = chatID;\nmsg.payload.type = \"message\";\nreturn  msg;\n","outputs":"1","timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":770,"y":700,"wires":[["c3e44e07e924c808"]]},{"id":"a77ca7c449de55e6","type":"function","z":"d1df51257f61bac2","name":"Enter name to register","func":"// Send a message to the user via Telegram asking them to enter their name (i.e. register) \nlet chatID = flow.get(\"chatID\");\n\nlet message = 'Enter your name to register.';\nmsg.payload = {chatId : chatID, type : 'message', content : message};\n\n// activate markdown\nmsg.payload.options = {disable_web_page_preview : true, parse_mode : \"Markdown\"};\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":920,"y":460,"wires":[["c3e44e07e924c808"]]},{"id":"8f9095813785d6ef","type":"delay","z":"d1df51257f61bac2","name":"","pauseType":"delay","timeout":"1","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":720,"y":460,"wires":[["a77ca7c449de55e6"]]},{"id":"bf2444d75e55327b","type":"delay","z":"d1df51257f61bac2","name":"","pauseType":"delay","timeout":"2","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":880,"y":500,"wires":[["9939eb0010207359"]]},{"id":"9e62f06e52ab3a64","type":"change","z":"d1df51257f61bac2","name":"Save chatID","rules":[{"t":"set","p":"chatID","pt":"flow","to":"payload.chatId","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":370,"y":300,"wires":[["8dcfde95a4527737"]]},{"id":"4e9f6cd051e8bcb5","type":"comment","z":"d1df51257f61bac2","name":"Telegram >>> Input to Bot","info":"","x":170,"y":280,"wires":[]},{"id":"ede70afe09cde994","type":"comment","z":"d1df51257f61bac2","name":"Output from Bot >>> Telegram","info":"","x":1060,"y":260,"wires":[]},{"id":"8377b5808407f5a0","type":"function","z":"d1df51257f61bac2","name":"Unknown command","func":"\n\n// Send a message to Alexa saying a new player has joined the Team\nlet AlexaLocation = flow.get(\"AlexaLocation\");\nmsg.device = AlexaLocation;\nmsg.payload = \"You have entered an unknown command<break time='0.5s'/> did you mean to enter a slash character<break time='0.5s'/> if you want to enter your name <break time='0.5s'/> just enter text characters without a slash \";\nreturn msg;\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":490,"y":660,"wires":[["c5f023cf8caa8d41"]]},{"id":"c3e44e07e924c808","type":"link out","z":"d1df51257f61bac2","name":"Send_to_Telegram","mode":"link","links":["d2e38eea5b23aaca"],"x":795,"y":300,"wires":[]},{"id":"d2e38eea5b23aaca","type":"link in","z":"d1df51257f61bac2","name":"Telegram_send_IN","links":["c3e44e07e924c808"],"x":875,"y":300,"wires":[["0b93329b949d42f8"]]},{"id":"e5ebca5ee5b64f99","type":"telegram receiver","z":"d1df51257f61bac2","name":"","bot":"","saveDataDir":"","filterCommands":false,"x":150,"y":320,"wires":[["9e62f06e52ab3a64"],[]]},{"id":"0b93329b949d42f8","type":"telegram sender","z":"d1df51257f61bac2","name":"","bot":"","haserroroutput":false,"outputs":1,"x":1030,"y":300,"wires":[[]]}]

You should be able to use it when you run your games sessions at your Christmas Party as it should avoid the disagreements, fights or even personal injury when trying to decide who answered first.

Apart from the normal nodes.the flow uses 'node-red-contrib-telegrambot' and an 'Alexa' node. I normally place one of my Amazon Echo-Dots in the centre of the table where we play the game.

Buzz_IN_bot_A

Here's the settings you need inside the 'Routine Speak' node.
Buzz_IN_bot_B

You will need to set-up a Telegram bot on your Telegram account and use the 'Telegram receiver' and 'Telegram sender' nodes in your flow.

This is what will appear when you enter /start on your mobile-phone.
Buzz_IN_bot_C
As the names of the commands suggest, running the 'Bot' is fairly simple. The only one that is a bit tricky is changing the name of the Alexa device that is used to tell you who was first.
The command format is... /alexa,<device_name>
e.g. /alexa,study
People sometimes leave out the comma and then wonder why it didn't work.

To save time, you can setup the default name for your Alexa device in the 'One phone' function node.
Buzz_IN_bot_E

When you have registered, by entering your name/nickname, you should see this...
Buzz_IN_bot_D

EDIT:
If you want to make your Welcome screen look more festive try this...
Buzz_IN_bot_F
Change the appropriate line in the 'Welcome screen' function node, to something like this...

my_message = my_message + "\u{1F384} \u{1F384} \u{2603} \u{1F385} \u{2603} \u{1F384} \u{1F384} ";

I hope you and your family have some fun with this mini-project.

Happy Christmas from DynamicDave.

7 Likes

Looks like an interesting little project.

However I can see the arguments starting due to perceived "lag" issues :wink:

1 Like

Not had any problems with latency when I used it at school and at home. My students and the family thought it was great fun especially when using a silly (or maybe a rude) nickname (say no more).

1 Like

Just thinking it could be interesting to make something with actual buttons connected to one of my spare S1-Minis. :thinking:

1 Like

I think we are too late to corner the games market this year :rofl:

Oh hiss boo - I was hoping to make my "Ā£Ā£Ā£ millions" with it. And here I am "giving" it away (just crazy).

1 Like