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())