Untitled

4ae4d avatar
4ae4d
plain_text
02/10/2026 8:39 AM
51.4 KB
9
Indexable
--- src/bot/core/handlers/events_filters.py ---
from dialog_bot_sdk.entities.messaging import UpdateInteractiveMediaEvent

from core.bot_kit.fsm import FSMContext, State, StatesGroup
from core.config import bot
from core.handlers.events_ui import send_city_events_page, send_my_events_page, send_points_page
from core.markups.events_filters import events_filters_city_keyboard, events_filters_menu_keyboard
from core.markups.tags import TagChoice
from core.services.city import CityService
from core.services.event import EventService
from core.services.tag import TagService
from core.utils import delete_prev_message_by_peer
from core.utils.events_filter import (
    format_events_filter_summary,
    get_events_filter,
    reset_events_filter,
    set_events_filter,
)
from core.utils.tags_toggle_ui import TagsToggleCfg, is_done, parse_toggle_key, send_tags_toggle_ui


class EventsFiltersTagsState(StatesGroup):
    tags = State()


class EventsFiltersCityState(StatesGroup):
    city = State()


FILTERS_TAGS_CFG = TagsToggleCfg(
    media_id="events_filters_tags_toggle",
    toggle_prefix="toggle:",
    done_value="done",
)

_CTX_KEY = "events_filters_edit_ctx"
_OFFSET_KEY = "events_filters_edit_offset"
_TAG_IDS_KEY = "events_filters_edit_tags_selected"

_CITIES_OFFSET_KEY = "events_filters_city_offset"
_CITY_SELECTED_ID_KEY = "events_filters_city_selected_id"
_CITY_SELECTED_NAME_KEY = "events_filters_city_selected_name"

_CITIES_PAGE_LIMIT = 10


def _split_ctx_offset(raw: object) -> tuple[str, int]:
    s = str(raw or "").strip()
    if "|" not in s:
        return (s or "events", 0)
    a, b = s.split("|", 1)
    ctx = (a or "events").strip()
    try:
        off = int(str(b).strip())
        if off < 0:
            off = 0
    except Exception:
        off = 0
    return ctx, off


def _send_list(
    *, peer, ctx_name: str, offset: int, context: FSMContext, event_service: EventService
):
    # apply/back используют эту маршрутизацию
    if ctx_name == "points":
        return send_points_page(peer, offset=offset, event_service=event_service, context=context)
    if ctx_name == "my_events":
        return send_my_events_page(
            peer,
            offset=offset,
            event_service=event_service,
            requester_messenger_id=peer.id,
            context=context,
        )
    # events / moderation
    return send_city_events_page(
        peer,
        offset=offset,
        event_service=event_service,
        ctx_name=ctx_name,
        context=context,
    )


@bot.di
def events_filters_open_handler(
    event: UpdateInteractiveMediaEvent, context: FSMContext, event_service: EventService
):
    delete_prev_message_by_peer(bot, event.peer)
    context.set_state(None)

    ctx_name, offset = _split_ctx_offset(event.data.value)
    flt = get_events_filter(context, ctx_name=ctx_name)
    summary = format_events_filter_summary(flt)

    bot.messaging.send_message(
        peer=event.peer,
        text=f"🧰 *Фильтры*\n\n{summary}\n\nВыбери, что изменить:",
        interactive_media_groups=events_filters_menu_keyboard(ctx_name=ctx_name, offset=offset),
    )


@bot.di
def events_filters_reset_handler(
    event: UpdateInteractiveMediaEvent, context: FSMContext, event_service: EventService
):
    delete_prev_message_by_peer(bot, event.peer)
    context.set_state(None)

    ctx_name, offset = _split_ctx_offset(event.data.value)
    reset_events_filter(context, ctx_name=ctx_name)

    # остаёмся в меню, чтобы пользователь видел что “сбросилось”
    flt = get_events_filter(context, ctx_name=ctx_name)
    summary = format_events_filter_summary(flt)
    bot.messaging.send_message(
        peer=event.peer,
        text=f"🧰 *Фильтры*\n\n{summary}\n\nВыбери, что изменить:",
        interactive_media_groups=events_filters_menu_keyboard(ctx_name=ctx_name, offset=offset),
    )


@bot.di
def events_filters_apply_handler(
    event: UpdateInteractiveMediaEvent, context: FSMContext, event_service: EventService
):
    delete_prev_message_by_peer(bot, event.peer)
    context.set_state(None)

    ctx_name, _offset = _split_ctx_offset(event.data.value)
    # безопаснее возвращать на первую страницу (фильтр мог сузить выдачу)
    _send_list(
        peer=event.peer, ctx_name=ctx_name, offset=0, context=context, event_service=event_service
    )


