Enhance bot functionality and refactor database interactions
- Added `ca-certificates` installation to Dockerfile for improved network security. - Updated health check command in Dockerfile to include better timeout handling. - Refactored `run_helper.py` to implement proper signal handling and logging during shutdown. - Transitioned database operations to an asynchronous model in `async_db.py`, improving performance and responsiveness. - Updated database schema to support new foreign key relationships and optimized indexing for better query performance. - Enhanced various bot handlers to utilize async database methods, improving overall efficiency and user experience. - Removed obsolete database and fix scripts to streamline the project structure.
This commit is contained in:
@@ -40,7 +40,8 @@ admin_router.message.middleware(AdminAccessMiddleware())
|
||||
)
|
||||
async def admin_panel(
|
||||
message: types.Message,
|
||||
state: FSMContext
|
||||
state: FSMContext,
|
||||
**kwargs
|
||||
):
|
||||
"""Главное меню администратора"""
|
||||
try:
|
||||
@@ -66,11 +67,11 @@ async def get_last_users(
|
||||
try:
|
||||
logger.info(f"Получение списка последних пользователей. Пользователь: {message.from_user.full_name}")
|
||||
admin_service = AdminService(bot_db)
|
||||
users = admin_service.get_last_users()
|
||||
users = await admin_service.get_last_users()
|
||||
|
||||
# Преобразуем в формат для клавиатуры (кортежи как ожидает create_keyboard_with_pagination)
|
||||
users_data = [
|
||||
(user.full_name, user.username) # (full_name, username) - формат кортежей
|
||||
(user.full_name, user.user_id)
|
||||
for user in users
|
||||
]
|
||||
|
||||
@@ -97,7 +98,7 @@ async def get_banned_users(
|
||||
try:
|
||||
logger.info(f"Получение списка заблокированных пользователей. Пользователь: {message.from_user.full_name}")
|
||||
admin_service = AdminService(bot_db)
|
||||
message_text, buttons_list = admin_service.get_banned_users_for_display(0)
|
||||
message_text, buttons_list = await admin_service.get_banned_users_for_display(0)
|
||||
|
||||
if buttons_list:
|
||||
keyboard = create_keyboard_with_pagination(1, len(buttons_list), buttons_list, 'unlock')
|
||||
@@ -120,6 +121,7 @@ async def get_banned_users(
|
||||
async def start_ban_process(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
**kwargs
|
||||
):
|
||||
"""Начало процесса блокировки пользователя"""
|
||||
try:
|
||||
@@ -151,15 +153,15 @@ async def process_ban_target(
|
||||
|
||||
# Определяем пользователя
|
||||
if ban_type == "username":
|
||||
user = admin_service.get_user_by_username(message.text)
|
||||
user = await admin_service.get_user_by_username(message.text)
|
||||
if not user:
|
||||
await message.answer(f"Пользователь с username '{escape_html(message.text)}' не найден.")
|
||||
await return_to_admin_menu(message, state)
|
||||
return
|
||||
else: # ban_type == "id"
|
||||
try:
|
||||
user_id = admin_service.validate_user_input(message.text)
|
||||
user = admin_service.get_user_by_id(user_id)
|
||||
user_id = await admin_service.validate_user_input(message.text)
|
||||
user = await admin_service.get_user_by_id(user_id)
|
||||
if not user:
|
||||
await message.answer(f"Пользователь с ID {user_id} не найден в базе данных.")
|
||||
await return_to_admin_menu(message, state)
|
||||
@@ -195,7 +197,8 @@ async def process_ban_target(
|
||||
)
|
||||
async def process_ban_reason(
|
||||
message: types.Message,
|
||||
state: FSMContext
|
||||
state: FSMContext,
|
||||
**kwargs
|
||||
):
|
||||
"""Обработка причины блокировки"""
|
||||
try:
|
||||
@@ -218,6 +221,7 @@ async def process_ban_reason(
|
||||
async def process_ban_duration(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
**kwargs
|
||||
):
|
||||
"""Обработка срока блокировки"""
|
||||
try:
|
||||
@@ -260,7 +264,8 @@ async def process_ban_duration(
|
||||
async def confirm_ban(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
bot_db: MagicData("bot_db")
|
||||
bot_db: MagicData("bot_db"),
|
||||
**kwargs
|
||||
):
|
||||
"""Подтверждение блокировки пользователя"""
|
||||
try:
|
||||
@@ -269,7 +274,7 @@ async def confirm_ban(
|
||||
|
||||
|
||||
# Выполняем блокировку
|
||||
admin_service.ban_user(
|
||||
await admin_service.ban_user(
|
||||
user_id=user_data['target_user_id'],
|
||||
username=user_data['target_username'],
|
||||
reason=user_data['ban_reason'],
|
||||
@@ -298,7 +303,8 @@ async def confirm_ban(
|
||||
)
|
||||
async def cancel_ban_process(
|
||||
message: types.Message,
|
||||
state: FSMContext
|
||||
state: FSMContext,
|
||||
**kwargs
|
||||
):
|
||||
"""Отмена процесса блокировки"""
|
||||
try:
|
||||
@@ -312,7 +318,8 @@ async def cancel_ban_process(
|
||||
@admin_router.message(Command("test_metrics"))
|
||||
async def test_metrics_handler(
|
||||
message: types.Message,
|
||||
bot_db: MagicData("bot_db")
|
||||
bot_db: MagicData("bot_db"),
|
||||
**kwargs
|
||||
):
|
||||
"""Тестовый хендлер для проверки метрик"""
|
||||
from helper_bot.utils.metrics import metrics
|
||||
@@ -325,18 +332,23 @@ async def test_metrics_handler(
|
||||
|
||||
# Проверяем активных пользователей
|
||||
if hasattr(bot_db, 'connect') and hasattr(bot_db, 'cursor'):
|
||||
# Используем UNIX timestamp для сравнения с date_changed
|
||||
import time
|
||||
current_timestamp = int(time.time())
|
||||
one_day_ago = current_timestamp - (24 * 60 * 60) # 24 часа назад
|
||||
|
||||
active_users_query = """
|
||||
SELECT COUNT(DISTINCT user_id) as active_users
|
||||
FROM our_users
|
||||
WHERE date_changed > datetime('now', '-1 day')
|
||||
WHERE date_changed > ?
|
||||
"""
|
||||
try:
|
||||
bot_db.connect()
|
||||
bot_db.cursor.execute(active_users_query)
|
||||
result = bot_db.cursor.fetchone()
|
||||
await bot_db.connect()
|
||||
await bot_db.cursor.execute(active_users_query, (one_day_ago,))
|
||||
result = await bot_db.cursor.fetchone()
|
||||
active_users = result[0] if result else 0
|
||||
finally:
|
||||
bot_db.close()
|
||||
await bot_db.close()
|
||||
else:
|
||||
active_users = "N/A"
|
||||
|
||||
|
||||
@@ -17,6 +17,9 @@ class AdminAccessMiddleware(BaseMiddleware):
|
||||
async def __call__(self, handler, event: TelegramObject, data: Dict[str, Any]) -> Any:
|
||||
if hasattr(event, 'from_user'):
|
||||
user_id = event.from_user.id
|
||||
username = getattr(event.from_user, 'username', 'Unknown')
|
||||
|
||||
logger.info(f"AdminAccessMiddleware: проверка доступа для пользователя {username} (ID: {user_id})")
|
||||
|
||||
# Получаем bot_db из data (внедренного DependenciesMiddleware)
|
||||
bot_db = data.get('bot_db')
|
||||
@@ -25,7 +28,11 @@ class AdminAccessMiddleware(BaseMiddleware):
|
||||
bdf = get_global_instance()
|
||||
bot_db = bdf.get_db()
|
||||
|
||||
if not check_access(user_id, bot_db):
|
||||
is_admin_result = await check_access(user_id, bot_db)
|
||||
logger.info(f"AdminAccessMiddleware: результат проверки для {username}: {is_admin_result}")
|
||||
|
||||
if not is_admin_result:
|
||||
logger.warning(f"AdminAccessMiddleware: доступ запрещен для пользователя {username} (ID: {user_id})")
|
||||
if hasattr(event, 'answer'):
|
||||
await event.answer('Доступ запрещен!')
|
||||
return
|
||||
|
||||
@@ -29,10 +29,10 @@ class AdminService:
|
||||
def __init__(self, bot_db):
|
||||
self.bot_db = bot_db
|
||||
|
||||
def get_last_users(self) -> List[User]:
|
||||
async def get_last_users(self) -> List[User]:
|
||||
"""Получить список последних пользователей"""
|
||||
try:
|
||||
users_data = self.bot_db.get_last_users_from_db()
|
||||
users_data = await self.bot_db.get_last_users(30)
|
||||
return [
|
||||
User(
|
||||
user_id=user[1],
|
||||
@@ -45,31 +45,37 @@ class AdminService:
|
||||
logger.error(f"Ошибка при получении списка последних пользователей: {e}")
|
||||
raise
|
||||
|
||||
def get_banned_users(self) -> List[BannedUser]:
|
||||
async def get_banned_users(self) -> List[BannedUser]:
|
||||
"""Получить список заблокированных пользователей"""
|
||||
try:
|
||||
banned_users_data = self.bot_db.get_banned_users_from_db()
|
||||
return [
|
||||
BannedUser(
|
||||
user_id=user[1], # user_id
|
||||
username=user[0], # user_name
|
||||
reason=user[2], # message_for_user
|
||||
unban_date=user[3] # date_to_unban
|
||||
)
|
||||
for user in banned_users_data
|
||||
]
|
||||
banned_users_data = await self.bot_db.get_banned_users_from_db()
|
||||
banned_users = []
|
||||
for user_data in banned_users_data:
|
||||
user_id, reason, unban_date = user_data
|
||||
# Получаем username и full_name из таблицы users
|
||||
username = await self.bot_db.get_username(user_id)
|
||||
full_name = await self.bot_db.get_full_name_by_id(user_id)
|
||||
user_name = username or full_name or f"User_{user_id}"
|
||||
|
||||
banned_users.append(BannedUser(
|
||||
user_id=user_id,
|
||||
username=user_name,
|
||||
reason=reason,
|
||||
unban_date=unban_date
|
||||
))
|
||||
return banned_users
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении списка заблокированных пользователей: {e}")
|
||||
raise
|
||||
|
||||
def get_user_by_username(self, username: str) -> Optional[User]:
|
||||
async def get_user_by_username(self, username: str) -> Optional[User]:
|
||||
"""Получить пользователя по username"""
|
||||
try:
|
||||
user_id = self.bot_db.get_user_id_by_username(username)
|
||||
user_id = await self.bot_db.get_user_id_by_username(username)
|
||||
if not user_id:
|
||||
return None
|
||||
|
||||
full_name = self.bot_db.get_full_name_by_id(user_id)
|
||||
full_name = await self.bot_db.get_full_name_by_id(user_id)
|
||||
return User(
|
||||
user_id=user_id,
|
||||
username=username,
|
||||
@@ -79,27 +85,27 @@ class AdminService:
|
||||
logger.error(f"Ошибка при поиске пользователя по username {username}: {e}")
|
||||
raise
|
||||
|
||||
def get_user_by_id(self, user_id: int) -> Optional[User]:
|
||||
async def get_user_by_id(self, user_id: int) -> Optional[User]:
|
||||
"""Получить пользователя по ID"""
|
||||
try:
|
||||
user_info = self.bot_db.get_user_info_by_id(user_id)
|
||||
user_info = await self.bot_db.get_user_by_id(user_id)
|
||||
if not user_info:
|
||||
return None
|
||||
|
||||
return User(
|
||||
user_id=user_id,
|
||||
username=user_info.get('username', 'Неизвестно'),
|
||||
full_name=user_info.get('full_name', 'Неизвестно')
|
||||
username=user_info.username or 'Неизвестно',
|
||||
full_name=user_info.full_name or 'Неизвестно'
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при поиске пользователя по ID {user_id}: {e}")
|
||||
raise
|
||||
|
||||
def ban_user(self, user_id: int, username: str, reason: str, ban_days: Optional[int]) -> None:
|
||||
async def ban_user(self, user_id: int, username: str, reason: str, ban_days: Optional[int]) -> None:
|
||||
"""Заблокировать пользователя"""
|
||||
try:
|
||||
# Проверяем, не заблокирован ли уже пользователь
|
||||
if self.bot_db.check_user_in_blacklist(user_id):
|
||||
if await self.bot_db.check_user_in_blacklist(user_id):
|
||||
raise UserAlreadyBannedError(f"Пользователь {user_id} уже заблокирован")
|
||||
|
||||
# Рассчитываем дату разблокировки
|
||||
@@ -107,8 +113,8 @@ class AdminService:
|
||||
if ban_days is not None:
|
||||
date_to_unban = add_days_to_date(ban_days)
|
||||
|
||||
# Сохраняем в БД
|
||||
self.bot_db.set_user_blacklist(user_id, username, reason, date_to_unban)
|
||||
# Сохраняем в БД (username больше не передается, так как не используется в новой схеме)
|
||||
await self.bot_db.set_user_blacklist(user_id, None, reason, date_to_unban)
|
||||
|
||||
logger.info(f"Пользователь {user_id} ({username}) заблокирован. Причина: {reason}, срок: {ban_days} дней")
|
||||
|
||||
@@ -116,16 +122,16 @@ class AdminService:
|
||||
logger.error(f"Ошибка при блокировке пользователя {user_id}: {e}")
|
||||
raise
|
||||
|
||||
def unban_user(self, user_id: int) -> None:
|
||||
async def unban_user(self, user_id: int) -> None:
|
||||
"""Разблокировать пользователя"""
|
||||
try:
|
||||
self.bot_db.delete_user_blacklist(user_id)
|
||||
await self.bot_db.delete_user_blacklist(user_id)
|
||||
logger.info(f"Пользователь {user_id} разблокирован")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при разблокировке пользователя {user_id}: {e}")
|
||||
raise
|
||||
|
||||
def validate_user_input(self, input_text: str) -> int:
|
||||
async def validate_user_input(self, input_text: str) -> int:
|
||||
"""Валидация введенного ID пользователя"""
|
||||
try:
|
||||
user_id = int(input_text.strip())
|
||||
@@ -135,11 +141,12 @@ class AdminService:
|
||||
except ValueError:
|
||||
raise InvalidInputError("ID пользователя должен быть числом")
|
||||
|
||||
def get_banned_users_for_display(self, page: int = 0) -> tuple[str, list]:
|
||||
async def get_banned_users_for_display(self, page: int = 0) -> tuple[str, list]:
|
||||
"""Получить данные заблокированных пользователей для отображения"""
|
||||
try:
|
||||
message_text = get_banned_users_list(page, self.bot_db)
|
||||
buttons_list = get_banned_users_buttons(self.bot_db)
|
||||
message_text = await get_banned_users_list(page, self.bot_db)
|
||||
|
||||
buttons_list = await get_banned_users_buttons(self.bot_db)
|
||||
return message_text, buttons_list
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении данных заблокированных пользователей: {e}")
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
import html
|
||||
import traceback
|
||||
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
from aiogram import Router, F
|
||||
from aiogram.types import CallbackQuery
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.filters import MagicData
|
||||
|
||||
from helper_bot.handlers.voice.constants import CALLBACK_SAVE, CALLBACK_DELETE
|
||||
from helper_bot.handlers.voice.services import AudioFileService
|
||||
from logs.custom_logger import logger
|
||||
|
||||
from aiogram import Router
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import CallbackQuery
|
||||
from aiogram import F
|
||||
from aiogram.filters import MagicData
|
||||
|
||||
from helper_bot.keyboards.keyboards import create_keyboard_with_pagination, get_reply_keyboard_admin, \
|
||||
create_keyboard_for_ban_reason
|
||||
from helper_bot.utils.helper_func import get_banned_users_list, get_banned_users_buttons
|
||||
@@ -96,7 +89,7 @@ async def decline_post_for_group(
|
||||
|
||||
|
||||
@callback_router.callback_query(F.data == CALLBACK_BAN)
|
||||
async def ban_user_from_post(call: CallbackQuery):
|
||||
async def ban_user_from_post(call: CallbackQuery, **kwargs):
|
||||
ban_service = get_ban_service()
|
||||
# TODO: переделать на MagicData
|
||||
try:
|
||||
@@ -116,21 +109,29 @@ async def ban_user_from_post(call: CallbackQuery):
|
||||
|
||||
|
||||
@callback_router.callback_query(F.data.contains(CALLBACK_BAN))
|
||||
async def process_ban_user(call: CallbackQuery, state: FSMContext):
|
||||
async def process_ban_user(call: CallbackQuery, state: FSMContext, **kwargs):
|
||||
ban_service = get_ban_service()
|
||||
# TODO: переделать на MagicData
|
||||
user_id = call.data[4:]
|
||||
logger.info(f"Вызов функции process_ban_user. Данные callback: {call.data} пользователь: {user_id}")
|
||||
|
||||
# Проверяем, что user_id является валидным числом
|
||||
try:
|
||||
user_name = await ban_service.ban_user(user_id, "")
|
||||
await state.update_data(user_id=user_id, user_name=user_name, message_for_user=None, date_to_unban=None)
|
||||
user_id_int = int(user_id)
|
||||
except ValueError:
|
||||
logger.error(f"Некорректный user_id в callback: {user_id}")
|
||||
await call.answer(text="Ошибка: некорректный ID пользователя", show_alert=True, cache_time=3)
|
||||
return
|
||||
|
||||
try:
|
||||
user_name = await ban_service.ban_user(str(user_id_int), "")
|
||||
await state.update_data(user_id=user_id_int, user_name=user_name, message_for_user=None, date_to_unban=None)
|
||||
markup = create_keyboard_for_ban_reason()
|
||||
|
||||
user_name_escaped = html.escape(str(user_name))
|
||||
full_name_escaped = html.escape(str(call.message.from_user.full_name))
|
||||
await call.message.answer(
|
||||
text=f"<b>Выбран пользователь:\nid:</b> {user_id}\n<b>username:</b> {user_name_escaped}\nИмя:{full_name_escaped}\nВыбери причину бана из списка или напиши ее в чат",
|
||||
text=f"<b>Выбран пользователь:\nid:</b> {user_id_int}\n<b>username:</b> {user_name_escaped}\nИмя:{full_name_escaped}\nВыбери причину бана из списка или напиши ее в чат",
|
||||
reply_markup=markup
|
||||
)
|
||||
await state.set_state('BAN_2')
|
||||
@@ -141,13 +142,21 @@ async def process_ban_user(call: CallbackQuery, state: FSMContext):
|
||||
|
||||
|
||||
@callback_router.callback_query(F.data.contains(CALLBACK_UNLOCK))
|
||||
async def process_unlock_user(call: CallbackQuery):
|
||||
async def process_unlock_user(call: CallbackQuery, **kwargs):
|
||||
ban_service = get_ban_service()
|
||||
# TODO: переделать на MagicData
|
||||
user_id = call.data[7:]
|
||||
|
||||
# Проверяем, что user_id является валидным числом
|
||||
try:
|
||||
username = await ban_service.unlock_user(user_id)
|
||||
user_id_int = int(user_id)
|
||||
except ValueError:
|
||||
logger.error(f"Некорректный user_id в callback: {user_id}")
|
||||
await call.answer(text="Ошибка: некорректный ID пользователя", show_alert=True, cache_time=3)
|
||||
return
|
||||
|
||||
try:
|
||||
username = await ban_service.unlock_user(str(user_id_int))
|
||||
await call.answer(f'{MESSAGE_USER_UNLOCKED} {username}', show_alert=True)
|
||||
except UserNotFoundError:
|
||||
await call.answer(text='Пользователь не найден в базе', show_alert=True, cache_time=3)
|
||||
@@ -157,7 +166,7 @@ async def process_unlock_user(call: CallbackQuery):
|
||||
|
||||
|
||||
@callback_router.callback_query(F.data == CALLBACK_RETURN)
|
||||
async def return_to_main_menu(call: CallbackQuery):
|
||||
async def return_to_main_menu(call: CallbackQuery, **kwargs):
|
||||
await call.message.delete()
|
||||
logger.info(f"Запуск админ панели для пользователя: {call.message.from_user.id}")
|
||||
markup = get_reply_keyboard_admin()
|
||||
@@ -167,14 +176,21 @@ async def return_to_main_menu(call: CallbackQuery):
|
||||
@callback_router.callback_query(F.data.contains(CALLBACK_PAGE))
|
||||
async def change_page(
|
||||
call: CallbackQuery,
|
||||
bot_db: MagicData("bot_db")
|
||||
bot_db: MagicData("bot_db"),
|
||||
**kwargs
|
||||
):
|
||||
page_number = int(call.data[5:])
|
||||
try:
|
||||
page_number = int(call.data[5:])
|
||||
except ValueError:
|
||||
logger.error(f"Некорректный номер страницы в callback: {call.data}")
|
||||
await call.answer(text="Ошибка: некорректный номер страницы", show_alert=True, cache_time=3)
|
||||
return
|
||||
|
||||
logger.info(f"Переход на страницу {page_number}")
|
||||
|
||||
if call.message.text == 'Список пользователей которые последними обращались к боту':
|
||||
list_users = bot_db.get_last_users_from_db()
|
||||
keyboard = create_keyboard_with_pagination(int(page_number), len(list_users), list_users, 'ban')
|
||||
list_users = await bot_db.get_last_users(30)
|
||||
keyboard = create_keyboard_with_pagination(page_number, len(list_users), list_users, 'ban')
|
||||
await call.bot.edit_message_reply_markup(
|
||||
chat_id=call.message.chat.id,
|
||||
message_id=call.message.message_id,
|
||||
@@ -189,7 +205,7 @@ async def change_page(
|
||||
)
|
||||
|
||||
buttons = get_banned_users_buttons(bot_db)
|
||||
keyboard = create_keyboard_with_pagination(int(call.data[5:]), len(buttons), buttons, 'unlock')
|
||||
keyboard = create_keyboard_with_pagination(page_number, len(buttons), buttons, 'unlock')
|
||||
await call.bot.edit_message_reply_markup(
|
||||
chat_id=call.message.chat.id,
|
||||
message_id=call.message.message_id,
|
||||
@@ -201,27 +217,31 @@ async def change_page(
|
||||
async def save_voice_message(
|
||||
call: CallbackQuery,
|
||||
bot_db: MagicData("bot_db"),
|
||||
settings: MagicData("settings")
|
||||
settings: MagicData("settings"),
|
||||
**kwargs
|
||||
):
|
||||
try:
|
||||
# Создаем сервис для работы с аудио файлами
|
||||
audio_service = AudioFileService(bot_db)
|
||||
|
||||
# Получаем ID пользователя из базы
|
||||
user_id = bot_db.get_user_id_by_message_id_for_voice_bot(call.message.message_id)
|
||||
user_id = await bot_db.get_user_id_by_message_id_for_voice_bot(call.message.message_id)
|
||||
|
||||
# Генерируем имя файла
|
||||
file_name = audio_service.generate_file_name(user_id)
|
||||
file_name = await audio_service.generate_file_name(user_id)
|
||||
|
||||
# Собираем инфо о сообщении
|
||||
time_UTC = int(time.time())
|
||||
date_added = datetime.fromtimestamp(time_UTC)
|
||||
|
||||
# Получаем file_id из voice сообщения
|
||||
file_id = call.message.voice.file_id if call.message.voice else ""
|
||||
|
||||
# Сохраняем в базу данных
|
||||
audio_service.save_audio_file(file_name, user_id, date_added)
|
||||
await audio_service.save_audio_file(file_name, user_id, date_added, file_id)
|
||||
|
||||
# Скачиваем и сохраняем файл
|
||||
await audio_service.download_and_save_audio(call.bot, call.message.message_id, file_name)
|
||||
await audio_service.download_and_save_audio(call.bot, call.message, file_name)
|
||||
|
||||
# Удаляем сообщение из предложки
|
||||
await call.bot.delete_message(
|
||||
@@ -240,7 +260,8 @@ async def save_voice_message(
|
||||
async def delete_voice_message(
|
||||
call: CallbackQuery,
|
||||
bot_db: MagicData("bot_db"),
|
||||
settings: MagicData("settings")
|
||||
settings: MagicData("settings"),
|
||||
**kwargs
|
||||
):
|
||||
try:
|
||||
# Удаляем сообщение из предложки
|
||||
|
||||
@@ -10,24 +10,16 @@ from .services import PostPublishService, BanService
|
||||
def get_post_publish_service() -> PostPublishService:
|
||||
"""Фабрика для PostPublishService"""
|
||||
bdf = get_global_instance()
|
||||
bot = Bot(
|
||||
token=bdf.settings['Telegram']['bot_token'],
|
||||
default=DefaultBotProperties(parse_mode='HTML'),
|
||||
timeout=30.0
|
||||
)
|
||||
|
||||
db = bdf.get_db()
|
||||
settings = bdf.settings
|
||||
return PostPublishService(bot, db, settings)
|
||||
return PostPublishService(None, db, settings)
|
||||
|
||||
|
||||
def get_ban_service() -> BanService:
|
||||
"""Фабрика для BanService"""
|
||||
bdf = get_global_instance()
|
||||
bot = Bot(
|
||||
token=bdf.settings['Telegram']['bot_token'],
|
||||
default=DefaultBotProperties(parse_mode='HTML'),
|
||||
timeout=30.0
|
||||
)
|
||||
|
||||
db = bdf.get_db()
|
||||
settings = bdf.settings
|
||||
return BanService(bot, db, settings)
|
||||
return BanService(None, db, settings)
|
||||
|
||||
@@ -26,12 +26,19 @@ from logs.custom_logger import logger
|
||||
|
||||
class PostPublishService:
|
||||
def __init__(self, bot: Bot, db, settings: Dict[str, Any]):
|
||||
# bot может быть None - в этом случае используем бота из контекста сообщения
|
||||
self.bot = bot
|
||||
self.db = db
|
||||
self.settings = settings
|
||||
self.group_for_posts = settings['Telegram']['group_for_posts']
|
||||
self.main_public = settings['Telegram']['main_public']
|
||||
self.important_logs = settings['Telegram']['important_logs']
|
||||
|
||||
def _get_bot(self, message) -> Bot:
|
||||
"""Получает бота из контекста сообщения или использует переданного"""
|
||||
if self.bot:
|
||||
return self.bot
|
||||
return message.bot
|
||||
|
||||
async def publish_post(self, call: CallbackQuery) -> None:
|
||||
"""Основной метод публикации поста"""
|
||||
@@ -57,7 +64,7 @@ class PostPublishService:
|
||||
async def _publish_text_post(self, call: CallbackQuery) -> None:
|
||||
"""Публикация текстового поста"""
|
||||
text_post = html.escape(str(call.message.text))
|
||||
author_id = self._get_author_id(call.message.message_id)
|
||||
author_id = await self._get_author_id(call.message.message_id)
|
||||
|
||||
await send_text_message(self.main_public, call.message, text_post)
|
||||
await self._delete_post_and_notify_author(call, author_id)
|
||||
@@ -66,7 +73,7 @@ class PostPublishService:
|
||||
async def _publish_photo_post(self, call: CallbackQuery) -> None:
|
||||
"""Публикация поста с фото"""
|
||||
text_post_with_photo = html.escape(str(call.message.caption))
|
||||
author_id = self._get_author_id(call.message.message_id)
|
||||
author_id = await self._get_author_id(call.message.message_id)
|
||||
|
||||
await send_photo_message(self.main_public, call.message, call.message.photo[-1].file_id, text_post_with_photo)
|
||||
await self._delete_post_and_notify_author(call, author_id)
|
||||
@@ -75,7 +82,7 @@ class PostPublishService:
|
||||
async def _publish_video_post(self, call: CallbackQuery) -> None:
|
||||
"""Публикация поста с видео"""
|
||||
text_post_with_photo = html.escape(str(call.message.caption))
|
||||
author_id = self._get_author_id(call.message.message_id)
|
||||
author_id = await self._get_author_id(call.message.message_id)
|
||||
|
||||
await send_video_message(self.main_public, call.message, call.message.video.file_id, text_post_with_photo)
|
||||
await self._delete_post_and_notify_author(call, author_id)
|
||||
@@ -83,7 +90,7 @@ class PostPublishService:
|
||||
|
||||
async def _publish_video_note_post(self, call: CallbackQuery) -> None:
|
||||
"""Публикация поста с кружком"""
|
||||
author_id = self._get_author_id(call.message.message_id)
|
||||
author_id = await self._get_author_id(call.message.message_id)
|
||||
|
||||
await send_video_note_message(self.main_public, call.message, call.message.video_note.file_id)
|
||||
await self._delete_post_and_notify_author(call, author_id)
|
||||
@@ -92,7 +99,7 @@ class PostPublishService:
|
||||
async def _publish_audio_post(self, call: CallbackQuery) -> None:
|
||||
"""Публикация поста с аудио"""
|
||||
text_post_with_photo = html.escape(str(call.message.caption))
|
||||
author_id = self._get_author_id(call.message.message_id)
|
||||
author_id = await self._get_author_id(call.message.message_id)
|
||||
|
||||
await send_audio_message(self.main_public, call.message, call.message.audio.file_id, text_post_with_photo)
|
||||
await self._delete_post_and_notify_author(call, author_id)
|
||||
@@ -100,7 +107,7 @@ class PostPublishService:
|
||||
|
||||
async def _publish_voice_post(self, call: CallbackQuery) -> None:
|
||||
"""Публикация поста с войсом"""
|
||||
author_id = self._get_author_id(call.message.message_id)
|
||||
author_id = await self._get_author_id(call.message.message_id)
|
||||
|
||||
await send_voice_message(self.main_public, call.message, call.message.voice.file_id)
|
||||
await self._delete_post_and_notify_author(call, author_id)
|
||||
@@ -108,12 +115,12 @@ class PostPublishService:
|
||||
|
||||
async def _publish_media_group(self, call: CallbackQuery) -> None:
|
||||
"""Публикация медиагруппы"""
|
||||
post_content = self.db.get_post_content_from_telegram_by_last_id(call.message.message_id)
|
||||
pre_text = self.db.get_post_text_from_telegram_by_last_id(call.message.message_id)
|
||||
post_content = await self.db.get_post_content_from_telegram_by_last_id(call.message.message_id)
|
||||
pre_text = await self.db.get_post_text_from_telegram_by_last_id(call.message.message_id)
|
||||
post_text = html.escape(str(pre_text))
|
||||
author_id = self._get_author_id_for_media_group(call.message.message_id)
|
||||
author_id = await self._get_author_id_for_media_group(call.message.message_id)
|
||||
|
||||
await send_media_group_to_channel(bot=self.bot, chat_id=self.main_public, post_content=post_content, post_text=post_text)
|
||||
await send_media_group_to_channel(bot=self._get_bot(call.message), chat_id=self.main_public, post_content=post_content, post_text=post_text)
|
||||
await self._delete_media_group_and_notify_author(call, author_id)
|
||||
|
||||
async def decline_post(self, call: CallbackQuery) -> None:
|
||||
@@ -130,8 +137,8 @@ class PostPublishService:
|
||||
|
||||
async def _decline_single_post(self, call: CallbackQuery) -> None:
|
||||
"""Отклонение одиночного поста"""
|
||||
author_id = self._get_author_id(call.message.message_id)
|
||||
await self.bot.delete_message(chat_id=self.group_for_posts, message_id=call.message.message_id)
|
||||
author_id = await self._get_author_id(call.message.message_id)
|
||||
await self._get_bot(call.message).delete_message(chat_id=self.group_for_posts, message_id=call.message.message_id)
|
||||
try:
|
||||
await send_text_message(author_id, call.message, MESSAGE_POST_DECLINED)
|
||||
except Exception as e:
|
||||
@@ -142,12 +149,12 @@ class PostPublishService:
|
||||
|
||||
async def _decline_media_group(self, call: CallbackQuery) -> None:
|
||||
"""Отклонение медиагруппы"""
|
||||
post_ids = self.db.get_post_ids_from_telegram_by_last_id(call.message.message_id)
|
||||
post_ids = await self.db.get_post_ids_from_telegram_by_last_id(call.message.message_id)
|
||||
message_ids = [row[0] for row in post_ids]
|
||||
message_ids.append(call.message.message_id)
|
||||
|
||||
author_id = self._get_author_id_for_media_group(call.message.message_id)
|
||||
await self.bot.delete_messages(chat_id=self.group_for_posts, message_ids=message_ids)
|
||||
author_id = await self._get_author_id_for_media_group(call.message.message_id)
|
||||
await self._get_bot(call.message).delete_messages(chat_id=self.group_for_posts, message_ids=message_ids)
|
||||
try:
|
||||
await send_text_message(author_id, call.message, MESSAGE_POST_DECLINED)
|
||||
except Exception as e:
|
||||
@@ -155,23 +162,24 @@ class PostPublishService:
|
||||
raise UserBlockedBotError("Пользователь заблокировал бота")
|
||||
raise
|
||||
|
||||
def _get_author_id(self, message_id: int) -> int:
|
||||
async def _get_author_id(self, message_id: int) -> int:
|
||||
"""Получение ID автора по ID сообщения"""
|
||||
author_id = self.db.get_author_id_by_message_id(message_id)
|
||||
author_id = await self.db.get_author_id_by_message_id(message_id)
|
||||
if not author_id:
|
||||
raise PostNotFoundError(f"Автор не найден для сообщения {message_id}")
|
||||
return author_id
|
||||
|
||||
def _get_author_id_for_media_group(self, message_id: int) -> int:
|
||||
async def _get_author_id_for_media_group(self, message_id: int) -> int:
|
||||
"""Получение ID автора для медиагруппы"""
|
||||
author_id = self.db.get_author_id_by_helper_message_id(message_id)
|
||||
author_id = await self.db.get_author_id_by_helper_message_id(message_id)
|
||||
if not author_id:
|
||||
raise PostNotFoundError(f"Автор не найден для медиагруппы {message_id}")
|
||||
return author_id
|
||||
|
||||
async def _delete_post_and_notify_author(self, call: CallbackQuery, author_id: int) -> None:
|
||||
"""Удаление поста и уведомление автора"""
|
||||
await self.bot.delete_message(chat_id=self.group_for_posts, message_id=call.message.message_id)
|
||||
await self._get_bot(call.message).delete_message(chat_id=self.group_for_posts, message_id=call.message.message_id)
|
||||
|
||||
try:
|
||||
await send_text_message(author_id, call.message, MESSAGE_POST_PUBLISHED)
|
||||
except Exception as e:
|
||||
@@ -181,10 +189,10 @@ class PostPublishService:
|
||||
|
||||
async def _delete_media_group_and_notify_author(self, call: CallbackQuery, author_id: int) -> None:
|
||||
"""Удаление медиагруппы и уведомление автора"""
|
||||
post_ids = self.db.get_post_ids_from_telegram_by_last_id(call.message.message_id)
|
||||
post_ids = await self.db.get_post_ids_from_telegram_by_last_id(call.message.message_id)
|
||||
message_ids = [row[0] for row in post_ids]
|
||||
message_ids.append(call.message.message_id)
|
||||
await self.bot.delete_messages(chat_id=self.group_for_posts, message_ids=message_ids)
|
||||
await self._get_bot(call.message).delete_messages(chat_id=self.group_for_posts, message_ids=message_ids)
|
||||
try:
|
||||
await send_text_message(author_id, call.message, MESSAGE_POST_PUBLISHED)
|
||||
except Exception as e:
|
||||
@@ -203,24 +211,23 @@ class BanService:
|
||||
|
||||
async def ban_user_from_post(self, call: CallbackQuery) -> None:
|
||||
"""Бан пользователя за спам"""
|
||||
author_id = self.db.get_author_id_by_message_id(call.message.message_id)
|
||||
author_id = await self.db.get_author_id_by_message_id(call.message.message_id)
|
||||
if not author_id:
|
||||
raise UserNotFoundError(f"Автор не найден для сообщения {call.message.message_id}")
|
||||
|
||||
user_name = self.db.get_username(user_id=author_id)
|
||||
current_date = datetime.now()
|
||||
date_to_unban = current_date + timedelta(days=7)
|
||||
date_to_unban = int((current_date + timedelta(days=7)).timestamp())
|
||||
|
||||
self.db.set_user_blacklist(
|
||||
await self.db.set_user_blacklist(
|
||||
user_id=author_id,
|
||||
user_name=user_name,
|
||||
user_name=None,
|
||||
message_for_user="Спам",
|
||||
date_to_unban=date_to_unban
|
||||
)
|
||||
|
||||
await self.bot.delete_message(chat_id=self.group_for_posts, message_id=call.message.message_id)
|
||||
await self._get_bot(call.message).delete_message(chat_id=self.group_for_posts, message_id=call.message.message_id)
|
||||
|
||||
date_str = date_to_unban.strftime("%d.%m.%Y %H:%M")
|
||||
date_str = (current_date + timedelta(days=7)).strftime("%d.%m.%Y %H:%M")
|
||||
try:
|
||||
await send_text_message(author_id, call.message, MESSAGE_USER_BANNED_SPAM.format(date=date_str))
|
||||
except Exception as e:
|
||||
@@ -232,7 +239,7 @@ class BanService:
|
||||
|
||||
async def ban_user(self, user_id: str, user_name: str) -> str:
|
||||
"""Бан пользователя по ID"""
|
||||
user_name = self.db.get_username(user_id=user_id)
|
||||
user_name = await self.db.get_username(int(user_id))
|
||||
if not user_name:
|
||||
raise UserNotFoundError(f"Пользователь с ID {user_id} не найден в базе")
|
||||
|
||||
@@ -240,10 +247,10 @@ class BanService:
|
||||
|
||||
async def unlock_user(self, user_id: str) -> str:
|
||||
"""Разблокировка пользователя"""
|
||||
user_name = self.db.get_username(user_id=user_id)
|
||||
user_name = await self.db.get_username(int(user_id))
|
||||
if not user_name:
|
||||
raise UserNotFoundError(f"Пользователь с ID {user_id} не найден в базе")
|
||||
|
||||
delete_user_blacklist(user_id, self.db)
|
||||
await delete_user_blacklist(int(user_id), self.db)
|
||||
logger.info(f"Разблокирован пользователь с ID: {user_id} username:{user_name}")
|
||||
return user_name
|
||||
|
||||
@@ -5,6 +5,7 @@ from aiogram import Router, types
|
||||
from aiogram.fsm.context import FSMContext
|
||||
|
||||
# Local imports - filters
|
||||
from database.async_db import AsyncBotDB
|
||||
from helper_bot.filters.main import ChatTypeFilter
|
||||
|
||||
# Local imports - modular components
|
||||
@@ -26,7 +27,7 @@ from helper_bot.utils.metrics import (
|
||||
class GroupHandlers:
|
||||
"""Main handler class for group messages"""
|
||||
|
||||
def __init__(self, db, keyboard_markup: types.ReplyKeyboardMarkup):
|
||||
def __init__(self, db: AsyncBotDB, keyboard_markup: types.ReplyKeyboardMarkup):
|
||||
self.db = db
|
||||
self.keyboard_markup = keyboard_markup
|
||||
self.admin_reply_service = AdminReplyService(db)
|
||||
@@ -45,7 +46,7 @@ class GroupHandlers:
|
||||
)
|
||||
|
||||
@error_handler
|
||||
async def handle_message(self, message: types.Message, state: FSMContext):
|
||||
async def handle_message(self, message: types.Message, state: FSMContext, **kwargs):
|
||||
"""Handle admin reply to user through group chat"""
|
||||
|
||||
logger.info(
|
||||
@@ -67,7 +68,7 @@ class GroupHandlers:
|
||||
|
||||
try:
|
||||
# Get user ID for reply
|
||||
chat_id = self.admin_reply_service.get_user_id_for_reply(message_id)
|
||||
chat_id = await self.admin_reply_service.get_user_id_for_reply(message_id)
|
||||
|
||||
# Send reply to user
|
||||
await self.admin_reply_service.send_reply_to_user(
|
||||
@@ -86,7 +87,7 @@ class GroupHandlers:
|
||||
|
||||
|
||||
# Factory function to create handlers with dependencies
|
||||
def create_group_handlers(db, keyboard_markup: types.ReplyKeyboardMarkup) -> GroupHandlers:
|
||||
def create_group_handlers(db: AsyncBotDB, keyboard_markup: types.ReplyKeyboardMarkup) -> GroupHandlers:
|
||||
"""Create group handlers instance with dependencies"""
|
||||
return GroupHandlers(db, keyboard_markup)
|
||||
|
||||
@@ -103,6 +104,7 @@ def init_legacy_router():
|
||||
from helper_bot.keyboards.keyboards import get_reply_keyboard_leave_chat
|
||||
|
||||
bdf = get_global_instance()
|
||||
#TODO: поменять архитектуру и подключить правильный BotDB
|
||||
db = bdf.get_db()
|
||||
keyboard_markup = get_reply_keyboard_leave_chat()
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@ from helper_bot.utils.metrics import (
|
||||
|
||||
class DatabaseProtocol(Protocol):
|
||||
"""Protocol for database operations"""
|
||||
def get_user_by_message_id(self, message_id: int) -> Optional[int]: ...
|
||||
async def get_user_by_message_id(self, message_id: int) -> Optional[int]: ...
|
||||
async def add_message(self, message_text: str, user_id: int, message_id: int, date: int = None): ...
|
||||
|
||||
|
||||
class AdminReplyService:
|
||||
@@ -31,7 +32,7 @@ class AdminReplyService:
|
||||
def __init__(self, db: DatabaseProtocol) -> None:
|
||||
self.db = db
|
||||
|
||||
def get_user_id_for_reply(self, message_id: int) -> int:
|
||||
async def get_user_id_for_reply(self, message_id: int) -> int:
|
||||
"""
|
||||
Get user ID for reply by message ID.
|
||||
|
||||
@@ -44,7 +45,7 @@ class AdminReplyService:
|
||||
Raises:
|
||||
UserNotFoundError: If user is not found in database
|
||||
"""
|
||||
user_id = self.db.get_user_by_message_id(message_id)
|
||||
user_id = await self.db.get_user_by_message_id(message_id)
|
||||
if user_id is None:
|
||||
raise UserNotFoundError(f"User not found for message_id: {message_id}")
|
||||
return user_id
|
||||
|
||||
@@ -10,6 +10,7 @@ from aiogram.filters import Command, StateFilter
|
||||
from aiogram.fsm.context import FSMContext
|
||||
|
||||
# Local imports - filters and middlewares
|
||||
from database.async_db import AsyncBotDB
|
||||
from helper_bot.filters.main import ChatTypeFilter
|
||||
from helper_bot.middlewares.album_middleware import AlbumMiddleware
|
||||
from helper_bot.middlewares.blacklist_middleware import BlacklistMiddleware
|
||||
@@ -43,7 +44,7 @@ sleep = asyncio.sleep
|
||||
class PrivateHandlers:
|
||||
"""Main handler class for private messages"""
|
||||
|
||||
def __init__(self, db, settings: BotSettings):
|
||||
def __init__(self, db: AsyncBotDB, settings: BotSettings):
|
||||
self.db = db
|
||||
self.settings = settings
|
||||
self.user_service = UserService(db, settings)
|
||||
@@ -83,7 +84,7 @@ class PrivateHandlers:
|
||||
async def handle_emoji_message(self, message: types.Message, state: FSMContext, **kwargs):
|
||||
"""Handle emoji command"""
|
||||
await self.user_service.log_user_message(message)
|
||||
user_emoji = check_user_emoji(message)
|
||||
user_emoji = await check_user_emoji(message)
|
||||
await state.set_state(FSM_STATES["START"])
|
||||
if user_emoji is not None:
|
||||
await message.answer(f'Твоя эмодзя - {user_emoji}', parse_mode='HTML')
|
||||
@@ -91,11 +92,11 @@ class PrivateHandlers:
|
||||
@error_handler
|
||||
async def handle_restart_message(self, message: types.Message, state: FSMContext, **kwargs):
|
||||
"""Handle restart command"""
|
||||
markup = get_reply_keyboard(self.db, message.from_user.id)
|
||||
markup = await get_reply_keyboard(self.db, message.from_user.id)
|
||||
await self.user_service.log_user_message(message)
|
||||
await state.set_state(FSM_STATES["START"])
|
||||
await update_user_info('love', message)
|
||||
check_user_emoji(message)
|
||||
await check_user_emoji(message)
|
||||
await message.answer('Я перезапущен!', reply_markup=markup, parse_mode='HTML')
|
||||
|
||||
@error_handler
|
||||
@@ -110,7 +111,7 @@ class PrivateHandlers:
|
||||
await self.sticker_service.send_random_hello_sticker(message)
|
||||
|
||||
# Send welcome message with metrics
|
||||
markup = get_reply_keyboard(self.db, message.from_user.id)
|
||||
markup = await get_reply_keyboard(self.db, message.from_user.id)
|
||||
hello_message = messages.get_message(get_first_name(message), 'HELLO_MESSAGE')
|
||||
await message.answer(hello_message, reply_markup=markup, parse_mode='HTML')
|
||||
|
||||
@@ -151,7 +152,7 @@ class PrivateHandlers:
|
||||
await self.post_service.process_post(message, album)
|
||||
|
||||
# Send success message and return to start state
|
||||
markup_for_user = get_reply_keyboard(self.db, message.from_user.id)
|
||||
markup_for_user = await get_reply_keyboard(self.db, message.from_user.id)
|
||||
success_send_message = messages.get_message(get_first_name(message), 'SUCCESS_SEND_MESSAGE')
|
||||
await message.answer(success_send_message, reply_markup=markup_for_user)
|
||||
await state.set_state(FSM_STATES["START"])
|
||||
@@ -160,8 +161,8 @@ class PrivateHandlers:
|
||||
async def stickers(self, message: types.Message, state: FSMContext, **kwargs):
|
||||
"""Handle stickers request"""
|
||||
# User service operations with metrics
|
||||
markup = get_reply_keyboard(self.db, message.from_user.id)
|
||||
self.db.update_info_about_stickers(user_id=message.from_user.id)
|
||||
markup = await get_reply_keyboard(self.db, message.from_user.id)
|
||||
await self.db.update_stickers_info(message.from_user.id)
|
||||
await self.user_service.log_user_message(message)
|
||||
await message.answer(
|
||||
text=ERROR_MESSAGES["STICKERS_LINK"],
|
||||
@@ -187,14 +188,14 @@ class PrivateHandlers:
|
||||
await message.forward(chat_id=self.settings.group_for_message)
|
||||
|
||||
current_date = datetime.now()
|
||||
date = current_date.strftime("%Y-%m-%d %H:%M:%S")
|
||||
self.db.add_new_message_in_db(message.text, message.from_user.id, message.message_id + 1, date)
|
||||
date = int(current_date.timestamp())
|
||||
await self.db.add_message(message.text, message.from_user.id, message.message_id + 1, date)
|
||||
|
||||
question = messages.get_message(get_first_name(message), 'QUESTION')
|
||||
user_state = await state.get_state()
|
||||
|
||||
if user_state == FSM_STATES["PRE_CHAT"]:
|
||||
markup = get_reply_keyboard(self.db, message.from_user.id)
|
||||
markup = await get_reply_keyboard(self.db, message.from_user.id)
|
||||
await message.answer(question, reply_markup=markup)
|
||||
await state.set_state(FSM_STATES["START"])
|
||||
elif user_state == FSM_STATES["CHAT"]:
|
||||
@@ -203,7 +204,7 @@ class PrivateHandlers:
|
||||
|
||||
|
||||
# Factory function to create handlers with dependencies
|
||||
def create_private_handlers(db, settings: BotSettings) -> PrivateHandlers:
|
||||
def create_private_handlers(db: AsyncBotDB, settings: BotSettings) -> PrivateHandlers:
|
||||
"""Create private handlers instance with dependencies"""
|
||||
return PrivateHandlers(db, settings)
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ from dataclasses import dataclass
|
||||
# Third-party imports
|
||||
from aiogram import types
|
||||
from aiogram.types import FSInputFile
|
||||
from database.models import TelegramPost, User
|
||||
|
||||
# Local imports - utilities
|
||||
from helper_bot.utils.helper_func import (
|
||||
@@ -41,16 +42,14 @@ from helper_bot.utils.metrics import (
|
||||
|
||||
class DatabaseProtocol(Protocol):
|
||||
"""Protocol for database operations"""
|
||||
def user_exists(self, user_id: int) -> bool: ...
|
||||
def add_new_user_in_db(self, user_id: int, first_name: str, full_name: str,
|
||||
username: str, is_bot: bool, language_code: str,
|
||||
emoji: str, created_date: str, updated_date: str) -> None: ...
|
||||
def update_username_and_full_name(self, user_id: int, username: str, full_name: str) -> None: ...
|
||||
def update_date_for_user(self, date: str, user_id: int) -> None: ...
|
||||
def add_post_in_db(self, message_id: int, text: str, user_id: int) -> None: ...
|
||||
def update_info_about_stickers(self, user_id: int) -> None: ...
|
||||
def add_new_message_in_db(self, text: str, user_id: int, message_id: int, date: str) -> None: ...
|
||||
def update_helper_message_in_db(self, message_id: int, helper_message_id: int) -> None: ...
|
||||
async def user_exists(self, user_id: int) -> bool: ...
|
||||
async def add_user(self, user: User) -> None: ...
|
||||
async def update_user_info(self, user_id: int, username: str = None, full_name: str = None) -> None: ...
|
||||
async def update_user_date(self, user_id: int) -> None: ...
|
||||
async def add_post(self, post: TelegramPost) -> None: ...
|
||||
async def update_stickers_info(self, user_id: int) -> None: ...
|
||||
async def add_message(self, message_text: str, user_id: int, message_id: int, date: int = None) -> None: ...
|
||||
async def update_helper_message(self, message_id: int, helper_message_id: int) -> None: ...
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -75,11 +74,10 @@ class UserService:
|
||||
|
||||
@track_time("update_user_activity", "user_service")
|
||||
@track_errors("user_service", "update_user_activity")
|
||||
@db_query_time("update_user_activity", "users", "update")
|
||||
@db_query_time("update_user_activity", "user_service")
|
||||
async def update_user_activity(self, user_id: int) -> None:
|
||||
"""Update user's last activity timestamp with metrics tracking"""
|
||||
current_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
self.db.update_date_for_user(current_date, user_id)
|
||||
await self.db.update_user_date(user_id)
|
||||
|
||||
@track_time("ensure_user_exists", "user_service")
|
||||
@track_errors("user_service", "ensure_user_exists")
|
||||
@@ -92,19 +90,28 @@ class UserService:
|
||||
is_bot = message.from_user.is_bot
|
||||
language_code = message.from_user.language_code
|
||||
|
||||
current_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
if not self.db.user_exists(user_id):
|
||||
# Record database operation
|
||||
self.db.add_new_user_in_db(
|
||||
user_id, first_name, full_name, username, is_bot, language_code,
|
||||
"", current_date, current_date
|
||||
if not await self.db.user_exists(user_id):
|
||||
# Create User object with current timestamp
|
||||
current_timestamp = int(datetime.now().timestamp())
|
||||
user = User(
|
||||
user_id=user_id,
|
||||
first_name=first_name,
|
||||
full_name=full_name,
|
||||
username=username,
|
||||
is_bot=is_bot,
|
||||
language_code=language_code,
|
||||
emoji="",
|
||||
has_stickers=False,
|
||||
date_added=current_timestamp,
|
||||
date_changed=current_timestamp,
|
||||
voice_bot_welcome_received=False
|
||||
)
|
||||
metrics.record_db_query("add_new_user", 0.0, "users", "insert")
|
||||
await self.db.add_user(user)
|
||||
metrics.record_db_query("add_user", 0.0, "users", "insert")
|
||||
else:
|
||||
is_need_update = check_username_and_full_name(user_id, username, full_name, self.db)
|
||||
is_need_update = await check_username_and_full_name(user_id, username, full_name, self.db)
|
||||
if is_need_update:
|
||||
self.db.update_username_and_full_name(user_id, username, full_name)
|
||||
await self.db.update_user_info(user_id, username, full_name)
|
||||
metrics.record_db_query("update_username_fullname", 0.0, "users", "update")
|
||||
safe_full_name = html.escape(full_name) if full_name else "Неизвестный пользователь"
|
||||
safe_username = html.escape(username) if username else "Без никнейма"
|
||||
@@ -115,8 +122,8 @@ class UserService:
|
||||
chat_id=self.settings.group_for_logs,
|
||||
text=f'Для пользователя: {user_id} обновлены данные в БД.\nНовое имя: {safe_full_name}\nНовый ник:{safe_username}')
|
||||
|
||||
self.db.update_date_for_user(current_date, user_id)
|
||||
metrics.record_db_query("update_date_for_user", 0.0, "users", "update")
|
||||
await self.db.update_user_date(user_id)
|
||||
metrics.record_db_query("update_user_date", 0.0, "users", "update")
|
||||
|
||||
|
||||
@track_errors("user_service", "log_user_message")
|
||||
@@ -146,7 +153,13 @@ class PostService:
|
||||
markup = get_reply_keyboard_for_post()
|
||||
|
||||
sent_message_id = await send_text_message(self.settings.group_for_posts, message, post_text, markup)
|
||||
self.db.add_post_in_db(sent_message_id, message.text, message.from_user.id)
|
||||
post = TelegramPost(
|
||||
message_id=sent_message_id,
|
||||
text=message.text,
|
||||
author_id=message.from_user.id,
|
||||
created_at=int(datetime.now().timestamp())
|
||||
)
|
||||
await self.db.add_post(post)
|
||||
|
||||
@track_time("handle_photo_post", "post_service")
|
||||
@track_errors("post_service", "handle_photo_post")
|
||||
@@ -161,7 +174,13 @@ class PostService:
|
||||
self.settings.group_for_posts, message, message.photo[-1].file_id, post_caption, markup
|
||||
)
|
||||
|
||||
self.db.add_post_in_db(sent_message.message_id, sent_message.caption, message.from_user.id)
|
||||
post = TelegramPost(
|
||||
message_id=sent_message.message_id,
|
||||
text=sent_message.caption or "",
|
||||
author_id=message.from_user.id,
|
||||
created_at=int(datetime.now().timestamp())
|
||||
)
|
||||
await self.db.add_post(post)
|
||||
await add_in_db_media(sent_message, self.db)
|
||||
|
||||
@track_time("handle_video_post", "post_service")
|
||||
@@ -177,7 +196,13 @@ class PostService:
|
||||
self.settings.group_for_posts, message, message.video.file_id, post_caption, markup
|
||||
)
|
||||
|
||||
self.db.add_post_in_db(sent_message.message_id, sent_message.caption, message.from_user.id)
|
||||
post = TelegramPost(
|
||||
message_id=sent_message.message_id,
|
||||
text=sent_message.caption or "",
|
||||
author_id=message.from_user.id,
|
||||
created_at=int(datetime.now().timestamp())
|
||||
)
|
||||
await self.db.add_post(post)
|
||||
await add_in_db_media(sent_message, self.db)
|
||||
|
||||
@track_time("handle_video_note_post", "post_service")
|
||||
@@ -189,7 +214,13 @@ class PostService:
|
||||
self.settings.group_for_posts, message, message.video_note.file_id, markup
|
||||
)
|
||||
|
||||
self.db.add_post_in_db(sent_message.message_id, sent_message.caption, message.from_user.id)
|
||||
post = TelegramPost(
|
||||
message_id=sent_message.message_id,
|
||||
text=sent_message.caption or "",
|
||||
author_id=message.from_user.id,
|
||||
created_at=int(datetime.now().timestamp())
|
||||
)
|
||||
await self.db.add_post(post)
|
||||
await add_in_db_media(sent_message, self.db)
|
||||
|
||||
@track_time("handle_audio_post", "post_service")
|
||||
@@ -205,7 +236,13 @@ class PostService:
|
||||
self.settings.group_for_posts, message, message.audio.file_id, post_caption, markup
|
||||
)
|
||||
|
||||
self.db.add_post_in_db(sent_message.message_id, sent_message.caption, message.from_user.id)
|
||||
post = TelegramPost(
|
||||
message_id=sent_message.message_id,
|
||||
text=sent_message.caption or "",
|
||||
author_id=message.from_user.id,
|
||||
created_at=int(datetime.now().timestamp())
|
||||
)
|
||||
await self.db.add_post(post)
|
||||
await add_in_db_media(sent_message, self.db)
|
||||
|
||||
@track_time("handle_voice_post", "post_service")
|
||||
@@ -217,7 +254,13 @@ class PostService:
|
||||
self.settings.group_for_posts, message, message.voice.file_id, markup
|
||||
)
|
||||
|
||||
self.db.add_post_in_db(sent_message.message_id, sent_message.caption, message.from_user.id)
|
||||
post = TelegramPost(
|
||||
message_id=sent_message.message_id,
|
||||
text=sent_message.caption or "",
|
||||
author_id=message.from_user.id,
|
||||
created_at=int(datetime.now().timestamp())
|
||||
)
|
||||
await self.db.add_post(post)
|
||||
await add_in_db_media(sent_message, self.db)
|
||||
|
||||
@track_time("handle_media_group_post", "post_service")
|
||||
@@ -239,7 +282,7 @@ class PostService:
|
||||
markup = get_reply_keyboard_for_post()
|
||||
help_message_id = await send_text_message(self.settings.group_for_posts, message, "^", markup)
|
||||
|
||||
self.db.update_helper_message_in_db(
|
||||
await self.db.update_helper_message(
|
||||
message_id=media_group_message_id, helper_message_id=help_message_id
|
||||
)
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ class VoiceMessage:
|
||||
self.date_added = date_added
|
||||
self.file_id = file_id
|
||||
|
||||
|
||||
class VoiceBotService:
|
||||
"""Сервис для работы с голосовыми сообщениями"""
|
||||
|
||||
@@ -141,10 +140,10 @@ class VoiceBotService:
|
||||
logger.error(f"Ошибка при отправке приветственных сообщений: {e}")
|
||||
raise VoiceMessageError(f"Не удалось отправить приветственные сообщения: {e}")
|
||||
|
||||
def get_random_audio(self, user_id: int) -> Optional[Tuple[str, str, str]]:
|
||||
async def get_random_audio(self, user_id: int) -> Optional[Tuple[str, str, str]]:
|
||||
"""Получить случайное аудио для прослушивания"""
|
||||
try:
|
||||
check_audio = self.bot_db.check_listen_audio(user_id=user_id)
|
||||
check_audio = await self.bot_db.check_listen_audio(user_id=user_id)
|
||||
list_audio = list(check_audio)
|
||||
|
||||
if not list_audio:
|
||||
@@ -155,9 +154,9 @@ class VoiceBotService:
|
||||
audio_for_user = check_audio[number_element]
|
||||
|
||||
# Получаем информацию об авторе
|
||||
user_id_author = self.bot_db.get_user_id_by_file_name(audio_for_user)
|
||||
date_added = self.bot_db.get_date_by_file_name(audio_for_user)
|
||||
user_emoji = self.bot_db.check_emoji_for_user(user_id_author)
|
||||
user_id_author = await self.bot_db.get_user_id_by_file_name(audio_for_user)
|
||||
date_added = await self.bot_db.get_date_by_file_name(audio_for_user)
|
||||
user_emoji = await self.bot_db.get_user_emoji(user_id_author)
|
||||
|
||||
return audio_for_user, date_added, user_emoji
|
||||
|
||||
@@ -165,26 +164,26 @@ class VoiceBotService:
|
||||
logger.error(f"Ошибка при получении случайного аудио: {e}")
|
||||
raise AudioProcessingError(f"Не удалось получить случайное аудио: {e}")
|
||||
|
||||
def mark_audio_as_listened(self, file_name: str, user_id: int) -> None:
|
||||
async def mark_audio_as_listened(self, file_name: str, user_id: int) -> None:
|
||||
"""Пометить аудио как прослушанное"""
|
||||
try:
|
||||
self.bot_db.mark_listened_audio(file_name, user_id=user_id)
|
||||
await self.bot_db.mark_listened_audio(file_name, user_id=user_id)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при пометке аудио как прослушанного: {e}")
|
||||
raise DatabaseError(f"Не удалось пометить аудио как прослушанное: {e}")
|
||||
|
||||
def clear_user_listenings(self, user_id: int) -> None:
|
||||
async def clear_user_listenings(self, user_id: int) -> None:
|
||||
"""Очистить прослушивания пользователя"""
|
||||
try:
|
||||
self.bot_db.delete_listen_count_for_user(user_id)
|
||||
await self.bot_db.delete_listen_count_for_user(user_id)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при очистке прослушиваний: {e}")
|
||||
raise DatabaseError(f"Не удалось очистить прослушивания: {e}")
|
||||
|
||||
def get_remaining_audio_count(self, user_id: int) -> int:
|
||||
async def get_remaining_audio_count(self, user_id: int) -> int:
|
||||
"""Получить количество оставшихся непрослушанных аудио"""
|
||||
try:
|
||||
check_audio = self.bot_db.check_listen_audio(user_id=user_id)
|
||||
check_audio = await self.bot_db.check_listen_audio(user_id=user_id)
|
||||
return len(list(check_audio))
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении количества аудио: {e}")
|
||||
@@ -215,23 +214,29 @@ class AudioFileService:
|
||||
def __init__(self, bot_db):
|
||||
self.bot_db = bot_db
|
||||
|
||||
def generate_file_name(self, user_id: int) -> str:
|
||||
async def generate_file_name(self, user_id: int) -> str:
|
||||
"""Сгенерировать имя файла для аудио"""
|
||||
try:
|
||||
# Проверяем есть ли запись о файле в базе данных
|
||||
is_having_audio_from_user = self.bot_db.get_last_user_audio_record(user_id=user_id)
|
||||
user_audio_count = await self.bot_db.get_user_audio_records_count(user_id=user_id)
|
||||
|
||||
if is_having_audio_from_user is False:
|
||||
if user_audio_count == 0:
|
||||
# Если нет, то генерируем имя файла
|
||||
file_name = f'message_from_{user_id}_number_1'
|
||||
else:
|
||||
# Иначе берем последнюю запись из БД, добавляем к ней 1
|
||||
file_name = self.bot_db.get_path_for_audio_record(user_id=user_id)
|
||||
file_id = self.bot_db.get_id_for_audio_record(user_id) + 1
|
||||
path = Path(f'voice_users/{file_name}.ogg')
|
||||
file_name = await self.bot_db.get_path_for_audio_record(user_id=user_id)
|
||||
if file_name:
|
||||
# Извлекаем номер из имени файла и увеличиваем на 1
|
||||
try:
|
||||
current_number = int(file_name.split('_')[-1])
|
||||
new_number = current_number + 1
|
||||
except (ValueError, IndexError):
|
||||
new_number = user_audio_count + 1
|
||||
else:
|
||||
new_number = user_audio_count + 1
|
||||
|
||||
if path.exists():
|
||||
file_name = f'message_from_{user_id}_number_{file_id}'
|
||||
file_name = f'message_from_{user_id}_number_{new_number}'
|
||||
|
||||
return file_name
|
||||
|
||||
@@ -239,23 +244,31 @@ class AudioFileService:
|
||||
logger.error(f"Ошибка при генерации имени файла: {e}")
|
||||
raise FileOperationError(f"Не удалось сгенерировать имя файла: {e}")
|
||||
|
||||
def save_audio_file(self, file_name: str, user_id: int, date_added: datetime) -> None:
|
||||
async def save_audio_file(self, file_name: str, user_id: int, date_added: datetime, file_id: str) -> None:
|
||||
"""Сохранить информацию об аудио файле в базу данных"""
|
||||
try:
|
||||
self.bot_db.add_audio_record(file_name, user_id, date_added)
|
||||
await self.bot_db.add_audio_record_simple(file_name, user_id, date_added)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при сохранении аудио файла в БД: {e}")
|
||||
raise DatabaseError(f"Не удалось сохранить аудио файл в БД: {e}")
|
||||
|
||||
async def download_and_save_audio(self, bot, message_id: int, file_name: str) -> None:
|
||||
async def download_and_save_audio(self, bot, message, file_name: str) -> None:
|
||||
"""Скачать и сохранить аудио файл"""
|
||||
try:
|
||||
# Получаем информацию о файле
|
||||
file_info = await bot.get_file(file_id=bot.get_message(message_id).voice.file_id)
|
||||
# Проверяем наличие голосового сообщения
|
||||
if not message or not message.voice:
|
||||
raise FileOperationError("Сообщение или голосовое сообщение не найдено")
|
||||
|
||||
file_id = message.voice.file_id
|
||||
file_info = await bot.get_file(file_id=file_id)
|
||||
downloaded_file = await bot.download_file(file_path=file_info.file_path)
|
||||
|
||||
# Создаем директорию если она не существует
|
||||
import os
|
||||
os.makedirs(VOICE_USERS_DIR, exist_ok=True)
|
||||
|
||||
# Сохраняем файл
|
||||
with open(f'voice_users/{file_name}.ogg', 'wb') as new_file:
|
||||
with open(f'{VOICE_USERS_DIR}/{file_name}.ogg', 'wb') as new_file:
|
||||
new_file.write(downloaded_file.read())
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@@ -70,26 +70,30 @@ def plural_time(type: int, n: float) -> str:
|
||||
return str(new_number) + ' ' + word[p]
|
||||
|
||||
|
||||
def get_last_message_text(bot_db) -> Optional[str]:
|
||||
async def get_last_message_text(bot_db) -> Optional[str]:
|
||||
"""Получить текст сообщения о времени последней записи"""
|
||||
try:
|
||||
date_from_db = bot_db.last_date_audio()
|
||||
return format_time_ago(date_from_db)
|
||||
date_from_db = await bot_db.last_date_audio()
|
||||
if date_from_db is None:
|
||||
return None
|
||||
# Преобразуем UNIX timestamp в строку для format_time_ago
|
||||
date_string = datetime.fromtimestamp(date_from_db).strftime("%Y-%m-%d %H:%M:%S")
|
||||
return format_time_ago(date_string)
|
||||
except Exception as e:
|
||||
logger.error(f"Не удалось получить дату последнего сообщения - {e}")
|
||||
return None
|
||||
|
||||
|
||||
def validate_voice_message(message) -> bool:
|
||||
async def validate_voice_message(message) -> bool:
|
||||
"""Проверить валидность голосового сообщения"""
|
||||
return message.content_type == 'voice'
|
||||
|
||||
|
||||
def get_user_emoji_safe(bot_db, user_id: int) -> str:
|
||||
async def get_user_emoji_safe(bot_db, user_id: int) -> str:
|
||||
"""Безопасно получить эмодзи пользователя"""
|
||||
try:
|
||||
user_emoji = bot_db.check_emoji_for_user(user_id)
|
||||
return user_emoji if user_emoji else "😊"
|
||||
user_emoji = await bot_db.get_user_emoji(user_id)
|
||||
return user_emoji if user_emoji and user_emoji != "Смайл еще не определен" else "😊"
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при получении эмодзи пользователя {user_id}: {e}")
|
||||
return "😊"
|
||||
|
||||
@@ -98,7 +98,7 @@ class VoiceHandlers:
|
||||
"""Обработчик кнопки 'Голосовой бот' из основной клавиатуры"""
|
||||
try:
|
||||
# Проверяем, получал ли пользователь приветственное сообщение
|
||||
welcome_received = bot_db.check_voice_bot_welcome_received(message.from_user.id)
|
||||
welcome_received = await bot_db.check_voice_bot_welcome_received(message.from_user.id)
|
||||
logger.info(f"Пользователь {message.from_user.id}: welcome_received = {welcome_received}")
|
||||
|
||||
if welcome_received:
|
||||
@@ -124,7 +124,7 @@ class VoiceHandlers:
|
||||
logger.info(f"Пользователь {message.from_user.id}: вызывается функция restart_function")
|
||||
await message.forward(chat_id=settings['Telegram']['group_for_logs'])
|
||||
await update_user_info(VOICE_BOT_NAME, message)
|
||||
check_user_emoji(message)
|
||||
await check_user_emoji(message)
|
||||
markup = get_main_keyboard()
|
||||
await message.answer(text='🎤 Записывайся или слушай!', reply_markup=markup)
|
||||
await state.set_state(STATE_START)
|
||||
@@ -136,7 +136,7 @@ class VoiceHandlers:
|
||||
settings: MagicData("settings")
|
||||
):
|
||||
await message.forward(chat_id=settings['Telegram']['group_for_logs'])
|
||||
user_emoji = check_user_emoji(message)
|
||||
user_emoji = await check_user_emoji(message)
|
||||
await state.set_state(STATE_START)
|
||||
if user_emoji is not None:
|
||||
await message.answer(f'Твоя эмодзя - {user_emoji}', parse_mode='HTML')
|
||||
@@ -167,7 +167,7 @@ class VoiceHandlers:
|
||||
await state.set_state(STATE_START)
|
||||
await message.forward(chat_id=settings['Telegram']['group_for_logs'])
|
||||
await update_user_info(VOICE_BOT_NAME, message)
|
||||
user_emoji = get_user_emoji_safe(bot_db, message.from_user.id)
|
||||
user_emoji = await get_user_emoji_safe(bot_db, message.from_user.id)
|
||||
|
||||
# Создаем сервис и отправляем приветственные сообщения
|
||||
voice_service = VoiceBotService(bot_db, settings)
|
||||
@@ -175,7 +175,7 @@ class VoiceHandlers:
|
||||
|
||||
# Отмечаем, что пользователь получил приветственное сообщение
|
||||
try:
|
||||
bot_db.mark_voice_bot_welcome_received(message.from_user.id)
|
||||
await bot_db.mark_voice_bot_welcome_received(message.from_user.id)
|
||||
logger.info(f"Пользователь {message.from_user.id}: отмечен как получивший приветствие")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при отметке получения приветствия: {e}")
|
||||
@@ -194,7 +194,7 @@ class VoiceHandlers:
|
||||
|
||||
# Очищаем прослушивания через сервис
|
||||
voice_service = VoiceBotService(bot_db, settings)
|
||||
voice_service.clear_user_listenings(message.from_user.id)
|
||||
await voice_service.clear_user_listenings(message.from_user.id)
|
||||
|
||||
listenings_cleared_message = messages.get_message(get_first_name(message), 'LISTENINGS_CLEARED_MESSAGE')
|
||||
await message.answer(
|
||||
@@ -218,7 +218,7 @@ class VoiceHandlers:
|
||||
await message.answer(text=record_voice_message, reply_markup=markup)
|
||||
|
||||
try:
|
||||
message_with_date = get_last_message_text(bot_db)
|
||||
message_with_date = await get_last_message_text(bot_db)
|
||||
if message_with_date:
|
||||
await message.answer(text=message_with_date, parse_mode="html")
|
||||
except Exception as e:
|
||||
@@ -240,7 +240,7 @@ class VoiceHandlers:
|
||||
await message.forward(chat_id=settings['Telegram']['group_for_logs'])
|
||||
markup = get_main_keyboard()
|
||||
|
||||
if validate_voice_message(message):
|
||||
if await validate_voice_message(message):
|
||||
markup_for_voice = get_reply_keyboard_for_voice()
|
||||
|
||||
# Отправляем аудио в приватный канал
|
||||
@@ -252,7 +252,7 @@ class VoiceHandlers:
|
||||
)
|
||||
|
||||
# Сохраняем в базу инфо о посте
|
||||
bot_db.set_user_id_and_message_id_for_voice_bot(sent_message.message_id, message.from_user.id)
|
||||
await bot_db.set_user_id_and_message_id_for_voice_bot(sent_message.message_id, message.from_user.id)
|
||||
|
||||
# Отправляем юзеру ответ и возвращаем его в меню
|
||||
voice_saved_message = messages.get_message(get_first_name(message), 'VOICE_SAVED_MESSAGE')
|
||||
@@ -278,13 +278,13 @@ class VoiceHandlers:
|
||||
|
||||
try:
|
||||
# Получаем случайное аудио
|
||||
audio_data = voice_service.get_random_audio(message.from_user.id)
|
||||
audio_data = await voice_service.get_random_audio(message.from_user.id)
|
||||
|
||||
if not audio_data:
|
||||
no_audio_message = messages.get_message(get_first_name(message), 'NO_AUDIO_MESSAGE')
|
||||
await message.answer(text=no_audio_message, reply_markup=markup)
|
||||
try:
|
||||
message_with_date = get_last_message_text(bot_db)
|
||||
message_with_date = await get_last_message_text(bot_db)
|
||||
if message_with_date:
|
||||
await message.answer(text=message_with_date, parse_mode="html")
|
||||
except Exception as e:
|
||||
@@ -331,10 +331,10 @@ class VoiceHandlers:
|
||||
)
|
||||
|
||||
# Маркируем сообщение как прослушанное только после успешной отправки
|
||||
voice_service.mark_audio_as_listened(audio_for_user, message.from_user.id)
|
||||
await voice_service.mark_audio_as_listened(audio_for_user, message.from_user.id)
|
||||
|
||||
# Получаем количество оставшихся аудио только после успешной отправки
|
||||
remaining_count = voice_service.get_remaining_audio_count(message.from_user.id) - 1
|
||||
remaining_count = await voice_service.get_remaining_audio_count(message.from_user.id) - 1
|
||||
await message.answer(
|
||||
text=f'Осталось непрослушанных: <b>{remaining_count}</b>',
|
||||
reply_markup=markup
|
||||
|
||||
Reference in New Issue
Block a user