introduce telegram-chat

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!)
This commit is contained in:
Christian Hesse 2023-01-30 16:08:00 +01:00
parent f666d2f8ff
commit 819c7294c6
9 changed files with 222 additions and 1 deletions

View File

@ -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)

View File

@ -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)

Binary file not shown.

Binary file not shown.

89
doc/telegram-chat.md Normal file
View File

@ -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)

View File

@ -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;

View File

@ -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

View File

@ -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"

120
telegram-chat Normal file
View File

@ -0,0 +1,120 @@
#!rsc by RouterOS
# RouterOS script: telegram-chat
# Copyright (c) 2023 Christian Hesse <mail@eworm.de>
# 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;
}
}
}