@bot.di
def events_filters_back_handler(
    event: UpdateInteractiveMediaEvent, context: FSMContext, event_service: EventService
):
    delete_prev_message_by_peer(bot, event.peer)
    context.set_state(None)

    ctx_name, offset = _split_ctx_offset(event.data.value)
    _send_list(
        peer=event.peer,
        ctx_name=ctx_name,
        offset=offset,
        context=context,
        event_service=event_service,
    )


@bot.di
def events_filters_not_implemented_handler(event: UpdateInteractiveMediaEvent, context: FSMContext):
    # handlers для tags/city/date/mode до следующих коммитов
    delete_prev_message_by_peer(bot, event.peer)
    context.set_state(None)

    ctx_name, offset = _split_ctx_offset(event.data.value)
    flt = get_events_filter(context, ctx_name=ctx_name)
    summary = format_events_filter_summary(flt)
    bot.messaging.send_message(
        peer=event.peer,
        text=f"⚠️ Пока не реализовано в этом коммите.\n\n{summary}\n\nВыбери, что изменить:",
        interactive_media_groups=events_filters_menu_keyboard(ctx_name=ctx_name, offset=offset),
    )


def _tag_choices_from_backend(all_tags) -> list[TagChoice]:
    out: list[TagChoice] = []
    for t in all_tags or []:
        tid = getattr(t, "id", None)
        name = getattr(t, "name", None)
        if tid and name:
            out.append(TagChoice(key=str(tid), label=str(name)))
    return out


@bot.di
def events_filters_tags_open_handler(
    event: UpdateInteractiveMediaEvent,
    context: FSMContext,
    event_service: EventService,
    tag_service: TagService,
):
    delete_prev_message_by_peer(bot, event.peer)
    context.set_state(None)

    ctx_name, offset = _split_ctx_offset(event.data.value)
    flt = get_events_filter(context, ctx_name=ctx_name)
    selected_ids = set(flt.tag_ids or [])

    all_tags = tag_service.get_all_tags()
    choices = _tag_choices_from_backend(all_tags)

    # сохраняем "куда вернуться"
    context.update_data(
        {
            _CTX_KEY: ctx_name,
            _OFFSET_KEY: int(offset),
            _TAG_IDS_KEY: list(selected_ids),
        }
    )
    context.set_state(EventsFiltersTagsState.tags)

    send_tags_toggle_ui(
        peer=event.peer,
        title="🏷️ *Фильтры: теги*\n\nВыбери теги, затем нажми «Готово»:",
        choices=choices,
        selected_keys=set(selected_ids),
        cfg=FILTERS_TAGS_CFG,
        done_label="✅ Готово",
    )


@bot.di
def events_filters_tags_toggle_handler(
    event: UpdateInteractiveMediaEvent,
    context: FSMContext,
    event_service: EventService,
    tag_service: TagService,
):
    state = context.get_state()
    if not (state == EventsFiltersTagsState.tags or str(state) == str(EventsFiltersTagsState.tags)):
        return

    delete_prev_message_by_peer(bot, event.peer)

    raw = str(event.data.value or "")
    data = context.get_data() or {}

    ctx_name = str(data.get(_CTX_KEY) or "events")
    offset = int(data.get(_OFFSET_KEY) or 0)
    selected = set(data.get(_TAG_IDS_KEY) or [])

    # DONE -> сохранить filter.tag_ids и вернуться в меню фильтров
    if is_done(raw, FILTERS_TAGS_CFG):
        flt = get_events_filter(context, ctx_name=ctx_name)
        flt.tag_ids = sorted([str(x) for x in selected if str(x).strip()])

        all_tags = tag_service.get_all_tags()
        id_to_name = {
            str(getattr(t, "id", "")).strip(): str(getattr(t, "name", "")).strip()
            for t in (all_tags or [])
            if str(getattr(t, "id", "")).strip() and str(getattr(t, "name", "")).strip()
        }
        flt.tag_names = [id_to_name[i] for i in flt.tag_ids if i in id_to_name]
        set_events_filter(context, ctx_name=ctx_name, flt=flt)

        context.set_state(None)
        # чистим только "служебные" ключи экрана выбора тегов
        context.update_data({_CTX_KEY: None, _OFFSET_KEY: None, _TAG_IDS_KEY: None})

        summary = format_events_filter_summary(get_events_filter(context, ctx_name=ctx_name))
        bot.messaging.send_message(
            peer=event.peer,
            text=f"🧰 *Фильтры*\n\n{summary}\n\nВыбери, что изменить:",
            interactive_media_groups=events_filters_menu_keyboard(ctx_name=ctx_name, offset=offset),
        )
        return

    # TOGGLE
    key = parse_toggle_key(raw, FILTERS_TAGS_CFG)
    if not key:
        return

    if key in selected:
        selected.remove(key)
    else:
        selected.add(key)

    context.update_data({_TAG_IDS_KEY: list(selected)})

    all_tags = tag_service.get_all_tags()
    choices = _tag_choices_from_backend(all_tags)

    send_tags_toggle_ui(
        peer=event.peer,
        title="🏷️ *Фильтры: теги*\n\nВыбери теги, затем нажми «Готово»:",
        choices=choices,
        selected_keys=set(selected),
        cfg=FILTERS_TAGS_CFG,
        done_label="✅ Готово",
    )


