
Según el artículo, el
bot de Telegram para el administrador del sistema (el artículo no es mío, lo acabo de leer) quería compartir la experiencia de crear un bot de Telegram en PowerShell para administrar servidores de aplicaciones. Habrá texto, código y algunas fotos. La crítica constructiva es bienvenida (lo principal no es decir "¿por qué en PowerShell? Debería haber estado en Perl").
Creo que este artículo será más adecuado para los recién llegados a PowerShell, pero los administradores experimentados pueden ver algo útil aquí.
Trató de construir el artículo en partes, de simple a complejo. Quizás ocurra plagio, ¡ten cuidado!
Por lo tanto, tenemos la necesidad de administrar servicios o aplicaciones en varios servidores (detener, iniciar), reiniciar el servidor, mirar los registros y alguna otra información si es necesario. Todo esto lo quiero hacer (en realidad no), estar en el metro, en la tienda o incluso acostado en el sofá, sin una VPN y computadoras portátiles. De los requisitos (que fueron escritos, por supuesto, en la rodilla).
- Tareas fáciles de agregar / cambiar en el bot de Telegram
- Multitarea o concurrencia
- Interfaz de gestión intuitiva
- Al menos algo de seguridad
En algún momento, se decidió poner la configuración en un archivo separado, en nuestro caso xml (aquí alguien puede decir que vamos todos en json, pero lo hicimos en xml y quedamos satisfechos)
Comencemos desde el principio:
Parte 1: un bot de telegramas simple
Estamos buscando una carpeta bot (no un directorio) -
BotFather (@BotFather) en Telegram

Write
/ newbotA continuación, debe encontrar un nombre para el bot (en mi caso, llamé a Haaaabr específicamente para el artículo) y un nombre de usuario, que debería terminar en "bot" (Haaaabr_bot)
Después de eso, BotFather emitirá un token, que usaremos:

Luego puede cargar una imagen para el bot, poner Descripción, crear una lista de comandos, pero era demasiado vago.
Creamos un bot simple que recibirá mensajes y responderá a ellos.
Escribiré el código PS en partes e insertaré periódicamente el código completo para la referencia.
Como referencia, necesitaremos descripciones de llamadas de
API API de Telegram BotNecesitaremos 2 métodos:
getUpdates - recepción de mensajes por bot (script)
sendMessage : envío de mensajes por un bot (script) a un usuario
Allí, vemos que:
Hacer solicitudes
Todas las consultas a la API de Bot de Telegram deben ser atendidas a través de HTTPS y deben presentarse de esta forma: api.telegram.org/bot<token>/METHOD_NAME
Paso 1 - recibe mensajesVariables
Ahora comprobaremos que devuelve una llamada a
$ URL_get Invoke-RestMethod -Uri $URL_get
ok result
-- ------
True {}
No esta mal. Escribamos algo al bot:

Y lee:
ok result
-- ------
True {@{update_id=635172027; message=}, @{update_id=635172028; message=}
ok result
-- ------
True {@{update_id=635172027; message=}, @{update_id=635172028; message=}
}
Obviamente, necesitamos resultado. Debo decir de inmediato que solo estamos interesados en el último mensaje del usuario, así que así:
Ahora tenemos que confirmar que recibimos el mensaje. Esto se hace de todos
modos , a través del método
getUpdates con el parámetro
offset :
De forma predeterminada, se devuelven las actualizaciones que comienzan con la primera actualización no confirmada. Una actualización se considera confirmada tan pronto como se llama a getUpdates con un desplazamiento superior a su update_id
Hacer
Invoke-RestMethod "$($URL_get)?offset=$($($data.update_id)+1)" -Method Get | Out-Null
Y lo ponemos todo en un ciclo con un tiempo de espera de 1 segundo:
Ahora hagamos una función de lectura de mensajes. Porque necesitamos devolver varios valores de la función: decidimos usar una tabla hash (matriz nombrada / asociativa)
Script de recepción de mensajes Paso 2: envío de datosPara enviar un mensaje, necesitamos el método
sendMessage y
los campos
chat_id y
texto (el resto son
https://core.telegram.org/bots/api#sendmessage opcionales).
Función de corte inmediato
function sendMessage($URL, $chat_id, $text) {
Ahora llamando
sendMessage $URL_set <__id> "123"
recibe un mensaje en el carrito.
Paso 3: poner todo juntoA continuación se muestra todo el código para enviar y recibir mensajes.
Se puede construir una lógica adicional sobre la base de
$ return.text y, por ejemplo, la
instrucción 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("", "", "", ""))"} }
Emojiemoji se usa en el cmdlet Get-Random, no pude incrustarlos en el código del artículo, pero PS los entiende de forma nativa

Parte 2: necesita botones
En el bot de Telegram hay una opción para especificar una lista de comandos (se abre aquí en este icono

)
Inicialmente, hicimos exactamente eso: había un conjunto de comandos, pasamos los nombres de servidores o servicios allí como parámetros. Luego decidimos que necesitábamos avanzar más hacia interfaces amigables para el usuario y conectamos la funcionalidad de los botones.
Usado por las
llamadas de
sendMessage con el parámetro
reply_markupPara nuestra funcionalidad, utilizamos el tipo
InlineKeyboardMarkuphttps://core.telegram.org/bots/api#inlinekeyboardmarkup .
De la descripción se deduce que el campo
inline_keyboard es una matriz de una variedad de botones
(Matriz de matriz de InlineKeyboardButton)
Intentando hacer una prueba enviar botones
Obtenemos error:
Invoke-RestMethod: {"ok": false, "error_code": 400, "description": "Solicitud incorrecta: el campo \" inline_keyboard \ "del InlineKeyboardMarkup debería ser una matriz de matrices"}
En línea: 21 caracteres: 1Comprobando lo que contiene la variable
$ jsonConclusión
{ "reply_markup": { "inline_keyboard": [ "System.Collections.Hashtable System.Collections.Hashtable" ] }, "chat_id": **********, "text": "Test Text", "parse_mode": "Markdown" }
Aparentemente, de alguna manera, el objeto HashTable ("System.Collections.Hashtable System.Collections.Hashtable") para la API de telegramas no es muy transmisor. Un poco de Google y el resultado final: al convertir a Json, establezca la profundidad de conversión
Obtenemos los botones:

Hacemos una función para enviar botones, enviaremos una matriz de botones a la entrada
Poniendo todo junto cambiando un poco el bloque de interruptores Ahora para "hola", el bot nos enviará un par de botones. Queda por entender en qué botón hizo clic el usuario. La función ps actual getUpdates tiene una comprobación para
if($text)...
Cuando hace clic en el botón, no se devuelve ningún texto; en consecuencia, debe modificar la función. Haga clic en el botón

Y ejecute un fragmento de código para verificar el contenido de
$ data
Ningún mensaje llega más. En cambio, ahora
callback_query . Función de edición
Ahora la función devuelve
texto si hay un mensaje, o
callback_data si hubo un clic en el botón. En la etapa de prueba, detectaron un error al llamar:
sendMessage $URL_set $($return.chat_id) $($return.callback_data)
Invoke-RestMethod: {"ok": false, "error_code": 400, "description": "Solicitud incorrecta: no se pueden analizar entidades: No se puede encontrar el final de la entidad que comienza en el desplazamiento de bytes 8"}Como
parse_mode está configurado en
Markdown y el texto que se enviará
$return.callback_data = “Project1_CD”
necesita formatear el mensaje antes de enviarlo, más detalles aquí:
https://core.telegram.org/bots/api#formatting-optionso elimine el guión bajo "_"
Parte 3: haz la configuración
Es hora de poner todo en la configuración. Aquí todo es simple: hacemos 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>
Describimos tareas y especificamos un script o comando para cada tarea.
Comprobamos:
[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
Poniéndolo en el guión principal [xml]$xmlConfig = Get-Content -Path ("c:\Temp\Habr\telegram_bot.xml") $token = $xmlConfig.config.system.token $timeout = $xmlConfig.config.system.timeout.'#text'
Ahora, si escribe "hola", el bot devolverá una lista de botones que corresponde a las tareas descritas en los archivos xml. Habrá un comando o script en callback_data.
Si realiza cambios cosméticos, es deseable que los botones fueran 3-4 por línea, de lo contrario no se mostrarán completamente:

Haremos 3 botones por línea (máximo).
Esquemáticamente, la matriz de teclado debería verse así:

De esta manera:
Botón [i]: una matriz (asociativa) de la forma
$button = @{ "text" = $task.name; callback_data = $task.script}
Línea [1-3]: se trata de matrices (de botones) que almacenan matrices de botones (esto es importante)
Teclado: una variedad de Line'ov.
Modificar la función
sendKeyboard function sendKeyboard($URL, $buttons, $chat_id, $text) { $keyboard = @{}
Comprobamos:

Guión final [xml]$xmlConfig = Get-Content -Path ("c:\Temp\Habr\telegram_bot.xml") $token = $xmlConfig.config.system.token $timeout = $xmlConfig.config.system.timeout.'#text'
Parte 4: tareas y multitarea
Es hora de que el botón haga cosas.Para la multitarea, utilizaremos el mecanismo de trabajo. Revise este 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
Y después de 5 segundos hacemos: foreach($job in (Get-Job | Where {$_.State -eq "Completed"} )) { $output = Get-Job -ID $job.Id | Receive-Job $output $job | Remove-Job }
$ output debería devolver ipconfig con localhostAgregue esto al script principal, al bloque callback_data
Y esto esta abajo
Verificar, detectar errorInvoke-RestMethod: {"ok": false, "error_code": 400, "description": "Solicitud incorrecta: el mensaje es demasiado largo"}En Internet, encontramos información de que la longitud del mensaje no puede exceder los 4096 caracteres. Okey ... $output.Length
dice longitud 39. Pensamosdurante mucho tiempo lo que está mal, como resultado, intentamos este código: $text = $null foreach($string in $output) { $text = "$text`n$string" } sendMessage $URL_set $job.Name $text
Intentamos todo juntos [xml]$xmlConfig = Get-Content -Path ("c:\Temp\Habr\telegram_bot.xml") $token = $xmlConfig.config.system.token $timeout = $xmlConfig.config.system.timeout.'#text'
Ahora agreguemos un poco de seguridad.Agregue una nueva línea a la configuración xml, llámela usuarios e indique chat_id allí para aquellos que pueden comunicarse con el bot: <system> <token>*********************************</token> <timeout desc="bot check timeout in seconds">1</timeout> <users>111111111, 222222222</users> </system>
En el script obtendremos la matriz de usuarios $users = (($xmlConfig.config.system.users).Split(",")).Trim()
Y comprobar if($users -contains $return.chat_id) { ... }
Guión 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()
Parte 5: en conclusión
Verificamos la funcionalidad del bot: agregamos scripts que harán algo útil.Para las operaciones en 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
En este caso, la cuenta bajo la cual se ejecutará el script de bot de telegram debe tener los privilegios apropiados en la máquina remota.Además, no toqué la función de registro, pero aquí, creo, todo es simple, a voluntad, cada uno puede decidir qué quiere registrar y qué no.Seguramente alguien tendrá un problema al enviar un mensaje> 4096 caracteres, pero esto se resuelve con Substring y el bucle de envío.Y finalmente, el control remoto desde cualquier parte del mundo (desde casi cualquier lugar) es bueno, pero siempre existe el riesgo de que algo salga mal (alguien malo puede obtener un control de bot). Para este caso, acabamos de agregar Salir del script para una palabra específica switch -Wildcard ($return["text"]) { "**" {
Lo tengo todo