Исправления критикал проблем

This commit is contained in:
2025-10-19 12:52:54 +03:00
parent 50d137ffc8
commit 44a8b54ddc
12 changed files with 166 additions and 52 deletions

View File

@@ -1,5 +1,5 @@
## changed at Sat Oct 18 12:59:47 MSK 2025 ## changed at Sun Oct 19 12:21:52 MSK 2025
#Sat Oct 18 12:59:47 MSK 2025 #Sun Oct 19 12:21:52 MSK 2025
com.gigaide.elements.ext.marker.solution.BeanMarkedPsi.shouldMark=true com.gigaide.elements.ext.marker.solution.BeanMarkedPsi.shouldMark=true
com.gigaide.elements.ext.marker.solution.ConfigMarkedPsi.shouldMark=true com.gigaide.elements.ext.marker.solution.ConfigMarkedPsi.shouldMark=true
com.gigaide.elements.ext.marker.solution.DataMarkedPsi.shouldMark=true com.gigaide.elements.ext.marker.solution.DataMarkedPsi.shouldMark=true

View File

@@ -1,5 +1,6 @@
from telebot.async_telebot import AsyncTeleBot from telebot.async_telebot import AsyncTeleBot
from telebot.types import Message from telebot.types import Message
from typing import Optional
import logging import logging
import os import os
from database import db from database import db
@@ -33,7 +34,7 @@ class ActionReporter:
return text return text
# Получает информацию об администраторе # Получает информацию об администраторе
async def _get_admin_info(self, admin_id: int | None) -> str: async def _get_admin_info(self, admin_id: Optional[int]) -> str:
# Если админ не указан (автоматическое действие) # Если админ не указан (автоматическое действие)
if admin_id is None: if admin_id is None:
return "🤖 <b>Администратор:</b> Автоматическое действие" return "🤖 <b>Администратор:</b> Автоматическое действие"
@@ -58,7 +59,7 @@ class ActionReporter:
return text return text
# Отправляет лог действия в админ-чат # Отправляет лог действия в админ-чат
async def log_action(self, action: str, user_id: int, admin_id: int | None, reason: str, duration: str, photo_path: str = None): async def log_action(self, action: str, user_id: int, admin_id: Optional[int], reason: str, duration: str, photo_path: Optional[str] = None):
try: try:
# Получаем информацию о пользователе и администраторе # Получаем информацию о пользователе и администраторе

View File

@@ -116,15 +116,37 @@ def contains_bad_word(text: str) -> bool:
bad_words = get_bad_words() bad_words = get_bad_words()
exceptions = get_exceptions() exceptions = get_exceptions()
# Проверяем исключения
for exception in exceptions:
if exception in text_lower:
text_lower = text_lower.replace(exception, '')
# Проверяем бранные слова # Проверяем бранные слова
for bad_word in bad_words: for bad_word in bad_words:
if bad_word in text_lower: if bad_word in text_lower:
return True # Проверяем, не является ли это слово частью исключения
# Ищем все вхождения плохого слова
start = 0
while True:
pos = text_lower.find(bad_word, start)
if pos == -1:
break
# Проверяем, входит ли это вхождение в какое-либо исключение
is_exception = False
for exception in exceptions:
# Проверяем, находится ли плохое слово внутри слова-исключения
# и содержится ли это слово-исключение в тексте в этой позиции
if bad_word in exception:
# Ищем позицию исключения, которое могло бы содержать это плохое слово
exc_start = text_lower.find(exception, max(0, pos - len(exception)))
if exc_start != -1:
exc_end = exc_start + len(exception)
# Если плохое слово находится внутри исключения
if exc_start <= pos < exc_end:
is_exception = True
break
# Если это не исключение, значит найдено плохое слово
if not is_exception:
return True
start = pos + 1
return False return False
@@ -148,14 +170,37 @@ def get_bad_words_from_text(text: str) -> list:
bad_words = get_bad_words() bad_words = get_bad_words()
exceptions = get_exceptions() exceptions = get_exceptions()
# Проверяем исключения
for exception in exceptions:
if exception in text_lower:
text_lower = text_lower.replace(exception, '')
# Ищем бранные слова # Ищем бранные слова
for bad_word in bad_words: for bad_word in bad_words:
if bad_word in text_lower: if bad_word in text_lower:
found_words.append(bad_word) # Проверяем, не является ли это слово частью исключения
start = 0
word_is_valid = False
while True:
pos = text_lower.find(bad_word, start)
if pos == -1:
break
# Проверяем, входит ли это вхождение в какое-либо исключение
is_exception = False
for exception in exceptions:
if bad_word in exception:
exc_start = text_lower.find(exception, max(0, pos - len(exception)))
if exc_start != -1:
exc_end = exc_start + len(exception)
if exc_start <= pos < exc_end:
is_exception = True
break
# Если найдено хотя бы одно вхождение, которое не является исключением
if not is_exception:
word_is_valid = True
break
start = pos + 1
# Добавляем слово только если оно действительно найдено (не в исключении)
if word_is_valid:
found_words.append(bad_word)
return found_words return found_words

