WebhookずJSONパヌサヌを備えたMikrotik甚のテレグラムボット

Mikrotikスクリプトのみを䜿甚しお、Webhookをサポヌトするルヌタヌ環境で完党に動䜜する察話型のTelegramボット、Telegram APIからの着信むベントを䜜成できるず思いたすか



前曞き

私はこの蚘事の執筆を長い間延期したしたが、Telegramメッセンゞャヌをめぐる最近の出来事は、私の蚈画を匕き継ぐための新しい゚ネルギヌで私を刺激したした。 Telegramは、ずりわけ、情報むンタラクティブサヌビスのための䟿利なニッチプラットフォヌムであり、人為的な制限なしに他のシステムず共存し、競合する必芁があるず思いたす。 この蚘事をTelegramのサポヌトぞのささやかな貢献ずしたしょう。



質問に答える前に、Webhookが機胜するためにボットプラットフォヌムに最䜎限必芁なものを理解する必芁がありたす。 SSLを䜿甚したWEBサヌバヌの存圚、有効なSSL蚌明曞、たたはTelegram APIに読み蟌たれた自己眲名蚌明曞、Webhookを凊理するためのWEBサヌバヌのURL。 たた、むンタヌネット実際のIP、ドメむン名からルヌタヌぞのアクセスを提䟛できる堎合、MikrotikにはWEBサヌバヌに問題がありSSLもなくなりたす、ナヌザヌサヌバヌはありたせん。 しかし、この問題は回避できたす。解決策を以䞋に提案したす。



MikrotikのTelegramボットは、「氷山の䞀角」にすぎたせん。 これは、Mikrotikスクリプト蚀語で蚘述された可胜な限り本栌的なJSONパヌサヌに基づいおいたす。 䞀般に、平均的なボットを蚘述するために、JSONの完党な分析を行う必芁はありたせん。完党に管理しお行をコピヌできたすが、別のパスを遞択したした。 次に、パヌサヌず、䜜業䞭にマスタヌされたMikrotikスクリプトのプログラミング手法に぀いお説明したす。



Mikrotik蚀語のパヌサヌJSON文字列



Mikrotikスクリプト蚀語でJSONパヌサヌを䜜成するこずは私にずっおスポヌツでした。 Mikrotikスクリプト蚀語の制限を考えるず、これがたったく可胜かどうかは面癜かったです。 しかし、コヌドの奥に行くほど、最終目暙ぞの道がはっきりず芋えおきたした。 以前、私は1぀のSCADAシステムのニヌズのために、オヌプンスペヌスにあるVBScriptの同様のパヌサヌを思い浮かべたした。そのため、そのVBScript実装のロゞックを基瀎ずしお、Mikrotik蚀語の構築を考慮しお凊理し、関数のラむブラリの圢でコヌドを蚭蚈したした。 その過皋で、スクリプト蚀語の興味深い機胜をいく぀か発芋したした。これに぀いおは、以䞋で喜んで共有したす。 制限事項に぀いお䞀蚀。 たず、Mikrotik倉数の文字列の長さは4096バむトです。䜕もする必芁はありたせん。すべおの倉数はもはや倉数に割り圓おられおいたせん。 2番目Mikrotikは実数に぀いお䜕も知らないため、パヌサヌはfloatを文字列倉数ずしお保存したす。bool、int、string型は通垞、内郚衚珟に解析されたす。



JSONパヌサヌを䜿甚する







関数は、関数コヌドをグロヌバル倉数に「拡匵」するJParseFunctionsラむブラリファむルによっお衚されたす。 このラむブラリは、パフォヌマンスを倧幅に䜎䞋させるこずなく、スクリプト内で䜕床でも呌び出すこずができたす。各関数に぀いお、アクションの重耇を避けるためにグロヌバル倉数の「拡匵」がチェックされたす。 ラむブラリファむルを線集するずきは、グロヌバル倉数関数コヌドを削陀しお、曎新で「再䜜成」する必芁がありたす。



JParseFunctionsラむブラリコヌド



