Справка

Пример расширения Чат на Python

Рассмотрим создание простого расширения Pyrus, реализующего интеграцию с Telegram через бота. Расширение позволит создавать задачи в Pyrus с внешним каналом для переписки с пользователем Telegram. Получаемые Telegram-ботом сообщения от разных пользователей будут приходить в отдельные задачи в Pyrus.

Для запуска примеров кода потребуется Python версии не ниже 3.8.

Подготовка окружения

Создадим папку проекта и виртуальное окружение для Python. Для этого выполните в консоли следующие команды:

  • для Windows (PowerShell)

    mkdir pyrus-telegram-extension
    cd pyrus-telegram-extension
    python -m venv venv
    .\venv\scripts\activate
    
  • для Linux/macOS

    mkdir pyrus-telegram-extension
    cd pyrus-telegram-extension
    python -m venv venv
    source .venv/bin/activate
    

Разработка

Будем использовать следующие библиотеки:

  • FastAPI — для обработки вебхуков от Pyrus и Telegram.

  • Pydantic — для описания запросов от Pyrus и их десериализации.

  • Aiogram — для работы с чат-ботами Telegram.

  • Uvicorn — реализация асинхронного веб-сервера для работы с FastAPI.

Для установки этих зависимостей создайте в текущей папке файл requirements.txt со следующим содержимым:

fastapi==0.110.1
pydantic==2.5.3
aiogram==3.4.1
uvicorn==0.29.0

Теперь установите зависимости командой:

pip install -r requirements.txt

Создайте файл main.py и добавьте следующие импорты:

from typing import List
from aiohttp import ClientSession
from fastapi import FastAPI, Request, status
from pydantic import BaseModel
from aiogram import Bot, Dispatcher
from aiogram.types import Update, Message

Создание расширения в Pyrus

Теперь создадим новое расширение в Pyrus, чтобы получить авторизационные параметры для доступа к Extensions API. Перейдите в Каталог расширений Pyrus и нажмите на вкладку Разработка расширений, затем Создать новое расширение.

Введите название расширения и нажмите Создать.

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

Скопируйте Логин и Секретный ключ в файл main.py в переменные CLIENT_ID и SECRET_KEY соответственно:

CLIENT_ID = "<Логин>"
SECRET_KEY = "<Секретный ключ>"

Обработка вебхуков Telegram

Добавьте функцию получения токена (через запрос token) для авторизации запросов к Extensions API:

async def fetch_token():
    async with ClientSession() as session:
        async with session.post(
                "https://extensions.pyrus.com/v1/token",
                json={
                    "client_id": CLIENT_ID,
                    "secret": SECRET_KEY,
                },
                headers={
                    "Content-Type": "application/json"
                }
        ) as response:
            return (await response.json()).get("access_token")

Затем создайте обработчик, который будет вызываться при получении нового сообщения ботом Telegram. При получении нового сообщения будем получать новый токен доступа и отправлять запрос getmessage в Extensions API для регистрации входящего сообщения (в виде новой задачи либо комментария в существующую задачу):

telegram = Dispatcher()

@telegram.message()
async def message_with_text(message: Message):
    access_token = await fetch_token()
    async with ClientSession() as session:
        await session.post(
            "https://extensions.pyrus.com/v1/getmessage",
            json={
                "account_id": str(message.bot.id),
                "channel_id": str(message.chat.id),
                "sender_name": message.from_user.full_name,
                "message_text": message.text,
            },
            headers={
                "Content-Type": "application/json",
                "Authorization": f"Bearer {access_token}"
            }
        )

Так же добавим обработчик вебхуков от Telegram. Для этого создадим инстанс FastAPI, который нам ещё понадобится позднее.

app = FastAPI()

@app.post("/webhook/bot/{bot_token}")
async def bot_webhook(bot_token: str, update: dict):
    bot = Bot(token=bot_token)
    telegram_update = Update(**update)
    await telegram.feed_update(bot=bot, update=telegram_update)
    await bot.session.close()

Обработка вебхуков Pyrus

Для обработки запросов от Pyrus потребуется реализовать следующие эндпоинты:

Для запросов authorize, toggle и sendmessage создадим модели данных, которые позволят автоматически десериализовывать запросы от Pyrus, а также добавим обработчики для каждого запроса:

class Credential(BaseModel):
    code: str
    value: str

