Добавление работы с конфликтными частями и исправление вайтлиста

This commit is contained in:
2026-02-25 17:50:11 +07:00
parent 6a4e56c367
commit 54125b82ac
15 changed files with 463 additions and 329 deletions

View File

@@ -99,7 +99,7 @@ async def add_conflict_word_cmd(message: Message) -> None:
try:
added = await manager.add_banword(
word=word,
word_type=BanWordType.CONFLICT_SUBSTRING,
word_type=BanWordType.CONFLICT_WORD,
added_by=message.from_user.id,
reason="Конфликтное слово"
)
@@ -192,7 +192,7 @@ async def remove_conflict_word_cmd(message: Message) -> None:
try:
removed = await manager.remove_banword(
word=word,
word_type=BanWordType.CONFLICT_SUBSTRING
word_type=BanWordType.CONFLICT_WORD
)
if removed:
@@ -433,3 +433,113 @@ async def conflict_status_cmd(message: Message) -> None:
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"
)

View File

@@ -7,7 +7,6 @@ from aiogram.types import Message, CallbackQuery
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiogram.exceptions import TelegramBadRequest
from bot.filters.admin import IsAdmin
from configs import settings, COMMANDS
from database import get_manager
from middleware.loggers import logger
@@ -27,7 +26,7 @@ def get_refresh_kb(page: int = 0):
return ikb.as_markup()
async def format_banwords_list(page: int = 0) -> str:
async def format_banwords_list() -> str:
"""
Форматирует список всех банвордов с разбивкой по типам.
@@ -46,13 +45,14 @@ async def format_banwords_list(page: int = 0) -> str:
stats = await manager.get_stats()
# Извлекаем данные из словаря
permanent_words = list(data.get('substring', set()))
permanent_words = list(data.get('word', set()))
permanent_lemmas = list(data.get('lemma', set()))
permanent_parts = list(data.get('part', set()))
temp_words = list(data.get('temp_substring', set()))
temp_words = list(data.get('temp_word', set()))
temp_lemmas = list(data.get('temp_lemma', set()))
conflict_words = list(data.get('conflict_substring', set()))
conflict_words = list(data.get('conflict_word', set()))
conflict_lemmas = list(data.get('conflict_lemma', set()))
conflict_parts = list(data.get('conflict_part', set()))
exceptions = list(data.get('whitelist', set()))
except Exception as e:
@@ -67,7 +67,7 @@ async def format_banwords_list(page: int = 0) -> str:
total_count = (
len(permanent_words) + len(permanent_lemmas) + len(permanent_parts) +
len(temp_words) + len(temp_lemmas) +
len(conflict_words) + len(conflict_lemmas)
len(conflict_words) + len(conflict_lemmas) + len(conflict_parts)
)
output += f"📊 <b>Общая статистика:</b>\n"
@@ -81,21 +81,21 @@ async def format_banwords_list(page: int = 0) -> str:
output += "🔴 <b>ПОСТОЯННЫЕ ПРАВИЛА:</b>\n\n"
if permanent_words:
output += f"📝 <b>Подстроки</b> ({len(permanent_words)}):\n"
output += f"📝 <b>Слова</b> ({len(permanent_words)}):\n"
words_str = ', '.join([f"<code>{w}</code>" for w in sorted(permanent_words)[:20]])
if len(permanent_words) > 20:
words_str += f" ... <i>(+{len(permanent_words) - 20} ещё)</i>"
output += f"{words_str}\n\n"
if permanent_lemmas:
output += f"🔤 <b>Леммы</b> ({len(permanent_lemmas)}):\n"
output += f"🔤 <b>Леммы (морф.формы)</b> ({len(permanent_lemmas)}):\n"
lemmas_str = ', '.join([f"<code>{w}</code>" for w in sorted(permanent_lemmas)[:20]])
if len(permanent_lemmas) > 20:
lemmas_str += f" ... <i>(+{len(permanent_lemmas) - 20} ещё)</i>"
output += f"{lemmas_str}\n\n"
if permanent_parts:
output += f"🧩 <b>Части</b> ({len(permanent_parts)}):\n"
output += f"🧩 <b>Части в сообщении</b> ({len(permanent_parts)}):\n"
parts_str = ', '.join([f"<code>{w}</code>" for w in sorted(permanent_parts)[:20]])
if len(permanent_parts) > 20:
parts_str += f" ... <i>(+{len(permanent_parts) - 20} ещё)</i>"
@@ -106,7 +106,7 @@ async def format_banwords_list(page: int = 0) -> str:
output += "⏱ <b>ВРЕМЕННЫЕ ПРАВИЛА:</b>\n\n"
if temp_words:
output += f"📝 <b>Временные подстроки</b> ({len(temp_words)}):\n"
output += f"📝 <b>Временные слова</b> ({len(temp_words)}):\n"
# Для временных слов нужна дополнительная информация о времени истечения
# Пока просто выводим список
words_str = ', '.join([f"<code>{w}</code>" for w in sorted(temp_words)[:15]])
@@ -122,7 +122,7 @@ async def format_banwords_list(page: int = 0) -> str:
output += f"{lemmas_str}\n\n"
# === КОНФЛИКТНЫЕ ПРАВИЛА ===
if conflict_words or conflict_lemmas:
if conflict_words or conflict_lemmas or conflict_parts:
output += "⚔️ <b>КОНФЛИКТНЫЕ ПРАВИЛА:</b>\n"
output += "<i>(работают только в режиме <code>/stopconflict</code> <code>время</code>)</i>\n\n"
@@ -140,10 +140,17 @@ async def format_banwords_list(page: int = 0) -> str:
lemmas_str += f" ... <i>(+{len(conflict_lemmas) - 15} ещё)</i>"
output += f"{lemmas_str}\n\n"
if conflict_parts:
output += f"🧩 <b>Конфликтные части</b> ({len(conflict_parts)}):\n"
parts_str = ', '.join([f"<code>{w}</code>" for w in sorted(conflict_parts)[:15]])
if len(conflict_parts) > 15:
parts_str += f" ... <i>(+{len(conflict_parts) - 15} ещё)</i>"
output += f"{parts_str}\n\n"
# === ИСКЛЮЧЕНИЯ (WHITELIST) ===
if exceptions:
output += f"✅ <b>ИСКЛЮЧЕНИЯ</b> ({len(exceptions)}):\n"
exc_str = ', '.join([f"<code>{exceptions}</code>" for w in sorted(exceptions)[:15]])
exc_str = ', '.join([f"<code>{w}</code>" for w in sorted(exceptions)[:15]])
if len(exceptions) > 15:
exc_str += f" ... <i>(+{len(exceptions) - 15} ещё)</i>"
output += f"{exc_str}\n\n"
@@ -183,7 +190,7 @@ async def format_banwords_list(page: int = 0) -> str:
@router.callback_query(F.data.startswith("listwords:refresh"))
@router.message(Command(*COMMANDS[CMD], prefix=settings.PREFIX, ignore_case=True), IsAdmin())
@router.message(Command(*COMMANDS[CMD], prefix=settings.PREFIX, ignore_case=True))
@log_action(action_name="LISTWORDS_COMMAND")
async def listwords_cmd(update: Message | CallbackQuery) -> None:
"""
@@ -209,7 +216,7 @@ async def listwords_cmd(update: Message | CallbackQuery) -> None:
# Формируем список
try:
text = await format_banwords_list(page)
text = await format_banwords_list()
keyboard = get_refresh_kb(page)
if is_callback:

View File

@@ -53,13 +53,13 @@ async def start_cmd(update: Message | CallbackQuery) -> None:
# Формируем текст помощи
help_text = (
f'{tg_emoji(4961073056677103064)} <b>PrimoGuard - Бот-модератор</b>\n\n'
'<blockquote>Автоматическое удаление сообщений с запрещёнными словами.\nПоддержка подстрок, лемм, временных блокировок и режимов модерации.</blockquote>\n\n'
f'{tg_emoji("4961073056677103064")} <b>PrimoGuard - Бот-модератор</b>\n\n'
'<blockquote>Автоматическое удаление сообщений с запрещёнными словами.\nПоддержка слов, лемм, временных блокировок и режимов модерации.</blockquote>\n\n'
)
# === Команды просмотра ===
help_text += (
f'{tg_emoji(4961141003059725568)} <b>Просмотр:</b>\n'
f'{tg_emoji("4961141003059725568")} <b>Просмотр:</b>\n'
'<b>/list</b> — список всех правил и слов\n'
'<b>/stats</b> — статистика по удалениям\n'
'<b>/id</b> — получение айди пользователя\n'
@@ -68,23 +68,23 @@ async def start_cmd(update: Message | CallbackQuery) -> None:
# === Постоянные банворды ===
help_text += (
f'{tg_emoji(4961019408240608234)} <b>Добавить банворд (постоянно):</b>\n'
'<code>/addword</code> <code>слово</code> — подстрока (простой поиск)\n'
'<code>/addlemma</code> <code>слово</code> — лемма (все формы слова)\n'
'<code>/addpart</code> <code>комбинация</code> — часть (поиск без пробелов)\n\n'
f'{tg_emoji("4961019408240608234")} <b>Добавить банворд (постоянно):</b>\n'
'<code>/word</code> <code>слово</code> — слова (простой поиск)\n'
'<code>/lemma</code> <code>слово</code> — лемма (все формы слова)\n'
'<code>/part</code> <code>комбинация</code> — часть (поиск без пробелов)\n\n'
)
# === Временные банворды ===
help_text += (
f'{tg_emoji(4960719190026618714)} <b>Добавить банворд (временно):</b>\n'
'<code>/addtempword</code> <code>слово минуты</code> — временная подстрока\n'
'<code>/addtemplemma</code> <code>слово минуты</code> — временная лемма\n'
'<i>Пример: /addtempword спам 60</i>\n\n'
f'{tg_emoji("4960719190026618714")} <b>Добавить банворд (временно):</b>\n'
'<code>/tempword</code> <code>слово минуты</code> — временная слова\n'
'<code>/templemma</code> <code>слово минуты</code> — временная лемма\n'
'<i>Пример: /tempword спам 60</i>\n\n'
)
# === Исключения (whitelist) ===
help_text += (
f'{tg_emoji(4963010134172239128)} <b>Исключения (whitelist):</b>\n'
f'{tg_emoji("4963010134172239128")} <b>Исключения (whitelist):</b>\n'
'<code>/addexcept</code> <code>текст</code> — добавить исключение\n'
'<code>/remexcept</code> <code>текст</code> — удалить исключение\n'
'<i>Исключения не проверяются фильтром</i>\n\n'
@@ -92,36 +92,38 @@ async def start_cmd(update: Message | CallbackQuery) -> None:
# === Режимы модерации ===
help_text += (
f'{tg_emoji(4960987543878239236)} <b>Режим тишины:</b>\n'
f'{tg_emoji("4960987543878239236")} <b>Режим тишины:</b>\n'
'<code>/silence</code> <code>минуты</code> — удалять ВСЕ сообщения\n'
'<b>/unsilence</b> — отключить режим тишины\n'
'<code>/report</code> — отправить репорт\n\n'
)
help_text += (
f'{tg_emoji(4960986152308835400)} <b>Режим антиконфликта:</b>\n'
f'{tg_emoji("4960986152308835400")} <b>Режим антиконфликта:</b>\n'
'<code>/addconflictword</code> <code>слово</code> — добавить конфликтное слово\n'
'<code>/addconflictlemma</code> <code>слово</code> — добавить конфликтную лемму\n'
'<code>/addconflictpart</code> <code>слово</code> — добавить конфликтную часть\n'
'<code>/stopconflict</code> <code>минуты</code> — активировать режим\n'
'<code>/unstopconflict</code> — отключить режим\n\n'
)
# === Удаление ===
help_text += (
f'{tg_emoji(4961196485447254983)} <b>Удалить:</b>\n'
'<code>/remword</code> <code>слово</code> — удалить подстроку\n'
f'{tg_emoji("4961196485447254983")} <b>Удалить:</b>\n'
'<code>/remword</code> <code>слово</code> — удалить слову\n'
'<code>/remlemma</code> <code>слово</code> — удалить лемму\n'
'<code>/rempart</code> <code>комбинация</code> — удалить часть\n'
'<code>/remtempword</code> <code>слово</code> — удалить временную подстроку\n'
'<code>/remtempword</code> <code>слово</code> — удалить временную слову\n'
'<code>/remtemplemma</code> <code>слово</code> — удалить временную лемму\n'
'<code>/remconflictword</code> <code>слово</code> — удалить конфликтное слово\n'
'<code>/remconflictpart</code> <code>слово</code> — удалить конфликтное часть\n'
'<code>/remconflictlemma</code> <code>слово</code> — удалить конфликтную лемму\n\n'
)
# === Управление админами (только для суперадминов) ===
if is_super_admin:
help_text += (
f'{tg_emoji(4960891456869893259)} <b>Управление админами (только для владельцев):</b>\n'
f'{tg_emoji("4960891456869893259")} <b>Управление админами (только для владельцев):</b>\n'
'<code>/addadmin</code> <i>ID</i> — добавить администратора\n'
'<code>/remadmin</code> <i>ID</i> — удалить администратора\n'
'<b>/redactcomment</b> — изменить комментарий под постом\n'
@@ -130,8 +132,8 @@ async def start_cmd(update: Message | CallbackQuery) -> None:
# === Типы проверок ===
help_text += (
f'{tg_emoji(4961021096162755737)} <b>Типы проверок:</b>\n'
'• <b>Подстрока</b> — простой поиск в тексте\n'
f'{tg_emoji("4961021096162755737")} <b>Типы проверок:</b>\n'
'• <b>Слово</b> — простой поиск в тексте\n'
'• <b>Лемма</b> — все формы слова (купить→куплю, купил, купишь...)\n'
'• <b>Часть</b> — поиск без пробелов (обходит \"к у п и т ь\")\n'
'• <b>Временные</b> — автоматически удаляются через N минут\n'
@@ -159,4 +161,4 @@ async def start_cmd(update: Message | CallbackQuery) -> None:
log_type="ERROR"
)
if is_callback:
await update.answer(f'{tg_emoji(4963277744994518278)} Ошибка отображения справки', show_alert=True)
await update.answer(f'{tg_emoji("4963277744994518278")} Ошибка отображения справки', show_alert=True)

View File

@@ -153,7 +153,13 @@ async def stats_cmd(update: Message | CallbackQuery) -> None:
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
conflict_parts_count = len(data.get('conflict_part', set()))
total_conflict = (
conflict_words_count +
conflict_lemmas_count +
conflict_parts_count
)
output += f"⚔️ <b>Режим антиконфликта</b>\n"
output += f"├─ ⏱ Осталось: <code>{format_time_remaining(time_left_minutes)}</code>\n"

View File

@@ -108,7 +108,7 @@ async def add_word_cmd(message: Message) -> None:
try:
added = await manager.add_banword(
word=word,
word_type=BanWordType.SUBSTRING,
word_type=BanWordType.WORD,
added_by=message.from_user.id,
reason=f"Добавлено через команду"
)
@@ -126,7 +126,7 @@ async def add_word_cmd(message: Message) -> None:
await message.answer(text, parse_mode="HTML")
except Exception as e:
logger.error(f"Ошибка добавления банворда: {e}", log_type="CMD", exc_info=True)
logger.error(f"Ошибка добавления банворда: {e}", log_type="CMD")
await message.answer("❌ <b>Ошибка добавления</b>\n\nПопробуйте позже", parse_mode="HTML")
@@ -168,7 +168,7 @@ async def add_lemma_cmd(message: Message) -> None:
await message.answer(text, parse_mode="HTML")
except Exception as e:
logger.error(f"Ошибка добавления леммы: {e}", log_type="CMD", exc_info=True)
logger.error(f"Ошибка добавления леммы: {e}", log_type="CMD")
await message.answer("❌ <b>Ошибка добавления</b>\n\nПопробуйте позже", parse_mode="HTML")
@@ -210,7 +210,7 @@ async def add_part_cmd(message: Message) -> None:
await message.answer(text, parse_mode="HTML")
except Exception as e:
logger.error(f"Ошибка добавления части: {e}", log_type="CMD", exc_info=True)
logger.error(f"Ошибка добавления части: {e}", log_type="CMD")
await message.answer("❌ <b>Ошибка добавления</b>\n\nПопробуйте позже", parse_mode="HTML")
@@ -246,7 +246,7 @@ async def add_temp_word_cmd(message: Message) -> None:
try:
added = await manager.add_temp_banword(
word=word,
word_type=BanWordType.SUBSTRING,
word_type=BanWordType.WORD,
minutes=minutes,
added_by=message.from_user.id
)
@@ -265,7 +265,7 @@ async def add_temp_word_cmd(message: Message) -> None:
await message.answer(text, parse_mode="HTML")
except Exception as e:
logger.error(f"Ошибка добавления временного банворда: {e}", log_type="CMD", exc_info=True)
logger.error(f"Ошибка добавления временного банворда: {e}", log_type="CMD")
await message.answer("❌ <b>Ошибка добавления</b>\n\nПопробуйте позже", parse_mode="HTML")
@@ -319,7 +319,7 @@ async def add_temp_lemma_cmd(message: Message) -> None:
await message.answer(text, parse_mode="HTML")
except Exception as e:
logger.error(f"Ошибка добавления временной леммы: {e}", log_type="CMD", exc_info=True)
logger.error(f"Ошибка добавления временной леммы: {e}", log_type="CMD")
await message.answer("❌ <b>Ошибка добавления</b>\n\nПопробуйте позже", parse_mode="HTML")
@@ -360,7 +360,7 @@ async def add_exception_cmd(message: Message) -> None:
await message.answer(text, parse_mode="HTML")
except Exception as e:
logger.error(f"Ошибка добавления исключения: {e}", log_type="CMD", exc_info=True)
logger.error(f"Ошибка добавления исключения: {e}", log_type="CMD")
await message.answer("❌ <b>Ошибка добавления</b>\n\nПопробуйте позже", parse_mode="HTML")
@@ -384,7 +384,7 @@ async def remove_word_cmd(message: Message) -> None:
manager = get_manager()
try:
removed = await manager.remove_banword(word=word, word_type=BanWordType.SUBSTRING)
removed = await manager.remove_banword(word=word, word_type=BanWordType.WORD)
if removed:
text = format_success_message("удалена", word, "подстрока")
@@ -394,7 +394,7 @@ async def remove_word_cmd(message: Message) -> None:
await message.answer(text, parse_mode="HTML")
except Exception as e:
logger.error(f"Ошибка удаления банворда: {e}", log_type="CMD", exc_info=True)
logger.error(f"Ошибка удаления банворда: {e}", log_type="CMD")
await message.answer("❌ <b>Ошибка удаления</b>\n\nПопробуйте позже", parse_mode="HTML")
@@ -422,7 +422,7 @@ async def remove_lemma_cmd(message: Message) -> None:
await message.answer(text, parse_mode="HTML")
except Exception as e:
logger.error(f"Ошибка удаления леммы: {e}", log_type="CMD", exc_info=True)
logger.error(f"Ошибка удаления леммы: {e}", log_type="CMD")
await message.answer("❌ <b>Ошибка удаления</b>\n\nПопробуйте позже", parse_mode="HTML")
@@ -450,7 +450,7 @@ async def remove_part_cmd(message: Message) -> None:
await message.answer(text, parse_mode="HTML")
except Exception as e:
logger.error(f"Ошибка удаления части: {e}", log_type="CMD", exc_info=True)
logger.error(f"Ошибка удаления части: {e}", log_type="CMD")
await message.answer("❌ <b>Ошибка удаления</b>\n\nПопробуйте позже", parse_mode="HTML")
@@ -469,7 +469,7 @@ async def remove_temp_word_cmd(message: Message) -> None:
manager = get_manager()
try:
removed = await manager.remove_temp_banword(word=word, word_type=BanWordType.SUBSTRING)
removed = await manager.remove_temp_banword(word=word, word_type=BanWordType.WORD)
if removed:
text = format_success_message("удалена", word, "временная подстрока")
@@ -479,7 +479,7 @@ async def remove_temp_word_cmd(message: Message) -> None:
await message.answer(text, parse_mode="HTML")
except Exception as e:
logger.error(f"Ошибка удаления временного банворда: {e}", log_type="CMD", exc_info=True)
logger.error(f"Ошибка удаления временного банворда: {e}", log_type="CMD")
await message.answer("❌ <b>Ошибка удаления</b>\n\nПопробуйте позже", parse_mode="HTML")
@@ -508,7 +508,7 @@ async def remove_temp_lemma_cmd(message: Message) -> None:
await message.answer(text, parse_mode="HTML")
except Exception as e:
logger.error(f"Ошибка удаления временной леммы: {e}", log_type="CMD", exc_info=True)
logger.error(f"Ошибка удаления временной леммы: {e}", log_type="CMD")
await message.answer("❌ <b>Ошибка удаления</b>\n\nПопробуйте позже", parse_mode="HTML")
@@ -536,5 +536,5 @@ async def remove_exception_cmd(message: Message) -> None:
await message.answer(text, parse_mode="HTML")
except Exception as e:
logger.error(f"Ошибка удаления исключения: {e}", log_type="CMD", exc_info=True)
logger.error(f"Ошибка удаления исключения: {e}", log_type="CMD")
await message.answer("❌ <b>Ошибка удаления</b>\n\nПопробуйте позже", parse_mode="HTML")

View File

@@ -1,15 +1,9 @@
from aiogram import Router
from .default_msg import router as default_message_router
from .ping_test import router as ping_test_message_router
# Настройка экспорта и роутера
router: Router = Router(name=__name__)
# Подготовка роутера команд
# router.include_routers(
# ping_test_message_router,
# )
# Подключение стандартного роутера
router.include_router(default_message_router)