Скрипт уведомлений и исполнения команд через Телеграмм для РоутерОС Микротик

от автора

Мессенджер Телеграмм полагаю представлять не нужно. В сравнении с другими существующими мессенджерами, Телеграмм выгодно отличается тем, что позволяет создавать боты, которые могут участвовать в чатах. Это удобно во многих случаях, в том числе для разработчиков программного обеспечения и продвинутых пользователей.

Применительно к работе с роутерами Микротик, Телеграмм удобен тем, что в чат-бот можно организовать пересылку сообщения от роутера, а пользуясь возможностями парсинга чата через API Telegram, пересылать исполняемые команды.

Как создать свой бот, чат и «прикрутить» к ним Микротик можно прочитать например здесь или здесь.

За время появления Телеграмм был создан далеко не один скрипт по уведомлению о событиях роутера Микротик, в том числе как зарубежными так и отечественными пользователями энтузиастами. Например, здесь или в этой теме на русскоязычном форуме Микротик.

Мы объединили накопленные за это время идеи и код в единый скрипт под названием TLGRM, который хотим представить здесь.

TLGRM — это обкатанная, но постоянно дорабатываемая версия скрипта, объединившего в себе пересылку важных и критических событий из лога роутера, возможность запуска скриптов по именам, а также новейшие (для скрипта) оригинальные возможности: запуск функций из окружения переменных, в том числе с параметрами, и прямое выполнение команд РоутерОС. Вероятно, последние две возможности применены авторами впервые (по крайней мере скрипты с аналогичными возможностями нам до сих пор не были известны).

Итак, TLGRM — комбинированный скрипт оповещения в Телеграм и удалённого запуска функций, скриптов и команд в RouterOS.

В скрипте использованы идеи и часть кода нескольких авторов: Sertik, Virtue, Pepelxl, Dimonw, Jotne, Alice Tails, drPioneer (никнеймы авторов на русскоязычном и англоязычном форумах Микротик).

Для работы скрипта должны быть настроены BotID и ChatID, того чата, который будет «слушать» и обрабатывать TLGRM. Скрипт необходимо добавить в System/Sheduler и установить запуск с нужной периодичностью (оптимально 1 минута).

Скрипт выполняет две основные задачи:

  1. Чтение в Телеграм-группе последнего сообщения на предмет наличия адресованного роутеру или группе роутеров послания. При наличии такого послания скрипт пытается разобрать его и исполнить.
  2. Поиск в журнале устройства сообщений о важных или подозрительных событиях и их пересылка в Телеграм-группу. В том числе пересылаются записи журнала, выполненные как warning и error (отображаемые в логе синим и красным цветом).

Работа пользователя со скриптом:

Для отправки команды конкретному роутеру в Телеграм-группе, пользователю необходимо сформировать сообщение:

Начало сообщения обозначается символом «/», далее указывается имя роутера (должно соответствовать записи в /system identity, при этом регистр букв имеет значение, и не должно содержать пробелов), затем через пробел пишется команда.

В качестве команды могут выступать: имя глобальной функции установленной в окружение, имя скрипта репозитория роутера пользователя, команда RouterOS. В том числе поддерживаются вложенные конструкции.

Примеры:

/Mikrotik1 funcarp par1 par2  /Mikrotik2 scriptwol /Mikrotik3 log warning [/system identity get name]

Если необходимо отправить команду всем роутерам, слушающим данный чат, необходимо вместо identity конкретного роутера написать /forall.

Например:

 /forall system reboot

или, допустим

/forall backup

для того, чтобы выполнить скрипт backup — сохранения конфигурации на всех роутерах группы (для этого в репозитории роутеров должен быть искомый скрипт).

Особенности работы скрипта TLGRM:

  • поддерживается отправка сообщений в Телеграм-группу с кириллицей
  • непечатные символы при отправке в Телеграм-группу обрезаются
  • сообщение длиной более 4096 перед отправкой в Телеграм-группу обрезается до 4096 символов
  • скрипт читает только последнее сообщение в Телеграм-группе. По этой причине не имеет смысла подавать в чате сразу много команд, в любом случае будет выполнена только последняя.
  • теперь поддерживается индивидуальная и групповая адресация команд
  • при запуске скрипта в терминале можно наблюдать за ходом его выполнения

