Первый коммит

This commit is contained in:
admin
2025-08-30 07:39:44 +07:00
commit d0baf76f8f
86 changed files with 7362 additions and 0 deletions

View File

@@ -0,0 +1,218 @@
import re
from typing import Optional, Dict, Tuple
from aiogram import Router, F
from aiogram.filters import Command
from aiogram.fsm.context import FSMContext
from aiogram.types import Message, CallbackQuery, InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiogram.utils.i18n import gettext as _
from bot.core.bots import BotInfo
from bot.keyboards.inline.decision import decision_keyboard
from bot.states.new_states import NewStates
from bot.templates import msg
from middleware.loggers import log
from configs import COMMANDS, ImportantID, RpValue
# Глобальная мапа для хранения связей пользователь-топик
user_topic_map: Dict[Tuple[int, str], int] = {}
__all__ = ("router",)
CMD: str = "new"
router: Router = Router(name=f"{CMD}_cmd_router")
TOPIC_TYPE: str = "anketa"
TEXTS: Dict[str, Dict[str, str]] = {
"anketa": {
"accept": f"<b>🎉 Ваша анкета принята!</b>\n\nДобро пожаловать в проект!\n\nФлуд: {RpValue.FLUD_URL}\nРолевая: {RpValue.RP_URL}",
"reject": "<b>❌ Ваша анкета отклонена.</b>\n\nВы можете попробовать позже."
}
}
async def validate_russian_text(text: str) -> bool:
"""Проверяет текст на соответствие русским буквам, пробелам и дефисам."""
return bool(re.fullmatch(r"[А-Яа-яЁё\s\-]+", text))
# ===================== Команда /new =====================
@router.callback_query(F.data == CMD)
@router.message(Command(*COMMANDS[CMD], prefix=BotInfo.prefix, ignore_case=True))
@log(level='INFO', log_type=CMD.upper(), text=f"использовал команду /{CMD}")
async def new_cmd(message: Message | CallbackQuery, state: FSMContext) -> None:
"""
Начало анкеты /new.
Отправляет пользователю сообщение с просьбой указать желаемую роль.
"""
await state.clear()
await state.set_state(NewStates.role)
ikb: InlineKeyboardBuilder = InlineKeyboardBuilder()
ikb.row(InlineKeyboardButton(text="Отмена↩️", callback_data='start'))
text: str = _(
"Пожалуйста, отправьте желаемую роль:\n"
"(только русские буквы, пробелы или дефисы)"
)
await msg(message=message, text=text, markup=ikb)
# ===================== Обработка роли =====================
@router.message(NewStates.role)
async def process_role(message: Message, state: FSMContext) -> None:
"""Обрабатывает ввод роли и запрашивает сортол."""
if not await validate_russian_text(message.text):
await message.reply("Ошибка: роль должна содержать только русские буквы, пробелы или дефисы.")
return
await state.update_data(role=message.text.strip().title())
await state.set_state(NewStates.sorol)
ikb: InlineKeyboardBuilder = InlineKeyboardBuilder()
ikb.row(InlineKeyboardButton(text="Отмена↩️", callback_data='start'))
await message.reply(
text="Теперь укажите желаемый сортол:\n(только русские буквы, пробелы или дефисы)",
reply_markup=ikb.as_markup()
)
# ===================== Обработка сортола =====================
@router.message(NewStates.sorol)
async def process_sortol(message: Message, state: FSMContext) -> None:
"""Обрабатывает ввод сортола и запрашивает кодовую фразу."""
if not await validate_russian_text(message.text):
await message.reply("Ошибка: сорол должен содержать только русские буквы, пробелы или дефисы.")
return
await state.update_data(sortol=message.text.strip().title())
await state.set_state(NewStates.code_phrase)
ikb: InlineKeyboardBuilder = InlineKeyboardBuilder()
ikb.row(InlineKeyboardButton(text="Отмена↩️", callback_data='start'))
await message.reply(
text="Теперь введите кодовую фразу из правил:",
reply_markup=ikb.as_markup()
)
# ===================== Обработка кодовой фразы =====================
@router.message(NewStates.code_phrase)
async def process_code_phrase(message: Message, state: FSMContext) -> None:
"""Обрабатывает ввод кодовой фразы и показывает предпросмотр анкеты."""
code_phrase = message.text.strip()
if not code_phrase:
await message.reply("Кодовая фраза не может быть пустой.")
return
await state.update_data(code_phrase=code_phrase)
data: Dict[str, str] = await state.get_data()
ikb: InlineKeyboardBuilder = InlineKeyboardBuilder()
ikb.row(
InlineKeyboardButton(text="Отправить!", callback_data="submit_new"),
InlineKeyboardButton(text="Отмена↩️", callback_data="start")
)
text: str = (
f"<b>Проверьте данные анкеты:</b>\n\n"
f"• Роль: {data['role']}\n"
f"• Сортол: {data['sortol']}\n"
f"• Кодовая фраза: {data['code_phrase']}"
)
await message.reply(text, reply_markup=ikb.as_markup())
# ===================== Отправка анкеты в поддержку =====================
@router.callback_query(F.data == "submit_new")
async def submit_new_cmd(callback: CallbackQuery, state: FSMContext) -> None:
"""Отправляет анкету в топик форума поддержки и создает запись в мапе."""
data: Dict[str, str] = await state.get_data()
user = callback.from_user
# Создаем топик в форуме
topic = await callback.bot.create_forum_topic(
chat_id=ImportantID.SUPPORT_CHAT_ID,
name=f"Анкета от {user.full_name}"
)
thread_id: int = topic.message_thread_id
# Сохраняем связь пользователь-топик
user_topic_map[(user.id, TOPIC_TYPE)] = thread_id
# Формируем текст анкеты
text: str = (
f'<b><a href="tg://user?id={user.id}">Анкета</a></b>\n\n'
f"• Роль: {data['role']}\n"
f"• Сортол: {data['sortol']}\n"
f"• Кодовая фраза: {data['code_phrase']}"
)
# Отправляем в топик с кнопками принятия/отклонения
await callback.bot.send_message(
chat_id=ImportantID.SUPPORT_CHAT_ID,
message_thread_id=thread_id,
text=text,
parse_mode="HTML",
reply_markup=decision_keyboard(thread_id=thread_id, kind=TOPIC_TYPE)
)
await callback.message.edit_text("✅ Ваша анкета успешно отправлена на рассмотрение!")
await state.clear()
# ===================== Обработка решения админов =====================
@router.callback_query(F.data.regexp(r"^([a-z_]+):(accept|reject):(\d+)$"))
async def process_decision_callback(callback: CallbackQuery) -> None:
"""Обрабатывает решение администраторов и отправляет результат пользователю."""
kind, action, thread_id_str = callback.data.split(":")
thread_id = int(thread_id_str)
# Ищем пользователя по thread_id в мапе
user_id = None
for (uid, k), tid in user_topic_map.items():
if k == kind and tid == thread_id:
user_id = uid
break
if not user_id:
await callback.answer("Пользователь не найден.", show_alert=True)
return
text_to_send: Optional[str] = TEXTS.get(kind, {}).get(action)
if not text_to_send:
await callback.answer("Некорректные данные.", show_alert=True)
return
await callback.bot.send_message(chat_id=user_id, text=text_to_send, parse_mode="HTML")
await callback.message.edit_reply_markup(reply_markup=None)
await callback.answer("Ответ отправлен пользователю.")
# ===================== Пересылка ответов админов пользователю =====================
@router.message(F.is_topic_message, F.reply_to_message, ~F.from_user.is_bot)
async def forward_reply_to_user(message: Message) -> None:
"""Пересылает ответы администраторов из топика пользователю."""
thread_id = message.message_thread_id
if not thread_id:
return
# Ищем пользователя по thread_id
user_id = None
for (uid, _), tid in user_topic_map.items():
if tid == thread_id:
user_id = uid
break
if not user_id:
return
reply_text: str = f"<b>Ответ администратора:</b>\n{message.html_text}"
try:
await message.bot.send_message(chat_id=user_id, text=reply_text, parse_mode="HTML")
except Exception as e:
await message.reply(f"⚠️ Не удалось отправить сообщение пользователю: {e}")