Бродил тут по интернетам в поисках новых блогов о программирование на delphi и натолкнулся  на блог о программирование на lazarus,  там была пара статей о том как начать делать бота для Telegram. Эта тема меня раньше как то не интересовала, но тут решил попробовать написать хотя бы маленького бота что бы разобраться с принципами работы. Побродив по интернету в поисках наработок по данному вопросу, я ничего интересного не нашел, в основном это были библиотеки для работы с telegram при помощи C#, Python, PHP для delphi нашел только один какой то пробный проект на github. Поэтому решил попробовать сделать все сам с нуля так сказать.

 

 Telegram - бесплатный кроссплатформенный мессенджер для смартфонов и других устройств, позволяющий обмениваться текстовыми сообщениями и медиафайлами различных форматов. Создан Павлом Дуровым не за долго до ухода из вконтакта. В феврале 2016 года один из создателей Telegram Павел Дуров заявил, что мессенджером пользуются уже более 100 миллионов человек, при этом сервис доставляет около 15 миллиардов сообщений ежедневно. 

 

Что бы начать создавать бота необходимо его зарегистрировать в  системе Telegram. 

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

Сначала вводится команда /newbot Затем система предложит ввести имя для бота, если имя систему устроит она предложит указать пользовательское имя для бота с обязательным окончанием имени на bot или _bot, если все продет успешно то система вам выдать уникальный идентификатор бота, который затем можно будет использовать для работы. Он примерно такого вида 12345678:AAHOjf*****ROYb03utz*********. Более подробно можно почитать тут

telephon screen

 

Общение с системой происходит посредством HTTPS запросов GET и POST, в качестве параметров запроса можно передавать 

  • URL query string
  • application/x-www-form-urlencoded
  • application/json (кроме загрузки файлов)
  • multipart/form-data (для загрузки файлов)

Если запрос выполнен удачно то в ответ система вернет json объект

{"ok":true,"result":{[]}}

Где поле ok будет равно true.

Для работы с запросами я решил  использовать набор бесплатных компонентов Synapse, а для парсинга JSON объектов использовать superobject. Поскольку работа с сервисом  осуществляется посредством протокола https, то также потребуются библиотеки OpenSSL,   собрать из исходников можно тут, я же просто вбил название библиотеки в поиск windows и нашел на компе несколько штук из которых выбрал наиболее последнюю версию. Выкладываю для скачивания. ссылка

Поскольку раньше мне не доводилось работать с synapse то пришлось обратиться к интернету, там нашлась подборка статей  по работе с данной библиотекой. 

После скачивания всего и вся я создал простой проект приложения в среде delphi. 

 В раздел uses добавил  httpsend, ssl_openssl, superobject  и начал экспериментировать.

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

var
http: THTTPSend;
begin
  http := THTTPSend.Create;
  Http.Sock.CreateWithSSL(TSSLOpenSSL);
  Http.Sock.SSLDoConnect;

Затем данные объект можно использовать для работы с запросами. 

 

В качестве первого запроса я решил получить информацию о боте.

Выполняется это посредством запроса.

https://api.telegram.org/bot<код вашего бота >/getMe

 В коде это выглядело так 

var
  s: string;
  jo: ISuperObject;
  list: TStringList;
begin
  s := 'https://api.telegram.org/bot' + bot_key + '/GetMe';
  if http.HTTPMethod('GET', s) then
  begin
    list := TStringList.Create;
    list.LoadFromStream(http.Document);
    jo := SO(list.Text);
    if jo.B['ok'] = true then
    begin
      Label1.Caption := jo.S['result.id'];
      Label2.Caption := jo.S['result.first_name'];
      Label3.Caption := jo.S['result.username'];
    end;

  end;
  list.Free;
end;

 В качестве результата сервис  возвращает JSON обьект, который распарсивается при помощи объекта ISuperObject. 

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

https://api.telegram.org/bot<код вашего бота >/getUpdates

В запрос передаются параметры 

 chat_id Идентификатор первого сообщения с которого необходимо получать данные, должен быть на единицу больше чем последнее полученное сообщение. 
 offsetlimit  Ограничитель на количество возвращаемых обновлений, по умолчанию равен 100
timeout  Таймаут в секундах для длительных запросов

 

 

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

 

var
  s: string;
  list: TStringList;
  jo: ISuperObject;
  tod: TSuperArray;
  i: Integer;
