diff --git a/instruct/handlers.py b/instruct/handlers.py index 482afd7..b2f3f01 100644 --- a/instruct/handlers.py +++ b/instruct/handlers.py @@ -2,6 +2,8 @@ import logging import asyncio import os import sys +import json +import httpx from trueconf import Router, Message from trueconf.types import FSInputFile @@ -16,7 +18,6 @@ from utils.texts import ( ) from utils.states import get_state, set_state, clear_state from utils.stats_logger import log_menu_stats -from service_desk.handlers import create_ticket_in_sd # Безопасный импорт конфигурации try: @@ -96,12 +97,47 @@ def get_ad_user_info(user_id: str) -> dict | None: return None +async def _send_sd_api_request(requester_email: str, subject: str, description: str, city: str) -> str | None: + """ + Автономная прямая отправка HTTP-запроса в Service Desk API v3. + """ + # Если запрашивает системный ИИ или не указан email — ищем по ИМЕНИ + if requester_email == DEFAULT_REQUESTER or requester_email == "ai@sibcem.ru": + requester_payload = {"name": "Искусственный Интеллект"} + else: + requester_payload = {"email_id": requester_email} + + payload = { + "request": { + "subject": subject, + "description": description, + "requester": requester_payload, + "udf_fields": { + "udf_pick_301": city + } + } + } + + url = f"{SD_URL.rstrip('/')}/api/v3/requests" + headers = {"TECHNICIAN_KEY": SD_TOKEN} + data = {"input_data": json.dumps(payload)} + + async with httpx.AsyncClient(verify=False) as client: + response = await client.post(url, headers=headers, data=data, timeout=15.0) + + if response.status_code in (200, 201): + res_json = response.json() + ticket_id = res_json.get("request", {}).get("id") + return str(ticket_id) if ticket_id else None + else: + logger.error(f"Service Desk API Error ({response.status_code}): {response.text}") + return None + + async def create_sd_ticket_for_trueconf(user_id: str, cn: str) -> str | None: """ Создать заявку в Service Desk для запроса доступа к Trueconf. - - Если пользователь не найден в AD или у него нет email — создаёт заявку от DEFAULT_REQUESTER (ai@sibcem.ru). - Если пользователь найден — создаёт заявку от его email. + Полностью автономная функция. """ subject = "Запрос доступа к Trueconf" @@ -110,7 +146,6 @@ async def create_sd_ticket_for_trueconf(user_id: str, cn: str) -> str | None: logger.info(f"AD search result for SD ticket ({user_id}): {ad_info}") if ad_info and ad_info.get("mail"): - # Пользователь найден в AD и имеет email requester_email = ad_info["mail"] city = ad_info.get("city", "Кемерово") description = ( @@ -118,7 +153,6 @@ async def create_sd_ticket_for_trueconf(user_id: str, cn: str) -> str | None: f"но не имеет группы 2FA. Требуется выдача доступа." ) else: - # Пользователь не найден в AD ИЛИ не имеет email — создаём от DEFAULT_REQUESTER (ai@sibcem.ru) requester_email = DEFAULT_REQUESTER city = "Кемерово" logger.info(f"Using DEFAULT_REQUESTER ({requester_email}) for user {user_id} (cn={cn})") @@ -135,8 +169,8 @@ async def create_sd_ticket_for_trueconf(user_id: str, cn: str) -> str | None: ) try: - logger.info(f"Calling create_ticket_in_sd: requester={requester_email}, city={city}") - ticket_id = await create_ticket_in_sd(requester_email, subject, description, city) + logger.info(f"Calling SD API directly: requester={requester_email}, city={city}") + ticket_id = await _send_sd_api_request(requester_email, subject, description, city) if ticket_id: logger.info(f"SD ticket created for {cn}: #{ticket_id} (requester={requester_email})") else: @@ -192,8 +226,16 @@ async def instruct_router_handler(msg: Message): if cn is None: cn = user_id # fallback - # Создаём заявку в SD - ticket_id = await create_sd_ticket_for_trueconf(user_id, cn) + # Проверяем наличие группы 2FA в AD перед созданием заявки + from utils.ad_checker import check_group_membership + ad_check = check_group_membership(cn, "2FA") + + ticket_id = None + # Создаём заявку в SD ТОЛЬКО если у пользователя НЕТ группы 2FA (или он не найден) + if not ad_check.get("in_group", False): + ticket_id = await create_sd_ticket_for_trueconf(user_id, cn) + else: + logger.info(f"User {cn} already has 2FA group. SD ticket creation skipped.") # Показываем текст с результатом text = INSTRUCT_TRUECONF_WITH_AD(EMOJI_DIGITS, cn, ticket_id)