Написать Telegram клиент — легко


    Чем отличается Telegram от других популярных мессенджеров? Он — открытый!
    Другие мессенджеры тоже имеют API, но почему-то именно телеграм известен как наиболее открытый из самых популярных?


    Начнем с того, что у Telegram действительно полностью открытый клиентский
    код. К сожалению, мы не видим комиты каждый день прямо на GitHub, но у нас есть код под открытой лицензией. Архитектура Telegram подразумевает, что и Bot и API имеет практически такие же методы — https://core.telegram.org/methods.


    На самом деле, Telegram представляет не просто чат-мессенджер, а социальную платформу, доступ к которой открыт для разного рода приложений. Они могут предоставлять дополнительные фишки пользователям, взамен используя готовую сеть пользователей и сервера для доставки сообщений. Звучит настолько привлекательно, что нам захотелось попробовать написать своего "клиента" для Телеграм.


    Суть приложения


    В основном мы занимаемся картами и навигацией, поэтому мы сразу смотрели что-нибудь связанные с геолокацией. Мне очень понравилось, что в Telegram, раньше всех остальных приложений, появился удобный способ делится местоположением в реальном времени (https://telegram.org/blog/live-locations) и я достаточно часто этим пользуюсь: помочь сориентироваться другу, показать дорогу и самое главное ответить на главный вопрос "Когда ты будешь?". В принципе, этого хватает большинству людей, но как всегда есть сценарии, когда простых возможностей не хватает. Например, это может быть группа более 10 человек, с разными устройствами (некоторые устройства возможно не являются телефонами) и разными людьми. Этим людям было бы удобно обмениваться сообщениями в группе, а также видеть перемещения друг друга на карте.


    Во главу угла мы поставили задачу создать дополнительную ценность для Telegram, а не пытаться использовать его не по назначению. Мы не хотели, чтобы люди у которых нет специального клиента Телеграм, видели в чате месиво сообщений или что-то невразумительное. У людей с "улучшенным" клиентом, появляются же дополнительные возможности, например:


    1. Более тонкое управление временем при отправке локации в реальном времени в чат.
    2. Просмотр местоположения контактов на карте.
    3. Подключение к чату маячковых устройств, через внешний API (Bot).

    Как мы это делали


    К счастью, весь код, который мы пишем — Open-Source, поэтому я сразу могу дать ссылку на его реализацию — Реализация Bot и Реализация Telegram Client на Kotlin.


    Bot — основы

    По реализации Bot существует достаточно много документации и примеров, но все же хочется пройтись и рассказать про некоторые подводные камни. Для начала, мы писали серверную часть
    на Java и выбрали библиотеку org.telegram:telegrambots. Так как наш сервер — это обычный SpringBoot, то инициализация крайне простая:


       // Gradle implementation "org.telegram:telegrambots:3.6"
       TelegramBotsApi telegramBotsApi = new TelegramBotsApi();
       telegramBotsApi.registerBot(new TelegramLongPollingBot() {...});

    Основная особенность передачи location, что его надо часто обновлять, и боту необходимо редактировать уже отправленные сообщения. Если бы не было такой возможности, то Bot бы просто заспамил чат и это, конечно, был бы Epic Fail. Слава богу, Telegram предоставляет права боту редактировать сообщения на протяжении 24 часов (минимум, возможно и дольше).


    Передать сообщение можно многими способами. Есть тип Plain Text, Venue, Location, Game, Contact, Invoice и т.д. Казалось, что для нашей задачи отлично подходит Location, но вскрылась неприятная особенность. Location можно передать только с одного устройства для одного аккаунта или бота одновременно! Представьте у вас 2 телефона и с двух телефонов вы отправили свой Location в один чат. Так вот, на сервере случится ошибка и первый Location Sharing просто остановится. Казалось бы, это явно неральный случай, но представьте, у вас много китайских маячков, которые умеют отправлять Location по заданному URL, но они не умеют отправлять прямо в Telegram. Вы пишите Bot, который забирает с сервера и пушит в телеграм. Вот тут и вылазит, то что Bot не сможет отправить больше одного сообщения маячка с типом Location. Получается, это отлично подходит для единоразовой отправки, но не подходит для Live Location.


    Решение простое — отправлять текстовые сообщения, а клиент будет парсить текст и показывать локации на карте. К сожалению в стандартном клиенте Telegram будут видны только текстовые сообщения, но там можно вставить ссылку, чтобы открыть карту.


    Bot — Подводные камни

    К сожалению, Bot пришлось переписывать аж 2.5 раза. Основная проблема — неправильный дизайн коммуникации.


    1. Почему-то вначале казалось хорошей идеей, если бот будет полноценным участником чата и отправлять сообщения. Но, это плохо и с точки зрения Privacy переписки и с точки зрения взаимодействия с ботом. Правильное решение, использовать Inline bots. Таким образом, гарантируется, что бот не видит ничего кроме своего Location и его можно использовать в любом чате. По-человечески говоря, некультурно тащить своего бота в какой-то общий чат, а нужно пообщаться с ботом один на один и настроить его, а дальше он сможет отправлять нужные сообщения в любой выбранный чат.
    2. В Telegram Message API есть исторически 2 типа взаимодействия: кнопки под текстом ( (inline buttons)[https://core.telegram.org/bots/2-0-intro#switch-to-inline-buttons] ) и ответы боту напрямую текстом. В общем, ответы с ботом безнадежно устарели. Кнопки немного сложнее с точки зрения реализации, но это полностью окупается удобством использования и именно их надо использовать для всего нетекстового ввода.
    3. В качестве примера бота можно посмотреть популярный @vote_bot или наш @osmand_bot.

    Telegram Client

    Найти примеры готовых telegram client, кроме основного, нам не удалось, но достаточно простая структура tdlib помогла нам создать базовый клиент буквально за пару дней.


    Настройка Gradle:
    task downloadTdLibzip {
        doLast {
            ant.get(src: 'https://core.telegram.org/tdlib/tdlib.zip', dest: 'tdlib.zip', skipexisting: 'true')
            ant.unzip(src: 'tdlib.zip', dest: 'tdlib/')
        }
    }
    
    task copyNativeLibs(type: Copy) {
        dependsOn downloadTdLibzip
        from "tdlib/libtd/src/main/libs"
        into "libs"
    }
    
    task copyJavaSources(type: Copy) {
        dependsOn downloadTdLibzip
        from "tdlib/libtd/src/main/java/org/drinkless/td"
        into "src/org/drinkless/td"
    }
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
    }

    Практически все внутренности Телеграмма написаны на С++ и с точки зрения Android виден только класс API на 1.5 Мб прокси методов TdApi.java. Путем сопоставления документации ботов и названия методов, можно достаточно просто сориентироваться куда двигаться.


    Инициализация клиента с global handler:
    fun init(): Boolean {
        return if (libraryLoaded) {
            // create client
            client = Client.create(UpdatesHandler(), null, null)
            true
        } else {
            false
        }
    }

    Запрос фото пользователя:
    private fun requestUserPhoto(user: TdApi.User) {
        val remotePhoto = user.profilePhoto?.small?.remote
        if (remotePhoto != null && remotePhoto.id.isNotEmpty()) {
            downloadUserFilesMap[remotePhoto.id] = user
            client!!.send(TdApi.GetRemoteFile(remotePhoto.id, null)) { obj ->
                when (obj.constructor) {
                    TdApi.Error.CONSTRUCTOR -> {
                        val error = obj as TdApi.Error
                        val code = error.code
                        if (code != IGNORED_ERROR_CODE) {
                            listener?.onTelegramError(code, error.message)
                        }
                    }
                    TdApi.File.CONSTRUCTOR -> {
                        val file = obj as TdApi.File
                        client!!.send(TdApi.DownloadFile(file.id, 10), defaultHandler)
                    }
                    else -> listener?.onTelegramError(-1, "Receive wrong response from TDLib: $obj")
                }
            }
        }
    }

    Telegram Client — подводные камни

    1. Регистрация/Login и Logout. При регистрации необходимо учесть разные сценарии: когда код доступа присылается SMS или в другой телеграм клиент, двухфакторную авторизацию и т.п. Самая большая сложность — это тестирование. Любая авторизация более 3-х раз вела к блокировке аккаунта на 24 часа, поэтому тестировать Logout было особенно весело. Несмотря на то, что регистрация нужна всего лишь один раз, наверное это самая сложная часть интеграции.
    2. Определить как и в каком порядке вычитывать сообщения. Любой клиент имеет доступ ко всем сообщениям во всех чатах, но вычитывать их надо последовательно. В нашем случае 99% сообщений нужно отбрасывать. Сначала мы почему-то сделали чтение всех сообщений за последние 3 дня при логине, но в дальнейшем это только вызвало проблемы и при рестарте у нас пропадали сообщения. Поэтому сейчас мы читаем только новые сообщения, а для тех сообщений, что нам нужны сохраняем id во внутренней БД.

    Что получилось


    Наверное, зная все подводные камни можно было бы все сделать в разы быстрее, но получилось где-то 1-2 месяца на трех человек. Финальное приложение можно найти в Google Play.



    Главный вопрос в этой истории, насколько правильно это взаимодействие с точки зрения Телеграма и понравятся ли пользователям такого рода интеграции. В любом случае, сама идея нишевая и отдельных клиентов она уже нашла.


    Буду рад ответить на ваши вопросы.

    Поделиться публикацией

    Похожие публикации

    Комментарии 11
      0
      у Telegram действительно полностью открытый клиентский код
      Где там реализация секретных чатов?
        +1

        в tdlib реализована поддержка секретных чатов.

        0
        Отличное приложение у вас получилось: ) Не думали развиваться в сторону игр основанных на геолокации и использующих телеграмм?
          0
          Мы достаточно маленькие + еще и open-source, нет мы думаем развиваться в сторону API, чтобы другие могли написать. Telegram клиент полностью написан на этом API через AIDL и наши сервисы. Грубо говоря, Telegram-client независимое Android приложение, но его интеграция очень тесная и оно запускается, даже когда просто запускают карты.

          Надеюсь, идея независимых приложений получит продолжение.
          0

          кстати, tdlib поддерживает в том числе и ботов — можно унифицировать.

            0
            Чем отличается Telegram от других популярных мессенджеров?
            it does not matter how many other messaging apps are out there if all of them s*ck.
              0
              Псевдобезопасностью
              0
              Оригинальное приложение) Интересует такой момент: можно ли как-то в Telegram на inline кнопку поставить действие, чтобы пользователь делился своим местоположением?
                0
                Inline боты могут запрашивать местоположение, это когда набираешь текст, но при нажатии кнопок в чате никаких координат боту не передается и тем более ничего не запускается.
                0
                По поводу тестирования Регистрация/Login и Logout:
                если запустить бета-приложение Telegram под Android, там есть возможность «Switch Backend» в настройках, что позволяет залогиниться в тестовом окружении телеграм.

                Предполагаю(!), не утверждаю, что там либо нет таких жестких ограничений на логины итп либо они существенно мягче.

                Надеюсь, это так и в будущем кому-то это поможет ;)
                  0
                  В гугл-картах можно делиться своим местонахождением.
                  Но да, это несколько неудобно и больше подходит тем кто настроил один раз и смотрит по мере необходимости.
                  Настроил на телефонах всей семьи и в любой момент можно зайти и глянуть.
                  Если интернет конечно есть на отслеживаемом телефоне.
                  Защитникам приватности — все в курсе и все с их согласия/желания.
                  Детям нравится знать где сейчас родители и когда они будут дома )

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                  Самое читаемое