Добрый день, читатели данной публикации. В этой статье я бы хотел рассказать про то, как сделать телеграм бота попугая, используя базу 1С как основу для обработки поступающих запросов от бота. Прошу не судить строго, опыта в написании статей нет.
Итак, первое, что делаем, это подключаем IIS, с помощью которого будем публиковать базу.
Открываем Панель управления->Программы->Программы и компоненты->Включение и отключение компонентов windows. Раскрываем следующий путь Службы IIS->Службы интернета->Компоненты разработки приложений. Включаем все с припиской ASP.
После этого проверяем в браузере работу IIS, вбиваем localhost, либо внутренний ip нашего компьютера, мы должны увидеть следующие заглушку
Создаем новую базу и переходим к её расположению в проводнике для выдачи прав доступа.
Настройка прав IIS для нашей базы
Открываем папку в которой находится папка с нашей конфигурацией, если в списке пользователей на вкладке Безопасность нет пользователей IIS_IUSRS и IUSR. Теперь делаем следующее для каждого Вкладка Безопасность->Изменить->Добавить->Дополнительно->Поиск, находим пользователя, двойной клик, ок, полный доступ в колонке разрешить, применить. Повторить для второго.
Теперь перейдем к базе
Работа с документом для логов
В базе Создаем документ ВходящиеЗапросы с реквизитами Метод(Строка,20), КодОтвета(Число,3), ТелоЗапроса(Строка, неограниченная), ТелоОтвета(Строка,Неограниченнная). На форме документа, для ТелоЗапроса и ТелоОтвета вид устанавливаем на Поле текстового документа, а сами реквизиты помещаем в группу с горизонтальной ориентации для удобства. На форме списке документа, убираем колонки ТелоЗапроса и ТелоОтвета чтобы не нагружать список.
Работа с HTTP сервисом и публикация базы
Создадим HTTPсервис СервисТелеграммБота, корневой URL установим на TgBots, создаем шаблон с именем Ping и шаблоном /Ping, методом для него будет GetPing, с методом Get. Создаем второй шаблон с именем Send и шаблоном /Send, методом для него будет PostSend с методом Post.
В обработчике PingGetPing для ответа установим тело из Строки
Функция PingGetPing(Запрос)
Ответ = Новый HTTPСервисОтвет(200);
Ответ.УстановитьТелоИзСтроки("Its all good");
Возврат Ответ;
КонецФункции
Обновляем базу и теперь необходимо опубликовать нашу базу(Только если база запущена от имени Администратора, если нет, то выйдите и запустите уже от админа). Переходим в Администрирование->Публикация на Веб-сервере, в поле имя пишем, то как сможем обратиться к базе в адресной строке, Веб сервер должен быть Internet Information Services, Каталог, то где находиться папка с файлами для публикации базы на Веб Сервере, на закладке HTTP сервисы должен быть включен созданный нами сервис.
Публикуем базу. Чтобы проверить открываем адресную строку и по такому шаблону подставляя нужное(вместо localhost может быть внутренний ip)
locahost/{Имя Публикуемой Базы}/hs/{КорневойURL HTTPСервиса}/{Прописанный Шаблон}
В моем случае "localhost/InfostartBaseTG/hs/TgBots/Ping"
Получаем Its all good
Теперь надо написать обработку получаемых базой запросов, создаем серверные Общие модули "ОбщийМодульРаботыСHTTP" и "ОбщийМодульРаботыСТГ" и пишем следующий код в соответствующих модулях
СервисТелеграммБота модуль
//Наш модуль HTTP сервиса
Функция PingGetPing(Запрос)
//GET запрос будет возвращать простой ответ
//об успешной обработке запроса
Возврат МодульРаботыСHTTP.ПолучитьПростойУспешныйОтвет();
КонецФункции
Функция SendPostSend(Запрос)
//Обрабатывать сообщения мы будем через попытку и при ошибке
//мы будет вызывать функцию обработки ошибки
//При любом варианте мы будем получать уже готовый для отправки ответ.
Попытка
Ответ=МодульРаботыСТГ.ОбработатьВходящийЗапрос(Запрос);
Исключение
Ответ=МодульРаботыСHTTP.ОтветОбОшибке(ИнформацияОбОшибке());
КонецПопытки;
//Вызов процедуры, фиксирующей все обращения к шаблону send этого HTTPСервиса
МодульРаботыСHTTP.ЗаписатьЛог("send",Запрос,Ответ);
Возврат Ответ;
КонецФункции
#Область ПрограммныйИнтерфейс
//функция возвращающая строку JSON с табуляцией
//на основании переданного объекта
Функция ПолучитьСтрокуJSON(ОбъектJSON) Экспорт
ПараметрыЗаписиJSON=Новый ПараметрыЗаписиJSON(,Символы.Таб);
ЗаписьJSON=Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку(ПараметрыЗаписиJSON);
ЗаписатьJSON(ЗаписьJSON,ОбъектJSON);
Возврат ЗаписьJSON.Закрыть();
КонецФункции
//функция возвращающая объект
//на основании переданной строки JSON
Функция ПолучитьОбъектJSON(СтрокаJSON) Экспорт
ЧтениеJSON=Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(СтрокаJSON);
ОбъектJSON=ПрочитатьJSON(ЧтениеJSON);
ЧтениеJSON.Закрыть();
Возврат ОбъектJSON
КонецФункции
//функция создает ответ, на основании переданного ей объекта
Функция ОтветJSON(Объект,КодСостояния=200) Экспорт
HTTPСервисОтвет=Новый HTTPСервисОтвет(КодСостояния);
HTTPСервисОтвет.УстановитьТелоИзСтроки(ПолучитьСтрокуJSON(Объект));
HTTPСервисОтвет.Заголовки.Вставить("Content-Type","application/json");
Возврат HTTPСервисОтвет;
КонецФункции
//функция создает простой ответ об успешном обрабатывании кода
Функция ПолучитьПростойУспешныйОтвет() Экспорт
СтруктураОтвета=Новый Структура;
СтруктураОтвета.Вставить("Result","Its all good");
Возврат ОтветJSON(СтруктураОтвета)
КонецФункции
//функция фиксирует ошибку в журнале регистрации и
//передает информацию о ней в ответ
Функция ОтветОбОшибке(ИнформацияОбОшибке) Экспорт
ЗаписьЖурналаРегистрации("HTTPСервисы.Ошибка",УровеньЖурналаРегистрации.Ошибка,,,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке));
Результат=Новый Структура;
Результат.Вставить("Result","Error");
Результат.Вставить("ErrorText",КраткоеПредставлениеОшибки(ИнформацияОбОшибке));
Возврат ОтветJSON(Результат,500);
КонецФункции
//Процедура будет фиксировать все поступающие http запросы
Процедура ЗаписатьЛог(ИмяМетода,Запрос,Ответ) Экспорт
НовыйЛог=Документы.ВходящиеЗапросы.СоздатьДокумент();
НовыйЛог.Метод=ИмяМетода;
ТелоЗапроса=Запрос.ПолучитьТелоКакСтроку();
Попытка
ТелоЗапроса=ПолучитьСтрокуJSON(ПолучитьОбъектJSON(ТелоЗапроса));
Исключение
ОтветОбОшибке(ИнформацияОбОшибке());
КонецПопытки;
НовыйЛог.ТелоЗапроса=ТелоЗапроса;
НовыйЛог.ТелоОтвета=Ответ.ПолучитьТелоКакСтроку();
НовыйЛог.КодОтвета=Ответ.КодСостояния;
НовыйЛог.Дата=ТекущаяДатаСеанса();
НовыйЛог.Записать();
КонецПроцедуры
#КонецОбласти
#Область ПрограммныйИнтерфейс
//обрабатываем входящий запрос
Функция ОбработатьВходящийЗапрос(Запрос) Экспорт
//Преобразуем JSONСтроку в структура(объеки) и разбираем его по свойствам(полям)
ДанныеЗапроса= МодульРаботыСHTTP.ПолучитьОбъектJSON(Запрос.ПолучитьТелоКакСтроку());
//Если полученная структура имеет свойство message то обрабатываем её иначе
//возвращаем простой успешный ответ
Если ДанныеЗапроса.Свойство("message") тогда
Возврат ОбработатьСообщение(ДанныеЗапроса.message)
Иначе
Возврат МодульРаботыСHTTP.ПолучитьПростойУспешныйОтвет()
КонецЕсли;
КонецФункции
#КонецОбласти
#Область СлужебныеПроцедурыИФункции
Функция ОбработатьСообщение(ДанныеСообщения)
//У каждого чата в телеграм есть уникальный идентификатор,
//с помощью которого можно обратиться к нему и отправить сообщение
ИдентификаторЧата=ДанныеСообщения.Chat.id;
СтруктураОтвета=Новый Структура;
СтруктураОтвета.Вставить("chat_id",ИдентификаторЧата);
//получаем ваш текст сообщения или подпись
//которую вы прикрепили в фото или аудио
//
ТекстСообщения=ПолучитьТекстСообщения(ДанныеСообщения);
ПолучательСообщения="";
//Так как в полученной структуре может отсутствовать отправитель
//то проверяем есть ли он
Если ДанныеСообщения.Свойство("from") тогда
Отправитель=ДанныеСообщения.from;
//username это то как мы можем найти нашего пользователя через собаку
//если его нет то пытаемся получить имя или фамилию отправителя
//в крайнем случае получаем уникальный id пользователя,
//он не пишется в профиле пользователя, но его можно найти через
//другого бота @useridgetbot
Если Отправитель.Свойство("username") тогда
ПолучательСообщения="@"+Отправитель.username;
ИначеЕсли Отправитель.Свойство("first_name") тогда
ПолучательСообщения=Отправитель.first_name;
ИначеЕсли Отправитель.Свойство("last_name") тогда
ПолучательСообщения=Отправитель.last_name;
Иначе
ПолучательСообщения=Отправитель.id;
КонецЕсли;
КонецЕсли;
//Теперь мы проверяем на то что мы отправили нашему боту для каждого из них есть свой уникальный тип
//если мы отправляем какую-либо аудио запись, то используется метод sendAudio,а к файлу можно получить доступ через уникальный file_id
//он указывает на файл на серверах телеграма и зная этот id, мы можем вновь его отправить
//свойство capition это подпись под фото или аудио
//фото в запросе приходит Photo как массив объектов PhotoSize, мы получаем последний, так как его качество лучше
Если ДанныеСообщения.Свойство("audio") тогда
СтруктураОтвета.Вставить("method","sendAudio");
СтруктураОтвета.Вставить("caption",ПолучательСообщения+", "+ТекстСообщения);
СтруктураОтвета.Вставить("audio",ДанныеСообщения.audio.file_id);
ИначеЕсли ДанныеСообщения.Свойство("photo") тогда
КоличествоРазмеров=ДанныеСообщения.photo.Количество();
СтруктураОтвета.Вставить("method","sendPhoto");
СтруктураОтвета.Вставить("caption",ПолучательСообщения+", "+ТекстСообщения);
СтруктураОтвета.Вставить("photo",ДанныеСообщения.photo[КоличествоРазмеров-1].file_id);
ИначеЕсли ДанныеСообщения.Свойство("voice") тогда
СтруктураОтвета.Вставить("method","sendVoice");
СтруктураОтвета.Вставить("voice",ДанныеСообщения.voice.file_id);
ИначеЕсли ДанныеСообщения.Свойство("video_note") тогда
СтруктураОтвета.Вставить("method","sendVideoNote");
СтруктураОтвета.Вставить("video_note",ДанныеСообщения.video_note.file_id);
ИначеЕсли ДанныеСообщения.Свойство("sticker") тогда
СтруктураОтвета.Вставить("method","sendSticker");
СтруктураОтвета.Вставить("sticker",ДанныеСообщения.sticker.file_id);
Иначе
СтруктураОтвета.Вставить("method","sendMessage");
СтруктураОтвета.Вставить("text",ПолучательСообщения+", "+ТекстСообщения);
КонецЕсли;
Возврат МодульРаботыСHTTP.ОтветJSON(СтруктураОтвета);
КонецФункции
Функция ПолучитьТекстСообщения(ДанныеСообщения)
//подпись бота нужна, для выделения от кого это сообщения
//если добавить его в группу, то будет неудобно читать беседу
ПодписьБота=Символы.ПС+"© бот";
//у сообщения есть text если это простое текстовое сообщение
//и capition если это надпись у аудио или фото файла
//если нет ни текста ни capition значит в сообщении пришел объект,
//логику обработки которого я еще не написал
Если ДанныеСообщения.Свойство("text") тогда
ТекстСообщения=ДанныеСообщения.text+ПодписьБота;
ИначеЕсли ДанныеСообщения.Свойство("caption") тогда
ТекстСообщения=ДанныеСообщения.caption+ПодписьБота;
Иначе
ТекстСообщения="ошибка в распознавании сообщения 1с"+ПодписьБота;
КонецЕсли;
Возврат ТекстСообщения
КонецФункции
#КонецОбласти
#Область ПрограммныйИнтерфейс
//обрабатываем входящий запрос
Функция ОбработатьВходящийЗапрос(Запрос) Экспорт
//Преобразуем JSONСтроку в структура(объеки) и разбираем его по свойствам(полям)
ДанныеЗапроса= МодульРаботыСHTTP.ПолучитьОбъектJSON(Запрос.ПолучитьТелоКакСтроку());
//Если полученная структура имеет свойство message то обрабатываем её иначе
//возвращаем простой успешный ответ
Если ДанныеЗапроса.Свойство("message") тогда
Возврат ОбработатьСообщение(ДанныеЗапроса.message)
Иначе
Возврат МодульРаботыСHTTP.ПолучитьПростойУспешныйОтвет()
КонецЕсли;
КонецФункции
#КонецОбласти
#Область СлужебныеПроцедурыИФункции
Функция ОбработатьСообщение(ДанныеСообщения)
//У каждого чата в телеграм есть уникальный идентификатор,
//с помощью которого можно обратиться к нему и отправить сообщение
ИдентификаторЧата=ДанныеСообщения.Chat.id;
СтруктураОтвета=Новый Структура;
СтруктураОтвета.Вставить("chat_id",ИдентификаторЧата);
//получаем ваш текст сообщения или подпись
//которую вы прикрепили в фото или аудио
//
ТекстСообщения=ПолучитьТекстСообщения(ДанныеСообщения);
ПолучательСообщения="";
//Так как в полученной структуре может отсутствовать отправитель
//то проверяем есть ли он
Если ДанныеСообщения.Свойство("from") тогда
Отправитель=ДанныеСообщения.from;
//username это то как мы можем найти нашего пользователя через собаку
//если его нет то пытаемся получить имя или фамилию отправителя
//в крайнем случае получаем уникальный id пользователя,
//он не пишется в профиле пользователя, но его можно найти через
//другого бота @useridgetbot
Если Отправитель.Свойство("username") тогда
ПолучательСообщения="@"+Отправитель.username;
ИначеЕсли Отправитель.Свойство("first_name") тогда
ПолучательСообщения=Отправитель.first_name;
ИначеЕсли Отправитель.Свойство("last_name") тогда
ПолучательСообщения=Отправитель.last_name;
Иначе
ПолучательСообщения=Отправитель.id;
КонецЕсли;
КонецЕсли;
//Теперь мы проверяем на то что мы отправили нашему боту для каждого из них есть свой уникальный тип
//если мы отправляем какую-либо аудио запись, то используется метод sendAudio,а к файлу можно получить доступ через уникальный file_id
//он указывает на файл на серверах телеграма и зная этот id, мы можем вновь его отправить
//свойство capition это подпись под фото или аудио
//фото в запросе приходит Photo как массив объектов PhotoSize, мы получаем последний, так как его качество лучше
Если ДанныеСообщения.Свойство("audio") тогда
СтруктураОтвета.Вставить("method","sendAudio");
СтруктураОтвета.Вставить("caption",ПолучательСообщения+", "+ТекстСообщения);
СтруктураОтвета.Вставить("audio",ДанныеСообщения.audio.file_id);
ИначеЕсли ДанныеСообщения.Свойство("photo") тогда
КоличествоРазмеров=ДанныеСообщения.photo.Количество();
СтруктураОтвета.Вставить("method","sendPhoto");
СтруктураОтвета.Вставить("caption",ПолучательСообщения+", "+ТекстСообщения);
СтруктураОтвета.Вставить("photo",ДанныеСообщения.photo[КоличествоРазмеров-1].file_id);
ИначеЕсли ДанныеСообщения.Свойство("voice") тогда
СтруктураОтвета.Вставить("method","sendVoice");
СтруктураОтвета.Вставить("voice",ДанныеСообщения.voice.file_id);
ИначеЕсли ДанныеСообщения.Свойство("video_note") тогда
СтруктураОтвета.Вставить("method","sendVideoNote");
СтруктураОтвета.Вставить("video_note",ДанныеСообщения.video_note.file_id);
ИначеЕсли ДанныеСообщения.Свойство("sticker") тогда
СтруктураОтвета.Вставить("method","sendSticker");
СтруктураОтвета.Вставить("sticker",ДанныеСообщения.sticker.file_id);
Иначе
СтруктураОтвета.Вставить("method","sendMessage");
СтруктураОтвета.Вставить("text",ПолучательСообщения+", "+ТекстСообщения);
КонецЕсли;
Возврат МодульРаботыСHTTP.ОтветJSON(СтруктураОтвета);
КонецФункции
Функция ПолучитьТекстСообщения(ДанныеСообщения)
//подпись бота нужна, для выделения от кого это сообщения
//если добавить его в группу, то будет неудобно читать беседу
ПодписьБота=Символы.ПС+"© бот";
//у сообщения есть text если это простое текстовое сообщение
//и capition если это надпись у аудио или фото файла
//если нет ни текста ни capition значит в сообщении пришел объект,
//логику обработки которого я еще не написал
Если ДанныеСообщения.Свойство("text") тогда
ТекстСообщения=ДанныеСообщения.text+ПодписьБота;
ИначеЕсли ДанныеСообщения.Свойство("caption") тогда
ТекстСообщения=ДанныеСообщения.caption+ПодписьБота;
Иначе
ТекстСообщения="ошибка в распознавании сообщения 1с"+ПодписьБота;
КонецЕсли;
Возврат ТекстСообщения
КонецФункции
#КонецОбласти
Для установки Webhook`ов необходим белый ip, мы можем купить, либо использовать ngrok, он позволяет обращаться к внутреннему ip компьютера из внешней сети путем выдаваемого публичного адреса.
Переходим по этой ссылке, регистрируемся и качаем https://dashboard.ngrok.com/get-started/setup , после скачивания вводим команду из пункта 2 и запускаем туннель последней командой. Проверяем введя в адресную строку выданный нами адрес, если все сделано правильно, то увидим заглушку IIS.
Почти конец, теперь нам нужен телеграм бот, переходим по еще одной ссылке https://t.me/botfather и создаем нового бота. Переходим по выданной ссылке и активируем бота отправим /start .
Копируем токен после создания бота и переходим в адресную строку браузера. Нам нужно прописать следующее
https://api.telegram.org/bot{Скопированный нами токен бота}/SetWebhook?url={Полный путь до нашего шаблона send, но вместо localhost или внутреннего апи, адрес который выдал нам ngrock}
То есть это будет выглядеть так в моем случае
https://api.telegram.org/bot1111111111:AAAAAAAAAAAAAAAAAAAAAAA-BBBBBBBB/setWebhook?url=https://my_ngrock_adress.ngrok-free.app/InfostartBaseTG/hs/TgBots/Send
Если все сделано правильно, то нам выдаст
{"ok":true,"result":true,"description":"Webhook was set"} это значит что мы связали телеграм бота с нашим шаблоном send и все запросы, которые будет получать, будут идти на нашу базу.
Также стоит учитывать, что при ошибке в записи логов, мы не сможем получить отклика от бота, а в базе лога не будет, поэтому советую использовать http://localhost:4040/inspect/http эту ссылку, на ней также ведутся все логи. Я, конечно, проверил, но мало ли, да и просто полезно.
Также стоит упомянуть, то откуда брал информацию, в основном это один ролик https://www.youtube.com/watch?v=peZsik57m4k от ребят из Желтого клуба 1с, документация по API телеграм https://core.telegram.org/bots/api и https://telegram-bot-sdk.readme.io/ этот сайт, но туда я заходил просто для проверить параметры.
Разумеется, это не все, что можно реализовать. Можно создать клавиатуру или просто настроить команды и проверять текст на наличие "/", чтобы запускать команды по рассылке или получение информации о чем-либо, но здесь я показал только основы.
И еще, я пишу первый раз и во всех Тегах, Категориях и прочем я не разбираюсь, увы, поэтому если где-то не там опубликовал, напишите, может можно переопубликовать.