JParseFunctions
# -------------------------------- JParseFunctions --------------------------------------------------- # ------------------------------- fJParsePrint ---------------------------------------------------------------- :global fJParsePrint :if (!any $fJParsePrint) do={ :global fJParsePrint do={ :global JParseOut :local TempPath :global fJParsePrint :if ([:len $1] = 0) do={ :set $1 "\$JParseOut" :set $2 $JParseOut } :foreach k,v in=$2 do={ :if ([:typeof $k] = "str") do={ :set k "\"$k\"" } :set TempPath ($1. "->" . $k) :if ([:typeof $v] = "array") do={ :if ([:len $v] > 0) do={ $fJParsePrint $TempPath $v } else={ :put "$TempPath = [] ($[:typeof $v])" } } else={ :put "$TempPath = $v ($[:typeof $v])" } } }} # ------------------------------- fJParsePrintVar ---------------------------------------------------------------- :global fJParsePrintVar :if (!any $fJParsePrintVar) do={ :global fJParsePrintVar do={ :global JParseOut :local TempPath :global fJParsePrintVar :local fJParsePrintRet "" :if ([:len $1] = 0) do={ :set $1 "\$JParseOut" :set $2 $JParseOut } :foreach k,v in=$2 do={ :if ([:typeof $k] = "str") do={ :set k "\"$k\"" } :set TempPath ($1. "->" . $k) :if ($fJParsePrintRet != "") do={ :set fJParsePrintRet ($fJParsePrintRet . "\r\n") } :if ([:typeof $v] = "array") do={ :if ([:len $v] > 0) do={ :set fJParsePrintRet ($fJParsePrintRet . [$fJParsePrintVar $TempPath $v]) } else={ :set fJParsePrintRet ($fJParsePrintRet . "$TempPath = [] ($[:typeof $v])") } } else={ :set fJParsePrintRet ($fJParsePrintRet . "$TempPath = $v ($[:typeof $v])") } } :return $fJParsePrintRet }} # ------------------------------- fJSkipWhitespace ---------------------------------------------------------------- :global fJSkipWhitespace :if (!any $fJSkipWhitespace) do={ :global fJSkipWhitespace do={ :global Jpos :global JSONIn :global Jdebug :while ($Jpos < [:len $JSONIn] and ([:pick $JSONIn $Jpos] ~ "[ \r\n\t]")) do={ :set Jpos ($Jpos + 1) } :if ($Jdebug) do={:put "fJSkipWhitespace: Jpos=$Jpos Char=$[:pick $JSONIn $Jpos]"} }} # -------------------------------- fJParse --------------------------------------------------------------- :global fJParse :if (!any $fJParse) do={ :global fJParse do={ :global Jpos :global JSONIn :global Jdebug :global fJSkipWhitespace :local Char :if (!$1) do={ :set Jpos 0 } $fJSkipWhitespace :set Char [:pick $JSONIn $Jpos] :if ($Jdebug) do={:put "fJParse: Jpos=$Jpos Char=$Char"} :if ($Char="{") do={ :set Jpos ($Jpos + 1) :global fJParseObject :return [$fJParseObject] } else={ :if ($Char="[") do={ :set Jpos ($Jpos + 1) :global fJParseArray :return [$fJParseArray] } else={ :if ($Char="\"") do={ :set Jpos ($Jpos + 1) :global fJParseString :return [$fJParseString] } else={ # :if ([:pick $JSONIn $Jpos ($Jpos+2)]~"^-\?[0-9]") do={ :if ($Char~"[eE0-9.+-]") do={ :global fJParseNumber :return [$fJParseNumber] } else={ :if ($Char="n" and [:pick $JSONIn $Jpos ($Jpos+4)]="null") do={ :set Jpos ($Jpos + 4) :return [] } else={ :if ($Char="t" and [:pick $JSONIn $Jpos ($Jpos+4)]="true") do={ :set Jpos ($Jpos + 4) :return true } else={ :if ($Char="f" and [:pick $JSONIn $Jpos ($Jpos+5)]="false") do={ :set Jpos ($Jpos + 5) :return false } else={ :put "Err.Raise 8732. No JSON object could be fJParseed" :set Jpos ($Jpos + 1) :return [] } } } } } } } }} #-------------------------------- fJParseString --------------------------------------------------------------- :global fJParseString :if (!any $fJParseString) do={ :global fJParseString do={ :global Jpos :global JSONIn :global Jdebug :global fUnicodeToUTF8 :local Char :local StartIdx :local Char2 :local TempString "" :local UTFCode :local Unicode :set StartIdx $Jpos :set Char [:pick $JSONIn $Jpos] :if ($Jdebug) do={:put "fJParseString: Jpos=$Jpos Char=$Char"} :while ($Jpos < [:len $JSONIn] and $Char != "\"") do={ :if ($Char="\\") do={ :set Char2 [:pick $JSONIn ($Jpos + 1)] :if ($Char2 = "u") do={ :set UTFCode [:tonum "0x$[:pick $JSONIn ($Jpos+2) ($Jpos+6)]"] :if ($UTFCode>=0xD800 and $UTFCode<=0xDFFF) do={ # Surrogate pair :set Unicode (($UTFCode & 0x3FF) << 10) :set UTFCode [:tonum "0x$[:pick $JSONIn ($Jpos+8) ($Jpos+12)]"] :set Unicode ($Unicode | ($UTFCode & 0x3FF) | 0x10000) :set TempString ($TempString . [:pick $JSONIn $StartIdx $Jpos] . [$fUnicodeToUTF8 $Unicode]) :set Jpos ($Jpos + 12) } else= { # Basic Multilingual Plane (BMP) :set Unicode $UTFCode :set TempString ($TempString . [:pick $JSONIn $StartIdx $Jpos] . [$fUnicodeToUTF8 $Unicode]) :set Jpos ($Jpos + 6) } :set StartIdx $Jpos :if ($Jdebug) do={:put "fJParseString Unicode: $Unicode"} } else={ :if ($Char2 ~ "[\\bfnrt\"]") do={ :if ($Jdebug) do={:put "fJParseString escape: Char+Char2 $Char$Char2"} :set TempString ($TempString . [:pick $JSONIn $StartIdx $Jpos] . [[:parse "(\"\\$Char2\")"]]) :set Jpos ($Jpos + 2) :set StartIdx $Jpos } else={ :if ($Char2 = "/") do={ :if ($Jdebug) do={:put "fJParseString /: Char+Char2 $Char$Char2"} :set TempString ($TempString . [:pick $JSONIn $StartIdx $Jpos] . "/") :set Jpos ($Jpos + 2) :set StartIdx $Jpos } else={ :put "Err.Raise 8732. Invalid escape" :set Jpos ($Jpos + 2) } } } } else={ :set Jpos ($Jpos + 1) } :set Char [:pick $JSONIn $Jpos] } :set TempString ($TempString . [:pick $JSONIn $StartIdx $Jpos]) :set Jpos ($Jpos + 1) :if ($Jdebug) do={:put "fJParseString: $TempString"} :return $TempString }} #-------------------------------- fJParseNumber --------------------------------------------------------------- :global fJParseNumber :if (!any $fJParseNumber) do={ :global fJParseNumber do={ :global Jpos :local StartIdx :global JSONIn :global Jdebug :local NumberString :local Number :set StartIdx $Jpos :set Jpos ($Jpos + 1) :while ($Jpos < [:len $JSONIn] and [:pick $JSONIn $Jpos]~"[eE0-9.+-]") do={ :set Jpos ($Jpos + 1) } :set NumberString [:pick $JSONIn $StartIdx $Jpos] :set Number [:tonum $NumberString] :if ([:typeof $Number] = "num") do={ :if ($Jdebug) do={:put "fJParseNumber: StartIdx=$StartIdx Jpos=$Jpos $Number ($[:typeof $Number])"} :return $Number } else={ :if ($Jdebug) do={:put "fJParseNumber: StartIdx=$StartIdx Jpos=$Jpos $NumberString ($[:typeof $NumberString])"} :return $NumberString } }} #-------------------------------- fJParseArray --------------------------------------------------------------- :global fJParseArray :if (!any $fJParseArray) do={ :global fJParseArray do={ :global Jpos :global JSONIn :global Jdebug :global fJParse :global fJSkipWhitespace :local Value :local ParseArrayRet [:toarray ""] $fJSkipWhitespace :while ($Jpos < [:len $JSONIn] and [:pick $JSONIn $Jpos]!= "]") do={ :set Value [$fJParse true] :set ($ParseArrayRet->([:len $ParseArrayRet])) $Value :if ($Jdebug) do={:put "fJParseArray: Value="; :put $Value} $fJSkipWhitespace :if ([:pick $JSONIn $Jpos] = ",") do={ :set Jpos ($Jpos + 1) $fJSkipWhitespace } } :set Jpos ($Jpos + 1) # :if ($Jdebug) do={:put "ParseArrayRet: "; :put $ParseArrayRet} :return $ParseArrayRet }} # -------------------------------- fJParseObject --------------------------------------------------------------- :global fJParseObject :if (!any $fJParseObject) do={ :global fJParseObject do={ :global Jpos :global JSONIn :global Jdebug :global fJSkipWhitespace :global fJParseString :global fJParse # Syntax :local ParseObjectRet ({}) don't work in recursive call, use [:toarray ""] for empty array!!! :local ParseObjectRet [:toarray ""] :local Key :local Value :local ExitDo false $fJSkipWhitespace :while ($Jpos < [:len $JSONIn] and [:pick $JSONIn $Jpos]!="}" and !$ExitDo) do={ :if ([:pick $JSONIn $Jpos]!="\"") do={ :put "Err.Raise 8732. Expecting property name" :set ExitDo true } else={ :set Jpos ($Jpos + 1) :set Key [$fJParseString] $fJSkipWhitespace :if ([:pick $JSONIn $Jpos] != ":") do={ :put "Err.Raise 8732. Expecting : delimiter" :set ExitDo true } else={ :set Jpos ($Jpos + 1) :set Value [$fJParse true] :set ($ParseObjectRet->$Key) $Value :if ($Jdebug) do={:put "fJParseObject: Key=$Key Value="; :put $Value} $fJSkipWhitespace :if ([:pick $JSONIn $Jpos]=",") do={ :set Jpos ($Jpos + 1) $fJSkipWhitespace } } } } :set Jpos ($Jpos + 1) # :if ($Jdebug) do={:put "ParseObjectRet: "; :put $ParseObjectRet} :return $ParseObjectRet }} # ------------------- fByteToEscapeChar ---------------------- :global fByteToEscapeChar :if (!any $fByteToEscapeChar) do={ :global fByteToEscapeChar do={ # :set $1 [:tonum $1] :return [[:parse "(\"\\$[:pick "0123456789ABCDEF" (($1 >> 4) & 0xF)]$[:pick "0123456789ABCDEF" ($1 & 0xF)]\")"]] }} # ------------------- fUnicodeToUTF8---------------------- :global fUnicodeToUTF8 :if (!any $fUnicodeToUTF8) do={ :global fUnicodeToUTF8 do={ :global fByteToEscapeChar # :local Ubytes [:tonum $1] :local Nbyte :local EscapeStr "" :if ($1 < 0x80) do={ :set EscapeStr [$fByteToEscapeChar $1] } else={ :if ($1 < 0x800) do={ :set Nbyte 2 } else={ :if ($1 < 0x10000) do={ :set Nbyte 3 } else={ :if ($1 < 0x20000) do={ :set Nbyte 4 } else={ :if ($1 < 0x4000000) do={ :set Nbyte 5 } else={ :if ($1 < 0x80000000) do={ :set Nbyte 6 } } } } } :for i from=2 to=$Nbyte do={ :set EscapeStr ([$fByteToEscapeChar ($1 & 0x3F | 0x80)] . $EscapeStr) :set $1 ($1 >> 6) } :set EscapeStr ([$fByteToEscapeChar (((0xFF00 >> $Nbyte) & 0xFF) | $1)] . $EscapeStr) } :return $EscapeStr }} # ------------------- End JParseFunctions----------------------
      
      





