from utils.texts import ( UNKNOWN_MAIN_CMD_TEXT, EMOJI_DIGITS, INSTRUCT_MAIN_MENU_TEXT, INSTRUCT_TRUECONF_TEXT, INSTRUCT_TRUECONF_WITH_AD, INSTRUCT_EMAIL_TEXT, ) import logging import asyncio import httpx import json import os from trueconf import Router, Message from trueconf.types import FSInputFile from utils.states import get_state, set_state, clear_state # 🔌 Импортируем централизованную функцию сбора статистики из main from utils.stats_logger import log_menu_stats logger = logging.getLogger(__name__) async def sd_create_ticket(login: str, subject: str, description: str) -> str: """Создать заявку в SD.""" from config.config import SD_URL, SD_TOKEN headers = {"authtoken": SD_TOKEN, "Accept": "application/vnd.manageengine.sdp.v3+json"} endpoint = f"{SD_URL}/api/v3/requests" html_desc = f"
{description}
Создано через TrueConf КЛЕВЕР
" payload = {"request": {"subject": subject, "description": html_desc, "requester": {"email_id": None}, "udf_fields": {}}} async with httpx.AsyncClient(verify=False) as client: resp = await client.post(endpoint, headers=headers, data={"input_data": json.dumps(payload)}, timeout=15.0) data = resp.json() if data.get("response_status", {}).get("status_code") == 2000: return str(data.get("request", {}).get("id")) return None router = Router() # Путь к файлу инструкции Trueconf TRUECONF_INSTRUCT_PATH = os.path.join(os.path.dirname(__file__), "Проверка_наличия_и_авторизация_на_мобильном_устройстве_Trueconf.docx") def get_user_cn(user_id: str) -> str | None: """ Получить CN пользователя из AD по его user_id (email или логин). user_id из TrueConf — это обычно email, например 'ds.krivochenko@tcs.sibcem.ru' Заменяет tcs.sibcem.ru на sibcem.ru для поиска в AD. Ищет по всем OU из AD_BASES. """ from utils.ad_search import search_by_user_id try: # Заменяем tcs.sibcem.ru на sibcem.ru search_id = user_id.replace("@tcs.sibcem.ru", "@sibcem.ru") short_username = search_id.split("@")[0] if "@" in search_id else user_id logger.info(f"AD lookup: user_id={user_id}, search_id={search_id}, short={short_username}") entries = search_by_user_id(search_id, ["cn", "sAMAccountName", "mail", "userPrincipalName"]) if entries: entry = entries[0] cn = str(entry.cn) mail = str(entry.mail) if 'mail' in entry else "N/A" sam = str(entry.sAMAccountName) if 'sAMAccountName' in entry else "N/A" upn = str(entry.userPrincipalName) if 'userPrincipalName' in entry else "N/A" logger.info(f"AD found: cn={cn}, mail={mail}, sam={sam}, upn={upn}") return cn logger.warning(f"AD not found for user_id={user_id}, search_id={search_id}, short={short_username}") except Exception as e: logger.error(f"Ошибка получения CN из AD для {user_id}: {e}", exc_info=True) return user_id # fallback — используем user_id как CN @router.message() async def instruct_router_handler(msg: Message): user_id = msg.from_user.id user_name = getattr(msg.from_user, 'name', 'N/A') user_username = getattr(msg.from_user, 'username', 'N/A') logger.info(f"Instruct: user_id={user_id}, name={user_name}, username={user_username}") current_state = get_state(user_id) if not msg.text: return cmd = msg.text.strip().lower() for raw_num, emoji_num in EMOJI_DIGITS.items(): if cmd == emoji_num: cmd = raw_num break if current_state is None: from utils.menu import MENU_MAP if MENU_MAP.get(cmd) == "INSTRUCT": msg.handled = True set_state(user_id, "INSTRUCT_MODE") await msg.answer(INSTRUCT_MAIN_MENU_TEXT, parse_mode="html") return return if current_state == "INSTRUCT_MODE": msg.handled = True if cmd == "0": log_menu_stats(user_id, "Инструкции", "Выход в главное меню") clear_state(user_id) from utils.menu import MENU_TEXT await msg.answer(MENU_TEXT, parse_mode="html") elif cmd == "1": log_menu_stats(user_id, "Инструкции", "Trueconf") set_state(user_id, "INSTRUCT_TRUECONF_VIEW") cn = get_user_cn(user_id) login = user_id.split("@")[0] if "@" in user_id else user_id ticket_id = None try: from utils.ad_checker import check_group_membership result = check_group_membership(cn, "2FA") if not result.get("in_group", True): subject = f"Запрос доступа к Trueconf" description = f"Пользователь {cn} ({user_id}) запросил инструкцию по Trueconf, но не имеет группы 2FA. Требуется выдача доступа." ticket_id = await sd_create_ticket(login, subject, description) logger.info(f"SD ticket created for {cn}: #{ticket_id}") except Exception as e: logger.error(f"Ошибка создания SD заявки: {e}", exc_info=True) ticket_id = None text = INSTRUCT_TRUECONF_WITH_AD(EMOJI_DIGITS, cn, ticket_id) await msg.answer(text, parse_mode="html") if os.path.isfile(TRUECONF_INSTRUCT_PATH): await msg.answer_document(FSInputFile(TRUECONF_INSTRUCT_PATH)) elif cmd == "2": log_menu_stats(user_id, "Инструкции", "Почта") set_state(user_id, "INSTRUCT_EMAIL_VIEW") await msg.answer(INSTRUCT_EMAIL_TEXT, parse_mode="html") else: await msg.answer(UNKNOWN_MAIN_CMD_TEXT, parse_mode="html") elif current_state in ["INSTRUCT_TRUECONF_VIEW", "INSTRUCT_EMAIL_VIEW"]: msg.handled = True if cmd == "0": clear_state(user_id) from utils.menu import MENU_TEXT await msg.answer(MENU_TEXT, parse_mode="html") elif cmd == "9": set_state(user_id, "INSTRUCT_MODE") await msg.answer(INSTRUCT_MAIN_MENU_TEXT, parse_mode="html") else: await msg.answer(UNKNOWN_MAIN_CMD_TEXT, parse_mode="html")