diff --git a/instruct/__pycache__/handlers.cpython-311.pyc b/instruct/__pycache__/handlers.cpython-311.pyc new file mode 100644 index 0000000..5072fda Binary files /dev/null and b/instruct/__pycache__/handlers.cpython-311.pyc differ diff --git a/lk/__pycache__/handlers.cpython-311.pyc b/lk/__pycache__/handlers.cpython-311.pyc new file mode 100644 index 0000000..dc6634e Binary files /dev/null and b/lk/__pycache__/handlers.cpython-311.pyc differ diff --git a/photo_bot/__pycache__/handlers.cpython-311.pyc b/photo_bot/__pycache__/handlers.cpython-311.pyc new file mode 100644 index 0000000..546fe29 Binary files /dev/null and b/photo_bot/__pycache__/handlers.cpython-311.pyc differ diff --git a/sd_dispatcher b/sd_dispatcher new file mode 160000 index 0000000..b7b099c --- /dev/null +++ b/sd_dispatcher @@ -0,0 +1 @@ +Subproject commit b7b099cb38cc6060293fb41a9969181dc977eda3 diff --git a/service_desk/__pycache__/handlers.cpython-311.pyc b/service_desk/__pycache__/handlers.cpython-311.pyc new file mode 100644 index 0000000..e2ef18d Binary files /dev/null and b/service_desk/__pycache__/handlers.cpython-311.pyc differ diff --git a/service_desk/test_basic.py b/service_desk/test_basic.py new file mode 100644 index 0000000..86d1d56 --- /dev/null +++ b/service_desk/test_basic.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +import asyncio +import httpx +import json +import urllib3 + +BASE_URL = "https://192.168.1.237:8080" +API_TOKEN = "062CE056-C1F5-490C-A6DF-FD2C653A71DD" + +async def main(): + url = f"{BASE_URL}/api/v3/requests" + headers = { + "authtoken": API_TOKEN, + "Accept": "application/vnd.manageengine.sdp.v3+json" + } + + async with httpx.AsyncClient(verify=False, timeout=30.0) as client: + resp = await client.get(url, headers=headers, params={ + "input_data": json.dumps({ + "list_info": {"row_count": 100, "start_index": 1} + }) + }) + data = resp.json() + print(f"Status: {resp.status_code}") + print(f"Has more: {data.get('list_info', {}).get('has_more_rows')}") + requests = data.get("requests", []) + print(f"Total: {len(requests)}") + for r in requests: + print(f" ID={r.get('id')} subject={r.get('subject')} created={r.get('created_time')}") + +if __name__ == "__main__": + urllib3.disable_warnings() + asyncio.run(main()) diff --git a/service_desk/test_date_format.py b/service_desk/test_date_format.py new file mode 100644 index 0000000..5246195 --- /dev/null +++ b/service_desk/test_date_format.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +"""Проверка формата даты и вариантов фильтрации.""" + +import asyncio +import httpx +import json +import urllib3 + +BASE_URL = "https://192.168.1.237:8080" +API_TOKEN = "062CE056-C1F5-490C-A6DF-FD2C653A71DD" + +async def debug(): + url = f"{BASE_URL}/api/v3/requests" + headers = { + "authtoken": API_TOKEN, + "Accept": "application/vnd.manageengine.sdp.v3+json" + } + + # Вариант 1: без фильтра, посмотреть все поля + print("=== Все заявки (без фильтра) ===") + async with httpx.AsyncClient(verify=False, timeout=30.0) as client: + resp = await client.get(url, headers=headers, params={ + "input_data": json.dumps({ + "list_info": {"row_count": 100, "start_index": 1} + }) + }) + data = resp.json() + requests = data.get("requests", []) + + for req in requests: + print(f"\n--- Заявка {req.get('request_id', '?')} ---") + print(f" subject: {req.get('subject')}") + print(f" created_time: {req.get('created_time')}") + print(f" created_time type: {type(req.get('created_time')).__name__}") + # Показать все ключи + print(f" Ключи: {list(req.keys())}") + +if __name__ == "__main__": + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + asyncio.run(debug()) diff --git a/service_desk/test_filter.py b/service_desk/test_filter.py new file mode 100644 index 0000000..c0039a5 --- /dev/null +++ b/service_desk/test_filter.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +"""Поиск рабочего формата фильтрации.""" + +import asyncio +import httpx +import json +import urllib3 + +BASE_URL = "https://192.168.1.237:8080" +API_TOKEN = "062CE056-C1F5-490C-A6DF-FD2C653A71DD" + +async def test_filters(): + url = f"{BASE_URL}/api/v3/requests" + headers = { + "authtoken": API_TOKEN, + "Accept": "application/vnd.manageengine.sdp.v3+json" + } + + # Сначала посмотрим все заявки с их ID и датами + print("=== Все заявки ===") + async with httpx.AsyncClient(verify=False, timeout=30.0) as client: + resp = await client.get(url, headers=headers, params={ + "input_data": json.dumps({ + "list_info": {"row_count": 100, "start_index": 1} + }) + }) + data = resp.json() + requests = data.get("requests", []) + + for req in requests: + req_id = req.get("request_id", req.get("id", "?")) + subject = req.get("subject", "?") + created = req.get("created_time", {}) + if isinstance(created, dict): + created_str = created.get("display_value", "?") + ts = created.get("value", "?") + else: + created_str = str(created) + ts = "?" + print(f" ID={req_id} created={created_str} ts={ts}") + + # Попробуем разные форматы фильтрации + print("\n=== Попробуем фильтры ===") + + filters = [ + # Вариант 1: ISO строка + { + "search_criteria": { + "column_name": "created_time", + "condition": "between", + "values": ["Jul 1, 2026 00:00:00", "Jul 31, 2026 23:59:59"] + } + }, + # Вариант 2: без search_criteria, с custom_field + { + "search_criteria": { + "column_name": "created_time", + "condition": "between", + "values": ["2026-07-01", "2026-07-31"] + } + }, + # Вариант 3: timestamp как строка + { + "search_criteria": { + "column_name": "created_time", + "condition": "between", + "values": ["1782864000000", "1785542399000"] + } + }, + ] + + for i, criteria in enumerate(filters): + params = { + "input_data": json.dumps({ + "list_info": {"row_count": 100, "start_index": 1}, + "search_criteria": criteria.get("search_criteria") + }) + } + resp = await client.get(url, headers=headers, params=params) + data = resp.json() + reqs = data.get("requests", []) + print(f"\nФильтр {i+1}: {criteria['search_criteria']['values']} -> {len(reqs)} заявок") + for r in reqs: + print(f" {r.get('id')}: {r.get('subject')}") + +if __name__ == "__main__": + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + asyncio.run(test_filters()) diff --git a/service_desk/test_list_tickets.py b/service_desk/test_list_tickets.py new file mode 100644 index 0000000..0bceb68 --- /dev/null +++ b/service_desk/test_list_tickets.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +"""Получение списка заявок из ServiceDesk Plus через API v3.""" + +import asyncio +import httpx +import json +import urllib3 + +BASE_URL = "https://192.168.1.237:8080" +API_TOKEN = "062CE056-C1F5-490C-A6DF-FD2C653A71DD" + +async def list_tickets(): + url = f"{BASE_URL}/api/v3/requests" + headers = { + "authtoken": API_TOKEN, + "Accept": "application/vnd.manageengine.sdp.v3+json" + } + + params = { + "input_data": json.dumps({ + "list_info": { + "row_count": 50, + "start_index": 1 + } + }) + } + + async with httpx.AsyncClient(verify=False, timeout=30.0) as client: + resp = await client.get(url, headers=headers, params=params) + data = resp.json() + + requests = data.get("requests", []) + print(f"Всего заявок: {len(requests)}\n") + + for req in requests: + req_id = req.get("request_id", req.get("id", "?")) + subject = req.get("subject", req.get("short_description", "?")) + + # Группа + group = req.get("group", {}) + if isinstance(group, dict): + group_name = group.get("name", "?") + else: + group_name = str(group) + + # Статус + status = req.get("status", {}) + if isinstance(status, dict): + status_name = status.get("name", "?") + else: + status_name = str(status) + + # Приоритет + priority = req.get("priority", {}) + if isinstance(priority, dict): + priority_name = priority.get("name", "?") + else: + priority_name = str(priority) + + # Описание (убираем HTML) + desc = req.get("description", "") + desc_clean = desc.replace("

