Untitled
4ae4d
plain_text
04/17/2026 9:24 AM
66.7 KB
8
Indexable
diff --git a/services/bot-new/core/handlers/moderation.py b/services/bot-new/core/handlers/moderation.py
index 60f7876..edbe4fb 100644
--- a/services/bot-new/core/handlers/moderation.py
+++ b/services/bot-new/core/handlers/moderation.py
@@ -1,1170 +1,1176 @@
import re
from datetime import datetime
from uuid import UUID
from dialog_bot_sdk.entities.messaging import UpdateInteractiveMediaEvent, UpdateMessage
from dialog_bot_sdk.interactive_media import Button
from core.bot_kit.fsm import FSMContext, State, StatesGroup
from core.bot_kit.router import Router
from core.config import bot
from core.handlers.events_ui import (
build_back_to_event_keyboard,
send_event_card,
send_event_cards_page,
send_my_events_page,
split_event_value,
)
from core.markups.event import event_actions_keyboard, format_event_details
from core.markups.tags import choices_from_backend_tags, tags_toggle_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
from core.services.tag import TagService
from core.utils import delete_prev_message, delete_prev_message_by_peer
from core.utils.events_filter import clear_context_keep_events_filters
from core.utils.logger import logger
from ..markups import (
back_to_moderation_keyboard,
event_edit_fields_keyboard,
moderation_menu_keyboard,
)
TAGS_MEDIA_ID = "event_tags_toggle"
TAGS_TOGGLE_PREFIX = "toggle:"
TAGS_DONE_VALUE = "done"
-DEFAULT_CITY_UUID = "be9da3ba-d-42f9-9ae2-9875c8625083" # SPB
# TODO убрать хардкод
events_rt = Router()
class EventEditTagsState(StatesGroup): # untested
tags = State()
class EventEditState(StatesGroup): # untested
wait_value = State()
class EventEnterCodeState(StatesGroup): # untested
wait_code = State()
class EventViewState(StatesGroup): # untested
wait_event_id = State()
class EventCreateState(StatesGroup): # untested
name = State()
date = State()
time = State()
# location удалён
- gosp = State()
- project = State()
+ gosp = State()
+ project = State()
description = State()
points = State()
tags = State()
code = State()
@bot.di
def moderation_menu_handler(event: UpdateInteractiveMediaEvent, context: FSMContext): # untested
clear_context_keep_events_filters(context)
delete_prev_message_by_peer(bot, event.peer)
bot.messaging.send_message(
peer=event.peer,
text=("Ты в доме модерации!\n\nПожалуйста, выбери, что хочешь сделать 👇"),
interactive_media_groups=moderation_menu_keyboard(),
)
@bot.di
def all_events_handler( # untested
event: UpdateInteractiveMediaEvent,
context: FSMContext,
event_service: EventService,
user: UserSchema | None,
):
delete_prev_message_by_peer(bot, event.peer)
clear_context_keep_events_filters(context)
send_event_cards_page(
event.peer,
offset=0,
event_service=event_service,
ctx_name="moderation",
context=context,
)
@bot.di
def my_events_handler( # untested
event: UpdateInteractiveMediaEvent,
context: FSMContext,
event_service: EventService,
user: UserSchema | None,
):
delete_prev_message_by_peer(bot, event.peer)
clear_context_keep_events_filters(context)
send_my_events_page(
event.peer,
offset=0,
event_service=event_service,
requester_messenger_id=event.peer.id,
context=context,
)
@bot.di
def my_events_page_handler( # untested
event: UpdateInteractiveMediaEvent,
context: FSMContext,
event_service: EventService,
user: UserSchema | None,
):
delete_prev_message_by_peer(bot, event.peer)
clear_context_keep_events_filters(context)
try:
offset = int(event.data.value)
if offset < 0:
offset = 0
except Exception:
offset = 0
send_my_events_page(
event.peer,
offset=offset,
event_service=event_service,
requester_messenger_id=event.peer.id,
context=context,
)
@bot.di
def my_events_open_handler( # untested
event: UpdateInteractiveMediaEvent,
context: FSMContext,
event_service: EventService,
report_service: ReportService,
user: UserSchema | None,
):
delete_prev_message_by_peer(bot, event.peer)
context.set_state(None)
event_id = str(event.data.value or "").strip()
send_event_card(
event.peer,
event_id=event_id,
event_service=event_service,
user=user,
ctx_name="my_events",
report_service=report_service,
)
@bot.di
def all_events_page_handler( # untested
event: UpdateInteractiveMediaEvent,
context: FSMContext,
event_service: EventService,
user: UserSchema | None,
):
delete_prev_message_by_peer(bot, event.peer)
clear_context_keep_events_filters(context)
try:
offset = int(event.data.value)
if offset < 0:
offset = 0
except Exception:
offset = 0
send_event_cards_page(
event.peer,
offset=offset,
event_service=event_service,
ctx_name="moderation",
context=context,
)
@bot.di
def all_events_open_handler( # untested
event: UpdateInteractiveMediaEvent,
context: FSMContext,
event_service: EventService,
report_service: ReportService,
user: UserSchema | None,
):
delete_prev_message_by_peer(bot, event.peer)
context.set_state(None)
event_id, ctx = split_event_value(event.data.value)
ctx_name = ctx or "moderation"
send_event_card(
event.peer,
event_id=event_id,
event_service=event_service,
user=user,
ctx_name=ctx_name,
report_service=report_service,
)
@bot.di
-def create_event_start_handler( # untested
+def create_event_start_handler(
event: UpdateInteractiveMediaEvent,
context: FSMContext,
):
"""старт мастера создания мероприятия"""
delete_prev_message_by_peer(bot, event.peer)
context.clear()
bot.messaging.send_message(
peer=event.peer,
text=("Создание мероприятия.\n\nШаг 1/9: введи, пожалуйста, название мероприятия:"),
interactive_media_groups=back_to_moderation_keyboard(),
)
context.set_state(EventCreateState.name)
@events_rt.message(state=EventCreateState.name)
@bot.di
-def event_create_name_step(message: UpdateMessage, context: FSMContext): # untested
+def event_create_name_step(message: UpdateMessage, context: FSMContext):
delete_prev_message(bot, message)
name = message.message.text_message.text.strip()
context.update_data({"event_name": name})
bot.messaging.send_message(
peer=message.peer,
text=("Шаг 2/9: введи, пожалуйста, дату мероприятия (dd.mm.yyyy)\nНапример: `20.02.2002`:"),
interactive_media_groups=back_to_moderation_keyboard(),
)
context.set_state(EventCreateState.date)
@events_rt.message(state=EventCreateState.date)
@bot.di
-def event_create_date_step(message: UpdateMessage, context: FSMContext): # untested
+def event_create_date_step(message: UpdateMessage, context: FSMContext):
delete_prev_message(bot, message)
date = message.message.text_message.text.strip()
try:
date_obj = datetime.strptime(date, "%d.%m.%Y").date()
+ # TODO make it try to recognize a lot of patterns
+ if date_obj <= datetime.now().date():
+ raise RuntimeError
except ValueError:
bot.messaging.send_message(
peer=message.peer,
text=(
"⚠️ Я не смог распознать дату.\n"
"Пожалуйста, введи в формате `dd.mm.yyyy`, например: `20.02.2002`:"
),
interactive_media_groups=back_to_moderation_keyboard(),
)
return
+ except RuntimeError:
+ bot.messaging.send_message(
+ peer=message.peer,
+ text=(
+ "⚠️ Ты не можешь создать мероприятие раньше завтрашнего дня.\n"
+ "Пожалуйста, введи в формате `dd.mm.yyyy`, например: `20.02.2002`:"
+ ),
+ interactive_media_groups=back_to_moderation_keyboard(),
+ )
+ return
context.update_data({"event_date": date_obj})
bot.messaging.send_message(
peer=message.peer,
text=("Шаг 3/9: введи время мероприятия в формате `HH:MM`\nНапример: `10:30`:"),
interactive_media_groups=back_to_moderation_keyboard(),
)
context.set_state(EventCreateState.time)
@events_rt.message(state=EventCreateState.time)
@bot.di
-def event_create_time_step(message: UpdateMessage, context: FSMContext): # untested
+def event_create_time_step(message: UpdateMessage, context: FSMContext):
delete_prev_message(bot, message)
time = message.message.text_message.text.strip()
try:
time_obj = datetime.strptime(time, "%H:%M").time()
+ # TODO make it try to recognize a lot of patterns
except ValueError:
bot.messaging.send_message(
peer=message.peer,
text=(
"⚠️ Я не смог распознать время.\n"
"Пожалуйста, введи в формате `HH:MM`, например: `10:30`:"
),
interactive_media_groups=back_to_moderation_keyboard(),
)
return
context.update_data({"event_time": time_obj})
- # Пропускаем локацию, сразу переходим к ГОСБ
bot.messaging.send_message(
peer=message.peer,
- text="Шаг 4/9: Введи ГОСБ (UUID или текст для отладки):\nНапример: `be9da3ba-d-42f9-9ae2-9875c8625083`",
+ text="Шаг 4/9: Введи айди ГОСБ'а:\nНапример: `gosb_spb_1`",
interactive_media_groups=back_to_moderation_keyboard(),
)
context.set_state(EventCreateState.gosp)
@events_rt.message(state=EventCreateState.gosp)
@bot.di
-def event_create_gosp_step(message: UpdateMessage, context: FSMContext): # untested
+def event_create_gosp_step(message: UpdateMessage, context: FSMContext):
delete_prev_message(bot, message)
gosp = message.message.text_message.text.strip()
context.update_data({"gosp_id": gosp})
bot.messaging.send_message(
peer=message.peer,
- text="Шаг 5/9: Введи id проекта (UUID или текст для отладки, можно пропустить отправив `-`):",
+ text=(
+ "Шаг 5/9: Введи id проекта, если хочешь создать мероприятие в проекте\n"
+ "Введи `-`, если хочешь создать событийное мероприятие - мероприятие мне проекта."
+ ),
interactive_media_groups=back_to_moderation_keyboard(),
)
context.set_state(EventCreateState.project)
@events_rt.message(state=EventCreateState.project)
@bot.di
-def event_create_project_step(message: UpdateMessage, context: FSMContext): # untested
+def event_create_project_step(message: UpdateMessage, context: FSMContext):
delete_prev_message(bot, message)
raw = message.message.text_message.text.strip()
project_id = "" if raw == "-" else raw
context.update_data({"project_id": project_id})
bot.messaging.send_message(
peer=message.peer,
- text='Шаг 6/9: Введи описание:',
+ text="Шаг 6/9: Введи описание:",
interactive_media_groups=back_to_moderation_keyboard(),
)
context.set_state(EventCreateState.description)
@events_rt.message(state=EventCreateState.description)
@bot.di
-def event_create_description_step(message: UpdateMessage, context: FSMContext): # untested
+def event_create_description_step(message: UpdateMessage, context: FSMContext):
delete_prev_message(bot, message)
description = message.message.text_message.text.strip()
context.update_data({"event_description": description})
bot.messaging.send_message(
peer=message.peer,
- text="Шаг 7/9: Введи количество очков начисляемых за мероприятие:",
+ text="Шаг 7/9: Введи количество волонтёрских часов, начисляемых за мероприятие:",
interactive_media_groups=back_to_moderation_keyboard(),
)
context.set_state(EventCreateState.points)
@events_rt.message(state=EventCreateState.points)
@bot.di
-def event_create_points_step(
- message: UpdateMessage, context: FSMContext, tag_service: TagService
-): # untested
+def event_create_points_step(message: UpdateMessage, context: FSMContext):
delete_prev_message(bot, message)
text = message.message.text_message.text.strip()
try:
points = int(text)
if points <= 0 or points >= 2**31:
raise ValueError
except ValueError:
bot.messaging.send_message(
peer=message.peer,
text=f"Количество баллов должно быть натуральным числом, меньшим {2**31}. Попробуй ещё раз.",
interactive_media_groups=back_to_moderation_keyboard(),
)
return
context.update_data({"event_points": points})
bot.messaging.send_message(
peer=message.peer,
text=(
- "Шаг 8/9: Введи теги мероприятия через пробел (UUID):\n"
- "Например: `a1b2c3d4-e5f6-7890-abcd-ef1234567890 f9e8d7c6-b5a4-3210-fedc-ba0987654321`\n"
+ "Шаг 8/9: Введи id тегов мероприятия через пробел:\n"
+ "Например: `tag_eco_1 tag_soc_1`\n"
"Можно пропустить, отправив `-`:"
),
interactive_media_groups=back_to_moderation_keyboard(),
)
context.set_state(EventCreateState.tags)
@events_rt.message(state=EventCreateState.tags)
@bot.di
-def event_create_tags_step(message: UpdateMessage, context: FSMContext): # untested
+def event_create_tags_step(message: UpdateMessage, context: FSMContext):
delete_prev_message(bot, message)
raw = message.message.text_message.text.strip()
-
- if raw == "-":
- tag_ids = []
- else:
- tag_ids = [t.strip() for t in raw.split() if t.strip()]
- for tag_id in tag_ids:
- try:
- UUID(tag_id)
- except Exception:
- bot.messaging.send_message(
- peer=message.peer,
- text=(
- "⚠️ Один из тегов не является валидным UUID.\n"
- "Пожалуйста, введи теги через пробел, например:\n"
- "`a1b2c3d4-e5f6-7890-abcd-ef1234567890 f9e8d7c6-b5a4-3210-fedc-ba0987654321`\n"
- "Или отправь `-`, чтобы пропустить:"
- ),
- interactive_media_groups=back_to_moderation_keyboard(),
- )
- return
+ tag_ids = [] if raw == "-" else [t.strip() for t in raw.split() if t.strip()]
context.update_data({"event_app_tag_ids": tag_ids})
bot.messaging.send_message(
peer=message.peer,
text=(
"Шаг 9/9: введи код мероприятия:\n"
"Без пробелов, не больше 16 символов. Например: WELCOME2025"
),
interactive_media_groups=back_to_moderation_keyboard(),
)
context.set_state(EventCreateState.code)
@bot.di
-def event_tags_toggle_handler( # half-tested
+def event_tags_toggle_handler( # not in use, tag ui choosing is turned off for now
event: UpdateInteractiveMediaEvent,
context: FSMContext,
tag_service: TagService,
event_service: EventService,
):
state = context.get_state()
is_create = False # интерфейс ввода тегов временно отключён
is_edit = state == EventEditTagsState.tags or str(state) == str(EventEditTagsState.tags)
if not (is_create or is_edit):
return
raw = str(event.data.value or "")
data = context.get_data()
key_field = "event_tag_keys" if is_create else "edit_tag_keys"
selected = set(data.get(key_field) or [])
all_tags = data.get("event_all_tags") or data.get("edit_all_tags")
if not all_tags:
tags = tag_service.get_all_tags()
all_tags = [{"id": t.id, "title": t.title} for t in tags]
else:
norm = []
for t in all_tags:
if isinstance(t, dict):
norm.append(t)
else:
norm.append({"id": getattr(t, "id", None), "title": getattr(t, "title", None)})
all_tags = norm
choices = choices_from_backend_tags(all_tags)
if raw == TAGS_DONE_VALUE:
title_to_id = {t["title"]: t["id"] for t in all_tags if t.get("title") and t.get("id")}
app_tag_ids = [title_to_id[n] for n in selected if n in title_to_id]
delete_prev_message_by_peer(bot, event.peer)
if is_create:
context.update_data({"event_app_tag_ids": app_tag_ids})
bot.messaging.send_message(
peer=event.peer,
- text=("Шаг 9/9: введи код мероприятия:\nБез пробелов, не больше 16 символов. Например: WELCOME2025"),
+ text=(
+ "Шаг 9/9: введи код мероприятия:\nБез пробелов, не больше 16 символов. Например: WELCOME2025"
+ ),
interactive_media_groups=back_to_moderation_keyboard(),
)
context.set_state(EventCreateState.code)
return
event_id = data.get("edit_event_id")
if not event_id:
context.set_state(None)
bot.messaging.send_message(
peer=event.peer, text="Сессия редактирования сброшена. Открой мероприятие заново."
)
return
updated = event_service.update_event(
event_id,
{"app_tag_ids": app_tag_ids},
requester_messenger_id=event.peer.id,
)
context.set_state(None)
if not updated:
_send_event_edit_menu(
event.peer,
event_id=event_id,
event_service=event_service,
note="⚠️ Не удалось обновить теги.",
event_obj=event_service.get_event_by_id(event_id),
show_organizer=True,
)
return
_send_event_edit_menu(
event.peer,
event_id=event_id,
event_service=event_service,
note="✅ Теги обновлены.",
event_obj=event_service.get_event_by_id(event_id),
show_organizer=True,
)
return
if raw.startswith(TAGS_TOGGLE_PREFIX):
key = raw[len(TAGS_TOGGLE_PREFIX) :].strip()
if key in selected:
selected.remove(key)
else:
selected.add(key)
context.update_data({key_field: list(selected)})
delete_prev_message_by_peer(bot, event.peer)
event_id = data.get("edit_event_id") or ""
footer = [
Button(
media_id="event_menu_edit",
value=event_id,
label="⬅️ К редактированию мероприятия",
)
]
title = "Редактирование тегов: выбери теги, затем нажми «Готово»:"
_send_tags_toggle_ui(
peer=event.peer,
title=title,
choices=choices,
selected_keys=set(selected),
footer_buttons=footer,
)
return
@events_rt.message(state=EventCreateState.code)
@bot.di
def event_create_code_step( # untested
message: UpdateMessage,
context: FSMContext,
event_service: EventService,
- report_service: ReportService,
):
delete_prev_message(bot, message)
code = message.message.text_message.text.strip()
if not re.fullmatch(r"^[A-Za-zА-Яа-я0-9]{1,16}$", code):
bot.messaging.send_message(
peer=message.peer,
text="⚠️ Код должен быть комбинацией русских и латинских букв и цифр, длиной не больше 16 символов. Попробуй еще раз:",
interactive_media_groups=back_to_moderation_keyboard(),
)
return
context.update_data({"event_bonus_code": code})
+ context.update_data({"hours": 0}) # TODO make a step for hours
data = context.get_data()
try:
created = event_service.create_from_wizard(data, message.peer)
except Exception as e:
+ logger.error("Error while creating an event via wizard: %s", e)
bot.messaging.send_message(
peer=message.peer,
text="⚠️ Не удалось создать мероприятие.",
interactive_media_groups=back_to_moderation_keyboard(),
)
context.clear()
raise e
context.clear()
- note = "✅ Мероприятие успешно создано."
+ note = {
+ "REQUEST": "✅ Заявка на мероприятие успешно создана.",
+ "COMPLETELY": "✅ Мероприятие успешно создано.",
+ "FAILED": "⚠️ Произошла ошибка: не удалось создать заявку/мероприятие.",
+ }.get(
+ created.get("TYPE", ""),
+ "⚠️ Произошла ошибка: не получить статус заявки/мероприятия. Проверь результат в 'Своих заявках' или в 'Своих мероприятиях'.",
+ )
+
+ formatted_data = str(data).strip('{}').replace('), ', '),\n').replace('], ', '],\n').replace('\', ', '\',\n')
bot.messaging.send_message(
peer=message.peer,
- text=f"{note}\n```\n{data}\n```",
+ text=f"{note}\n{formatted_data}",
interactive_media_groups=moderation_menu_keyboard(),
)
@bot.di
def event_view_by_id_start(event: UpdateInteractiveMediaEvent, context: FSMContext): # untested
"""старт диалога - просим у модератора UUID мероприятия"""
delete_prev_message_by_peer(bot, event.peer)
bot.messaging.send_message(
peer=event.peer,
text="Введи UUID мероприятия, которое хочешь посмотреть.\n\n",
interactive_media_groups=back_to_moderation_keyboard(),
)
context.set_state(EventViewState.wait_event_id)
@events_rt.message(state=EventViewState.wait_event_id)
@bot.di
def event_view_by_id_step( # untested
message: UpdateMessage, context: FSMContext, event_service: EventService, user: UserSchema
):
"""читаем UUID, тянем мероприятие из бэкенда и показываем информацию"""
delete_prev_message(bot, message)
raw_id = message.message.text_message.text.strip()
event = event_service.get_event_by_id(raw_id)
if event is None:
bot.messaging.send_message(
peer=message.peer,
text="⚠️ Мероприятие с таким UUID не найдено.\nПопробуй ещё раз. Пример UUID: `932cf1cb-1ee7-4a45-bc96-41a232f3f538`",
interactive_media_groups=back_to_moderation_keyboard(),
)
return
context.update_data({"current_event_id": event.id})
context.set_state(None)
send_event_card(
message.peer,
event_id=event.id,
event_service=event_service,
user=user,
ctx_name="moderation_menu",
)
def _update_event_card( # untested
peer,
event_service: EventService,
event_id: str,
can_manage: bool,
show_organizer: bool,
): # deprecated?
refreshed = event_service.get_event_by_id(event_id)
part = event_service.get_participation_status(event_id, peer.id)
is_participant = part is not None
prev = bot.messaging.load_message_history_sync(peer=peer, limit=1)[0]
bot.messaging.update_message_sync(
prev,
text=format_event_details(refreshed, show_code=True, show_organizer=show_organizer)
if refreshed
else "⚠️ Мероприятие не найдено",
interactive_media_groups=event_actions_keyboard(
event_id,
is_participant=is_participant,
can_manage=can_manage,
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",
ctx="moderation_menu",
),
)
@bot.di
def event_menu_sign_up_handler( # untested
event: UpdateInteractiveMediaEvent,
context: FSMContext,
event_service: EventService,
user: UserSchema | None,
):
delete_prev_message_by_peer(bot, event.peer)
context.set_state(None)
event_id, ctx = split_event_value(event.data.value)
ctx_name = ctx or "moderation"
ok = event_service.sign_up(event_id, event.peer.id)
if not ok:
note = "⚠️ Не удалось записаться на мероприятие."
else:
note = "✅ Запись на мероприятие прошла успешно!"
send_event_card(
event.peer,
event_id=event_id,
event_service=event_service,
user=user,
ctx_name=ctx_name,
note=note,
)
@bot.di
def event_menu_sign_out_handler( # untested
event: UpdateInteractiveMediaEvent,
context: FSMContext,
event_service: EventService,
user: UserSchema | None,
):
delete_prev_message_by_peer(bot, event.peer)
context.set_state(None)
event_id, ctx = split_event_value(event.data.value)
ctx_name = ctx or "moderation"
status = event_service.get_participation_status(event_id, event.peer.id)
if not status:
note = "⚠️ Не удалось отписаться: ты не записан на это мероприятие."
elif status.get("is_claimed"):
note = "⚠️ Нельзя отписаться: баллы уже получен за это участие."
else:
ok = event_service.sign_out_by_event(event_id, event.peer.id)
if ok:
note = "✅ Ты успешно отписался от мероприятия."
else:
note = "⚠️ Не удалось отписаться. Попробуй ещё раз."
send_event_card(
event.peer,
event_id=event_id,
event_service=event_service,
user=user,
ctx_name=ctx_name,
note=note,
)
@bot.di
def event_menu_enter_code_start_handler(
event: UpdateInteractiveMediaEvent, context: FSMContext
): # untested
delete_prev_message_by_peer(bot, event.peer)
event_id, ctx = split_event_value(event.data.value)
ctx_name = ctx or "moderation"
context.update_data({"current_event_id": event_id, "current_event_ctx": ctx_name})
context.set_state(EventEnterCodeState.wait_code)
bot.messaging.send_message(
peer=event.peer,
text="🎟️ Введи бонус-код мероприятия (без пробелов):",
interactive_media_groups=build_back_to_event_keyboard(event_id, ctx_name),
)
@events_rt.message(state=EventEnterCodeState.wait_code)
@bot.di
def event_menu_enter_code_step( # untested
message: UpdateMessage, context: FSMContext, event_service: EventService, user: UserSchema
):
delete_prev_message(bot, message)
event_id = context.get_data().get("current_event_id")
code = message.message.text_message.text.strip()
note = None
status = event_service.get_participation_status(event_id, message.peer.id)
if not status:
note = "⚠️ Код не принят, сначала нужно записаться на мероприятие"
elif status.get("is_claimed"):
note = "⚠️ Код не принят, баллы уже получены"
else:
result = event_service.enter_code_by_event(event_id, message.peer.id, code)
if not result:
note = "⚠️ Код не принят, проверь правильность кода"
else:
note = f"✅ Код принят. Текущие баллы: {result.get('points', '-')}"
data = context.get_data()
ctx_name = data.get("current_event_ctx") or "moderation"
context.set_state(None)
send_event_card(
message.peer,
event_id=event_id,
event_service=event_service,
user=user,
ctx_name=ctx_name,
note=note,
)
@bot.di
def event_menu_delete_handler( # untested
event: UpdateInteractiveMediaEvent,
context: FSMContext,
event_service: EventService,
user: UserSchema | None,
):
delete_prev_message_by_peer(bot, event.peer)
context.set_state(None)
event_id, ctx = split_event_value(event.data.value)
ctx_name = ctx or "moderation"
if ctx_name not in ("moderation", "moderation_menu"):
ctx_name = "moderation"
try:
ok = event_service.delete_event(
event_id,
requester_messenger_id=event.peer.id,
)
except Exception:
ok = False
if ok:
context.clear()
if ctx_name == "moderation":
send_event_cards_page(
event.peer,
offset=0,
event_service=event_service,
ctx_name="moderation",
note="Мероприятие удалено.",
)
else:
bot.messaging.send_message(
peer=event.peer,
text="Мероприятие удалено.\n\nТы в доме модерации",
interactive_media_groups=moderation_menu_keyboard(),
)
return
send_event_card(
event.peer,
event_id=event_id,
event_service=event_service,
user=user,
ctx_name=ctx_name,
note="⚠️ Не удалось удалить мероприятие",
)
def _send_event_edit_menu( # untested
peer,
*,
event_id: str,
event_service: EventService,
note: str | None = None,
event_obj: EventSchema | None = None,
show_organizer: bool,
):
refreshed = event_obj or event_service.get_event_by_id(event_id)
base = (
format_event_details(refreshed, show_code=True, show_organizer=show_organizer)
if refreshed
else "⚠️ Мероприятие не найдено"
)
prefix = f"{note}\n\n" if note else ""
bot.messaging.send_message(
peer=peer,
text=(f"{prefix}Редактирование мероприятия\n\n{base}\n\nВыбери поле для Редактирования:"),
interactive_media_groups=event_edit_fields_keyboard(event_id),
)
@bot.di
def event_menu_edit_handler( # untested
event: UpdateInteractiveMediaEvent, context: FSMContext, event_service: EventService
):
delete_prev_message_by_peer(bot, event.peer)
context.set_state(None)
event_id, _ctx = split_event_value(event.data.value)
_send_event_edit_menu(
event.peer,
event_id=event_id,
event_service=event_service,
show_organizer=True,
)
def _get_field_label(field: str) -> str: # untested
return {
"name": "Название",
"date": "Дата",
"time": "Время",
"location": "Локация",
"tags": "Теги",
"creator_id": "Организатор",
"points": "Баллы",
"description": "Описание",
"bonus_code": "Код",
}.get(field, field)
def _get_current_value(event: EventSchema, field: str) -> str: # untested
val = getattr(event, field, None)
if val is None or val == "":
return "-"
if field == "date":
try:
return event.date.strftime("%d.%m.%Y")
except Exception:
return str(val)
if field == "time":
try:
return event.date.strftime("%H:%M")
except Exception:
return str(val)
return str(val)
def _send_tags_toggle_ui( # untested
*, peer, title: str, choices, selected_keys: set[str], footer_buttons: list[Button]
):
bot.messaging.send_message(
peer=peer,
text=title,
interactive_media_groups=tags_toggle_keyboard(
choices=choices,
selected_keys=selected_keys,
media_id=TAGS_MEDIA_ID,
toggle_value_prefix=TAGS_TOGGLE_PREFIX,
done_value=TAGS_DONE_VALUE,
done_label="✅ Готово",
extra_footer=footer_buttons,
),
)
@bot.di
def event_menu_edit_field_handler( # untested
event: UpdateInteractiveMediaEvent,
context: FSMContext,
event_service: EventService,
tag_service: TagService,
):
delete_prev_message_by_peer(bot, event.peer)
raw = str(event.data.value or "")
if "|" not in raw:
bot.messaging.send_message(peer=event.peer, text="⚠️ Некорректная команда редактирования.")
return
event_id, field = raw.split("|", 1)
field = field.strip()
refreshed = event_service.get_event_by_id(event_id)
if not refreshed:
bot.messaging.send_message(peer=event.peer, text="⚠️ Мероприятие не найдено.")
return
if field == "tags":
# редактируем теги через toggle UI
all_tags = tag_service.get_all_tags()
all_tags_dump = [{"id": t.id, "name": t.name} for t in all_tags]
choices = choices_from_backend_tags(all_tags)
selected_names: set[str] = set()
for t in refreshed.tags or []:
if isinstance(t, dict) and t.get("name"):
selected_names.add(str(t["name"]))
else:
name = getattr(t, "name", None)
if name:
selected_names.add(str(name))
context.update_data(
{
"edit_event_id": event_id,
"edit_all_tags": all_tags_dump,
"edit_tag_keys": list(selected_names),
}
)
context.set_state(EventEditTagsState.tags)
_send_tags_toggle_ui(
peer=event.peer,
title="Редактирование тегов: выбери теги, затем нажми «Готово»:",
choices=choices,
selected_keys=set(selected_names),
footer_buttons=[
Button(
media_id="event_menu_edit",
value=event_id,
label="⬅️ К редактированию мероприятия",
)
],
)
return
context.update_data({"edit_event_id": event_id, "edit_field": field})
context.set_state(EventEditState.wait_value)
label = _get_field_label(field)
cur = _get_current_value(refreshed, field)
if field == "creator_id":
hint = 'Введи UUID организатора или отправь минус "-", чтобы поставить организатором себя.'
elif field == "date":
hint = "Введи дату в формате `dd.mm.yyyy`, например: `20.02.2002`"
elif field == "time":
hint = "Введи время в формате `HH:MM`, например: `10:30`"
else:
hint = 'Введи новое значение. Чтобы очистить поле - отправь: "-"'
bot.messaging.send_message(
peer=event.peer, text=(f"Редактирование: {label}\nТекущее значение: {cur}\n{hint}")
)
@events_rt.message(state=EventEditState.wait_value)
@bot.di
def event_edit_value_step( # untested
message: UpdateMessage,
context: FSMContext,
event_service: EventService,
user: UserSchema | None,
):
delete_prev_message(bot, message)
data = context.get_data()
event_id = data.get("edit_event_id")
field = data.get("edit_field")
if not event_id or not field:
context.set_state(None)
bot.messaging.send_message(
peer=message.peer, text="Сессия редактирования сброшена. Открой мероприятие заново."
)
return
raw = message.message.text_message.text.strip()
value: object = None if raw == "-" else raw
if field == "creator_id":
if raw == "-":
if not user:
bot.messaging.send_message(
peer=message.peer, text="⚠️ Не удалось определить ваш user."
)
return
if not user.id:
bot.messaging.send_message(
peer=message.peer, text="⚠️ Не удалось определить ваш user.id."
)
return
value = user.id
else:
try:
value = str(UUID(raw))
except Exception:
bot.messaging.send_message(
peer=message.peer,
text=(
'Организатор должен быть UUID пользователя или "-". Попробуй ещё раз.\n'
"Пример UUID: `d0a794d0-a128-47b7-b5f3-da53d19bfb07`"
),
)
return
if field == "points":
if raw == "-":
value = None
else:
try:
value = int(raw)
if value <= 0 or value >= 2**31:
raise ValueError
except ValueError:
bot.messaging.send_message(
peer=message.peer,
text=(
"⚠️ Баллы должны быть натуральным числом,"
f" не большим {2**31}, попробуй еще раз."
),
)
return
if (
field == "bonus_code"
and value is not None
and not re.fullmatch(r"^[A-Za-zА-Яа-я0-9]{1,16}$", str(value))
):
bot.messaging.send_message(
peer=message.peer,
text=(
"Код должен быть до 16 символов: латинские и кириллические"
" буквы и цифры без пробелов до 16 символов."
),
)
return
if field == "date" and value is not None:
try:
value = datetime.strptime(value, "%d.%m.%Y").date()
except ValueError:
bot.messaging.send_message(
peer=message.peer,
text=(
"⚠️ Я не смог распознать дату.\n"
"Пожалуйста, введи в формате `dd.mm.yyyy`, например: `20.02.2002`:"
),
interactive_media_groups=back_to_moderation_keyboard(),
)
return
if field == "time" and value is not None:
try:
value = datetime.strptime(value, "%H:%M").time()
except ValueError:
bot.messaging.send_message(
peer=message.peer,
text=(
"⚠️ Я не смог распознать время.\n"
"Пожалуйста, введи в формате `HH:MM`, например: `10:30`:"
),
interactive_media_groups=back_to_moderation_keyboard(),
)
return
if field in ("date", "time"):
current = event_service.get_event_by_id(event_id)
if not current:
context.set_state(None)
bot.messaging.send_message(peer=message.peer, text="⚠️ Мероприятие не найдено.")
return
current_dt = current.date
tz = getattr(current_dt, "tzinfo", None)
if value is None:
bot.messaging.send_message(
peer=message.peer, text="⚠️ Дату/время нельзя очистить. Выбери другое значение."
)
return
if field == "date":
new_dt = datetime.combine(value, current_dt.time())
else:
new_dt = datetime.combine(current_dt.date(), value)
if tz is not None:
new_dt = new_dt.replace(tzinfo=tz)
iso = new_dt.isoformat()
if iso.endswith("+00:00"):
iso = iso.replace("+00:00", "Z")
payload = {"date": iso}
else:
payload = {field: value}
updated = event_service.update_event(
event_id,
payload,
requester_messenger_id=message.peer.id,
)
context.set_state(None)
if not updated:
_send_event_edit_menu(
message.peer,
event_id=event_id,
event_service=event_service,
note="⚠️ Не удалось обновить поле.",
event_obj=event_service.get_event_by_id(event_id),
show_organizer=True,
)
return
_send_event_edit_menu(
message.peer,
event_id=event_id,
event_service=event_service,
note="✅ Поле обновлено.",
event_obj=event_service.get_event_by_id(event_id),
show_organizer=True,
)
diff --git a/services/bot-new/core/services/event.py b/services/bot-new/core/services/event.py
index b3274fe..78eb817 100644
--- a/services/bot-new/core/services/event.py
+++ b/services/bot-new/core/services/event.py
@@ -1,159 +1,161 @@
from datetime import datetime, timezone
from typing import Any
from requests import HTTPError
from core.clients.event import EventClient
from core.schemas.event import EventCardSchema
from core.services.base import BaseService
from core.services.registry import BaseServiceRegistry
-from core.utils.logger import logger
class EventService(BaseService): # untested
"""
сервис мероприятий на стороне бота.
на данный момент:
- ходит в backend через EventClient
- умеет получать список мероприятий по городу
- умеет получать одно мероприятие по id
- создает мероприятие через клиент используя мастера создания
"""
def __init__(self, registry: BaseServiceRegistry, client: EventClient): # untested
# DI может вызвать клиент без аргументов.
super().__init__(registry)
self.client = client
def get_event_cards_page(
self,
*,
requester_messenger_id: int,
page: int = 1,
limit: int = 10,
title_str: str | None = None,
tag_ids: list[str] | None = None,
) -> tuple[list[EventCardSchema], int, int]: # меняем тип возвращаемого списка
resp = self.client.get_event_cards_page(
requester_messenger_id=requester_messenger_id,
page=page,
limit=limit,
title_str=title_str,
tag_ids=tag_ids,
)
match resp.status_code:
case 200:
pass
case 400:
raise HTTPError("Error while getting event cards list: invalid parameters")
case 401:
raise HTTPError("Error while getting event cards list: unauthorized")
case 500:
raise HTTPError("Error while getting event cards list: internal server error")
case _:
raise HTTPError(
f"Error while getting event cards list: unexpected status {resp.status_code}"
)
body = resp.json() or {}
items = body.get("event_cards") or []
page_meta = body.get("page_meta") or {}
result: list[EventCardSchema] = []
for item in items:
if not isinstance(item, dict):
continue
result.append(self._map_event_card(item))
current_page = int(page_meta.get("current_page") or page)
total_pages = int(page_meta.get("total_pages") or current_page)
return result, current_page, total_pages
def sign_up(self, event_id: str, messenger_id: int) -> bool: # untested
"""messenger_id= = peer.id (как на бэкенде: user.get_by_messenger_id)"""
resp = self.client.sign_up(event_id=event_id, messenger_id=messenger_id)
return resp.status_code == 200
def create_event( # untested
self,
*,
title: str,
description: str,
date_obj: datetime,
gosp_id: str,
project_id: str,
verification_code: str,
hours: int,
requester_messenger_id: int,
tags: list[str],
):
"""
создает мероприятие через backend и возвращает dict из backend'a
- ожидает, что все данные уже провалидированы и разобраны
- метод не мапит ответ backend'a в EventSchema и не делает никакой обработки, а возвращает ровно тот json, который вернул backend (dect)
- может бросить Exception, если status_code не 201/200
"""
date_iso = date_obj.isoformat()
resp = self.client.create_event(
title=title,
description=description,
date_time=date_iso,
gosp_id=gosp_id,
project_id=project_id,
verification_code=verification_code,
hours=hours,
requester_messenger_id=requester_messenger_id,
tags=tags,
)
if resp.status_code not in [200, 201]:
raise Exception(f"Error while creating event: {resp.status_code}\n{resp.text}")
return resp.json()
def create_from_wizard(self, data: dict[str, Any], peer) -> dict[str, Any]:
title = data.get("event_name", "")
description = data.get("event_description", "")
date_obj = data.get("event_date", "")
time_obj = data.get("event_time", "")
- # надо додел
- gosp_id = data.get("gosp_id", "666666666666")
- # пока не делаем пусть отдыхает
+ gosp_id = data.get("gosp_id", "")
project_id = data.get("project_id", "")
hours = data.get("hours", "")
verification_code = data.get("event_bonus_code", "")
- # Теги: массив UUID, уже сформированный в хендлере # TODO not uuid
+ # Теги: массив ID, сформированный в хендлере
tags: list[str] = data.get("event_app_tag_ids") or []
# === Формирование RFC3339-совместимой строки ===
event_dt = datetime.combine(date_obj, time_obj).replace(tzinfo=timezone.utc)
date_time = event_dt.isoformat().replace("+00:00", "Z")
resp = self.client.create_event(
title=title,
description=description,
date_time=date_time,
gosp_id=gosp_id,
project_id=project_id,
verification_code=verification_code,
hours=hours,
requester_messenger_id=peer.id,
tags=tags if tags else None,
)
- logger.info("TYPE OF CREATED ENTITY: %s", resp.json().get("code"))
+ match resp.status_code:
+ case 200:
+ pass
+ case 401:
+ raise HTTPError("Error while getting user: unauthorized")
+ case 500:
+ raise HTTPError("Error while getting user: internal server error")
+ case _:
+ raise HTTPError("Error while getting user: unexpected status {resp.status_code}")
- if resp.status_code not in [200, 201]:
- raise HTTPError(f"Error while creating event: {resp.status_code}\n{resp.text}")
- # подключить когда сделают карточку мероприятия
- return {}
+ return {"TYPE": resp.json().get("code")} # показывать карточку мероприятия
def _map_event_card(self, body: dict[str, Any]) -> EventCardSchema:
"""преобразует json event card в EventCardSchema"""
from core.schemas.event import EventCardSchema
return EventCardSchema.from_dict(body)
Editor is loading...
Leave a Comment