546 lines
21 KiB
Python
546 lines
21 KiB
Python
"""
|
||
Обработчики команд режима антиконфликта
|
||
"""
|
||
from datetime import datetime
|
||
from aiogram import Router
|
||
from aiogram.filters import Command
|
||
from aiogram.types import Message
|
||
|
||
from bot.filters.admin import IsAdmin
|
||
from configs import settings, COMMANDS
|
||
from database import get_manager
|
||
from database.models import BanWordType
|
||
from middleware.loggers import logger
|
||
from bot.utils.decorators import log_action
|
||
|
||
__all__ = ("router",)
|
||
|
||
router: Router = Router(name="conflict_mode_router")
|
||
|
||
|
||
# ================= ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ =================
|
||
|
||
def parse_conflict_args(text: str, command: str, need_minutes: bool = False) -> tuple[bool, str | list]:
|
||
"""
|
||
Парсит аргументы команды для конфликтного режима.
|
||
|
||
Args:
|
||
text: Полный текст сообщения
|
||
command: Название команды
|
||
need_minutes: Требуется ли параметр минут
|
||
|
||
Returns:
|
||
(success, result): result это либо список аргументов, либо текст ошибки
|
||
"""
|
||
parts = text.split(maxsplit=2 if need_minutes else 1)
|
||
|
||
min_args = 1 if need_minutes else 1
|
||
|
||
if len(parts) < min_args + 1:
|
||
if need_minutes:
|
||
return False, f"❌ Использование: <code>/{command} [минуты]</code>"
|
||
else:
|
||
return False, f"❌ Использование: <code>/{command} [слово]</code>"
|
||
|
||
args = parts[1:]
|
||
|
||
# Валидация слова
|
||
if not need_minutes:
|
||
if len(args[0]) < 2:
|
||
return False, "❌ Слово должно содержать минимум 2 символа"
|
||
|
||
if len(args[0]) > 100:
|
||
return False, "❌ Слово слишком длинное (максимум 100 символов)"
|
||
|
||
return True, args
|
||
|
||
|
||
def format_time_str(minutes: int) -> str:
|
||
"""Форматирует время в читабельный формат"""
|
||
if minutes < 60:
|
||
return f"{minutes} мин"
|
||
elif minutes < 1440:
|
||
hours = minutes // 60
|
||
mins = minutes % 60
|
||
return f"{hours}ч {mins}м" if mins else f"{hours}ч"
|
||
else:
|
||
days = minutes // 1440
|
||
hours = (minutes % 1440) // 60
|
||
return f"{days}д {hours}ч" if hours else f"{days}д"
|
||
|
||
|
||
def format_datetime(dt: datetime) -> str:
|
||
"""Форматирует datetime в читабельный формат"""
|
||
return dt.strftime("%d.%m.%Y %H:%M:%S")
|
||
|
||
|
||
# ================= ДОБАВЛЕНИЕ КОНФЛИКТНЫХ СЛОВ =================
|
||
|
||
@router.message(
|
||
Command(*COMMANDS.get("addconflictword", ["addconflictword"]), prefix=settings.PREFIX, ignore_case=True), IsAdmin())
|
||
@log_action(action_name="ADD_CONFLICT_WORD", log_args=True)
|
||
async def add_conflict_word_cmd(message: Message) -> None:
|
||
"""
|
||
Добавляет конфликтное слово-подстроку.
|
||
|
||
Конфликтные слова работают только в режиме /stopconflict.
|
||
|
||
Использование: /addconflictword <слово>
|
||
"""
|
||
success, result = parse_conflict_args(message.text, "addconflictword", need_minutes=False)
|
||
|
||
if not success:
|
||
await message.answer(result, parse_mode="HTML")
|
||
return
|
||
|
||
word = result[0].lower().strip()
|
||
manager = get_manager()
|
||
|
||
try:
|
||
added = await manager.add_banword(
|
||
word=word,
|
||
word_type=BanWordType.CONFLICT_WORD,
|
||
added_by=message.from_user.id,
|
||
reason="Конфликтное слово"
|
||
)
|
||
|
||
if added:
|
||
text = (
|
||
f"✅ <b>Конфликтное слово добавлено</b>\n\n"
|
||
f"📝 Слово: <code>{word}</code>\n"
|
||
f"🔍 Тип: подстрока\n\n"
|
||
f"⚔️ <i>Будет работать только в режиме антиконфликта</i>\n"
|
||
f"Активируйте: <code>/stopconflict [минуты]</code>"
|
||
)
|
||
else:
|
||
text = f"⚠️ Конфликтное слово <code>{word}</code> уже существует"
|
||
|
||
await message.answer(text, parse_mode="HTML")
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка добавления конфликтного слова: {e}", log_type="CONFLICT")
|
||
await message.answer("❌ <b>Ошибка добавления</b>\n\nПопробуйте позже", parse_mode="HTML")
|
||
|
||
|
||
@router.message(
|
||
Command(*COMMANDS.get("addconflictlemma", ["addconflictlemma"]), prefix=settings.PREFIX, ignore_case=True),
|
||
IsAdmin())
|
||
@log_action(action_name="ADD_CONFLICT_LEMMA", log_args=True)
|
||
async def add_conflict_lemma_cmd(message: Message) -> None:
|
||
"""
|
||
Добавляет конфликтную лемму.
|
||
|
||
Конфликтные леммы работают только в режиме /stopconflict.
|
||
|
||
Использование: /addconflictlemma <слово>
|
||
"""
|
||
success, result = parse_conflict_args(message.text, "addconflictlemma", need_minutes=False)
|
||
|
||
if not success:
|
||
await message.answer(result, parse_mode="HTML")
|
||
return
|
||
|
||
word = result[0].lower().strip()
|
||
manager = get_manager()
|
||
|
||
try:
|
||
added = await manager.add_banword(
|
||
word=word,
|
||
word_type=BanWordType.CONFLICT_LEMMA,
|
||
added_by=message.from_user.id,
|
||
reason="Конфликтная лемма"
|
||
)
|
||
|
||
if added:
|
||
text = (
|
||
f"✅ <b>Конфликтная лемма добавлена</b>\n\n"
|
||
f"🔤 Слово: <code>{word}</code>\n"
|
||
f"🔍 Тип: лемма (все формы слова)\n\n"
|
||
f"⚔️ <i>Будет работать только в режиме антиконфликта</i>\n"
|
||
f"Активируйте: <code>/stopconflict [минуты]</code>"
|
||
)
|
||
else:
|
||
text = f"⚠️ Конфликтная лемма <code>{word}</code> уже существует"
|
||
|
||
await message.answer(text, parse_mode="HTML")
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка добавления конфликтной леммы: {e}", log_type="CONFLICT")
|
||
await message.answer("❌ <b>Ошибка добавления</b>\n\nПопробуйте позже", parse_mode="HTML")
|
||
|
||
|
||
# ================= УДАЛЕНИЕ КОНФЛИКТНЫХ СЛОВ =================
|
||
|
||
@router.message(
|
||
Command(*COMMANDS.get("remconflictword", ["remconflictword"]), prefix=settings.PREFIX, ignore_case=True), IsAdmin())
|
||
@log_action(action_name="REMOVE_CONFLICT_WORD", log_args=True)
|
||
async def remove_conflict_word_cmd(message: Message) -> None:
|
||
"""
|
||
Удаляет конфликтное слово-подстроку.
|
||
|
||
Использование: /remconflictword <слово>
|
||
"""
|
||
success, result = parse_conflict_args(message.text, "remconflictword", need_minutes=False)
|
||
|
||
if not success:
|
||
await message.answer(result, parse_mode="HTML")
|
||
return
|
||
|
||
word = result[0].lower().strip()
|
||
manager = get_manager()
|
||
|
||
try:
|
||
removed = await manager.remove_banword(
|
||
word=word,
|
||
word_type=BanWordType.CONFLICT_WORD
|
||
)
|
||
|
||
if removed:
|
||
text = f"🗑 <b>Конфликтное слово удалено</b>\n\n📝 Слово: <code>{word}</code>"
|
||
else:
|
||
text = f"⚠️ Конфликтное слово <code>{word}</code> не найдено"
|
||
|
||
await message.answer(text, parse_mode="HTML")
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка удаления конфликтного слова: {e}", log_type="CONFLICT")
|
||
await message.answer("❌ <b>Ошибка удаления</b>\n\nПопробуйте позже", parse_mode="HTML")
|
||
|
||
|
||
@router.message(
|
||
Command(*COMMANDS.get("remconflictlemma", ["remconflictlemma"]), prefix=settings.PREFIX, ignore_case=True),
|
||
IsAdmin())
|
||
@log_action(action_name="REMOVE_CONFLICT_LEMMA", log_args=True)
|
||
async def remove_conflict_lemma_cmd(message: Message) -> None:
|
||
"""
|
||
Удаляет конфликтную лемму.
|
||
|
||
Использование: /remconflictlemma <слово>
|
||
"""
|
||
success, result = parse_conflict_args(message.text, "remconflictlemma", need_minutes=False)
|
||
|
||
if not success:
|
||
await message.answer(result, parse_mode="HTML")
|
||
return
|
||
|
||
word = result[0].lower().strip()
|
||
manager = get_manager()
|
||
|
||
try:
|
||
removed = await manager.remove_banword(
|
||
word=word,
|
||
word_type=BanWordType.CONFLICT_LEMMA
|
||
)
|
||
|
||
if removed:
|
||
text = f"🗑 <b>Конфликтная лемма удалена</b>\n\n🔤 Слово: <code>{word}</code>"
|
||
else:
|
||
text = f"⚠️ Конфликтная лемма <code>{word}</code> не найдена"
|
||
|
||
await message.answer(text, parse_mode="HTML")
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка удаления конфликтной леммы: {e}", log_type="CONFLICT")
|
||
await message.answer("❌ <b>Ошибка удаления</b>\n\nПопробуйте позже", parse_mode="HTML")
|
||
|
||
|
||
# ================= УПРАВЛЕНИЕ РЕЖИМОМ АНТИКОНФЛИКТА =================
|
||
|
||
@router.message(Command(*COMMANDS.get("stopconflict", ["stopconflict"]), prefix=settings.PREFIX, ignore_case=True),
|
||
IsAdmin())
|
||
@log_action(action_name="START_CONFLICT_MODE", log_args=True)
|
||
async def start_conflict_mode_cmd(message: Message) -> None:
|
||
"""
|
||
Активирует режим антиконфликта на указанное время.
|
||
|
||
В этом режиме работают только конфликтные слова/леммы.
|
||
Обычные банворды временно отключаются.
|
||
|
||
Использование: /stopconflict <минуты>
|
||
Пример: /stopconflict 30
|
||
"""
|
||
success, result = parse_conflict_args(message.text, "stopconflict", need_minutes=True)
|
||
|
||
if not success:
|
||
await message.answer(result, parse_mode="HTML")
|
||
return
|
||
|
||
# Валидация минут
|
||
try:
|
||
minutes = int(result[0])
|
||
if minutes < 1 or minutes > 10080: # Максимум неделя
|
||
await message.answer(
|
||
"❌ Время должно быть от 1 минуты до 10080 минут (7 дней)",
|
||
parse_mode="HTML"
|
||
)
|
||
return
|
||
except ValueError:
|
||
await message.answer("❌ Неверный формат времени. Укажите число минут", parse_mode="HTML")
|
||
return
|
||
|
||
manager = get_manager()
|
||
|
||
try:
|
||
# Получаем статистику конфликтных слов
|
||
data = await manager.get_all_words_list()
|
||
conflict_words_count = len(data.get('conflict_substring', set()))
|
||
conflict_lemmas_count = len(data.get('conflict_lemma', set()))
|
||
total_conflict = conflict_words_count + conflict_lemmas_count
|
||
|
||
if total_conflict == 0:
|
||
await message.answer(
|
||
"⚠️ <b>Нет конфликтных слов</b>\n\n"
|
||
"Сначала добавьте конфликтные слова:\n"
|
||
"• <code>/addconflictword [слово]</code>\n"
|
||
"• <code>/addconflictlemma [слово]</code>",
|
||
parse_mode="HTML"
|
||
)
|
||
return
|
||
|
||
# Активируем режим
|
||
expires_at = await manager.set_conflict_mode(minutes)
|
||
|
||
time_str = format_time_str(minutes)
|
||
expires_str = format_datetime(expires_at)
|
||
|
||
text = (
|
||
f"⚔️ <b>РЕЖИМ АНТИКОНФЛИКТА АКТИВИРОВАН</b>\n\n"
|
||
f"⏱ Длительность: {time_str}\n"
|
||
f"🕐 Окончание: {expires_str}\n\n"
|
||
f"📊 Активные правила:\n"
|
||
f"├─ Конфликтные слова: <code>{conflict_words_count}</code>\n"
|
||
f"└─ Конфликтные леммы: <code>{conflict_lemmas_count}</code>\n\n"
|
||
f"⚠️ <i>Обычные банворды временно отключены</i>\n"
|
||
f"Отключить режим: /unstopconflict"
|
||
)
|
||
|
||
await message.answer(text, parse_mode="HTML")
|
||
|
||
logger.info(
|
||
f"Режим антиконфликта активирован на {minutes} мин "
|
||
f"(конфликтных правил: {total_conflict})",
|
||
log_type="CONFLICT"
|
||
)
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка активации режима антиконфликта: {e}", log_type="CONFLICT")
|
||
await message.answer("❌ <b>Ошибка активации режима</b>\n\nПопробуйте позже", parse_mode="HTML")
|
||
|
||
|
||
@router.message(Command(*COMMANDS.get("unstopconflict", ["unstopconflict"]), prefix=settings.PREFIX, ignore_case=True),
|
||
IsAdmin())
|
||
@log_action(action_name="STOP_CONFLICT_MODE")
|
||
async def stop_conflict_mode_cmd(message: Message) -> None:
|
||
"""
|
||
Отключает режим антиконфликта.
|
||
|
||
Использование: /unstopconflict
|
||
"""
|
||
manager = get_manager()
|
||
|
||
try:
|
||
# Проверяем, активен ли режим
|
||
is_active = await manager.is_conflict_active()
|
||
|
||
if not is_active:
|
||
await message.answer(
|
||
"⚠️ <b>Режим антиконфликта не активен</b>\n\n"
|
||
"Активируйте: <code>/stopconflict [минуты]</code>",
|
||
parse_mode="HTML"
|
||
)
|
||
return
|
||
|
||
# Отключаем режим
|
||
await manager.disable_conflict_mode()
|
||
|
||
text = (
|
||
f"✅ <b>Режим антиконфликта отключен</b>\n\n"
|
||
f"🔄 Обычные банворды снова активны\n"
|
||
f"⚔️ Конфликтные слова деактивированы"
|
||
)
|
||
|
||
await message.answer(text, parse_mode="HTML")
|
||
|
||
logger.info("Режим антиконфликта отключён", log_type="CONFLICT")
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка отключения режима антиконфликта: {e}", log_type="CONFLICT")
|
||
await message.answer("❌ <b>Ошибка отключения режима</b>\n\nПопробуйте позже", parse_mode="HTML")
|
||
|
||
|
||
@router.message(Command(*COMMANDS.get("conflictstatus", ["conflictstatus"]), prefix=settings.PREFIX, ignore_case=True),
|
||
IsAdmin())
|
||
@log_action(action_name="CONFLICT_STATUS")
|
||
async def conflict_status_cmd(message: Message) -> None:
|
||
"""
|
||
Показывает статус режима антиконфликта.
|
||
|
||
Использование: /conflictstatus
|
||
"""
|
||
manager = get_manager()
|
||
|
||
try:
|
||
# Проверяем активность режима
|
||
is_active = await manager.is_conflict_active()
|
||
|
||
# Получаем статистику
|
||
data = await manager.get_all_words_list()
|
||
conflict_words_count = len(data.get('conflict_substring', set()))
|
||
conflict_lemmas_count = len(data.get('conflict_lemma', set()))
|
||
total_conflict = conflict_words_count + conflict_lemmas_count
|
||
|
||
if is_active:
|
||
# Режим активен - показываем детали
|
||
conflict_until_str = await manager.repo.get_setting("conflict_until")
|
||
conflict_until = float(conflict_until_str)
|
||
expires_at = datetime.fromtimestamp(conflict_until)
|
||
|
||
now = datetime.now()
|
||
time_left_seconds = (expires_at - now).total_seconds()
|
||
time_left_minutes = int(time_left_seconds / 60)
|
||
|
||
text = (
|
||
f"⚔️ <b>РЕЖИМ АНТИКОНФЛИКТА АКТИВЕН</b>\n\n"
|
||
f"⏱ Осталось: {format_time_str(time_left_minutes)}\n"
|
||
f"🕐 Окончание: {format_datetime(expires_at)}\n\n"
|
||
f"📊 Активные правила:\n"
|
||
f"├─ Конфликтные слова: <code>{conflict_words_count}</code>\n"
|
||
f"└─ Конфликтные леммы: <code>{conflict_lemmas_count}</code>\n\n"
|
||
f"⚠️ <i>Обычные банворды отключены</i>\n"
|
||
f"Отключить: /unstopconflict"
|
||
)
|
||
else:
|
||
# Режим не активен
|
||
text = (
|
||
f"💤 <b>Режим антиконфликта НЕ активен</b>\n\n"
|
||
f"📊 Конфликтных правил в базе:\n"
|
||
f"├─ Слова: <code>{conflict_words_count}</code>\n"
|
||
f"└─ Леммы: <code>{conflict_lemmas_count}</code>\n\n"
|
||
)
|
||
|
||
if total_conflict > 0:
|
||
text += f"Активировать: <code>/stopconflict [минуты]</code>"
|
||
else:
|
||
text += (
|
||
f"⚠️ <i>Нет конфликтных слов</i>\n"
|
||
f"Добавьте:\n"
|
||
f"• <code>/addconflictword [слово]</code>\n"
|
||
f"• <code>/addconflictlemma [слово]</code>"
|
||
)
|
||
|
||
await message.answer(text, parse_mode="HTML")
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка получения статуса режима: {e}", log_type="CONFLICT")
|
||
await message.answer("❌ <b>Ошибка получения статуса</b>", parse_mode="HTML")
|
||
|
||
|
||
@router.message(
|
||
Command(*COMMANDS.get("addconflictpart", ["addconflictpart"]),
|
||
prefix=settings.PREFIX,
|
||
ignore_case=True),
|
||
IsAdmin()
|
||
)
|
||
@log_action(action_name="ADD_CONFLICT_PART", log_args=True)
|
||
async def add_conflict_part_cmd(message: Message) -> None:
|
||
"""
|
||
Добавляет конфликтную часть.
|
||
|
||
Использование: /addconflictpart <комбинация>
|
||
"""
|
||
success, result = parse_conflict_args(
|
||
message.text,
|
||
"addconflictpart",
|
||
need_minutes=False
|
||
)
|
||
|
||
if not success:
|
||
await message.answer(result, parse_mode="HTML")
|
||
return
|
||
|
||
word = result[0].lower().strip()
|
||
manager = get_manager()
|
||
|
||
try:
|
||
added = await manager.add_banword(
|
||
word=word,
|
||
word_type=BanWordType.CONFLICT_PART,
|
||
added_by=message.from_user.id,
|
||
reason="Конфликтная часть"
|
||
)
|
||
|
||
if added:
|
||
text = (
|
||
f"✅ <b>Конфликтная часть добавлена</b>\n\n"
|
||
f"🧩 Часть: <code>{word}</code>\n"
|
||
f"🔍 Тип: поиск без пробелов\n\n"
|
||
f"⚔️ <i>Работает только в режиме антиконфликта</i>\n"
|
||
f"Активируйте: <code>/stopconflict [минуты]</code>"
|
||
)
|
||
else:
|
||
text = f"⚠️ Конфликтная часть <code>{word}</code> уже существует"
|
||
|
||
await message.answer(text, parse_mode="HTML")
|
||
|
||
except Exception as e:
|
||
logger.error(
|
||
f"Ошибка добавления конфликтной части: {e}",
|
||
log_type="CONFLICT"
|
||
)
|
||
await message.answer(
|
||
"❌ <b>Ошибка добавления</b>\n\nПопробуйте позже",
|
||
parse_mode="HTML"
|
||
)
|
||
|
||
@router.message(
|
||
Command(*COMMANDS.get("remconflictpart", ["remconflictpart"]),
|
||
prefix=settings.PREFIX,
|
||
ignore_case=True),
|
||
IsAdmin()
|
||
)
|
||
@log_action(action_name="REMOVE_CONFLICT_PART", log_args=True)
|
||
async def remove_conflict_part_cmd(message: Message) -> None:
|
||
"""
|
||
Удаляет конфликтную часть.
|
||
|
||
Использование: /remconflictpart <комбинация>
|
||
"""
|
||
success, result = parse_conflict_args(
|
||
message.text,
|
||
"remconflictpart",
|
||
need_minutes=False
|
||
)
|
||
|
||
if not success:
|
||
await message.answer(result, parse_mode="HTML")
|
||
return
|
||
|
||
word = result[0].lower().strip()
|
||
manager = get_manager()
|
||
|
||
try:
|
||
removed = await manager.remove_banword(
|
||
word=word,
|
||
word_type=BanWordType.CONFLICT_PART
|
||
)
|
||
|
||
if removed:
|
||
text = (
|
||
f"🗑 <b>Конфликтная часть удалена</b>\n\n"
|
||
f"🧩 Часть: <code>{word}</code>"
|
||
)
|
||
else:
|
||
text = f"⚠️ Конфликтная часть <code>{word}</code> не найдена"
|
||
|
||
await message.answer(text, parse_mode="HTML")
|
||
|
||
except Exception as e:
|
||
logger.error(
|
||
f"Ошибка удаления конфликтной части: {e}",
|
||
log_type="CONFLICT"
|
||
)
|
||
await message.answer(
|
||
"❌ <b>Ошибка удаления</b>\n\nПопробуйте позже",
|
||
parse_mode="HTML"
|
||
)
|