121 lines
3.4 KiB
Python
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())
|