View File

@@ -7,6 +7,23 @@ DATABASE_NAME = 'users.db'
# Название файла для логов # Название файла для логов
LOG_FILE_NAME = 'bot.log' LOG_FILE_NAME = 'bot.log'
# ===========================================
# Временные константы (в секундах)
# ===========================================
# Период учёта нарушений (30 дней)
VIOLATIONS_PERIOD = 2592000
# Кулдаун для благодарностей (1 час)
THANK_COOLDOWN = 3600
# Периоды для предупреждений
ONE_WEEK = 604800 # 7 дней
TWO_WEEKS = 1209600 # 14 дней
# Максимальное время мута (30 дней)
MAX_MUTE_TIME = 2592000
# Сообщения команд # Сообщения команд
COMMAND_MESSAGES = { COMMAND_MESSAGES = {
'start': 'Бот-администратор для чата @linux_gaming_ru', 'start': 'Бот-администратор для чата @linux_gaming_ru',
@@ -152,13 +169,13 @@ COMMAND_MESSAGES = {
" <code>/warn 123456789 причина</code>\n\n" " <code>/warn 123456789 причина</code>\n\n"
"<b>📋 Система накопления:</b>\n" "<b>📋 Система накопления:</b>\n"
"• 1-й варн: просто предупреждение\n" "• 1-й варн: просто предупреждение\n"
"• 2-й варн за неделю: автомут на сутки\n" "• 2-й варн за неделю: автомут на 7 дней (строгое)\n"
"Повтор в течение 2 недель: мут на неделю\n\n" "2-й варн за 2 недели: автомут на 1 день (мягкое)\n\n"
"<i> Причину обязательно указывайте для прозрачности</i>" "<i> Причину обязательно указывайте для прозрачности</i>"
), ),
'warned': '⚠️ Пользователь получил предупреждение.', 'warned': '⚠️ Пользователь получил предупреждение.',
'warned_auto_mute_day': '⚠️ Пользователь получил предупреждение и автомут на 1 день (повторное нарушение за неделю).', 'warned_auto_mute_day': '⚠️ Пользователь получил предупреждение и автомут на 1 день (2-е предупреждение за 2 недели).',
'warned_auto_mute_week': '⚠️ Пользователь получил предупреждение и автомут на 7 дней (множественные нарушения).', 'warned_auto_mute_week': '⚠️ Пользователь получил предупреждение и автомут на 7 дней (2-е предупреждение за неделю - строгое наказание).',
'karma_help': ( 'karma_help': (
"<b>⭐ Команда /karma</b>\n\n" "<b>⭐ Команда /karma</b>\n\n"
"<i>Показывает карму пользователя в этом чате</i>\n\n" "<i>Показывает карму пользователя в этом чате</i>\n\n"

View File

@@ -1,5 +1,6 @@
import sqlite3 import sqlite3
import os import os
import time
from typing import Optional, Tuple from typing import Optional, Tuple
import logging import logging
@@ -65,7 +66,34 @@ class Database: # Инициализация класса
FOREIGN KEY (to_user_id) REFERENCES users (id) FOREIGN KEY (to_user_id) REFERENCES users (id)
) )
''') ''')
# Создаём индексы для оптимизации часто используемых запросов
# Индекс для проверки нарушений пользователя в чате за период
cursor.execute('''
CREATE INDEX IF NOT EXISTS idx_violations_user_chat_date
ON violations(user_id, chat_id, violation_date)
''')
# Индекс для проверки предупреждений пользователя в чате за период
cursor.execute('''
CREATE INDEX IF NOT EXISTS idx_warnings_user_chat_date
ON warnings(user_id, chat_id, warn_date)
''')
# Индекс для проверки истории кармы (кулдаун благодарностей)
cursor.execute('''
CREATE INDEX IF NOT EXISTS idx_karma_history_cooldown
ON karma_history(from_user_id, to_user_id, chat_id, timestamp)
''')
# Индекс для поиска пользователя по username
cursor.execute('''
CREATE INDEX IF NOT EXISTS idx_users_tag
ON users(tag COLLATE NOCASE)
''')
connect.commit() connect.commit()
logger.info("База данных и индексы успешно инициализированы")
# Возвращает соединение с базой данных # Возвращает соединение с базой данных
def _get_connection(self): def _get_connection(self):
@@ -123,7 +151,6 @@ class Database: # Инициализация класса
# Добавляет нарушение в базу данных # Добавляет нарушение в базу данных
def add_violation(self, user_id: int, chat_id: int, violation_type: str = 'bad_language'): def add_violation(self, user_id: int, chat_id: int, violation_type: str = 'bad_language'):
import time
with self._get_connection() as connect: with self._get_connection() as connect:
cursor = connect.cursor() cursor = connect.cursor()
cursor.execute(''' cursor.execute('''
@@ -135,7 +162,6 @@ class Database: # Инициализация класса
# Получает количество нарушений за период (по умолчанию - за последний месяц) # Получает количество нарушений за период (по умолчанию - за последний месяц)
def get_violations_count(self, user_id: int, chat_id: int, period_seconds: int = 2592000) -> int: def get_violations_count(self, user_id: int, chat_id: int, period_seconds: int = 2592000) -> int:
import time
with self._get_connection() as connect: with self._get_connection() as connect:
cursor = connect.cursor() cursor = connect.cursor()
cutoff_time = int(time.time()) - period_seconds cutoff_time = int(time.time()) - period_seconds
@@ -161,7 +187,6 @@ class Database: # Инициализация класса
# Очищает старые нарушения (старше указанного периода) # Очищает старые нарушения (старше указанного периода)
def clean_old_violations(self, period_seconds: int = 2592000): def clean_old_violations(self, period_seconds: int = 2592000):
import time
with self._get_connection() as connect: with self._get_connection() as connect:
cursor = connect.cursor() cursor = connect.cursor()
cutoff_time = int(time.time()) - period_seconds cutoff_time = int(time.time()) - period_seconds
@@ -189,7 +214,6 @@ class Database: # Инициализация класса
# Добавляет предупреждение в базу данных # Добавляет предупреждение в базу данных
def add_warning(self, user_id: int, chat_id: int, reason: str, admin_id: int): def add_warning(self, user_id: int, chat_id: int, reason: str, admin_id: int):
import time
with self._get_connection() as connect: with self._get_connection() as connect:
cursor = connect.cursor() cursor = connect.cursor()
cursor.execute(''' cursor.execute('''
@@ -201,7 +225,6 @@ class Database: # Инициализация класса
# Получает количество предупреждений за период # Получает количество предупреждений за период
def get_warnings_count(self, user_id: int, chat_id: int, period_seconds: int) -> int: def get_warnings_count(self, user_id: int, chat_id: int, period_seconds: int) -> int:
import time
with self._get_connection() as connect: with self._get_connection() as connect:
cursor = connect.cursor() cursor = connect.cursor()
cutoff_time = int(time.time()) - period_seconds cutoff_time = int(time.time()) - period_seconds
@@ -290,9 +313,40 @@ class Database: # Инициализация класса
''', (chat_id, limit)) ''', (chat_id, limit))
return cursor.fetchall() return cursor.fetchall()
# Проверяет, может ли пользователь поблагодарить другого (проверка кулдауна) # Атомарно проверяет кулдаун и добавляет запись в историю кармы
# Возвращает True если благодарность засчитана, False если кулдаун не прошёл
def try_add_karma_thank(self, from_user_id: int, to_user_id: int, chat_id: int, cooldown_seconds: int = 3600) -> bool:
with self._get_connection() as connect:
cursor = connect.cursor()
current_time = int(time.time())
cutoff_time = current_time - cooldown_seconds
# Проверяем кулдаун и добавляем запись в одной транзакции
# Это предотвращает race condition при параллельных запросах
cursor.execute('''
SELECT COUNT(*)
FROM karma_history
WHERE from_user_id = ? AND to_user_id = ? AND chat_id = ? AND timestamp > ?
''', (from_user_id, to_user_id, chat_id, cutoff_time))
result = cursor.fetchone()
# Если кулдаун не прошёл, возвращаем False
if result and result[0] > 0:
return False
# Кулдаун прошёл, добавляем запись в той же транзакции
cursor.execute('''
INSERT INTO karma_history (from_user_id, to_user_id, chat_id, timestamp)
VALUES (?, ?, ?, ?)
''', (from_user_id, to_user_id, chat_id, current_time))
connect.commit()
logger.info(f"Пользователь {from_user_id} поблагодарил {to_user_id} в чате {chat_id}")
return True
# УСТАРЕВШИЙ МЕТОД - оставлен для обратной совместимости
# Используйте try_add_karma_thank() для атомарной операции без race condition
def can_thank(self, from_user_id: int, to_user_id: int, chat_id: int, cooldown_seconds: int = 3600) -> bool: def can_thank(self, from_user_id: int, to_user_id: int, chat_id: int, cooldown_seconds: int = 3600) -> bool:
import time
with self._get_connection() as connect: with self._get_connection() as connect:
cursor = connect.cursor() cursor = connect.cursor()
cutoff_time = int(time.time()) - cooldown_seconds cutoff_time = int(time.time()) - cooldown_seconds
@@ -304,9 +358,9 @@ class Database: # Инициализация класса
result = cursor.fetchone() result = cursor.fetchone()
return result[0] == 0 if result else True return result[0] == 0 if result else True
# Добавляет запись в историю кармы # УСТАРЕВШИЙ МЕТОД - оставлен для обратной совместимости
# Используйте try_add_karma_thank() для атомарной операции без race condition
def add_karma_history(self, from_user_id: int, to_user_id: int, chat_id: int): def add_karma_history(self, from_user_id: int, to_user_id: int, chat_id: int):
import time
with self._get_connection() as connect: with self._get_connection() as connect:
cursor = connect.cursor() cursor = connect.cursor()
cursor.execute(''' cursor.execute('''

View File

@@ -52,7 +52,7 @@ def setup_logging(): # Инициализирует систему логиро
# Создаем корневой логгер # Создаем корневой логгер
logger = logging.getLogger() logger = logging.getLogger()
logger.setLevel(logging.DEBUG) # Временно DEBUG для отладки logger.setLevel(logging.INFO) # INFO для продакшена
# Проверяем, не настроен ли логгер ранее # Проверяем, не настроен ли логгер ранее
if not logger.hasHandlers(): if not logger.hasHandlers():

View File

@@ -47,7 +47,7 @@ validate_env_vars()
bot = AsyncTeleBot(os.getenv("BOT_TOKEN"), parse_mode="html") bot = AsyncTeleBot(os.getenv("BOT_TOKEN"), parse_mode="html")
# Загружаем ID админ-чата из .env и инициализируемся для логов в чат # Загружаем ID админ-чата из .env и инициализируемся для логов в чат
init_action_reporter(bot, os.getenv("ADMIN_CHAT_ID"), os.getenv("LOG_THREAD_ID")) init_action_reporter(bot, int(os.getenv("ADMIN_CHAT_ID")), int(os.getenv("LOG_THREAD_ID")))
# Получаем логгер для текущего модуля # Получаем логгер для текущего модуля
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@@ -1,16 +1,15 @@
from telebot.async_telebot import AsyncTeleBot from telebot.async_telebot import AsyncTeleBot
from telebot.types import Message from telebot.types import Message
import asyncio
import logging import logging
from database import db from database import db
from thank_words import contains_thank_word from thank_words import contains_thank_word
from bad_words import contains_bad_word from bad_words import contains_bad_word
from config import THANK_COOLDOWN
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Кулдаун для благодарностей (в секундах) - 1 час
THANK_COOLDOWN = 3600
def register_handlers(bot: AsyncTeleBot): def register_handlers(bot: AsyncTeleBot):
"""Регистрирует обработчики для отслеживания благодарностей""" """Регистрирует обработчики для отслеживания благодарностей"""
logger.info("Регистрация обработчика благодарностей (karma_tracker)") logger.info("Регистрация обработчика благодарностей (karma_tracker)")
@@ -55,15 +54,15 @@ def register_handlers(bot: AsyncTeleBot):
logger.info(f"Пользователь {from_user.id} попытался поблагодарить бота") logger.info(f"Пользователь {from_user.id} попытался поблагодарить бота")
return return
# Проверяем кулдаун (можно благодарить одного пользователя раз в час) # Атомарно проверяем кулдаун и записываем благодарность
if not db.can_thank(from_user.id, to_user.id, chat_id, THANK_COOLDOWN): # Это предотвращает race condition при параллельных запросах
if not db.try_add_karma_thank(from_user.id, to_user.id, chat_id, THANK_COOLDOWN):
logger.info(f"Пользователь {from_user.id} уже благодарил {to_user.id} недавно") logger.info(f"Пользователь {from_user.id} уже благодарил {to_user.id} недавно")
# Молча игнорируем, чтобы не спамить # Молча игнорируем, чтобы не спамить
return return
# Начисляем карму # Начисляем карму (благодарность уже записана атомарно выше)
db.add_karma(to_user.id, chat_id, 1) db.add_karma(to_user.id, chat_id, 1)
db.add_karma_history(from_user.id, to_user.id, chat_id)
# Получаем новую карму пользователя # Получаем новую карму пользователя
new_karma = db.get_karma(to_user.id, chat_id) new_karma = db.get_karma(to_user.id, chat_id)
@@ -83,7 +82,6 @@ def register_handlers(bot: AsyncTeleBot):
logger.info(f"Пользователь {from_user.id} поблагодарил {to_user.id}, карма: {new_karma}") logger.info(f"Пользователь {from_user.id} поблагодарил {to_user.id}, карма: {new_karma}")
# Удаляем уведомление через 5 секунд # Удаляем уведомление через 5 секунд
import asyncio
await asyncio.sleep(5) await asyncio.sleep(5)
try: try:
await bot.delete_message(chat_id, sent_message.message_id) await bot.delete_message(chat_id, sent_message.message_id)

View File

@@ -8,6 +8,7 @@ from database import db
from bad_words import contains_bad_word, get_bad_words_from_text from bad_words import contains_bad_word, get_bad_words_from_text
from action_reporter import action_reporter from action_reporter import action_reporter
from utils import delete_messages, format_mute_time from utils import delete_messages, format_mute_time
from config import VIOLATIONS_PERIOD
# Получаем логгер для текущего модуля # Получаем логгер для текущего модуля
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -31,9 +32,6 @@ MUTE_LEVELS = [
None, # 14. Перманентный мут (режим только чтения навсегда) None, # 14. Перманентный мут (режим только чтения навсегда)
] ]
# Период для подсчета нарушений (30 дней в секундах)
VIOLATIONS_PERIOD = 2592000
def get_mute_duration(violations_count: int) -> int: def get_mute_duration(violations_count: int) -> int:
""" """
Определяет длительность мута на основе количества нарушений. Определяет длительность мута на основе количества нарушений.

View File

@@ -1,5 +1,6 @@
from telebot.async_telebot import AsyncTeleBot from telebot.async_telebot import AsyncTeleBot
from telebot.types import Message from telebot.types import Message
import asyncio
import logging import logging
from database import db from database import db
@@ -8,7 +9,6 @@ logger = logging.getLogger(__name__)
async def _delete_message_delayed(bot: AsyncTeleBot, chat_id: int, message_id: int, delay: int): async def _delete_message_delayed(bot: AsyncTeleBot, chat_id: int, message_id: int, delay: int):
"""Удаляет сообщение с задержкой""" """Удаляет сообщение с задержкой"""
import asyncio
try: try:
await asyncio.sleep(delay) await asyncio.sleep(delay)
await bot.delete_message(chat_id, message_id) await bot.delete_message(chat_id, message_id)
@@ -94,7 +94,6 @@ def register_handlers(bot: AsyncTeleBot):
sent_message = await bot.reply_to(message, response) sent_message = await bot.reply_to(message, response)
# Удаляем команду через 20 секунд и ответ через 60 секунд # Удаляем команду через 20 секунд и ответ через 60 секунд
import asyncio
asyncio.create_task(_delete_message_delayed(bot, chat_id, message.message_id, 20)) asyncio.create_task(_delete_message_delayed(bot, chat_id, message.message_id, 20))
asyncio.create_task(_delete_message_delayed(bot, chat_id, sent_message.message_id, 60)) asyncio.create_task(_delete_message_delayed(bot, chat_id, sent_message.message_id, 60))
@@ -142,10 +141,9 @@ def register_handlers(bot: AsyncTeleBot):
response += f"{medal} {user_display} — <b>{karma_points}</b> кармы\n" response += f"{medal} {user_display} — <b>{karma_points}</b> кармы\n"
sent_message = await bot.reply_to(message, response, parse_mode='HTML') sent_message = await bot.reply_to(message, response)
# Удаляем команду через 20 секунд и ответ через 60 секунд # Удаляем команду через 20 секунд и ответ через 60 секунд
import asyncio
asyncio.create_task(_delete_message_delayed(bot, chat_id, message.message_id, 20)) asyncio.create_task(_delete_message_delayed(bot, chat_id, message.message_id, 20))
asyncio.create_task(_delete_message_delayed(bot, chat_id, sent_message.message_id, 60)) asyncio.create_task(_delete_message_delayed(bot, chat_id, sent_message.message_id, 60))

View File

@@ -33,7 +33,7 @@ async def mute_command(bot: AsyncTeleBot, message: Message, photo_path: str = No
# Определяем целевого пользователя # Определяем целевого пользователя
target_user = None target_user = None
# Отпределяем время # Определяем время
time_arg = None time_arg = None
# Определяем причину # Определяем причину
@@ -233,8 +233,11 @@ async def mute_command(bot: AsyncTeleBot, message: Message, photo_path: str = No
await delete_messages(bot, message, time_sleep=5, number_message=2) await delete_messages(bot, message, time_sleep=5, number_message=2)
return return
# Максимальное время мута - 30 дней (2592000 секунд) # Импортируем максимальное время мута
if mute_seconds > 2592000: from config import MAX_MUTE_TIME
# Максимальное время мута - 30 дней
if mute_seconds > MAX_MUTE_TIME:
# Отправляем предупреждение # Отправляем предупреждение
await bot.send_message( await bot.send_message(

View File

@@ -144,10 +144,10 @@ async def warn_command(bot: AsyncTeleBot, message: Message):
admin_id=message.from_user.id admin_id=message.from_user.id
) )
# Проверяем количество предупреждений # Импортируем константы времени
ONE_WEEK = 604800 # 7 дней в секундах from config import ONE_WEEK, TWO_WEEKS
TWO_WEEKS = 1209600 # 14 дней в секундах
# Проверяем количество предупреждений
warns_week = db.get_warnings_count(target_user.id, message.chat.id, ONE_WEEK) warns_week = db.get_warnings_count(target_user.id, message.chat.id, ONE_WEEK)
warns_two_weeks = db.get_warnings_count(target_user.id, message.chat.id, TWO_WEEKS) warns_two_weeks = db.get_warnings_count(target_user.id, message.chat.id, TWO_WEEKS)