Одна из особенностей мессенджера Telegram — широкие возможности API (Bot API и Telegram API). Команда Telegram пошла ещё дальше и выпустила библиотеку TDLib (Telegram Database Library), позволяющую разрабатывать альтернативные клиенты Telegram и не задумываться о низкоуровневых деталях реализации (работа с сетью, шифрование и локальное хранение данных).?
TDLib работает на Android, iOS, Windows, macOS, Linux, Windows Phone, WebAssembly, watchOS, tvOS, Tizen, Cygwin и других *nix системах, а так же интегрируется с любым языком программирования, поддерживающим выполнение C-функций.
В этой статье мы рассмотрим использование TDLib в Ruby и создание gem'а для взаимодействия с JSON-интерфейсом библиотеки.
Подключение libtdjson
Для начала нам понадобится скомпилированная TDLib. Инструкцию по сборке можно прочесть на официальном сайте. Из скомпилированных бинарников нам нужен только libtdjson.[so|dylib|dll].
Чтобы подключить функции библиотеки в Ruby можно использовать модуль Fiddle из стандартной библиотеки. Fiddle::Importer предоставляет удобный DSL для импорта функций из динамических библиотек:
module Dl
extend Fiddle::Importer
dlload('libtdjson.so')
extern 'void* td_json_client_create()'
extern 'void* td_json_client_send(void*, char*)'
extern 'char* td_json_client_receive(void*, double)'
extern 'char* td_json_client_execute(void*, char*)'
extern 'void td_set_log_verbosity_level(int)'
extern 'void td_json_client_destroy(void*)'
extern 'void td_set_log_file_path(char*)'
end
Теперь мы можем вызывать функции TDLib:
client = Dl.td_json_client_create
Dl.td_json_client_send(client, '{"@type": "getAuthorizationState"}')
Создание клиента
TDLib — полностью асинхронная библиотека (лишь немногие функции можно вызывать синхронно с помощью td_json_client_execute
), поэтому работать с ней нужно соответствующим образом:
Dl.td_json_client_send(client, '{"@type": "getAuthorizationState"}')
timeout = 10
loop do
update = Dl.td_json_client_receive(client, timeout)
next if update.null?
update = JSON.parse(update.to_s)
if update['@type'] = 'updateAuthorizationState'
p update
break
end
end
Это рабочий код, однако не самый удобный. Лучше разработать обертку для взаимодействия с библиотекой: с обработчиками событий, callback'ами, удобным конфигурированием и возможностью не писать boilerplate-код с первоначальной авторизацией.
Далее рассмотрим основную функциональность gem'а tdlib-ruby (ссылка в конце статьи).
Инициализация клиента
Процедуры отправки параметров библиотеки и проверки ключа шифрования скрыты внутри. Для начала работы достаточно создать экземпляр клиента:
client = TD::Client.new
client.on_ready do |client|
# some useful stuff
end
Отправка "сообщений"
Сообщения отправляются в tdlib асинхронно.
client.broadcast('@type' => 'getAuthorizationState')
Есть возможность повесить callback-обработчик.
client.broadcast('@type' => 'getMe') do |update|
p update
end
Подписка на обновления определённого типа
client.on('updateAuthorizationState') do |update|
p update
end
При получении от TDLib обновления с типом `updateAuthorizationState' всегда будет выполняться обработчик, переданный как блок.
Синхронная отправка сообщений
Некоторые методы (их немного, и я пока что таковых не встретил) могут возвращать ответ синхронно. Для этих случаев предусмотрен метод execute
.
client.execute('@type' => 'someType')
Работа с асинхронными сообщениями/обновлениями в синхронном стиле
Надо просто отправить запрос и получить результат? Асинхронная природа TDLib этого не позволяет, однако нужный механизм реализован в gem'е.
authorization_state = client.broadcast_and_receive('@type' => 'getAuthorizationState')
Напоследок приведу пример консольного скрипта авторизации:
require 'tdlib-ruby'
TD.configure do |config|
config.lib_path = 'path_to_dir_containing_tdlibjson'
config.client.api_id = your_api_id
config.client.api_hash = 'your_api_hash'
end
TD::Api.set_log_verbosity_level(1)
client = TD::Client.new
begin
state = nil
client.on('updateAuthorizationState') do |update|
next unless update.dig('authorization_state', '@type') == 'authorizationStateWaitPhoneNumber'
state = :wait_phone
end
client.on('updateAuthorizationState') do |update|
next unless update.dig('authorization_state', '@type') == 'authorizationStateWaitCode'
state = :wait_code
end
client.on('updateAuthorizationState') do |update|
next unless update.dig('authorization_state', '@type') == 'authorizationStateReady'
state = :ready
end
loop do
case state
when :wait_phone
p 'Please, enter your phone number:'
phone = STDIN.gets.strip
params = {
'@type' => 'setAuthenticationPhoneNumber',
'phone_number' => phone
}
client.broadcast_and_receive(params)
when :wait_code
p 'Please, enter code from SMS:'
code = STDIN.gets.strip
params = {
'@type' => 'checkAuthenticationCode',
'code' => code
}
client.broadcast_and_receive(params)
when :ready
@me = client.broadcast_and_receive('@type' => 'getMe')
break
end
end
ensure
client.close
end
p @me
Полезные ссылки
TDLib на Github
Документация TDLib
Инструкция по сборке
Telegram-плагины для Redmine от Southbridge
Автор
Ruby-разработчик Southbridge Владислав Яшин
Vitalik1705
Скажите пожалуйста, есть ли возможность скомпилировать tdjson Си-компилятором? Я хочу использовать tdlib на старом девайсе, C++ компилятор которого лишь частично поддерживает С++11, не говоря уже о стандарте С++14. Вот думаю, реально ли хотя бы частично использовать ее с С-компилятором.
RENESiS
Боюсь, что нет. В требованиях указан C++14-совместимый компилятор. Но можно попробовать спросить в issues — может быть, есть обходные пути.