image
Одна из особенностей мессенджера 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


gem tdlib-ruby


Автор


Ruby-разработчик Southbridge Владислав Яшин

Комментарии (2)


  1. Vitalik1705
    21.02.2018 00:51

    Скажите пожалуйста, есть ли возможность скомпилировать tdjson Си-компилятором? Я хочу использовать tdlib на старом девайсе, C++ компилятор которого лишь частично поддерживает С++11, не говоря уже о стандарте С++14. Вот думаю, реально ли хотя бы частично использовать ее с С-компилятором.


    1. RENESiS
      21.02.2018 11:27
      +1

      Боюсь, что нет. В требованиях указан C++14-совместимый компилятор. Но можно попробовать спросить в issues — может быть, есть обходные пути.