begin
  http.Headers.Clear;
  http.Document.Clear;
  s := 'https://api.telegram.org/bot' + bot_key + '/getUpdates?offset=' + last_id;
  if http.HTTPMethod('GET', s) then
  begin
    list := TStringList.Create;
    list.LoadFromStream(http.Document);
    jo := SO(list.Text);
    if jo.B['ok'] = True then
    begin

      tod := jo['result'].AsArray;
      for I := 0 to tod.Length - 1 do
      begin
        last_id := tod[i].S['update_id'];
        Memo1.Lines.Add('kod: ' + tod[i].S['update_id'] + '  ' + tod[i].S['message.from.first_name'] + '  ' + tod[i].S['message.from.last_name'] + '  ' + tod[i].S['message.text']);
        with ListView1.Items.Add do
        begin
          Caption := tod[i].S['message.from.id'];
          SubItems.Add(tod[i].S['message.from.first_name'] + ' ' + tod[i].S['message.from.last_name']);
        end;
      end;

    end;

  end;
  list.Free;
end;

 

 

Данный код выдергивает текст и json объекта складывает в memo, а так же собирает в listview идентификаторы пользователей. Затем  выделенному в listview пользователю можно отсылать сообщение.

Сообщение отсылается посредством такого запроса.

https://api.telegram.org/bot<код вашего бота >/sendMessage

В качестве параметров передаются

 chat_id Идентификатор чата то есть пользователя которому отправляется сообщение
 text  Тест сообщения
 parse_mod  если отправляет текст с html или  markdown оформлением
 disable_web_page_preview  Показывать содержимое отправленных ссылок
disable_notification Отсутствие индикации при получение сообщения, у пользователей ios  это будет сообщение без оповещение, у пользователей android это будет сообщение без звука.
reply_to_message_id Идентификатор сообщения на которое был произведен ответ
reply_markup Дополнительные  пользовательские функции, подробнее тут 

 Не обязательно перечислять все параметры в  запросе можно использовать лишь  chat_id и text 

Код получился такой

var
  s: string;
begin
  if ListView1.SelCount = 0 then
  begin
    ShowMessage('выберете получателя сообщения');
    Exit;
  end;
  http.Document.Clear;
  http.Headers.Clear;
  s := Format('https://api.telegram.org/bot%s/sendmessage?chat_id=%s&text=%s', [bot_key, listview1.Selected.Caption, urlencode(edit1.Text)]);
  if http.HTTPMethod('GET', s) then
  Memo1.Lines.LoadFromStream(http.Document);
  end;

 Поскольку запрос передавался методом get то пришлось обработать отправляемый текст функцией urlencode  стянутой с просторов интернета

function UrlEncode(const S: string): string;
var
  i: integer;
  u: ansistring;
  r: ansistring;
begin
  r := '';
  u := ansistring(UTF8Encode(S));
  for i := 1 to Length(u) do
  begin
    case u[i] of
      'A'..'Z', 'a'..'z', '0'..'9', '-', '_', '.':
        r := r + u[i];
    else
      r := r + '%' + ansistring(IntToHex(Ord(u[i]), 2));
    end;
  end;
  Result := string(r);
end;

send message

 

Затем я попробовал отправить фото на телефон, пришлось изрядно повозиться, поскольку раньше не доводилось работать с http запросами на низком уровне.

Сам запрос отправки фото такой.

https://api.telegram.org/bot<код вашего бота >/sendPhoto

В качестве параметров передаются

chat_id Идентификатор чата то есть пользователя которому отправляется сообщение
photo Само фото, либо его идентификатор на серверах telegram
caption Подпись к фото
disable_notification Отсутствие индикации при получение сообщения, у пользователей ios  это будет сообщение без оповещение, у пользователей android это будет сообщение без звука.
reply_to_message_id Идентификатор сообщения на которое был произведен ответ
reply_markup Дополнительные  пользовательские функции, подробнее тут

 Опять же в качестве передаваемых параметров можно использовать не все параметры. Пришлось изрядно повозится с кодировками  структурой http  post запроса.

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

var
  s: AnsiString;
  f: TFileStream;
begin
  if not od.Execute() then
    Exit;

  http.Document.Clear;
  http.Headers.Clear;
  //
  http.MimeType := 'multipart/form-data; boundary=' + bound;
  s := GetBoundary(true) + 'Content-Disposition: form-data; name="chat_id";' + sLineBreak + sLineBreak + ListView1.Selected.Caption + sLineBreak + sLineBreak;
  http.Document.Write(PAnsiChar(s)^, Length(s));
  s := GetBoundary(true) + 'Content-Disposition: form-data; name="photo"; filename=' + extractfilename(od.FileName) + sLineBreak + 'Content-Type: multipart/form-data' + sLineBreak + sLineBreak;
  http.Document.Write(PAnsiChar(s)^, Length(s));
  f := TFileStream.Create(od.FileName, fmOpenRead);
  f.Position := 0;
  http.Document.CopyFrom(f, f.Size);
  f.Free;
  s := sLineBreak + GetBoundary(true);
  http.Document.Write(PAnsiChar(s)^, Length(s));
  s := 'Content-Disposition: form-data; name="caption";' + sLineBreak + slinebreak + AnsiToUtf8('Тестовая фотка') + sLineBreak + getboundary(false);
  http.Document.Write(PAnsiChar(s)^, Length(s));
  s := Format('https://api.telegram.org/bot%s/sendphoto', [bot_key]);
  if http.HTTPMethod('POST', s) then
    Memo1.Lines.LoadFromStream(http.Document);

 Разделитель для передаваемых параметров запроса 

