Initial commit: TrueConf Chatbot КЛЕВЕР
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
import requests
|
||||
import config.config as config
|
||||
|
||||
def get_embedding(text, model_name="multilingual-e5-base"):
|
||||
"""
|
||||
Заменяет model.encode(text). Вместо локальной загрузки
|
||||
отправляет запрос к API-серверу.
|
||||
"""
|
||||
url = f"http://127.0.0.1:8010/embed"
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
# Если в конфиге задан токен, добавляем его
|
||||
if hasattr(config, 'EMBEDDING_API_TOKEN') and config.EMBEDDING_API_TOKEN:
|
||||
headers["X-Embedding-Token"] = config.EMBEDDING_API_TOKEN
|
||||
|
||||
payload = {
|
||||
"model": model_name,
|
||||
"input_type": "query", # или "passage", зависит от того, что кодируешь
|
||||
"texts": [text]
|
||||
}
|
||||
|
||||
response = requests.post(url, json=payload, headers=headers)
|
||||
if response.status_code == 200:
|
||||
return response.json()["embeddings"][0]
|
||||
else:
|
||||
raise Exception(f"Ошибка API эмбеддингов: {response.text}")
|
||||
@@ -0,0 +1,79 @@
|
||||
# /opt/trueconf_bot/utils/menu.py
|
||||
import config.config as config
|
||||
|
||||
MENU_MAP = {}
|
||||
MENU_TEXT = ""
|
||||
WELCOME_TEXT = "" # 💡 Добавили шаблон для развернутого приветствия
|
||||
|
||||
def build_dynamic_menu():
|
||||
global MENU_MAP, MENU_TEXT, WELCOME_TEXT
|
||||
MENU_MAP.clear()
|
||||
|
||||
# Старый формат keycap-цифр (как в VK)
|
||||
emoji_digits = {
|
||||
1: "1⃣",
|
||||
2: "2⃣",
|
||||
3: "3⃣",
|
||||
4: "4⃣",
|
||||
5: "5⃣",
|
||||
6: "6⃣",
|
||||
7: "7⃣",
|
||||
8: "8⃣",
|
||||
9: "9⃣",
|
||||
10: "🔟",
|
||||
}
|
||||
|
||||
dynamic_items = []
|
||||
counter = 1
|
||||
|
||||
# Безопасно проверяем флаги из конфига и наполняем массив пунктов
|
||||
if getattr(config, 'ENABLE_LK', True):
|
||||
num_char = emoji_digits.get(counter, str(counter))
|
||||
MENU_MAP[str(counter)] = "LK"
|
||||
MENU_MAP[num_char] = "LK"
|
||||
dynamic_items.append(f"{num_char} — 👤 Личный кабинет")
|
||||
counter += 1
|
||||
|
||||
if getattr(config, 'ENABLE_TRANSCRIPTION', True):
|
||||
num_char = emoji_digits.get(counter, str(counter))
|
||||
MENU_MAP[str(counter)] = "TRANSCRIPTION"
|
||||
MENU_MAP[num_char] = "TRANSCRIPTION"
|
||||
dynamic_items.append(f"{num_char} — 🎙 Расшифровка совещаний")
|
||||
counter += 1
|
||||
|
||||
if getattr(config, 'ENABLE_SERVICE_DESK', True):
|
||||
num_char = emoji_digits.get(counter, str(counter))
|
||||
MENU_MAP[str(counter)] = "SD"
|
||||
MENU_MAP[num_char] = "SD"
|
||||
dynamic_items.append(f"{num_char} — 🎫 Создать заявку в Service Desk")
|
||||
counter += 1
|
||||
|
||||
if getattr(config, 'ENABLE_PHOTO_BOT', True):
|
||||
num_char = emoji_digits.get(counter, str(counter))
|
||||
MENU_MAP[str(counter)] = "PHOTO"
|
||||
MENU_MAP[num_char] = "PHOTO"
|
||||
dynamic_items.append(f"{num_char} — Изменить фото профиля")
|
||||
counter += 1
|
||||
|
||||
if getattr(config, 'ENABLE_SEARCH_BOT', True):
|
||||
num_char = emoji_digits.get(counter, str(counter))
|
||||
MENU_MAP[str(counter)] = "SEARCH"
|
||||
MENU_MAP[num_char] = "SEARCH"
|
||||
dynamic_items.append(f"{num_char} — Поиск по регламентам")
|
||||
counter += 1
|
||||
|
||||
if counter == 1:
|
||||
dynamic_items.append("<i>В данный момент бот находится на техническом обслуживании.</i>")
|
||||
|
||||
# 1️⃣ Сценарий Б: Короткий шаблон для возврата из подмодулей
|
||||
MENU_TEXT = "🍀 <b>Главное меню:</b>\n\n" + "\n".join(dynamic_items)
|
||||
|
||||
# 2️⃣ Сценарий А: Длинный приветственный шаблон для старта/таймаута (с исправленной грамматикой)
|
||||
WELCOME_TEXT = (
|
||||
"🍀 <b>Здравствуйте! Я КЛЕВЕР — ваш персональный цифровой ассистент.</b>\n\n"
|
||||
"С моей помощью вы можете быстро получить доступ к нижеперечисленным корпоративным сервисам.\n\n"
|
||||
"👇 Пожалуйста, отправьте мне в чат цифру, соответствующую нужному пункту меню:\n\n"
|
||||
) + "\n".join(dynamic_items)
|
||||
|
||||
# Gенерируем меню один раз при импорте файла
|
||||
build_dynamic_menu()
|
||||
@@ -0,0 +1,133 @@
|
||||
# /opt/trueconf_bot/utils/otp_service.py
|
||||
import re
|
||||
import secrets
|
||||
import logging
|
||||
import httpx
|
||||
import urllib3
|
||||
from ldap3 import Server, Connection, ALL
|
||||
from ldap3.utils.conv import escape_filter_chars
|
||||
import config.config as config
|
||||
|
||||
# Отключаем во внутреннем контуре предупреждения об SSL
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def generate_otp_code(length: int = 4) -> str:
|
||||
"""Генерирует криптографически безопасный цифровой код"""
|
||||
return "".join(secrets.choice("0123456789") for _ in range(length))
|
||||
|
||||
async def send_otp_via_api(phone: str, code: str) -> tuple:
|
||||
"""
|
||||
Асинхронно отправляет код на шлюз apisms.
|
||||
Парсит XML-ответ и определяет фактический канал доставки (MAX, Telegram, СМС).
|
||||
Возвращает: (True/False, "канал_доставки или текст_ошибки")
|
||||
"""
|
||||
url = "https://mobileapp.sibcem.ru/MobDriveService/MobDriveService/MobService.asmx/SendSMS"
|
||||
params = {
|
||||
"_number": phone,
|
||||
"_message": f"Код авторизации Сибцем : {code}"
|
||||
}
|
||||
try:
|
||||
async with httpx.AsyncClient(verify=False) as client:
|
||||
response = await client.get(url, params=params, timeout=40.0)
|
||||
|
||||
if response.status_code in [200, 201]:
|
||||
xml_content = response.text
|
||||
logger.info(f"Сырой ответ OTP-шлюза: {xml_content}")
|
||||
|
||||
# Извлекаем текст внутри тега <string>...</string>
|
||||
match = re.search(r'<string[^>]*>(.*?)</string>', xml_content, re.DOTALL)
|
||||
if match:
|
||||
inner_text = match.group(1).strip()
|
||||
inner_lower = inner_text.lower()
|
||||
|
||||
# Умное определение канала на основе ключевых слов шлюза Сибцема
|
||||
if "max" in inner_lower:
|
||||
channel_info = "в MAX"
|
||||
elif "telegram" in inner_lower or "телеграм" in inner_lower:
|
||||
channel_info = "в Telegram"
|
||||
elif "смс" in inner_lower or "sms" in inner_lower or "yeastar" in inner_lower or "шлюз" in inner_lower:
|
||||
channel_info = "по СМС"
|
||||
else:
|
||||
channel_info = "по СМС" # Дефолтный фоллбэк, если текст изменится
|
||||
else:
|
||||
channel_info = "по СМС"
|
||||
|
||||
return True, channel_info
|
||||
|
||||
logger.error(f"❌ Шлюз API вернул ошибку {response.status_code}: {response.text}")
|
||||
return False, f"Ошибка сервера шлюза ({response.status_code})"
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Критический сбой сети при отправке OTP на {phone}: {e}")
|
||||
return False, str(e)
|
||||
|
||||
def get_card_id_from_ad(user_id: str) -> str:
|
||||
"""Синхронно вытягивает CARD_ID сотрудника из Active Directory (extensionAttribute2)"""
|
||||
try:
|
||||
server = Server(config.AD_SERVER, get_info=ALL)
|
||||
conn = Connection(server, user=config.AD_USER, password=config.AD_PASSWORD, auto_bind=True)
|
||||
safe_user_id = escape_filter_chars(user_id)
|
||||
short_username = user_id.split('@')[0]
|
||||
|
||||
search_filter = f"(&(objectClass=user)(|(mail={safe_user_id})(userPrincipalName={safe_user_id})(userPrincipalName={escape_filter_chars(f'{short_username}@sibcem.ru')})(sAMAccountName={escape_filter_chars(short_username)})))"
|
||||
conn.search(config.AD_BASE, search_filter, attributes=['extensionAttribute2'])
|
||||
|
||||
if conn.entries and 'extensionAttribute2' in conn.entries[0] and conn.entries[0].extensionAttribute2.value:
|
||||
return str(conn.entries[0].extensionAttribute2.value).strip()
|
||||
raise Exception("Поле extensionAttribute2 не заполнено в AD.")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка получения CARD_ID из AD для {user_id}: {e}")
|
||||
raise Exception(f"Не удалось извлечь CARD_ID из AD: {e}")
|
||||
|
||||
def get_phone_from_db(card_id: str) -> str:
|
||||
"""Синхронно вытягивает телефон сотрудника из БД BossCopy (таблица UOV_SELFSERVICE_PR_CARD) по CARD_ID"""
|
||||
import pymssql
|
||||
try:
|
||||
conn = pymssql.connect(
|
||||
server=config.SQL_SERVER,
|
||||
user=config.DB_USER,
|
||||
password=config.DB_PASSWORD,
|
||||
database=config.SQL_DB_NAME,
|
||||
charset="cp1251",
|
||||
)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Телефон берётся из поля PHONE таблицы UOV_SELFSERVICE_PR_CARD
|
||||
query = """
|
||||
SELECT PHONE FROM UOV_SELFSERVICE_PR_CARD WHERE ID = %s
|
||||
"""
|
||||
cursor.execute(query, (card_id,))
|
||||
row = cursor.fetchone()
|
||||
conn.close()
|
||||
|
||||
if not row or not row[0]:
|
||||
raise Exception(f"Телефон не найден для CARD_ID {card_id} в UOV_SELFSERVICE_PR_CARD")
|
||||
|
||||
phone = str(row[0]).strip()
|
||||
# Очищаем номер от пробелов, дефисов, скобок
|
||||
phone = phone.replace(" ", "").replace("-", "").replace("(", "").replace(")", "")
|
||||
return phone
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка получения телефона из БД для CARD_ID {card_id}: {e}")
|
||||
raise Exception(f"Не удалось извлечь номер телефона из БД BossCopy: {e}")
|
||||
|
||||
def get_user_phone_from_ad(user_id: str) -> str:
|
||||
"""
|
||||
Синхронно вытягивает телефон сотрудника:
|
||||
1. Получает CARD_ID из Active Directory (extensionAttribute2)
|
||||
2. Получает телефон из БД BossCopy (таблица UOV_SELFSERVICE_PR_CARD) по CARD_ID
|
||||
"""
|
||||
try:
|
||||
# Шаг 1: Получаем CARD_ID из AD
|
||||
card_id = get_card_id_from_ad(user_id)
|
||||
logger.info(f"✅ CARD_ID для {user_id}: {card_id}")
|
||||
|
||||
# Шаг 2: Получаем телефон из БД по CARD_ID
|
||||
phone = get_phone_from_db(card_id)
|
||||
logger.info(f"✅ Телефон для CARD_ID {card_id}: {phone}")
|
||||
|
||||
return phone
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка получения телефона для {user_id}: {e}")
|
||||
raise Exception(f"Не удалось извлечь номер телефона: {e}")
|
||||
@@ -0,0 +1,47 @@
|
||||
# /opt/trueconf_bot/utils/states.py
|
||||
import time
|
||||
import config.config as config
|
||||
|
||||
user_states = {}
|
||||
tc_bot = None # Общая переменная для экземпляра бота
|
||||
|
||||
# --- Константы состояний ---
|
||||
PHOTO_MODE = "PHOTO_MODE"
|
||||
SD_MODE = "SD_MODE"
|
||||
TRANSCRIPTION_MODE = "TRANSCRIPTION_MODE"
|
||||
SEARCH_MODE = "SEARCH_MODE"
|
||||
|
||||
def set_state(user_id, state):
|
||||
"""Фиксирует новое состояние пользователя и обновляет метку времени активности"""
|
||||
user_states[user_id] = {
|
||||
"state": state,
|
||||
"last_active": time.time()
|
||||
}
|
||||
|
||||
def get_state(user_id):
|
||||
"""
|
||||
Возвращает текущее состояние пользователя.
|
||||
Если время неактивности превысило лимит из config.py, сессия сбрасывается.
|
||||
"""
|
||||
if user_id in user_states:
|
||||
session = user_states[user_id]
|
||||
|
||||
# Динамически забираем значение таймаута из конфига (дефолт — 1800 сек / 30 мин)
|
||||
session_timeout = getattr(config, 'SESSION_TIMEOUT', 1800)
|
||||
|
||||
# Проверяем, сколько секунд прошло с момента последнего сообщения
|
||||
if time.time() - session["last_active"] > session_timeout:
|
||||
# Время вышло! Аппаратно сносим сессию навигации
|
||||
del user_states[user_id]
|
||||
return None
|
||||
|
||||
# Если время не вышло, обновляем таймер (пользователь проявил активность)
|
||||
session["last_active"] = time.time()
|
||||
return session["state"]
|
||||
|
||||
return None
|
||||
|
||||
def clear_state(user_id):
|
||||
"""Принудительно очищает состояние пользователя (выход в корень)"""
|
||||
if user_id in user_states:
|
||||
del user_states[user_id]
|
||||
@@ -0,0 +1,24 @@
|
||||
# utils/stats_logger.py
|
||||
import os
|
||||
import csv
|
||||
from datetime import datetime
|
||||
|
||||
def log_menu_stats(user_id: str, main_menu: str, sub_menu: str = ""):
|
||||
"""Записывает действие пользователя в лог статистики динамически в зависимости от среды"""
|
||||
try:
|
||||
# Динамически определяем корень проекта (на один уровень выше папки utils)
|
||||
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
log_dir = os.path.join(base_dir, "logs")
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
log_file = os.path.join(log_dir, "menu_stats.csv")
|
||||
|
||||
file_exists = os.path.isfile(log_file)
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
with open(log_file, "a", newline="", encoding="utf-8-sig") as f:
|
||||
writer = csv.writer(f, delimiter=";")
|
||||
if not file_exists:
|
||||
writer.writerow(["Timestamp", "User_ID", "Main_Menu", "Sub_Menu"])
|
||||
writer.writerow([timestamp, user_id, main_menu, sub_menu])
|
||||
except Exception as e:
|
||||
print(f"Ошибка записи статистики меню: {e}")
|
||||
+571
@@ -0,0 +1,571 @@
|
||||
# /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_created(ticket_id: int, subject: str, description: str) -> str:
|
||||
return _sd_ticket_created(ticket_id, subject, description, EMOJI_DIGITS)
|
||||
|
||||
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_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_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)
|
||||
Reference in New Issue
Block a user