Philip Wiki

Персональный wiki-сайт

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

Инструменты сайта


docs:blog:2017:09:ps_telegram-bot_part_1

Telegram-Bot на PowerShell (часть 1)

В продолжение статьи «Система наблюдения и Telegram-бот» описание того, с чего начать создание своего Telegram-бота на PowerShell. В этих статьях я не буду рассматривать процесс регистрации бота. Эту информацию достаточно легко найти как в официальной документации Telegram, так и на просторах интернета. Здесь же я буду рассматривать исключительно создание бота в контексте PowerShell.

С чего начать?

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

  1. POLLING – регулярный опрос сервера Telegram на появление новых событий
    1. Прост в реализации
    2. Не требует дополнительной настройки фаервола
    3. Не требует наличия статического IP-адреса (или настройки динамического DNS)
    4. Маленький, но всё же «лишний» трафик из-за регулярных запросов (в дачных условиях может быть критичным)
    5. Менее эффективный и быстрый
  2. WEBHOOKS – подразумивает наличие некой службы с Вашей стороны, которая будет слушать и принимать информацию о новых событиях, посылаемых сервером Telegram в Вашу сторону
    1. Эффективен и практически не потребляет трафика
    2. Сложнее в реализации
    3. Требует настройки фаервола
    4. Требует статический IP-адрес

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

Опрашиваем бота

Итак мы определились, что будем использовать пуллинг, т.е. ручной запрос к серверу Telegram. В упоминаемой выше документации написано, что пулинг надо производить используя метод getUpdates. В контексте данного метода самым интересным является параметр offset, этим параметром мы можем определить, как и в каком количестве получать обновления:

  • 0 – получать все обновления (новые, старые, все…)
  • -1 – получить только последнее обновление (одно!)
  • id последнего обновления + 1 – получить только новые события и отметить их просмотренными (логично, что id последнего обновления мы должны заранее знать и, само собой, не забыть сохранить новое)

Пример простого скрипта для получания массива всех обновлений бота посредством Invoke-WebRequest:

Get-TelegramBotUpdates.ps1
# определяем переменные
$token = <ваш_токен> # указываем свой токен
$ChatTimeout = 3 # таймаут в секундах
$allowed_updates = @("message","channel_post","inline_query","chosen_inline_result","callback_query") # ограничиваем тип получаемых событий
$allowed_updates = ConvertTo-Json -InputObject $allowed_updates # конвертируем тип обновлений в json
$UpdateId = 0 # какие события получаем? 0 - все, -1 только последнее, lastid+1 - получить новые и отметить их просмотренными
 
# формируем URL
$URL = "https://api.telegram.org/bot$token/getUpdates?offset=$UpdateId&allowed_updates=$allowed_updates&timeout=$ChatTimeout"
 
# делаем запрос и преобразуем ответ из json
$Request = Invoke-WebRequest -Uri $URL -Method Get
$content = ConvertFrom-Json $Request.content
 
# смотрим, что получили, но из содержания массива $content "вытащим" только текст сообщений
$content.result.message.text

Разумеется, если мы хотим получить «говорящего» бота, нужно регулярно проверять обновления, чтобы вовремя реагировать на них.

Создаём задание в планировщике

Итак, у нас есть бот, мы умеем «читать» сообщения из чата с ним, теперь нам осталось как-то это автоматизировать. Проще всего запускать наш скрипт заданием из планировщика Windows.

Правда здесь есть одна проблема: планировщик Windows не умеет запускать задачи чаще чем раз в пять минут, а это, согласитесь, достаточно долгая пауза для проверки чата. :-) Но можно обойти это ограничение, просто создав несколько триггеров с необходимым интервалом между ними. Сделаем такую задачу в планировщике (не забудьте изменить путь к скрипту):