Дополнительно можно настроить локальные флаги launch в начале скрипта, которые указывают разрешено ли распознавание и выполнение скриптом скриптов, функций и/или прямых команд РоутерОС соответственно (по умолчанию все данные опции разрешены). Если какие-то функции не нужны, установите соответствующие флаги в false.

    :local launchScr true;     :local launchFnc true;     :local launchCmd true;

Содержание и алгоритм работы TLGRM.

Скрипт TLGRM состоит из нескольких блоков и локальных функций: главный блок, блок парсинга ответов Телеграм, функция поиска комментария по MAC-адресу, функция конвертер русского текста в UTF8 формат, функции вычисления Юлианского времени и функции обратного преобразования времени UNIX в привычные форматы. Все блоки и их авторы отмечены непосредственно в тексте скрипта TLGRM.

При запуске управление передаётся главному блоку скрипта, который на первом этапе обменивается данными с Телеграм-сервером и пытается произвести активацию предназначенных ему команд. В нём скрипт формирует запрос с указанными пользователем botID и chatID и отправляет его на Телеграм-сервер.

В ответ Телеграм-сервер возвращает запрошенную информацию в JSON-формате.

Для извлечения требуемой информации скрипт использует вышеупомянутую локальную функцию парсинга ответов Телеграм — функцию-парсер MsgParser. Код функции был опубликован здесь. На вход функции подается JSON-последовательность и ключ, а функция возвращает найденное значение, соответствующее запрошенному ключу.

Таким образом, за несколько обращений к этой функции скрипт получает и накапливает информацию:

  • имя отправителя сообщения,
  • время отправки сообщения,
  • id-чата,
  • текст сообщения.

На основе этой информации принимается решение о необходимости обработки текста сообщения и дальнейшем исполнении распознанных в тексте сообщения команд.

Осуществляется синхронизация времени предыдущего и настоящего обращения к скрипту, для избежания повторных ложных срабатываний.

Как уже было сказано здесь TLGRM может исполнить скрипт из репозитория пользователя, активную функцию из окружения переменных, в том числе передав её необходимые позиционные и/или именные параметры, либо прямую команду РоутерОС.

Для примера вот этот важный фрагмент скрипта. Обратите внимание на использование для составления и исполнения команды :parse, заключенной в составе необходимой конструкции в квадратные скобки):

:local restline   []; :if ([:len [:find $msgTxt " "]] !=0) do={ :set params [:pick $msgTxt  ([:find $msgTxt " "] +1) [:len $msgTxt]]; :set msgTxt [:pick $msgTxt 0 [:find $msgTxt " "]];              }                 :if ($chatId=$myChatID && $timeAct<$timeStamp) do={   :set timeAct ($timeStamp);    :if ([/system script environment find name=$msgTxt;]!="" \  && $launchFnc=true) do={ :put ("$[$UnixTimeToFormat ([$EpochTime] + $timeOf) 1] - Telegram user \ $userName launches function '$msgTxt'."); :log warning ("Telegram user $userName launches function '$msgTxt'.");  [:parse ":global $msgTxt; [\$$msgTxt $restline]"];      }    :if ([/system script find name=$msgTxt;]!="" && $launchScr=true) do={    :put ("$[$UnixTimeToFormat ([$EpochTime] + $timeOf) 1] - Telegram user $userName activates script '$msgTxt'.");  :log warning ("Telegram user $userName activates script '$msgTxt'.");  /system script run $msgTxt;       } :if ([/system script find name=$msgTxt;]="" && [/system script environment find name=$msgTxt;]="" && $launchCmd=true) do={ :put ("$[$UnixTimeToFormat ([$EpochTime] + $timeOf) 1] - Telegram user $userName is trying to execute command '$msgTxt'."); :log warning ("Telegram user $userName is trying to execute command '$msgTxt'."); :do {[:parse "/$msgTxt $restline"]} on-error={}        } } else={ :put ("$[$UnixTimeToFormat ([$EpochTime] + $timeOf) 1] - Wrong time to launch."); } } else={ :put ("$[$UnixTimeToFormat ([$EpochTime] + $timeOf) 1] - No command found for this device."); } } else={ :put ("$[$UnixTimeToFormat ([$EpochTime] + $timeOf) 1] - Completion of response from Telegram."); }

