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"#{deal_id} — {title}\n"
f"Стадия: {stage}\n"
f"Сумма: {decimal.Decimal(opportunity):,.2f} {currency}\n"
f"Дата создания: {date}"
)
@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"Последние сделки:\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())