def _safe_int(value: object, default: int = 0) -> int:
    try:
        if value is None:
            return default
        n = int(value)
        return n if n >= 0 else default
    except Exception:
        return default


def _send_city_picker(
    *,
    peer,
    ctx_name: str,
    back_offset: int,
    cities_offset: int,
    selected_city_id: str | None,
    city_service: CityService,
):
    cities = city_service.get_all(offset=cities_offset, limit=_CITIES_PAGE_LIMIT)
    has_prev = cities_offset > 0
    has_next = len(cities) >= _CITIES_PAGE_LIMIT

    bot.messaging.send_message(
        peer=peer,
        text="🏙️ *Фильтры: город*\n\nВыбери один город и нажми «Готово»:",
        interactive_media_groups=events_filters_city_keyboard(
            ctx_name=ctx_name,
            back_offset=back_offset,
            cities=cities,
            cities_offset=cities_offset,
            limit=_CITIES_PAGE_LIMIT,
            selected_city_id=selected_city_id,
            has_prev=has_prev,
            has_next=has_next,
        ),
    )


@bot.di
def events_filters_city_open_handler(
    event: UpdateInteractiveMediaEvent,
    context: FSMContext,
    event_service: EventService,
    city_service: CityService,
):
    delete_prev_message_by_peer(bot, event.peer)
    context.set_state(None)

    ctx_name, back_offset = _split_ctx_offset(event.data.value)
    flt = get_events_filter(context, ctx_name=ctx_name)

    # сохраняем "куда вернуться" + текущий выбор
    context.update_data(
        {
            _CTX_KEY: ctx_name,
            _OFFSET_KEY: int(back_offset),
            _CITIES_OFFSET_KEY: 0,
            _CITY_SELECTED_ID_KEY: flt.city_id,
            _CITY_SELECTED_NAME_KEY: flt.city_name,
        }
    )
    context.set_state(EventsFiltersCityState.city)

    _send_city_picker(
        peer=event.peer,
        ctx_name=ctx_name,
        back_offset=back_offset,
        cities_offset=0,
        selected_city_id=flt.city_id,
        city_service=city_service,
    )


@bot.di
def events_filters_city_select_handler(
    event: UpdateInteractiveMediaEvent,
    context: FSMContext,
    event_service: EventService,
    city_service: CityService,
):
    state = context.get_state()
    if not (state == EventsFiltersCityState.city or str(state) == str(EventsFiltersCityState.city)):
        return

    delete_prev_message_by_peer(bot, event.peer)

    raw = str(event.data.value or "").strip()
    data = context.get_data() or {}

    ctx_name = str(data.get(_CTX_KEY) or "events")
    back_offset = _safe_int(data.get(_OFFSET_KEY), 0)
    cities_offset = _safe_int(data.get(_CITIES_OFFSET_KEY), 0)
    # selected_city_id = data.get(_CITY_SELECTED_ID_KEY)

    # BACK / DONE несут ctx|offset
    if raw.startswith("back:"):
        _ctx, _off = _split_ctx_offset(raw[len("back:") :])
        summary = format_events_filter_summary(get_events_filter(context, ctx_name=_ctx))
        context.set_state(None)
        context.update_data(
            {
                _CTX_KEY: None,
                _OFFSET_KEY: None,
                _CITIES_OFFSET_KEY: None,
                _CITY_SELECTED_ID_KEY: None,
                _CITY_SELECTED_NAME_KEY: None,
            }
        )
        bot.messaging.send_message(
            peer=event.peer,
            text=f"🧰 *Фильтры*\n\n{summary}\n\nВыбери, что изменить:",
            interactive_media_groups=events_filters_menu_keyboard(ctx_name=_ctx, offset=_off),
        )
        return

    if raw.startswith("done:"):
        _ctx, _off = _split_ctx_offset(raw[len("done:") :])
        sel_id = str(data.get(_CITY_SELECTED_ID_KEY) or "").strip() or None
        sel_name = str(data.get(_CITY_SELECTED_NAME_KEY) or "").strip() or None

        flt = get_events_filter(context, ctx_name=_ctx)
        flt.city_id = sel_id
        flt.city_name = sel_name
        set_events_filter(context, ctx_name=_ctx, flt=flt)

        summary = format_events_filter_summary(get_events_filter(context, ctx_name=_ctx))
        context.set_state(None)
        context.update_data(
            {
                _CTX_KEY: None,
                _OFFSET_KEY: None,
                _CITIES_OFFSET_KEY: None,
                _CITY_SELECTED_ID_KEY: None,
                _CITY_SELECTED_NAME_KEY: None,
            }
        )
        bot.messaging.send_message(
            peer=event.peer,
            text=f"🧰 *Фильтры*\n\n{summary}\n\nВыбери, что изменить:",
            interactive_media_groups=events_filters_menu_keyboard(ctx_name=_ctx, offset=_off),
        )
        return

    # CLEAR
    if raw == "clear":
        context.update_data({_CITY_SELECTED_ID_KEY: None, _CITY_SELECTED_NAME_KEY: None})
        _send_city_picker(
            peer=event.peer,
            ctx_name=ctx_name,
            back_offset=back_offset,
            cities_offset=cities_offset,
            selected_city_id=None,
            city_service=city_service,
        )
        return

    # PAGE
    if raw.startswith("page:"):
        new_off = _safe_int(raw[len("page:") :], 0)
        context.update_data({_CITIES_OFFSET_KEY: new_off})
        _send_city_picker(
            peer=event.peer,
            ctx_name=ctx_name,
            back_offset=back_offset,
            cities_offset=new_off,
            selected_city_id=str(data.get(_CITY_SELECTED_ID_KEY) or "").strip() or None,
            city_service=city_service,
        )
        return

    # SELECT
    if raw.startswith("select:"):
        cid = raw[len("select:") :].strip()
        if not cid:
            return
        city = city_service.get_by_id(cid)
        context.update_data(
            {
                _CITY_SELECTED_ID_KEY: cid,
                _CITY_SELECTED_NAME_KEY: (city.name if city else None),
            }
        )
        _send_city_picker(
            peer=event.peer,
            ctx_name=ctx_name,
            back_offset=back_offset,
            cities_offset=cities_offset,
            selected_city_id=cid,
            city_service=city_service,
        )
        return

