FALCON BLACK English

Боты для Falcon Black

Бот — программа, которая отвечает людям в Falcon Black. Создаёте его в переписке с @botnest («Гнездо ботов»), берёте ключ на сайте и подключаете свою программу к Bot API: она получает сообщения людей и отвечает им. Форма API знакома тем, кто писал ботов для других платформ: те же имена методов и полей.

Боты работают в личной переписке, в группах и в каналах (ниже); события — длинным опросом (getUpdates) или вебхуком (setWebhook). Мини-приложения — ниже. Встроенный режим — следующим шагом; на его вызовы сервер отвечает понятным отказом, а не молчанием.

Быстрый старт

  1. Откройте в Falcon Black переписку с @botnest (найдите по нику) и напишите /newbot.
  2. Пришлите имя бота, потом ник — латиницей, на конце bot (например, weather_kzn_bot).
  3. Напишите /token (или выберите бота в /mybots → «Ключ через сайт») — Гнездо пришлёт одноразовый код на 10 минут. Ключ сообщением не приходит никогда.
  4. Откройте falconblack.org/bots/key, введите код и пароль Falcon Black — ключ покажется один раз. Сохраните его в переменную окружения FB_BOT_TOKEN.
  5. Запустите пример ниже и напишите своему боту: он ответит.

Адрес и ключ

Все методы: https://falconblack.org/bot-api/v1/<метод>, GET или POST (JSON, форма или multipart/form-data для файлов). Ключ — только в заголовке:

Authorization: Bearer 1848831706977841:fb1_…
import zlib from "node:zlib";   // Node 20.15+ / 22
const B62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
export function keyLooksRight(key) {
  const m = /^(\d{10,16}:fb1_[0-9A-Za-z]{43})([0-9A-Za-z]{6})$/.exec(key);
  if (!m) return false;
  let n = zlib.crc32(m[1]), sum = "";
  for (let i = 0; i < 6; i++) { sum = B62[n % 62] + sum; n = Math.floor(n / 62); }
  return sum === m[2];
}

Ответ 401 говорит, что с ключом: token_malformed — нет ключа или не та форма, token_checksum — опечатка, token_unknown — такого ключа нет, token_revoked — ключ отозван (parameters.revoke_reason: reissue, owner, leak_message, leak_url, bot_deleted), key_in_url — ключ пришёл в адресе.

Пример: эхо-бот

Без библиотек, длинным опросом. Человек пишет — бот отвечает «Вы написали: …» и показывает кнопку «Бросить кубик»; человек отвечает «1» — бот бросает кубик. Проверено против сервера Falcon Black.

Node.js 20+ — echo.mjs

// Эхо-бот Falcon Black: Node.js 20+, без библиотек, длинный опрос.
// Запуск:  FB_BOT_TOKEN='ключ со страницы falconblack.org/bots/key' node echo.mjs
// Ключ — только в переменной окружения и только в заголовке Authorization: Bearer
// (ключ в адресе запроса сервер сразу отключает).
const API = process.env.FB_API || "https://falconblack.org/bot-api/v1";
const HEADERS = { Authorization: `Bearer ${(process.env.FB_BOT_TOKEN || "").trim()}`, "Content-Type": "application/json" };

async function call(method, params = {}) {
  const r = await fetch(`${API}/${method}`, { method: "POST", headers: HEADERS, body: JSON.stringify(params) });
  let j;
  try { j = await r.json(); } catch { throw new Error("ответ не JSON — сбой сети, повторим"); }
  if (j.ok) return j.result;
  throw Object.assign(new Error(j.description), { code: j.error_code, key: j.error_key, retryAfter: j.parameters?.retry_after });
}

// Кнопки люди пока видят списком с номерами под сообщением: ответ «1» приходит
// программе нажатием (callback_query). Ответ цифрой относится к ПОСЛЕДНЕМУ
// сообщению бота — поэтому кнопка есть под каждым ответом.
const DICE = { inline_keyboard: [[{ text: "Бросить кубик", callback_data: "roll" }]] };

async function handle(u) {
  const m = u.message;
  if (m?.text?.startsWith("/start")) {
    // «/start» приходит первым, когда человек впервые написал боту.
    await call("sendMessage", { chat_id: m.chat.id, text: "Здравствуйте! Напишите что-нибудь — я повторю.", reply_markup: DICE });
  } else if (m?.text) {
    await call("sendMessage", { chat_id: m.chat.id, text: `Вы написали: ${m.text}`, reply_markup: DICE });
  } else if (u.callback_query?.data === "roll") {
    const n = 1 + Math.floor(Math.random() * 6);
    await call("answerCallbackQuery", { callback_query_id: u.callback_query.id });
    await call("sendMessage", { chat_id: u.callback_query.message.chat.id, text: `Выпало ${n}`, reply_markup: DICE });
  }
}

let offset = 0;
for (;;) {
  let updates;
  try {
    updates = await call("getUpdates", { offset, timeout: 50 });
  } catch (e) {
    // 401 — ключ (причина — в e.key), 409 — запущена вторая копия: повтор не поможет.
    if ([401, 403, 409].includes(e.code)) { console.error(`${e.code} ${e.key}: ${e.message}`); process.exit(1); }
    await new Promise((ok) => setTimeout(ok, (e.retryAfter ?? 3) * 1000)); // сбой сети или 429
    continue;
  }
  for (const u of updates) {
    offset = u.update_id + 1;
    // Ошибка одного события (человек заблокировал бота — 403, предел — 429) не роняет программу.
    await handle(u).catch((e) => console.warn(`событие ${u.update_id}: ${e.code ?? ""} ${e.message}`));
  }
}

