Создаем бота Telegram



Легко и непринужденно создаем бота, который поможет получать данные из 1С, используя API Teletram

Используя побликации  //infostart.ru/public/387433/ и //infostart.ru/public/419846/ решил создать бота, используя API Telegram

Для разбора JSON использовалась обработка из публикации: //infostart.ru/public/119601/

Регистрируем бот и получаем ТОКЕН, тут: //infostart.ru/public/419846/  есть описание, как это сделать

В текущей версии реализована реакция на тестовые сообщения от пользователя;

«скажи» — бот в ответ посылает сообщение

«отчеты» — показывается клавиатура со списком доступных отчетов

«файлы» — показывается клавиатура со списком доступных файлов

Также бот получает присланные фотографии.

Основные процедуры бота:

&НаСервере
Процедура ПрочитатьСобщенияНаСервере() Экспорт
Сервер = "api.telegram.org";

СтрокаСоединения = "/bot"+Токен+"/getUpdates" + ?(ПоследнийИД = "", "", "?offset=" + ПоследнийИД);

HTTPЗапрос = Новый HTTPЗапрос();
Заголовки = Новый Соответствие;

HTTPЗапрос.Заголовки.Вставить("Connection", "keep-alive");
HTTPЗапрос.АдресРесурса = СтрокаСоединения;

ЗащищенноеСоединение = Новый ЗащищенноеСоединениеOpenSSL(Новый СертификатКлиентаWindows, Новый СертификатыУдостоверяющихЦентровWindows);
HTTPСоединение =  Новый HTTPСоединение(Сервер,,,,Новый ИнтернетПрокси,, ЗащищенноеСоединение);
Попытка
ОтветHTTP = HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос);
Исключение
Сообщить(ОписаниеОшибки());
Возврат;
КонецПопытки;

ДанныеКакСтрока = ОтветHTTP.ПолучитьТелоКакСтроку();

обОбработка = РеквизитФормыВЗначение("Объект");

Рез = обОбработка.мПрочитатьJSON(ДанныеКакСтрока);
Если Рез["ok"] <> Истина Тогда
Возврат;
КонецЕсли;


Рез = СоответвиеВСтруктуру(Рез);

result = Рез.result;
МаксИД = 0;

Для Каждого mes из result Цикл

ПоследнийИД = Формат(mes.update_id + 1, "ЧГ=");

Попытка
message  = mes.message;
Исключение
Продолжить;
КонецПопытки;

Дата = '19700101' + message.date +7*60*60;

Попытка
from = message.from.username;
Исключение
from = message.from.first_name;
КонецПопытки;

chatИД = Формат(message.chat.id, "ЧГ=");

Если message.Свойство("text") Тогда
Ок= истина;
Если Найти(ВРег(message.text), "СКАЖИ")<>0 Тогда

HTTPЗапрос.АдресРесурса = "/bot"+Токен+"/sendMessage?text=Hello I am BOT&chat_id="+chatИД+"&reply_to_message_id=" + message.message_id;
HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос);

ИначеЕсли  Врег(message.text) = "ОТЧЕТЫ" Тогда

HTTPЗапрос.АдресРесурса = "/bot"+Токен+"/sendMessage?text=Доступные отчеты: &chat_id="+chatИД+"&reply_to_message_id=" + message.message_id +
"&reply_markup="+КлаваОтчеты;
HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос);

ИначеЕсли  Врег(message.text) = "ФАЙЛЫ" Тогда

HTTPЗапрос.АдресРесурса = "/bot"+Токен+"/sendMessage?text=Доступные файлы: &chat_id="+chatИД+"&reply_to_message_id=" + message.message_id +
"&reply_markup="+КлаваФайлы;
HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос);

ИначеЕсли Лев(message.text, 7) = "Отчет: " Тогда

HTTPЗапрос.АдресРесурса = "/bot"+Токен+"/sendMessage?text=Ждите. Отчет формируется&chat_id="+chatИД+"&reply_to_message_id=" + message.message_id;
HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос);

