Вы находитесь на странице: 1из 33

Все потоки Разработка Научпоп Администрирование Дизайн Менеджмент Маркетинг Войти Регистрация

Andrey_O 12 января 2020 в 22:46

Telegram-бот для управления инфраструктурой


Системное администрирование, PowerShell

Из песочницы

Реклама

По мотивам статьи Телеграмм-бот для системного администратора (статья не моя, я только прочитал) захотел поделиться
опытом создания Telegram-бота на PowerShell для управления серверами приложений. Будет текст, код и немножко картинок.
Конструктивная критика приветствуется ( главное чтобы не звучало «зачем на PowerShell? Надо было на perl» ).

Думаю что статья больше подойдет «новичкам» в PowerShell, но и опытные администраторы могут что-то полезное здесь
увидеть.

Саму статью старался построить по частям – от простого к сложному. Возможно, встретится плагиат, будьте бдительны!

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

• Простота добавления/изменения задач в Telegram-бот

• Многозадачность или параллелизация

• «Понятный» интерфейс управления

• Хоть какая-то безопасность

В какой то момент было решено выносить конфиг в отдельный файл – в нашем случае xml (тут кто-то может сказать, что
давайте всё в json, но мы сделали в xml и были довольны)
Начнем с начала:
Часть 1: простой телеграм-бот

Ищем папку-бота (не каталог) – BotFather (@BotFather) в Telegram

Пишем /newbot
Далее, нужно придумать имя боту (в моем случае я назвал Haaaabr специально для статьи) и username, который должен
заканчиваться на «bot» (Haaaabr_bot)

После этого BotFather выдаст токен, который мы и будем использовать:


Дальше можно загрузить для бота картинку, поставить Description, создать список команд, но мне было лень.

Делаем простого бота, который будет принимать сообщения и отвечать на них.

Я буду писать код PS частями и периодически вставлять full-код для референса.

Для справки нам понадобятся описания вызовов API Telegram Bot API

Нам будет нужно 2 метода:

getUpdates – получение ботом(скриптом) сообщений


sendMessage – отправка сообщений ботом(скриптом) пользователю

Там же, видим, что:

Making requests
All queries to the Telegram Bot API must be served over HTTPS and need to be presented in this form:
api.telegram.org/bot<token>/METHOD_NAME

Шаг 1 – прием сообщений


Переменные

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

Теперь будем проверять, что отдает вызов $URL_get

Invoke-RestMethod -Uri $URL_get

ok result
-- ------
True {}
Нот бэд. Напишем что-нибудь боту:

И прочитаем:

# 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=}}

Очевидно, что нам нужен result. Сразу скажу, что нас интересует только последнее сообщение от пользователя, поэтому так:

# 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
Теперь нужно сделать confirm, что мы получили сообщение. Делается это все также, через метод getUpdates с параметром
offset:

By default, updates starting with the earliest unconfirmed update are returned. An update is considered confirmed as soon as
getUpdates is called with an offset higher than its update_id

Делаем

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

И кидаем это все в цикл c таймаутом в 1 секунду:

# 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
}

Теперь сделаем из этого функцию чтения сообщений. Т.к. нам нужно возвращать несколько значений из функции – решили
использовать HashTable (именованный/ассоциативный массив)

Скрипт получения сообщений

# 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
}

Шаг 2 – отправка данных


Для отправки сообщения нам нужен метод sendMessage и поля chat_id и text (остальные опционально
https://core.telegram.org/bots/api#sendmessage).

Сразу запилим функцию

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
}

Теперь, вызвав

sendMessage $URL_set <ваш_телеграм_id> "Тест123"

получим сообщение в телеге.

Шаг 3 – собираем все вместе


Ниже весь код для отправки-получения сообщений

Показать код

# 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
}
Дальнейшую логику можно строить на основе $return.text и, например, оператора 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:

в командлете Get-Random используются emoji, в код в статье у меня их встроить не получилось, но PS понимает их нативно

Часть 2: нужны кнопки