Python 3.9+ — echo.py (нужен requests)

# Эхо-бот Falcon Black: Python 3.9+, только requests, длинный опрос.
# Запуск:  FB_BOT_TOKEN='ключ со страницы falconblack.org/bots/key' python3 echo.py
# Ключ — только в переменной окружения и только в заголовке Authorization: Bearer
# (ключ в адресе запроса сервер сразу отключает).
import os, random, sys, time
import requests

API = os.environ.get("FB_API", "https://falconblack.org/bot-api/v1")
s = requests.Session()
s.headers["Authorization"] = "Bearer " + os.environ["FB_BOT_TOKEN"].strip()

class ApiError(Exception):
    def __init__(self, j):
        super().__init__(j.get("description"))
        self.code, self.key = j.get("error_code"), j.get("error_key")
        self.retry_after = (j.get("parameters") or {}).get("retry_after")

def call(method, **params):
    try:
        j = s.post(f"{API}/{method}", json=params, timeout=65).json()
    except ValueError:  # не JSON — страница прокси: временный сбой
        raise ApiError({"description": "ответ не JSON — сбой сети, повторим"})
    if not j.get("ok"):
        raise ApiError(j)
    return j["result"]

# Кнопки люди пока видят списком с номерами под сообщением: ответ «1» приходит
# программе нажатием (callback_query). Ответ цифрой относится к ПОСЛЕДНЕМУ
# сообщению бота — поэтому кнопка есть под каждым ответом.
DICE = {"inline_keyboard": [[{"text": "Бросить кубик", "callback_data": "roll"}]]}

def handle(u):
    msg = u.get("message") or {}
    text = msg.get("text") or ""
    if text.startswith("/start"):  # приходит первым, когда человек впервые написал боту
        call("sendMessage", chat_id=msg["chat"]["id"], text="Здравствуйте! Напишите что-нибудь — я повторю.", reply_markup=DICE)
    elif text:
        call("sendMessage", chat_id=msg["chat"]["id"], text=f"Вы написали: {text}", reply_markup=DICE)
    cq = u.get("callback_query")
    if cq and cq.get("data") == "roll":
        call("answerCallbackQuery", callback_query_id=cq["id"])
        call("sendMessage", chat_id=cq["message"]["chat"]["id"], text=f"Выпало {random.randint(1, 6)}", reply_markup=DICE)

offset = 0
while True:
    try:
        updates = call("getUpdates", offset=offset, timeout=50)
    except ApiError as e:
        if e.code in (401, 403, 409):  # ключ, бот отключён, вторая копия — повтор не поможет
            sys.exit(f"{e.code} {e.key}: {e}")
        time.sleep(e.retry_after or 3)  # сбой сети или 429
        continue
    except requests.RequestException:
        time.sleep(3)
        continue
    for u in updates:
        offset = u["update_id"] + 1
        try:
            handle(u)
        except (ApiError, requests.RequestException) as e:  # 403 от заблокировавшего и т. п. не роняет программу
            print(f"событие {u['update_id']}: {e}", file=sys.stderr)

Полные примеры — эхо фото и файлов, кнопки со счётчиком, меню команд на трёх языках, ключ повтора, разбор каждой ошибки: full.mjs (Node) и full.py (Python).

curl

curl -H "Authorization: Bearer $FB_BOT_TOKEN" https://falconblack.org/bot-api/v1/getMe

curl -H "Authorization: Bearer $FB_BOT_TOKEN" -H "Content-Type: application/json" \
     -d '{"chat_id": 4815162342108, "text": "Привет!"}' \
     https://falconblack.org/bot-api/v1/sendMessage

curl -H "Authorization: Bearer $FB_BOT_TOKEN" -F chat_id=4815162342108 -F caption="Отчёт" \
     -F document=@отчёт.pdf https://falconblack.org/bot-api/v1/sendDocument

Профиль бота

То, что люди видят в профиле бота, в поиске и в пустой переписке: имя (до 64 знаков), картинка, «О боте» (до 120 знаков — в профиле и в поиске) и «Что умеет этот бот» (до 512 знаков — в пустой переписке, до «Начать»). Всё это меняется в @botnest и в приложении: «Мои боты» → бот → «Информация». Имя и тексты не должны выдавать бота за Falcon Black.

Команды

Команда — слово после «/», которое человек выбирает в меню бота: до 100 команд, каждая — от 1 до 32 знаков (латиница, цифры и «_» или кириллица), описание — до 256 знаков. Меню задаётся в @botnest, в приложении («Мои боты» → бот → «Команды») или программой: setMyCommands — в том числе для отдельного языка (language_code) и для групп (scope).

Методы

Параметры — snake_case; числа можно строкой, булевы — true/false или "1"/"0"; reply_markup, commands, scope, allowed_updates — объектом или строкой JSON. Незнакомые параметры пропускаются молча. chat_id — номер человека у вашего бота (он же номер переписки) или отрицательный номер группы или канала (группы и каналы).

