Пример расширения Чат на 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/.
Настройка 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 на постоянный.