Telegram-bot para gerenciamento de infraestrutura

imagem

Com base no artigo, o bot do Telegram para o administrador do sistema (o artigo não é meu, acabei de ler) queria compartilhar a experiência de criar um bot do Telegram no PowerShell para gerenciar servidores de aplicativos. Haverá texto, código e algumas fotos. Críticas construtivas são bem-vindas (o principal não é dizer "por que no PowerShell? Deveria estar em perl").

Acho que este artigo será mais adequado para iniciantes no PowerShell, mas administradores experientes podem ver algo útil aqui.

Ele tentou criar o artigo em partes - do simples ao complexo. Talvez ocorra plágio, tenha cuidado!

Portanto, precisamos gerenciar serviços ou aplicativos em vários servidores (parar, iniciar), reiniciar o servidor, examinar os logs e outras informações, se necessário. Tudo isso que eu quero fazer (na verdade não), estar no metrô, na loja ou mesmo deitado no sofá, sem uma VPN e laptops. Dos requisitos (que foram escritos, é claro, no joelho).

  • Fácil de adicionar / alterar tarefas no bot Telegram
  • Multitarefa ou simultaneidade
  • Interface de gerenciamento intuitiva
  • Pelo menos alguma segurança

Em algum momento, foi decidido colocar a configuração em um arquivo separado - no nosso caso xml (aqui alguém pode dizer que vamos todos em json, mas fizemos em xml e ficamos satisfeitos)
Vamos começar do começo:

Parte 1: um simples bot de telegrama


Estamos procurando uma pasta bot (não um diretório) - BotFather (@BotFather) no Telegram

Botfather

Write / newbot
Em seguida, você precisa criar um nome para o bot (no meu caso, chamei o Haaaabr especificamente para o artigo) e um nome de usuário, que deve terminar em "bot" (Haaaabr_bot)

Depois disso, o BotFather emitirá um token, que usaremos:

imagem

Então você pode fazer upload de uma foto para o bot, colocar Descrição, criar uma lista de comandos, mas fiquei com preguiça.

Nós fazemos um bot simples que receberá mensagens e responderá a elas.

Escreverei o código PS em partes e periodicamente insiro o código completo para a referência.

Para referência, precisaremos de descrições de chamada da API da API do Telegram Bot

Vamos precisar de 2 métodos:

getUpdates - recebendo mensagens por bot (script)
sendMessage - enviando mensagens de um bot (script) para um usuário

Lá, vemos que:
Fazendo pedidos
Todas as consultas à API do Telegram Bot devem ser atendidas por HTTPS e precisam ser apresentadas neste formulário: api.telegram.org/bot<token>/METHOD_NAME

Etapa 1 - receber mensagens
Variáveis

# Token $token = "***********************" # Telegram URLs $URL_get = "https://api.telegram.org/bot$token/getUpdates" $URL_set = "https://api.telegram.org/bot$token/sendMessage" 

Agora vamos verificar se ele retorna uma chamada para $ URL_get

 Invoke-RestMethod -Uri $URL_get 

ok result
-- ------
True {}


Nada mal. Vamos escrever algo para o bot:

Olá

E leia:

 # Token $token = "***********************" # Telegram URLs $URL_get = "https://api.telegram.org/bot$token/getUpdates" $URL_set = "https://api.telegram.org/bot$token/sendMessage" Invoke-RestMethod -Uri $URL_get 


