Шаблон простейшего бота для телеграм для школьников 7-9 класса на Powershell

В ходе разговоров с приятелем внезапно для себя узнал, что детей в 8-10 классе в их школе вообще не учат программированию. Word, Excel и все. Ни лого, ни даже паскаля, ни даже VBA для Excel.

Очень удивился, открыл интернет и полез читать —
Одна из задач профильной школы – содействовать воспитанию нового поколения, отвечающего по своему уровню развития и образу жизни условиям информационного общества.
Данный курс позволит закрепить на практике знания учащихся по основным конструкциям языка программирования Паскаль. (из программы какой-то гимназии за 2017 год)

В итоге решил потратить пару часов и набросать пример «как создать простого бота — для школьников».

Под катом о том, как написать очередного простого бота на Powershell и заставить его работать без webhook, белых IP, выделенных серверов, развертываемых виртуалок в облаке и прочая прочая — на обычном домашнем ПК с обычной Windows.

TLDR: Еще одна скучная статья с грамматическими и фактическими ошибками, читать нечего, юмора нет, картинок нет.

Ничего нового в статье нет, почти все написанное раньше уже было на Хабре, например в статьях Инструкция: Как создавать ботов в Telegram и Телеграмм-бот для системного администратора.
Более того, статья специально избыточна, чтобы не ссылаться каждый раз на учебную литературу. Никаких отсылок на банду 4, PowerShell Deep Dives или скажем The 5 Pillars of the AWS Well-Architected Framework в тексте нет.

Вместо предисловия, можно пропустить

Смело пропускаемВ 2006 году Microsoft выпустила PowerShell 1.0 для тогда еще Windows XP, Vista и 2003 сервера. В чем-то он заменил такие вещи как, cmdbat скрипты, vb скрипты, Windows Script Host и JScript.

Даже сейчас PowerShell можно рассматривать только как следующий шаг после вариантов Logo, вместо где-то наверное до сих пор используемых Delphi (или чего постарше), несмотря на наличие в нем циклов, классов, функций, вызова MS GUI, интеграции с Git и так далее.

Используется powershell сравнительно редко, столкнуться с ним можно разве что в виде PowerShell Core, VMware vSphere PowerCLI, Azure PowerShell, MS Exchange, Desired State Configuration, PowerShell Web Access и еще десятка-другого редко используемых программ и функций. Возможно, второе дыхание у него появится с выходом WSL2, но это не точно.

У Powershell есть и три больших плюса:

  1. Он относительно простой, про него есть масса литературы и примеров, и даже на русском, например статья про Foreach — из книги PowerShell in depth — про разницу () и {}
  2. Он идет вместе с редактором ISE, в комплекте с Windows. Там даже кое-какой отладчик есть.
  3. Из него легко вызываются компоненты для построения графического интерфейса.

0. Подготовка.

Нам потребуется:

  • ПК с Windows (у меня Windows 10)
  • Хоть какой-то выход в интернет (через NAT например)
  • Для тех, у кого ограничен доступ к телеграмму — установленный и настроенный freegate в браузере, в некоторых сложных случаях совместно с Symple DNS Crypt
  • Наличие работающего клиента телеграм на телефоне
  • Понимание совсем основ — что такое переменная, массив, цикл.

Открытые и прочитанные статьи — Инструкция: Как создавать ботов в Telegram и Телеграмм-бот для системного администратора

1. Создадим очередного тестового бота.

Поскольку это и так все знают, и уже было, то тоже можно пропуститьКак сказано в статье выше — Прежде всего, бот для Telegram — это по-прежнему приложение, запущенное на вашей стороне и осуществляющее запросы к Telegram Bot API. Причем API понятное — бот обращается на определенный URL с параметрами, а Telegram отвечает JSON объектом.

Связанные с этим проблемы: если каким-то неведомым образом вы возьмете некий код из JSON обьекта и как-то отправите его на выполнение (не специально) — код выполнится у вас.

Процесс создания описан в двух статьях выше, но повторюсь: в телеграмме открываем контакты, ищем @botfather, говорим ему /newbot, создадим бота Botfortest12344321, назовем его Mynext1234bot, и получим сообщение с уникальным ключем вида 1234544311:AbcDefNNNNNNNNNNNNN

Ключ берегите и не раздавайте!

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