テレグラムボットコヌドの䟋を䜿甚しお、パヌサヌの䜜業を怜蚎したす。 次のコマンドを段階的に実行しおみたしょう。



j.txtファむルでJSON文字列を返すgetWebhookInfo Telegram API関数のステヌタスを芁求したす。



 :do {/tool fetch url="https://api.telegram.org/bot$TToken/getWebhookInfo" dst-path=j.txt} on-error={:put "getWebhookInfo error"};
      
      





 [admin@MikroTik] > :put [/file get j.txt contents]; {"ok":true,"result":{"url":"https://*****:8443","has_custom_certificate":false,"pending_update_count":0,"last_error_date":1524565055,"last_error_message":"Connection timed out","max_connections":4 0}}
      
      





JSON文字列を入力倉数にロヌドする



 :set JSONIn [/file get j.txt contents]
      
      





$ fJParseパヌサヌ関数を実行し、結果を$ JParseOut倉数にアップロヌドする



 :set JParseOut [$fJParse];
      
      





$ JParseOutには、元のJSON文字列からMikrotik配列およびデヌタ型ぞのマッピングである連想配列がありたす。 ここではコンテンツを提䟛したせん。以䞋に瀺したす。



グロヌバル倉数$ Jdebugtrueを蚭定しおから、手動モヌドでルヌタヌのコン゜ヌルで関数を呌び出すず、デバッグの必芁に応じお远加の出力を取埗できたす。



