Пишем Бота Для Мессенджера Tox

На фоне всеобщего увлечения созданием ботов для Telegram хотелось бы рассказать об API не очень широко известного мессенджера Tox и показать на примере простого эхо-бота, как можно так же легко и быстро создать твой собственный.



Пишем бота для мессенджера Tox

Введение Core Tox ( toxcore на github ) состоит из трех частей:

  • ToxCore - собственно само ядро, позволяющее управлять контактами, сообщениями, файлами, аватарами, статусами и групповыми чатами.

  • ТоксАВ — подсистема голосового и видеовызова.

  • ТоксDNS — подсистема получения ToxID с человекочитаемого адреса (примерно эквивалентного электронной почте или JabberID) через DNS-запрос.

Контактная единица – это ToxID (он же «адрес» в терминах API) представляет собой 76-значную шестнадцатеричную строковую кодировку:
  • Открытый ключ (32 байта или 64 символа).

  • Защита от спама «носпам» (4 байта или 8 символов) — это случайный набор данных, изменение которого позволяет продолжать поддерживать ранее авторизованные контакты, но игнорировать запросы от новых.

  • Контрольная сумма (2 байта или 4 символа) — операция XOR над открытым ключом и значением «nospam», используемая для быстрой проверки правильности ToxID.
Общий цикл работы с Tox API последовательно можно представить следующим образом:
  1. Инициализация ядра ( tox_new ) - здесь можно настроить протоколы (IPv4/IPv6, UDP/TCP), настройки прокси (HTTP/SOCKS5), диапазоны используемых портов и (если доступно) загрузить ранее сохраненное состояние со списком контактов.

  2. Установка функций обратного вызова для обработки событий ( tox_callback_* ) — обработчики событий вызываются из основного цикла (4) и обычно содержат основную логику приложения.

  3. Подключение к одному или нескольким узлам DHT ( tox_bootstrap ).

  4. Основной рабочий цикл ( tox_iterate ) и обработка событий.

  5. Пауза на некоторое время tox_iteration_interval и вернитесь к предыдущему шагу.

основной способ получить знания о работе Tox API — это чтение исходного кода (написанного на C), для упрощения дальнейшего изложения я буду использовать обертку для языка Python ( Pytoxcore на GitHub ).

Для тех, кто не хочет самостоятельно собирать библиотеку из исходников, также есть ссылки на готовые бинарные пакеты для распространенных дистрибутивов.

При использовании оболочки Python вы можете получить справку по библиотеке следующим образом:

  
  
  
  
  
  
  
  
  
   

$ python >>> from pytoxcore import ToxCore >>> help(ToxCore) class ToxCore(object) | ToxCore object .

| tox_add_tcp_relay(.

) | tox_add_tcp_relay(address, port, public_key) | Adds additional host:port pair as TCP relay. | This function can be used to initiate TCP connections to different ports on the same bootstrap node, or to add TCP relays without using them as bootstrap nodes. | | tox_bootstrap(.

) | tox_bootstrap(address, port, public_key) | Sends a "get nodes" request to the given bootstrap node with IP, port, and public key to setup connections. | This function will attempt to connect to the node using UDP. You must use this function even if Tox_Options.udp_enabled was set to false. .



Ниже мы рассмотрим каждый этап работы с API чуть подробнее.

Инициализация ядра Для инициализации ядра в качестве параметра используется структура Tox_Options .

В Python это может быть словарь с одноименными полями.

Значения по умолчанию можно получить, вызвав метод tox_options_default :

$ python >>> from pytoxcore import ToxCore >>> ToxCore.tox_options_default() {'start_port': 0L, 'proxy_host': None, 'tcp_port': 0L, 'end_port': 0L, 'udp_enabled': True, 'savedata_data': None, 'proxy_port': 0L, 'ipv6_enabled': True, 'proxy_type': 0L}

Здесь:

  • ipv6_enabled — Верно или неверно в зависимости от того, являетесь ли вы счастливым обладателем IPv6.
  • udp_enabled — за исключением случаев работы через прокси, рекомендуется установить значение True, поскольку UDP — родной протокол для Tox.
  • тип_прокси — тип прокси, может принимать значения:
    • TOX_PROXY_TYPE_NONE - не используйте прокси.

    • TOX_PROXY_TYPE_HTTP - HTTP-прокси.

    • TOX_PROXY_TYPE_SOCKS5 — SOCKS5-прокси (например, Tor).

  • proxy_host — хост или IP-адрес прокси-сервера.

  • порт прокси — порт прокси-сервера (игнорируется для TOX_PROXY_TYPE_NONE).

  • start_port — стартовый порт из диапазона разрешенных портов.

  • конечный_порт — последний порт из диапазона разрешенных портов.

    Если начальный и конечный порты равны 0, то используется диапазон [33445, 33545].

    Если только один из портов равен нулю, то используется единственный ненулевой порт. В случае start_port > end_port они будут заменены.

  • tcp_port — порт для поднятия TCP сервера (реле), при значении 0 сервер будет отключен.

    Ретрансляция TCP позволяет другим пользователям использовать ваш экземпляр в качестве промежуточного узла (концепция суперузла).

  • savedata_data — данные для загрузки или Нет, если они отсутствуют.
В подавляющем большинстве случаев все параметры, кроме savedata_data вы можете оставить его по умолчанию:

#!/usr/bin/env python # -*- coding: utf-8 -*- import os from pytoxcore import ToxCore class EchoBot(ToxCore): def __init__(self): tox_options = ToxCore.tox_options_default() if os.path.isfile("tox.save"): with open("tox.save", "rb") as f: tox_options["savedata_data"] = f.read() super(EchoBot, self).

__init__(tox_options)

При первом запуске ядро сгенерирует публичный и приватный ключи, которые при необходимости можно сохранить:

class EchoBot(ToxCore): .

def save_data(self): with open("tox.save", "wb") as f: f.write(self.tox_get_savedata())

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

Текущие значения адреса, который будет передан пользователям, открытого и закрытого ключей и значения «nospam» можно получить, вызвав:

  • tox_self_get_address — текущий адрес (ToxID).

  • tox_self_get_public_key - открытый ключ.

  • tox_self_get_secret_key - секретный ключ.

  • tox_self_get_nospam / tox_self_set_nospam — получение и установка значения «nospam».



$ python >>> from pytoxcore import ToxCore >>> core = ToxCore(ToxCore.tox_options_default()) >>> core.tox_self_get_address() '366EA3B25BA31E3ADC4C476098A8686E4EAE87B04E4E4A3A3A0B865CBB9725704189FEDAEB26' >>> core.tox_self_get_public_key() '366EA3B25BA31E3ADC4C476098A8686E4EAE87B04E4E4A3A3A0B865CBB972570' >>> core.tox_self_get_secret_key() '86003764B4C99395E164024A17DCD0ECB80363C5976FF43ECE11637FA0B683F9' >>> core.tox_self_get_nospam() '4189FEDA'

После инициализации ядра боту можно в любой момент задать никнейм и подпись с помощью звонков.

tox_self_set_name И tox_self_set_status_message .

Длина имени не должна превышать значение TOX_MAX_NAME_LENGTH , длина подписи не должна превышать значения TOX_MAX_STATUS_MESSAGE_LENGTH (размеры в байтах).

О настройке аватара речь пойдет ниже, потому что… технически это отправка файла контакту.

Установка функций обратного вызова В оболочке Python подключение к поддерживаемым функциям обратного вызова осуществляется автоматически.

Сами обработчики могут быть методами-потомками.

ToxCore и иметь суффикс *_cb :

class EchoBot(ToxCore): .

def tox_self_connection_status_cb(self, connection_status): if connection_status == ToxCore.TOX_CONNECTION_NONE: print("Disconnected from DHT") elif connection_status == ToxCore.TOX_CONNECTION_TCP: print("Connected to DHT via TCP") elif connection_status == ToxCore.TOX_CONNECTION_UDP: print("Connected to DHT via UDP") else: raise NotImplementedError("Unknown connection_status: {0}".

format(connection_status))

Конкретные обработчики и их аргументы будут рассмотрены ниже.

Подключение к узлу DHT Узел DHT определяется IP-адресом, номером порта и открытым ключом.

Исходный список узлов DHT может быть перенести это на вики проекта .

