Files
PrimoGuardBot/bot/handlers/commands/users/conflict.py

546 lines
21 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Обработчики команд режима антиконфликта
"""
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"
)