From 819c7294c62c84fbc59bbf16a7d9ce97b4957e57 Mon Sep 17 00:00:00 2001 From: Christian Hesse Date: Mon, 30 Jan 2023 16:08:00 +0100 Subject: introduce telegram-chat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Druvis from Mikrotik produced a video "MikroTik Telegram bot - Chat with your Router?". He shows his script to chat with a Router via Telegram bot to send it commands: https://youtu.be/KLX6j3sLRIE This script is kind of limited and has several issues... 🥴 Let's make it robust, usable, multi-device capable and just fun! 😁 (Sadly Mikrotik has a policy to not allow links in Youtube comments. Thus my comment with several hints was removed immediately. If anybody is in contact with Druvis... Please tell him about this script!) --- README.md | 1 + doc/mod/notification-telegram.md | 1 + doc/telegram-chat.d/01-chat-specific.avif | Bin 0 -> 31869 bytes doc/telegram-chat.d/02-chat-all.avif | Bin 0 -> 48099 bytes doc/telegram-chat.md | 89 ++++++++++++++++++++++ global-config | 8 ++ global-config.changes | 1 + global-functions | 3 +- telegram-chat | 120 ++++++++++++++++++++++++++++++ 9 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 doc/telegram-chat.d/01-chat-specific.avif create mode 100644 doc/telegram-chat.d/02-chat-all.avif create mode 100644 doc/telegram-chat.md create mode 100644 telegram-chat diff --git a/README.md b/README.md index a5a2894..42f897b 100644 --- a/README.md +++ b/README.md @@ -202,6 +202,7 @@ Available scripts * [Forward received SMS](doc/sms-forward.md) * [Import SSH keys](doc/ssh-keys-import.md) * [Play Super Mario theme](doc/super-mario-theme.md) +* [Chat with your router and send commands via Telegram bot](doc/telegram-chat.md) * [Install LTE firmware upgrade](doc/unattended-lte-firmware-upgrade.md) * [Update GRE configuration with dynamic addresses](doc/update-gre-address.md) * [Update tunnelbroker configuration](doc/update-tunnelbroker.md) diff --git a/doc/mod/notification-telegram.md b/doc/mod/notification-telegram.md index b55739a..5e6c1a0 100644 --- a/doc/mod/notification-telegram.md +++ b/doc/mod/notification-telegram.md @@ -66,6 +66,7 @@ methods: See also -------- +* [Chat with your router and send commands via Telegram bot](../telegram-chat.md) * [Send notifications via e-mail](notification-email.md) * [Send notifications via Matrix](notification-matrix.md) diff --git a/doc/telegram-chat.d/01-chat-specific.avif b/doc/telegram-chat.d/01-chat-specific.avif new file mode 100644 index 0000000..387dc3a Binary files /dev/null and b/doc/telegram-chat.d/01-chat-specific.avif differ diff --git a/doc/telegram-chat.d/02-chat-all.avif b/doc/telegram-chat.d/02-chat-all.avif new file mode 100644 index 0000000..32fc181 Binary files /dev/null and b/doc/telegram-chat.d/02-chat-all.avif differ diff --git a/doc/telegram-chat.md b/doc/telegram-chat.md new file mode 100644 index 0000000..b245efa --- /dev/null +++ b/doc/telegram-chat.md @@ -0,0 +1,89 @@ +Chat with your router and send commands via Telegram bot +======================================================== + +[⬅️ Go back to main README](../README.md) + +> ℹ️ **Info**: This script can not be used on its own but requires the base +> installation. See [main README](../README.md) for details. + +Description +----------- + +This script makes your device poll a Telegram bot for new messages. With +these messages you can send commands to your device and make it run them. +The resulting output is send back to you. + +Requirements and installation +----------------------------- + +Just install the script and the module for notifications via Telegram: + + $ScriptInstallUpdate telegram-chat,mod/notification-telegram; + +Then create a schedule that runs the script periodically: + + /system/scheduler/add start-time=startup interval=30s name=telegram-chat on-event="/system/script/run telegram-chat;"; + +> ⚠️ **Warning**: Make sure to keep the interval in sync when installing +> on several devices. Differing polling intervals will result in missed +> messages. + +Configuration +------------- + +Make sure to configure +[notifications via telegram](mod/notification-telegram.md) first. The +additional configuration goes to `global-config-overlay`, these are the +parameters: + +* `TelegramChatIdsTrusted`: an array with trusted chat ids or user names +* `TelegramChatGroups`: define the groups a device should belong to + +Usage and invocation +-------------------- + +This script is capable of chatting with multiple devices. By default a +device is passive and not acting on messages. To activate it send a message +containing `! identity` (exclamation mark, optional space and system's +identity). To query all dynamic ip addresses form a device named "*MikroTik*" +send `! MikroTik`, followed by `/ip/address/print where dynamic;`. + +![chat to specific device](telegram-chat.d/01-chat-specific.avif) + +Devices can be grouped to chat with them simultaneously. The default group +"*all*" can be activated by sending `! @all`, which will make all devices +act on your commands. + +![chat to all devices](telegram-chat.d/02-chat-all.avif) + +Send a single exclamation mark or non-existent identity to make all +devices passive again. + +Known limitations +----------------- + +### Do not use numeric ids! + +Numeric ids are valid within a session only. Usually you can use something +like this to print all ip addresses and remove the first one: + + /ip/address/print; + /ip/address/remove 0; + +This will fail when sent in separate messages. Instead you should use basic +scripting capabilities. Try to print what you want to act on... + + /ip/address/print where interface=eth; + +... verify and finally remove it. + + /ip/address/remove [ find where interface=eth ]; + +See also +-------- + +* [Send notifications via Telegram](mod/notification-telegram.md) + +--- +[⬅️ Go back to main README](../README.md) +[⬆️ Go back to top](#top) diff --git a/global-config b/global-config index dcd7a09..4b5338a 100644 --- a/global-config +++ b/global-config @@ -35,6 +35,14 @@ :global TelegramChatId ""; #:global TelegramTokenId "123456:ABCDEF-GHI"; #:global TelegramChatId "12345678"; +# Using telegram-chat you have to define trusted chat ids (not group ids!) +# or user names. Groups allow to chat with devices simultaneously. +#:global TelegramChatIdsTrusted { +# "12345678"; +# "example_user"; +#}; +:global TelegramChatGroups "(all)"; +#:global TelegramChatGroups "(all|home|office)"; # This is whether or not to send Telegram messages with fixed-width font. :global TelegramFixedWidthFont true; diff --git a/global-config.changes b/global-config.changes index 2ae335e..94da56c 100644 --- a/global-config.changes +++ b/global-config.changes @@ -98,6 +98,7 @@ 87="Added support for extra text (or emojis \F0\9F\9A\80) in notification tags."; 88="Added support for monitoring CPU load and available free RAM in 'check-health'."; 89="Made the warning time for 'check-certificates' configurable."; + 90="Chat with your router! Introduced 'telegram-chat' to chat via Telegram bot and send commands to your router."; }; # Migration steps to be applied on script updates diff --git a/global-functions b/global-functions index 165ac9a..7fe3873 100644 --- a/global-functions +++ b/global-functions @@ -12,7 +12,7 @@ :local 0 "global-functions"; # expected configuration version -:global ExpectedConfigVersion 89; +:global ExpectedConfigVersion 90; # global variables not to be changed by user :global GlobalFunctionsReady false; @@ -1101,6 +1101,7 @@ "pushpin"="\F0\9F\93\8C"; "scissors"="\E2\9C\82"; "sparkles"="\E2\9C\A8"; + "speech-balloon"="\F0\9F\92\AC"; "up-arrow"="\E2\AC\86"; "warning-sign"="\E2\9A\A0"; "white-heavy-check-mark"="\E2\9C\85" diff --git a/telegram-chat b/telegram-chat new file mode 100644 index 0000000..9c8261e --- /dev/null +++ b/telegram-chat @@ -0,0 +1,120 @@ +#!rsc by RouterOS +# RouterOS script: telegram-chat +# Copyright (c) 2023 Christian Hesse +# https://git.eworm.de/cgit/routeros-scripts/about/COPYING.md +# +# use Telegram to chat with your Router and send commands +# https://git.eworm.de/cgit/routeros-scripts/about/doc/telegram-chat.md + +:local 0 "telegram-chat"; +:global GlobalFunctionsReady; +:while ($GlobalFunctionsReady != true) do={ :delay 500ms; } + +:global Identity; +:global TelegramChatActive; +:global TelegramChatGroups; +:global TelegramChatId; +:global TelegramChatIdsTrusted; +:global TelegramChatOffset; +:global TelegramTokenId; + +:global CertificateAvailable; +:global EscapeForRegEx; +:global GetRandom20CharAlNum; +:global IfThenElse; +:global LogPrintExit2; +:global MkDir; +:global ScriptLock; +:global SendTelegram2; +:global SymbolForNotification; +:global ValidateSyntax; +:global WaitForFile; +:global WaitFullyConnected; + +$ScriptLock $0; + +$WaitFullyConnected; + +:if ([ :typeof $TelegramChatOffset ] != "num") do={ + :set TelegramChatOffset 0; +} + +:if ([ $CertificateAvailable "Go Daddy Secure Certificate Authority - G2" ] = false) do={ + $LogPrintExit2 warning $0 ("Downloading required certificate failed.") true; +} + +:local JsonGetKey do={ + :local Array [ :toarray $1 ]; + :local Key [ :tostr $2 ]; + + :for I from=0 to=([ :len $Array ] - 1) do={ + :if (($Array->$I) = $Key) do={ + :if ($Array->($I + 1) = ":") do={ + :return ($Array->($I + 2)); + } + :return [ :pick ($Array->($I + 1)) 1 [ :len ($Array->($I + 1)) ] ]; + } + } + + :return false; +} + +:local Data; +:do { + :set Data ([ /tool/fetch check-certificate=yes-without-crl output=user \ + ("https://api.telegram.org/bot" . $TelegramTokenId . "/getUpdates?offset=" . \ + $TelegramChatOffset . "&allowed_updates=%5B%22message%22%5D") as-value ]->"data"); + :set Data [ :pick $Data ([ :find $Data "[" ] + 1) ([ :len $Data ] - 2) ]; +} on-error={ + $LogPrintExit2 info $0 ("Failed getting updates from Telegram.") true; +} + +:foreach Update in=[ :toarray $Data ] do={ + :local UpdateID [ $JsonGetKey $Update "update_id" ]; + :if ($UpdateID >= $TelegramChatOffset) do={ + :set TelegramChatOffset ($UpdateID + 1); + :local Trusted false; + :local Message [ $JsonGetKey $Update "message" ]; + :local From [ $JsonGetKey $Message "from" ]; + :local FromID [ $JsonGetKey $From "id" ]; + :local FromUserName [ $JsonGetKey $From "username" ]; + :foreach IdsTrusted in=($TelegramChatId, $TelegramChatIdsTrusted) do={ + :if ($FromID = $IdsTrusted || $FromUserName = $IdsTrusted) do={ + :set Trusted true; + } + } + + :if ($Trusted = true) do={ + :local Text [ $JsonGetKey $Message "text" ]; + :if ([ :pick $Text 0 1 ] = "!") do={ + :if ($Text ~ ("^! *(" . [ $EscapeForRegEx $Identity ] . "|@" . $TelegramChatGroups . ")\$")) do={ + :set TelegramChatActive true; + } else={ + :set TelegramChatActive false; + } + $LogPrintExit2 info $0 ("Now " . [ $IfThenElse $TelegramChatActive "active" "passive" ] . "!") false; + } else={ + :if ($TelegramChatActive = true && [ :len $Text ] > 0) do={ + :if ([ $ValidateSyntax $Text ] = true) do={ + :local File ("tmpfs/telegram-chat/" . [ $GetRandom20CharAlNum 6 ]); + $MkDir "tmpfs/telegram-chat"; + $LogPrintExit2 info $0 ("Running command: " . $Text) false; + :exec script=($Text . "; :execute script=\":put\" file=" . $File . ".done") file=$File; + :if ([ $WaitForFile ($File . ".done.txt") 200 ] = false) do={ + $LogPrintExit2 warning $0 ("Command did not finish, possibly still running.") false; + } + :local Content [ /file/get ($File . ".txt") content ]; + $SendTelegram2 ({ origin=$0; silent=false; \ + subject=([ $SymbolForNotification "speech-balloon" ] . "Telegram Chat"); \ + message=("Command:\n" . $Text . "\n\nOutput:\n" . $Content) }); + /file/remove "tmpfs/telegram-chat"; + } else={ + $LogPrintExit2 warning $0 ("The command failed syntax validation: " . $Text) false; + } + } + } + } else={ + $LogPrintExit2 warning $0 ("Received a message from untrusted contact '" . $FromUserName . "' (ID " . $FromID . ")!") false; + } + } +} -- cgit v1.2.3-54-g00ecf