В телеграм боте есть опция задания списка команд (открывается вот по этому значку )
Первоначально мы так и сделали – был набор команд, в качестве параметров передавали туда имена серверов или сервисов.
Потом решили, что нужно двигаться дальше в сторону User Friendly интерфейсов и подключили функционал кнопок.

Используется вызвов sendMessage c параметром reply_markup

Для нашего функционала мы использовали тип InlineKeyboardMarkup


https://core.telegram.org/bots/api#inlinekeyboardmarkup .

Из описания следует, что поле inline_keyboard– это массив из массива кнопок

(Array of Array of InlineKeyboardButton )

Пробуем сделать тестовую отправку кнопок

# 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

Получаем Error:
Invoke-RestMethod: {«ok»:false,«error_code»:400,«description»:«Bad Request: field \»inline_keyboard\" of the InlineKeyboardMarkup
should be an Array of Arrays"}
At line:21 char:1
Проверяем что содержит переменная $json

Вывод:

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

Видимо как-то не очень передавать объект HashTable («System.Collections.Hashtable System.Collections.Hashtable») для api
телеграма. Немного гугла и итог – при конвертации в Json ставим глубину конвертации

# 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

Получаем кнопки:

Делаем функцию по отправке кнопок, на вход будем подавать массив кнопок

# 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

Собираем все воедино, немного поменяв блок switch

# 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
}

Теперь на «привет» бот будет отправлять нам пару кнопок. Осталось понять, какую кнопку нажал пользователь. В текущей ps-
функции getUpdates есть проверка на

if($text)...

При нажатии на кнопку никакой текст не возвращается, соответственно, нужно модифицировать функцию. Нажимаем на кнопку

И запускаем кусок кода для проверки содержимого $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

Никакой message больше не прилетает. Вместо него теперь callback_query. Правим функцию

# 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

Теперь функция возвращает text, если есть сообщение, или callback_data, если было нажатие на кнопку. На этапе тестов
словили ошибку при вызове:

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

Invoke-RestMethod: {«ok»:false,«error_code»:400,«description»:«Bad Request: can't parse entities: Can't find end of the entity starting
at byte offset 8»}

Так как parse_mode выставлен в Markdown, а отправляемый текст

$return.callback_data = “Project1_CD”

нужно перед отправкой форматировать сообщение, подробнее тут:


https://core.telegram.org/bots/api#formatting-options
или убрать нижнее подчеркивание «_»

Итоговый скрипт

# 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
}

Часть 3: делаем конфиг


Настало время всё вынести в конфиг. Тут все просто – делаем 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>

Описываем задачи (tasks) и для каждой задачи указываем скрипт или команду.
Проверяем:

[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 # скрипт
}

Собираем в основной скрипт

[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
}

Теперь, если написать «привет» — бот вернет список кнопок, который соответствует задачам, описанным в xml-файлы. В
callback_data будет команда или скрипт.

Если делать косметические изменения – то желательно, чтобы кнопок было 3-4 на строку, иначе они отображаются не
полностью:

Будем делать по 3 кнопки в линию (максимально).

Схематично массив keyboard должен выглядеть так:


Таким образом:
Button[i] — массив (ассоциативный) вида

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

Line[1-3] — это массивы (из кнопок), которые хранят в себе массивы кнопок (это важно)
Keyboard – массив из Line’ов.

Модифицируем функцию 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
}

Проверяем:
Итоговый скрипт

[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
}

Часть 4: задачность и многозадачность

Настало время по кнопке делать дела.

Для многозадачности будем использовать механизм Job’ов. Проверяем такой кусок кода:

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

И через 5 секунд выполняем:

foreach($job in (Get-Job | Where {$_.State -eq "Completed"} ))


{
$output = Get-Job -ID $job.Id | Receive-Job
$output
$job | Remove-Job
}

$output должен возвращать ipconfig с localhost

Добавляем это в основной скрипт, в блок 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

А это ниже

# смотрим, какие 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
}

Проверяем, ловим error


Invoke-RestMethod: {«ok»:false,«error_code»:400,«description»:«Bad Request: message is too long»}

На просторах интернета находим информацию, что длина сообщения не может превышать 4096 символов. Оукей…

$output.Length

говорит что длина 39


Долго думаем что не так, в результате пробуем такой кусок кода:

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

