163 lines
6.4 KiB
Python
163 lines
6.4 KiB
Python
"""
|
||
Описание кнопок как данных.
|
||
|
||
Чтобы добавить или убрать кнопку — редактируй 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
|