--- src/bot/core/markups/events_filters.py ---
from dialog_bot_sdk.interactive_media import Button, InteractiveMediaGroup, MediaGroupBuilder

from core.schemas.city import CitySchema


def events_filters_button_keyboard(*, ctx_name: str, offset: int) -> list[InteractiveMediaGroup]:
    return MediaGroupBuilder(
        [Button(media_id="events_filters_open", value=f"{ctx_name}|{offset}", label="🧰 Фильтры")]
    ).build()


def events_filters_menu_keyboard(*, ctx_name: str, offset: int) -> list[InteractiveMediaGroup]:
    # value всегда "ctx|offset" чтобы назад возвращаться туда же
    v = f"{ctx_name}|{offset}"
    top = MediaGroupBuilder(
        [
            Button(media_id="events_filters_tags", value=v, label="🏷️ Теги"),
            Button(media_id="events_filters_city", value=v, label="🏙️ Город"),
        ]
    ).build()
    mid = MediaGroupBuilder(
        [
            Button(media_id="events_filters_date", value=v, label="🗓️ Дата/время"),
            Button(media_id="events_filters_mode", value=v, label="🎛️ Режим"),
        ]
    ).build()
    bottom = MediaGroupBuilder(
        [
            Button(media_id="events_filters_apply", value=v, label="✅ Применить"),
            Button(media_id="events_filters_reset", value=v, label="♻️ Сбросить"),
            Button(media_id="events_filters_back", value=v, label="⬅️ Назад"),
        ]
    ).build()
    return top + mid + bottom


def events_filters_city_keyboard(
    *,
    ctx_name: str,
    back_offset: int,
    cities: list[CitySchema],
    cities_offset: int,
    limit: int,
    selected_city_id: str | None,
    has_prev: bool,
    has_next: bool,
) -> list[InteractiveMediaGroup]:
    groups: list[InteractiveMediaGroup] = []

    for c in cities or []:
        cid = str(getattr(c, "id", "") or "").strip()
        name = str(getattr(c, "name", "") or "").strip()
        if not cid or not name:
            continue
        mark = "✅ " if (selected_city_id and str(selected_city_id) == cid) else ""
        groups += MediaGroupBuilder(
            [
                Button(
                    media_id="events_filters_city_select",
                    value=f"select:{cid}",
                    label=mark + name,
                )
            ]
        ).build()

    if has_prev:
        groups += MediaGroupBuilder(
            [
                Button(
                    media_id="events_filters_city_select",
                    value=f"page:{max(cities_offset - limit, 0)}",
                    label="⬅️",
                )
            ]
        ).build()
    if has_next:
        groups += MediaGroupBuilder(
            [
                Button(
                    media_id="events_filters_city_select",
                    value=f"page:{cities_offset + limit}",
                    label="➡️",
                )
            ]
        ).build()

    groups += MediaGroupBuilder(
        [Button(media_id="events_filters_city_select", value="clear", label="Без города")]
    ).build()
    groups += MediaGroupBuilder(
        [
            Button(
                media_id="events_filters_city_select",
                value=f"done:{ctx_name}|{back_offset}",
                label="✅ Готово",
            )
        ]
    ).build()
    groups += MediaGroupBuilder(
        [
            Button(
                media_id="events_filters_city_select",
                value=f"back:{ctx_name}|{back_offset}",
                label="⬅️ Назад",
            )
        ]
    ).build()

    return groups

--- src/bot/core/utils/events_filter.py ---
from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, Literal

EventsMode = Literal["all", "participant", "organizer"]


FILTERS_CTX_KEY = "events_filters_v1"


@dataclass(slots=True)
class EventsFilter:
    """
    фильтры на стороне бота (храним в fsmcontext.data).

    - date_from/date_to сейчас не поддержаны backend (/events/search), поэтому
      они участвуют только в UI/summary, но не уходят в query params.
    """

    city_id: str | None = None
    city_name: str | None = None
    tag_ids: list[str] = field(default_factory=list)
    tag_names: list[str] = field(default_factory=list)
    mode: EventsMode = "all"

    # UI-only (пока не поддержано backend)
    date_from: str | None = None
    date_to: str | None = None

    @classmethod
    def from_dict(cls, raw: Any) -> EventsFilter:
        if not isinstance(raw, dict):
            return cls()

        mode = raw.get("mode") or "all"
        if mode not in ("all", "participant", "organizer"):
            mode = "all"

        city_id = raw.get("city_id")
        city_id = str(city_id) if city_id else None

        city_name = raw.get("city_name")
        city_name = str(city_name) if city_name else None

        tag_ids_raw = raw.get("tag_ids") or []
        if not isinstance(tag_ids_raw, list):
            tag_ids_raw = []
        tag_ids = [str(x) for x in tag_ids_raw if str(x).strip()]

        tag_names_raw = raw.get("tag_names") or []
        if not isinstance(tag_names_raw, list):
            tag_names_raw = []
        tag_names = [str(x) for x in tag_names_raw if str(x).strip()]

        date_from = raw.get("date_from")
        date_from = str(date_from) if date_from else None
        date_to = raw.get("date_to")
        date_to = str(date_to) if date_to else None

        return cls(
            city_id=city_id,
            city_name=city_name,
            tag_ids=tag_ids,
            tag_names=tag_names,
            mode=mode,  # type: ignore[assignment]
            date_from=date_from,
            date_to=date_to,
        )

    def to_dict(self) -> dict[str, Any]:
        return {
            "city_id": self.city_id,
            "city_name": self.city_name,
            "tag_ids": list(self.tag_ids or []),
            "tag_names": list(self.tag_names or []),
            "mode": self.mode,
            "date_from": self.date_from,
            "date_to": self.date_to,
        }


@dataclass(frozen=True, slots=True)
class EventsSearchBase:
    """
    базовые параметры конкретного списка (контекст):
    - например points всегда only_claimed=True
    - my_events (организатор) может фиксировать режим
    """

    city_id: str | None
    requester_messenger_id: int | None

    only_claimed: bool = False
    only_organizer: bool = False
    only_participant: bool = False
    has_report: bool | None = None

    tag_ids: list[str] = field(default_factory=list)

    # если True — mode из EventsFilter игнорируется
    lock_mode: bool = False


def build_events_search_params(
    *,
    base: EventsSearchBase,
    flt: EventsFilter | None,
    offset: int,
    limit: int,
) -> dict[str, Any]:
    """собираем kwargs для EventService.search_events_with_total(...)"""

    f = flt or EventsFilter()

    # city/tags: если в фильтре задано — переопределяет базу
    city_id = f.city_id if f.city_id else base.city_id
    tag_ids = f.tag_ids if f.tag_ids else (base.tag_ids or [])

    only_claimed = bool(base.only_claimed)
    only_organizer = bool(base.only_organizer)
    only_participant = bool(base.only_participant)

    # mode: работаем только с organizer/participant
    if not base.lock_mode:
        if f.mode == "organizer":
            only_organizer = True
            only_participant = False
        elif f.mode == "participant":
            only_participant = True
            only_organizer = False
        else:
            # all
            # оставляем только то, что задано базой
            pass

    params: dict[str, Any] = {
        "offset": int(offset),
        "limit": int(limit),
        "only_claimed": bool(only_claimed),
        "only_organizer": bool(only_organizer),
        "only_participant": bool(only_participant),
    }
    if city_id is not None:
        params["city_id"] = str(city_id)
    if base.requester_messenger_id is not None:
        params["requester_messenger_id"] = int(base.requester_messenger_id)
    if base.has_report is not None:
        params["has_report"] = bool(base.has_report)
    if tag_ids:
        params["tag_ids"] = list(tag_ids)

    return params


def format_events_filter_summary(flt: EventsFilter | None) -> str:
    f = flt or EventsFilter()
    parts: list[str] = []

    if f.tag_names:
        names = [str(x).strip() for x in f.tag_names if str(x).strip()]
        parts.append("теги: " + ", ".join(names))
    elif f.tag_ids:
        parts.append(f"теги: {len(f.tag_ids)}")

    if f.mode == "participant":
        parts.append("режим: участвую")
    elif f.mode == "organizer":
        parts.append("режим: организую")

    if f.city_id:
        if f.city_name:
            parts.append(f"город: {f.city_name}...")
        else:
            parts.append(f"город: {str(f.city_id)[:8]}...")

    a = f.date_from or "..."
    b = f.date_to or "..."
    parts.append(f"даты: {a} — {b}")

    if not parts:
        return "Фильтр\nбез доп. условий"
    return "Фильтр\n" + "\n".join(parts)


def get_events_filter(context, *, ctx_name: str) -> EventsFilter:
    data = (context.get_data() or {}).get(FILTERS_CTX_KEY) or {}
    if not isinstance(data, dict):
        return EventsFilter()
    return EventsFilter.from_dict(data.get(str(ctx_name)))


def set_events_filter(context, *, ctx_name: str, flt: EventsFilter) -> None:
    raw = (context.get_data() or {}).get(FILTERS_CTX_KEY) or {}
    data = dict(raw) if isinstance(raw, dict) else {}
    data[str(ctx_name)] = flt.to_dict()
    context.update_data({FILTERS_CTX_KEY: data})


def reset_events_filter(context, *, ctx_name: str) -> None:
    raw = (context.get_data() or {}).get(FILTERS_CTX_KEY) or {}
    data = dict(raw) if isinstance(raw, dict) else {}
    data.pop(str(ctx_name), None)
    context.update_data({FILTERS_CTX_KEY: data})


def clear_context_keep_events_filters(context) -> None:
    """
    нужен для мест, где раньше делали context.clear(),
    но теперь фильтры должны переживать пагинацию
    """
    data = context.get_data() or {}
    keep = data.get(FILTERS_CTX_KEY)
    context.clear()
    if keep is not None:
        context.update_data({FILTERS_CTX_KEY: keep})

--- src/bot/main.py ---
from core.bot_kit.router import Router
from core.config import bot
from core.handlers import (
    admin_menu_handler,
    ai_assistant_handler,
    all_events_handler,
    all_events_open_handler,
    all_events_page_handler,
    create_event_start_handler,
    event_menu_delete_handler,
    event_menu_edit_field_handler,
    event_menu_edit_handler,
    event_menu_enter_code_start_handler,
    event_menu_sign_out_handler,
    event_menu_sign_up_handler,
    event_participants_open_handler,
    event_tags_toggle_handler,
    event_user_enter_code_start_handler,
    event_user_sign_out_handler,
    event_user_sign_up_handler,
    event_view_by_id_start,
    events_filters_apply_handler,
    events_filters_back_handler,
    events_filters_city_open_handler,
    events_filters_city_select_handler,
    events_filters_not_implemented_handler,
    events_filters_open_handler,
    events_filters_reset_handler,
    events_filters_tags_open_handler,
    events_filters_tags_toggle_handler,
    events_menu_handler,
    events_rt,
    main_menu_handler,
    moderation_menu_handler,
    my_events_handler,
    my_events_open_handler,
    my_events_page_handler,
    my_profile_handler,
    noop_handler,
    points_event_open_handler,
    points_leaderboard_handler,
    points_menu_handler,
    points_page_handler,
    points_rt,
    profile_tags_toggle_handler,
    report_create_start_handler,
    report_rt,
    report_view_handler,
    reports_export_handler,
    start_handler,
    update_user_interests_start,
    update_user_menu_handler,
    update_user_name_start,
    update_user_region_start,
    update_user_rt,
    user_events_open_handler,
    user_events_page_handler,
    user_events_rt,
    view_by_uuid_rt,
    view_by_uuid_start_handler,
    volunteer_home_handler,
)
from dialog_bot_sdk.entities.messaging import CommandHandler, EventHandler