class ToggleRequest(BaseModel):
    credentials: List[Credential]
    account_id: str
    enabled: bool

class AuthorizeRequest(BaseModel):
    credentials: List[Credential]

class SendMessageRequest(BaseModel):
    credentials: List[Credential]
    account_id: str
    channel_id: str
    message_text: str

@app.post("/authorize", status_code=status.HTTP_200_OK)
async def authorize(request: AuthorizeRequest):
    token = next(c.value for c in request.credentials 
                 if c.code == "bot_token")
    bot = Bot(token)
    try:
        response = await bot.get_me()
        return {
            "account_name": response.full_name, 
            "account_id": response.id
        }
    finally:
        await bot.session.close()

@app.post("/sendmessage", status_code=status.HTTP_200_OK)
async def send_message(request: SendMessageRequest):
    token = next(c.value for c in request.credentials 
                 if c.code == "bot_token")
    bot = Bot(token)
    try:
        await bot.send_message(
            chat_id=request.channel_id, 
            text=request.message_text
        )
    finally:
        await bot.session.close()

@app.post("/toggle", status_code=status.HTTP_200_OK)
async def toggle(request: ToggleRequest, raw_request: Request):
    token = next(c.value for c in request.credentials 
                 if c.code == "bot_token")
    bot = Bot(token=token)
    hostname = raw_request.url.hostname
    bot_webhook = f"https://{hostname}/webhook/bot/{token}"
    try:
        if request.enabled:
            await bot.set_webhook(url=bot_webhook)
        else:
            await bot.delete_webhook(drop_pending_updates=True)
    finally:
        await bot.session.close()

@app.get("/pulse", status_code=status.HTTP_200_OK)
async def pulse(): return {}

Тестирование

Запустите сервер с приложением локально, выполнив команду:

uvicorn main:app

Сервер запустится на порту 8000 и будет доступен по адресу http://127.0.0.1:8000:

INFO:     Started server process [14876]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

Проверьте, что сервер вернёт статус код 200 на запрос pulse, выполнив команду:

curl http://127.0.0.1:8000/pulse

Получение временного публичного адреса

Для того чтобы к приложению могли идти запросы из Pyrus или Telegram, нужно предоставить ему публичный адрес с протоколом https. При первичном тестировании можно запустить приложение локально и создать туннель с временным адресом с помощью сервиса serveo.net:

ssh -R 80:127.0.0.1:8000 serveo.net

В консоли отобразится временный публичный адрес, который нам понадобится далее:

Forwarding HTTP traffic from https://<random-unique-name>.serveo.net

Конфигурация расширения Pyrus в личном кабинете

Перейдите в настройки созданного расширения в Pyrus.

В качестве адреса веб-сервиса укажите публичный адрес, полученный на предыдущем шаге:

Выберите авторизацию по параметрам и добавьте параметр bot_token:

На следующем шаге включите дополнительную функцию Онлайн-чат:

На шаге Информация о расширении введите локализованное название для параметра bot_token (например, Токен бота), а также заполните остальные обязательные поля.

После этого нажмите Продолжить для сохранения настроек — теперь расширение можно установить в форму.

Создание бота в Telegram

Для создания нового бота в Telegram перейдите в бота @BotFather и выполните команду /newbot. Далее, следуя инструкциям BotFather, введите название бота и сохраните его токен доступа — он понадобится при подключении расширения в форму.

Установка расширения в форму

Нажмите кнопку Установить под иконкой расширения и выберите из списка любую доступную вам форму.

После этого вы попадете на страницу подключения расширения. Заполните поле Токен бота и нажмите Подключить аккаунт.

Активируйте расширение переведя флаг в поле Статус в активное положение.

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

Теперь перейдите в созданного телеграм-бота и отправьте любое текстовое сообщение. Проверьте, что в Pyrus создалась новая задача, либо комментарий добавился в существующую, если ранее с помощью интеграции уже была создана задача.

Отправленные из Pyrus во внешний канал комментарии появятся в приложении Telegram:

Деплой сервиса расширений

Развернём сервис расширения в облаке Yandex.Cloud. Будем использовать виртуальную машину с Ubuntu и запуск приложения в docker-контейнере. Для хранения docker-образов воспользуемся Yandex Container Registry. Также потребуется доменное имя и ssl-сертификат для него.

Подготовка сервера