ТекстОтчета = ПолучитьТекстОтчета(message.text);

HTTPЗапрос.АдресРесурса = "/bot"+Токен+"/sendMessage?parse_mode=HTML&text="+Кодировать(ТекстОтчета)+"&chat_id="+chatИД+"&reply_to_message_id=" + message.message_id;
HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос);

ИначеЕсли message.text = "Файл: PDF" Тогда

ОтправитьФайлPDF(chatИД, HTTPСоединение, HTTPЗапрос)

ИначеЕсли message.text = "Файл: XLS" Тогда

ОтправитьФайлXLS(chatИД, HTTPСоединение, HTTPЗапрос)

КонецЕсли;

ИначеЕсли message.Свойство("photo") Тогда
МассивФото = message.photo;
Для Каждого Фото из МассивФото Цикл
HTTPЗапрос.АдресРесурса = "/bot"+Токен+"/getFile?file_id=" + Фото.file_id;
Ответ = HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос);
СтрокаОтвета = Ответ.ПолучитьТелоКакСтроку();
Путь = СтрЗаменить(Сред(СтрокаОтвета, Найти(СтрокаОтвета,"photo/") + 7) , """}}", "");
СтрФото = Фотографии.Добавить();
СтрФото.Имя = Путь;
СтрФото.Размер = "" + Фото.height +"x" + Фото.width;
КонецЦикла;

КонецЕсли;
КонецЦикла;
КонецПроцедуры


&НаСервере
Функция СоответвиеВСтруктуру(Рез)
мРез = Новый Структура;
Для Каждого Эл из Рез Цикл
Ключ = Эл.Ключ;
мЗнач = Эл.Значение;
Если ТипЗнч(мЗнач) =Тип("Соответствие") Тогда
мЗнач = СоответвиеВСтруктуру(мЗнач);
ИначеЕсли ТипЗнч(мЗнач) =Тип("Массив") Тогда
мЗнач = Новый Массив;
Для Каждого элМ из Эл.Значение Цикл
мЗнач.Добавить(СоответвиеВСтруктуру(элМ));
КонецЦикла;
КонецЕсли;
мРез.Вставить(Ключ, мЗнач);
КонецЦикла;
Возврат мРез;
КонецФункции

&НаСервере
функция Кодировать(Текст)
Возврат ?(Текст = "", "Пусто", КодироватьСтроку(Текст,  СпособКодированияСтроки.URLВКодировкеURL));
КонецФункции

&НаСервере
Процедура ОтправитьФайлPDF(chat_id, HTTPСоединение, HTTPЗапрос)

Таб = РеквизитФормыВЗначение("Объект").ПолучитьМакет("МакетОтчета");


ИмяФайла = ПолучитьИмяВременногоФайла("PDF");
Таб.АвтоМасштаб = Истина;
Таб.Записать(ИмяФайла, ТипФайлаТабличногоДокумента.PDF);


СтрокаСоединения = "/bot" + Токен + "/sendDocument";

Boundary = "----"+Строка(Новый УникальныйИдентификатор());

//Определяем массив для процедуры ОбъединитьФайлы
МассивФайловДляОбъединения = Новый Массив;

//Формируем начальный фрагмент файла POST-запроса
ИмяФайлаОтправкиНачало = ПолучитьИмяВременногоФайла("txt");
ФайлОтправкиНачало = Новый ЗаписьТекста(ИмяФайлаОтправкиНачало, КодировкаТекста.UTF8);

//Формируем конечный фрагмент файла POST-запроса
ИмяФайлаОтправкиКонец = ПолучитьИмяВременногоФайла("txt");
ФайлаОтправкиКонец = Новый ЗаписьТекста(ИмяФайлаОтправкиКонец, КодировкаТекста.UTF8);

ТекстДляОтправки = "";

ТекстДляОтправки = ТекстДляОтправки + "--"+Boundary + Символы.ПС;
ТекстДляОтправки = ТекстДляОтправки + "Content-Disposition: form-data; name=""chat_id""" + Символы.ПС + Символы.ПС;
ТекстДляОтправки = ТекстДляОтправки + chat_id + Символы.ПС;

ТекстДляОтправки = ТекстДляОтправки + "--"+Boundary + Символы.ПС;
ТекстДляОтправки = ТекстДляОтправки + "Content-Disposition: form-data; name=""document""; filename=""report.PDF""" + Символы.ПС;

ФайлОтправкиНачало.ЗаписатьСтроку(ТекстДляОтправки );
ФайлОтправкиНачало.Закрыть();

МассивФайловДляОбъединения.Добавить(ИмяФайлаОтправкиНачало);

МассивФайловДляОбъединения.Добавить(СокрЛП(ИмяФайла));

ТекстДляОтправки = "" + Символы.ПС;
ТекстДляОтправки = ТекстДляОтправки + "--"+Boundary+"--";
ФайлаОтправкиКонец.ЗаписатьСтроку(ТекстДляОтправки);
ФайлаОтправкиКонец.Закрыть();
МассивФайловДляОбъединения.Добавить(ИмяФайлаОтправкиКонец);

ИмяФайлаОтправки = ПолучитьИмяВременногоФайла("txt");
ОбъединитьФайлы(МассивФайловДляОбъединения, ИмяФайлаОтправки);

HTTPЗапрос.Заголовки.Вставить("Connection", "keep-alive");
HTTPЗапрос.Заголовки.Вставить("Content-Type", "multipart/form-data; boundary="+Boundary);

HTTPЗапрос.УстановитьИмяФайлаТела(ИмяФайлаОтправки);
HTTPЗапрос.АдресРесурса = СтрокаСоединения;

Попытка
ОтветHTTP = HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос);
Исключение
Сообщить(ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры

Добавлено:

Что бы клавиатура появлялась прямо в чате необходимо подавать другой текст клавиатуры в обработке он получается так:

 Строки = Новый Массив;
Кнопки = Новый Массив;
Кнопки.Добавить(Новый структура("text, callback_data", "Файл: PDF", "1"));
Кнопки.Добавить(Новый структура("text, callback_data", "Файл: XLS", "2"));
Строки.Добавить(Кнопки);

КлавиатураВСообщении = JSON.мЗаписатьJSON(Новый Структура("inline_keyboard", Строки), Ложь);

если текстом:

КлавиатураВСообщении =
"{""inline_keyboard"":[[{""text"":""Файл: PDF"",""callback_data"":""1""},{""text"":""Файл: XLS"",""callback_data"":""2""}]]}"

обработка нажатия на эту кнопку отличается от обработки нажатия на простую клавиатуру, которая впринципе просто отсылает сообщение с нужным текстом:

блок «сallback_query» как раз говорит нам о нажатии на такую кнопку, в нем будет содержаться информации о коде нажатой кноки, в нашем примере это «1» или «2»

КодНажатойКнопки = mes.callback_query.data

далее обратабываем так как нам надо

30 Comments

  1. TreeDogNight

    Баян =(

    Reply
  2. ifal

    Желтый у вас заголовок, где же тут процесс создания бота?

    Reply
  3. FirePyres

    (2) ifal, Как понять что он «желтый»?

    Reply
  4. ifal

    (3) В заголовке статьи написано: » создаем бота…» -, я ожидал увидеть описание процесса, а что видим внутри? ссылки на другие статьи, где описан процесс, а все что ваше личное упрятано в обработку. Поэтому я делаю такой вывод. Правильней звучало бы «Пример использования бота Telegram».

    Reply
  5. FirePyres

    (4) Добавил тексты основных процедур

    Хотел описать, но код вроде настолько простой что вроде и объяснений не надо

    Reply
  6. FirePyres

    Добавил пример inline клавиатуры

    Reply
  7. METAL

    Добрый день!

    Скажите, а не приходилось ли Вам делать интеграцию с Facebook API https://developers.facebook.com/ ?

    Иными словами, можно ли то же самое прикрутить не к Телеграм, а к Фейсбуку?

    Просто в нашей компании никто Телеграм не использует, а интеграцию с каким-нибудь популярным мессенджером хотят, и несмотря на мои уговоры, даже пробовать телеграм не хотят, аргумент такой — вот есть Фейсбук, приложение у всех стоит, АПИ есть, пусть корпоративный бот работает на этой платформе…

    Но мне пока не удалось найти ни одного примера реализации бота.. Это потому что на порядок сложней, чем с телеграмом, или просто руки не дошли ни у кого?..

    Спасибо!

    Reply
  8. FirePyres

    (7) Я бы попробовал, но не совсем понимаю как это должно работать

    Как вы это себе представляете?

    Reply
  9. METAL

    (8) аналогично

    Пользователь подписывается на бота

    И дальше например пишет в чат ОТЧЕТЫ и может выбрать нужный отчёт за нужную дату (sales today — без кнопок для выбора тоже подойдёт, непринципиально)

    Либо происходит событие в 1С какое-то, и пользователю в мессенджер приходит уведомление

    Но насколько я вчёра смог разобраться — при разработке придётся использовать некую php-прослойку, так как для ФБ обязательно использовать webhook, как я понял.. По крайней мере, именно на этом этапе я споткнулся http://prntscr.com/d4yojf

    Мнение одного специалиста, который делал и для ФБ (на php), и для телеграм (он писал мне в личку, поэтому имя не публикую):

    На 1С не делал, делал на php. но там чуть чуть меньше возможностей, есть ограничения и т.д. В принципе можно на php запустить на сайте готовый какой нить движок работы с фейсбуком, и написать для него АПИ интерфейса для 1с. Это самое простое решение будет
    Reply
  10. PhoenixAOD

    Интересное решение

    Reply
  11. FirePyres

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

    «Представленные паблик аккаунты — это чат-каналы, которые можно использовать для отправки важной (рекламной) информации пользователям, проводить презентации, размещать новости, полезные ссылки, GIF-изображения, всячески общаться и получать обратную реакцию от участников канала. Также создателям каналов доступны CRM-инструменты для управления чатом.»

    Reply
  12. rayastar

    Коллеги, приветствую! С ботом разобрался, все отлично, но есть одно НО. Почему то иногда мои команды не обрабатываются. Опишу проблему. Отправляю команду — и надеюсь увидеть ее в входящем файле json. Но там остается какой то перечень старых команд…в чем может быть проблема? Вот только что отправил боту команду — пришел ответ, все ок. отправляю следующую команду — и в ответ тишина…Мне не совсем понятна логика формирования json файла

    Reply
  13. terrorion

    Добрый день, коллеги.

    Никак не могу разобраться, как удалить клавиатуру в чате («inline_keyboard»).

    Был бы признателен за наглядный пример.

    Reply
  14. FirePyres

    День добрый.

    Вроде работает если передать пустую клавиатуру: «{«»keyboard»»:[],»»one_time_keyboard»»:true}»

    Reply
  15. terrorion

    (15) Спасибо за ответ. Но, к сожалению, ничего не получилось. Насколько я понял, как то нужно использовать ReplyKeyboardRemove.

    Reply
  16. PLAstic

    (13) Внимательно прочитайте описание https://core.telegram.org/bots/api#getupdates

    Наверняка, вы увидели, что параметры не обязательны и юзаете без них. Поэтому получаете первые 100 сообщений по сроку давности. Если вчитаться, то там написано, что этот оффсет — это не смещение, а номер первого непрочитанного сообщения. Т.е. последний полученный ид сообщения + 1.

    Получается, чтобы получать свежие сообщения, надо указывать параметр offset.

    Reply
  17. PLAstic

    (14) Передайте пустую клавиатуру.

    Процедура ИзменитьКлавиатуруСообщения(Чат, Сообщение, Клавиатура = «»)
    
    ТекстЗапроса = СтрШаблон(«bot%1/editMessageReplyMarkup?chat_id=%2&message_id=%3», ТокенБота, Формат(Чат, «ЧГ=»), Формат(Сообщение, «ЧГ=»));
    Если НЕ ПустаяСтрока(Клавиатура) Тогда
    ТекстЗапроса = ТекстЗапроса + «&reply_markup=» + Клавиатура;
    КонецЕсли;
    
    Запрос = Новый HTTPЗапрос(ТекстЗапроса);
    Соединение = Новый HTTPСоединение(«api.telegram.org», 443,,,,, Новый ЗащищенноеСоединениеOpenSSL());
    HTTPОтвет = Соединение.Получить(Запрос);
    ЧтениеJSON = Новый ЧтениеJSON;
    ЧтениеJSON.УстановитьСтроку(HTTPОтвет.ПолучитьТелоКакСтроку());
    Результат = ПрочитатьJSON(ЧтениеJSON);
    ЧтениеJSON.Закрыть();
    
    Если НЕ Результат.ok Тогда
    ТекстОписания = СтрШаблон(«%1
    |Чат: %2
    |Сообщение: %3», Результат.description, Чат, Сообщение);
    ЗаписьЖурналаРегистрации(«Бот», УровеньЖурналаРегистрации.Ошибка,,, ТекстОписания);
    КонецЕсли;
    
    КонецПроцедуры

    Показать

    Reply
  18. terrorion

    (18) Спасибо, получилось!

    Reply
  19. Euroset1

    Автор, как ты решил проблему с BOM кодом? Или веб сервис телеграма самостоятельно его вырезает перед первым boundary?

    Reply
  20. stako8

    Может кто сталкивался: отправляю клавиатуру «bot******/sendMessage?chat_id=******&text=Меню&reply_markup={«inline_keyboard»:[[{«text»:»1″,»callback_data»:»1″},{«text»:»2″,»callback_data»:»2″}]]}», она приходит пользователю, тот нажимает на неё, но через «getUpdates» не приходит ответ что он нажал. Куда копать?

    Reply
  21. user1061591

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

    Reply
  22. lishniy

    (21) Сообщения из inline_keyboard приходят чуть по другому. Добавьте в цикле

    Для Каждого mes из result Цикл
    Если message.Свойство(«text») Тогда

    условие

    ИначеЕсли mes.Свойство(«callback_query») Тогда
    Ответ = СтрокаМассива.callback_query.data;
    Reply
  23. stako8

    (23) У меня проблема не с поиском, а с тем, что ответ вообще не приходит, чтобы я не нажимал и не делал. Приходит только сообщение о том, что я вызывал меню. А вот апдейт, что я нажал кнопку не приходит.

    Reply
  24. lishniy

    (24) Странно. А клиент какой? Должно быть так.

    Reply
  25. stako8

    (25) Если Вы про клиент телеграмма — пробовал нажимать и на десктопе и на мобилке, результат на скрине в предыдущем моём сообщении.

    Reply
  26. lishniy

    (26)Конечно, это не совсем то, что вы делаете, попробуйте так сделать меню

    /sendMessage?chat_id=ЧЧЧ&text=Меню&reply_markup={«keyboard»:[[{«text»:»Пункт меню»}]],»resize_keyboard»:true}
    Reply
  27. stako8

    (27) Так это обычная кнопка, которая отправляется как текст

    Reply
  28. lishniy

    (28) Так и есть. Разница, что первая в чате под сообщением, а эта как отдельная клавиатура

    Reply
  29. stako8

    (29)Ещё раз повторюсь: возможно я не в том формате формирую строку и данные на сервер телеграмма приходят не совсем корректные (с виду строка с параметрами правильная), по-этому не могут быть возвращены через callback_query.

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *