HTML Parsing Specification¶
Полная спецификация поддерживаемых HTML элементов и классов для BotFarm2 и их преобразования в формат мессенджеров (Telegram, VK, Max, Instagram).
Базовая структура¶
Сообщение (.bf-message)¶
Базовый контейнер для всех сообщений.
Обязательные элементы:
- .bf-text — контейнер для текста сообщения
Опциональные элементы:
- img — изображения (одно или несколько)
- .bf-inline — контейнер для инлайн-кнопок
Классы сообщений¶
1. Обычное сообщение (по умолчанию)¶
Отправка: 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>
Результат:
Табличная разметка (строки и столбцы)¶
<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. Навигационные кнопки (callback)¶
При нажатии пользователь переходит на /next-page. В Telegram реализовано через callback_data, в VK/Max — через аналогичные механизмы.
2. URL-кнопки (.bf-url)¶
Открывают ссылку в браузере, без POST-запроса в botfarm.
При нажатии открывается внешняя ссылка в браузере.
3. Файловые кнопки (.bf-file)¶
Отмечает ссылку как файл для корректной обработки.
Аналогично навигационной кнопке, но URL.is_file=True.
4. Кнопка "Назад" (.bf-back)¶
Возвращает на предыдущую страницу из истории пользователя или на главную страницу.
Логика:
- Если страница показана по команде (
/startили другие команды): - Игнорирует
hrefи историю -
Всегда ведет на главную страницу (
company.home_page_url) -
Если страница показана НЕ по команде (callback, текст, файл и т.д.):
- Ищет последнюю запись в
UserPageHistory - Если история найдена: переходит на
UserPageHistory.urlс сохраненнойpage - Если истории нет: использует
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)¶
Не удаляет предыдущее сообщение при переходе.
При нажатии предыдущее сообщение не удаляется. В 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 пользователь¶
Результат (Telegram):
- Кликабельная ссылка на профиль пользователя
- Если есть
@username— добавляется после
Telegram бот → VK пользователь¶
Результат (Telegram):
- Обычная ссылка на VK профиль
VK бот → Telegram пользователь¶
Результат во ВКонтакте:
(Если нет username, просто имя)
VK бот → VK пользователь¶
Результат во ВКонтакте:
Несколько упоминаний¶
<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), он будет упомянут во всех:
3. parse_mode обязателен
Упоминания работают только с:
- .bf-html (parse_mode="HTML")
- .bf-markdown2 (parse_mode="MarkdownV2")
Для .bf-plain mentions не обрабатываются.
4. Экранирование
Текст вне mention автоматически экранируется для MarkdownV2:
→Привет, [Иван](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
}
Или из хедера запроса:
Пример использования¶
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):
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.pdf → file.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¶
Страница с этим метатегом не будет участвовать в навигации "Назад". Полезно для промежуточных страниц (подтверждения, загрузки, результаты действий).
bf-stack¶
Работает в связке с настройкой бота stack_mode="only_stack". В этом режиме в стек навигации попадают только страницы с этим метатегом.
bf-strip-query¶
При наличии этого метатега base_url страницы вычисляется без query string (все GET-параметры убираются). Без него убирается только служебный параметр bf_page.
Полезно когда одна и та же страница вызывается с разными GET-параметрами, но для навигации "Назад" должна считаться одной и той же страницей.
Пример:
Страница /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):
Навигация:
- ← — предыдущая страница
- → — следующая страница
- Обе стрелки на одной строке (экономия места)
Параметры:
- 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 параметр:
Ограничения платформ¶
Текстовые лимиты¶
| Поле | 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 пуст или отсутствует:
Результат:
- Для суперпользователя: ⚠️ На странице не найден текст (пустой .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):
Технические детали¶
Парсинг 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):
Кнопка (URL):
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], [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 символов) - [ ] Проверка работы через админ-панель (🔄 повтор навигации)