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:
2025-09-02 18:22:02 +03:00
parent 013892dcb7
commit 1c6a37bc12
59 changed files with 5682 additions and 4204 deletions

View File

@@ -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"

View File

@@ -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

View File

@@ -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}")

View File

@@ -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:
# Удаляем сообщение из предложки

View File

@@ -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)

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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)

View File

@@ -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
)

View File

@@ -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:

View File

@@ -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 "😊"

View File

@@ -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