倚次元連想配列



Mikrotikは、ネストされた倚次元連想配列をサポヌトしおいたす。

以䞋は、パヌサヌの結果が曞き蟌たれるグロヌバル倉数$ JParseOutの出力䟋です。



 [admin@MikroTik] > :put $JParseOut ok=true;result=has_custom_certificate=false;max_connections=40;pending_update_count=0;url=https://*****.ru:8443
      
      





 [admin@MikroTik] > :put ($JParseOut->"result") has_custom_certificate=false;max_connections=40;pending_update_count=0;url=https://*****:8443
      
      





 [admin@MikroTik] > :put ($JParseOut->"result"->"max_connections") 40
      
      





キヌの「結果」には倀ずしお連想配列も含たれおおり、「->」チェヌンを䜿甚しおその芁玠に到達できるこずがわかりたす。 さらに、すべおの芁玠が独自のデヌタ型数倀、文字列、ブヌル倀、配列を持぀こずが重芁です。



 [admin@MikroTik] > :put [:typeof ($JParseOut->"result")] array
      
      





 [admin@MikroTik] > :put [:typeof ($JParseOut->"result"->"max_connections")] num
      
      





 [admin@MikroTik] > :put [:typeof ($JParseOut->"result"->"url")] str
      
      





JSONパヌサヌを䜜成するずいうアむデアを促したのは、このマルチレベルの構築の実隓でした。 JSON圢匏は、Mikrotikスクリプト蚀語のこのような内郚衚珟にうたく倉換されたす。