Пробуем всё вместе

[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
}
Теперь прикрутим «немного безопасности»

Добавляем в xml конфиг новую строку, назовем ее users и укажем там chat_id тех, кому можно общаться с ботом:

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

В скрипте будем получать массив users

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

И проверять

if($users -contains $return.chat_id)


{
...
}

Скрипт целиком

[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
}

Часть 5: в заключение

Проверяем функционал бота – добавим туда скриптов, которые будут делать что-то полезное
Для операций на удаленных серверах мы используем Invoke-Command с последующим Write-Output

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

В этом случае, учетная запись, из-под которой будет работать скрипт телеграм-бота должна иметь соответствующие
привилегии на удаленной машине.

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

Наверняка у кого-то будет проблема с отправкой сообщения > 4096 символов, но это решаемо Substring и циклом отправки.

И напоследок – удаленное управление из любой точки мира (почти из любой) это хорошо, но всегда есть риск, что что-то
пойдет не так (управление ботом вдруг может получить кто-то нехороший). На этот случай мы просто добавили Exit из скрипта
по определенному слову

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("", "", "", ""))"}
}

У меня всё.

Теги: powershell, telegram bots

+15 227 14k 30 Поделиться

@Andrey_O
Пользователь

ПОХОЖИЕ ПУБЛИКАЦИИ

2 декабря 2016 в 23:51

Усовершенствование системы видеонаблюдения с использованием OpenCV и Telegram bot


+13 30,1k 199 47

28 июня 2016 в 17:15

Telegram bot и PostGIS


+10 20,6k 70 6

21 апреля 2016 в 12:40

Как я опробовал Microsoft Project Oxford + Telegram Bot API


+17 13,8k 82 10

ЗАКАЗЫ

Комплексный аудит (тестирование) безопасности веб-приложения


13 000 ₽ за проект • 2 отклика • 17 просмотров

Исправить бота для Telegram на PHP


1 000 ₽ за проект • 14 откликов • 67 просмотров

Провести рекламную компанию по телеграмм-блогерам


2 000 ₽ за проект • 1 отклик • 22 просмотра

Развернуть сервер WireGuard и настроить AC86U для работы с ним


10 000 ₽ за проект • 13 откликов • 36 просмотров

Необходимо сверстать с VUE.js 8 страницы портала и запрограммировать интерфейс


17 500 ₽ за проект • 24 отклика • 66 просмотров

Больше заказов на Хабр Фрилансе


Реклама

Комментарии 30

buldo 13 января 2020 в 00:29 0

А chat id для конкретного пользователя всегда постоянный?

lavilav 13 января 2020 в 01:07 0

Да. По крайней мере был всегда так.

frkbvfnjh 13 января 2020 в 07:33 0

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

aleki 13 января 2020 в 11:33 +1

ID чата с пользователем (тет-а-тет) не меняется.

ttcpip 15 января 2020 в 13:48 0

Глупость говоришь.
У пользователей айди всегда один. Он выглядит примерно так: 334463248.
Когда ты создаешь беседу (или группу, если тебе так удобнее называть), у беседы будет айди примерно такого же вида, только
со знаком минус.
Если ты зайдешь в настройки этой группы и сделаешь её публичной, применишь настройки, то эта группа превратится в так
называемую супер-группу. У супергрупп айди всегда такого вида: -1001492481741.
С этого момента айди группы у тебя не поменяется, что бы ты с ней не делал (кроме удаления этой группы, очевидно).
Также, когда ты в первый раз устанавливаешь приватность группы на значение «Публичная» и применяешь настройки, телеграм
присылает 2 сообщения (если это CallbackApi) или ты «вытягиваешь» это сообщение (если это LongPoolApi). Одно из этих
сообщений (или оба, точно не помню) сообщение содержит поля «migrate_from_chat_id» и «migrate_to_chat_id»
Подробнее можешь прочитать в официальной документации: core.telegram.org/bots/api#message

Anemesys 13 января 2020 в 05:08 0

Довольно интересное использование мессенджера. Спасибо за познавательную статью!

IRT 13 января 2020 в 07:25 0