Спросим у BotFather «/mybot» и поправим настройки, если чего-то не нравится.

Снова откроем контакты, найдем там @Botfortest12344321 (начинать поиск с @ обязательно), нажмем «начать» и напишем боту «/Слава роботам». Знак / обязателен, кавычки — не нужны.
Бот, конечно, ничего не ответит.

Проверим что бот создался — откроем.

api.telegram.org/bot1234544311:AbcDefNNNNNNNNNNNNN/getMe
где 1234544311:AbcDefNNNNNNNNNNNNN — ранее полученный ключ,
и получим строку вида
{«ok»:true,«result»:{«»}}

Первая секретная фраза (токен) у нас есть. Теперь нам нужно узнать вторую секретную цифру — ID чата с ботом. Каждый чат, группа и так далее — индивидуальны и имеют свой собственный номер (иногда с минусом — для открытых групп). Для того чтобы узнать этот номер, нам нужно запросить в браузере (на самом деле совершенно не обязательно в браузере, но для лучшего понимания можно начать с него) адрес (где 1234544311:NNNNNNNNN — это ваш токен

https://api.telegram.org/bot1234544311:NNNNNNNNN/getUpdates

и получить ответ вида

{«ok»:true,«result»:[{«update_id»:…,… chat«:{«id»:123456789

Нам нужен именно chat_id.

Проверим, что мы можем писать в чат вручную: вызовем из браузера адрес

https://api.telegram.org/botваштокен/sendMessage?chat_id=123456789&text="Life is directed motion"

Если к вам в чат пришло сообщение от бота — окей, вы переходите на следующий этап.

Таким образом (через браузер) вы всегда можете проверить, где проблемы — у вас при генерации ссылки, или что-то где-то прикрыто и не работает.

Что нужно знать, перед продолжением прочтения

В телеграмме есть несколько типов групповых чатов (открытые, закрытые). Для данных чатов часть функций (например, id) различается, что порой вызывает некоторые проблемы.

Будем считать, что на дворе конец 2019 года, и даже герой нашего времени, широкоизвестный Человек-Оркестр (администратор, юрист, инфобезопасник, программист и практически MVP) Евгений В. отличает переменную $i от массива, освоил циклы, глядишь в следующие пару лет освоит Chocolatey, а там и до Parallel processing with PowerShell и ForEach-Object Parallel дойдет.

1. Думаем, чего же будет делать наш бот

Идей у меня не было никаких, пришлось думать. Бота-записную книжку я уже писал. Делать бота «который что-то пересылает куда-то» не хотелось. Для подключения Azure нужна кредитка, а откуда она у школьника? Надо заметить, что все не так плохо: основные облака дают какой-то тестовый период бесплатно (но номер кредитки все равно нужен — и с него будет списан, кажется доллар. Не помню, вернулся ли он потом.)

Без AI ML делать бота-обормота-стихоплета не так интересно.

Решил сделать бота, который будет мне (или не мне) напоминать английские слова по словарю.
Словарь, чтобы не возиться с БД, будет лежать в текстовом файле и пополняться вручную.
В данном случае задача — показать основы в работе, а не сделать хотя бы частично готовый продукт.

2. Пробуем что и как в первый раз

Создадим папку C:poshtranslate
Для начала посмотрим, что у нас за powershell, запустим ISE через пуск-выполнить
powershell ise
или найдем Powershell ISE в установленных программах.
После запуска откроется обычный привычный «какой то там редактор», если текстового поля не будет, то всегда можно нажать «File — create new».

Посмотрим версию powershell — напишем в текстовом поле:

get-host 

и нажмем F5.

Powershell предложит сохраниться — «The script you are about to run will be saved.», согласимся, и и сохраним в C:poshtranslate файл из powershell с именем myfirstbotBT100.

После запуска, в нижнем текстовом окне получим табличку данных:

Name             : Windows PowerShell ISE Host
Version          : 5.1.(и так далее)

У меня 5.1 с чем-то, этого достаточно. Если у вас старый Windows 7/8, то ничего страшного — хотя PowerShell необходимо обновить до версии 5 — например, по инструкции.

Наберем в командной строке снизу Get-Date, нажмем Enter, посмотрим на время, перейдем в корневую папку командой
cd
и очистим экран командой cls (нет, не надо использовать rm)

Теперь проверим, что и как работает — напишем даже не код, а две строки, и попробуем понять, что они делают. Закомментируем строку с get-host символом # и немного допишем.

# Пример шаблона бота 
# get-host
<# это пример многострочного комментария #>
$TimeNow = Get-Date
$TimeNow

(Что интересно. В выпадающем списке оформления кода на хабре есть два десятка вариантов — но powershell там нет. Dos есть. Perl есть. )

И запустим код, нажав F5 или «>» из GUI.

Получим вывод:

Saturday, December 8, 2019 21:00:50 PM (или что-то типа)

Теперь разберемся с этими двумя строками, и некоторыми интересными моментами, чтобы в дальнейшем к этому не возвращаться.

В отличие от паскаля (и не только), повершелл сам пытается определить, какой тип присвоить переменной, подробнее про это написано в статье Ликбез по типизации в языках программирования
Поэтому, заводя переменную $TimeNow, и присваивая ей значение текущей даты и времени (Get-Date), нам не надо особенно беспокоиться, какой тип данных там будет.

Правда, от этого незнания потом может быть больно, но это потом. Ниже в тексте будет пример.
Посмотрим, что у нас получилось. Выполним (в командной строке)

$TimeNow | Get-member

и получим страницу непонятного текста

Пример непонятного текста номер 1

PS C:> $TimeNow | Get-member
   TypeName: System.DateTime
Name                 MemberType     Definition                                                                                                                                       
----                 ----------     ----------                                                                                                                                       
Add                  <b>Method         </b>datetime Add(timespan value)  
..
DisplayHint          NoteProperty   DisplayHintType DisplayHint=DateTime                                                                                                             
Date                 <b>Property       </b>datetime Date {get;}                                                                                                                             
Year                 Property       int Year {get;}   
..                                                                                                                               
DateTime             ScriptProperty System.Object DateTime {get=if ((& { Set-StrictMode -Version 1; $this.DisplayHint }) -ieq  "Date")...                                         

Как видно, создалась переменная типа TypeName: System.DateTime с кучей методов (в смысле, что мы можем делать с этим объектомпеременной) и свойств.

Вызовем $TimeNow.DayOfYear — получим номер дня в году.
Вызовем $TimeNow.DayOfYear | Get-Member — получим TypeName: System.Int32 и группу методов.
Вызовем $TimeNow.ToUniversalTime() — и получим время по UTC

Отладчик

Иногда бывает так, что необходимо выполнить программу до какой-то строки, и посмотреть состояние программы в этот момент. Для этого в ISE есть функция Debug — toggle break point
Поставьте точку остановки где-то посередине, запустите эти две строки на исполнение и посмотрите, как выглядит остановка.

3. Разбираемся с взаимодействием с ботом Телеграмм

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

В нашем случае необходимо:

  • Научиться что-то отправлять в переписку
  • Научиться получать что-то из переписки

3.1 Учимся отправлять что-то в переписку и получать из нее же

Немного кода — часть 3

Write-output "This is part 3"
$MyToken = "1234544311:AbcDefNNNNNNNNNNNNN"
$MyChatID = "123456789"
$MyProxy = "http://1.2.3.4:5678" 

$TimeNow = Get-Date
$TimeNow.ToUniversalTime()
$ScriptDir = Split-Path $script:MyInvocation.MyCommand.Path
$BotVersion = "BT102"

$MyText01 = "Life is directed motion - " + $TimeNow

$URL4SEND = "https://api.telegram.org/bot$MyToken/sendMessage?chat_id=$MyChatID&text=$MyText01"

Invoke-WebRequest -Uri $URL4SEND

и в РФ на этом месте мы получаем ошибку Unable to connect to the remote server.

Или не получаем — зависит от оператора связи и того, настроена ли и работает ли прокси
Что ж — остается добавить прокси. Учтите — использование нешифрованного и вообще левого прокси крайне опасно для вашего здоровья.

Задача поиска рабочего прокси не очень сложна — большая часть публикуемых http прокси работает. У меня сработала кажется пятая по счету.

Синтаксис с использованием прокси:

Invoke-WebRequest -Uri $URL4SEND -Proxy $MyProxy

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

Можете посмотреть, во что превращается ваша строка $URL4SEND и попробовать запросить ее в браузере, например вот так:

$URL4SEND2 = '"'+$URL4SEND+'"'
start chrome $URL4SEND2 

3.2. Писать «что-то» в чат мы научились, теперь попробуем читать

Добавим еще 4 строки и посмотрим что там внутри через | get-member

$URLGET = "https://api.telegram.org/bot$MyToken/getUpdates"
$MyMessageGet = Invoke-WebRequest -Uri $URLGET -Method Get -Proxy $MyProxy
Write-Host "Get-Member"
$MyMessageGet | Get-Member

Самое интересное нам предоставляет

Content           Property   string Content {get;}  
ParsedHtml        Property   mshtml.IHTMLDocument2 ParsedHtml {get;}                                    
RawContent        Property   string RawContent {get;set;}

Посмотрим что в них:

Write-Host "ParsedHtml"
$MyMessageGet.ParsedHtml # тут интересное
Write-Host "RawContent"
$MyMessageGet.RawContent # и тут интересное, но еще к тому же и читаемое. 
Write-Host "Content"
$MyMessageGet.Content

Если у вас все работает, что вы получите длинную строку вида:

{"ok":true,"result":[{"update_id":12345678,
"message":{"message_id":3,"from":{"id"

К счатью, в ранее вышедшей статье Телеграмм-бот для системного администратора эта строка (да, согласно $MyMessageGet.RawContent | get-member — это System.String), уже была разобрана.

4. Обработать полученное (отправлять хоть что-то мы уже умеем)

Как уже написано тут, самое нужное лежит в content. Посмотрим его внимательней.

Сначала напишем боту еще пару фраз из web интерфейса или с телефона

/message1
/message2
/message3

и посмотрим через браузер на адрес, который сформировался в переменной $URLGET.

Мы увидим что-то типа:

{"ok":true,"result":[{"update_id":NNNNNNN,
"message":{"message_id":10, .. "text":"/message1"
"message":{"message_id":11, .. "text":"/message2 
"message":{"message_id":12, .. "text":"/message3 

Что это такое? Какой-то сложный обьект из массивов обьектов, содержащий сквозной индентификатор сообщения, идентификатор чата, идентификатор отправления и еще массу информации.

Впрочем, разбирать нам «что это за объект такой» не нужно — часть работы уже проделана за нас. Посмотрим что там внутри:

Читаем полученные сообщения или часть 4

Write-Host "This is part 4" <# конечно эта строка нам не нужна в итоговом тексте, но по ней удобно искать. #> 

$Content4Pars01 = ConvertFrom-Json $MyMessageGet.Content
$Content4Pars01 | Get-Member
$Content4Pars01.result
$Content4Pars01.result[0]
$Content4Pars01.result[0] | Get-Member
$Content4Pars01.result[0].update_id
$Content4Pars01.result[0].message
$Content4Pars01.result[0].message.text
$Content4Pars01.result[1].message.text
$Content4Pars01.result[2].message.text

5. Что теперь нам с этим делать

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

<#start comment 105 end comment 105#>

Теперь нам надо определиться, где брать словарь (ну как где — на диске в файле) и как он будет выглядеть.

Конечно, можно набить огромный словарь прямо в тексте скрипта, но это же совершенно не дело.
Поэтому посмотрим, с чем умеет штатно работать powershell.
Вообще то ему все равно с каким файлом работать, не все равно нам.
На выбор у нас есть: txt (можно, но зачем), csv, xml.
А можно всех посмотреть Посмотрим всех.
Создадим класс MyVocabClassExample1 и переменную $MyVocabExample1
замечу что класс пишется без $

немного кода #5

write-host "This is part 5"
class MyVocabClassExample1 {
    [string]$Original  # слово
    [string]$Transcript
    [string]$Translate
    [string]$Example
    [int]$VocWordID # очень интересный момент. Использование int с его ограничениями может порой приводить к диким последствиям, для примера - недавний случай с SSD HPE. Изначально я не стал добавлять этот элемент, потом все же дописал и закомментировал.
    }

$MyVocabExample1 = [MyVocabClassExample1]::new()
$MyVocabExample1.Original = "Apple"
$MyVocabExample1.Transcript = "[ ˈapəl ]"
$MyVocabExample1.Translate = "Яблоко"
$MyVocabExample1.Example = "An apple is a sweet, edible fruit produced by an apple tree (Malus domestica)"
# $MyVocabExample1.$VocWordID = 1

$MyVocabExample2 = [MyVocabClassExample1]::new()
$MyVocabExample2.Original = "Pear"
$MyVocabExample2.Transcript = "[ pe(ə)r ]"
$MyVocabExample2.Translate = "Груша"
$MyVocabExample2.Example = "The pear (/ˈpɛər/) tree and shrub are a species of genus Pyrus"
# $MyVocabExample1.$VocWordID = 2

Попробуем записать это в файлы по образцу.

Немного кода #5.1

Write-Host $ScriptDir # надеюсь $ScriptDir вы не закомментировали 
$MyFilenameExample01 = $ScriptDir + "Example01.txt"
$MyFilenameExample02 = $ScriptDir + "Example02.txt"
Write-Host $MyFilenameExample01
Out-File  -FilePath $MyFilenameExample01 -InputObject $MyVocabExample1

Out-File  -FilePath $MyFilenameExample01 -InputObject -Append $MyVocabExample2
notepad $MyFilenameExample01

— и получим ошибку на строке Out-File -FilePath $MyFilenameExample01 -InputObject -Append $MyVocabExample2.

Не хочет добавлять, ай-ай какая досада.

$MyVocabExample3AsArray = @($MyVocabExample1,$MyVocabExample2)
Out-File  -FilePath $MyFilenameExample02 -InputObject $MyVocabExample3AsArray
notepad $MyFilenameExample02

Посмотрим что вышло. Прекрасный текстовый вид — а как из него обратно экспортировать? Вводить какие-то разделители текста, например запятые?

И в итоге получить «файл с разделителями — запятыми, comma-separated values (CSV) А СТОП ПОГОДИТЕ.
#

$MyFilenameExample03 = $ScriptDir + "Example03.csv"
$MyFilenameExample04 = $ScriptDir + "Example04.csv"
Export-Csv  -Path $MyFilenameExample03 -InputObject $MyVocabExample1 
Export-Csv  -Path $MyFilenameExample03 -InputObject $MyVocabExample2 -Append 
Export-Csv  -Path $MyFilenameExample04 -InputObject $MyVocabExample3AsArray 

Как легко увидеть, MS не особо отличилось логикой, для схожей процедуры в одном случае используется -FilePath, для другой -Path.

Кроме того этом в третьем файле пропал русский язык, в четвертом файле вышло… ну что то вышло. #TYPE System.Object[] 00
# „Count“,»Length»,«LongLength»,«Rank»,«SyncRoot»,«IsReadOnly»,«IsFixedSize»,«IsSynchronized»
#
Немного перепишем:

Export-Csv  -Path $MyFilenameExample03 -InputObject $MyVocabExample1 -Encoding Unicode
Export-Csv  -Path $MyFilenameExample03 -InputObject $MyVocabExample2 -Append -Encoding Unicode
notepad $MyFilenameExample03
notepad $MyFilenameExample04

Вроде помогло — но формат мне все равно не нравится.

Особенно мне не нравится, что я не могу положить строки из объекта в файл напрямую.
Кстати, раз уж начали в файлы писать — то может начинать вести лог запуска? Время как переменная у нас есть, имя файла задавать умеем.

Писать, правда, пока что нечего, зато можно подумать как лучше сделать ротацию логов.
Пока попробуем xml.

Немного xml

$MyFilenameExample05 = $ScriptDir + "Example05.xml"
$MyFilenameExample06 = $ScriptDir + "Example06.xml"
Export-Clixml  -Path $MyFilenameExample05 -InputObject $MyVocabExample1 
Export-Clixml  -Path $MyFilenameExample05 -InputObject $MyVocabExample2 -Append -Encoding Unicode
Export-Clixml  -Path $MyFilenameExample06 -InputObject $MyVocabExample3AsArray
notepad $MyFilenameExample05
notepad $MyFilenameExample06

У экспорта в xml сплошные плюсы — читаемость, экспорт всего объекта, и не надо выполнять uppend.

Попробуем прочитать файл xml.

Немного чтения из xml

$MyFilenameExample06 = $ScriptDir + "Example06.xml"
$MyVocabExample4AsArray = Import-Clixml -Path $MyFilenameExample06
# $MyVocabExample4AsArray 
# $MyVocabExample4AsArray[0]
# и немного о совершенно неочевидных нюансах. Powershell время от времени ведет себя не так, как вроде бы как бы стоило бы ожидать бы.
# например у меня эти два вывода отличаются
# Write-Output $MyVocabExample4AsArray 
# write-host $MyVocabExample4AsArray 

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

Напомню, что задача был сделать небольшого обучающего бота.

Формат работы: я отправляю боту команду «пример», бот шлет мне случайно выбранное слово и транскрипцию, и через 10 секунд шлет перевод и комментарий. Читать команды мы умеем, еще бы научиться конечно автоматически подбирать и проверять прокси, и сбрасывать счетчики сообщений в небытие.

Раскомментируем все ранее закомментированное как ненужное, закомментируем ставшие ненужными примеры с txt и csv, и сохраним файл как версию B106

А, да. Отправим что-то боту еще раз.

6. Отправка из функций и не только

Перед тем как обрабатывать прием, нужно сделать функцию отправки «хоть чего-то», кроме тестового сообщения.

Конечно, в примере у нас будет всего одна отправка и всего из одной обработки, а если нужно будет несколько раз делать одно и то же?

Проще написать функцию. Итак, у нас есть переменная типа объект $MyVocabExample4AsArray, считанная из файла, в виде массива на целых два элемента.
Поехали читать.

Одновременно разберемся с часами, нам потом это понадобится (на самом деле в этом примере — не понадобится 🙂

Немного кода #6.1

Write-Output "This is Part 6"
$Timezone = (Get-TimeZone)
IF($Timezone.SupportsDaylightSavingTime -eq $True){
    $TimeAdjust =  ($Timezone.BaseUtcOffset.TotalSeconds + 3600) } # приведенное время
    ELSE{$TimeAdjust = ($Timezone.BaseUtcOffset.TotalSeconds) 
    }
    
function MyFirstFunction($SomeExampleForFunction1){
$TimeNow = Get-Date
$TimeNow.ToUniversalTime()
# $MyText02 = $TimeNow + " " + $SomeExampleForFunction1 # и вот тут мы получим ошибку
$MyText02 = $SomeExampleForFunction1 + " " + $TimeNow # а тут не получим, кто догадается почему - тот молодец.

$URL4SendFromFunction = "https://api.telegram.org/bot$MyToken/sendMessage?chat_id=$MyChatID&text=$MyText02"
Invoke-WebRequest -Uri $URL4SendFromFunction -Proxy $MyProxy
}

Как легко увидеть, в функции вызывается $MyToken и $MyChatID, которые жестко присвоены ранее.

Так делать не нужно, и, если $MyToken — один для каждого бота, то $MyChatID будет меняться в зависимости от чата.

Однако, поскольку это пример, то пока мы это проигнорируем.

Поскольку $MyVocabExample4AsArray не массив, хотя и очень на него похож, то нельзя просто взять и запросить его длину.

Придется в очередной раз делать то, чего делать нельзя — десантироваться не по кодексу — взять и посчитать

Немного кода #6.2

$MaxRandomExample = 0 
foreach ($Obj in $MyVocabExample4AsArray) {
$MaxRandomExample ++
}
Write-Output $MaxRandomExample
$RandomExample = Get-Random -Minimum 0 -Maximum ($MaxRandomExample)
$TextForExample1 = $MyVocabExample4AsArray[$RandomExample].Original
# MyFirstFunction($TextForExample1)
# или в одну строку
# MyFirstFunction($MyVocabExample4AsArray[Get-Random -Minimum 0 -Maximum ($MaxRandomExample -1)].Example)
# Угадайте сами, какой пример легче читается посторонними людьми.

Random интересная функция. Допустим, мы хотим получать 0 или 1 (у нас всего два элемента в массиве). При задании границ 0..1 — получим ли мы «1»?
нет — не получим, у нас специально приведен пример Example 2: Get a random integer between 0 and 99 Get-Random -Maximum 100
Поэтому для 0..1 нам надо придется задавать размер 0..2, при этом максимальный номер элемента = 1.

7. Обработка пришедших сообщений и максимальная длина очереди

На чем мы ранее остановились? у нас есть полученная переменная $MyMessageGet
и полученная из нее $Content4Pars01, из которых нас интересуют элементы массива Content4Pars01.result

$Content4Pars01.result[0].update_id
$Content4Pars01.result[0].message
$Content4Pars01.result[0].message.text

Отправим боту /message10, /message11, /message12, /word и еще раз /word и /привет.
Посмотрим что мы получили:

$Content4Pars01.result[0].message.text
$Content4Pars01.result[2].message.text

Переберем все полученное и отправим ответ, если сообщение было /word
конструкция case of, то что некоторые описывают как if-elseif, в powershell вызывается через switch. При этом в коде ниже использован ключ -wildcard, что совершенно не обязательно и даже вредно.

Немного кода #7.1

Write-Output "This is part 7"
Foreach ($Result in $Content4Pars01.result) # Да, можно сделать быстрее 
 { 
    switch -wildcard ($Result.message.text) 
            {
            "/word" {MyFirstFunction($TextForExample1)}
            }
}

Выполним скрипт пару раз. Получим два раза одно и то же слово на каждую попытку выполнения, особенно если мы ошиблись в реализации random.

Но стоп. Мы же не слали /word еще раз, почему же сообщение обрабатывается повторно?

Очередь для отправки сообщений боту имеет конечную длину (кажется, 100 или 200 сообщений), и ее надо очищать вручную.

В документации это конечно же описано, но ее же надо читать!

В данном случае нам нужен параметр ?chat_id, а &timeout, &limit, &parse_mode=HTML и &disable_web_page_preview=true пока не нужны.

Документация по telegram api находится тут
Английским по белому там написано:
Identifier of the first update to be returned. Must be greater by one than the highest among the identifiers of previously received updates. 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. The negative offset can be specified to retrieve updates starting from -offset update from the end of the updates queue. All previous updates will forgotten.

Посмотрим на:

$Content4Pars01.result[0].update_id
$Content4Pars01.result[1].update_id 
$Content4Pars01.result | select -last 1
($Content4Pars01.result | select -last 1).update_id

Да и сбросим — немного перепишем функцию. У нас есть два варианта — передавать в функцию все сообщение и обрабатывать его целиком в функции, или же отдавать только ID сообщения и его же и сбрасывать. Для примера второе выглядит проще.

Раньше у нас строка запроса «всех сообщений» выглядела как

$URLGET = "https://api.telegram.org/bot$MyToken/getUpdates"

а будет выглядеть как

$LastMessageId = ($Content4Pars01.result | select -last 1).update_id
$URLGET1 = "https://api.telegram.org/bot$mytoken/getUpdates?offset=$LastMessageId&limit=100" 
$MyMessageGet = Invoke-WebRequest -Uri $URLGET1 -Method Get -Proxy $MyProxy 

Никто не запрещает сначала получить все сообщения, их обработать, и только после успешной обработки запрросить unconfirmed -> confirmed.

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

Еще пара строчек кода

$LastMessageId = ($Content4Pars01.result | select -last 1).update_id  #ошибку в этом месте предполагается исправить самостоятельно. 
$URLGET1 = "https://api.telegram.org/bot$mytoken/getUpdates?offset=$LastMessageId&limit=100" 
Invoke-WebRequest -Uri $URLGET1 -Method Get -Proxy $MyProxy

8. Вместо заключения

Основные функции — чтение сообщений, сброс очереди, чтение из файла и запись в файл выполнены и показаны.

Осталось сделать всего четыре вещи:

  • отправка правильного ответа на запрос в чат
  • отправка ответа в ЛЮБОЙ чат, в который добавили бота
  • выполнение кода в цикле
  • запуск бота из планировщика windows.

Все эти задачи простые и легко реализуются с помощью чтения документации про такие параметры, как
Set-ExecutionPolicy Unrestricted и -ExecutionPolicy Bypass
цикла вида

$TimeToSleep = 3 # опрос каждые 3 секунды
$TimeToWork = 10 # минут
$HowManyTimes = $TimeToWork*60/$TimeToSleep # счетчик для цикла
$MainCounter = 0
for ($MainCounter=0; $MainCounter -le $HowManyTimes) {
sleep $TimeToSleep
$MainCounter ++

Всем спасибо, кто дочитал.

Источник: habr.com