# создаём массив из триггеров
$triggers = @()
$triggers += New-ScheduledTaskTrigger -RepetitionInterval (New-TimeSpan -Minutes 1) -RepetitionDuration ([System.TimeSpan]::MaxValue) -At 12:00:00 -Once
$triggers += New-ScheduledTaskTrigger -RepetitionInterval (New-TimeSpan -Minutes 1) -RepetitionDuration ([System.TimeSpan]::MaxValue) -At 12:00:10 -Once
$triggers += New-ScheduledTaskTrigger -RepetitionInterval (New-TimeSpan -Minutes 1) -RepetitionDuration ([System.TimeSpan]::MaxValue) -At 12:00:20 -Once
$triggers += New-ScheduledTaskTrigger -RepetitionInterval (New-TimeSpan -Minutes 1) -RepetitionDuration ([System.TimeSpan]::MaxValue) -At 12:00:30 -Once
$triggers += New-ScheduledTaskTrigger -RepetitionInterval (New-TimeSpan -Minutes 1) -RepetitionDuration ([System.TimeSpan]::MaxValue) -At 12:00:40 -Once
$triggers += New-ScheduledTaskTrigger -RepetitionInterval (New-TimeSpan -Minutes 1) -RepetitionDuration ([System.TimeSpan]::MaxValue) -At 12:00:50 -Once
# определяем действие (запуск нашего скрипта)
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument '-File ".\Get-TelegramBotUpdates.ps1"' # путь изменить на свой
$settings = New-ScheduledTaskSettingsSet
# формируем задачу...
$task = New-ScheduledTask -Action $action -Trigger $triggers -Settings $settings
# ...и создаём её в планировщике
Register-ScheduledTask 'TelegramBot' -InputObject $task

Вот и всё. Теперь мы проверяем обновления нашего чата каждые 10 секунд.

Продолжение следует…

Обсуждение

vladimir, 19.02.2018 00:24

Доброго времени суток! Спасибо за очень познавательную статью! Но, есть пара нюансов: при выполнении кода «$Request = Invoke-WebRequest -Uri «https://api.telegram.org/bot$token/getUpdates?offset=-1&timeout=1″ -Method Get» получаю ошибку «Invoke-WebRequest : {«ok»:false,»error_code»:409,»description»:»Conflict: can’t use getUpdates method while webhook is active»}» Как побороть, пока не разобрался….

Philip, 22.02.2018 22:05

Здравствуйте. Прошу прощения за долгий ответ, сейчас немного занят. Ну и приветствую Вас в клубе интересных и познавательных, но чрезвычайно самобытных powershell-костылей для Telegram. )))

Как я написал немного выше, мне сейчас не совсем удобно (а точнее, скорее лениво) проверять все нюансы, но вероятно у вас настроен webhook для бота, который является ограничением в работе метода getUpdates. Проверьте наличие «прослушивателя».

А вообще, конечно хотелось бы собраться силами и запилить ещё несколько частей про бота. Мне вроде как удалось более или менее что-то работоспособное сделать для дома, даже отчасти модульное. После статей оно, быть может, обрело бы структуру и было готовым лечь на github… Может в ближайший месяц, если ещё будет для кого это делать. :-)

vladimir, 24.02.2018 17:03, 08.03.2018 14:22

Добрый день! Спасибо за ответ! В общем, я разобрался в чем суть проблемы: у меня есть бот, который является вторым админом (помимо меня) в приватном канале, и может публиковать и получать инфу из него — с этим все нормально. Т.е. с публикацией в канал от имени бота. Но тут другой нюанс — у бота есть и своя лента, в которую я (как хозяин бота) так же могу постить. И тут возникает коллизия — при отработке getUpdates, сначала (в цикле) проверяется наличие новых сообщений в каналах (в которых добавлен бот), а потом непосредственно лента самого бота! Т.е. если я что-то пишу в ленте бота, то через getUpdates четко получаю текст сообщения. Но, т.к. мне нужна инфа только(!) из канала — в ленте бота всегда пусто — то, на выходе я получаю пустой массив! Т.е. используя ваш вариант скрипта — полученные данные из любого канала всегда будут перезатираться «пустым» массивом из ленты бота. Видимо, нужна доработка, которая будет в цикле проверять все «chat»:{«id»: в которых добавлен админом бот, что бы собирать из них сообщения. Думаю получилось донести свою мысль)) Оговорюсь сразу, такое происходит только в случае, если кто-то постит в канал, в котором мой бот-админ. Если постить непосредственно боту — тогда все Ok! Сообщения апдейтятся успешно под катом пример кода:

$token = ‘536103686:AAGXXXXXXXXXXXXXXXXXT3GEPE1pz6rsgE’
$ChatTimeout = 3
$allowed_updates = @(«message»,»channel_post»,»inline_query»,»chosen_inline_result»,»callback_query») # ограничиваем тип получаемых событий
$allowed_updates = ConvertTo-Json -InputObject $allowed_updates # конвертируем тип обновлений в json
$UpdateId = -1 # какие события получаем? 0 — все, -1 только последнее, lastid+1 — получить новые и отметить их просмотренными
$msg = «»
$Request = «»
$props = «»
# формируем URL
$URL = «https://api.telegram.org/bot$token/getUpdates?offset=$UpdateId&allowed_updates=$allowed_updates&timeout=$ChatTimeout»
$Request = Invoke-WebRequest -Uri $URL -Method Get
$obj = (ConvertFrom-Json $Request.Content)
$var = $Request.Content
Write-Host $var
$props = [ordered]@{
ok = $var.ok
UpdateId = $var.update_id
Message_ID = $var.message_id
first_name = $var.from.first_name
last_name = $var.from.last_name
sender_ID = $var.from.id
chat_id = $var.chat.id
text = $var.text
}
$msg = New-Object -TypeName PSObject -Property $props
return $msg
vladimir , 24.02.2018 17:05, 08.03.2018 14:21

Вот что получаю на выходе:

PS C:\WINDOWS\system32> C:\scripts\PowerShell\bot.ps1
{«ok»:true,»result»:[{«update_id»:684XXXX478,
«channel_post»:{«message_id»:58,»author_signature»:»vladimir «,»chat»:{«id»:-100138XXXX100,»title»:»atm_support»,»type»:»channel»},»date»:15
19156267,»text»:»5555″}}]}

ok :
UpdateId :
Message_ID :
first_name :
last_name :
sender_ID :
chat_id :
text :

Собственно, в это «text»:«5555» — мне и нужно как-то выдрать из массива Json!

Philip, 08.03.2018 15:02

Ещё раз прошу прощения за долгий ответ.

У Вас явно лишнее определение переменной $var

$var = $Request.Content
Write-Host $var

Зачем Вы её создаёте и заполняете $Request.Content, если перед этим вы определили переменную $obj и заполнили её этими же данными, при этом логично преобразовав их из JSON?

Ниже Ваш код, но работающий. Удалена переменная $var, а $propsзаполняется значениями из $obj.

$token = 'ваш_токен'
$ChatTimeout = 3
$allowed_updates = @("message","channel_post","inline_query","chosen_inline_result","callback_query") # ограничиваем тип получаемых событий
$allowed_updates = ConvertTo-Json -InputObject $allowed_updates # конвертируем тип обновлений в json
$UpdateId = -1 # какие события получаем? 0 — все, -1 только последнее, lastid+1 — получить новые и отметить их просмотренными
$msg = ""
$Request = ""
$props = ""
# формируем URL
$URL = "https://api.telegram.org/bot$token/getUpdates?offset=$UpdateId&allowed_updates=$allowed_updates&timeout=$ChatTimeout"
$Request = Invoke-WebRequest -Uri $URL -Method Get
$obj = (ConvertFrom-Json $Request.Content)
$props = [ordered]@{
ok = $obj.ok
UpdateId = $obj.result.update_id
Message_ID = $obj.result.message.message_id
first_name = $obj.result.message.from.first_name
last_name = $obj.result.message.from.last_name
sender_ID = $obj.result.message.from.id
chat_id = $obj.result.message.chat.id
text = $obj.result.message.text
}
$msg = New-Object -TypeName PSObject -Property $props
return $msg

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

vladimir, 18.03.2018 17:58

Добрый день! К сожалению, ни чего не получилось - бот может получать команду либо от канала, либо от себя. Настроить параллельно обработку событий в канале и в своей ленте, у меня так и не получилось.

Philip, 18.03.2018 20:44, 18.03.2018 21:09

При условии $UpdateId = -1 бот получает только самое новое сообщение, про какую параллельность здесь может идти речь? Это же только пример работы. В вашем случае нужно видимо $UpdateId = $last_id + 1, где переменную $last_id читаем в самом начале скрипта и перезаписываем в конце (извне, например в файл).

Только авторизованные участники могут оставлять комментарии.
docs/blog/2017/09/ps_telegram-bot_part_1.txt · Последнее изменение: 08.09.2017 23:36 — philip

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki