Перейти к содержанию

HTML Parsing Specification

Полная спецификация поддерживаемых HTML элементов и классов для BotFarm2 и их преобразования в формат мессенджеров (Telegram, VK, Max, Instagram).

Базовая структура

Сообщение (.bf-message)

Базовый контейнер для всех сообщений.

<div class="bf-message">
    <span class="bf-text">Ваш текст здесь</span>
</div>

Обязательные элементы: - .bf-text — контейнер для текста сообщения

Опциональные элементы: - img — изображения (одно или несколько) - .bf-inline — контейнер для инлайн-кнопок


Классы сообщений

1. Обычное сообщение (по умолчанию)

<div class="bf-message">
    <span class="bf-text">Текст без форматирования</span>
</div>

Отправка: sendMessage без parse_mode


2. HTML сообщение (.bf-html)

Поддерживает HTML-теги для форматирования.

<div class="bf-message bf-html">
    <span class="bf-text">
        Текст с <b>жирным</b>, <i>курсивом</i> и <a href="https://example.com">ссылкой</a>
    </span>
</div>

Отправка: sendMessage с parse_mode="HTML"

Поддерживаемые HTML-теги: - <b>, <strong> — жирный текст - <i>, <em> — курсив - <u> — подчеркнутый - <s>, <strike>, <del> — зачеркнутый - <code> — моноширинный - <pre> — блок кода - <a href="URL"> — ссылка - <a href="bf://user?id=USER_ID"> — упоминание пользователя (mention) - <br>, <br/> — перенос строки (конвертируется в \n)

Примечание: HTML-теги автоматически экранируются в .bf-text.html(), поэтому <br> преобразуется в \n.


3. MarkdownV2 сообщение (.bf-markdown2)

Поддерживает форматирование Telegram MarkdownV2.

<div class="bf-message bf-markdown2">
    <span class="bf-text">
        Текст с *жирным*, _курсивом_ и [ссылкой](https://example.com)
    </span>
</div>

Отправка: sendMessage с parse_mode="MarkdownV2" (только Telegram)

Поддерживаемые элементы: - *текст* — жирный - _текст_ — курсив - __текст__ — подчеркнутый - ~текст~ — зачеркнутый - [текст](URL) — ссылка - `код` — моноширинный - ```код``` — блок кода

⚠️ Важно: Спецсимволы должны быть экранированы: _*[]()~ >#+-=|{}.!`


4. Простой текст (.bf-plain)

Отключает любое форматирование.

<div class="bf-message bf-plain">
    <span class="bf-text">
        Текст с символами <b>не форматируется</b> * _ # и т.д.
    </span>
</div>

Отправка: sendMessage без parse_mode


Кнопки

Инлайн-кнопки (.bf-inline)

Кнопки под сообщением (inline keyboard в Telegram, аналоги в других платформах).

Простые кнопки

<div class="bf-message">
    <span class="bf-text">Выберите действие:</span>
    <div class="bf-inline">
        <a href="/page1">Кнопка 1</a>
        <a href="/page2">Кнопка 2</a>
    </div>
</div>

Результат:

Выберите действие:
[Кнопка 1] [Кнопка 2]


Табличная разметка (строки и столбцы)

<div class="bf-message">
    <span class="bf-text">Меню:</span>
    <table class="bf-inline">
        <tr>
            <td><a href="/page1">Кнопка 1</a></td>
            <td><a href="/page2">Кнопка 2</a></td>
        </tr>
        <tr>
            <td><a href="/page3">Кнопка 3</a></td>
        </tr>
    </table>
</div>

Результат:

Меню:
[Кнопка 1] [Кнопка 2]
[    Кнопка 3     ]


Типы кнопок

1. Навигационные кнопки (callback)

<a href="/next-page">Следующая страница</a>

При нажатии пользователь переходит на /next-page. В Telegram реализовано через callback_data, в VK/Max — через аналогичные механизмы.


2. URL-кнопки (.bf-url)

Открывают ссылку в браузере, без POST-запроса в botfarm.

<a href="https://google.com" class="bf-url">Открыть Google</a>

При нажатии открывается внешняя ссылка в браузере.


3. Файловые кнопки (.bf-file)

Отмечает ссылку как файл для корректной обработки.

<a href="/download/file.pdf" class="bf-file">Скачать PDF</a>

Аналогично навигационной кнопке, но URL.is_file=True.


4. Кнопка "Назад" (.bf-back)

Возвращает на предыдущую страницу из истории пользователя или на главную страницу.

<a href="/default-url" class="bf-back">← Назад</a>

Логика:

  1. Если страница показана по команде (/start или другие команды):
  2. Игнорирует href и историю
  3. Всегда ведет на главную страницу (company.home_page_url)

  4. Если страница показана НЕ по команде (callback, текст, файл и т.д.):

  5. Ищет последнюю запись в UserPageHistory
  6. Если история найдена: переходит на UserPageHistory.url с сохраненной page
  7. Если истории нет: использует href со страницы как fallback

Пример:

<!-- Страница товара -->
<div class="bf-message">
    <span class="bf-text">Товар "Футболка"</span>
    <div class="bf-inline">
        <a href="/buy/123">Купить</a>
        <a href="/catalog" class="bf-back">← Назад</a>
    </div>
</div>

Поведение: - Пользователь набрал /product_123 (команда) → "Назад" ведет на главную страницу - Пользователь пришел из каталога по кнопке → "Назад" ведет на /catalog (из истории) - Первый визит (не по команде) → "Назад" ведет на /catalog (из href)

Важно: - История создается только при успешных переходах (статус 200) - История НЕ создается если preserve_location=True или при POST с пустым ответом - Команды всегда сбрасывают навигацию на главную для кнопки "Назад"

Стек навигации: - При каждом переходе ВСЕГДА создается запись в UserPageHistory (для аналитики) - При команде (/start, /book и т.д.): весь существующий стек истории очищается - все записи помечаются removed_from_stack=True - Записи с is_back_navigation=True (переходы по кнопке "Назад"): - Автоматически создаются с removed_from_stack=True - Исключаются из поиска предыдущей страницы - При нажатии "Назад": 1. Создается новая запись с is_back_navigation=True и removed_from_stack=True 2. Находится первая страница с другим base URL (где removed_from_stack=False) 3. Эта страница откуда ушли помечается как removed_from_stack=True 4. Также помечаются все более ранние записи (с id ≤) с тем же base URL (для пропуска листания страниц) - При поиске предыдущей страницы исключаются записи где: - removed_from_stack=True ИЛИ - base_url совпадает с текущей страницей (предотвращение зацикливания) - Повторное нажатие "Назад" ведет на ещё более ранний экран с другим URL - Все записи сохраняются для аналитики, независимо от флагов

Пример 1 - Навигация с кнопкой "Назад":

Пользователь: Главная → Каталог (page=0) → листает → Каталог (page=1) → листает → Каталог (page=2) → переход → Товар 1
История БД:
  [1: Главная, 2: Каталог p=0, 3: Каталог p=1, 4: Каталог p=2, 5: Товар 1]
  Все: removed_from_stack=False, is_back_navigation=False

Нажал "Назад" на Товаре 1:
- Создана запись: 6: Каталог p=2 (is_back_navigation=True, removed=True автоматически)
- Найдена первая запись где removed=False: 5: Товар 1 (откуда ушли)
- Помечены как removed: запись 5 (Товар 1 и все с тем же base URL где id≤5)
История БД:
  [1: Главная, 2: Каталог p=0, 3: Каталог p=1, 4: Каталог p=2,
   5: Товар 1 (removed), 6: Каталог p=2 (is_back, removed)]
Навигация ищет где removed=False AND base_url≠текущий → находит #4 (Каталог p=2)
Показан экран: Каталог page=2 ✅

Нажал "Назад" на Каталоге:
- Создана запись: 7: Главная (is_back_navigation=True, removed=True автоматически)
- Найдена первая запись где removed=False: 4: Каталог p=2 (откуда ушли, id=4)
- Помечены как removed: записи 2, 3, 4 (все /catalog где id≤4)
История БД:
  [1: Главная, 2: Каталог p=0 (removed), 3: Каталог p=1 (removed),
   4: Каталог p=2 (removed), 5: Товар 1 (removed), 6: Каталог p=2 (is_back, removed),
   7: Главная (is_back, removed)]
Навигация ищет где removed=False → находит #1 (Главная)
Показан экран: Главная ✅

Админ видит ВСЮ историю: 7 записей с пометками is_back_navigation и removed_from_stack

Пример 2 - Ввод команды очищает стек:

Пользователь: Главная → Каталог → Товар 1 → вводит команду /book
История БД перед командой:
  [1: Главная, 2: Каталог, 3: Товар 1]
  Все: removed_from_stack=False

Пользователь ввел /book:
- ВСЕ существующие записи помечаются как removed_from_stack=True
- Создана запись: 4: /book (source=command, removed=False)
История БД:
  [1: Главная (removed), 2: Каталог (removed), 3: Товар 1 (removed), 4: /book]

Пользователь переходит на другую страницу → Автор 1:
- Создана запись: 5: Автор 1 (removed=False)

Нажал "Назад" на Авторе 1:
- Навигация ищет где removed=False AND is_back=False
- Находит #4 (/book) - возвращается на страницу /book
- Не возвращается на Товар 1, потому что он removed=True

Админ видит ВСЮ историю: 5 записей включая старые с пометкой removed_from_stack

Важно про пагинацию: - При листании кнопок (← →) каждая страница сохраняется в истории со своим page - При переходе "Назад" пропускаются все страницы листания и происходит возврат на предыдущий экран (с другим URL) - Последняя страница листания показывается как целевая страница "Назад" - Это делает навигацию более естественной - пользователь не "проматывает назад" все листания


5. Платформенная видимость (.bf-only-tg, .bf-only-max, .bf-only-vk, .bf-only-ig)

Кнопка показывается только в указанных мессенджерах.

<!-- Только в Telegram -->
<a href="/tg-channel" class="bf-only-tg">Канал в Telegram</a>

<!-- Только в Max -->
<a href="/max-group" class="bf-only-max">Группа в Max</a>

<!-- В Telegram И Max (OR-логика) -->
<a href="/feature" class="bf-only-tg bf-only-max">TG и Max</a>

<!-- Без класса — для всех платформ -->
<a href="/page">Для всех</a>

Логика: - Без bf-only-* классов — кнопка видна на всех платформах - С одним или несколькими bf-only-* — кнопка видна только на перечисленных платформах (OR) - Можно комбинировать с другими классами: class="bf-only-tg bf-url", class="bf-only-max bf-back"


6. Сохранение сообщения (.bf-keep-message)

Не удаляет предыдущее сообщение при переходе.

<a href="/page" class="bf-keep-message">Не удалять сообщение</a>

При нажатии предыдущее сообщение не удаляется. В Telegram передается через callback_data с флагом keep-message: true.


Постоянные кнопки (.bf-inline-permanent)

Кнопки, которые всегда видны на всех страницах пагинации.

<div class="bf-inline">
    <!-- Обычные кнопки (пагинируются) -->
    <a href="/item1">Элемент 1</a>
    <a href="/item2">Элемент 2</a>
    <!-- ... еще 20 элементов ... -->

    <!-- Постоянные кнопки (всегда видны) -->
    <div class="bf-inline-permanent">
        <a href="/">🏠 Главная</a>
        <a href="/settings">⚙️ Настройки</a>
    </div>
</div>

Особенности: - Постоянные кнопки отображаются на всех страницах - Уменьшают доступное место для обычных кнопок - Ассерт: len(permanent_buttons) <= bot.page_size - 2


Изображения

Одно изображение

<div class="bf-message">
    <span class="bf-text">Описание изображения</span>
    <img src="/path/to/image.jpg">
</div>

Отправка: sendPhoto с caption="Описание изображения" (Telegram), аналогичные методы на других платформах.


Несколько изображений (Media Group)

<div class="bf-message">
    <span class="bf-text">Описание альбома</span>
    <img src="/image1.jpg">
    <img src="/image2.jpg">
    <img src="/image3.jpg">
</div>

Отправка: 1. Группа фото (в Telegram — sendMediaGroup) 2. Отдельное сообщение с кнопками (если есть .bf-inline)

Лимиты: - Максимум 10 изображений в группе - caption ограничен 1024 символами


д## Упоминания пользователей (Mentions)

HTML формат (для .bf-html)

<div class="bf-message bf-html">
    <span class="bf-text">
        Привет, <a href="bf://user?id=123">Иван Иванов</a>!
    </span>
</div>

MarkdownV2 формат (для .bf-markdown2)

<div class="bf-message bf-markdown2">
    <span class="bf-text">
        Привет, [Иван Иванов](bf://user?id=123)!
    </span>
</div>

Где: - 123 — это BFUser.id (внутренний ID пользователя в BotFarm)


Преобразование в Telegram

Telegram бот → Telegram пользователь

<a href="mention:123">Иван Иванов</a>

Результат (Telegram):

[Иван Иванов](tg://user?id=987654321) @ivanov

  • Кликабельная ссылка на профиль пользователя
  • Если есть @username — добавляется после

Telegram бот → VK пользователь

<a href="mention:123">Иван Иванов</a>

Результат (Telegram):

Иван Иванов https://vk.com/ivanov

  • Обычная ссылка на VK профиль

VK бот → Telegram пользователь

<a href="mention:123">Иван Иванов</a>

Результат во ВКонтакте:

Иван Иванов https://t.me/ivanov

(Если нет username, просто имя)


VK бот → VK пользователь

<a href="mention:123">Иван Иванов</a>

Результат во ВКонтакте:

Иван Иванов https://vk.com/ivanov


Несколько упоминаний

<div class="bf-message bf-html">
    <span class="bf-text">
        Встреча: <a href="mention:123">Иван</a>,
        <a href="mention:456">Петр</a> и
        <a href="mention:789">Мария</a>
    </span>
</div>

Результат (Telegram):

Встреча: [Иван](tg://user?id=111) @ivan,
[Петр](tg://user?id=222) @petr и
[Мария](tg://user?id=333) @maria


Важные замечания

1. BotUser должен существовать

Система ищет BotUser для данной компании. Если пользователь привязан к нескольким ботам в одной компании, упоминания будут для всех.

2. Cross-platform mentions

Если пользователь есть в нескольких платформах (TG + VK), он будет упомянут во всех:

[Иван](tg://user?id=123) @ivan Иван https://vk.com/ivan

3. parse_mode обязателен

Упоминания работают только с: - .bf-html (parse_mode="HTML") - .bf-markdown2 (parse_mode="MarkdownV2")

Для .bf-plain mentions не обрабатываются.

4. Экранирование

Текст вне mention автоматически экранируется для MarkdownV2:

Привет, <a href="mention:123">Иван</a>! Как дела?
Привет, [Иван](tg://user?id=123) @ivan\! Как дела\?


Получение BFUser.id

Через API:

# Получить пользователя по Telegram ID
GET /api/v1/getUserByTelegramId/123456789

Response:
{
    "id": 123,  # ← BFUser.id для mention
    "first_name": "Иван",
    "tg_ext_id": 123456789
}

Или из хедера запроса:

# В вашем Django view:
user_id = request.META.get('HTTP_BOTFARM_USER_ID')  # "123"


Пример использования

1. В HTML-ответе (Django View)

def meeting_page(request):
    user_id = request.META.get('HTTP_BOTFARM_USER_ID')

    # Получаем участников встречи
    participants = Meeting.objects.get(id=123).participants.all()

    mentions = []
    for user in participants:
        # Находим BFUser.id через внешний ID
        bf_user = BFUser.objects.filter(tg_ext_id=user.telegram_id).first()
        if bf_user:
            mentions.append(f'<a href="mention:{bf_user.id}">{user.name}</a>')

    html = f'''
    <div class="bf-message bf-html">
        <span class="bf-text">
            Участники встречи: {', '.join(mentions)}
        </span>
    </div>
    '''

    return HttpResponse(html)

Результат (Telegram):

Участники встречи: [Иван](tg://user?id=111) @ivan, [Петр](tg://user?id=222) @petr


2. Через API (HTML формат)

import requests

response = requests.post(
    "https://app2.botfarm.me/api/v1/sendMessage",
    headers={"Authorization": "Bearer YOUR_TOKEN"},
    json={
        "text": 'Привет, <a href="mention:123">Иван</a>!',
        "user_ids": [456],
        "parse_mode": "HTML"
    }
)

Результат: Пользователь 456 получит сообщение с упоминанием пользователя 123.


3. Через API (MarkdownV2 формат)

import requests

response = requests.post(
    "https://app2.botfarm.me/api/v1/sendMessage",
    headers={"Authorization": "Bearer YOUR_TOKEN"},
    json={
        "text": "Привет, [Иван](mention:123)\\!",
        "user_ids": [456],
        "parse_mode": "MarkdownV2"
    }
)

⚠️ Внимание: В MarkdownV2 нужно экранировать спецсимволы: \!


Специальные элементы

1. Геолокация (.bf-location)

<!-- Простая геолокация (sendLocation) -->
<div class="bf-message bf-location" data-lat="55.7558" data-lon="37.6173">
    <span class="bf-text">Москва, Красная площадь</span>
</div>

<!-- Venue — место с названием и адресом (sendVenue) -->
<div class="bf-message bf-location" data-lat="55.7558" data-lon="37.6173"
     data-title="Кремль" data-address="Москва, Красная площадь, 1">
    <span class="bf-text"></span>
</div>

Атрибуты: - data-lat — широта (обязательно) - data-lon — долгота (обязательно) - data-title — название места (опционально, для venue) - data-address — адрес места (опционально, для venue) - data-foursquare-id — Foursquare ID места (опционально) - data-google-place-id — Google Places ID места (опционально)

Отправка: если указаны data-title и data-address — venue (место), иначе — геолокация. В Telegram: sendVenue / sendLocation.


2. Запрос контакта (.bf-request-contact)

<div class="bf-message bf-request-contact">
    <span class="bf-text">Отправьте свой номер телефона</span>
</div>

Отправка: Сообщение с кнопкой запроса контакта (в Telegram — reply keyboard с request_contact: true).

POST-запрос при отправке контакта:

{
    "contact": {
        "phone_number": "+79001234567",
        "first_name": "Иван",
        "last_name": "Иванов",
        "user_id": 123
    }
}

Дополнительная настройка:

Можно изменить текст кнопки и сообщения:

<div class="bf-message bf-request-contact">
    <span class="bf-text">Нажми кнопку ниже</span>
    📱 Отправить телефон
</div>

Логика: - Текст внутри .bf-text = текст сообщения - Текст вне .bf-text (но внутри .bf-message) = текст кнопки - Если текст кнопки пустой → "Отправить номер телефона" (по умолчанию)

Автоматическое удаление клавиатуры: - Если предыдущая страница имела .bf-request-contact, а текущая — нет, клавиатура автоматически удаляется


3. Неудаляемое сообщение (.bf-permanent)

Сообщение с классом .bf-permanent не удаляется при любой навигации — ни при нажатии кнопок, ни при /start, ни при отправке команд. Используется для рассылок и уведомлений, которые должны оставаться в чате.

<div class="bf-message bf-plain bf-permanent">
    <div class="bf-text">
        Это сообщение останется в чате навсегда.
    </div>
    <div class="list-group bf-inline">
        <a href="/next" class="list-group-item">Перейти</a>
    </div>
</div>

Поведение: - Флаг is_permanent сохраняется в UserPage при загрузке страницы - При навигации от permanent-страницы message_to_remove не устанавливается - Работает со всеми платформами (Telegram, Max, VK) - Совместим с другими классами: .bf-html, .bf-plain, .bf-markdown2


4. Подтверждение (.bf-confirm / .bf-confirm-toast)

Показывает всплывающее уведомление (в Telegram — через answerCallbackQuery) и автоматически навигирует на ссылку. Используется как модификатор на .bf-message, аналогично .bf-html, .bf-plain и т.д.

<!-- Модальное окно с кнопкой OK (show_alert: true) -->
<div class="bf-message bf-confirm">
    <span class="bf-text">Вы подтвердили запись!</span>
    <div class="bf-inline">
        <a href="/next-page">any text</a>
    </div>
</div>

<!-- Тост-уведомление сверху, исчезает само (show_alert: false) -->
<div class="bf-message bf-confirm-toast">
    <span class="bf-text">Сохранено</span>
    <div class="bf-inline">
        <a href="/next-page">any text</a>
    </div>
</div>

Классы (на .bf-message): - bf-confirm — модальное окно, пользователь должен нажать OK - bf-confirm-toast — тост-уведомление сверху экрана, исчезает через ~5 секунд

Можно комбинировать с другими модификаторами .bf-message:

<div class="bf-message bf-plain bf-confirm-toast">
    <span class="bf-text">Сохранено</span>
    <div class="bf-inline">
        <a href="/next-page">ok</a>
    </div>
</div>

Структура: - .bf-text — текст уведомления (максимум 200 символов) - .bf-inline a — ссылка для автоматической навигации после подтверждения (текст ссылки не важен)

Поведение: 1. Пользователь нажимает кнопку → загружается страница с bf-confirm 2. Показывается всплывающее уведомление с текстом из .bf-text 3. Автоматически происходит навигация на URL из ссылки в .bf-inline 4. Предыдущее сообщение удаляется как обычно

Fallback: если страница открыта не из callback (например, через /start или текстовое сообщение), текст отправляется как обычное сообщение, навигация на ссылку происходит так же.

Комбинация с обычными .bf-message: на одной странице можно использовать и .bf-message.bf-confirm, и обычные .bf-message. Сначала обрабатывается confirm, затем обычные сообщения, после чего происходит навигация на ссылку.

<div class="bf-message bf-confirm">
    <span class="bf-text">Действие выполнено</span>
    <div class="bf-inline">
        <a href="/result">ok</a>
    </div>
</div>
<div class="bf-message">
    <span class="bf-text">Дополнительная информация</span>
</div>

4. PDF документы

При получении Content-Type: application/pdf файл автоматически отправляется как документ.

Отправка: как документ (в Telegram — sendDocument).

Имя файла: 1. Из заголовка Content-Disposition: attachment; filename="document.pdf" 2. Из пути URL /path/to/file.pdffile.pdf 3. По умолчанию: document.pdf

Пример:

# В вашем Django view:
response = HttpResponse(pdf_content, content_type='application/pdf')
response['Content-Disposition'] = 'attachment; filename="Отчет.pdf"'
return response


Метатеги страниц

Метатеги в <head> управляют поведением страницы в системе навигации.

Метатег Описание
bf-skip-stack Страница не попадает в стек навигации
bf-stack Страница явно попадает в стек (для режима only_stack)
bf-strip-query При вычислении base_url убирается вся query string

bf-skip-stack

Страница с этим метатегом не будет участвовать в навигации "Назад". Полезно для промежуточных страниц (подтверждения, загрузки, результаты действий).

<head>
  <meta name="bf-skip-stack">
</head>

bf-stack

Работает в связке с настройкой бота stack_mode="only_stack". В этом режиме в стек навигации попадают только страницы с этим метатегом.

<head>
  <meta name="bf-stack">
</head>

bf-strip-query

При наличии этого метатега base_url страницы вычисляется без query string (все GET-параметры убираются). Без него убирается только служебный параметр bf_page.

Полезно когда одна и та же страница вызывается с разными GET-параметрами, но для навигации "Назад" должна считаться одной и той же страницей.

<head>
  <meta name="bf-strip-query">
</head>

Пример:

Страница /profile?user_id=1 и /profile?user_id=2 — разные URL, но логически одна страница "Профиль".

  • Без метатега: base_url = /profile?user_id=1 и /profile?user_id=2 — считаются разными страницами в стеке навигации
  • С метатегом: base_url = /profile — считаются одной страницей, кнопка "Назад" пропустит обе
Навигация: Каталог → Профиль?user_id=1 → Профиль?user_id=2 → Товар

Без bf-strip-query:
  "Назад" на Товаре → Профиль?user_id=2
  "Назад" на Профиле?user_id=2 → Профиль?user_id=1
  "Назад" на Профиле?user_id=1 → Каталог

С bf-strip-query:
  "Назад" на Товаре → Профиль?user_id=2
  "Назад" на Профиле → Каталог (пропустил user_id=1 т.к. base_url совпадает)

Пагинация кнопок

Автоматическая пагинация

Если кнопок больше, чем bot.page_size (по умолчанию 16), они автоматически разбиваются на страницы.

<div class="bf-inline">
    <a href="/item1">Элемент 1</a>
    <a href="/item2">Элемент 2</a>
    <!-- ... 20 элементов всего ... -->
    <a href="/item20">Элемент 20</a>
</div>

Результат (страница 1):

[Элемент 1] ... [Элемент 15]
[    ←        →    ]

Навигация: - — предыдущая страница - — следующая страница - Обе стрелки на одной строке (экономия места)

Параметры: - bot.page_size — максимум кнопок на странице (по умолчанию 16) - Навигация резервирует 1 строку (когда total_buttons > page_size) - real_page_size = bot.page_size - 1 - len(permanent_buttons)

Логика: - Если total_buttons <= page_size → навигация НЕ показывается - Если total_buttons > page_size → навигация показывается

URL параметр:

?bf_page=0  # первая страница
?bf_page=1  # вторая страница


Ограничения платформ

Текстовые лимиты

Поле Telegram VK
Текст сообщения 4096 символов 4096 символов
Подпись к фото 1024 символа
Имя кнопки 80 символов 39 символов

Авто-обрезка: - trim_text(text, 4096) для сообщений - trim_text(text, 1024) для подписей - textwrap.shorten(text, 80) для кнопок TG - textwrap.shorten(text, 39) для кнопок VK


Кнопки (Telegram)

Параметр Лимит
Кнопок в строке ~8 (зависит от длины текста)
Строк кнопок Без лимита (рекомендуется ≤ 8)

Изображения (Telegram)

Параметр Лимит
Размер фото 10 МБ
Размер документа 50 МБ
Фото в группе 10

В content-режиме (tg_photo_mode=content) фото автоматически сжимаются до TELEGRAM_PHOTO_MAX_SIZE_KB (по умолчанию 300 КБ) и кешируются на 24ч.


Обработка ошибок

Пустой .bf-text

Если .bf-text пуст или отсутствует:

<div class="bf-message">
    <span class="bf-text"></span>
</div>

Результат: - Для суперпользователя: ⚠️ На странице не найден текст (пустой .bf-text)\n\nURL: {url}\n\n{bot.error_message} - Для обычного пользователя: {bot.error_message} - Отправка уведомления в сервисный чат - Запись в UserPageHistory с ошибкой


HTTP статус ≠ 200

Fallback (405, 401, 403)

Если bot.fallback_url установлен:

POST {bot.fallback_url}
Headers:
  FBOT-UID: {user.id}
  FBOT-CRM-URL: {bot.company.home_page_url}/bf_user_redir/{user.id}
  FBOT-Original-URL: {url}
  FBOT-Original-Method: {method}
  FBOT-Original-Status: {status_code}
Body: {request_data}

Запись в историю: - fallback_triggered=True - fallback_status_code={fallback_resp.status_code}


Другие ошибки

Для суперпользователя: - Парсинг JSON с ошибкой от BotfarmDebugMiddleware - Форматированный вывод traceback

Для обычного пользователя: - {bot.error_message}

Отправка в сервисный чат:

🚨 Ошибка навигации

👤 Пользователь: {user.full_name} (ID: {user.id})
🤖 Бот: {bot.name}
🔢 Статус: {status_code}

📍 {method} {url}


Примеры

1. Текстовое сообщение с кнопками

<div class="bf-message bf-html">
    <span class="bf-text">
        Добро пожаловать в <b>BotFarm</b>!<br>
        Выберите раздел:
    </span>
    <div class="bf-inline">
        <a href="/catalog">📚 Каталог</a>
        <a href="/profile">👤 Профиль</a>
        <a href="/settings">⚙️ Настройки</a>
    </div>
</div>

2. Изображение с кнопками

<div class="bf-message">
    <span class="bf-text">Новый товар в продаже!</span>
    <img src="/media/product.jpg">
    <div class="bf-inline">
        <a href="/buy?product_id=123">🛒 Купить</a>
        <a href="/catalog">← Назад к каталогу</a>
    </div>
</div>

3. Геолокация с кнопками

<div class="bf-message bf-location" data-lat="55.7558" data-lon="37.6173">
    <span class="bf-text">📍 Наш офис в Москве</span>
    <div class="bf-inline">
        <a href="https://yandex.ru/maps/?ll=37.6173,55.7558&z=16" class="bf-url">🗺 Открыть карту</a>
        <a href="/contacts">Контакты</a>
    </div>
</div>

3.1 Venue (место) с кнопками

<div class="bf-message bf-location" data-lat="55.7558" data-lon="37.6173"
     data-title="Офис BotFarm" data-address="Москва, ул. Примерная, 1">
    <span class="bf-text"></span>
    <div class="bf-inline">
        <a href="/contacts">Контакты</a>
    </div>
</div>

4. Запрос контакта

<div class="bf-message bf-request-contact">
    <span class="bf-text">Для завершения регистрации нужен ваш номер телефона</span>
    📱 Поделиться номером
</div>

5. Многостраничное меню

<div class="bf-message">
    <span class="bf-text">Выберите товар:</span>
    <div class="bf-inline">
        <!-- 20 товаров -->
        <a href="/product/1">Товар 1</a>
        <a href="/product/2">Товар 2</a>
        <!-- ... -->
        <a href="/product/20">Товар 20</a>

        <!-- Постоянные кнопки -->
        <div class="bf-inline-permanent">
            <a href="/">🏠 Главная</a>
            <a href="/cart">🛒 Корзина</a>
        </div>
    </div>
</div>

Результат (страница 1):

Выберите товар:
[Товар 1] ... [Товар 13]
[    ←        →    ]
[🏠 Главная] [🛒 Корзина]


Технические детали

Парсинг HTML

Библиотека: pyquery (jQuery-like синтаксис для Python)

from pyquery import PyQuery as pq

d = pq(response.text)
for message in d('.bf-message').items():
    text = pq(message)('.bf-text').text().strip()
    # ...

Преобразование в JSON для Telegram

Кнопка (callback):

{
    "text": "Кнопка 1",
    "callback_data": json.dumps({"url_id": 123, "page": 0})
}

Кнопка (URL):

{
    "text": "Открыть сайт",
    "url": "https://example.com"
}

Inline keyboard:

{
    "inline_keyboard": [
        [{"text": "Кнопка 1", "callback_data": "..."}],
        [{"text": "Кнопка 2", "callback_data": "..."}, {"text": "Кнопка 3", "callback_data": "..."}]
    ]
}


Headers отправляемые на ваш сервер

При запросе страницы BotFarm отправляет заголовки с информацией о пользователе:

Botfarm-ID: 123
Botfarm-User-ID: 123
Botfarm-First-Name: %D0%98%D0%B2%D0%B0%D0%BD (URL-encoded)
Botfarm-Last-Name: %D0%98%D0%B2%D0%B0%D0%BD%D0%BE%D0%B2
Botfarm-Username: ivanov:123
Botfarm-Type: tg
Botfarm-Page: 0
Botfarm-Tg-Ext-ID: 123456789
Botfarm-VK-Ext-ID: 987654321       (если есть)
Botfarm-Max-Ext-ID: 555555555      (если есть)
Accept-Language: ru
User-Agent: BotFarm/1.0

Cookies

BotFarm автоматически сохраняет и передает cookies для каждого пользователя:

# Получение из БД
bot_user = BotUser.objects.get(bot=bot, user=user)
session = requests.Session()
session.cookies = cookiejar_from_dict(bot_user.cookies)

# После запроса cookies автоматически обновляются
bot_user.cookies = dict(session.cookies)
bot_user.save()

Отладка

Логирование

import structlog
logger = structlog.get_logger(__name__)

logger.info("Sending message", text=text, chat_id=chat_id, buttons=buttons)

Просмотр истории в админке

В Django Admin → BFUser → "История посещений":

[POST] 24.01.2026 (Сб) 22:52 (💬 test) [405] ↪️ fb → 200 ⏱0.235s 💬 текст
/b/ 🔄
↩️ откуда: /a/

Значки: - [POST], [GET] — метод запроса - [405] — статус код (красный = ошибка) - ↪️ fb → 200 — сработал fallback - ⏱0.235s — время ответа - 💬 текст — источник (text, callback, command и т.д.) - 🔄 — повторить навигацию


Повтор навигации

В админке можно повторить любой запрос: 1. Открыть BFUser → История посещений 2. Кликнуть 🔄 рядом с записью 3. Откроется форма с параметрами запроса 4. Нажать "Выполнить"


Чеклист интеграции

  • [ ] Убедитесь, что все сообщения обернуты в <div class="bf-message">
  • [ ] Текст всегда внутри <span class="bf-text">
  • [ ] Для HTML-форматирования добавлен класс .bf-html
  • [ ] Кнопки обернуты в .bf-inline
  • [ ] Для URL-кнопок используется класс .bf-url
  • [ ] Для многостраничных списков кнопки не превышают bot.page_size
  • [ ] PDF файлы отдаются с правильным Content-Type и Content-Disposition
  • [ ] Обработка POST-запросов с contact, text, location
  • [ ] Для подтверждений используется .bf-confirm или .bf-confirm-toast с .bf-text (до 200 символов)
  • [ ] Проверка работы через админ-панель (🔄 повтор навигации)

См. также

  • API — Полная документация REST API
  • Вебхуки — Формат входящих запросов (текст, контакт, геолокация)
  • Навигация — Стек страниц, кнопка "Назад", пагинация, refresh