# Эхо-бот 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)