forked from Muzifs/LGBot
Убран медленный режим
This commit is contained in:
File diff suppressed because it is too large
Load Diff
29
.gitignore
vendored
29
.gitignore
vendored
@@ -1,6 +1,33 @@
|
|||||||
|
# Python
|
||||||
.venv/
|
.venv/
|
||||||
.env
|
.env
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.pyd
|
||||||
|
*.db
|
||||||
|
*.sqlite
|
||||||
|
|
||||||
|
# IDE
|
||||||
.idea/
|
.idea/
|
||||||
|
.gigaide/
|
||||||
|
.claude/
|
||||||
|
.vscode/
|
||||||
|
*.iml
|
||||||
|
|
||||||
|
# Build
|
||||||
|
target/
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
*.class
|
||||||
|
|
||||||
|
# Logs
|
||||||
bot.log
|
bot.log
|
||||||
users.db
|
*.log
|
||||||
|
|
||||||
|
# Databases
|
||||||
|
users.db
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
@@ -201,10 +201,6 @@ COMMAND_MESSAGES = {
|
|||||||
"<b>🔥 БОНУС: Благодарность с восклицательным знаком даёт x2 кармы!</b>\n"
|
"<b>🔥 БОНУС: Благодарность с восклицательным знаком даёт x2 кармы!</b>\n"
|
||||||
"• спасибо! → +2 кармы 👍👍\n"
|
"• спасибо! → +2 кармы 👍👍\n"
|
||||||
"• thanks! → +2 кармы 👍👍\n\n"
|
"• thanks! → +2 кармы 👍👍\n\n"
|
||||||
"<b>⏱ Медленный режим на основе кармы:</b>\n"
|
|
||||||
"• Карма 0: 30 сек между сообщениями\n"
|
|
||||||
"• Карма > 0: меньше задержка (50+ = нет задержки)\n"
|
|
||||||
"• Карма < 0: больше задержка (-50 = 120 сек)\n\n"
|
|
||||||
"<b>⚠️ Снятие кармы:</b>\n"
|
"<b>⚠️ Снятие кармы:</b>\n"
|
||||||
"• Предупреждение (/warn): -5 кармы\n"
|
"• Предупреждение (/warn): -5 кармы\n"
|
||||||
"• Мут (/mute или автомут): -10 кармы\n\n"
|
"• Мут (/mute или автомут): -10 кармы\n\n"
|
||||||
|
@@ -1,149 +0,0 @@
|
|||||||
# Система медленного режима на основе кармы
|
|
||||||
# Управляет частотой отправки сообщений пользователями в зависимости от их кармы
|
|
||||||
|
|
||||||
import time
|
|
||||||
import logging
|
|
||||||
from collections import defaultdict
|
|
||||||
from typing import Dict, Tuple
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
# Хранилище последних сообщений: {(user_id, chat_id): timestamp}
|
|
||||||
_last_message_times: Dict[Tuple[int, int], float] = {}
|
|
||||||
|
|
||||||
def calculate_slow_mode_delay(karma: int) -> int:
|
|
||||||
"""
|
|
||||||
Вычисляет задержку между сообщениями на основе кармы пользователя.
|
|
||||||
|
|
||||||
Логика:
|
|
||||||
- Карма = 0: 30 секунд
|
|
||||||
- Карма < 0: прогрессивное увеличение (до 120 секунд при карме -50 и ниже)
|
|
||||||
- Карма > 0: прогрессивное уменьшение (до 0 секунд при карме 50 и выше)
|
|
||||||
|
|
||||||
Args:
|
|
||||||
karma: Количество кармы пользователя
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Задержка в секундах
|
|
||||||
"""
|
|
||||||
if karma == 0:
|
|
||||||
return 30
|
|
||||||
|
|
||||||
elif karma > 0:
|
|
||||||
# Положительная карма: уменьшаем задержку
|
|
||||||
# От 30 секунд (карма 1) до 0 секунд (карма 50+)
|
|
||||||
# Формула: 30 - (karma * 0.6)
|
|
||||||
delay = max(0, 30 - (karma * 0.6))
|
|
||||||
return int(delay)
|
|
||||||
|
|
||||||
else: # karma < 0
|
|
||||||
# Отрицательная карма: увеличиваем задержку
|
|
||||||
# От 30 секунд (карма -1) до 120 секунд (карма -50 и ниже)
|
|
||||||
# Формула: 30 + (abs(karma) * 1.8)
|
|
||||||
delay = min(120, 30 + (abs(karma) * 1.8))
|
|
||||||
return int(delay)
|
|
||||||
|
|
||||||
def check_slow_mode(user_id: int, chat_id: int, karma: int) -> Tuple[bool, int]:
|
|
||||||
"""
|
|
||||||
Проверяет, может ли пользователь отправить сообщение согласно медленному режиму.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
user_id: ID пользователя
|
|
||||||
chat_id: ID чата
|
|
||||||
karma: Карма пользователя
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Tuple[bool, int]: (можно ли отправить сообщение, оставшееся время ожидания в секундах)
|
|
||||||
"""
|
|
||||||
current_time = time.time()
|
|
||||||
key = (user_id, chat_id)
|
|
||||||
|
|
||||||
# Вычисляем необходимую задержку
|
|
||||||
required_delay = calculate_slow_mode_delay(karma)
|
|
||||||
|
|
||||||
# Если задержка 0, пользователь может писать без ограничений
|
|
||||||
if required_delay == 0:
|
|
||||||
_last_message_times[key] = current_time
|
|
||||||
return True, 0
|
|
||||||
|
|
||||||
# Проверяем, есть ли запись о последнем сообщении
|
|
||||||
if key not in _last_message_times:
|
|
||||||
_last_message_times[key] = current_time
|
|
||||||
return True, 0
|
|
||||||
|
|
||||||
# Вычисляем время с последнего сообщения
|
|
||||||
last_message_time = _last_message_times[key]
|
|
||||||
time_passed = current_time - last_message_time
|
|
||||||
|
|
||||||
# Проверяем, прошло ли достаточно времени
|
|
||||||
if time_passed >= required_delay:
|
|
||||||
_last_message_times[key] = current_time
|
|
||||||
return True, 0
|
|
||||||
else:
|
|
||||||
# Вычисляем оставшееся время ожидания
|
|
||||||
remaining_time = int(required_delay - time_passed)
|
|
||||||
return False, remaining_time
|
|
||||||
|
|
||||||
def get_slow_mode_info(karma: int) -> str:
|
|
||||||
"""
|
|
||||||
Возвращает информацию о медленном режиме для данного уровня кармы.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
karma: Карма пользователя
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Строка с описанием режима
|
|
||||||
"""
|
|
||||||
delay = calculate_slow_mode_delay(karma)
|
|
||||||
|
|
||||||
if delay == 0:
|
|
||||||
return "🟢 Медленный режим отключен (высокая карма!)"
|
|
||||||
elif delay <= 10:
|
|
||||||
return f"🟢 Медленный режим: {delay} сек (хорошая карма)"
|
|
||||||
elif delay <= 30:
|
|
||||||
return f"🟡 Медленный режим: {delay} сек (нейтральная карма)"
|
|
||||||
elif delay <= 60:
|
|
||||||
return f"🟠 Медленный режим: {delay} сек (низкая карма)"
|
|
||||||
else:
|
|
||||||
return f"🔴 Медленный режим: {delay} сек (очень низкая карма)"
|
|
||||||
|
|
||||||
def format_time(seconds: int) -> str:
|
|
||||||
"""
|
|
||||||
Форматирует время ожидания в читаемый формат.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
seconds: Количество секунд
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Отформатированная строка
|
|
||||||
"""
|
|
||||||
if seconds < 60:
|
|
||||||
return f"{seconds} сек"
|
|
||||||
else:
|
|
||||||
minutes = seconds // 60
|
|
||||||
remaining_seconds = seconds % 60
|
|
||||||
if remaining_seconds > 0:
|
|
||||||
return f"{minutes} мин {remaining_seconds} сек"
|
|
||||||
else:
|
|
||||||
return f"{minutes} мин"
|
|
||||||
|
|
||||||
def cleanup_old_records(max_age_seconds: int = 3600):
|
|
||||||
"""
|
|
||||||
Очищает устаревшие записи из кэша (старше max_age_seconds).
|
|
||||||
Рекомендуется вызывать периодически для экономии памяти.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
max_age_seconds: Максимальный возраст записи в секундах (по умолчанию 1 час)
|
|
||||||
"""
|
|
||||||
current_time = time.time()
|
|
||||||
keys_to_remove = []
|
|
||||||
|
|
||||||
for key, timestamp in _last_message_times.items():
|
|
||||||
if current_time - timestamp > max_age_seconds:
|
|
||||||
keys_to_remove.append(key)
|
|
||||||
|
|
||||||
for key in keys_to_remove:
|
|
||||||
del _last_message_times[key]
|
|
||||||
|
|
||||||
if keys_to_remove:
|
|
||||||
logger.info(f"Очищено {len(keys_to_remove)} устаревших записей slow mode")
|
|
93
src/main.py
93
src/main.py
@@ -16,8 +16,6 @@ from action_reporter import init_action_reporter
|
|||||||
|
|
||||||
from config import MODULES_DIR
|
from config import MODULES_DIR
|
||||||
|
|
||||||
from message_queue import init_message_queue, add_to_queue
|
|
||||||
|
|
||||||
# Загружаем токен бота из .env
|
# Загружаем токен бота из .env
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
@@ -95,12 +93,6 @@ class UserUpdateMiddleware(BaseMiddleware):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка кэширования сообщения: {e}", exc_info=True)
|
logger.error(f"Ошибка кэширования сообщения: {e}", exc_info=True)
|
||||||
|
|
||||||
# ВАЖНО: Проверяем slow mode на основе кармы
|
|
||||||
# Применяется только в групповых чатах и не для команд
|
|
||||||
if await self._check_slow_mode(message):
|
|
||||||
# Сообщение заблокировано slow mode, прерываем обработку
|
|
||||||
return None
|
|
||||||
|
|
||||||
# ВАЖНО: Проверяем на мат ДО передачи другим обработчикам
|
# ВАЖНО: Проверяем на мат ДО передачи другим обработчикам
|
||||||
# Это позволяет auto_mute работать независимо от karma_tracker
|
# Это позволяет auto_mute работать независимо от karma_tracker
|
||||||
await self._check_profanity(message)
|
await self._check_profanity(message)
|
||||||
@@ -115,87 +107,6 @@ class UserUpdateMiddleware(BaseMiddleware):
|
|||||||
)
|
)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
# Проверка slow mode на основе кармы (вызывается в middleware)
|
|
||||||
async def _check_slow_mode(self, message):
|
|
||||||
"""
|
|
||||||
Проверяет, может ли пользователь отправить сообщение согласно slow mode.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
True если сообщение заблокировано, False если разрешено
|
|
||||||
"""
|
|
||||||
from karma_slow_mode import check_slow_mode, format_time
|
|
||||||
|
|
||||||
# Только для групповых чатов
|
|
||||||
if message.chat.type not in ['group', 'supergroup']:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Не проверяем команды
|
|
||||||
if not message.text or message.text.startswith('/'):
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Проверяем, является ли пользователь администратором
|
|
||||||
try:
|
|
||||||
chat_member = await self.bot.get_chat_member(message.chat.id, message.from_user.id)
|
|
||||||
if chat_member.status in ['administrator', 'creator']:
|
|
||||||
# Администраторы не подчиняются slow mode
|
|
||||||
return False
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Ошибка проверки статуса пользователя для slow mode: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Получаем карму пользователя
|
|
||||||
user_karma = self.db.get_karma(message.from_user.id, message.chat.id)
|
|
||||||
|
|
||||||
# Проверяем slow mode
|
|
||||||
can_send, remaining_time = check_slow_mode(message.from_user.id, message.chat.id, user_karma)
|
|
||||||
|
|
||||||
if not can_send:
|
|
||||||
# Сохраняем текст сообщения перед удалением
|
|
||||||
message_text = message.text
|
|
||||||
|
|
||||||
# Добавляем сообщение в очередь для отправки после задержки
|
|
||||||
try:
|
|
||||||
add_to_queue(
|
|
||||||
chat_id=message.chat.id,
|
|
||||||
user_id=message.from_user.id,
|
|
||||||
user_name=message.from_user.first_name,
|
|
||||||
text=message_text,
|
|
||||||
delay_seconds=remaining_time,
|
|
||||||
thread_id=message.message_thread_id
|
|
||||||
)
|
|
||||||
logger.info(f"[SLOW MODE] Сообщение от {message.from_user.id} добавлено в очередь на {remaining_time}с")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Ошибка добавления сообщения в очередь: {e}")
|
|
||||||
|
|
||||||
# Удаляем оригинальное сообщение
|
|
||||||
try:
|
|
||||||
await self.bot.delete_message(message.chat.id, message.message_id)
|
|
||||||
logger.info(f"[SLOW MODE] Удалено сообщение от {message.from_user.id} (карма: {user_karma}, осталось ждать: {remaining_time}с)")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Не удалось удалить сообщение slow mode: {e}")
|
|
||||||
|
|
||||||
# Отправляем уведомление (и сразу удаляем через 5 секунд)
|
|
||||||
try:
|
|
||||||
notification = await self.bot.send_message(
|
|
||||||
chat_id=message.chat.id,
|
|
||||||
text=f"⏱ <b>{message.from_user.first_name}</b>, ваше сообщение будет отправлено через <b>{format_time(remaining_time)}</b>.\n\n"
|
|
||||||
f"💡 Ваша карма: <b>{user_karma}</b>. Повышайте карму, чтобы уменьшить задержку!",
|
|
||||||
message_thread_id=message.message_thread_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Удаляем уведомление через 5 секунд
|
|
||||||
await asyncio.sleep(5)
|
|
||||||
try:
|
|
||||||
await self.bot.delete_message(message.chat.id, notification.message_id)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Ошибка отправки уведомления slow mode: {e}")
|
|
||||||
|
|
||||||
return True # Сообщение заблокировано
|
|
||||||
|
|
||||||
return False # Сообщение разрешено
|
|
||||||
|
|
||||||
# Проверка на нецензурную лексику (вызывается в middleware)
|
# Проверка на нецензурную лексику (вызывается в middleware)
|
||||||
async def _check_profanity(self, message):
|
async def _check_profanity(self, message):
|
||||||
"""Проверяет сообщение на мат и применяет мут если нужно"""
|
"""Проверяет сообщение на мат и применяет мут если нужно"""
|
||||||
@@ -307,10 +218,6 @@ async def main():
|
|||||||
# Устанавливаем команды бота
|
# Устанавливаем команды бота
|
||||||
await setup_bot_commands()
|
await setup_bot_commands()
|
||||||
|
|
||||||
# Инициализируем систему очереди сообщений
|
|
||||||
init_message_queue(bot)
|
|
||||||
logger.info("Система очереди сообщений инициализирована")
|
|
||||||
|
|
||||||
# Запускаем бота с обработкой реакций
|
# Запускаем бота с обработкой реакций
|
||||||
logger.info("Запуск бота с allowed_updates: message, message_reaction, chat_member")
|
logger.info("Запуск бота с allowed_updates: message, message_reaction, chat_member")
|
||||||
await bot.infinity_polling(allowed_updates=['message', 'message_reaction', 'chat_member'])
|
await bot.infinity_polling(allowed_updates=['message', 'message_reaction', 'chat_member'])
|
||||||
|
@@ -1,150 +0,0 @@
|
|||||||
# Очередь отложенных сообщений для медленного режима
|
|
||||||
# Сохраняет сообщения и отправляет их после истечения задержки
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import time
|
|
||||||
import logging
|
|
||||||
from typing import Dict, List, Optional
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from collections import deque
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class QueuedMessage:
|
|
||||||
"""Сообщение в очереди"""
|
|
||||||
chat_id: int
|
|
||||||
user_id: int
|
|
||||||
user_name: str
|
|
||||||
text: str
|
|
||||||
send_time: float # Unix timestamp когда нужно отправить
|
|
||||||
thread_id: Optional[int] = None
|
|
||||||
|
|
||||||
# Очередь сообщений для каждого чата
|
|
||||||
_message_queues: Dict[int, deque[QueuedMessage]] = {}
|
|
||||||
_processing_task: Optional[asyncio.Task] = None
|
|
||||||
_bot_instance = None
|
|
||||||
|
|
||||||
def init_message_queue(bot):
|
|
||||||
"""
|
|
||||||
Инициализирует систему очереди сообщений.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
bot: Экземпляр бота для отправки сообщений
|
|
||||||
"""
|
|
||||||
global _bot_instance, _processing_task
|
|
||||||
_bot_instance = bot
|
|
||||||
|
|
||||||
# Запускаем фоновую задачу обработки очереди, если она ещё не запущена
|
|
||||||
if _processing_task is None or _processing_task.done():
|
|
||||||
_processing_task = asyncio.create_task(_process_queue())
|
|
||||||
logger.info("Система очереди сообщений инициализирована")
|
|
||||||
|
|
||||||
def add_to_queue(chat_id: int, user_id: int, user_name: str, text: str, delay_seconds: int, thread_id: Optional[int] = None):
|
|
||||||
"""
|
|
||||||
Добавляет сообщение в очередь для отправки после задержки.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
chat_id: ID чата
|
|
||||||
user_id: ID пользователя
|
|
||||||
user_name: Имя пользователя
|
|
||||||
text: Текст сообщения
|
|
||||||
delay_seconds: Задержка в секундах перед отправкой
|
|
||||||
thread_id: ID топика (для супергрупп с топиками)
|
|
||||||
"""
|
|
||||||
send_time = time.time() + delay_seconds
|
|
||||||
|
|
||||||
message = QueuedMessage(
|
|
||||||
chat_id=chat_id,
|
|
||||||
user_id=user_id,
|
|
||||||
user_name=user_name,
|
|
||||||
text=text,
|
|
||||||
send_time=send_time,
|
|
||||||
thread_id=thread_id
|
|
||||||
)
|
|
||||||
|
|
||||||
# Добавляем очередь для чата, если её ещё нет
|
|
||||||
if chat_id not in _message_queues:
|
|
||||||
_message_queues[chat_id] = deque()
|
|
||||||
|
|
||||||
_message_queues[chat_id].append(message)
|
|
||||||
logger.info(f"[QUEUE] Сообщение от {user_name} ({user_id}) добавлено в очередь чата {chat_id}, отправка через {delay_seconds}с")
|
|
||||||
|
|
||||||
async def _process_queue():
|
|
||||||
"""
|
|
||||||
Фоновая задача для обработки очереди сообщений.
|
|
||||||
Проверяет каждую секунду, есть ли сообщения готовые к отправке.
|
|
||||||
"""
|
|
||||||
global _bot_instance
|
|
||||||
logger.info("[QUEUE] Запущена фоновая задача обработки очереди")
|
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
current_time = time.time()
|
|
||||||
|
|
||||||
# Проходим по всем чатам
|
|
||||||
for chat_id in list(_message_queues.keys()):
|
|
||||||
queue = _message_queues[chat_id]
|
|
||||||
|
|
||||||
# Обрабатываем сообщения, которые готовы к отправке
|
|
||||||
while queue and queue[0].send_time <= current_time:
|
|
||||||
message = queue.popleft()
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Отправляем сообщение
|
|
||||||
formatted_text = f"💬 <b>{message.user_name}</b>:\n{message.text}"
|
|
||||||
|
|
||||||
await _bot_instance.send_message(
|
|
||||||
chat_id=message.chat_id,
|
|
||||||
text=formatted_text,
|
|
||||||
message_thread_id=message.thread_id
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info(f"[QUEUE] Сообщение от {message.user_name} ({message.user_id}) отправлено в чат {message.chat_id}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"[QUEUE] Ошибка отправки сообщения из очереди: {e}", exc_info=True)
|
|
||||||
|
|
||||||
# Удаляем пустые очереди
|
|
||||||
if not queue:
|
|
||||||
del _message_queues[chat_id]
|
|
||||||
|
|
||||||
# Ждём 1 секунду перед следующей проверкой
|
|
||||||
await asyncio.sleep(1)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"[QUEUE] Ошибка в обработке очереди: {e}", exc_info=True)
|
|
||||||
await asyncio.sleep(1)
|
|
||||||
|
|
||||||
def get_queue_size(chat_id: int) -> int:
|
|
||||||
"""
|
|
||||||
Возвращает количество сообщений в очереди для чата.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
chat_id: ID чата
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Количество сообщений в очереди
|
|
||||||
"""
|
|
||||||
return len(_message_queues.get(chat_id, []))
|
|
||||||
|
|
||||||
def get_user_queue_position(chat_id: int, user_id: int) -> Optional[int]:
|
|
||||||
"""
|
|
||||||
Возвращает позицию пользователя в очереди (1-based).
|
|
||||||
|
|
||||||
Args:
|
|
||||||
chat_id: ID чата
|
|
||||||
user_id: ID пользователя
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Позиция в очереди или None если нет сообщений
|
|
||||||
"""
|
|
||||||
if chat_id not in _message_queues:
|
|
||||||
return None
|
|
||||||
|
|
||||||
queue = _message_queues[chat_id]
|
|
||||||
for i, msg in enumerate(queue):
|
|
||||||
if msg.user_id == user_id:
|
|
||||||
return i + 1
|
|
||||||
|
|
||||||
return None
|
|
Reference in New Issue
Block a user