def handlers_setting() -> None:
    router = Router()
    router.register(update_user_rt)
    router.register(events_rt)
    router.register(user_events_rt)
    router.register(points_rt)
    router.register(view_by_uuid_rt)
    router.register(report_rt)

    router.subscribe(bot)
    bot.messaging.command_handler(
        [
            CommandHandler(
                function=start_handler,
                command="start",
                description="Расскажу о себе",
            ),
            CommandHandler(
                function=reports_export_handler,
                command="reports_export",
                description="Выгрузка отчётов",
            ),
        ]
    )

    bot.messaging.event_handler(
        [
            EventHandler(function=volunteer_home_handler, value="volunteer_home"),
            EventHandler(function=main_menu_handler, value="main_menu"),
            EventHandler(function=ai_assistant_handler, value="ai_assistant"),
            EventHandler(function=my_profile_handler, value="my_profile"),
            EventHandler(function=update_user_menu_handler, value="update_profile_menu"),
            EventHandler(function=update_user_name_start, _id="update_user_name"),
            EventHandler(function=update_user_region_start, _id="update_user_region"),
            EventHandler(function=update_user_interests_start, _id="update_user_interests"),
            EventHandler(function=profile_tags_toggle_handler, _id="profile_tags_toggle"),
            EventHandler(function=points_menu_handler, value="points"),
            EventHandler(function=points_leaderboard_handler, value="points_leaderboard"),
            EventHandler(function=moderation_menu_handler, value="moderation"),
            EventHandler(function=reports_export_handler, value="download_reports_list"),
            EventHandler(function=all_events_handler, value="all_events"),
            EventHandler(function=events_menu_handler, value="events"),
            EventHandler(function=user_events_page_handler, _id="user_events_prev"),
            EventHandler(function=user_events_page_handler, _id="user_events_next"),
            EventHandler(function=event_user_sign_up_handler, _id="event_user_sign_up"),
            EventHandler(function=event_user_sign_out_handler, _id="event_user_sign_out"),
            EventHandler(function=event_user_enter_code_start_handler, _id="event_user_enter_code"),
            EventHandler(function=event_tags_toggle_handler, _id="event_tags_toggle"),
            EventHandler(function=event_participants_open_handler, _id="event_participants_open"),
            EventHandler(function=report_create_start_handler, _id="event_report_create"),
            EventHandler(function=report_view_handler, _id="event_report_view"),
            EventHandler(function=all_events_page_handler, _id="all_events_prev"),
            EventHandler(function=all_events_page_handler, _id="all_events_next"),
            EventHandler(function=my_events_handler, value="my_events"),
            EventHandler(function=my_events_page_handler, _id="my_events_prev"),
            EventHandler(function=my_events_page_handler, _id="my_events_next"),
            EventHandler(function=my_events_open_handler, _id="my_events_open"),
            EventHandler(function=create_event_start_handler, value="create_event"),
            EventHandler(function=event_view_by_id_start, value="update_event"),
            EventHandler(function=admin_menu_handler, value="administration"),
            EventHandler(function=event_menu_sign_up_handler, _id="event_menu_sign_up"),
            EventHandler(function=event_menu_sign_out_handler, _id="event_menu_sign_out"),
            EventHandler(function=event_menu_enter_code_start_handler, _id="event_menu_enter_code"),
            EventHandler(function=event_menu_delete_handler, _id="event_menu_delete"),
            EventHandler(function=event_menu_edit_handler, _id="event_menu_edit"),
            EventHandler(function=view_by_uuid_start_handler, _id="view_by_uuid"),
            EventHandler(function=event_menu_edit_field_handler, _id="event_menu_edit_name"),
            EventHandler(function=event_menu_edit_field_handler, _id="event_menu_edit_date"),
            EventHandler(function=event_menu_edit_field_handler, _id="event_menu_edit_time"),
            EventHandler(function=event_menu_edit_field_handler, _id="event_menu_edit_location"),
            EventHandler(function=event_menu_edit_field_handler, _id="event_menu_edit_tags"),
            EventHandler(function=event_menu_edit_field_handler, _id="event_menu_edit_creator_id"),
            EventHandler(function=event_menu_edit_field_handler, _id="event_menu_edit_points"),
            EventHandler(function=event_menu_edit_field_handler, _id="event_menu_edit_description"),
            EventHandler(function=event_menu_edit_field_handler, _id="event_menu_edit_bonus_code"),
            EventHandler(function=user_events_open_handler, _id="user_events_open"),
            EventHandler(function=all_events_open_handler, _id="all_events_open"),
            EventHandler(function=points_page_handler, _id="points_prev"),
            EventHandler(function=points_page_handler, _id="points_next"),
            EventHandler(function=points_event_open_handler, _id="points_event_open"),
            EventHandler(function=points_event_open_handler, _id="points_events_open"),
            EventHandler(function=events_filters_open_handler, _id="events_filters_open"),
            EventHandler(function=events_filters_apply_handler, _id="events_filters_apply"),
            EventHandler(function=events_filters_reset_handler, _id="events_filters_reset"),
            EventHandler(function=events_filters_back_handler, _id="events_filters_back"),
            EventHandler(function=events_filters_tags_open_handler, _id="events_filters_tags"),
            EventHandler(
                function=events_filters_tags_toggle_handler, _id="events_filters_tags_toggle"
            ),
            EventHandler(
                function=events_filters_city_select_handler, _id="events_filters_city_select"
            ),
            EventHandler(function=events_filters_city_open_handler, _id="events_filters_city"),
            EventHandler(
                function=events_filters_not_implemented_handler, _id="events_filters_date"
            ),
            EventHandler(
                function=events_filters_not_implemented_handler, _id="events_filters_mode"
            ),
            # TODO почистить
            EventHandler(function=noop_handler, _id="noop_prev"),
            EventHandler(function=noop_handler, _id="noop_mid"),
            EventHandler(function=noop_handler, _id="noop_next"),
            EventHandler(function=noop_handler, _id="noop_uuid"),
        ]
    )