А long-polling можно реализовать в Powershell? Дергать API каждую секунду не самый изящный вариант.

centralhardware 13 января 2020 в 10:23 0

long polling это же и есть вариант, когда бот будет дергать сервер делеграмма N раз в секунду, для получения данных по
инициативе сервера надо реализовывать webhook

IRT 13 января 2020 в 10:55 0

Вы ошибаетесь. Long-polling это установка соединения с API и ожидание команд с сервера. Соединение при этом не
разрывается и команды бот получает мгновенно, а не через N секунд.
Если сделать GET к методу GetUpdates с параметром offset больше, чем последнее полученное сообщение, то GET повиснет в
ожидании данных с сервера, пока не наступит таймаут.
centralhardware 13 января 2020 в 22:00 0

А как тогда возможно использование прокси серверов с методом long polling?

YegorVin 14 января 2020 в 03:12 0

Да ладно. Сервера телеграмма будут держать открытым соединения с тысячами серверов управляющими ботами?

IRT 14 января 2020 в 07:42 0

Они же держат соединения с тысячами десктопных клиентов, у которых нет push. Как функционируют тысячи ботов, у
которых нет внешнего ip и сертификата на webhook, но которые отвечают мгновенно, не через секунду?
Посмотрите документацию core.telegram.org/bots/api#getupdates
Они там прямым текстом пишут, что лучше использовать таймаут побольше. Видимо, потому что накладные расходы на
обслуживание нового соединения куда больше, чем на поддержание открытого.

YegorVin 14 января 2020 в 12:34 0

Я не вижу в описании что коннект может остаться открытым. Я сейчас как раз пишу ВК бота. Там я получаю через long
pool обновления и соединение закрывается. Бот периодически (где то раз в секунду) стучится на их сервера за новыми
обновлениями.

IRT 14 января 2020 в 12:42 0

Да, после получения обновлений соединение закрывается и нужно устанавливать новое. Смысл в том, что при
ожидании обновлений соединение остается открытым. Вы делаете GET запрос к серверу, а он вам не сразу отвечает,
а только когда будут обновления, до этого соединение висит открытым. Так понятнее?

Telegram API приводит же ссылку на wiki:


en.wikipedia.org/wiki/Push_technology#Long_polling

With long polling, the client requests information from the server exactly as in normal polling, but with the expectation the
server may not respond immediately. If the server has no new information for the client when the poll is received, instead
of sending an empty response, the server holds the request open and waits for response information to become available.
Once it does have new information, the server immediately sends an HTTP/S response to the client, completing the open
HTTP/S Request. Upon receipt of the server response, the client often immediately issues another server request.

Когда вы просто дергаете сервер раз в секунду, это называется short poll, обычно это не рекомендуют делать,
возрастает нагрузка на сервер, плюс обновления приходят не мгновенно, а с задержкой в секунду. Да еще и лишний
трафик по сети гуляет, что критично в случае мобильных клиентов.

YegorVin 14 января 2020 в 12:50 0

Да так понятнее спасибо.


И какой максимальный таймаут допускает API телеграмма или сколько угодно долго?

IRT 14 января 2020 в 12:55 0

Incoming updates are stored on the server until the bot receives them either way, but they will not be kept longer
than 24 hours.

Раз в 24 часа соединение в любом случае прервется, поскольку они удаляют обновления, если их не забрали за
сутки. Я обычно ставлю 5 минут, если после пяти минут ничего не пришло, то соединение закрывается и
открывается новое. Так и трафик небольшой, и нагрузка на API слабая.

centralhardware 13 января 2020 в 09:36 0

Вы спалили token. Зачем использовать для написания бота powershell? Смысл то есть, но не в том чтобы ещё раз писать его на голом
апи, а вот написать библиотеку для работы с телеграмм апи это уже интересно, а потом подключив ее к скрипту можно было бы
доказать всему миру то что бота на powershell/bash могут быть написаны не только ради эксперементов.
Andrey_O 13 января 2020 в 09:44 0

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

centralhardware 13 января 2020 в 09:54 0

Просто на мой взгляд в отсутствие удобной библиотеки и отталкивает от написания ботов на данных языках, так как писать на
голом апи это мало кому надо