В случае с исполнением глобальной функции, она предварительно декларируется, а в переменной $restline содержатся следующие за именем параметры функции (если они переданы).

В случае же с передачей команды RouterOS в $restline попадет весь остаток строки парсинга после первой команды и вместе с последней фактически передается строка как есть целиком. Это позволяет исполнять практически любые команды ROS и даже сложные конструкции. Более сложные алгоритмы, разумеется целесообразно вынести в отдельные скрипты, также доступные для исполнения по имени через TLGRM.

После исполнения распознанных команд следует выполнение «информационного» блока скрипта, отвечающего за формирование сообщения и отправки его в Телеграм-чат.
Эта часть скрипта использует позаимствованную отсюда логику.

В журнале устройства производится поиск записей обо всех подозрительных событиях.

($topics ~"warning" || $topics ~"error" || $topics ~"critical" || $topics ~"caps" || $topics ~"wireless" || $message ~"logged in")

а также

($tempTopic ~"caps" || $tempTopic ~"wireless" || $tempTopic ~"dhcp")

(список логируемых записей можно самостоятельно расширить, добавив соответствующие «топикс»)

Если такие записи находятся, тогда формируется и отправляется запрос на Телеграм-сервер с целью отправки сообщения в Телеграм-чат.

Перед формированием запроса серверу производится обработка найденных записей:

  • записи пропускаются через функцию FindMacAddr (использована идея), которая пытается к обнаруженным MAC-адресам добавить текстовые комментарии для облегчения понимания пользователем, о каких хостах идет речь.
  • если текст сообщения длиннее 4096 символов, производится его обрезание до 4096 символов (ограничение Telegram API: tlgrm.ru/docs/bots/api).
  • текст пропускается через функцию-конвертер ‘CP1251toUTF8‘, которая производит обработку символов кириллицы и спецсимволов для правильного отображения в Телеграм.
  • так как Телеграм работает со временем в формате UnixTime, в скрипте используются функции EpochTime и UnixTimeToFormat для трансляции времени роутера в вид, понятный Телеграм и обратно.

На этом работа скрипта завершается до следующего запуска через Планировщик.

Под спойлером полный текст TLGRM (по состоянию на 08.02.2022 г.)

# TLGRM - combined notifications script & launch of commands (scripts & functions) via Telegram # Script uses ideas by Sertik, Virtue, Pepelxl, Dimonw, Jotne, Alice Tails, drPioneer. # https://forummikrotik.ru/viewtopic.php?p=81945#p81945 # tested on ROS 6.49 # updated 2022/02/08  :do {     :local botID    "botXXXXXXXXXX:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";     :local myChatID "-XXXXXXXXX";     :local launchScr true;     :local launchFnc true;     :local launchCmd true;      # Function of searching comments for MAC-address     # https://forummikrotik.ru/viewtopic.php?p=73994#p73994     :local FindMacAddr do={         :if ($1 ~"[0-F][0-F]:[0-F][0-F]:[0-F][0-F]:[0-F][0-F]:[0-F][0-F]:[0-F][0-F]") do={             :foreach idx in=[ /ip dhcp-server lease find dynamic=no disabled=no; ] do={                 :local mac  [ /ip dhcp-server lease get $idx mac-address; ];                 :if ($1 ~"$mac") do={                      :return ("$1 [$[ /ip dhcp-server lease get $idx address; ]/\                         $[ /ip dhcp-server lease get $idx comment; ]].");                  }              }             :foreach idx in=[ /interface bridge host find; ] do={                 :local mac  [ /interface bridge host get $idx mac-address; ];                 :if ($1 ~"$mac") do={ :return ("$1 [$[ /interface bridge host get $idx on-interface; ]]."); }             }         }         :return ($1);     }      # Function of converting CP1251 to UTF8     # https://forummikrotik.ru/viewtopic.php?p=81457#p81457     :local CP1251toUTF8 do={         :local cp1251 [:toarray {"\20";"\01";"\02";"\03";"\04";"\05";"\06";"\07";"\08";"\09";"\0A";"\0B";"\0C";"\0D";"\0E";"\0F"; \                                  "\10";"\11";"\12";"\13";"\14";"\15";"\16";"\17";"\18";"\19";"\1A";"\1B";"\1C";"\1D";"\1E";"\1F"; \                                  "\21";"\22";"\23";"\24";"\25";"\26";"\27";"\28";"\29";"\2A";"\2B";"\2C";"\2D";"\2E";"\2F";"\3A"; \                                  "\3B";"\3C";"\3D";"\3E";"\3F";"\40";"\5B";"\5C";"\5D";"\5E";"\5F";"\60";"\7B";"\7C";"\7D";"\7E"; \                                  "\C0";"\C1";"\C2";"\C3";"\C4";"\C5";"\C7";"\C7";"\C8";"\C9";"\CA";"\CB";"\CC";"\CD";"\CE";"\CF"; \                                  "\D0";"\D1";"\D2";"\D3";"\D4";"\D5";"\D6";"\D7";"\D8";"\D9";"\DA";"\DB";"\DC";"\DD";"\DE";"\DF"; \                                  "\E0";"\E1";"\E2";"\E3";"\E4";"\E5";"\E6";"\E7";"\E8";"\E9";"\EA";"\EB";"\EC";"\ED";"\EE";"\EF"; \                                  "\F0";"\F1";"\F2";"\F3";"\F4";"\F5";"\F6";"\F7";"\F8";"\F9";"\FA";"\FB";"\FC";"\FD";"\FE";"\FF"; \                                  "\A8";"\B8";"\B9"}];         :local utf8   [:toarray {"0020";"0020";"0020";"0020";"0020";"0020";"0020";"0020";"0020";"0020";"000A";"0020";"0020";"000D";"0020";"0020"; \                                  "0020";"0020";"0020";"0020";"0020";"0020";"0020";"0020";"0020";"0020";"0020";"0020";"0020";"0020";"0020";"0020"; \                                  "0021";"0022";"0023";"0024";"0025";"0026";"0027";"0028";"0029";"002A";"002B";"002C";"002D";"002E";"002F";"003A"; \                                  "003B";"003C";"003D";"003E";"003F";"0040";"005B";"005C";"005D";"005E";"005F";"0060";"007B";"007C";"007D";"007E"; \                                  "D090";"D091";"D092";"D093";"D094";"D095";"D096";"D097";"D098";"D099";"D09A";"D09B";"D09C";"D09D";"D09E";"D09F"; \                                  "D0A0";"D0A1";"D0A2";"D0A3";"D0A4";"D0A5";"D0A6";"D0A7";"D0A8";"D0A9";"D0AA";"D0AB";"D0AC";"D0AD";"D0AE";"D0AF"; \                                  "D0B0";"D0B1";"D0B2";"D0B3";"D0B4";"D0B5";"D0B6";"D0B7";"D0B8";"D0B9";"D0BA";"D0BB";"D0BC";"D0BD";"D0BE";"D0BF"; \                                  "D180";"D181";"D182";"D183";"D184";"D185";"D186";"D187";"D188";"D189";"D18A";"D18B";"D18C";"D18D";"D18E";"D18F"; \                                  "D001";"D191";"2116"}];         :local convStr "";          :local code    "";         :for i from=0 to=([:len $1]-1) do={             :local symb [:pick $1 $i ($i+1)];              :local idx  [:find $cp1251 $symb];             :local key  ($utf8->$idx);             :if ([:len $key] != 0) do={                 :set $code ("%$[:pick ($key) 0 2]%$[:pick ($key) 2 4]");                 :if ([pick $code 0 3] = "%00") do={ :set $code ([:pick $code 3 6]); }             } else={ :set code ($symb); };              :set $convStr ($convStr.$code);         }         :return ($convStr);     }      # Telegram messenger response parsing function     # https://habr.com/ru/post/482802/     :local MsgParser do={         :local variaMod ("\"".$2."\"");         :if ([:len [:find $1 $variaMod -1]]=0) do={ :return ("'unknown'"); }         :local startLoc ([:find $1 $variaMod -1] + [:len $variaMod] +1);         :local commaLoc ([:find $1 "," $startLoc]);         :local brakeLoc ([:find $1 "}" $startLoc]);         :local endLoc $commaLoc;         :local startSymbol [:pick $1 $startLoc];         :if ($brakeLoc != 0 and ($commaLoc = 0 or $brakeLoc < $commaLoc)) do={ :set endLoc $brakeLoc; };         :if ($startSymbol = "{") do={ :set endLoc ($brakeLoc + 1); };         :if ($3 = true) do={             :set startLoc ($startLoc + 1);             :set endLoc   ($endLoc   - 1);         }         :if ($endLoc < $startLoc) do={ :set endLoc ($startLoc + 1); };         :return ([:pick $1 $startLoc $endLoc]);     }          # Time translation function to UNIX-time     # https://forum.mikrotik.com/viewtopic.php?t=75555#p790745     # Usage: $EpochTime [time input]     # Get current time: put [$EpochTime]     # Read log time in one of three format: "hh:mm:ss", "mmm/dd hh:mm:ss" or "mmm/dd/yyyy hh:mm:ss"     :local EpochTime do={         :local ds [ /system clock get date; ];         :local ts [ /system clock get time; ];         :if ([:len $1] > 19) do={             :set ds "$[:pick $1 0 11]";             :set ts [:pick $1 12 20];         }         :if ([:len $1] > 8 && [:len $1] < 20) do={             :set ds "$[:pick $1 0 6]/$[:pick $ds 7 11]";             :set ts [:pick $1 7 15];         }         :local yesterday false;         :if ([:len $1] = 8) do={             :if ([:totime $1] > ts) do={ :set yesterday (true); }             :set ts $1;         }         :local months;         :if ((([:pick $ds 9 11]-1)/4) != (([:pick $ds 9 11])/4)) do={             :set months {"an"=0;"eb"=31;"ar"=60;"pr"=91;"ay"=121;"un"=152;"ul"=182;"ug"=213;"ep"=244;"ct"=274;"ov"=305;"ec"=335};         } else={             :set months {"an"=0;"eb"=31;"ar"=59;"pr"=90;"ay"=120;"un"=151;"ul"=181;"ug"=212;"ep"=243;"ct"=273;"ov"=304;"ec"=334};         }         :set ds (([:pick $ds 9 11]*365)+(([:pick $ds 9 11]-1)/4)+($months->[:pick $ds 1 3])+[:pick $ds 4 6]);         :set ts (([:pick $ts 0 2]*3600)+([:pick $ts 3 5]*60)+[:pick $ts 6 8]);         :if (yesterday) do={ :set ds ($ds-1); }         :return ($ds*86400 + $ts + 946684800 - [ /system clock get gmt-offset; ]);     }      # Time conversion function from UNIX-time     # https://forummikrotik.ru/viewtopic.php?t=11636     # usage: [$UnixTimeToFormat "timeStamp" "type"]     # type: "unspecified" - month/dd/yyyy <only>    (Mikrotik sheduller format)     #                   1 - yyyy/mm/dd hh:mm:ss     #                   2 - dd:mm:yyyy hh:mm:ss     #                   3 - dd month yyy hh mm ss     #                   4 - yyyy month dd hh mm ss     #                   5 - month/dd/yyyy-hh:mm:ss  (Mikrotik sheduller format)     :local UnixTimeToFormat do={         :local decodedLine "";         :local timeStamp $1;         :local timeS ($timeStamp % 86400);         :local timeH ($timeS / 3600);         :local timeM ($timeS % 3600 / 60);         :set  $timeS ($timeS - $timeH * 3600 - $timeM * 60);         :local dateD ($timeStamp / 86400);         :local dateM 2;         :local dateY 1970;         :local leap false;         :while (($dateD / 365) > 0) do={             :set $dateD ($dateD - 365);             :set $dateY ($dateY + 1);             :set $dateM ($dateM + 1);             :if ($dateM = 4) do={                 :set $dateM 0;                 :if (($dateY % 400 = 0) or ($dateY % 100 != 0)) do={                     :set $leap true;                     :set $dateD ($dateD - 1);                 }             } else={ :set $leap false; }         }         :local months [:toarray (0,31,28,31,30,31,30,31,31,30,31,30,31)];         :if (leap) do={             :set $dateD ($dateD + 1);             :set ($months->2) 29;         }         :do {             :for i from=1 to=12 do={                 :if (($months->$i) > $dateD) do={                     :set $dateM $i;                     :set $dateD ($dateD + 1);                     break;                 } else={ :set $dateD ($dateD - ($months->$i)); }             }         } on-error={}         :local tmod;         :if ([:len $2]!=0) do={ :set $tmod $2; } else={ :set $tmod (:nothing); }         :local s "/";         :local nf true;         :local mstr {"jan";"feb";"mar";"apr";"may";"jun";"jul";"aug";"sep";"oct";"nov";"dec"};         :local strY [:tostr $dateY];         :local strMn;         :local strD;         :local strH;         :local strM;         :local strS;         :if ($nf) do={             :if ($dateM > 9) do={ :set $strMn [:tostr $dateM]; } else={ :set $strMn ("0".[:tostr $dateM]); }             :if ($dateD > 9) do={ :set $strD  [:tostr $dateD]; } else={ :set $strD  ("0".[:tostr $dateD]); }             :if ($timeH > 9) do={ :set $strH  [:tostr $timeH]; } else={ :set $strH  ("0".[:tostr $timeH]); }             :if ($timeM > 9) do={ :set $strM  [:tostr $timeM]; } else={ :set $strM  ("0".[:tostr $timeM]); }             :if ($timeS > 9) do={ :set $strS  [:tostr $timeS]; } else={ :set $strS  ("0".[:tostr $timeS]); }         } else={             :set strMn [:tostr $dateM];             :set strD  [:tostr $dateD];             :set strH  [:tostr $timeH];             :set strM  [:tostr $timeM];             :set strS  [:tostr $timeS];         }         :do {             :if ([:len $tmod]=0) do={ :local mt ($mstr->($dateM - 1)); :set $decodedLine ("$mt/"."$strD/"."$strY"); break; }             :if ($tmod = 1) do={ :set $decodedLine "$strY$s$strMn$s$strD $strH:$strM:$strS"; break; }             :if ($tmod = 2) do={ :set $decodedLine "$strD$s$strMn$s$strY $strH:$strM:$strS"; break; }             :if ($tmod = 3) do={ :set $decodedLine ("$strD ".($mstr->($dateM - 1))." $strY $strH:$strM:$strS"); break; }             :if ($tmod = 4) do={ :set $decodedLine ("$strY ".($mstr->($dateM - 1))." $strD $strH:$strM:$strS"); break; }             :if ($tmod = 5) do={ :local m ($mstr->($dateM - 1)); :set $decodedLine ("$m/"."$strD/"."$strY"."-$strH:$strM:$strS"); break; }         } on-error={}         :return ($decodedLine);     }      # Main body of the script     :global timeAct;     :global timeLog;     :local  nameID [ /system identity get name; ];     :local  timeOf [ /system clock get gmt-offset; ];     :put ("$[$UnixTimeToFormat ([$EpochTime] + $timeOf) 1] - Start of TLGRM-script on '$nameID' router.");     :if ([:len $timeAct] > 0) do={ :put ("$[$UnixTimeToFormat ($timeAct + $timeOf) 1] - Time when the last command was launched."); }     :if ([:len $timeLog] > 0) do={ :put ("$[$UnixTimeToFormat ($timeLog + $timeOf) 1] - Time when the log entries were last sent."); }      # Part of the script body to launch via Telegram     # https://forummikrotik.ru/viewtopic.php?p=78085     :local  timeStamp [$EpochTime];     :local  urlString "https://api.telegram.org/$botID/getUpdates\?offset=-1&limit=1&allowed_updates=message";     :put ("$[$UnixTimeToFormat ([$EpochTime] + $timeOf) 1] - *** Stage of launch scripts, function & commands via Telegram:");     :if ([:len $timeAct] = 0) do={         :put ("$[$UnixTimeToFormat ([$EpochTime] + $timeOf) 1] - Time of the last launch of the command was not found.");         :set timeAct $timeStamp;     } else={         :local httpResp [ /tool fetch url=$urlString as-value output=user; ];         :local content ($httpResp->"data");         :if ([:len $content] > 30) do={             :local msgTxt   [$MsgParser $content "text" true];             :set   msgTxt  ([:pick $msgTxt  ([:find $msgTxt "/" -1] +1)  [:len $msgTxt]]);             :local msgAddr ([:pick $msgTxt 0 [:find $msgTxt " " -1]]);             :if ([:len [:find $msgTxt " "]]=0) do={ :set msgAddr ("$msgTxt "); }             :put ("$[$UnixTimeToFormat ([$EpochTime] + $timeOf) 1] - Recipient of the Telegram message: '$msgAddr'")             :if ($msgAddr=$nameID or $msgAddr="forall") do={                 :set   msgTxt  ([:pick $msgTxt ([:find $msgTxt $msgAddr -1] + [:len $msgAddr] +1) [:len $msgTxt]]);                 :local chat     [$MsgParser $content "chat"];                 :local chatId   [$MsgParser $chat    "id"];                 :local userName [$MsgParser $content "username"];                 :set  timeStamp [$MsgParser $content "date"];                 :put ("$[$UnixTimeToFormat ([$EpochTime] + $timeOf) 1] - Sender of the Telegram message: $userName")                 :put ("$[$UnixTimeToFormat ([$EpochTime] + $timeOf) 1] - Command to execute: '$msgTxt'")                 :local restline   [];                 :if ([:len [:find $msgTxt " "]] !=0) do={                     :set restline [:pick $msgTxt  ([:find $msgTxt " "] +1) [:len $msgTxt]];                     :set msgTxt [:pick $msgTxt 0 [:find $msgTxt " "]];                 }                 :if ($chatId=$myChatID && $timeAct<$timeStamp) do={                     :set timeAct ($timeStamp);                     :if ([/system script environment find name=$msgTxt;]!="" && $launchFnc=true) do={                         :put ("$[$UnixTimeToFormat ([$EpochTime] + $timeOf) 1] - Telegram user $userName launches function '$msgTxt'.");                         :log warning ("Telegram user $userName launches function '$msgTxt'.");                         [:parse ":global $msgTxt; [\$$msgTxt $restline]"];                     }                     :if ([/system script find name=$msgTxt;]!="" && $launchScr=true) do={                         :put ("$[$UnixTimeToFormat ([$EpochTime] + $timeOf) 1] - Telegram user $userName activates script '$msgTxt'.");                         :log warning ("Telegram user $userName activates script '$msgTxt'.");                         /system script run $msgTxt;                     }                     :if ([/system script find name=$msgTxt;]="" && [/system script environment find name=$msgTxt;]="" && $launchCmd=true) do={                         :put ("$[$UnixTimeToFormat ([$EpochTime] + $timeOf) 1] - Telegram user $userName is trying to execute command '$msgTxt'.");                         :log warning ("Telegram user $userName is trying to execute command '$msgTxt'.");                         :do {[:parse "/$msgTxt $restline"]} on-error={}                     }                 } else={ :put ("$[$UnixTimeToFormat ([$EpochTime] + $timeOf) 1] - Wrong time to launch."); }             } else={ :put ("$[$UnixTimeToFormat ([$EpochTime] + $timeOf) 1] - No command found for this device."); }         } else={ :put ("$[$UnixTimeToFormat ([$EpochTime] + $timeOf) 1] - Completion of response from Telegram."); }     }      # Part of the script body for notifications in Telegram     # https://www.reddit.com/r/mikrotik/comments/onusoj/sending_log_alerts_to_telegram/     :local outMsg "";     :local logGet [ :toarray [ /log find ($topics ~"warning" || $topics ~"error" || $topics ~"critical" || $topics ~"caps" \     || $topics ~"wireless" || $message ~"logged in"); ]];     :local logCnt [ :len $logGet ];     :put ("$[$UnixTimeToFormat ([$EpochTime] + $timeOf) 1] - *** Stage of sending notifications to Telegram:");     :if ([:len $timeLog] = 0) do={          :put ("$[$UnixTimeToFormat ([$EpochTime] + $timeOf) 1] - Time of the last log entry was not found.");         :set outMsg (">$[ /system clock get time; ] Telegram notification started.");     }     :if ($logCnt > 0) do={         :local lastTime [$EpochTime [ /log get [:pick $logGet ($logCnt-1)] time; ]];         :local index 0;         :local tempTime;         :local tempMessage;         :local tempTopic;         :local unixTime;         :do {             :set index        ($index + 1);              :set tempTime     [ /log get [:pick $logGet ($logCnt - $index)]    time; ];             :set tempTopic    [ /log get [:pick $logGet ($logCnt - $index)]  topics; ];             :set tempMessage  [ /log get [:pick $logGet ($logCnt - $index)] message; ];             :set tempMessage  (">".$tempTime." ".$tempMessage);             :local findMacMsg ([$FindMacAddr $tempMessage]);             :set unixTime [$EpochTime $tempTime];             :if (($unixTime > $timeLog) && (!(($tempTopic ~"caps" || $tempTopic ~"wireless" || $tempTopic ~"dhcp") \             && ($tempMessage != $findMacMsg)))) do={                 :put ("$[$UnixTimeToFormat ([$EpochTime] + $timeOf) 1] - Found log entry: $findMacMsg");                 :set outMsg ($findMacMsg."\n".$outMsg);             }         } while=(($unixTime > $timeLog) && ($index < $logCnt));         :if (([:len $timeLog] < 1) || (([:len $timeLog] > 0) && ($timeLog != $lastTime) && ([:len $outMsg] > 8) )) do={             :set timeLog $lastTime;             :if ([:len $outMsg] > 4096) do={ :set outMsg ([:pick $outMsg 0 4096]); }             :set outMsg [$CP1251toUTF8 $outMsg];             :set outMsg ("$Emoji "."$nameID".":"."%0A"."$outMsg");             :local urlString ("https://api.telegram.org/$botID/sendmessage\?chat_id=$myChatID&text=$outMsg");             :put ("$[$UnixTimeToFormat ([$EpochTime] + $timeOf) 1] - Generated string for Telegram:\r\n".$urlString);             /tool fetch url=$urlString as-value output=user;         } else={ :put ("$[$UnixTimeToFormat ([$EpochTime] + $timeOf) 1] - There are no log entries to send."); }     } else={ :put ("$[$UnixTimeToFormat ([$EpochTime] + $timeOf) 1] - Necessary log entries were not found."); }     :put ("$[$UnixTimeToFormat ([$EpochTime] + $timeOf) 1] - End of TLGRM-script on '$nameID' router."); } on-error={      :put ("Script error: something didn't work when sending a request to Telegram.");     :put ("*** First, check the correctness of the values of the variables botID & myChatID. ***");  }

От себя хочу отдельно поблагодарить drPioneer за основную работу над данным скриптом и комментариям по работе основных блоков кода.

С этапами создания и развития скрипта можно познакомиться на соответствующей ветви русскоязычного форума Микротик. Скрипт также добавлен в соответствующий раздел англоязычного форума.

Желаем приятного пользования. Ждём замечаний и предложений по улучшению скрипта. Коллектив авторов.


ссылка на оригинал статьи https://habr.com/ru/articles/650563/