Печать
Просмотров: 62208

Бродил тут по интернетам в поисках новых блогов о программирование на 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, в качестве параметров запроса можно передавать 

Если запрос выполнен удачно то в ответ система вернет 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 Кб