МетодЧто делает
getMeВаш бот: id, first_name, username; can_join_groups (можно ли добавлять в группы и каналы), can_read_all_group_messages (приватность выключена — так бот будет видеть в группах, куда его добавят теперь); falcon_trust (new / normal) и falcon_trust_until; has_main_web_app — у бота есть главное мини-приложение
getUpdatesСобытия: offset, limit (1–100), timeout (0–50 с), allowed_updates. Второй опрос вытесняет первый (тот получает 409)
sendMessagechat_id, text (до 4096 знаков UTF-16), parse_mode, reply_markup, reply_parameters (ответ на сообщение), disable_notification, protect_content (во всех send*: запрет пересылать и сохранять это сообщение — пересылку в другие переписки не пропускает сервер, в приложениях нет кнопок пересылки, копирования и сохранения; у сообщения — has_protected_content: true)
sendPhotophoto — файл загрузкой (multipart, до 10 МБ) или file_id; caption до 1024. Снимок пережимается, данные снимка (место съёмки) срезаются
sendDocumentdocument — файл загрузкой (до 20 МБ) или file_id любого вида (фото, видео, голосовое уйдут файлом)
sendVideovideo — MP4 или MOV загрузкой (до 20 МБ) или file_id видео; caption, duration, width, height. Формат проверяется по содержимому файла
sendAudioaudio — MP3, M4A (AAC), WAV или FLAC (до 20 МБ) или file_id; title, performer, duration, caption. OGG пока нет — iPhone его не играет
sendVoicevoice — голосовое M4A (AAC) или MP3 (до 20 МБ) или file_id; duration. OGG/Opus пока нет — отправьте sendDocument
sendStickersticker — только из наших наборов: file_id стикера, который прислал человек, или из getStickerSet. Свой файл и адрес — нельзя, кнопок под стикером пока нет
getStickerSetname — короткое имя открытого набора (из ссылки на набор); стикеры с file_id для sendSticker
sendLocationlatitude, longitude, horizontal_accuracy (0–1500 м). Живое место (live_period) — пока нет
sendContactphone_number, first_name, last_name; людям приходит текстом «имя, номер» (своего вида «контакт» в приложениях нет); vcard не учитывается
setMessageReactionРеакция бота на сообщение своей переписки: reaction — [{"type":"emoji","emoji":"👍"}], одна; пустой список — снять. Один эмодзи, в том числе флаг или клавиша с цифрой; у бота — одна реакция на сообщение. В канале — только реакции, которые разрешил владелец, и не больше разных на запись, чем задано (getChat: available_reactions, max_reaction_count). Пределы — как у правок
editMessageText, editMessageReplyMarkupТолько свои сообщения; правка одних кнопок — без пометки «изменено»
deleteMessageСвои сообщения моложе 48 ч (сообщения человека в личной переписке — его, бот их не удаляет); свои записи канала — в любое время; чужие в группе или канале — с правом can_delete_messages
answerCallbackQueryОтвет на нажатие; show_alert: true с text — человек получит этот текст сообщением
sendChatActiontyping — «печатает…» около 5 с
setMyCommands, getMyCommands, deleteMyCommandsМеню команд: до 100, команда — латиница или кириллица (одна азбука), строчные, цифры, _, до 32 знаков; scope, language_code. Через API ничего не «исправляется»: /Всё — command_invalid
getChatПереписка с человеком: id, type: "private", first_name; группа или канал: type: "group" / "channel", title, has_protected_content (владелец запретил пересылать и сохранять), falcon_join_by_request (вход по любой ссылке — только по заявке)
setWebhook, deleteWebhook, getWebhookInfoВебхук: события приходят POST-запросом на ваш адрес (ниже). url, secret_token, max_connections (до 10), allowed_updates, drop_pending_updates; пустой url — снять
setChatMenuButton, getChatMenuButtonКнопка у поля ввода: команды или мини-приложение (ниже)
answerWebAppQueryОтвет мини-приложения, открытого из переписки: одно сообщение бота (ниже)

Вебхук

// Node.js 20+: приёмник вебхука. Свой https-сервер или прокси перед ним (порт 443, 88 или 8443).
import http from "node:http";
const SECRET = process.env.FB_WEBHOOK_SECRET;            // тот же, что в setWebhook
http.createServer((req, res) => {
  // Сверяйте НАШ заголовок сами: готовые библиотеки ищут заголовок с другим именем.
  if (req.headers["x-falcon-bot-api-secret-token"] !== SECRET) { res.writeHead(401).end(); return; }
  let body = "";
  req.on("data", (c) => (body += c));
  req.on("end", () => {
    const update = JSON.parse(body);                     // update_id — и в заголовке Falcon-Update-Id
    res.writeHead(200).end();                            // 2xx за 10 с — доставлено; работу — после ответа
    handle(update);
  });
}).listen(8080);                                         // за прокси с https

// Один раз, при запуске программы:
// curl -H "Authorization: Bearer $FB_BOT_TOKEN" -d url=https://bot.example.org/hook \
//      -d secret_token=$FB_WEBHOOK_SECRET https://falconblack.org/bot-api/v1/setWebhook

События