MegaShIzoID 13 января 2020 в 12:53 0

есть враперы на чем угодно, можно ими пользоваться, но использование голого апи более чем удобно, да и нет там ничего
сложного, городить для этого библиотеку — глупо

centralhardware 13 января 2020 в 22:12 0

Вопрос в желаемой сложности для простого не имеет смысла для чего то более сложного ещё какой.

MegaShIzoID 14 января 2020 в 06:28 0

там апи простейший, я вообще не понимаю зачем там либа нужна, вне зависимости от сложности проекта обращение к
апи читабельно и незамутнено кривыми руками библиотекописателя, если сложно использовать напрямую обращение к
апи то может стоит подумать об уходе из профессии?

centralhardware 14 января 2020 в 08:11 0

Последний бот которого я писал имел 12 диалогов с одним из которых содержал 13 шагов, 17 команд, и 16 callback и
при таком раскладе хочется написать один раз код который делает всю грязную работу и убрать его с глаз долой. Да
можно писать каждый раз кучу шаблонного кода, но делать так это медленное самоубийство.

Raivon 13 января 2020 в 15:16 0

Правильно я понимаю, что в настоящий момент нет возможности ограничить список chat_id через настройки бота при помощи
BotFather? Как вы оцениваете вероятность нахождения бота и его DDoS? (Не придирки )

Andrey_O 13 января 2020 в 16:11 0

Функционал ограничения через BotFather отсутствует. Кейсы с DDoS мы не проверяли, но точно могу сказать, что за время работы
бота было несколько случаев, что ему писали с неизвестных chat_id. В целом, отказ бота возможен по причине утечки памяти

centralhardware 13 января 2020 в 22:15 0

Мне боту о котором знает не больше 20 человек за пол года писали от силы 10 левых пользователей

werwolflg 13 января 2020 в 23:48 0

Так ограничивать через BotFather и не надо, легко же решается созданием списка с разрешенными chat_id, и проверять от кого
пришло сообщение. Если этого chat_id нет в списке, то игнорировать его.

Andrey_O 14 января 2020 в 00:12 +1

Все верно, но это не сможет помешать кому угодно писать боту (да, он может не отвечать, но сообщения все равно будут
обрабатываться)

akio_alx 14 января 2020 в 21:22 0


ну вообще BotFather не для этого. Вы chat_id отфильтровываете в коде по любым вашим критериям. Поэтому все боты могут быть
секторными по доступу.
DDoS — пробовал :) Для начала сервера телеграмма просто не пропустят ваши сообщения с ваших айди в несметном множестве.
А если берем очень много id (которые кстати должны быть созданы и все они должны нажать старт в вашем боте, только потом они
могут отправлять хоть что то в этот канал) — смотрим пункт выше — фильтруем по айдишникам. равносильно drop в iptables.

centralhardware 15 января 2020 в 06:12 0

Так как бот на bash его возможности могут оказаться меньше чем лимиты телеграмма. Надо тестить.

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

САМОЕ ЧИТАЕМОЕ

Сутки Неделя Месяц

Как конкуренты могут легко заблокировать ваш сайт


+114 35,2k 99 140

Microsoft постепенно убирает возможность создавать локальные аккаунты Windows 10


+38 39,8k 25 279

Анатомия накопителей: жёсткие диски


+34 16,5k 91 18

«Медуза»: ИТ-компании в РФ «борются за выживание» при помощи связей и силовиков


+45 22,3k 22 184

Андрей Зарецкий, Александр Труханов: «Гонорара хватило, чтобы кофе попить»


+94 18k 61 48

Ваш аккаунт Разделы Информация Услуги

Войти Публикации Правила Реклама

Регистрация Новости Помощь Тарифы

Хабы Документация Контент

Компании Соглашение Семинары

Пользователи Конфиденциальность Мегапроекты

Песочница

Если нашли опечатку в посте, выделите ее и нажмите Ctrl+Enter, чтобы сообщить автору.

© 2006 – 2020 «TM» Настройка языка О сайте Служба поддержки Мобильная версия

Вам также может понравиться