", "").replace("

", "").replace("
", "\n").replace("
", "\n").replace("
", "\n") + desc_clean = desc_clean[:300] + + # Создатель + requester = req.get("requester", {}) + req_name = requester.get("name", "?") if isinstance(requester, dict) else "?" + + print(f"--- Заявка #{req_id} ---") + print(f" Тема: {subject}") + print(f" Группа: {group_name}") + print(f" Статус: {status_name}") + print(f" Приоритет: {priority_name}") + print(f" Автор: {req_name}") + print(f" Описание:\n {desc_clean}") + print() + +if __name__ == "__main__": + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + asyncio.run(list_tickets()) diff --git a/service_desk/test_list_tickets_debug.py b/service_desk/test_list_tickets_debug.py new file mode 100644 index 0000000..fd7f262 --- /dev/null +++ b/service_desk/test_list_tickets_debug.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +"""Дебаг запроса к ServiceDesk Plus API v3.""" + +import asyncio +import httpx +import json +import urllib3 + +BASE_URL = "https://192.168.1.237:8080" +API_TOKEN = "062CE056-C1F5-490C-A6DF-FD2C653A71DD" + +async def debug(): + url = f"{BASE_URL}/api/v3/requests" + headers = { + "authtoken": API_TOKEN, + "Accept": "application/vnd.manageengine.sdp.v3+json" + } + + # Разные варианты формата + variants = [ + # Вариант 1: input_data как JSON + { + "input_data": json.dumps({ + "list_info": { + "row_count": 5, + "start_index": 1 + } + }) + }, + # Вариант 2: без input_data, просто параметры + None, + # Вариант 3: input_data без json.dumps + { + "input_data": { + "list_info": { + "row_count": 5, + "start_index": 1 + } + } + } + ] + + for i, params in enumerate(variants): + print(f"\n=== Вариант {i+1} ===") + print(f"Params: {params}") + + async with httpx.AsyncClient(verify=False, timeout=15.0) as client: + try: + resp = await client.get(url, headers=headers, params=params) + print(f"HTTP {resp.status_code}") + print(f"Response: {resp.text[:500]}") + except Exception as e: + print(f"Error: {e}") + +if __name__ == "__main__": + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + asyncio.run(debug()) diff --git a/service_desk/test_list_tickets_july.py b/service_desk/test_list_tickets_july.py new file mode 100644 index 0000000..7e441b3 --- /dev/null +++ b/service_desk/test_list_tickets_july.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +"""Получение списка заявок из ServiceDesk Plus с 20 июля 2026. Экспорт: Группа, Описание, Назначено, Категория в JSONL.""" + +import asyncio +import httpx +import json +import urllib3 +from datetime import datetime, timezone +from html import unescape +import re + +BASE_URL = "https://192.168.1.237:8080" +API_TOKEN = "062CE056-C1F5-490C-A6DF-FD2C653A71DD" + +FROM_DATE_MS = int(datetime(2026, 7, 20, 0, 0, 0, tzinfo=timezone.utc).timestamp() * 1000) + +def get_ts(req): + ct = req.get("created_time", {}) + if isinstance(ct, dict): + v = ct.get("value", "0") + return int(v) if v else 0 + return 0 + +def clean_html(text): + if not text: + return "" + text = re.sub(r'<[^>]+>', '', text) + text = unescape(text) + return text.strip() + +def get_name(obj): + """Извлечь имя из объекта группы/техника/категории.""" + if not obj: + return "" + if isinstance(obj, dict): + return obj.get("name", "") or obj.get("display_value", "") + return str(obj) + +async def fetch_ticket_detail(client, req_id): + try: + resp = await client.get( + f"{BASE_URL}/api/v3/requests/{req_id}", + headers={"authtoken": API_TOKEN, "Accept": "application/vnd.manageengine.sdp.v3+json"}, + timeout=15.0 + ) + data = resp.json() + return data.get("request", data) + except Exception as e: + return {} + +async def list_tickets(): + url = f"{BASE_URL}/api/v3/requests" + headers = {"authtoken": API_TOKEN, "Accept": "application/vnd.manageengine.sdp.v3+json"} + + async with httpx.AsyncClient(verify=False, timeout=30.0) as client: + resp = await client.get(url, headers=headers) + data = resp.json() + all_reqs = data.get("requests", []) + + from_20 = [r for r in all_reqs if get_ts(r) >= FROM_DATE_MS] + + # Получаем детали для каждой заявки + tasks = [fetch_ticket_detail(client, r.get("request_id", r.get("id", "?"))) for r in from_20] + details = await asyncio.gather(*tasks) + + rows = [] + for req, detail in zip(from_20, details): + # Группа — из списка (есть там) + group = req.get("group", {}) + group_name = get_name(group) + + # Описание — из детального запроса + desc = detail.get("description", "") + desc_clean = clean_html(desc) + + # Назначено (специалист) — из детального запроса + tech = detail.get("technician", {}) + assignee = get_name(tech) + + # Категория — из детального запроса + cat = detail.get("category", {}) + category = get_name(cat) + + rows.append({ + "group": group_name, + "description": desc_clean, + "assignee": assignee, + "category": category + }) + + # Выводим JSONL + for row in rows: + print(json.dumps(row, ensure_ascii=False)) + +if __name__ == "__main__": + urllib3.disable_warnings() + asyncio.run(list_tickets()) diff --git a/transcription_bot/__pycache__/handlers.cpython-311.pyc b/transcription_bot/__pycache__/handlers.cpython-311.pyc new file mode 100644 index 0000000..bf9eee9 Binary files /dev/null and b/transcription_bot/__pycache__/handlers.cpython-311.pyc differ diff --git a/utils/__pycache__/states.cpython-311.pyc b/utils/__pycache__/states.cpython-311.pyc new file mode 100644 index 0000000..2a4ef94 Binary files /dev/null and b/utils/__pycache__/states.cpython-311.pyc differ diff --git a/utils/__pycache__/texts.cpython-311.pyc b/utils/__pycache__/texts.cpython-311.pyc new file mode 100644 index 0000000..649d6a0 Binary files /dev/null and b/utils/__pycache__/texts.cpython-311.pyc differ diff --git a/utils/texts.py.bak b/utils/texts.py.bak new file mode 100644 index 0000000..2cda2fa --- /dev/null +++ b/utils/texts.py.bak @@ -0,0 +1,655 @@ +# /opt/trueconf_bot/utils/texts.py +# Все текстовые сообщения бота, отправляемые пользователю + +# ========================================================= +# GLOBAL CONSTANTS +# ========================================================= + +# --- Main Bot --- +# Общее для главного бота +UNKNOWN_MAIN_CMD_TEXT = ( + "⚠️ Неизвестная команда.\n\n" + "Пожалуйста, используйте только цифры, соответствующие нужным пунктам меню, и отправьте выбранную цифру мне в сообщении." +) + +# --- Emoji Digits --- +# Используются для навигации и выбора пунктов меню +EMOJI_DIGITS = { + "0": "0⃣", + "1": "1⃣", + "2": "2⃣", + "3": "3⃣", + "4": "4⃣", + "5": "5⃣", + "6": "6⃣", + "7": "7⃣", + "8": "8⃣", + "9": "9⃣", + "10": "🔟" +} + +# --- Footer Templates --- +# Шаблоны подвалов сообщений для навигации + +def _footer(emojis: dict) -> str: + """Стандартный футер с кнопками Назад и Меню""" + return f"\n\n{emojis['9']} — Назад\n{emojis['0']} — В главное меню" + +def _footer_main_only(emojis: dict) -> str: + """Футер только с кнопкой Меню""" + return f"\n\n{emojis['0']} — В главное меню" + + +# ========================================================= +# TRANSCRIPTION BOT +# ========================================================= +# Модуль для расшифровки аудио и видео файлов + +# --- Private functions (Internal logic) --- + +def _transcription_welcome(emojis: dict) -> str: + return ( + f"🎙 Добро пожаловать! Я — Ассистент расшифровки совещаний.\n\n" + f"Я помогу вам перевести аудио или видео в текст, а также подготовлю краткое резюме вашей встречи.\n\n" + f"📎 Для начала работы просто отправьте нужный файл прямо в этот чат.\n\n" + f"{emojis['0']} — В главное меню" + ) + +def _transcription_unknown(emojis: dict) -> str: + return ( + f"⚠️ Неизвестная команда.\n\n" + f"Пожалуйста, отправьте в сообщении мне нужную цифру соответствующего пункта меню в чат." + ) + +def _transcription_bad_extension(ext: str, emojis: dict) -> str: + return ( + f"❌ Ошибка: Недопустимый формат файла ({ext}).\n\n" + f"Пожалуйста, отправьте поддерживаемый аудио или видео файл.\n\n" + f"{_footer(emojis)}" + ) + +def _transcription_uploading() -> str: + return "⏳ Загружаю файл..." + +def _transcription_success(email: str, emojis: dict) -> str: + return ( + f"✅ Хорошо, файл принят в обработку.\n\n" + f"Как только всё будет готово, я отправлю вам результаты на почту ({email}).\n\n" + f"⏳ Обычно это занимает от 5 до 30 минут.\n\n" + f"{_footer(emojis)}" + ) + +def _transcription_api_error(status_code: int, emojis: dict) -> str: + return f"❌ Ошибка API сервера ({status_code}).{_footer(emojis)}" + +def _transcription_system_error(emojis: dict) -> str: + return f"❌ Произошла системная ошибка при передаче файла.{_footer(emojis)}" + +# --- Public constants and functions --- + +TRANSCRIPTION_MAIN_MENU_TEXT = _transcription_welcome(EMOJI_DIGITS) +TRANSCRIPTION_UNKNOWN_CMD_TEXT = _transcription_unknown(EMOJI_DIGITS) + +def transcription_error_bad_extension(ext: str) -> str: + return _transcription_bad_extension(ext, EMOJI_DIGITS) + +TRANSCRIPTION_UPLOADING_TEXT = _transcription_uploading() + +def transcription_success_received(email: str) -> str: + return _transcription_success(email, EMOJI_DIGITS) + +def transcription_error_api(status_code: int) -> str: + return _transcription_api_error(status_code, EMOJI_DIGITS) + +def transcription_error_system() -> str: + return _transcription_system_error(EMOJI_DIGITS) + + +# ========================================================= +# SERVICE DESK BOT +# ========================================================= +# Модуль для создания и управления заявками в Service Desk + +# --- Private functions (Internal logic) --- + +def _sd_welcome(emojis: dict) -> str: + return ( + f"🎫 Режим создания заявки в Service Desk\n\n" + f"• Опишите проблему текстом.\n" + f"• Можно прикрепить фото или документ.\n\n" + f"{emojis['0']} — В главное меню" + ) + +def _sd_unknown(emojis: dict) -> str: + return ( + f"⚠️ Неизвестная команда.\n" + f"Пожалуйста, используйте цифры меню из сообщения выше.\n\n" + f"{emojis['9']} — Назад\n" + f"{emojis['0']} — В главное меню" + ) + +def _sd_text_required(emojis: dict) -> str: + return f"⚠️ Сначала опишите проблему текстом." + +def _sd_creating() -> str: + return "⏳ Создаю заявку, пожалуйста, подождите..." + +def _sd_ticket_created(ticket_id: int, subject: str, description: str, emojis: dict) -> str: + clean_desc = description.replace("\n", " ").strip() + if len(clean_desc) > 150: + clean_desc = clean_desc[:147] + "..." + return ( + f"✅ Заявка #{ticket_id} создана!\n\n" + f"📌 Тема: {subject}\n\n" + f"📝 Текст: {clean_desc}\n\n" + f"Если нужно добавить скриншоты или документ — отправьте их сейчас.\n\n" + f"{_footer(emojis)}" + ) + +def _sd_ticket_create_error(emojis: dict) -> str: + return f"❌ Ошибка создания заявки.{_footer_main_only(emojis)}" + +def _sd_system_error(emojis: dict) -> str: + return f"❌ Системная ошибка. Попробуйте позже.{_footer_main_only(emojis)}" + +def _sd_uploading_file(ticket_id: int, emojis: dict) -> str: + return f"⏳ Загружаю файл в заявку #{ticket_id}..." + +def _sd_file_attached(emojis: dict) -> str: + return f"✅ Файл успешно прикреплен!{_footer(emojis)}" + +def _sd_file_upload_error(emojis: dict) -> str: + return f"❌ Ошибка загрузки файла.{_footer_main_only(emojis)}" + +# --- Public constants and functions --- + +SD_MAIN_MENU_TEXT = _sd_welcome(EMOJI_DIGITS) +SD_UNKNOWN_CMD_TEXT = _sd_unknown(EMOJI_DIGITS) +SD_TEXT_REQUIRED = _sd_text_required(EMOJI_DIGITS) +SD_CREATING = _sd_creating() + +def sd_ticket_created(ticket_id: int, subject: str, description: str) -> str: + return _sd_ticket_created(ticket_id, subject, description, EMOJI_DIGITS) + +def sd_ticket_create_error() -> str: + return _sd_ticket_create_error(EMOJI_DIGITS) + +def sd_system_error() -> str: + return _sd_system_error(EMOJI_DIGITS) + +def sd_uploading_file(ticket_id: int) -> str: + return _sd_uploading_file(ticket_id, EMOJI_DIGITS) + +def sd_file_attached() -> str: + return _sd_file_attached(EMOJI_DIGITS) + +def sd_file_upload_error() -> str: + return _sd_file_upload_error(EMOJI_DIGITS) + + +# ========================================================= +# PHOTO BOT +# ========================================================= +# Модуль для обновления фото профиля + +# --- Private functions (Internal logic) --- + +def _photo_welcome(emojis: dict) -> str: + return ( + f"🖼 Режим обновления фото профиля\n\n" + f"📸 Пришлите мне вашу фотографию.\n\n" + f"{emojis['0']} — В главное меню" + ) + +def _photo_unknown(emojis: dict) -> str: + return ( + f"⚠️ Неизвестная команда.\n" + f"Пожалуйста, используйте цифры меню из сообщения выше.\n\n" + f"{emojis['9']} — Назад\n" + f"{emojis['0']} — В главное меню" + ) + +def _photo_error_handling(emojis: dict) -> str: + return ( + f"⚠️ В данный момент выполнить это действие невозможно. " + f"Информация о проблеме передана ответственным.\n" + f"Приносим извинения за доставленные неудобства.\n\n" + f"{emojis['0']} — В главное меню" + ) + +def _photo_uploading() -> str: + return "⏳ Отправка..." + +def _photo_success_sent() -> str: + return "📩 Фото отправлено на утверждение." + +def _photo_cancel(emojis: dict) -> str: + return ( + f"🚫 Отменено. Пришлите другое фото.\n\n" + f"{emojis['0']} — В главное меню" + ) + +def _photo_processing() -> str: + return "⏳ Обработка..." + +def _photo_preview_ready(emojis: dict) -> str: + return ( + f"... Фото готово!\n\n" + f"{emojis['1']} — Отправить на утверждение\n" + f"{emojis['2']} — Попробовать другое\n\n" + f"{emojis['0']} — В главное меню" + ) + +def _photo_error_processing(error_text: str, emojis: dict) -> str: + return ( + f"❌ Ошибка: {error_text}\n\n" + f"Пожалуйста, отправьте другое фото.\n\n" + f"{emojis['0']} — В главное меню" + ) + +# --- Public constants and functions --- + +PHOTO_MAIN_MENU_TEXT = _photo_welcome(EMOJI_DIGITS) +PHOTO_UNKNOWN_CMD_TEXT = _photo_unknown(EMOJI_DIGITS) + +def photo_error_handling() -> str: + return _photo_error_handling(EMOJI_DIGITS) + +PHOTO_UPLOADING_TEXT = _photo_uploading() + +def photo_success_sent() -> str: + return _photo_success_sent() + +def photo_cancel_text() -> str: + return _photo_cancel(EMOJI_DIGITS) + +PHOTO_PROCESSING_TEXT = _photo_processing() + +def photo_preview_ready() -> str: + return _photo_preview_ready(EMOJI_DIGITS) + +def photo_error_processing(error_text: str) -> str: + return _photo_error_processing(error_text, EMOJI_DIGITS) + + +# ========================================================= +# SEARCH BOT +# ========================================================= +# Модуль поиска по регламентам + +# --- Private functions (Internal logic) --- + +def _search_welcome(emojis: dict) -> str: + return ( + f"📚 Поиск по регламентам\n\n" + f"Введите ваш вопрос (например: 'время хранения электронных документов' ).\n" + f"Бот найдет наиболее подходящие абзацы из нормативных документов.\n\n" + f"Для возврата в главное меню отправьте {emojis['0']}." + ) + +def _search_unknown(emojis: dict) -> str: + return ( + f"⚠️ Неизвестная команда.\n" + f"Пожалуйста, используйте цифры меню из сообщения выше.\n\n" + f"{emojis['9']} — Назад\n" + f"{emojis['0']} — В главное меню" + ) + +def _search_searching() -> str: + return "🔍 Ищу информацию в регламентах..." + +def _search_not_found(emojis: dict) -> str: + return ( + f"🤖 Точной информации в регламентах не найдено.\n\n" + f"Для выхода отправьте {emojis['0']}." + ) + +def _search_feedback_thanks(emojis: dict) -> str: + return ( + f"🙏 Спасибо за ваш отзыв! Мы передали его разработчикам.\n\n" + f"🔍 Можете сразу задать следующий вопрос по регламентам.\n\n" + f"Для выхода отправьте {emojis['0']}." + ) + +def _search_feedback_info(emojis: dict) -> str: + return ( + f"⚠️ Информация передана.\n\n" + f"Если желаете, напишите прямо сейчас текстовым сообщением, что конкретно вам не понравилось в ответе.\n\n" + f"{emojis['1']} — Задать ещё вопрос\n" + f"{emojis['0']} — Выйти в главное меню" + ) + +def _search_unavailable(emojis: dict) -> str: + return ( + "⚠️ В данный момент выполнить это действие невозможно. " + "Информация о проблеме передана ответственным.\n" + "Приносим извинения за доставленные неудобства.\n\n" + f"{emojis['0']} — В главное меню" + ) + +# --- Public constants and functions --- + +SEARCH_MAIN_MENU_TEXT = _search_welcome(EMOJI_DIGITS) +SEARCH_UNKNOWN_CMD_TEXT = _search_unknown(EMOJI_DIGITS) + +def search_searching_text() -> str: + return _search_searching() + +def search_not_found_text(emojis: dict) -> str: + return _search_not_found(emojis) + +def search_feedback_thanks_text(emojis: dict) -> str: + return _search_feedback_thanks(emojis) + +def search_feedback_info_text(emojis: dict) -> str: + return _search_feedback_info(emojis) + +def search_unavailable_action(emojis: dict) -> str: + return _search_unavailable(emojis) + + +# ========================================================= +# LK BOT +# ========================================================= +# Модуль Личного Кабинета (финансовые документы, табель и др.) + +# --- Constants --- + +DOC_TYPES = { + "1": "Справка о доходах физического лица (2-НДФЛ)", + "2": "Справка об удержаниях за ДМС", + "3": "Справка о средней заработной плате", + "4": "Справка о получении единовременного пособия по рождению ребенка", + "5": "Справка о получении ежемесячного пособия и назначении отпуска по уходу за ребенком", + "6": "Справка об удержаниях по исполнительным листам", + "7": "Копия трудовой книжки" +} + +# --- Private functions (Internal logic) --- + +def _lk_error_handling(emojis: dict) -> str: + return ( + "⚠️ В данный момент выполнить это действие невозможно. Информация о проблеме передана ответственным. Приносим извинения за доставленные неудобства.\n\n" + f"{emojis['9']} — Назад\n{emojis['0']} — В главное меню" + ) + +def _lk_auth_processing(emojis: dict) -> str: + return "⏳ Проверка безопасности... Запрашиваю контакты авторизации..." + +def _lk_auth_success(emojis: dict) -> str: + return "⏳ Авторизация успешна. Загружаю детализацию расчетного листа..." + +def _lk_auth_2fa_expired(emojis: dict) -> str: + return f"⏱ Время действия кода подтверждения изменилось или истекло.\nПожалуйста, запросите расчетный лист заново.{emojis['9']} — Назад" + +def _lk_auth_2fa_wrong_code(emojis: dict, attempts_left: int) -> str: + return ( + f"⚠️ Неверный код подтверждения.\nОсталось попыток: {attempts_left}. Попробуйте еще раз или вернитесь назад.\n\n{emojis['9']} — Назад\n{emojis['0']} — В главное меню" + ) + +def _lk_auth_2fa_blocked_max(emojis: dict, minutes: int) -> str: + return ( + f"❌ Превышено максимальное количество попыток ввода кода.\nДоступ к финансовым документам заблокирован на {minutes} минут.
\n{emojis['9']} — Назад\n{emojis['0']} — В главное меню" + ) + +def _lk_payslip_loading(emojis: dict) -> str: + return "⏳ Формирую архив расчетных листков..." + +def _lk_vacation_loading(emojis: dict) -> str: + return "⏳ Формирую справку по отпускам..." + +def _lk_sick_loading(emojis: dict) -> str: + return "⏳ Загружаю историю больничных листов..." + +def _lk_missions_loading(emojis: dict) -> str: + return "⏳ Загружаю историю командировок..." + +def _lk_rewards_loading(emojis: dict) -> str: + return "⏳ Ищу информацию о ваших наградах..." + +def _lk_transfers_loading(emojis: dict) -> str: + return "⏳ Загружаю историю кадровых перемещений..." + +def _lk_tabel_loading(emojis: dict) -> str: + return "⏳ Рассчитываю баланс рабочего времени..." + +def _lk_auth_sending(emojis: dict) -> str: + return "⏳ Отправка заявки..." + +def _lk_success_submission(emojis: dict) -> str: + return ( + "✅ Ваша заявка успешно отправлена!\n" + "Специалисты свяжутся с вами по готовности документа.\n\n" + f"{emojis['9']} — Назад в список справок\n{emojis['0']} — В главное меню" + ) + +def _lk_main_menu_text(header: str, emojis: dict) -> str: + return ( + f"{header}\n" + f"{emojis['1']} — Расчётный лист\n" + f"{emojis['2']} — Информация о днях отпуска\n" + f"{emojis['3']} — Больничные\n" + f"{emojis['4']} — Командировки\n" + f"{emojis['5']} — Награды\n" + f"{emojis['6']} — Переводы\n" + f"{emojis['7']} — Табель учёта рабочего времени\n" + f"{emojis['8']} — Справки\n\n" + f"{emojis['0']} — Вернуться в главное меню" + ) + +def _lk_tabel_months_menu_text(prev_1, prev_2, months_ru, emojis: dict) -> str: + return ( + "⏱ Выберите месяц для просмотра табеля:\n\n" + f"{emojis['1']} — {months_ru[prev_1.month]} {prev_1.year} г.\n" + f"{emojis['2']} — {months_ru[prev_2.month]} {prev_2.year} г.\n\n" + f"{emojis['9']} — Назад\n{emojis['0']} — В главное меню" + ) + +def _lk_payslip_months_menu_text(months_data: list, months_ru: dict, emojis: dict) -> str: + menu_text = "📅 Выберите месяц для просмотра:\n\n" + for idx, cmonth in enumerate(months_data, start=1): + month_name = months_ru.get((cmonth - 1) % 12 + 1, '00') + year = (cmonth - 1) // 12 + menu_text += f"{emojis[str(idx)]} — {month_name} {year} г.\n" + menu_text += f"\n{emojis['9']} — Назад\n{emojis['0']} — В главное меню" + return menu_text + +def _lk_docs_quantity_prompt(emojis: dict) -> str: + return f"Укажите необходимое количество экземпляров (максимум — 5 шт.):\n\n{emojis['9']} — Назад\n{emojis['0']} — В главное меню" + +def _lk_docs_year_prompt(emojis: dict) -> str: + return f"📅 Выберите год, за который требуется справка:\n\n{emojis['9']} — Назад\n{emojis['0']} — В главное меню" + +def _lk_years_menu(current_year: int, emojis: dict) -> str: + lines = ["📅 Выберите год запрашиваемого периода:\n"] + for i in range(1, 9): + year = current_year - (i - 1) + emoji = emojis.get(str(i), f"{i}⃣") + lines.append(f"{emoji} — {year} год") + lines.append(f"\n{emojis.get('9', '9⃣')} — Назад\n{emojis.get('0', '0⃣')} — В главное меню") + return "\n".join(lines) + +def _lk_docs_avg_salary_prompt(emojis: dict) -> str: + return f"Укажите количество месяцев для расчета средней ЗП (например: 3):\n\n{emojis['9']} — Назад\n{emojis['0']} — В главное меню" + +def _lk_docs_child_age_prompt(emojis: dict) -> str: + return f"👶 Выберите возраст ребенка (до скольки лет):\n\n{emojis['1']} — до 1.5 лет\n{emojis['2']} — до 3 лет\n\n{emojis['9']} — Назад\n{emojis['0']} — В главное меню" + +def _lk_docs_period_prompt(year: int, emojis: dict) -> str: + return f"за {year} год\n\n{emojis['9']} — Назад\n{emojis['0']} — В главное меню" + +def _lk_docs_child_age_options(emojis: dict) -> str: + return f"{emojis['1']} — до 1.5 лет\n{emojis['2']} — до 3 лет\n\n{emojis['9']} — Назад\n{emojis['0']} — В главное меню" + +def _lk_docs_period_options(emojis: dict) -> str: + return f"{emojis['1']} — до 1.5 лет\n{emojis['2']} — до 3 лет\n\n{emojis['9']} — Назад\n{emojis['0']} — В главное меню" + +def _lk_nav_text(emojis: dict) -> str: + return f"\n\n{emojis['9']} — Назад\n{emojis['0']} — В главное меню" + +# --- Public constants and functions --- + +LK_DOCS_MENU_TEXT = ( + "\n".join([f"{EMOJI_DIGITS[k]} — {v}" for k, v in DOC_TYPES.items()]) + + f"\n\n{EMOJI_DIGITS['9']} — Назад\n{EMOJI_DIGITS['0']} — В главное меню" +) +LK_UNKNOWN_CMD_TEXT = ( + "⚠️ Неизвестная команда.\n" + "Пожалуйста, выберите пункт меню, используя цифры или кнопки.
\n" + f"{EMOJI_DIGITS['9']} — Назад\n{EMOJI_DIGITS['0']} — В главное меню" +) + + +def lk_error_handling(emojis: dict) -> str: + return _lk_error_handling(emojis) + +def lk_auth_processing(emojis: dict) -> str: + return _lk_auth_processing(emojis) + +def lk_auth_success(emojis: dict) -> str: + return _lk_auth_success(emojis) + +def lk_auth_2fa_expired(emojis: dict) -> str: + return _lk_auth_2fa_expired(emojis) + +def lk_auth_2fa_wrong_code(emojis: dict, attempts_left: int) -> str: + return _lk_auth_2fa_wrong_code(emojis, attempts_left) + +def lk_auth_2fa_blocked_max(emojis: dict, minutes: int) -> str: + return _lk_auth_2fa_blocked_max(emojis, minutes) + +def lk_payslip_loading(emojis: dict) -> str: + return _lk_payslip_loading(emojis) + +def lk_vacation_loading(emojis: dict) -> str: + return _lk_vacation_loading(emojis) + +def lk_sick_loading(emojis: dict) -> str: + return _lk_sick_loading(emojis) + +def lk_missions_loading(emojis: dict) -> str: + return _lk_missions_loading(emojis) + +def lk_rewards_loading(emojis: dict) -> str: + return _lk_rewards_loading(emojis) + +def lk_transfers_loading(emojis: dict) -> str: + return _lk_transfers_loading(emojis) + +def lk_tabel_loading(emojis: dict) -> str: + return _lk_tabel_loading(emojis) + +def lk_auth_sending(emojis: dict) -> str: + return _lk_auth_sending(emojis) + +def lk_success_submission(emojis: dict) -> str: + return _lk_success_submission(emojis) + +def lk_main_menu_text(header: str, emojis: dict) -> str: + return _lk_main_menu_text(header, emojis) + +def lk_tabel_months_menu_text(prev_1, prev_2, months_ru, emojis: dict) -> str: + return _lk_tabel_months_menu_text(prev_1, prev_2, months_ru, emojis) + +def lk_payslip_months_menu_text(months_data: list, months_ru: dict, emojis: dict) -> str: + return _lk_payslip_months_menu_text(months_data, months_ru, emojis) + +def lk_docs_quantity_prompt(emojis: dict) -> str: + return _lk_docs_quantity_prompt(emojis) + +def lk_docs_year_prompt(emojis: dict) -> str: + return _lk_docs_year_prompt(emojis) + +def lk_docs_avg_salary_prompt(emojis: dict) -> str: + return _lk_docs_avg_salary_prompt(emojis) + +def lk_docs_child_age_prompt(emojis: dict) -> str: + return _lk_docs_child_age_prompt(emojis) + +def lk_docs_period_prompt(year: int, emojis: dict) -> str: + return _lk_docs_period_prompt(year, emojis) + +def lk_docs_child_age_options(emojis: dict) -> str: + return _lk_docs_child_age_options(emojis) + +def lk_docs_period_options(emojis: dict) -> str: + return _lk_docs_period_options(emojis) + +def lk_nav_text(emojis: dict) -> str: + return _lk_nav_text(emojis) + +def lk_years_menu(current_year: int, emojis: dict) -> str: + return _lk_years_menu(current_year, emojis) + + +# ========================================================= +# INSTRUCT BOT (Инструкции) +# ========================================================= +# Модуль инструкций + +# --- Private functions (Internal logic) --- + +def _instruct_main_menu(emojis: dict) -> str: + return ( + f"📋 Выберите инструкцию:\n\n" + f"{emojis['1']} — 🖥 Trueconf\n" + f"{emojis['2']} — 📧 Почта\n\n" + f"{emojis['0']} — В главное меню" + ) + +def _instruct_trueconf(emojis: dict) -> str: + """Статический текст Trueconf — без проверки группы.""" + return ( + "🖥 Инструкция по Trueconf\n\n" + "Данная функция находится в разработке.\n\n" + f"{emojis['9']} — Назад\n{emojis['0']} — В главное меню" + ) + + +def _instruct_trueconf_with_ad(emojis: dict, cn: str, ticket_id: str = None) -> str: + """ + Текст Trueconf с проверкой группы 2FA. + cn — CN пользователя из AD. + ticket_id — номер созданной заявки в SD. + """ + from utils.ad_checker import check_group_membership + + result = check_group_membership(cn, "2FA") + + if not result["found"]: + ticket_part = f"\n\n🎫 Заявка #{ticket_id} создана!" if ticket_id else "" + return ( + f"🖥 Инструкция по Trueconf\n\n" + f"Пользователь {cn} не найден в Active Directory.{ticket_part}\n\n" + f"{emojis['9']} — Назад\n{emojis['0']} — В главное меню" + ) + + if result["in_group"]: + body = ( + f"🖥 Инструкция по Trueconf\n\n" + f"{emojis['9']} — Назад\n{emojis['0']} — В главное меню" + ) + else: + body = ( + f"ℹ Доступ к Trueconf пока не настроен.\n\n" + f"🎫 Заявка #23651 для настройки создана в ServiceDesk!\n\n" + f"🔄 После выполнения заявки запросите инструкцию ещё раз.\n\n" + f"{emojis['9']} — Назад\n{emojis['0']} — В главное меню" + ) + + return body + + +def _instruct_email(emojis: dict) -> str: + return ( + "📧 Инструкция по Почте\n\n" + "Данная функция находится в разработке.\n\n" + f"{emojis['9']} — Назад\n{emojis['0']} — В главное меню" + ) + +# --- Public constants and functions --- + +INSTRUCT_MAIN_MENU_TEXT = _instruct_main_menu(EMOJI_DIGITS) +INSTRUCT_TRUECONF_TEXT = _instruct_trueconf(EMOJI_DIGITS) +INSTRUCT_TRUECONF_WITH_AD = _instruct_trueconf_with_ad +INSTRUCT_EMAIL_TEXT = _instruct_email(EMOJI_DIGITS) \ No newline at end of file