Бродил тут по интернетам в поисках новых блогов о программирование на delphi и натолкнулся на блог о программирование на lazarus, там была пара статей о том как начать делать бота для Telegram. Эта тема меня раньше как то не интересовала, но тут решил попробовать написать хотя бы маленького бота что бы разобраться с принципами работы. Побродив по интернету в поисках наработок по данному вопросу, я ничего интересного не нашел, в основном это были библиотеки для работы с telegram при помощи C#, Python, PHP для delphi нашел только один какой то пробный проект на github. Поэтому решил попробовать сделать все сам с нуля так сказать.
Telegram - бесплатный кроссплатформенный мессенджер для смартфонов и других устройств, позволяющий обмениваться текстовыми сообщениями и медиафайлами различных форматов. Создан Павлом Дуровым не за долго до ухода из вконтакта. В феврале 2016 года один из создателей Telegram Павел Дуров заявил, что мессенджером пользуются уже более 100 миллионов человек, при этом сервис доставляет около 15 миллиардов сообщений ежедневно.
Что бы начать создавать бота необходимо его зарегистрировать в системе Telegram.
Для этого необходимо установить на телефон само приложение (либо воспользоваться веб версией клиента ) и добавить в контакты бота BotFather через чат с ним можно управлять своими ботами.
Сначала вводится команда /newbot Затем система предложит ввести имя для бота, если имя систему устроит она предложит указать пользовательское имя для бота с обязательным окончанием имени на bot или _bot, если все продет успешно то система вам выдать уникальный идентификатор бота, который затем можно будет использовать для работы. Он примерно такого вида 12345678:AAHOjf*****ROYb03utz*********. Более подробно можно почитать тут.
Общение с системой происходит посредством 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;
Затем я попробовал отправить фото на телефон, пришлось изрядно повозиться, поскольку раньше не доводилось работать с 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;
Отправка всего остального происходит аналогично вышеуказанному способу. Дальше я особо не стал заморачиваться с ботом, поскольку программу минимум я выполнил. А остальное будет дополняться по мере потребностей.
P.S. Буквально в день написания статьи узнал что Павел Дуров раздает миллион мертвых президентов разработчикам самых интересных ботов для Telegram. Более подробно можно почитать тут.
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 Не верный запрос. Что не так ?
Цитирую Михаил:
Разобрался уже. Спасибо. Нужно было обновить библиотеки.
Статья была написана 4 года назад, возможно что то поменялось в api
Цитирую erekepost:
Тут все просто. indy хорош для начинающих, но когда начинаешь более глубоко использовать эти компоненты, то начинают вылезать ошибки.
RSS лента комментариев этой записи