Создайте виртуальную машину с Ubuntu в yandex cloud.

После выбора параметров виртуальной машины нажмите Создать ВМ.

После создания зайдите на сервер через ssh и установите docker и docker-compose:

curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
curl -L "https://github.com/docker/compose/releases/download/1.28.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

Создание Docker-образа приложения

Добавьте файл Dockerfile в папку с кодом приложения:

# /Dockerfile

FROM python:3.9

WORKDIR /app

ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

RUN apt-get update && pip install --upgrade pip

COPY . .

RUN pip install --no-cache-dir --upgrade -r requirements.txt

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

EXPOSE 8000

Соберите образ локально:

docker build . -t pyrus-ext

Публикация в yandex registry

Создайте новый реестр Docker-образов в yandex cloud.

Сохраните идентификатор реестра, он понадобится далее.

Установите Yandex Cloud CLI и создайте профиль по инструкции.

Авторизуйтесь в Container Registry:

yc container registry configure-docker

Переименуем образ, созданный на предыдущем шаге. В качестве <ID реестра> используется идентификатор, полученный при создании реестра.

docker image tag pyrus-ext:latest cr.yandex/<ID реестра>/pyrus-ext:1.0

Загрузите необходимый Docker-образ в реестр:

docker push cr.yandex/<ID реестра>/pyrus-ext:1.0

Настройка доменного имени

Зарегистрируйте свой домен на любом регистраторе доменных имён, например, Рег.ру.

Добавьте ресурсную запись к домену и укажите публичный IP-адрес виртуальной машины.

Получение ssl сертификата Let's Encrypt

Установите утилиту Certbot для генерации SSL сертификатов:

sudo apt-get update 
sudo apt-get install certbot

Запустите Certbot для создания SSL сертификата:

sudo certbot certonly --manual --preferred-challenges dns -d example.com -d www.example.com

Замените example.com на ваш домен.

Certbot попросит вас подтвердить, что вы владеете доменом. Он также предоставит вам TXT запись, которую нужно добавить в DNS вашего домена.

Войдите в панель управления хостингом и добавьте TXT запись с указанными данными. Обновление DNS может занять некоторое время.

После того как запись будет добавлена и успешно обновлена, нажмите Enter для продолжения.

После этого сертификаты будут сохранены в /etc/letsencrypt/live/example.com/.

Настройка nginx

Создайте на севере файл nginx.conf.

Замените example.com на ваш домен.

server {
    listen 80;
    listen [::]:80;

	server_name example.com;

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl;
    listen [::]:443 ssl http2;

    server_name example.com;

	ssl_certificate /etc/nginx/certs/fullchain.pem;
    ssl_certificate_key /etc/nginx/certs/privkey.pem;

    location / {
			proxy_pass http://web:8000;
			proxy_set_header Host $host;
			proxy_set_header X-Real-IP $remote_addr;
			proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
			proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Настройка Yandex Cloud CLI на сервере

Авторизация в Container Registry Yandex.Cloud

Установите инструмент командной строки yc cli:

curl -sSL https://storage.yandexcloud.net/yandexcloud-yc/install.sh | bash

Выполните команду для аутентификации в yandex cloud:

yc init

Следуйте инструкциям, чтобы войти в свою учетную запись Yandex.Cloud.

После успешной аутентификации выполните команду для авторизации в Container Registry:

yc container registry configure-docker

Эта команда настроит ваш Docker CLI для доступа к Container Registry.

Запуск с docker-compose

Создайте файл docker-compose.yaml

version: "3.7"

services:
  nginx:
	image: nginx:latest
    container_name: nginx
    restart: always
    ports:
      - 80:80
      - 443:443
    volumes:
	  - ./nginx.conf:/etc/nginx/conf.d/default.conf
      - /etc/letsencrypt/live/example.com/fullchain.pem:/etc/nginx/certs/fullchain.pem
      - /etc/letsencrypt/live/example.com/privkey.pem:/etc/nginx/certs/privkey.pem

  web:
    image: cr.yandex/<ID реестра>/pyrus-telegram-integration
	container_name: pyrus-telegram-integration
    restart: always
	ports:
	   - 8000:8000

Выполните команду docker ps и убедитесь, что контейнеры nginx и web запущены.

Теперь замените адрес сервиса в настройках своего расширения Pyrus на постоянный.

Была ли эта статья полезной?