{"update_id": 1, "my_chat_member": {"chat": {"id": 4815162342108, "type": "private", "first_name": "Маша"},
  "from": {...}, "date": 1790000000,
  "old_chat_member": {"status": "left", "user": {<бот>}}, "new_chat_member": {"status": "member", "user": {<бот>}}}}
{"update_id": 2, "message": {"message_id": 1, "text": "/start", "entities": [{"type": "bot_command", "offset": 0, "length": 6}], ...}}
{"update_id": 3, "message": {"message_id": 2, "date": 1790000000,
  "chat": {"id": 4815162342108, "type": "private", "first_name": "Маша"},
  "from": {"id": 4815162342108, "is_bot": false, "first_name": "Маша", "language_code": "ru"},
  "text": "Привет"}}

Очередь событий хранится на сервере сутки и переживает его перезапуск; подтверждение — следующим getUpdates с offset = последний update_id + 1. Удалённое человеком до выдачи сообщение не отдаётся.

Кнопки

Новые приложения Falcon Black рисуют кнопки и клавиатуру-ответы сами. В старых сборках inline_keyboard человек видит списком с номерами под текстом, а ответ цифрой (или надписью кнопки) приходит программе нажатием callback_query:

Погода в Казани: +12, облачно.

1 — Обновить
2 — Завтра
3 — Карта: https://example.org/map
Ответьте цифрой.

Что бот знает о человеке

Ссылки

Ошибки

{"ok": true, "result": {...}}
{"ok": false, "error_code": 403, "description": "Forbidden: bot was blocked by the user",
 "error_key": "blocked"}
{"ok": false, "error_code": 429, "description": "Too Many Requests: retry after 3",
 "error_key": "too_many", "parameters": {"retry_after": 3}}

HTTP-код равен error_code; description — по-английски; error_key — машинный код, его значения не меняются. В каждом ответе — заголовок X-Request-Id. Ответ не JSON — это сбой сети, повторите запрос.

Кодerror_keyКогда
400chat_not_foundНет такого человека у этого бота
400text_empty, too_longПустой текст; длиннее 4096 (подпись — 1024)
400param_missing, param_invalid, bad_jsonНет параметра, не тот тип, сломанный JSON
400param_unsupported, markup_unsupportedПока не поддержано: файл по адресу, login_url…
400content_protected, join_request_not_foundФайл по file_id из переписки, где запрещено пересылать и сохранять, — в другую переписку (бот там не начальник); заявки нет (уже рассмотрели или отменили)
400webapp_not_registered, webapp_in_group, key_leakАдрес кнопки web_app не ведёт на ваше мини-приложение; кнопка web_app в группе или канале; ключ бота в адресе (ключ отозван)
400query_invalid, query_no_chat, text_reservedanswerWebAppQuery: запрос чужой, истёк или уже отвечен; приложение открыто не из переписки; текст кнопки меню выдаёт себя за Falcon Black
400reply_not_found, edit_not_found, not_modified, delete_forbiddenСообщение не найдено, не изменилось, удалить нельзя
400file_invalidЧужой или испорченный file_id
400callback_expiredНажатию больше 15 минут или чужой номер
401token_*, key_in_urlКлюч (см. выше)
403blocked, user_deletedЧеловек заблокировал бота; удалил учётку
403links_new_botСсылка от нового бота не хозяину (в группе и канале — любая)
403bot_kicked, not_member, cant_initiate, not_enough_rightsБота убрали из группы или канала; бот вышел сам; человек из группы не писал боту лично; у бота нет нужного права
400user_not_found, member_list_inaccessible, user_is_admin, private_chatБот не видел этого человека в группе; участники канала скрыты; начальника убрать нельзя; метод только для групп и каналов
404method_unknown, method_unsupportedОпечатка в методе; метода пока нет (в описании — чем заменить)
400bad_webhook, file_formatАдрес вебхука не подходит; файл не того формата (видео, звук, голосовое)
409poll_replaced, webhook_activeЗапущена вторая копия программы; getUpdates при вебхуке
413file_too_bigФайл или тело больше предела
422idempotency_mismatchТот же ключ повтора с другим телом
429too_manyПредел; ждать parameters.retry_after секунд (и заголовок Retry-After)
503not_configuredБоты на сервере временно выключены

Ключ повтора: заголовок Idempotency-Key или параметр falcon_idempotency_key (1–64 знака A-Z a-z 0-9 _ -) у всех send*. Повтор с тем же ключом вернёт то же сообщение (заголовок Idempotent-Replayed: true), второго не будет.

Пределы

ЧтоНовый бот (первые 3 дня)Обычный
Отправка (send*) всего10 в секунду, 300 в минуту30 в секунду, 1200 в минуту
Отправка в одну переписку1 в секунду, всплеск до 5
Правки и удаления в одну переписку3 в секунду
Прочие вызовы30 в секунду50 в секунду
Текст / подпись4096 / 1024 знака (UTF-16, после снятия разметки)
Файлыфото 10 МБ, файл, видео, звук и голосовое — 20 МБ; JSON и форма — 128 КБ
Вебхукдо 10 соединений; на один чужой сайт от всех ботов — 20 запросов разом и 30 в секунду
Опрос getUpdatesодин на бота, timeout до 50 с; 30 вызовов в секундуодин на бота, timeout до 50 с; 50 вызовов в секунду
Загрузка файлов (multipart)не больше 2 одновременно на ключ
Запросы с неверным ключом60 в минуту с адреса, дальше 429 на 10 минут