関数、再垰呌び出し



倚くの堎合、独自の機胜を定矩できるのは秘密ではありたせん;フォヌラムサむトwww.mikrotik.comで、そのような蚭蚈の倚くの䟋を芋぀けるこずができたす。 私のパヌサヌは、関数、ネストされた再垰的な呌び出しにも基づいおいたす。 はい、再垰的な関数呌び出しがサポヌトされおいたす



䟋ずしお、連想配列$ JParseOutの内容たたは、スクリプトでコピヌしお配列の芁玠にアクセスするために䜿甚できるパスの圢匏ずその凊理結果を印刷するパヌサヌセットの$ fJParsePrint関数を瀺したす。



 :global fJParsePrint :if (!any $fJParsePrint) do={ :global fJParsePrint do={ :global JParseOut :local TempPath :global fJParsePrint :if ([:len $1] = 0) do={ :set $1 "\$JParseOut" :set $2 $JParseOut } :foreach k,v in=$2 do={ :if ([:typeof $k] = "str") do={ :set k "\"$k\"" } :set TempPath ($1. "->" . $k) :if ([:typeof $v] = "array") do={ :if ([:len $v] > 0) do={ $fJParsePrint $TempPath $v } else={ :put "$TempPath = [] ($[:typeof $v])" } } else={ :put "$TempPath = $v ($[:typeof $v])" } } }}
      
      





 [admin@MikroTik] > $fJParsePrint $JParseOut->"ok" = true (bool) $JParseOut->"result"->"has_custom_certificate" = false (bool) $JParseOut->"result"->"last_error_date" = 1524483204 (num) $JParseOut->"result"->"last_error_message" = Connection timed out (str) $JParseOut->"result"->"max_connections" = 40 (num) $JParseOut->"result"->"pending_update_count" = 0 (num) $JParseOut->"result"->"url" = https://*****.ru:8443 (str)
      
      





珟圚の添付ファむルのレベルずサブ配列芁玠を関数に枡す関数コヌドで再垰呌び出しを確認できたす。これにより、$ JParseOut倉数で配列ツリヌ党䜓を走査したす。



 $fJParsePrint $TempPath $v
      
      





興味深いこずに、コン゜ヌルからパラメヌタヌを指定しおこの関数を呌び出し、初期出力パス「home」などず配列倉数を手動で指定できたす。



 [admin@MikroTik] > $fJParsePrint "home" $JParseOut home->"ok" = true (bool) home->"result"->"has_custom_certificate" = false (bool) home->"result"->"last_error_date" = 1524483204 (num) home->"result"->"last_error_message" = Connection timed out (str) home->"result"->"max_connections" = 40 (num) home->"result"->"pending_update_count" = 0 (num) home->"result"->"url" = https://*****.ru:8443 (str)
      
      





この関数は、パラメヌタヌありずパラメヌタヌなしの呌び出しを凊理するように蚘述されおいたす。 可倉数のパラメヌタヌが䜿甚されたす。 埓来、呌び出す前に、ブロック内、この堎合は関数の本䜓でグロヌバル倉数ず関数を宣蚀するたたは宣蚀する必芁がありたす。 「global fJParsePrint」ずいう宣蚀が存圚するこずに泚意しおください。 関数自䜓が宣蚀されおおり、再垰呌び出しに必芁なのも圓然です。



「オンザフラむ」コヌドずその実行で行を解析する



