Untitled
4ae4d
plain_text
02/09/2026 10:59 AM
17.3 KB
8
Indexable
--- src/bot/core/handlers/events_ui.py ---
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from typing import Any
from dialog_bot_sdk.interactive_media import Button, InteractiveMediaGroup, MediaGroupBuilder
from core.config import bot
from core.handlers.events_pages import EVENTS_PAGE_SIZE, fetch_events_page
from core.markups.event import (
event_actions_keyboard,
events_select_keyboard,
format_event_details,
my_events_pagination_keyboard,
user_events_pagination_keyboard,
)
from core.markups.inline import all_events_pagination_keyboard
from core.markups.points import points_events_pagination_keyboard, points_menu_keyboard
from core.schemas.event import EventSchema
from core.schemas.user import UserSchema
from core.services.event import EventService
from core.services.report import ReportService
DEFAULT_CITY_UUID = "be9da3ba-a0e4-42f9-9ae2-9875c8625083" # SPB
ADMIN_CAN_MANAGE_OTHERS = True # иммет ли администратор доступ ко всем мероприятиям?
@dataclass(frozen=True)
class EventUIContext:
name: str
# list
list_open_media_id: str
pagination_builder: Callable[..., list[InteractiveMediaGroup]]
extra_groups_builder: Callable[[], list[InteractiveMediaGroup]] | None
# card
back_value: str
back_label: str
enter_code_media_id: str
sign_up_media_id: str
sign_out_media_id: str
_UI: dict[str, EventUIContext] = {
# дом волонтёра -> мероприятия
"events": EventUIContext(
name="events",
list_open_media_id="user_events_open",
pagination_builder=user_events_pagination_keyboard,
extra_groups_builder=None,
back_value="events",
back_label="⬅️ К списку мероприятий",
enter_code_media_id="event_user_enter_code",
sign_up_media_id="event_user_sign_up",
sign_out_media_id="event_user_sign_out",
),
# дом волонтёра -> посмотреть по uuid
"volunteer_home": EventUIContext(
name="volunteer_home",
list_open_media_id="user_events_open", # не используется в этом контексте
pagination_builder=user_events_pagination_keyboard,
extra_groups_builder=None,
back_value="volunteer_home",
back_label="⬅️ В дом волонтёра",
enter_code_media_id="event_user_enter_code",
sign_up_media_id="event_user_sign_up",
sign_out_media_id="event_user_sign_out",
),
# дом модерации -> все мероприятия
"moderation": EventUIContext(
name="moderation",
list_open_media_id="all_events_open",
pagination_builder=all_events_pagination_keyboard,
extra_groups_builder=None,
back_value="all_events",
back_label="⬅️ К списку мероприятий",
enter_code_media_id="event_menu_enter_code",
sign_up_media_id="event_menu_sign_up",
sign_out_media_id="event_menu_sign_out",
),
# меню баллов -> мероприятия с начисленными баллами
"points": EventUIContext(
name="points",
list_open_media_id="points_event_open",
pagination_builder=points_events_pagination_keyboard,
extra_groups_builder=points_menu_keyboard, # лидерборд + назад
back_value="points",
back_label="⬅️ К баллам",
enter_code_media_id="event_user_enter_code",
sign_up_media_id="event_user_sign_up",
sign_out_media_id="event_user_sign_out",
),
# дом модерации -> посмотреть по uuid
"moderation_menu": EventUIContext(
name="moderation_menu",
list_open_media_id="all_events_open", # не используется в этом контексте
pagination_builder=all_events_pagination_keyboard,
extra_groups_builder=None,
back_value="moderation",
back_label="⬅️ В меню модерации",
enter_code_media_id="event_menu_enter_code",
sign_up_media_id="event_menu_sign_up",
sign_out_media_id="event_menu_sign_out",
),
# дом модерации -> мои мероприятия
"my_events": EventUIContext(
name="my_events",
list_open_media_id="my_events_open",
pagination_builder=my_events_pagination_keyboard,
extra_groups_builder=None,
back_value="my_events",
back_label="⬅️ К моим мероприятиям",
enter_code_media_id="event_menu_enter_code",
sign_up_media_id="event_menu_sign_up",
sign_out_media_id="event_menu_sign_out",
),
}
def get_ui(ctx_name: str) -> EventUIContext:
return _UI.get(ctx_name, _UI["events"])
def build_back_keyboard(ctx_name: str) -> list[InteractiveMediaGroup]:
ui = get_ui(ctx_name)
return MediaGroupBuilder(
[Button(media_id="leave", value=ui.back_value, label=ui.back_label)]
).build()
def split_event_value(raw: Any) -> tuple[str, str | None]:
"""
ожидаем:
- "<event_id>"
- "<event_id>|<ctx>"
"""
s = str(raw or "").strip()
if "|" in s:
ev_id, ctx = s.split("|", 1)
return ev_id.strip(), (ctx.strip() or None)
return s, None
def can_manage_event(ev: EventSchema, user: UserSchema | None) -> bool:
if getattr(user, "role", None) == "admin" and ADMIN_CAN_MANAGE_OTHERS:
return True
uid = getattr(user, "id", None)
if not uid:
return False
return bool(getattr(ev, "creator_id", None)) and (str(ev.creator_id) == str(uid))
def can_view_code(ev: EventSchema, user: UserSchema | None) -> bool:
# кто может управлять - тот видит код
return can_manage_event(ev, user)
def _send_events_list_screen(
peer,
*,
ui: EventUIContext,
offset: int,
limit: int,
page_events: list[EventSchema],
total: int | None,
has_prev: bool,
has_next: bool,
note: str | None = None,
header: str | None = None,
empty_text: str | None = None,
empty_groups: list[InteractiveMediaGroup] | None = None,
):
prefix = f"{note}\n\n" if note else ""
header_part = header or ""
if not page_events:
text = (
prefix
+ header_part
+ (
empty_text
or "⚠️ Мероприятий не найдено.\n\nПопробуй изменить критерии или вернитесь назад."
)
)
im: list[InteractiveMediaGroup] = []
footer: list[InteractiveMediaGroup] = []
if empty_groups is not None:
footer = empty_groups
elif ui.extra_groups_builder:
footer = ui.extra_groups_builder()
im += ui.pagination_builder(
offset=offset, limit=limit, has_prev=has_prev, has_next=has_next
)
im += footer
bot.messaging.send_message(peer=peer, text=text, interactive_media_groups=im)
return
total_str = str(total) if total is not None else "?"
text = (
prefix
+ header_part
+ (
"📅 Мероприятия\n"
f"Показаны: {offset + 1} — {offset + len(page_events)} из {total_str}\n\n"
"Выбери мероприятие:"
)
)
im: list[InteractiveMediaGroup] = []
im += events_select_keyboard(
page_events, open_media_id=ui.list_open_media_id, start_index=offset + 1, per_row=1
)
im += ui.pagination_builder(offset=offset, limit=limit, has_prev=has_prev, has_next=has_next)
if ui.extra_groups_builder:
im += ui.extra_groups_builder()
bot.messaging.send_message(peer=peer, text=text, interactive_media_groups=im)
def send_city_events_page(
peer,
*,
offset: int,
event_service: EventService,
ctx_name: str,
city_id: str = DEFAULT_CITY_UUID,
note: str | None = None,
):
ui = get_ui(ctx_name)
limit = EVENTS_PAGE_SIZE
page_events, has_prev, has_next, total = fetch_events_page(
event_service=event_service, city_id=city_id, offset=offset, limit=limit
)
_send_events_list_screen(
peer,
ui=ui,
offset=offset,
limit=limit,
page_events=page_events,
total=total,
has_prev=has_prev,
has_next=has_next,
note=note,
)
def send_points_page(peer, *, offset: int, event_service: EventService):
limit = EVENTS_PAGE_SIZE
claimed_raw, total = event_service.get_claimed_events_page(
messenger_id=peer.id,
city_id=DEFAULT_CITY_UUID,
offset=offset,
limit=limit + 1,
)
points_total = "?"
_send_events_list_screen(
peer,
ui=get_ui("points"),
offset=offset,
limit=limit,
page_events=claimed_raw[:limit],
total=total,
has_prev=offset > 0,
has_next=len(claimed_raw) > limit,
header=f"🏆 Твои баллы: {points_total}\n\n",
empty_text=(
"Пока нет мероприятий, за которые уже начислены баллы.\n\n"
"Как получить баллы:\n"
"1) Запишитесь на мероприятие\n"
"2) После участия введи код\n"
),
empty_groups=points_menu_keyboard(),
)
def send_my_events_page(
peer,
*,
offset: int,
event_service: EventService,
requester_messenger_id: int,
city_id: str = DEFAULT_CITY_UUID,
note: str | None = None,
):
ui = get_ui("my_events")
limit = EVENTS_PAGE_SIZE
page_events, has_prev, has_next, total = fetch_events_page(
event_service=event_service,
city_id=city_id,
offset=offset,
limit=limit,
requester_messenger_id=requester_messenger_id,
only_organizer=True,
)
_send_events_list_screen(
peer,
ui=ui,
offset=offset,
limit=limit,
page_events=page_events,
total=total,
has_prev=has_prev,
has_next=has_next,
note=note,
header="📋 Мои мероприятия\n\n",
empty_text="Пока у тебя нет созданных мероприятий.",
)
def send_event_card(
peer,
*,
event_id: str,
event_service: EventService,
report_service: ReportService | None = None,
user: UserSchema | None,
ctx_name: str,
note: str | None = None,
show_organizer: bool = True,
):
ui = get_ui(ctx_name)
ev = event_service.get_event_by_id(event_id)
if ev is None:
text = f"{note}\n\n⚠️ Мероприятие не найдено." if note else "⚠️ Мероприятие не найдено."
bot.messaging.send_message(
peer=peer,
text=text,
interactive_media_groups=build_back_keyboard(ctx_name),
)
return
part = event_service.get_participation_status(ev.id, peer.id)
is_participant = part is not None
can_manage = can_manage_event(ev, user)
show_code = can_view_code(ev, user)
role = getattr(user, "role", None) if user else None
can_access_reports = (role in ("admin", "moderator")) or can_manage
report_exists: bool | None = None
if can_access_reports and report_service is not None:
report_exists = report_service.report_exists(
event_id=str(ev.id),
requester_messenger_id=peer.id,
)
else:
report_exists = None
can_create_report = bool(can_manage and (report_exists is False))
can_view_report = bool(can_access_reports and (report_exists is True))
base = format_event_details(
ev,
show_code=show_code,
show_organizer=show_organizer,
show_report_status=bool(can_access_reports and report_exists is not None),
report_exists=bool(report_exists) if report_exists is not None else None,
)
text = f"{note}\n\n{base}" if note else base
bot.messaging.send_message(
peer=peer,
text=text,
interactive_media_groups=event_actions_keyboard(
ev.id,
is_participant=is_participant,
can_manage=can_manage,
back_value=ui.back_value,
back_label=ui.back_label,
enter_code_media_id=ui.enter_code_media_id,
sign_up_media_id=ui.sign_up_media_id,
sign_out_media_id=ui.sign_out_media_id,
ctx=ui.name, # важно: чтобы value был "<event_id>|<ctx>"
can_create_report=can_create_report,
can_view_report=can_view_report,
),
)
def build_back_to_event_keyboard(event_id: str, ctx_name: str) -> list[InteractiveMediaGroup]:
ui = get_ui(ctx_name)
return MediaGroupBuilder(
[
Button(
media_id=ui.list_open_media_id,
value=f"{event_id}|{ui.name}",
label="⬅️ К мероприятию",
)
]
).build()
Editor is loading...
Leave a Comment