function GetBoundary(endf: Boolean): string;
begin
  if (endf) then
    Result := '--' + bound + sLineBreak
  else
    Result := '--' + bound + '--' + sLineBreak;

end;

 send photo

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

P.S.  Буквально в день написания статьи узнал что Павел Дуров раздает  миллион мертвых президентов  разработчикам самых интересных ботов  для Telegram. Более подробно можно почитать тут.

конкурс для разработчиков

 

Вложения:
Скачать этот файл (telegram2016.zip)Исходники[Исходник бота]7920 Кб
Комментарии   
0 #46 vaz 14.12.2021 10:46
Подскажите как можно добавить меню в бот ?
Цитировать
0 #45 Владимир 15.10.2020 18:35
var
PostData: TIdMultiPartFormDataStream;
Begin
PostData := TIdMultiPartFor mDataStream.Cre ate;

Id[censored].Re quest.CharSet:= 'utf8';
Id[censored].Re quest.ContentTy pe:= 'multipart/form -data';
PostData.AddFor mField('Chat_id ', Chat_id);
PostData.AddFil e('Photo', FileName, 'multipart/form -data');
PostData.AddFor mField('caption ', 'TestFiles');


request := Base[censored]+ Key+'/sendphoto ';
S := Id[censored].post(request,PostData);

===========Запрос выглядит так==========
----------101520142124711
Content-Disposition: form-data; name="Chat_id"
Content-Type: text/plain
Content-Transfer-Encoding: quoted-printable

847040207
----------101520142124711
Content-Disposition: form-data; name="Photo"; filename="1600074944844.jpg"
Content-Type: multipart/form-data
Content-Transfer-Encoding: binary

яШябЋExif

Ошибка 400 Не верный запрос. Что не так ?
Цитировать
0 #44 Pavel 29.09.2020 18:00
А что конкретно обновили? У меня тоже самое. И, вроде, все обновил - но так же тишина...

Цитирую Михаил:
Цитирую Sasha:
Цитирую Михаил:
Добрый день. Скачал исходники, подключил библиотеки, создал бота, получил token bota, который вписал в проект, после компиляции проекта при нажатии кнопки "Проверить" ничего не происходит. Подскажите, что я делаю не так. Спасибо.

Статья была написана 4 года назад, возможно что то поменялось в api

Разобрался уже. Спасибо. Нужно было обновить библиотеки.
Цитировать
0 #43 SUXROB 29.06.2020 18:03
Kruto srabotal :D :D :D :D :D
Цитировать
0 #42 Svema 28.04.2020 00:41
Скиньте кто нибудь рабочий вариант, если не трудно
Цитировать
0 #41 Михаил 13.03.2020 14:55
Цитирую Sasha:
Цитирую Михаил:
Добрый день. Скачал исходники, подключил библиотеки, создал бота, получил token bota, который вписал в проект, после компиляции проекта при нажатии кнопки "Проверить" ничего не происходит. Подскажите, что я делаю не так. Спасибо.

Статья была написана 4 года назад, возможно что то поменялось в api

Разобрался уже. Спасибо. Нужно было обновить библиотеки.
Цитировать
0 #40 Sasha 12.03.2020 13:38
Цитирую Михаил:
Добрый день. Скачал исходники, подключил библиотеки, создал бота, получил token bota, который вписал в проект, после компиляции проекта при нажатии кнопки "Проверить" ничего не происходит. Подскажите, что я делаю не так. Спасибо.

Статья была написана 4 года назад, возможно что то поменялось в api
Цитировать
0 #39 Михаил 25.12.2019 17:24
Добрый день. Скачал исходники, подключил библиотеки, создал бота, получил token bota, который вписал в проект, после компиляции проекта при нажатии кнопки "Проверить" ничего не происходит. Подскажите, что я делаю не так. Спасибо.
Цитировать
0 #38 test 14.11.2019 11:33
Цитирую erekepost:
Почему все избегают Indy?)) Не подскажете: Indy чем то не подходит?

Цитирую erekepost:
Почему все избегают Indy?)) Не подскажете: Indy чем то не подходит?

Тут все просто. indy хорош для начинающих, но когда начинаешь более глубоко использовать эти компоненты, то начинают вылезать ошибки.
Цитировать
0 #37 erekepost 02.06.2019 21:09
Почему все избегают Indy?)) Не подскажете: Indy чем то не подходит?
Цитировать
Добавить комментарий