Отказ всегда явный — 429 с retry_after; молча в очередь ничего не ставится. Уведомления людям от бота приходят как от обычного собеседника, но в одну переписку — не чаще раза в 10 секунд.

Если переносите бота с другой платформы

ЧтоУ нас
КлючТа же форма «номер:секрет», но только в заголовке Authorization: Bearer на /bot-api/v1/; в адресе — отключается
Где работают ботыЛичная переписка, группы и каналы; в канале бот только начальник. Режим приватности в группе — каким был в момент добавления; бот-начальник получает все сообщения
Получение событийgetUpdates или вебхук; секрет — в заголовке X-Falcon-Bot-Api-Secret-Token
Номер человекаСвой у каждого бота; ника, фото и телефона нет
Первое сообщениеПрограмма сначала получает my_chat_member и /start; ссылка с параметром — falconblack.org/bot/<ник>?start=…
КнопкиВ новых приложениях — кнопки; в старых — список с номерами, нажатие — ответ цифрой (falcon_via)
Разметка текстаПринимается и снимается
ФайлыЗагрузкой или file_id; по адресу — пока нет; getFile — пока нет; голосовые и звук в OGG — пока нет (M4A, MP3)
КонтактПриходит людям текстом; стикеры — только из наших наборов
Удалить сообщение человекаВ личной переписке — нельзя; в группе и канале — с правом can_delete_messages
Ссылки у нового ботаПервые 3 дня — только хозяину
Мини-приложенияТолько зарегистрированные адреса, кнопки — в личной переписке, человек сначала разрешает; отличия ниже
Нет (404 с подсказкой)Встроенный режим, платежи, игры, опросы, пересылка, живое место, sendAnimation, sendVideoNote, sendMediaGroup

Группы и каналы

Бота добавляет начальник группы или канала — из профиля бота («Добавить в группу или канал») или по ссылке (ниже). Автор бота может это запретить: /setjoingroups в @botnest или «Мои боты» в приложении; запрет не убирает бота оттуда, где он уже есть. В группе бот — обычный участник (его видят все, и старые версии приложения), в канале бот всегда начальник.

{"update_id": 7, "my_chat_member": {"chat": {"id": -2251799813685, "type": "group", "title": "Семья"},
  "from": {"id": 4815162342108, "is_bot": false, "first_name": "Маша"}, "date": 1790000000,
  "old_chat_member": {"status": "left", "user": {<бот>}},
  "new_chat_member": {"status": "member", "user": {<бот>}}}}
{"update_id": 8, "message": {"message_id": 41, "date": 1790000060,
  "chat": {"id": -2251799813685, "type": "group", "title": "Семья"},
  "from": {"id": 4815162342108, "is_bot": false, "first_name": "Маша"},
  "text": "/weather@weather_kzn_bot", "entities": [{"type": "bot_command", "offset": 0, "length": 24}]}}
{"update_id": 9, "channel_post": {"message_id": 7, "date": 1790000120,
  "chat": {"id": -3377699720527, "type": "channel", "title": "Новости района"},
  "sender_chat": {"id": -3377699720527, "type": "channel", "title": "Новости района"}, "text": "Завтра ярмарка"}}

Что бот получает в группе: режим приватности

РежимЧто приходит программе
Приватность включена (так у всех новых ботов)/команда@ваш_бот; просто /команда — если из ботов группы ваш последним писал в неё; ответы (цитаты) на сообщения бота; служебные события. Упоминание @ника без команды и без ответа не приходит
Приватность выключенаВсе сообщения группы
Бот — начальник группыВсе сообщения группы при любом режиме

Каналы

curl -H "Authorization: Bearer $FB_BOT_TOKEN" -H "Content-Type: application/json" \
     -d '{"chat_id": -3377699720527, "text": "Свежий выпуск",
          "reply_markup": {"inline_keyboard": [[{"text": "Читать", "url": "https://example.org/news/1"}]]}}' \
     https://falconblack.org/bot-api/v1/sendMessage

Пример: новостной бот — сам публикует новости из ленты RSS или Atom по таймеру с кнопкой «Читать», правит свою запись, если новость в ленте поменялась, удаляет старые; номер канала узнаёт из my_chat_member: news.mjs (Node.js 20+) и news.py (Python 3.9+ с requests).

Права бота-начальника

Те же имена, что в ChatAdministratorRights. В группе бот становится начальником, если ему дали хоть одно право (или просто «сделать начальником»). Права проверяются при каждом вызове: начальник группы их поменял — действует сразу, а программа получает my_chat_member с новыми правами.

ПравоГруппаКаналЧто разрешает
can_change_infoдадаsetChatTitle, setChatDescription, setChatPhoto, deleteChatPhoto
can_delete_messagesдадаУдалять чужие сообщения (в канале — любые записи)
can_restrict_membersда—banChatMember, unbanChatMember
can_invite_usersдадаexportChatInviteLink, createChatInviteLink, editChatInviteLink, revokeChatInviteLink, approveChatJoinRequest, declineChatJoinRequest
can_pin_messagesда—pinChatMessage, unpinChatMessage, unpinAllChatMessages
can_promote_membersдадаpromoteChatMember — с правами не больше своих
is_anonymousда—Сообщения бота подписаны группой
can_post_messages—даПубликовать записи
can_edit_messages—даПравить чужие записи; закреплять