В соответствии с Рекомендации для Tox-клиентов , клиент должен пытаться подключиться как минимум к четырем случайным узлам каждые 5 секунд, пока ядро не сообщит об успешном соединении (см.

tox_self_connection_status_cb ).

При загрузке из файла состояния клиент не должен пытаться подключиться в течение 10 секунд после первого вызова.

tox_iterate и, в случае отсутствия соединения, повторите описанную выше стратегию агрессивного подключения.

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

Вы можете попытаться уменьшить необходимое количество узлов DHT для подключения, подняв собственный локальный узел DHT. Дополнительным преимуществом наличия локального узла, помимо постоянной связи с ним, является «усиление» самой сети Tox. Для поднятия локальной ноды потребуется установить и настроить демон tox-bootstrapd .

Его можно скомпилировать вместе с библиотекой toxcore, а также получить в бинарном виде с сайта репозиторий разработчика .

Конфигурация демона указана в файле /etc/tox-bootstrapd.conf И хорошо документированы .

Дополнительную информацию о запуске демона см.

в соответствующем разделе.

ПРОЧТИ МЕНЯ и для дистрибутивов deb проект tox.pkg установка пакета tox-bootstrapd самодостаточный.

Открытый ключ локального узла DHT можно найти в системном журнале после запуска демона.

Таким образом, упрощенный вариант подключения к узлу DHT и рабочий цикл можно представить как:

class EchoBot(ToxCore): .

def run(self): checked = False self.tox_bootstrap("127.0.0.1", 33445, "366EA.72570") while True: status = self.tox_self_get_connection_status() if not checked and status != ToxCore.TOX_CONNECTION_NONE: checked = True if checked and status == ToxCore.TOX_CONNECTION_NONE: self.tox_bootstrap("127.0.0.1", 33445, "366EA.72570") checked = False self.tox_iterate() interval = self.tox_iteration_interval() time.sleep(interval / 1000.0)

В некоторых случаях клиент может работать вообще без звонка tox_bootstrap — для этого необходимо, чтобы в пределах одного широковещательного домена сети был запущен еще один клиент или узел DHT. Эта функция дает возможность общаться внутри локальной сети без необходимости выхода в Интернет и общаться с внешним миром, если хотя бы один клиент имеет доступ к сети и является ретранслятором.

Обработка событий Как писалось ранее, для подключения обработчика событий в Python достаточно добавить в класс-потомок нужный метод с необходимыми аргументами, а в качестве примера был приведен обработчик состояния подключения tox_self_connection_status_cb , который вызывается, когда бот подключается и отключается от сети DHT.

Работа с контактами

Чтобы бот мог взаимодействовать с другими участниками сети, их необходимо добавить в список контактов бота.