$ fByteToEscapeChar関数を芋おみたしょう



 :global fByteToEscapeChar :if (!any $fByteToEscapeChar) do={ :global fByteToEscapeChar do={ # :set $1 [:tonum $1] :return [[:parse "(\"\\$[:pick "0123456789ABCDEF" (($1 >> 4) & 0xF)]$[:pick "0123456789ABCDEF" ($1 & 0xF)]\")"]] }}
      
      





この関数は、パラメヌタヌ$ 1バむト数を文字列文字に倉換したす。 ASCIIコヌドを文字に倉換したす。 たずえば、蚘号「+」に察応するコヌド0x2Bがありたす。 ゚スケヌプ "\ NN"を䜿甚しお文字コヌドを指定できたす。NNはASCIIコヌドですが、次の行のみです。



 [admin@MikroTik] > :put "\2B" +
      
      





しかし、゜ヌスコヌドが数字バむトで衚されおいる堎合、文字を取埗するこずは簡単な䜜業ではありたせん。これには、既補の組み蟌み関数がないためです。 ここで、別の組み蟌み解析関数が圹立ちたす。これにより、文字列を収集できたす。これは、「\ 2B」などの元の数倀に基づいおシヌケンスを制埡する匏です。



フォヌムの衚珟



 :put [:parse "(\"\\$[:pick "0123456789ABCDEF" ((0x2B >> 4) & 0xF)]$[:pick "0123456789ABCDEF" (0x2B & 0xF)]\")"] (<%% + )
      
      





-出力で文字列文字を取埗するために実行する必芁があるコヌド行を収集したす。 解析埌に受け取ったコヌドの2回目の実行は、同じ角括匧[...]を䜿甚しお行われるため、最終匏はやや耇雑な倖芳を取り、二重角括匧[[...]]でフレヌミングしたす。



 [admin@MikroTik] > :put [[:parse "(\"\\$[:pick "0123456789ABCDEF" ((0x2B >> 4) & 0xF)]$[:pick "0123456789ABCDEF" (0x2B & 0xF)]\")"]] +
      
      





JSONパヌサヌに基づくテレグラムボット



ポヌリングボット



Telegram APIからJSON応答のコンテンツに簡単にアクセスできるようになったので、ポヌリングモヌドで動䜜するボットの最初のバヌゞョン、぀たり Telegram APIの定期的なリク゚スト。 たずえば、uptime-ルヌタヌの動䜜時間の芁求、ip-すべおのDHCPクラむアントIPアドレスの芁求、parse-倉数$ JParseOutの内容の衚瀺、぀たり 最埌のリク゚ストに察する解析されたJSONレスポンス。 他のコマンドや文字を入力するず、ボットは単に゚コヌしたす。



このボットは、1分に1回など、スケゞュヌラから定期的に呌び出され、getUpdatesテレグラムAPI関数を読み取る1぀のスクリプトです。応答を解析した埌、if-elseは倉数$ v-> "message"-> "text"のアクションを遞択したす。 たた、$ JParseOutの内容を読み取り可胜な圢匏で返す䞀連のパヌサヌ関数からの関数 "text = $ [$ fJParsePrintVar]"の呌び出しにも泚意を払いたいず思いたす。 完党なボットコヌドを以䞋に瀺したす。



長所からスクリプトが亀換を開始するため、蚭定なしでNATを介しお機胜したす。

この実装の欠点芁求に察するMikrotikの応答速床は、スクリプト呌び出しの頻床によっお決たりたす。各呌び出しでgetUpdates芁求が実行され、䞀般に、プロセッサをロヌドする完党な芁求分析サむクルを解析したす。 各呌び出しはj.txtファむルの曞き蟌みに぀ながりたす。フラッシュドラむブ䞊のパヌティションの堎合、これは悪いこずです。RAMドラむブの堎合は怖くありたせん。



ポヌリングボットスクリプトコヌド



TelegramPollingBot
 /system script run JParseFunctions :global TToken "12312312:32131231231" :global TChatId "43242342423" :global Toffset :if ([:typeof $Toffset] != "num") do={:set Toffset 0} /tool fetch url="https://api.telegram.org/bot$TToken/getUpdates\?chat_id=$TChatId&offset=$Toffset" dst-path=j.txt #:delay 2 :global JSONIn [/file get j.txt contents] :global fJParse :global fJParsePrintVar :global Jdebug false :global JParseOut [$fJParse] :local Results ($JParseOut->"result") :if ([:len $Results]>0) do={ :foreach k,v in=$Results do={ :if (any ($v->"message"->"text")) do={ :if ($v->"message"->"text" ~ "uptime") do={ /tool fetch url="https://api.telegram.org/bot$TToken/sendmessage\?chat_id=$TChatId" http-method=post http-data="text=$[/system resource get uptime]" keep-result=no } else={ :if ($v->"message"->"text" ~ "ip") do={ /tool fetch url="https://api.telegram.org/bot$TToken/sendmessage\?chat_id=$TChatId" http-method=post http-data="text=$[/ip dhcp-client print as-value]" keep-result=no } else={ :if ($v->"message"->"text" ~ "parse") do={ /tool fetch url="https://api.telegram.org/bot$TToken/sendmessage\?chat_id=$TChatId" http-method=post http-data="text=$[$fJParsePrintVar]" keep-result=no } else={ /tool fetch url="https://api.telegram.org/bot$TToken/sendmessage\?chat_id=$TChatId" http-method=post http-data="text=$($v->"message"->"text")" keep-result=no } } } } :set $Toffset ($v->"update_id" + 1) } } else={ :set $Toffset 0 }
      
      







Webhookボット



これらのマむナスを取り陀くために、Webhookを凊理するスクリプトの2番目のバヌゞョンを䜜成したす。 Telegram API自䜓が、新しいメッセヌゞを送信するために、指定されたアドレスでルヌタヌに「スラム」するずき。



もちろん、Mikrotikは、Telegram APIからのWebhook通知を完党に操䜜するために必芁な、カスタムWebサヌバヌを内郚に䜜成する方法を知りたせん。 しかし、この問題を巧劙に回避できたす。 これを行うには、Webhookが「クラッシュ」する存圚しないTCP゜ケットを監芖する必芁がありたす。これは、マングルたたはファむアりォヌルルヌルを䜿甚しお行われたす。 Telegram APIには、Webhookの動䜜setWebhook API関数が含たれ、ルヌタヌのドメむン名ずTCPポヌトが瀺されたす。SSL蚌明曞はここでは圹割を果たしたせん。 必芁ありたせん Mangleルヌルのパケットカりンタヌの倀を倉曎するこずにより、Webhookが存圚しないTCPポヌトたたは他の䜕かに「切り取られる」こずを理解できたす。src-address= 149.154.167.192 / 26フィルタヌで超過分をカットできたす。 残念ながら、マングルルヌルはナヌザヌ定矩のスクリプトを盎接呌び出すこずはできたせんそのようなアクションはありたせんが、スクリプトからパケットカりンタヌをポヌリングするこずはできたす。 スクリプトもスケゞュヌルどおりに実行されたすが、最小間隔は1秒です。 アむドル状態では、パケットカりンタヌ倀の倉化のみがチェックされたす。 新しい着信パケットを怜出した埌、Webhookを無効にする芁求がTelegram APIに送信され、メッセヌゞが最初のスクリプトポヌリングのように読み取られお凊理されたす。その埌、Webhookが再びオンになり、スタンバむ状態に戻りたす。 䞻な手順は、スクリプトの図に瀺されおいたす。







既に述べたように、スクリプトは頻繁に実行され、呌び出されたスクリプトのむンスタンスの重耇を避けるために、スクリプトの先頭で重耇に察する保護が行われたす。このスクリプトの名前をそこに瀺す必芁がありたす。



 :if ([:len [/system script job find script=TelegramWebhookBot]] <= 1) do={...}
      
      





Webhookボットスクリプトコヌド



TelegramWebhookBot
 :if ([:len [/system script job find script=TelegramWebhookBot]] <= 1) do={ #:while (true) do={ :global TelegramWebhookPackets :local TWebhookURL "https://www.yourdomain" :local TWebhookPort "8443" # Create Telegram webhook mangle action :if ([:len [/ip firewall mangle find dst-port=$TWebhookPort]] = 0) do={ /ip firewall mangle add action=accept chain=prerouting connection-state=new dst-port=$TWebhookPort protocol=tcp src-address=149.154.167.192/26 comment="Telegram" } :if ([/ip firewall mangle get [find dst-port=$TWebhookPort] packets] != $TelegramWebhookPackets) do={ /system script run JParseFunctions :local TToken "123123123:123123123123123" :local TChatId "3213123123123" :global TelegramOffset :global fJParse :global fJParsePrintVar :global Jdebug false :global JSONIn :global JParseOut :if ([:typeof $TelegramOffset] != "num") do={:set TelegramOffset 0} :put "getWebhookInfo" :do {/tool fetch url="https://api.telegram.org/bot$TToken/getWebhookInfo" dst-path=j.txt} on-error={:put "getWebhookInfo error"} :set JSONIn [/file get j.txt contents] :set JParseOut [$fJParse] :put $JParseOut :if ($JParseOut->"result"->"pending_update_count" > 0) do={ :put "pending_update_count > 0" :do {/tool fetch url="https://api.telegram.org/bot$TToken/deleteWebhook" http-method=get keep-result=no} on-error={:put "deleteWebhook error"} :put "getUpdates" :do {/tool fetch url="https://api.telegram.org/bot$TToken/getUpdates\?chat_id=$TChatId&offset=$TelegramOffset" dst-path=j.txt} on-error={:put "getUpdates error"} :set JSONIn [/file get j.txt contents] :set JParseOut [$fJParse] :put $JParseOut :if ([:len ($JParseOut->"result")] > 0) do={ :foreach k,v in=($JParseOut->"result") do={ :if (any ($v->"message"->"text")) do={ :if ($v->"message"->"text" ~ "uptime") do={ :do {/tool fetch url="https://api.telegram.org/bot$TToken/sendmessage\?chat_id=$TChatId" http-method=post http-data="text=$[/system resource get uptime]" keep-result=no} on-error={:put "sendmessage error"} } else={ :if ($v->"message"->"text" ~ "ip") do={ :do {/tool fetch url="https://api.telegram.org/bot$TToken/sendmessage\?chat_id=$TChatId" http-method=post http-data="text=$[/ip dhcp-client print as-value]" keep-result=no} on-error={:put "sendmessage error"} } else={ :if ($v->"message"->"text" ~ "parse") do={ :do {/tool fetch url="https://api.telegram.org/bot$TToken/sendmessage\?chat_id=$TChatId" http-method=post http-data="text=$[$fJParsePrintVar]" keep-result=no} on-error={:put "sendmessage error"} } else={ :if ($v->"message"->"text" ~ "add") do={ :local addIP [:toip [:pick ($v->"message"->"text") 4 [:len ($v->"message"->"text")]]] :if ([:typeof $addIP] = "ip") do={ :do {/ip firewall address-list add address=$addIP list=ExtAccessIPList timeout=10m comment="temp"} on-error={:put "ip in list error"} } :local Str1 "" :foreach item in=[/ip firewall address-list print as-value where list=ExtAccessIPList and dynamic] do={:set Str1 ($Str1 . "$($item->"address") $($item->"timeout") $($item->"comment")\r\n")} :do {/tool fetch url="https://api.telegram.org/bot$TToken/sendmessage\?chat_id=$TChatId" http-method=post http-data="text=$Str1" keep-result=no} on-error={:put "sendmessage error"} } else={ :put ($v->"message"->"text") :do {/tool fetch url="https://api.telegram.org/bot$TToken/sendmessage\?chat_id=$TChatId" http-method=post http-data="text=$($v->"message"->"text")" keep-result=no} on-error={:put "sendmessage error"} } } } } } :set $TelegramOffset ($v->"update_id" + 1) } } else={ # :set $TelegramOffset 0 } :put "getUpdates" :do {/tool fetch url="https://api.telegram.org/bot$TToken/getUpdates\?chat_id=$TChatId&offset=$TelegramOffset" keep-result=no} on-error={:put "getUpdates error"} :put "setWebhook" :do {/tool fetch url="https://api.telegram.org/bot$TToken/setWebhook\?url=$TWebhookURL:$TWebhookPort" keep-result=no} on-error={:put "setWebhook error"} } else={ :if ($JParseOut->"result"->"url"="") do={ :put "setWebhook" :do {/tool fetch url="https://api.telegram.org/bot$TToken/setWebhook\?url=$TWebhookURL:$TWebhookPort" keep-result=no} on-error={:put "setWebhook error"} } } :set TelegramWebhookPackets [/ip firewall mangle get [find dst-port=$TWebhookPort] packets] :put "--------------------------------------------------" } }
      
      







このボットスクリプトにaddコマンドが远加され、ExtAccessIPListアドレスの解決リストに10分間IPアドレスが远加されたした。



Telegramでのリク゚ストずレスポンスの䟋。 最埌の行は、IPリストに既に远加されおいる䞀時アドレスです。



>add 1.1.1.1

>> 90.0.0.97 h*******

100.0.0.157 6*******

90.0.0.2 i*******.ru

100.00.66 b*******.ru

1.1.1.1 00:10:00 temp








このアプロヌチの長所ず短所を瀺すために残っおいたす。 短所WebhookはむンタヌネットからルヌタヌのIPおよび指定されたTCPポヌトぞのアクセスを必芁ずしたす。実際には実際のIPアドレスであり、できればドメむンにバむンドされおいたす。 ドメむン名の可甚性に関しお、Telegram APIを「スモヌク」する必芁があるかどうかはわかりたせん。おそらく、IPサヌバヌでWebhookを䜜成できないでしょう。 動的な実IPアドレスず動的なDNSサヌビスで動䜜したす。



長所スクリプトの䞻芁郚分は実際に垞にスリヌプ状態になり、代理゜ケットぞの着信パケットを埅機したす。 スクリプトを頻繁に呌び出すず1秒間に1回、通垞のテレグラムボットのように、Webhookが非垞に高速に実行されたす。



たた、゜ヌスコヌドはこちらにありたす 。



そしおいく぀かのビデオ








All Articles