setMyDefaultAdministratorRights (for_channels: true — для каналов) — какие права программа просит по умолчанию: приложение отмечает их на листе прав, когда бота добавляют. getMyDefaultAdministratorRights — прочитать.

События групп и каналов

Методы для групп и каналов

МетодЧто делает
getChat, getChatMemberCountНазвание и вид; число людей (боты не считаются)
getChatMemberСам бот или человек, которого бот уже видел в этой переписке; в канале — только сам бот
getChatAdministratorsВ группе — начальники-люди и боты-начальники; в канале — только боты (начальники канала скрыты от подписчиков)
leaveChatВыйти самому
pinChatMessage, unpinChatMessage, unpinAllChatMessagesС правом can_pin_messages (в канале — can_edit_messages)
banChatMember, unbanChatMember, promoteChatMemberС правами can_restrict_members и can_promote_members
setChatTitle, setChatDescription, setChatPhoto, deleteChatPhotoС правом can_change_info
exportChatInviteLink, createChatInviteLink, editChatInviteLink, revokeChatInviteLinkС правом can_invite_users. Бот работает только со своими ссылками. exportChatInviteLink — своя главная ссылка бота (is_primary: true; прежняя отзывается; отозвали главную — новая заводится сама). createChatInviteLink — с name (до 32 знаков, видно только администраторам), expire_date, member_limit (1–99 999). editChatInviteLink — меняет только присланные поля: expire_date: 0 снимает срок, member_limit: 0 — предел, name: "" — название; главную ссылку не правят. Действующих дополнительных ссылок — до 30 на переписку. creates_join_request: true — вход по ссылке только по заявке (вместе с member_limit нельзя; у публичного канала — нельзя); в editChatInviteLink не передан — не меняется. У ссылки — pending_join_request_count
approveChatJoinRequest, declineChatJoinRequestС правом can_invite_users: принять или отклонить заявку (chat_id, user_id — номер человека у бота из chat_join_request). Отклонённый сможет подать снова через сутки; заявки без ответа исчезают через 30 дней
setMyCommandsОбласти для групп: all_group_chats, all_chat_administrators, chat и chat_administrators с номером группы

Пределы в группах и каналах

ЧтоПредел
Сообщений от бота в одну группу20 в минуту (вдобавок к 1 в секунду со всплеском до 5)
Сообщений от всех ботов одной группы60 в минуту
Записей от бота в один канал20 в минуту
Ботов в одной переписке10 в группе, 5 в канале
Событий в очереди бота10 000; сверх — первыми выбрасываются старые сообщения групп, my_chat_member — никогда

Что видят участники

Бот — программа другого человека: всё, что бот видит в группе, получает его автор. Поэтому по умолчанию бот видит только обращённое к нему, а участники видят, когда бот получает все сообщения.

Мини-приложения

Мини-приложение — ваш сайт, который открывается внутри Falcon Black: шторкой поверх переписки, на весь экран или во весь экран для игр. Человек открывает его из профиля бота, кнопкой под сообщением, кнопкой у поля ввода, ячейкой клавиатуры или по ссылке. При первом открытии он сам разрешает передать сайту своё имя и язык; ник, номер телефона, контакты и переписки сайт не получает. Сверху всегда видны название приложения и адрес сайта — их показывает Falcon Black, страница их не закрывает.

  1. В @botnest: /newapp — бот → название → короткое имя (часть ссылки, потом не меняется) → адрес https:// → описание → значок → адрес политики конфиденциальности → возраст. То же — в новых приложениях: «Мои боты» → «Мини-приложения». Список — /myapps, правка — /editapp (снимки для профиля, как открывать, другие адреса сайта), удалить — /deleteapp.
  2. Адрес — только https, порт 443, имя (не IP), сайт в открытом интернете. Адреса Falcon Black и местные адреса не подходят. Другие адреса сайта, куда приложение может переходить, — до 5 хостов (/editapp → «Другие адреса сайта» или /setdomain).
  3. Ссылка на приложение: https://falconblack.org/bot/<ник бота>/<короткое имя>; с параметром — ?startapp=<параметр> (до 512 знаков A-Z a-z 0-9 _ -, приходит в start_param). Главное приложение бота — https://falconblack.org/bot/<ник>?startapp. Такие ссылки можно присылать и в сообщениях бота.
  4. Первые 3 дня мини-приложения нового бота открываются только у его хозяина.

SDK

Подключите SDK в <head> страницы, до своего кода. Версионный файл не меняется никогда — поэтому integrity; /sdk/mini-app.js — последний 1.x (без integrity).

<script src="https://falconblack.org/sdk/mini-app-1.0.0.js" integrity="sha384-iPxcV8KtBhjKcb6hdeKa5FnZRrlW+kJU+AYoR4BKChP9HgNjye3Giaz2muZbzZ8B" crossorigin="anonymous"></script>

Объект — window.FalconBlack.WebApp (то же — window.Falcon.WebApp). Методы возвращают обещание; отказ — Error с name = "FalconWebAppError" и code (unsupported, not_allowed, busy, cancelled, too_large, rate_limited). Вне Falcon Black (в обычном браузере) initData пустая, а методы приложения отказывают unsupported. SDK ничего не пишет ни в localStorage, ни в cookie.