В терминах API контакт называется «друг» и обозначается целым числом (« номер_друга "), уникальный на протяжении всего существования экземпляра ToxCore. Когда другой клиент делает запрос на добавление бота в список контактов, на стороне бота вызывается обработчик.

tox_friend_request_cb (открытый_ключ, сообщение) , Где:

  • открытый_ключ — открытый ключ друга.

  • сообщение - сообщение от друга типа «Привет, это Эббот! Добавишь меня в друзья? .

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

Все дальнейшие обработчики событий будут использовать номер_друга в качестве идентификатора друга, который можно получить из открытого ключа, позвонив tox_friend_by_public_key .

После добавления контакта в друзья на стороне бота могут произойти следующие события:

  • tox_friend_connection_status_cb (номер_друга, статус_соединения) - изменить состояние соединения друга, где состояние_соединения может принимать значения:
    • TOX_CONNECTION_NONE - друг офлайн.

    • TOX_CONNECTION_TCP — друг онлайн и подключен по TCP.
    • TOX_CONNECTION_UDP — друг онлайн и подключен по UDP.
  • tox_friend_name_cb(номер_друга, имя) — изменение ника друга.

  • tox_friend_status_message_cb (номер_друга, сообщение) — изменение подписи.

  • tox_friend_status_cb (номер_друга, статус) — изменение состояния, при котором положение дел может принимать значения:
    • TOX_USER_STATUS_NONE - доступно онлайн").

    • TOX_USER_STATUS_AWAY - ушел.

    • TOX_USER_STATUS_BUSY - занятый.

  • tox_friend_message_cb (номер_друга, сообщение) - сообщение от друга.

  • tox_friend_read_receipt_cb (номер_друга, идентификатор_сообщения) — квитанция о получении другого сообщения, отправленного вызовом tox_friend_send_message (см.

    ниже).

В случае с эхо-ботом при получении сообщения от друга вам просто нужно отправить его обратно:

class EchoBot(ToxCore): .

def tox_friend_request_cb(self, public_key, message): self.tox_friend_add_norequest(public_key) def tox_friend_message_cb(self, friend_number, message): message_id = self.tox_friend_send_message(friend_number, ToxCore.TOX_MESSAGE_TYPE_NORMAL, message)

Отправка сообщения другу осуществляется вызовом метода tox_friend_send_message который возвращает идентификатор сообщения message_id — монотонно возрастающее число, уникальное для каждого друга.

Метод принимает в качестве параметров идентификатор друга, тип сообщения и текст сообщения.

На текст сообщения распространяются следующие ограничения:

  • Сообщение не может быть пустым.

  • Сообщение не может быть больше TOX_MAX_MESSAGE_LENGTH (байты), длинные сообщения необходимо разбивать на части.

Если обработка сообщения от друга занимает некоторое время, поведение бота можно разнообразить случайной передачей событий «набора сообщения» ( tox_self_set_typing ).

По стоимости номер_друга В любой момент работы вы можете получить следующую информацию о своем друге:

  • tox_friend_get_connection_status — текущий статус друга в сети (последнее значение из tox_friend_connection_status_cb ).

  • tox_friend_get_name — текущий никнейм друга (последнее значение из tox_friend_name_cb ).

  • tox_friend_get_status_message — текущая подпись друга (последнее значение tox_friend_status_message_cb ).

  • tox_friend_get_status — текущий статус друга (последнее значение из tox_friend_status_cb ).

  • tox_friend_get_last_online — дата последнего появления в сети (unixtime).

Дополнительные операции с друзьями:
  • tox_self_get_friend_list_size — получение количества друзей.

  • tox_self_get_friend_list — получение списка номер_друга друзья.

  • tox_friend_delete — удаление друга из списка контактов.

Здесь все кажется простым и интуитивно понятным.

Дальше будет немного сложнее.



Работа с файлами



Получение файлов

Когда один из ваших друзей отправляет файл боту, происходит событие tox_file_recv_cb (номер_друга, номер_файла, вид, размер_файла, имя файла) , Где:
  • номер_друга — номер друга (см.

    «Работа с контактами»).

  • номер дела — номер файла, уникальный номер в текущем списке полученных и переданных файлов от этого друга.

  • добрый - тип файла:
    • TOX_FILE_KIND_DATA — передаваемый файл является простым файлом.

    • TOX_FILE_KIND_AVATAR — передаваемый файл является аватаркой друга.

  • размер файла - размер файла.

    Для TOX_FILE_KIND_AVATAR Размер файла 0 означает, что у друга не установлен аватар.

    Размер файла, равный UINT64_MAX, указывает на неизвестный размер файла (потоковая передача).

  • имя файла - имя файла.

Здесь следует обратить особое внимание на параметр имя файла .

Несмотря на то, что спецификация требует, чтобы все данные передавались в UTF-8 и имя файла не должно содержать частей пути, в реальной жизни может поступить что угодно, включая нечитаемые двоичные данные, содержащие символы новой строки и нули.

При возникновении этого события следующим действием бота должен быть вызов метода управления.

tox_file_control(номер_друга, номер_файла, контроль) , Где:

  • номер_друга - номер друга.

  • номер дела - номер дела.

  • контроль - команда управления файлами:
    • TOX_FILE_CONTROL_CANCEL — отменить получение файла.

    • TOX_FILE_CONTROL_PAUSE — приостановить передачу файлов (поддерживается не всеми клиентами).

    • TOX_FILE_CONTROL_RESUME — продолжить передачу файла.

Эхо-боту не нужно получать файлы, поэтому он всегда может отменить операцию:

class EchoBot(ToxCore): .

def tox_file_recv_cb(self, friend_number, file_number, kind, file_size, filename): self.tox_file_control(friend_number, file_number, ToxCore.TOX_FILE_CONTROL_CANCEL)

В случае передачи управления через TOX_FILE_CONTROL_RESUME , событие начинает срабатывать tox_file_recv_chunk_cb (номер_друга, номер_файла, позиция, данные) , Где:

  • номер_друга - номер друга.

  • номер дела - номер дела.

  • позиция — текущая позиция в файле.

  • данные — фрагмент данных или «Нет» для окончания передачи.

Здесь следует обратить внимание на то, что позиция не обязательно монотонно увеличиваться — в общем, куски могут приходить в любой последовательности и любой длины.



Передача файлов

Для запуска процедуры передачи файлов необходимо вызвать метод tox_file_send (номер_друга, вид, размер_файла, идентификатор_файла, имя файла) , Где:
  • номер_друга - номер друга.

  • добрый - значение TOX_FILE_KIND_DATA или TOX_FILE_KIND_AVATAR .

  • размер файла — размер файла (специальные значения 0 и UINT64_MAX рассмотрены выше).

  • file_id - длина уникального идентификатора файла TOX_FILE_ID_LENGTH , что позволяет продолжить передачу после перезапуска ядра или None для автоматической генерации.

  • имя файла - имя файла.

Специальный параметр здесь file_id .

Если он сгенерирован автоматически, его позже можно получить, вызвав tox_file_get_file_id , однако при передаче аватара рекомендуется устанавливать его значение в результат вызова tox_hash из данных файла аватара, что позволяет принимающей стороне отменить передачу ранее загруженных аватаров, экономя трафик.

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

Отключение друга от сети останавливает передачу файлов.

После звонка tox_file_send Ядро ожидает решения принимающей стороны.

Решение обрабатывается событием tox_file_recv_control_cb (номер_друга, номер_файла, контроль) , Где:

  • номер_друга - номер друга.

  • номер дела - номер дела.

  • контроль - команда управления файлами ( TOX_FILE_CONTROL_CANCEL , TOX_FILE_CONTROL_PAUSE или TOX_FILE_CONTROL_RESUME обсуждалось ранее).

Обработка этого события позволяет освободить ресурсы, если клиент отказывается принять файл.

Эхо-боту нужно только передать аватар.

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

Если токс-клиент друга ранее загрузил аватар с этим file_id , то он может отменить повторную передачу аватара.



class EchoBot(ToxCore): .

def __init__(self): .

self.avatar_name = "avatar.png" self.avatar_size = os.path.getsize(avatar_name) self.avatar_fd = open(avatar_name, "rb") data = self.avatar_fd.read() self.avatar_id = ToxCore.tox_hash(data) def tox_friend_connection_status_cb(self, friend_number, connection_status): if connection_status != ToxCore.TOX_CONNECTION_NONE: send_avatar(friend_number) def send_avatar(self, friend_number): file_number = self.tox_file_send(friend_number, ToxCore.TOX_FILE_KIND_AVATAR, self.avatar_size, self.avatar_id, self.avatar_name) def tox_file_recv_control_cb(self, friend_number, file_number, control): pass def tox_file_chunk_request_cb(self, friend_number, file_number, position, length): if length == 0: return self.avatar_fd.seek(position, 0) data = self.avatar_fd.read(length) self.tox_file_send_chunk(friend_number, file_number, position, data)

Помимо приема и передачи сообщений и файлов, ядро реализует возможность передачи пакетов (с потерями или без потерь).

Методы для этой цели tox_friend_send_lossy_packet И tox_friend_send_lossless_packet , а также события tox_friend_lossy_packet_cb И tox_friend_lossless_packet_cb .

Ссылки

  • tox.chat — текущий официальный сайт проекта Tox (вместо tox.im).

  • Проект ядра toxcore на github .

  • Проект оболочки Python Pytoxcore на GitHub .

  • Проект создания бинарных версий клиентов и библиотек tox.pkg на GitHub .

  • «Официальный» проект оболочки Python PyTox на GitHub (на момент написания библиотека не была скомпилирована из-за использования «старого» API).

Теги: #im #tox #api #мессенджеры #api
Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.