photo_bot: remove AI validator, restore prepare_ad_photo workflow

This commit is contained in:
Денис Кривоченко
2026-08-14 00:54:30 +07:00
parent 95803b2c3c
commit 7b129e0836
15 changed files with 1051 additions and 0 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Submodule
+1
Submodule sd_dispatcher added at b7b099cb38
Binary file not shown.
+33
View File
@@ -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())
+40
View File
@@ -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())
+88
View File
@@ -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())
+80
View File
@@ -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("<p>", "").replace("</p>", "").replace("<br>", "\n").replace("<br/>", "\n").replace("<br />", "\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())
+57
View File
@@ -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())
+97
View File
@@ -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())
Binary file not shown.
Binary file not shown.
+655
View File
@@ -0,0 +1,655 @@
# /opt/trueconf_bot/utils/texts.py
# Все текстовые сообщения бота, отправляемые пользователю
# =========================================================
# GLOBAL CONSTANTS
# =========================================================
# --- Main Bot ---
# Общее для главного бота
UNKNOWN_MAIN_CMD_TEXT = (
"⚠️ <b>Неизвестная команда.</b>\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<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
def _footer_main_only(emojis: dict) -> str:
"""Футер только с кнопкой Меню"""
return f"\n\n<i>{emojis['0']} — В главное меню</i>"
# =========================================================
# TRANSCRIPTION BOT
# =========================================================
# Модуль для расшифровки аудио и видео файлов
# --- Private functions (Internal logic) ---
def _transcription_welcome(emojis: dict) -> str:
return (
f"🎙 <b>Добро пожаловать! Я — Ассистент расшифровки совещаний.</b>\n\n"
f"Я помогу вам перевести аудио или видео в текст, а также подготовлю краткое резюме вашей встречи.\n\n"
f"📎 Для начала работы просто отправьте нужный файл прямо в этот чат.\n\n"
f"{emojis['0']} — В главное меню"
)
def _transcription_unknown(emojis: dict) -> str:
return (
f"⚠️ <b>Неизвестная команда.</b>\n\n"
f"Пожалуйста, отправьте в сообщении мне нужную цифру соответствующего пункта меню в чат."
)
def _transcription_bad_extension(ext: str, emojis: dict) -> str:
return (
f"❌ <b>Ошибка: Недопустимый формат файла ({ext}).</b>\n\n"
f"Пожалуйста, отправьте поддерживаемый аудио или видео файл.\n\n"
f"{_footer(emojis)}"
)
def _transcription_uploading() -> str:
return "⏳ <b>Загружаю файл...</b>"
def _transcription_success(email: str, emojis: dict) -> str:
return (
f"✅ <b>Хорошо, файл принят в обработку.</b>\n\n"
f"Как только всё будет готово, я отправлю вам результаты на почту (<code>{email}</code>).\n\n"
f"⏳ Обычно это занимает от 5 до 30 минут.\n\n"
f"{_footer(emojis)}"
)
def _transcription_api_error(status_code: int, emojis: dict) -> str:
return f"❌ <b>Ошибка API сервера ({status_code}).</b>{_footer(emojis)}"
def _transcription_system_error(emojis: dict) -> str:
return f"❌ <b>Произошла системная ошибка при передаче файла.</b>{_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"🎫 <b>Режим создания заявки в Service Desk</b>\n\n"
f"• Опишите проблему текстом.\n"
f"• Можно прикрепить фото или документ.\n\n"
f"{emojis['0']} — В главное меню"
)
def _sd_unknown(emojis: dict) -> str:
return (
f"⚠️ <b>Неизвестная команда.</b>\n"
f"Пожалуйста, используйте цифры меню из сообщения выше.\n\n"
f"{emojis['9']} — Назад\n"
f"{emojis['0']} — В главное меню"
)
def _sd_text_required(emojis: dict) -> str:
return f"⚠️ <b>Сначала опишите проблему текстом.</b>"
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"✅ <b>Заявка #{ticket_id} создана!</b>\n\n"
f"📌 <b>Тема: {subject}</b>\n\n"
f"📝 <b>Текст: {clean_desc}</b>\n\n"
f"Если нужно добавить скриншоты или документ — отправьте их сейчас.\n\n"
f"{_footer(emojis)}"
)
def _sd_ticket_create_error(emojis: dict) -> str:
return f"❌ <b>Ошибка создания заявки.</b>{_footer_main_only(emojis)}"
def _sd_system_error(emojis: dict) -> str:
return f"❌ <b>Системная ошибка.</b> Попробуйте позже.{_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"✅ <b>Файл успешно прикреплен!</b>{_footer(emojis)}"
def _sd_file_upload_error(emojis: dict) -> str:
return f"❌ <b>Ошибка загрузки файла.</b>{_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"🖼 <b>Режим обновления фото профиля</b>\n\n"
f"📸 Пришлите мне вашу фотографию.\n\n"
f"{emojis['0']} — В главное меню"
)
def _photo_unknown(emojis: dict) -> str:
return (
f"⚠️ <b>Неизвестная команда.</b>\n"
f"Пожалуйста, используйте цифры меню из сообщения выше.\n\n"
f"{emojis['9']} — Назад\n"
f"{emojis['0']} — В главное меню"
)
def _photo_error_handling(emojis: dict) -> str:
return (
f"⚠️ <i>В данный момент выполнить это действие невозможно. "
f"Информация о проблеме передана ответственным.\n"
f"Приносим извинения за доставленные неудобства.</i>\n\n"
f"{emojis['0']} — В главное меню"
)
def _photo_uploading() -> str:
return "⏳ Отправка..."
def _photo_success_sent() -> str:
return "📩 <b>Фото отправлено на утверждение.</b>"
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"... <b>Фото готово!</b>\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"❌ <b>Ошибка:</b> {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"📚 <b>Поиск по регламентам</b>\n\n"
f"Введите ваш вопрос (например: <i>'время хранения электронных документов'</i> ).\n"
f"Бот найдет наиболее подходящие абзацы из нормативных документов.\n\n"
f"Для возврата в главное меню отправьте {emojis['0']}."
)
def _search_unknown(emojis: dict) -> str:
return (
f"⚠️ <b>Неизвестная команда.</b>\n"
f"Пожалуйста, используйте цифры меню из сообщения выше.\n\n"
f"{emojis['9']} — Назад\n"
f"{emojis['0']} — В главное меню"
)
def _search_searching() -> str:
return "🔍 <b>Ищу информацию в регламентах...</b>"
def _search_not_found(emojis: dict) -> str:
return (
f"🤖 Точной информации в регламентах не найдено.\n\n"
f"Для выхода отправьте {emojis['0']}."
)
def _search_feedback_thanks(emojis: dict) -> str:
return (
f"🙏 <b>Спасибо за ваш отзыв!</b> Мы передали его разработчикам.\n\n"
f"🔍 <b>Можете сразу задать следующий вопрос по регламентам.</b>\n\n"
f"Для выхода отправьте {emojis['0']}."
)
def _search_feedback_info(emojis: dict) -> str:
return (
f"⚠️ <b>Информация передана.</b>\n\n"
f"Если желаете, напишите прямо сейчас текстовым сообщением, что конкретно вам не понравилось в ответе.\n\n"
f"{emojis['1']} — Задать ещё вопрос\n"
f"{emojis['0']} — Выйти в главное меню"
)
def _search_unavailable(emojis: dict) -> str:
return (
"⚠️ <i>В данный момент выполнить это действие невозможно. "
"Информация о проблеме передана ответственным.\n"
"Приносим извинения за доставленные неудобства.</i>\n\n"
f"<i>{emojis['0']} — В главное меню</i>"
)
# --- 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 (
"⚠️ <i>В данный момент выполнить это действие невозможно. Информация о проблеме передана ответственным. Приносим извинения за доставленные неудобства.</i>\n\n"
f"<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
)
def _lk_auth_processing(emojis: dict) -> str:
return "⏳ <i>Проверка безопасности... Запрашиваю контакты авторизации...</i>"
def _lk_auth_success(emojis: dict) -> str:
return "⏳ <i>Авторизация успешна. Загружаю детализацию расчетного листа...</i>"
def _lk_auth_2fa_expired(emojis: dict) -> str:
return f"⏱ <b>Время действия кода подтверждения изменилось или истекло.</b>\nПожалуйста, запросите расчетный лист заново.{emojis['9']} — Назад"
def _lk_auth_2fa_wrong_code(emojis: dict, attempts_left: int) -> str:
return (
f"⚠️ <b>Неверный код подтверждения.</b>\nОсталось попыток: {attempts_left}. Попробуйте еще раз или вернитесь назад.\n\n<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
)
def _lk_auth_2fa_blocked_max(emojis: dict, minutes: int) -> str:
return (
f"❌ <b>Превышено максимальное количество попыток ввода кода.</b>\nДоступ к финансовым документам заблокирован на <b>{minutes} минут</b>.<br>\n<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
)
def _lk_payslip_loading(emojis: dict) -> str:
return "⏳ <i>Формирую архив расчетных листков...</i>"
def _lk_vacation_loading(emojis: dict) -> str:
return "⏳ <i>Формирую справку по отпускам...</i>"
def _lk_sick_loading(emojis: dict) -> str:
return "⏳ <i>Загружаю историю больничных листов...</i>"
def _lk_missions_loading(emojis: dict) -> str:
return "⏳ <i>Загружаю историю командировок...</i>"
def _lk_rewards_loading(emojis: dict) -> str:
return "⏳ <i>Ищу информацию о ваших наградах...</i>"
def _lk_transfers_loading(emojis: dict) -> str:
return "⏳ <i>Загружаю историю кадровых перемещений...</i>"
def _lk_tabel_loading(emojis: dict) -> str:
return "⏳ <i>Рассчитываю баланс рабочего времени...</i>"
def _lk_auth_sending(emojis: dict) -> str:
return "⏳ <i>Отправка заявки...</i>"
def _lk_success_submission(emojis: dict) -> str:
return (
"✅ <b>Ваша заявка успешно отправлена!</b>\n"
"Специалисты свяжутся с вами по готовности документа.\n\n"
f"<i>{emojis['9']} — Назад в список справок\n{emojis['0']} — В главное меню</i>"
)
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"<i>{emojis['0']} — Вернуться в главное меню</i>"
)
def _lk_tabel_months_menu_text(prev_1, prev_2, months_ru, emojis: dict) -> str:
return (
"⏱ <b>Выберите месяц для просмотра табеля:</b>\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"<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
)
def _lk_payslip_months_menu_text(months_data: list, months_ru: dict, emojis: dict) -> str:
menu_text = "📅 <b>Выберите месяц для просмотра:</b>\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<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
return menu_text
def _lk_docs_quantity_prompt(emojis: dict) -> str:
return f"Укажите необходимое количество экземпляров (максимум — 5 шт.):\n\n<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
def _lk_docs_year_prompt(emojis: dict) -> str:
return f"📅 <b>Выберите год, за который требуется справка:</b>\n\n<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
def _lk_years_menu(current_year: int, emojis: dict) -> str:
lines = ["📅 <b>Выберите год запрашиваемого периода:</b>\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<i>{emojis.get('9', '9⃣')} — Назад\n{emojis.get('0', '0⃣')} — В главное меню</i>")
return "\n".join(lines)
def _lk_docs_avg_salary_prompt(emojis: dict) -> str:
return f"Укажите количество месяцев для расчета средней ЗП (например: 3):\n\n<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
def _lk_docs_child_age_prompt(emojis: dict) -> str:
return f"👶 <b>Выберите возраст ребенка (до скольки лет):</b>\n\n{emojis['1']} — до 1.5 лет\n{emojis['2']} — до 3 лет\n\n<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
def _lk_docs_period_prompt(year: int, emojis: dict) -> str:
return f"за {year} год\n\n<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
def _lk_docs_child_age_options(emojis: dict) -> str:
return f"{emojis['1']} — до 1.5 лет\n{emojis['2']} — до 3 лет\n\n<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
def _lk_docs_period_options(emojis: dict) -> str:
return f"{emojis['1']} — до 1.5 лет\n{emojis['2']} — до 3 лет\n\n<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
def _lk_nav_text(emojis: dict) -> str:
return f"\n\n<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
# --- 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<i>{EMOJI_DIGITS['9']} — Назад\n{EMOJI_DIGITS['0']} — В главное меню</i>"
)
LK_UNKNOWN_CMD_TEXT = (
"⚠️ <b>Неизвестная команда.</b>\n"
"Пожалуйста, выберите пункт меню, используя цифры или кнопки.<br>\n"
f"<i>{EMOJI_DIGITS['9']} — Назад\n{EMOJI_DIGITS['0']} — В главное меню</i>"
)
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"📋 <b>Выберите инструкцию:</b>\n\n"
f"{emojis['1']} — 🖥 Trueconf\n"
f"{emojis['2']} — 📧 Почта\n\n"
f"<i>{emojis['0']} — В главное меню</i>"
)
def _instruct_trueconf(emojis: dict) -> str:
"""Статический текст Trueconf — без проверки группы."""
return (
"🖥 <b>Инструкция по Trueconf</b>\n\n"
"Данная функция находится в разработке.\n\n"
f"<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
)
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🎫 <b>Заявка #{ticket_id} создана!</b>" if ticket_id else ""
return (
f"🖥 <b>Инструкция по Trueconf</b>\n\n"
f"Пользователь {cn} не найден в Active Directory.{ticket_part}\n\n"
f"<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
)
if result["in_group"]:
body = (
f"🖥 <b>Инструкция по Trueconf</b>\n\n"
f"<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
)
else:
body = (
f" <b>Доступ к Trueconf пока не настроен.</b>\n\n"
f"🎫 Заявка #23651 для настройки создана в ServiceDesk!\n\n"
f"🔄 После выполнения заявки запросите инструкцию ещё раз.\n\n"
f"<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
)
return body
def _instruct_email(emojis: dict) -> str:
return (
"📧 <b>Инструкция по Почте</b>\n\n"
"Данная функция находится в разработке.\n\n"
f"<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
)
# --- 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)