Почему Telegram, а не свой фронтенд

Telegram — лучший UI для персональной автоматизации и большинства внутренних инструментов. Причины простые: приложение уже стоит на телефоне, не нужно делать авторизацию, push-уведомления работают из коробки, поддерживаются кнопки и структурированные ответы.

Альтернатива — веб-интерфейс или мобильное приложение — требует в разы больше работы и поддержки. Для инструментов, которыми пользуется один-два человека, это оверинжиниринг.

Polling vs Webhook

Polling — бот сам периодически спрашивает Telegram API: «есть новые сообщения?» Просто в реализации, работает за NAT, не нужен публичный IP. Недостаток: задержка (обычно 1–3 секунды) и лишние запросы.

Webhook — Telegram сам отправляет сообщения на ваш сервер при поступлении. Нужен HTTPS-эндпоинт с валидным SSL (Let's Encrypt подходит). Задержка минимальная, нагрузка меньше. Это предпочтительный вариант для production.

# Регистрация webhook (curl)
curl -F "url=https://example.com/bot/webhook" \
     https://api.telegram.org/bot{TOKEN}/setWebhook

# Проверка
curl https://api.telegram.org/bot{TOKEN}/getWebhookInfo

# Удаление webhook (вернуться к polling)
curl https://api.telegram.org/bot{TOKEN}/deleteWebhook

Выбор библиотеки

Две основные библиотеки для Python:

python-telegram-bot — классика, богатая документация, синхронный и асинхронный режимы. Хороший выбор для начала. Версия 20+ полностью асинхронная.

aiogram — чисто асинхронная, более идиоматична для asyncio, лучше подходит для высоконагруженных ботов. Синтаксис с декораторами и роутерами удобен для сложной логики.

# Установка aiogram 3.x
pip install aiogram

# Установка python-telegram-bot 20+
pip install python-telegram-bot

Структура бота-инструмента на aiogram

Минимальная структура, которая не превращается в спагетти при росте:

project/
  bot.py          # точка входа, запуск
  config.py       # токен, допустимые user_id, настройки
  handlers/
    __init__.py
    commands.py   # /start, /help и т.д.
    messages.py   # обработка текста
    callbacks.py  # нажатия на инлайн-кнопки
  services/
    __init__.py
    my_service.py # бизнес-логика отдельно от хендлеров
  keyboards/
    __init__.py
    inline.py     # построение клавиатур
# bot.py
import asyncio
from aiogram import Bot, Dispatcher
from aiogram.fsm.storage.memory import MemoryStorage
from config import TOKEN
from handlers import commands, messages

async def main():
    bot = Bot(token=TOKEN)
    dp = Dispatcher(storage=MemoryStorage())

    dp.include_router(commands.router)
    dp.include_router(messages.router)

    # Удалить старые апдейты при старте
    await bot.delete_webhook(drop_pending_updates=True)
    await dp.start_polling(bot)

if __name__ == "__main__":
    asyncio.run(main())
# handlers/commands.py
from aiogram import Router
from aiogram.filters import Command
from aiogram.types import Message
from config import ALLOWED_USERS

router = Router()

# Фильтр по user_id — обязательно для личных инструментов
def is_allowed(message: Message) -> bool:
    return message.from_user.id in ALLOWED_USERS

@router.message(Command("start"))
async def cmd_start(message: Message):
    if not is_allowed(message):
        return
    await message.answer(
        "Готов к работе.\n\n"
        "/status — статус сервисов\n"
        "/logs — последние логи\n"
        "/deploy — деплой"
    )

Инлайн-кнопки и callback

# keyboards/inline.py
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder

def confirm_keyboard(action: str, item_id: str) -> InlineKeyboardMarkup:
    builder = InlineKeyboardBuilder()
    builder.button(text="✓ Подтвердить", callback_data=f"confirm:{action}:{item_id}")
    builder.button(text="✗ Отмена", callback_data="cancel")
    builder.adjust(2)
    return builder.as_markup()

# handlers/callbacks.py
from aiogram import Router, F
from aiogram.types import CallbackQuery

router = Router()

@router.callback_query(F.data.startswith("confirm:"))
async def handle_confirm(callback: CallbackQuery):
    _, action, item_id = callback.data.split(":", 2)
    # выполнить действие...
    await callback.answer("Выполнено")
    await callback.message.edit_text(f"Действие {action} выполнено для {item_id}")

Отправка уведомлений без ответа на сообщение

Боты часто используются для push-уведомлений из других систем: мониторинг упал, деплой завершился, задача выполнена. Для этого нужен только токен и chat_id:

import httpx

async def send_alert(token: str, chat_id: int, text: str):
    url = f"https://api.telegram.org/bot{token}/sendMessage"
    async with httpx.AsyncClient() as client:
        await client.post(url, json={
            "chat_id": chat_id,
            "text": text,
            "parse_mode": "HTML"
        })

# Использование из любого места:
await send_alert(TOKEN, MY_CHAT_ID, "⚠️ Сервис упал: api.example.com")

Типичные применения

Запуск через systemd

# /etc/systemd/system/mybot.service
[Unit]
Description=My Telegram Bot
After=network.target

[Service]
Type=simple
User=botuser
WorkingDirectory=/opt/mybot
ExecStart=/opt/mybot/venv/bin/python bot.py
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
systemctl enable mybot
systemctl start mybot
journalctl -u mybot -f  # логи в реальном времени