intrabot/bot/keyboards.py
2026-03-15 21:36:03 +03:00

163 lines
6.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Описание кнопок как данных.
Чтобы добавить или убрать кнопку — редактируй TASK_BUTTONS.
Чтобы добавить новый тип клавиатуры — добавь список ButtonSpec + функцию build_*_keyboard().
Keyboard (агностичный тип) конвертируется в нативный формат
в адаптере (см. bot/notifier.py).
"""
from __future__ import annotations
import hashlib
import hmac
import time
from dataclasses import dataclass, field
from typing import Callable
import config
from bot.notifier import Keyboard, KeyboardButton
# ── Спецификация кнопки ────────────────────────────────────────────────────────
@dataclass
class ButtonSpec:
"""
Описание одной кнопки главной клавиатуры задачи.
url_factory — функция(ctx) → str, для URL-кнопок
callback_prefix — строковый префикс callback_data, для inline-кнопок
condition — функция(ctx) → bool, показывать ли кнопку (по умолчанию: всегда)
"""
label: str
url_factory: Callable[[dict], str] | None = None
callback_prefix: str | None = None
condition: Callable[[dict], bool] = field(
default_factory=lambda: (lambda _ctx: True)
)
# ── Вспомогательная функция: подписанная ссылка на Mini App ───────────────────
def _sign_task_url(task_number: str) -> str:
"""Генерирует HMAC-подписанную ссылку на Mini App (TTL 30 дней)."""
expires = int(time.time()) + 30 * 24 * 3600
msg = f"{task_number}:{expires}".encode()
sig = hmac.new(config.MINIAPP_SECRET.encode(), msg, hashlib.sha256).hexdigest()[:16]
return f"{config.PUBLIC_URL}/miniapp?task={task_number}&exp={expires}&sig={sig}"
# ── Главная клавиатура задачи ─────────────────────────────────────────────────
#
# Добавить кнопку → добавь ButtonSpec в TASK_BUTTONS
# Убрать кнопку → удали строку из TASK_BUTTONS
# Временно скрыть → раскомментируй condition или закомментируй весь ButtonSpec
#
TASK_BUTTONS: list[ButtonSpec] = [
ButtonSpec(
label="📄 Предпросмотр",
url_factory=lambda ctx: _sign_task_url(ctx["task_number"]),
condition=lambda ctx: bool(config.PUBLIC_URL),
),
ButtonSpec(
label="🔗 Открыть в IntraDesk",
url_factory=lambda ctx: config.BASE_TASK_URL + ctx["task_number"],
),
# ── Управляющие кнопки (раскомментируй для включения) ─────────────────────
# ButtonSpec(
# label="📋 Статус",
# callback_prefix="st",
# condition=lambda ctx: not ctx.get("closed", False),
# ),
# ButtonSpec(
# label="👤 Исполнитель",
# callback_prefix="ex",
# condition=lambda ctx: not ctx.get("closed", False),
# ),
# ButtonSpec(
# label="🙋 Взять",
# callback_prefix="take",
# condition=lambda ctx: not ctx.get("closed", False),
# ),
# ButtonSpec(
# label="❌ Снять",
# callback_prefix="drop",
# condition=lambda ctx: not ctx.get("closed", False),
# ),
]
def _make_callback_data(spec: ButtonSpec, ctx: dict) -> str:
task_number = ctx["task_number"]
group_id = ctx.get("group_id", 0)
# take/drop — без group_id
if spec.callback_prefix in ("take", "drop"):
return f"{spec.callback_prefix}:{task_number}"
return f"{spec.callback_prefix}:{task_number}:{group_id}"
def build_task_keyboard(ctx: dict) -> Keyboard:
"""
Строит главную клавиатуру задачи по TASK_BUTTONS.
ctx ожидает:
task_number : str
group_id : int (опционально)
closed : bool (опционально)
Раскладка: по 2 кнопки в ряд.
"""
buttons: list[KeyboardButton] = []
for spec in TASK_BUTTONS:
if spec.condition(ctx):
if spec.url_factory:
buttons.append(KeyboardButton(
label=spec.label,
url=spec.url_factory(ctx),
))
elif spec.callback_prefix:
buttons.append(KeyboardButton(
label=spec.label,
callback_data=_make_callback_data(spec, ctx),
))
return [buttons[i:i + 2] for i in range(0, len(buttons), 2)]
# ── Клавиатура выбора статуса ─────────────────────────────────────────────────
def build_status_keyboard(task_number: str, group_id: int) -> Keyboard:
from intradesk import reference
rows: Keyboard = []
for sid, name in reference.STATUSES.items():
rows.append([KeyboardButton(
label=name,
callback_data=f"ss:{task_number}:{group_id}:{sid}",
)])
rows.append([KeyboardButton(
label="↩ Назад",
callback_data=f"back:{task_number}:{group_id}",
)])
return rows
# ── Клавиатура выбора исполнителя ─────────────────────────────────────────────
def build_executor_keyboard(task_number: str, group_id: int) -> Keyboard:
import database
rows: Keyboard = []
for uid in config.EXECUTOR_USER_IDS:
name = database.resolve("employees", uid) or f"ID {uid}"
rows.append([KeyboardButton(
label=name,
callback_data=f"se:{task_number}:{group_id}:{uid}",
)])
rows.append([KeyboardButton(
label=" Только группа",
callback_data=f"su:{task_number}:{group_id}",
)])
rows.append([KeyboardButton(
label="↩ Назад",
callback_data=f"back:{task_number}:{group_id}",
)])
return rows