ok result
-- ------
True {@{update_id=635172027; message=}, @{update_id=635172028; message=}
ok result
-- ------
True {@{update_id=635172027; message=}, @{update_id=635172028; message=}
}

Obviamente, precisamos de resultado. Devo dizer imediatamente que estamos interessados ​​apenas na última mensagem do usuário, assim:

 # Token $token = "***********************" # Telegram URLs $URL_get = "https://api.telegram.org/bot$token/getUpdates" $URL_set = "https://api.telegram.org/bot$token/sendMessage" $json = Invoke-RestMethod -Uri $URL_get $data = $json.result | Select-Object -Last 1 $data.update_id $data.message.chat.id $data.message.text $data.message.chat.first_name $data.message.chat.last_name $data.message.chat.type $data.message.chat.username 

Agora precisamos confirmar que recebemos a mensagem. Isso é feito da mesma maneira, através do método getUpdates com o parâmetro offset :
Por padrão, as atualizações iniciadas com a atualização não confirmada mais antiga são retornadas. Uma atualização é considerada confirmada assim que getUpdates é chamado com um deslocamento maior que seu update_id

Fazer

 Invoke-RestMethod "$($URL_get)?offset=$($($data.update_id)+1)" -Method Get | Out-Null 

E jogamos tudo em um ciclo com um tempo limite de 1 segundo:

 # Token $token = "***********************" # Telegram URLs $URL_get = "https://api.telegram.org/bot$token/getUpdates" $URL_set = "https://api.telegram.org/bot$token/sendMessage" # timeout sec $timeout = 1 while($true) #   { $json = Invoke-RestMethod -Uri $URL_get $data = $json.result | Select-Object -Last 1 $data.update_id $data.message.chat.id $data.message.text $data.message.chat.first_name $data.message.chat.last_name $data.message.chat.type $data.message.chat.username Invoke-RestMethod "$($URL_get)?offset=$($($data.update_id)+1)" -Method Get | Out-Null Start-Sleep -s $timeout } 

Agora vamos criar uma função de leitura de mensagem. Porque precisamos retornar vários valores da função - decidimos usar um HashTable (matriz nomeada / associativa)

Script de recebimento de mensagens
 # Token $token = "***********************"# Telegram URLs $URL_get = "https://api.telegram.org/bot$token/getUpdates" $URL_set = "https://api.telegram.org/bot$token/sendMessage" # timeout sec $timeout = 1 function getUpdates($URL) { $json = Invoke-RestMethod -Uri $URL $data = $json.result | Select-Object -Last 1 #$data.update_id $chat_id = $data.message.chat.id $text = $data.message.text $f_name = $data.message.chat.first_name $l_name = $data.message.chat.last_name $type = $data.message.chat.type $username = $data.message.chat.username #   text  if($text) { # confirm Invoke-RestMethod "$($URL)?offset=$($($data.update_id)+1)" -Method Get | Out-Null # HashTable $ht = @{} $ht["chat_id"] = $chat_id $ht["text"] = $text $ht["f_name"] = $f_name $ht["l_name"] = $l_name $ht["username"] = $username return $ht } } while($true) #   { #   getUpdates $URL_get Start-Sleep -s $timeout } 



Etapa 2 - enviando dados
Para enviar uma mensagem, precisamos do método sendMessage e dos campos chat_id e text (o restante é opcional https://core.telegram.org/bots/api#sendmessage ).

Função de corte imediato

 function sendMessage($URL, $chat_id, $text) { #  HashTable,       $ht = @{ text = $text #    Markdown parse_mode = "Markdown" chat_id = $chat_id } #      json $json = $ht | ConvertTo-Json #   Invoke-RestMethod,        Invoke-WebRequest # Method Post - ..  ,   Get Invoke-RestMethod $URL -Method Post -ContentType 'application/json; charset=utf-8' -Body $json } 

Agora chamando

 sendMessage $URL_set <__id> "123" 

receba uma mensagem no carrinho.

Etapa 3 - Juntando tudo

Abaixo está todo o código para enviar e receber mensagens

Mostrar código
 # Token $token = "***********************" # Telegram URLs $URL_get = "https://api.telegram.org/bot$token/getUpdates" $URL_set = "https://api.telegram.org/bot$token/sendMessage" # timeout sec $timeout = 1 function getUpdates($URL) { $json = Invoke-RestMethod -Uri $URL $data = $json.result | Select-Object -Last 1 #$data.update_id $chat_id = $data.message.chat.id $text = $data.message.text $f_name = $data.message.chat.first_name $l_name = $data.message.chat.last_name $type = $data.message.chat.type $username = $data.message.chat.username #   text  if($text) { # confirm Invoke-RestMethod "$($URL)?offset=$($($data.update_id)+1)" -Method Get | Out-Null # HashTable $ht = @{} $ht["chat_id"] = $chat_id $ht["text"] = $text $ht["f_name"] = $f_name $ht["l_name"] = $l_name $ht["username"] = $username return $ht } } function sendMessage($URL, $chat_id, $text) { #  HashTable,       $ht = @{ text = $text #    Markdown parse_mode = "Markdown" chat_id = $chat_id } #      json $json = $ht | ConvertTo-Json #   Invoke-RestMethod,        Invoke-WebRequest # Method Post - ..  ,   Get Invoke-RestMethod $URL -Method Post -ContentType 'application/json; charset=utf-8' -Body $json | Out-Null } while($true) #   { $return = getUpdates $URL_get if($return) { # http://apps.timwhitlock.info/emoji/tables/unicode#block-1-emoticons sendMessage $URL_set $return.chat_id (Get-Random("", "", "", "")) } Start-Sleep -s $timeout } 


Lógica adicional pode ser construída com base em $ return.text e, por exemplo, na instrução switch :
 switch -Wildcard ($return["text"]) { "**" { sendMessage $URL_set $return.chat_id ", $($return["f_name"])" } "* ?*" { sendMessage $URL_set $return.chat_id "" } default {sendMessage $URL_set $return.chat_id "$(Get-Random("", "", "", ""))"} } 

Emoji:
emoji é usado no cmdlet Get-Random, não pude incorporá-los no código do artigo, mas o PS os entende nativamente
Seja aleatório

Parte 2: precisa de botões


No bot de telegrama, há uma opção para especificar uma lista de comandos (abre aqui neste ícone Ícone )
Inicialmente, fizemos exatamente isso - havia um conjunto de comandos, passamos os nomes de servidores ou serviços lá como parâmetros. Decidimos então que precisávamos avançar ainda mais em direção às interfaces amigáveis ​​ao usuário e conectamos a funcionalidade dos botões.

Usado pelas chamadas sendMessage com o parâmetro reply_markup

Para nossa funcionalidade, usamos o tipo InlineKeyboardMarkup
https://core.telegram.org/bots/api#inlinekeyboardmarkup .

A partir da descrição, segue-se que o campo inline_keyboard é uma matriz de uma matriz de botões
(Matriz de matriz de InlineKeyboardButton)

Tentando fazer um teste enviar botões

 # Token $token = "***********************" # Telegram URLs $URL_get = "https://api.telegram.org/bot$token/getUpdates" $URL_set = "https://api.telegram.org/bot$token/sendMessage" #   callback_data  ,     $button1 = @{ "text" = "Test1"; callback_data = "Test1_CD"} $button2 = @{ "text" = "Test2"; callback_data = "Test2_CD"} $keyboard = @{"inline_keyboard" = @(,@($button1, $button2))} $ht = @{ parse_mode = "Markdown" reply_markup = $keyboard chat_id = ******** #     Telegram ID text = "Test Text" } $json = $ht | ConvertTo-Json Invoke-RestMethod $URL_set -Method Post -ContentType 'application/json; charset=utf-8' -Body $json 

Temos erro:
Invoke-RestMethod: {"ok": false, "error_code": 400, "description": "Bad Request: field \" inline_keyboard \ "do InlineKeyboardMarkup deve ser uma matriz de matrizes"}
Na linha: 21 caracteres: 1

Verificando o que a variável $ json contém

Conclusão:

 { "reply_markup": { "inline_keyboard": [ "System.Collections.Hashtable System.Collections.Hashtable" ] }, "chat_id": **********, "text": "Test Text", "parse_mode": "Markdown" } 

Aparentemente, de alguma forma, o objeto HashTable ("System.Collections.Hashtable System.Collections.Hashtable") da API do telegrama não está transmitindo muito. Um pouco do Google e os resultados - ao converter para Json, defina a profundidade da conversão

 # Token $token = "***********************" # Telegram URLs $URL_get = "https://api.telegram.org/bot$token/getUpdates" $URL_set = "https://api.telegram.org/bot$token/sendMessage" #   callback_data  ,     $button1 = @{ "text" = "Test1"; callback_data = "Test1_CD"} $button2 = @{ "text" = "Test2"; callback_data = "Test2_CD"} $keyboard = @{"inline_keyboard" = @(,@($button1, $button2))} $ht = @{ parse_mode = "Markdown" reply_markup = $keyboard chat_id = ******** text = "Test Text" } $json = $ht | ConvertTo-Json -Depth 5 Invoke-RestMethod $URL_set -Method Post -ContentType 'application/json; charset=utf-8' -Body $json 

Temos os botões:

Botões

Fazemos uma função para enviar botões, enviaremos uma matriz de botões para a entrada

 # Token $token = "***********************" # Telegram URLs $URL_get = "https://api.telegram.org/bot$token/getUpdates" $URL_set = "https://api.telegram.org/bot$token/sendMessage" #   callback_data  ,     $button1 = @{ "text" = "Test1"; callback_data = "Test1_CD"} $button2 = @{ "text" = "Test2"; callback_data = "Test2_CD"} $buttons = ($button1, $button2) function sendKeyboard($URL, $buttons) { $keyboard = @{"inline_keyboard" = @(,$buttons)} $ht = @{ parse_mode = "Markdown" reply_markup = $keyboard chat_id = ******** text = "Test Text" } $json = $ht | ConvertTo-Json -Depth 5 Invoke-RestMethod $URL_set -Method Post -ContentType 'application/json; charset=utf-8' -Body $json } sendKeyboard $URL_set $buttons 

Juntando tudo, alterando um pouco o bloco do interruptor
 # Token $token = "***********************" # Telegram URLs $URL_get = "https://api.telegram.org/bot$token/getUpdates" $URL_set = "https://api.telegram.org/bot$token/sendMessage" # timeout sec $timeout = 1 function getUpdates($URL) { $json = Invoke-RestMethod -Uri $URL $data = $json.result | Select-Object -Last 1 #$data.update_id $chat_id = $data.message.chat.id $text = $data.message.text $f_name = $data.message.chat.first_name $l_name = $data.message.chat.last_name $type = $data.message.chat.type $username = $data.message.chat.username #   text  if($text) { # confirm Invoke-RestMethod "$($URL)?offset=$($($data.update_id)+1)" -Method Get | Out-Null # HashTable $ht = @{} $ht["chat_id"] = $chat_id $ht["text"] = $text $ht["f_name"] = $f_name $ht["l_name"] = $l_name $ht["username"] = $username return $ht } } function sendMessage($URL, $chat_id, $text) { #  HashTable,       $ht = @{ text = $text #    Markdown parse_mode = "Markdown" chat_id = $chat_id } #      json $json = $ht | ConvertTo-Json #   Invoke-RestMethod,        Invoke-WebRequest # Method Post - ..  ,   Get Invoke-RestMethod $URL -Method Post -ContentType 'application/json; charset=utf-8' -Body $json | Out-Null } function sendKeyboard($URL, $buttons, $chat_id, $text) { $keyboard = @{"inline_keyboard" = @(,$buttons)} $ht = @{ parse_mode = "Markdown" reply_markup = $keyboard chat_id = $chat_id text = $text } $json = $ht | ConvertTo-Json -Depth 5 Invoke-RestMethod $URL -Method Post -ContentType 'application/json; charset=utf-8' -Body $json } while($true) #   { $return = getUpdates $URL_get if($return) { # http://apps.timwhitlock.info/emoji/tables/unicode#block-1-emoticons #sendMessage $URL_set $return.chat_id (Get-Random("", "", "", "")) write-host "$($return["chat_id"])" switch -Wildcard ($return["text"]) { "**" { $button1 = @{ "text" = "Project1"; callback_data = "Project1_CD"} $button2 = @{ "text" = "Project2"; callback_data = "Project2_CD"} $buttons = ($button1, $button2) $text = "Available projects:" $chat_id = $return.chat_id sendKeyboard $URL_set $buttons $chat_id $text #sendMessage $URL_set $return.chat_id ", $($return["f_name"])" } "* ?*" { sendMessage $URL_set $return.chat_id "" } default {sendMessage $URL_set $return.chat_id "$(Get-Random("", "", "", ""))"} } } Start-Sleep -s $timeout } 


Agora, para "olá", o bot nos enviará alguns botões. Resta entender em qual botão o usuário clicou. A função ps atual getUpdates tem uma verificação para

 if($text)... 

Quando você clica no botão, nenhum texto é retornado; portanto, você precisa modificar a função. Clique no botão

Pushthebutton

E execute um pedaço de código para verificar o conteúdo de $ data

 # Token $token = "***********************" # Telegram URLs $URL_get = "https://api.telegram.org/bot$token/getUpdates" $URL_set = "https://api.telegram.org/bot$token/sendMessage" # timeout sec $timeout = 1 function getUpdates($URL) { $json = Invoke-RestMethod -Uri $URL $data = $json.result | Select-Object -Last 1 $data <# $chat_id = $data.message.chat.id $text = $data.message.text $f_name = $data.message.chat.first_name $l_name = $data.message.chat.last_name $type = $data.message.chat.type $username = $data.message.chat.username #   text  if($text) { # confirm Invoke-RestMethod "$($URL)?offset=$($($data.update_id)+1)" -Method Get | Out-Null # HashTable $ht = @{} $ht["chat_id"] = $chat_id $ht["text"] = $text $ht["f_name"] = $f_name $ht["l_name"] = $l_name $ht["username"] = $username return $ht } #> } getUpdates $URL_get 

Nenhuma mensagem chega mais. Em vez disso, agora callback_query . Editar função

 # Token $token = "***********************" # Telegram URLs $URL_get = "https://api.telegram.org/bot$token/getUpdates" $URL_set = "https://api.telegram.org/bot$token/sendMessage" # timeout sec $timeout = 1 function getUpdates($URL) { $json = Invoke-RestMethod -Uri $URL $data = $json.result | Select-Object -Last 1 #    if($data.callback_query) { $callback_data = $data.callback_query.data $chat_id = $data.callback_query.from.id $f_name = $data.callback_query.from.first_name $l_name = $data.callback_query.from.last_name $username = $data.callback_query.from.username } #   elseif($data.message) { $chat_id = $data.message.chat.id $text = $data.message.text $f_name = $data.message.chat.first_name $l_name = $data.message.chat.last_name $type = $data.message.chat.type $username = $data.message.chat.username } $ht = @{} $ht["chat_id"] = $chat_id $ht["text"] = $text $ht["f_name"] = $f_name $ht["l_name"] = $l_name $ht["username"] = $username $ht["callback_data"] = $callback_data # confirm Invoke-RestMethod "$($URL)?offset=$($($data.update_id)+1)" -Method Get | Out-Null return $ht } getUpdates $URL_get 

Agora, a função retorna texto se houver uma mensagem ou callback_data se houver um clique no botão. Na fase de teste, eles detectaram um erro ao chamar:

 sendMessage $URL_set $($return.chat_id) $($return.callback_data) 

Invoke-RestMethod: {"ok": false, "error_code": 400, "description": "Solicitação incorreta: não é possível analisar entidades: não é possível encontrar o final da entidade iniciando no deslocamento de bytes 8"}

Como parse_mode está definido como Markdown , e o texto a ser enviado

 $return.callback_data = “Project1_CD” 

você precisa formatar a mensagem antes de enviar, mais detalhes aqui:
https://core.telegram.org/bots/api#formatting-options
ou remova o sublinhado "_"

Roteiro final
 # Token $token = "***********************" # Telegram URLs $URL_get = "https://api.telegram.org/bot$token/getUpdates" $URL_set = "https://api.telegram.org/bot$token/sendMessage" # timeout sec $timeout = 1 function getUpdates($URL) { $json = Invoke-RestMethod -Uri $URL $data = $json.result | Select-Object -Last 1 #   $text = $null $callback_data = $null #    if($data.callback_query) { $callback_data = $data.callback_query.data $chat_id = $data.callback_query.from.id $f_name = $data.callback_query.from.first_name $l_name = $data.callback_query.from.last_name $username = $data.callback_query.from.username } #   elseif($data.message) { $chat_id = $data.message.chat.id $text = $data.message.text $f_name = $data.message.chat.first_name $l_name = $data.message.chat.last_name $type = $data.message.chat.type $username = $data.message.chat.username } $ht = @{} $ht["chat_id"] = $chat_id $ht["text"] = $text $ht["f_name"] = $f_name $ht["l_name"] = $l_name $ht["username"] = $username $ht["callback_data"] = $callback_data # confirm Invoke-RestMethod "$($URL)?offset=$($($data.update_id)+1)" -Method Get | Out-Null return $ht } function sendMessage($URL, $chat_id, $text) { #  HashTable,       $ht = @{ text = $text #    Markdown parse_mode = "Markdown" chat_id = $chat_id } #      json $json = $ht | ConvertTo-Json #   Invoke-RestMethod,        Invoke-WebRequest # Method Post - ..  ,   Get Invoke-RestMethod $URL -Method Post -ContentType 'application/json; charset=utf-8' -Body $json | Out-Null } function sendKeyboard($URL, $buttons, $chat_id, $text) { $keyboard = @{"inline_keyboard" = @(,$buttons)} $ht = @{ parse_mode = "Markdown" reply_markup = $keyboard chat_id = $chat_id text = $text } $json = $ht | ConvertTo-Json -Depth 5 Invoke-RestMethod $URL -Method Post -ContentType 'application/json; charset=utf-8' -Body $json } while($true) #   { $return = getUpdates $URL_get #$return #    if($return.text) { # http://apps.timwhitlock.info/emoji/tables/unicode#block-1-emoticons #sendMessage $URL_set $return.chat_id (Get-Random("", "", "", "")) write-host "$($return["chat_id"])" switch -Wildcard ($return["text"]) { "**" { $button1 = @{ "text" = "Project1"; callback_data = "Project1CD"} $button2 = @{ "text" = "Project2"; callback_data = "Project2CD"} $buttons = ($button1, $button2) $text = "Available projects:" $chat_id = $return.chat_id sendKeyboard $URL_set $buttons $chat_id $text #sendMessage $URL_set $return.chat_id ", $($return["f_name"])" } "* ?*" { sendMessage $URL_set $return.chat_id "" } default {sendMessage $URL_set $return.chat_id "$(Get-Random("", "", "", ""))"} } } #      elseif($return.callback_data) { sendMessage $URL_set $($return.chat_id) $($return.callback_data) } Start-Sleep -s $timeout } 


Parte 3: faça a configuração

É hora de colocar tudo na configuração. Tudo é simples aqui - fazemos xml:

 <config> <system> <token>***********************</token> <timeout desc="bot check timeout in seconds">1</timeout> </system> <tasks> <task name=" " script="c:\Temp\Habr\reboot_all.ps1"></task> <task name=" " script="c:\Temp\Habr\status.ps1"></task> <task name="ipconfig1" script="ipconfig"></task> <task name="ipconfig2" script="ipconfig"></task> <task name="ipconfig3" script="ipconfig"></task> <task name="ipconfig4" script="ipconfig"></task> <task name="ipconfig5" script="ipconfig"></task> </tasks> </config> 

Descrevemos tarefas e especificamos um script ou comando para cada tarefa.
Verificamos:

 [xml]$xmlConfig = Get-Content -Path ("c:\Temp\Habr\telegram_bot.xml") $token = $xmlConfig.config.system.token $timeout = $xmlConfig.config.system.timeout.'#text' foreach($task in $xmlConfig.config.tasks.task) { $task.name #   $task.script #  } 

Colocando no script principal
 [xml]$xmlConfig = Get-Content -Path ("c:\Temp\Habr\telegram_bot.xml") $token = $xmlConfig.config.system.token $timeout = $xmlConfig.config.system.timeout.'#text' # Telegram URLs $URL_get = "https://api.telegram.org/bot$token/getUpdates" $URL_set = "https://api.telegram.org/bot$token/sendMessage" function getUpdates($URL) { $json = Invoke-RestMethod -Uri $URL $data = $json.result | Select-Object -Last 1 #   $text = $null $callback_data = $null #    if($data.callback_query) { $callback_data = $data.callback_query.data $chat_id = $data.callback_query.from.id $f_name = $data.callback_query.from.first_name $l_name = $data.callback_query.from.last_name $username = $data.callback_query.from.username } #   elseif($data.message) { $chat_id = $data.message.chat.id $text = $data.message.text $f_name = $data.message.chat.first_name $l_name = $data.message.chat.last_name $type = $data.message.chat.type $username = $data.message.chat.username } $ht = @{} $ht["chat_id"] = $chat_id $ht["text"] = $text $ht["f_name"] = $f_name $ht["l_name"] = $l_name $ht["username"] = $username $ht["callback_data"] = $callback_data # confirm Invoke-RestMethod "$($URL)?offset=$($($data.update_id)+1)" -Method Get | Out-Null return $ht } function sendMessage($URL, $chat_id, $text) { #  HashTable,       $ht = @{ text = $text #    Markdown parse_mode = "Markdown" chat_id = $chat_id } #      json $json = $ht | ConvertTo-Json #   Invoke-RestMethod,        Invoke-WebRequest # Method Post - ..  ,   Get Invoke-RestMethod $URL -Method Post -ContentType 'application/json; charset=utf-8' -Body $json | Out-Null } function sendKeyboard($URL, $buttons, $chat_id, $text) { $keyboard = @{"inline_keyboard" = @(,$buttons)} $ht = @{ parse_mode = "Markdown" reply_markup = $keyboard chat_id = $chat_id text = $text } $json = $ht | ConvertTo-Json -Depth 5 Invoke-RestMethod $URL -Method Post -ContentType 'application/json; charset=utf-8' -Body $json } while($true) #   { $return = getUpdates $URL_get #    if($return.text) { # http://apps.timwhitlock.info/emoji/tables/unicode#block-1-emoticons #sendMessage $URL_set $return.chat_id (Get-Random("", "", "", "")) write-host "$($return["chat_id"])" switch -Wildcard ($return["text"]) { "**" { #   $buttons = @() foreach($task in $xmlConfig.config.tasks.task) { $button = @{ "text" = $task.name; callback_data = $task.script} $buttons += $button } $text = "Available tasks:" $chat_id = $return.chat_id sendKeyboard $URL_set $buttons $chat_id $text #sendMessage $URL_set $return.chat_id ", $($return["f_name"])" } "* ?*" { sendMessage $URL_set $return.chat_id "" } default {sendMessage $URL_set $return.chat_id "$(Get-Random("", "", "", ""))"} } } #      elseif($return.callback_data) { sendMessage $URL_set $($return.chat_id) $($return.callback_data) } Start-Sleep -s $timeout } 


Agora, se você escrever “olá”, o bot retornará uma lista de botões que correspondem às tarefas descritas nos arquivos xml. Haverá um comando ou script em callback_data.

Se você fizer alterações cosméticas - é desejável que os botões sejam de 3 a 4 por linha, caso contrário eles não serão totalmente exibidos:

Teclado

Faremos 3 botões por linha (máximo).

Esquematicamente, a matriz do teclado deve ficar assim:

Teclado

Desta forma:
Botão [i] - uma matriz (associativa) do formulário

 $button = @{ "text" = $task.name; callback_data = $task.script} 

Linha [1-3] - são matrizes (de botões) que armazenam matrizes de botões (isso é importante)
Teclado - uma matriz de Line'ov.

Modifique a função sendKeyboard

 function sendKeyboard($URL, $buttons, $chat_id, $text) { $keyboard = @{} #    ArrayList, .       -   $lines = 3 $buttons_line = New-Object System.Collections.ArrayList for($i=0; $i -lt $buttons.Count; $i++) { #     (line).    3 -  line  keyboard $buttons_line.Add($buttons[$i]) | Out-Null #   -      0 if( ($i + 1 )%$lines -eq 0 ) { #     keyboard $keyboard["inline_keyboard"] += @(,@($buttons_line)) $buttons_line.Clear() } } #     $keyboard["inline_keyboard"] += @(,@($buttons_line)) #$keyboard = @{"inline_keyboard" = @(,$buttons)} $ht = @{ parse_mode = "Markdown" reply_markup = $keyboard chat_id = $chat_id text = $text } $json = $ht | ConvertTo-Json -Depth 5 Invoke-RestMethod $URL -Method Post -ContentType 'application/json; charset=utf-8' -Body $json } 

Verificamos:

Keyboard_Telegram

Roteiro final
 [xml]$xmlConfig = Get-Content -Path ("c:\Temp\Habr\telegram_bot.xml") $token = $xmlConfig.config.system.token $timeout = $xmlConfig.config.system.timeout.'#text' # Telegram URLs $URL_get = "https://api.telegram.org/bot$token/getUpdates" $URL_set = "https://api.telegram.org/bot$token/sendMessage" function getUpdates($URL) { $json = Invoke-RestMethod -Uri $URL $data = $json.result | Select-Object -Last 1 #   $text = $null $callback_data = $null #    if($data.callback_query) { $callback_data = $data.callback_query.data $chat_id = $data.callback_query.from.id $f_name = $data.callback_query.from.first_name $l_name = $data.callback_query.from.last_name $username = $data.callback_query.from.username } #   elseif($data.message) { $chat_id = $data.message.chat.id $text = $data.message.text $f_name = $data.message.chat.first_name $l_name = $data.message.chat.last_name $type = $data.message.chat.type $username = $data.message.chat.username } $ht = @{} $ht["chat_id"] = $chat_id $ht["text"] = $text $ht["f_name"] = $f_name $ht["l_name"] = $l_name $ht["username"] = $username $ht["callback_data"] = $callback_data # confirm Invoke-RestMethod "$($URL)?offset=$($($data.update_id)+1)" -Method Get | Out-Null return $ht } function sendMessage($URL, $chat_id, $text) { #  HashTable,       $ht = @{ text = $text #    Markdown parse_mode = "Markdown" chat_id = $chat_id } #      json $json = $ht | ConvertTo-Json #   Invoke-RestMethod,        Invoke-WebRequest # Method Post - ..  ,   Get Invoke-RestMethod $URL -Method Post -ContentType 'application/json; charset=utf-8' -Body $json | Out-Null } function sendKeyboard($URL, $buttons, $chat_id, $text) { $keyboard = @{} #    ArrayList, .       -   $lines = 3 $buttons_line = New-Object System.Collections.ArrayList for($i=0; $i -lt $buttons.Count; $i++) { #     (line).    3 -  line  keyboard $buttons_line.Add($buttons[$i]) | Out-Null #   -      0 if( ($i + 1 )%$lines -eq 0 ) { #     keyboard $keyboard["inline_keyboard"] += @(,@($buttons_line)) $buttons_line.Clear() } } #     $keyboard["inline_keyboard"] += @(,@($buttons_line)) $ht = @{ parse_mode = "Markdown" reply_markup = $keyboard chat_id = $chat_id text = $text } $json = $ht | ConvertTo-Json -Depth 5 Invoke-RestMethod $URL -Method Post -ContentType 'application/json; charset=utf-8' -Body $json } while($true) #   { $return = getUpdates $URL_get #$return.text = "" #    if($return.text) { # http://apps.timwhitlock.info/emoji/tables/unicode#block-1-emoticons #sendMessage $URL_set $return.chat_id (Get-Random("", "", "", "")) switch -Wildcard ($return["text"]) { "**" { #   $buttons = @() foreach($task in $xmlConfig.config.tasks.task) { $i++ $button = @{ "text" = $task.name; callback_data = $task.script} $buttons += $button } $text = "Available tasks:" $chat_id = $return.chat_id sendKeyboard $URL_set $buttons $chat_id $text #sendMessage $URL_set $return.chat_id ", $($return["f_name"])" } "* ?*" { sendMessage $URL_set $return.chat_id "" } default {sendMessage $URL_set $return.chat_id "$(Get-Random("", "", "", ""))"} } } #      elseif($return.callback_data) { #sendMessage $URL_set $($return.chat_id) $($return.callback_data) write-host "$($return.chat_id) $($return.callback_data)" } Start-Sleep -s $timeout } 


Parte 4: tarefas e multitarefa


É hora do botão fazer as coisas.

Para multitarefa, usaremos o mecanismo Job. Verifique este trecho de código:

 $script = "ipconfig" $script_block = { Param($script) ; Invoke-Expression $script } $job_name = "TestJob" Start-Job -ScriptBlock $script_block -ArgumentList $script -Name $job_name | Out-Null 

E após 5 segundos, fazemos:
 foreach($job in (Get-Job | Where {$_.State -eq "Completed"} )) { $output = Get-Job -ID $job.Id | Receive-Job $output $job | Remove-Job } 

$ output deve retornar ipconfig com localhost

Adicione isso ao script principal, ao bloco callback_data

 #      elseif($return.callback_data) { $script = $($return.callback_data) $job_name = $($return.chat_id) $script_block = { Param($script) ; Invoke-Expression $script } # Job Start-Job -ScriptBlock $script_block -ArgumentList $script -Name $job_name | Out-Null } 

E isso está abaixo

 # ,  job'   foreach($job in (Get-Job | Where {$_.State -eq "Completed"} )) { $output = Get-Job -ID $job.Id | Receive-Job #   ,   job sendMessage $URL_set $job.Name $output $job | Remove-Job #     $text = "Available tasks:" sendKeyboard $URL_set $buttons $job.Name $text } 

Verifique, pegue o erro
Invoke-RestMethod: {"ok": false, "error_code": 400, "description": "Solicitação incorreta: a mensagem é muito longa"}

Na Internet, encontramos informações de que o tamanho da mensagem não pode exceder 4096 caracteres. Okey ...

 $output.Length 
diz comprimento 39. Pensamos
por um longo tempo o que está errado; como resultado, tentamos este trecho de código:

 $text = $null foreach($string in $output) { $text = "$text`n$string" } sendMessage $URL_set $job.Name $text 

Tentamos tudo juntos
 [xml]$xmlConfig = Get-Content -Path ("c:\Temp\Habr\telegram_bot.xml") $token = $xmlConfig.config.system.token $timeout = $xmlConfig.config.system.timeout.'#text' # Telegram URLs $URL_get = "https://api.telegram.org/bot$token/getUpdates" $URL_set = "https://api.telegram.org/bot$token/sendMessage" function getUpdates($URL) { $json = Invoke-RestMethod -Uri $URL $data = $json.result | Select-Object -Last 1 #   $text = $null $callback_data = $null #    if($data.callback_query) { $callback_data = $data.callback_query.data $chat_id = $data.callback_query.from.id $f_name = $data.callback_query.from.first_name $l_name = $data.callback_query.from.last_name $username = $data.callback_query.from.username } #   elseif($data.message) { $chat_id = $data.message.chat.id $text = $data.message.text $f_name = $data.message.chat.first_name $l_name = $data.message.chat.last_name $type = $data.message.chat.type $username = $data.message.chat.username } $ht = @{} $ht["chat_id"] = $chat_id $ht["text"] = $text $ht["f_name"] = $f_name $ht["l_name"] = $l_name $ht["username"] = $username $ht["callback_data"] = $callback_data # confirm Invoke-RestMethod "$($URL)?offset=$($($data.update_id)+1)" -Method Get | Out-Null return $ht } function sendMessage($URL, $chat_id, $text) { #  HashTable,       $ht = @{ text = $text #    Markdown parse_mode = "Markdown" chat_id = $chat_id } #      json $json = $ht | ConvertTo-Json #   Invoke-RestMethod,        Invoke-WebRequest # Method Post - ..  ,   Get Invoke-RestMethod $URL -Method Post -ContentType 'application/json; charset=utf-8' -Body $json | Out-Null } function sendKeyboard($URL, $buttons, $chat_id, $text) { $keyboard = @{} $lines = 3 #    ArrayList, .       -   $buttons_line = New-Object System.Collections.ArrayList for($i=0; $i -lt $buttons.Count; $i++) { #     (line).    3 -  line  keyboard $buttons_line.Add($buttons[$i]) | Out-Null #   -      0 if( ($i + 1 )%$lines -eq 0 ) { #     keyboard $keyboard["inline_keyboard"] += @(,@($buttons_line)) $buttons_line.Clear() } } #     $keyboard["inline_keyboard"] += @(,@($buttons_line)) $ht = @{ parse_mode = "Markdown" reply_markup = $keyboard chat_id = $chat_id text = $text } $json = $ht | ConvertTo-Json -Depth 5 Invoke-RestMethod $URL -Method Post -ContentType 'application/json; charset=utf-8' -Body $json } while($true) #   { $return = getUpdates $URL_get #$return.text = "" #    if($return.text) { # http://apps.timwhitlock.info/emoji/tables/unicode#block-1-emoticons #sendMessage $URL_set $return.chat_id (Get-Random("", "", "", "")) switch -Wildcard ($return["text"]) { "**" { #   $buttons = @() foreach($task in $xmlConfig.config.tasks.task) { $i++ $button = @{ "text" = $task.name; callback_data = $task.script} $buttons += $button } $text = "Available tasks:" $chat_id = $return.chat_id sendKeyboard $URL_set $buttons $chat_id $text #sendMessage $URL_set $return.chat_id ", $($return["f_name"])" } "* ?*" { sendMessage $URL_set $return.chat_id "" } default {sendMessage $URL_set $return.chat_id "$(Get-Random("", "", "", ""))"} } } #      elseif($return.callback_data) { $script = $($return.callback_data) $job_name = $($return.chat_id) write-host "$script $job_name" $script_block = { Param($script) ; Invoke-Expression $script } # Job Start-Job -ScriptBlock $script_block -ArgumentList $script -Name $job_name | Out-Null } # ,  job'   foreach($job in (Get-Job | Where {$_.State -eq "Completed"} )) { $output = Get-Job -ID $job.Id | Receive-Job $text = $null foreach($string in $output) { $text = "$text`n$string" } #   ,   job sendMessage $URL_set $job.Name $text $job | Remove-Job #     $text = "Available tasks:" sendKeyboard $URL_set $buttons $job.Name $text } Start-Sleep -s $timeout } 


Saída

Agora vamos estragar um pouco de segurança:

adicione uma nova linha à configuração xml, chame-a de users e indique chat_id lá para quem puder se comunicar com o bot:

  <system> <token>*********************************</token> <timeout desc="bot check timeout in seconds">1</timeout> <users>111111111, 222222222</users> </system> 

No script, obteremos a matriz de usuários

 $users = (($xmlConfig.config.system.users).Split(",")).Trim() 

E verifique

  if($users -contains $return.chat_id) { ... } 

Script completo
 [xml]$xmlConfig = Get-Content -Path ("c:\Temp\Habr\telegram_bot.xml") $token = $xmlConfig.config.system.token $timeout = $xmlConfig.config.system.timeout.'#text' $users = (($xmlConfig.config.system.users).Split(",")).Trim() # Telegram URLs $URL_get = "https://api.telegram.org/bot$token/getUpdates" $URL_set = "https://api.telegram.org/bot$token/sendMessage" function getUpdates($URL) { $json = Invoke-RestMethod -Uri $URL $data = $json.result | Select-Object -Last 1 #   $text = $null $callback_data = $null #    if($data.callback_query) { $callback_data = $data.callback_query.data $chat_id = $data.callback_query.from.id $f_name = $data.callback_query.from.first_name $l_name = $data.callback_query.from.last_name $username = $data.callback_query.from.username } #   elseif($data.message) { $chat_id = $data.message.chat.id $text = $data.message.text $f_name = $data.message.chat.first_name $l_name = $data.message.chat.last_name $type = $data.message.chat.type $username = $data.message.chat.username } $ht = @{} $ht["chat_id"] = $chat_id $ht["text"] = $text $ht["f_name"] = $f_name $ht["l_name"] = $l_name $ht["username"] = $username $ht["callback_data"] = $callback_data # confirm Invoke-RestMethod "$($URL)?offset=$($($data.update_id)+1)" -Method Get | Out-Null return $ht } function sendMessage($URL, $chat_id, $text) { #  HashTable,       $ht = @{ text = $text #    Markdown parse_mode = "Markdown" chat_id = $chat_id } #      json $json = $ht | ConvertTo-Json #   Invoke-RestMethod,        Invoke-WebRequest # Method Post - ..  ,   Get Invoke-RestMethod $URL -Method Post -ContentType 'application/json; charset=utf-8' -Body $json | Out-Null } function sendKeyboard($URL, $buttons, $chat_id, $text) { $keyboard = @{} $lines = 3 #    ArrayList, .       -   $buttons_line = New-Object System.Collections.ArrayList for($i=0; $i -lt $buttons.Count; $i++) { #     (line).    3 -  line  keyboard $buttons_line.Add($buttons[$i]) | Out-Null #   -      0 if( ($i + 1 )%$lines -eq 0 ) { #     keyboard $keyboard["inline_keyboard"] += @(,@($buttons_line)) $buttons_line.Clear() } } #     $keyboard["inline_keyboard"] += @(,@($buttons_line)) $ht = @{ parse_mode = "Markdown" reply_markup = $keyboard chat_id = $chat_id text = $text } $json = $ht | ConvertTo-Json -Depth 5 Invoke-RestMethod $URL -Method Post -ContentType 'application/json; charset=utf-8' -Body $json } while($true) #   { $return = getUpdates $URL_get if($users -contains $return.chat_id) { #    if($return.text) { #write-host $return.chat_id # http://apps.timwhitlock.info/emoji/tables/unicode#block-1-emoticons #sendMessage $URL_set $return.chat_id (Get-Random("", "", "", "")) switch -Wildcard ($return["text"]) { "**" { #   $buttons = @() foreach($task in $xmlConfig.config.tasks.task) { $i++ $button = @{ "text" = $task.name; callback_data = $task.script} $buttons += $button } $text = "Available tasks:" $chat_id = $return.chat_id sendKeyboard $URL_set $buttons $chat_id $text #sendMessage $URL_set $return.chat_id ", $($return["f_name"])" } "* ?*" { sendMessage $URL_set $return.chat_id "" } default {sendMessage $URL_set $return.chat_id "$(Get-Random("", "", "", ""))"} } } #      elseif($return.callback_data) { $script = $($return.callback_data) $job_name = $($return.chat_id) write-host "$script $job_name" $script_block = { Param($script) ; Invoke-Expression $script } # Job Start-Job -ScriptBlock $script_block -ArgumentList $script -Name $job_name | Out-Null } # ,  job'   foreach($job in (Get-Job | Where {$_.State -eq "Completed"} )) { $output = Get-Job -ID $job.Id | Receive-Job $text = $null foreach($string in $output) { $text = "$text`n$string" } #   ,   job sendMessage $URL_set $job.Name $text $job | Remove-Job #     $text = "Available tasks:" sendKeyboard $URL_set $buttons $job.Name $text } } else { if($return.text) { sendMessage $URL_set $return.chat_id "  ?    !" } } Start-Sleep -s $timeout } 


Parte 5: em conclusão


Verificamos a funcionalidade do bot - adicionamos scripts para fazer algo útil.Para
operações em servidores remotos, usamos Invoke-Command seguido de Write-Output

 $hostname = "hostname" $service = "MSSQLSERVER" $output = Invoke-Command -ComputerName $hostname -ScriptBlock{param($service); (Get-Service -Name $service).Status} -ArgumentList $service write-output $output.Value 

Nesse caso, a conta na qual o script bot de telegrama será executado deve ter os privilégios apropriados na máquina remota.

Além disso, eu não toquei na funcionalidade de registro, mas aqui, acho, tudo é simples, à vontade, todos podem decidir o que ele deseja registrar e o que não.

Certamente alguém terá problemas ao enviar uma mensagem com mais de 4096 caracteres, mas isso é resolvido pelo Substring e pelo ciclo de envio.

E finalmente - o controle remoto de qualquer lugar do mundo (de quase qualquer lugar) é bom, mas sempre existe o risco de que algo dê errado (alguém ruim pode obter um controle de bot). Nesse caso, adicionamos Exit do script para uma palavra específica

  switch -Wildcard ($return["text"]) { "**" { #   $buttons = @() foreach($task in $xmlConfig.config.tasks.task) { $i++ $button = @{ "text" = $task.name; callback_data = $task.script} $buttons += $button } $text = "Available tasks:" $chat_id = $return.chat_id sendKeyboard $URL_set $buttons $chat_id $text #sendMessage $URL_set $return.chat_id ", $($return["f_name"])" } "* ?*" { sendMessage $URL_set $return.chat_id "" } "!" {sendMessage $URL_set $return.chat_id "bb" ; Exit} default {sendMessage $URL_set $return.chat_id "$(Get-Random("", "", "", ""))"} } 

Eu tenho tudo.

Source: https://habr.com/ru/post/pt483660/


All Articles