ЧтоКак
Готово, закрыть, развернутьready() — когда страница нарисована (до этого видна заглушка с вашим значком); close(); expand(); requestFullscreen() / exitFullscreen()
Главная кнопкаMainButton.setText("Оформить").show(), onClick(fn), enable() / disable(), showProgress() / hideProgress(), setParams({text, color, text_color, is_visible, is_active})
НазадBackButton.show() / hide(), onClick(fn) — вместо «Отмена» в шапке появляется «‹ Назад»
ОкнаshowPopup({title, message, buttons}) (до 3 кнопок), showAlert(text), showConfirm(text) — наши окна с названием и адресом вашего сайта в заголовке
ОткликHapticFeedback.impactOccurred("light"), notificationOccurred("success"), selectionChanged()
Тема и размерыthemeParams, colorScheme, viewportHeight, viewportStableHeight, safeAreaInset, contentSafeAreaInset; CSS-переменные --fb-theme-bg-color, --fb-viewport-height, --fb-safe-area-inset-top…; события onEvent("themeChanged" | "viewportChanged" | …, fn)
ЦветаsetHeaderColor, setBackgroundColor, setBottomBarColor (#rrggbb или ключ темы); enableClosingConfirmation()
СсылкиopenLink(url) — внешний сайт после подтверждения человеком; openFalconLink(url) — только /@ник, /bot/…, /j/…
Боту и в чатsendData(строка) (ниже), shareToChat({text, startParam}) — человек сам выбирает переписку и отправляет сообщение от себя; приложение не узнаёт, кому; requestWriteAccess() — разрешить боту писать человеку
ПроверкаisSupported("sendData"), isVersionAtLeast("1.0"), platform (ios, android, macos)

Камеры, микрофона, места, контактов и номера телефона у мини-приложений в этой версии нет. Пример страницы — miniapp-example.html.

Как сервер приложения узнаёт человека: initData

WebApp.initData — строка формы (application/x-www-form-urlencoded). Отправьте её на свой сервер в теле запроса (не в адресе: адреса оседают в журналах) и проверьте там. initDataUnsafe — те же поля объектом, для показа на экране; решения по нему без проверки не принимайте.

auth_date=1767225600
chat_instance=4815162342
chat_type=private
falcon_app=shop
query_id=AgkQFx4lLDM6QUhPVl1ka3J5gIc
user={"id":4815162342,"is_bot":false,"first_name":"Анна","language_code":"ru","allows_write_to_pm":true}
signature=D0MD7q5YPfPXAYSUqYEeKQNaGELaM5L2wbT6VOW8Gf3OXTT-XY4vr9Dig8x4cpjIavTTdU6q_VQiSDwFZFsUDQ
hash=c1fc345d429647abd3ef6b19875af9b209fb854d9140b31c4ad0055fed761a1e
ПолеЧто
userJSON: id — номер человека у вашего бота (тот же, что в Bot API; у двух ботов — разный), first_name, language_code (если известен), allows_write_to_pm — бот может ему писать
auth_dateВремя открытия, секунды
query_idНомер открытия — для answerWebAppQuery
chat_type, chat_instanceТолько если открыто из переписки с ботом: private и номер переписки (как chat_instance у callback_query)
start_paramПараметр из ссылки ?startapp=
falcon_appКороткое имя приложения
signature, hashПодпись Falcon Black и HMAC ключом бота (ниже)

Ника, фото, номера телефона, настоящего id, контактов и названий переписок в initData нет никогда.

Строка проверки: все поля, кроме исключённых, по имени поля, каждое — имя=значение (значение раскодировано: + — пробел), через \n. Разбирайте только декодером форм (URLSearchParams, parse_qsl); поле дважды — строку отвергайте целиком.

WebAppData — постоянная строка схемы, а не имя: готовые библиотеки проверки работают без правок. Готовые проверки без библиотек: validate-initdata.mjs (Node.js 18+) и validate-initdata.py (Python 3.9+, cryptography или PyNaCl); векторы для своей реализации — initdata-vectors.json (подписаны учебным ключом, на боевом его нет).

import { validateInitData, fetchPublicKeys } from "./validate-initdata.mjs";

const keys = await fetchPublicKeys();      // /sdk/keys.json: держите в памяти, обновляйте раз в час
// initData — из ТЕЛА запроса страницы, не из адреса
const r = validateInitData(initData, { botToken: process.env.FB_BOT_TOKEN, publicKeys: keys });
if (!r.ok) { res.writeHead(401).end(); return; }
const person = r.data.user;                 // {id, first_name, language_code?, allows_write_to_pm}

Кнопки web_app

{"chat_id": 4815162342108, "text": "Каталог открыт",
 "reply_markup": {"inline_keyboard": [[{"text": "Открыть магазин", "web_app": {"url": "https://shop.example.com/item/5"}}]]}}

{"chat_id": 4815162342108, "text": "Что посмотрим?",
 "reply_markup": {"keyboard": [[{"text": "Каталог", "web_app": {"url": "https://shop.example.com/catalog"}}, "Помощь"]],
                  "resize_keyboard": true}}

setChatMenuButton — кнопка у поля ввода в переписке с ботом: вместо «/ Меню» новые приложения показывают кнопку вашего мини-приложения (команды остаются в подсказках «/»). Без chat_id — для всех; с номером человека — только в его переписке. Текст — 1–64 знака (выдаёт себя за Falcon Black — 400 text_reserved); адрес — по правилам кнопок web_app. getChatMenuButton {chat_id?} — что задано. То же — /setmenubutton в @botnest и «Мои боты».

{"menu_button": {"type": "web_app", "text": "Магазин", "web_app": {"url": "https://shop.example.com/"}}}
{"chat_id": 4815162342108, "menu_button": {"type": "commands"}}     // только в этой переписке
{"chat_id": 4815162342108, "menu_button": {"type": "default"}}      // вернуть кнопку бота

Данные боту: sendData и web_app_data

Мини-приложение, открытое ячейкой клавиатуры, может один раз вызвать WebApp.sendData(строка) (1–4096 байт UTF-8, не чаще 10 раз в минуту на человека): окно закрывается, а программа получает message с полем web_app_data и без text — и опросом, и вебхуком (в allowed_updates это message). В переписке ничего не появляется. Человек остановил бота — данные не уходят.

{"update_id": 9, "message": {"message_id": 41, "date": 1790000000,
  "chat": {"id": 4815162342108, "type": "private", "first_name": "Анна"},
  "from": {"id": 4815162342108, "is_bot": false, "first_name": "Анна", "language_code": "ru"},
  "web_app_data": {"data": "{\"item\":\"tea\"}", "button_text": "Каталог"}}}

answerWebAppQuery

Мини-приложению, открытому из переписки с ботом (кнопкой под сообщением, кнопкой меню или ячейкой клавиатуры), ваш сервер может ответить один раз, пока открытие живо (час): человек получит одно обычное сообщение бота с уведомлением, программа события о нём не получает. result — только статья с текстом (input_message_content.message_text, parse_mode, кнопки inline_keyboard); текст и кнопки — по всем правилам sendMessage. Ответ — {"falcon_message_id": N}.

{"web_app_query_id": "AgkQFx4lLDM6QUhPVl1ka3J5gIc",
 "result": {"type": "article", "id": "1", "title": "Заказ",
            "input_message_content": {"message_text": "Заказ №42 принят: чай, 250 ₽"},
            "reply_markup": {"inline_keyboard": [[{"text": "Открыть заказ", "web_app": {"url": "https://shop.example.com/order/42"}}]]}}}
→ {"ok": true, "result": {"falcon_message_id": 57}}

Ошибки: 400 query_invalid — запрос чужой, истёк или уже отвечен; 400 query_no_chat — приложение открыто не из переписки (из профиля, списка или по ссылке); 403 cant_initiate — человек не запускал бота или остановил его: открытие мини-приложения не даёт боту права писать первым.

Чем отличается от других платформ

ЧтоУ нас
answerWebAppQueryОбычное сообщение бота в личной переписке; ответ {"falcon_message_id"}, inline_message_id нет
Кнопки web_appТолько адреса зарегистрированного приложения и только в личной переписке
Встроенный режимНет (switch_inline_query, подготовленные сообщения — потом)
initDataЧеловек сначала разрешает; своё поле falcon_app; кроме hash — signature (Ed25519, без ключа бота); user.id — номер у вашего бота
Возможности страницыБез камеры, микрофона, места, контактов, оплаты внутри; у каждой учётки — свои данные сайта

Пределы

ЧтоПредел
Мини-приложений у бота10; снимков для профиля — 5 (до 10 МБ каждый)
Открытий30 в минуту и 300 в час на человека
sendDataодин раз за открытие с клавиатуры, до 4096 байт, 10 в минуту на человека
answerWebAppQueryодин раз за открытие, в течение часа
Свежесть initDataчас (жёстко — сутки)

Правила для авторов мини-приложений

  1. Не подражайте виду Falcon Black: шапку, окна и кнопки Falcon Black рисует сам, с названием и адресом вашего сайта.
  2. Никогда не спрашивайте пароль или код входа Falcon Black.
  3. Не продавайте цифровые товары и услуги пользователям iPhone (правило App Store 3.1.1); физические товары можно продавать через свою оплату на сайте.
  4. Политика конфиденциальности обязательна. Удаляйте данные человека по его просьбе.
  5. Возраст указывайте честно.
  6. На каждое приложение можно пожаловаться прямо в нём. Нарушит правила — приложение отключим; повторится — бота тоже.

Правила для авторов ботов

  1. Бот не выдаёт себя за Falcon Black, за другого человека или организацию.
  2. Бот не рассылает рекламу и не пишет тем, кто не просил.
  3. Бот не выманивает пароли, коды входа, данные карт и деньги обманом.
  4. Всё, что люди пишут боту, — чужое. Храните бережно, никому не показывайте и удаляйте по просьбе человека.
  5. Ключ — как пароль. Мы его никогда не спрашиваем.
  6. За бота отвечает его хозяин. Нарушит правила — бота отключим; повторится — создавать ботов будет нельзя.

Полностью — falconblack.org/bots/rules.

Политика конфиденциальности бота: адрес — «Мои боты» → бот → «Настройки бота»; без него действует стандартная политика Falcon Black для ботов.

Чего пока нет

Встроенный режим, передача бота другому хозяину, скачивание файлов (getFile), отправка файла по адресу, живое место, обсуждения под записями канала. На эти вызовы — понятный отказ. Управлять ботом можно в @botnest и в новых приложениях: «Мои боты» — имя, описание, картинка, команды, ключ, группы и приватность, мини-приложения. Вопросы и пожелания — в @botnest: /help.