def main() -> None:
    handlers_setting()
    bot.profile.edit_about_sync("Волонтёрский бот, который помогает сотрудникам Сбера нести добро!")
    bot.updates.on_updates(do_read_message=True, do_register_commands=True)


if __name__ == "__main__":
    main()

--- src/bot/core/handlers/__init__.py ---
from .admin import admin_menu_handler
from .ai import ai_assistant_handler
from .enter_event_code import rt as enter_code_rt
from .event import (
    event_participants_open_handler,
    event_user_enter_code_start_handler,
    event_user_sign_out_handler,
    event_user_sign_up_handler,
    events_menu_handler,
    user_events_open_handler,
    user_events_page_handler,
    user_events_rt,
)
from .events_filters import (
    events_filters_apply_handler,
    events_filters_back_handler,
    events_filters_city_open_handler,
    events_filters_city_select_handler,
    events_filters_not_implemented_handler,
    events_filters_open_handler,
    events_filters_reset_handler,
    events_filters_tags_open_handler,
    events_filters_tags_toggle_handler,
)
from .main import main_menu_handler, start_handler
from .moderation import (
    all_events_handler,
    all_events_open_handler,
    all_events_page_handler,
    create_event_start_handler,
    event_menu_delete_handler,
    event_menu_edit_field_handler,
    event_menu_edit_handler,
    event_menu_enter_code_start_handler,
    event_menu_sign_out_handler,
    event_menu_sign_up_handler,
    event_tags_toggle_handler,
    event_view_by_id_start,
    events_rt,
    moderation_menu_handler,
    my_events_handler,
    my_events_open_handler,
    my_events_page_handler,
)
from .noop import noop_handler
from .points import points_event_open_handler, points_menu_handler, points_page_handler, points_rt
from .profile import (
    my_profile_handler,
    profile_tags_toggle_handler,
    update_user_interests_start,
    update_user_menu_handler,
    update_user_name_start,
    update_user_region_start,
)
from .profile import rt as update_user_rt
from .report import (
    report_create_start_handler,
    report_rt,
    report_view_handler,
    reports_export_handler,
)
from .view_by_uuid import rt as view_by_uuid_rt
from .view_by_uuid import view_by_uuid_start_handler
from .volunteer_home import points_leaderboard_handler, volunteer_home_handler

__all__ = [
    "start_handler",
    "main_menu_handler",
    "volunteer_home_handler",
    "my_profile_handler",
    "ai_assistant_handler",
    "update_user_menu_handler",
    "update_user_name_start",
    "update_user_region_start",
    "update_user_interests_start",
    "profile_tags_toggle_handler",
    "points_menu_handler",
    "points_leaderboard_handler",
    "events_menu_handler",
    "points_page_handler",
    "points_event_open_handler",
    "points_rt",
    "view_by_uuid_start_handler",
    "moderation_menu_handler",
    "all_events_handler",
    "all_events_page_handler",
    "all_events_open_handler",
    "create_event_start_handler",
    "event_tags_toggle_handler",
    "event_view_by_id_start",
    "admin_menu_handler",
    "event_menu_sign_up_handler",
    "event_menu_sign_out_handler",
    "event_menu_enter_code_start_handler",
    "event_menu_delete_handler",
    "event_menu_edit_handler",
    "event_menu_edit_field_handler",
    "event_participants_open_handler",
    "user_events_page_handler",
    "user_events_open_handler",
    "event_user_sign_up_handler",
    "event_user_sign_out_handler",
    "event_user_enter_code_start_handler",
    "noop_handler",
    "reports_export_handler",
    "report_create_start_handler",
    "report_view_handler",
    "my_events_handler",
    "my_events_page_handler",
    "my_events_open_handler",
    "events_filters_apply_handler",
    "events_filters_back_handler",
    "events_filters_not_implemented_handler",
    "events_filters_open_handler",
    "events_filters_reset_handler",
    "events_filters_tags_open_handler",
    "events_filters_tags_toggle_handler",
    "events_filters_city_open_handler",
    "events_filters_city_select_handler",
    # Routers
    "user_events_rt",
    "enter_code_rt",
    "update_user_rt",
    "events_rt",
    "view_by_uuid_rt",
    "report_rt",
]

Editor is loading...
Leave a Comment