Files
BitrixDealsBot/app/main.py
T
2026-07-02 16:42:37 +03:00

121 lines
3.4 KiB
Python

import asyncio
import html
import os
import decimal
import httpx
from aiogram import Bot, Dispatcher
from aiogram.filters import Command
from aiogram.types import Message
from dotenv import load_dotenv
load_dotenv()
BOT_TOKEN = os.getenv("BOT_TOKEN")
BITRIX_WEBHOOK_URL = os.getenv("BITRIX_WEBHOOK_URL")
dp = Dispatcher()
async def bitrix_call(method: str, params: dict | None = None) -> dict:
if not BITRIX_WEBHOOK_URL:
raise RuntimeError("BITRIX_WEBHOOK_URL is not set")
base_url = BITRIX_WEBHOOK_URL.rstrip("/") + "/"
url = base_url + method
async with httpx.AsyncClient(timeout=15) as client:
response = await client.post(url, json=params or {})
response.raise_for_status()
data = response.json()
if "error" in data:
description = data.get("error_description", data["error"])
raise RuntimeError(f"Bitrix API error: {description}")
return data
def format_deal(deal: dict) -> str:
deal_id = html.escape(str(deal.get("ID", "—")))
title = html.escape(str(deal.get("TITLE", "Без названия")))
stage = html.escape(str(deal.get("STAGE_ID", "—")))
opportunity = html.escape(str(deal.get("OPPORTUNITY", "—")))
currency = html.escape(str(deal.get("CURRENCY_ID", "")))
date = html.escape(str(deal.get("DATE_CREATE", "—")))
return (
f"<b>#{deal_id}{title}</b>\n"
f"Стадия: <code>{stage}</code>\n"
f"Сумма: {decimal.Decimal(opportunity):,.2f} {currency}\n"
f"Дата создания: <code>{date}</code>"
)
@dp.message(Command("start"))
async def start_handler(message: Message) -> None:
await message.answer(
"Привет. Команда /leads покажет последние сделки из Битрикс24."
)
@dp.message(Command("leads"))
async def leads_handler(message: Message) -> None:
await message.answer("Запрашиваю сделки...")
try:
data = await bitrix_call(
"crm.deal.list",
{
"order": {"DATE_CREATE": "DESC"},
"filter": {},
"select": [
"ID",
"TITLE",
"STAGE_ID",
"OPPORTUNITY",
"CURRENCY_ID",
"DATE_CREATE",
],
"start": 0,
},
)
deals = data.get("result", [])
if not deals:
await message.answer("Сделки не найдены.")
return
text = "\n\n".join(format_deal(deal) for deal in deals[:10])
await message.answer(
f"<b>Последние сделки:</b>\n\n{text}",
parse_mode="HTML",
)
except httpx.HTTPStatusError as e:
await message.answer(
f"Ошибка HTTP при запросе к Битрикс24: {e.response.status_code}")
except httpx.RequestError:
await message.answer("Не удалось подключиться к Битрикс24.")
except RuntimeError as e:
await message.answer(f"Ошибка: {html.escape(str(e))}")
except Exception:
await message.answer("Произошла неизвестная ошибка.")
async def main() -> None:
if not BOT_TOKEN:
raise RuntimeError("BOT_TOKEN is not set")
bot = Bot(token=BOT_TOKEN)
await dp.start_polling(bot)
if __name__ == "__main__":
asyncio.run(main())