From e0e0a6de51948d29d4bd577f0a239555ffb84d1e Mon Sep 17 00:00:00 2001 From: Andrey Date: Sun, 17 Nov 2024 00:50:55 +0300 Subject: [PATCH] add new func in voice bot --- database/db.py | 70 +++++++++++++++++++- helper_bot/utils/helper_func.py | 2 +- requirements.txt | 45 +++++++------ voice_bot/handlers/callback_handler.py | 68 +++++++++++++++++++ voice_bot/handlers/voice_handler.py | 92 +++++++++++++++----------- voice_bot/keyboards/keyboards.py | 14 +++- voice_bot/main.py | 3 +- voice_bot/utils/helper_func.py | 3 + 8 files changed, 229 insertions(+), 68 deletions(-) create mode 100644 voice_bot/handlers/callback_handler.py diff --git a/database/db.py b/database/db.py index 999b796..c7c2361 100644 --- a/database/db.py +++ b/database/db.py @@ -719,7 +719,7 @@ class BotDB: None: Если обновление прошло успешно sqlite3. Error: Если произошла ошибка при выполнении запроса """ - self.logger.info(f"Запуск функции update_date_for_user: user_id={user_id}, emoji={emoji}") + self.logger.info(f"Запуск функции update_emoji_for_user: user_id={user_id}, emoji={emoji}") try: self.connect() self.cursor.execute("UPDATE our_users SET emoji = ? WHERE user_id = ?", @@ -750,8 +750,12 @@ class BotDB: try: self.connect() self.cursor.execute("SELECT emoji FROM our_users WHERE user_id = ?", (user_id,)) - result = self.cursor.fetchone()[0] - return result + pre_result = self.cursor.fetchone() + if pre_result[0] is not None: + print(f'Результат функции check_emoji_for_user - {str(pre_result[0])}') + return str(pre_result[0]) + else: + return None except sqlite3.Error as error: self.logger.error(f"Ошибка проверки эмодзи в базе: {error}") return None @@ -1046,6 +1050,48 @@ class BotDB: except Exception as e: self.logger.error(f"Ошибка в функции get_author_id_by_helper_message_id {str(e)}") + def get_user_id_by_message_id_for_voice_bot(self, message_id: int): + self.logger.info(f"Запуск функции get_user_id_by_message_id_for_voice_bot, идентификатор поста " + f"{message_id}") + try: + self.connect() + result = self.cursor.execute("SELECT user_id " + "FROM audio_moderate WHERE message_id = ?", + (message_id,)) + user_id = result.fetchone()[0] + self.logger.info(f"Функция get_user_id_by_message_id_for_voice_bot получила author_id {user_id}") + return user_id + except Exception as e: + self.logger.error(f"Ошибка в функции get_user_id_by_message_id_for_voice_bot {str(e)}") + + def set_user_id_and_message_id_for_voice_bot(self, message_id: int, user_id: int): + self.logger.info(f"Запуск функции set_user_id_and_message_id_for_voice_bot, идентификатор поста " + f"{message_id}, user_id {user_id}") + try: + self.connect() + result = self.cursor.execute( + "INSERT INTO audio_moderate (message_id, user_id)" + "VALUES (?, ?)", (message_id, user_id)) + self.conn.commit() + self.logger.info(f"Функция set_user_id_and_message_id_for_voice_bot отработала успешно") + return True + except Exception as e: + self.logger.error(f"Ошибка в функции set_user_id_and_message_id_for_voice_bot {str(e)}") + + def get_user_id_by_file_name(self, file_name: str): + self.logger.info(f"Запуск функции get_user_id_by_file_name, идентификатор поста " + f"{file_name}") + try: + self.connect() + result = self.cursor.execute("SELECT author_id " + "FROM audio_message_reference WHERE file_name = ?", + (file_name,)) + user_id = result.fetchone()[0] + self.logger.info(f"Функция get_user_id_by_file_name получила author_id {user_id}") + return user_id + except Exception as e: + self.logger.error(f"Ошибка в функции get_user_id_by_file_name {str(e)}") + def add_post_content_in_db(self, post_id: int, message_id: int, content_name: str, type_content: str): self.logger.info( f"Запуск функции add_post_content_in_db: post_id={post_id}, message_id={message_id}, " @@ -1156,6 +1202,24 @@ class BotDB: finally: self.close() + def delete_listen_count_for_user(self, user_id): + """Удаляет данные о прослушанных пользователем аудио""" + self.logger.info( + f"Запуск функции delete_listen_count_for_user. user_id={user_id}") + try: + self.connect() + self.cursor.execute("DELETE FROM `listen_audio_users` WHERE `user_id` = ?", + (user_id,)) + self.conn.commit() + self.logger.info( + f"Функция delete_listen_count_for_user успешно отработала") + return None + except sqlite3.Error as error: + self.logger.error(f"Ошибка удаления записей прослушивания по пользователю: {error}") + raise + finally: + self.close() + def get_id_for_audio_record(self, user_id): """Получает ID аудио сообщения пользователя""" self.logger.info( diff --git a/helper_bot/utils/helper_func.py b/helper_bot/utils/helper_func.py index 42b5d09..facd583 100644 --- a/helper_bot/utils/helper_func.py +++ b/helper_bot/utils/helper_func.py @@ -425,7 +425,7 @@ async def update_user_info(source: str, message: types.Message): def check_user_emoji(user_id: int): - if not BotDB.check_emoji_for_user(user_id=user_id): + if BotDB.check_emoji_for_user(user_id=user_id) is None: user_emoji = get_random_emoji() BotDB.update_emoji_for_user(user_id=user_id, emoji=user_emoji) diff --git a/requirements.txt b/requirements.txt index 6da9b91..9dea5b4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,22 +1,23 @@ -APScheduler==3.10.4 -certifi~=2024.6.2 -charset-normalizer==3.3.2 -coverage==7.5.4 -exceptiongroup==1.2.1 -idna==3.7 -iniconfig==2.0.0 -loguru==0.7.2 -packaging==24.1 -pluggy==1.5.0 -pytest==8.2.2 -pytz==2024.1 -requests==2.32.3 -six==1.16.0 -tomli==2.0.1 -tzlocal==5.2 -urllib3~=2.2.1 -pip~=23.2.1 -attrs~=23.2.0 -typing_extensions~=4.12.2 -aiohttp~=3.9.5 -aiogram~=3.10.0 \ No newline at end of file +APScheduler==3.10.4 +certifi~=2024.6.2 +charset-normalizer==3.3.2 +coverage==7.5.4 +exceptiongroup==1.2.1 +idna==3.7 +iniconfig==2.0.0 +loguru==0.7.2 +packaging==24.1 +pluggy==1.5.0 +pytest==8.2.2 +pytz==2024.1 +requests==2.32.3 +six==1.16.0 +tomli==2.0.1 +tzlocal==5.2 +urllib3~=2.2.1 +pip~=23.2.1 +attrs~=23.2.0 +typing_extensions~=4.12.2 +aiohttp~=3.9.5 +aiogram~=3.10.0 +emoji~=2.14.0 \ No newline at end of file diff --git a/voice_bot/handlers/callback_handler.py b/voice_bot/handlers/callback_handler.py new file mode 100644 index 0000000..e79cbdd --- /dev/null +++ b/voice_bot/handlers/callback_handler.py @@ -0,0 +1,68 @@ +import time +from datetime import datetime +from pathlib import Path + +from aiogram import Router, F +from aiogram.types import CallbackQuery + +from helper_bot.utils.base_dependency_factory import BaseDependencyFactory + +callback_router = Router() + +bdf = BaseDependencyFactory() + +GROUP_FOR_LOGS = bdf.settings['Telegram']['group_for_logs'] +GROUP_FOR_POST = bdf.settings['Telegram']['group_for_posts'] +IMPORTANT_LOGS = bdf.settings['Telegram']['important_logs'] +PREVIEW_LINK = bdf.settings['Telegram']['preview_link'] +LOGS = bdf.settings['Settings']['logs'] +TEST = bdf.settings['Settings']['test'] + +BotDB = bdf.get_db() + + +@callback_router.callback_query( + F.data == "save" +) +async def save_voice_message(call: CallbackQuery): + file_name = '' + file_id = 1 + user_id = BotDB.get_user_id_by_message_id_for_voice_bot(call.message.message_id) + # Проверяем что запись о файле есть в базе данных + is_having_audio_from_user = BotDB.get_last_user_audio_record(user_id=user_id) + if is_having_audio_from_user is False: + # Если нет, то генерируем имя файла + file_name = f'message_from_{user_id}_number_{file_id}' + else: + # Иначе берем последнюю запись из БД, добавляем к ней 1, и создаем новую запись + file_name = BotDB.get_path_for_audio_record(user_id=user_id) + file_id = BotDB.get_id_for_audio_record(user_id) + 1 + path = Path(f'voice_users/{file_name}.ogg') + if path.exists(): + file_name = f'message_from_{user_id}_number_{file_id}' + else: + pass + # Собираем инфо о сообщении + time_UTC = int(time.time()) + date_added = datetime.fromtimestamp(time_UTC) + + # Сохраняем в базку + BotDB.add_audio_record(file_name, user_id, date_added, 0, file_id) + + file_info = await call.message.bot.get_file(file_id=call.message.voice.file_id) + downloaded_file = await call.message.bot.download_file(file_path=file_info.file_path) + with open(f'voice_users/{file_name}.ogg', 'wb') as new_file: + new_file.write(downloaded_file.read()) + + await call.bot.delete_message(chat_id=GROUP_FOR_POST, message_id=call.message.message_id) + await call.answer(text='Сохранено!', cache_time=3) + + +@callback_router.callback_query( + F.data == "delete" +) +async def delete_voice_message(call: CallbackQuery): + # Получаем из базы автора + отправляем сообщение + удаляем сообщение из предложки + + await call.bot.delete_message(chat_id=GROUP_FOR_POST, message_id=call.message.message_id) + await call.answer(text='Удалено!', cache_time=3) diff --git a/voice_bot/handlers/voice_handler.py b/voice_bot/handlers/voice_handler.py index bdd385d..b571503 100644 --- a/voice_bot/handlers/voice_handler.py +++ b/voice_bot/handlers/voice_handler.py @@ -1,6 +1,5 @@ import random import time -from datetime import datetime from pathlib import Path from aiogram import Router, types, F @@ -11,9 +10,9 @@ from aiogram.types import FSInputFile from helper_bot.filters.main import ChatTypeFilter from helper_bot.middlewares.blacklist_middleware import BlacklistMiddleware from helper_bot.utils.base_dependency_factory import BaseDependencyFactory -from helper_bot.utils.helper_func import update_user_info, check_user_emoji +from helper_bot.utils.helper_func import update_user_info, check_user_emoji, send_voice_message from logs.custom_logger import logger -from voice_bot.keyboards.keyboards import get_main_keyboard +from voice_bot.keyboards.keyboards import get_main_keyboard, get_reply_keyboard_for_voice from voice_bot.utils.helper_func import last_message voice_router = Router() @@ -21,6 +20,7 @@ voice_router = Router() bdf = BaseDependencyFactory() GROUP_FOR_LOGS = bdf.settings['Telegram']['group_for_logs'] +GROUP_FOR_POST = bdf.settings['Telegram']['group_for_posts'] IMPORTANT_LOGS = bdf.settings['Telegram']['important_logs'] PREVIEW_LINK = bdf.settings['Telegram']['preview_link'] LOGS = bdf.settings['Settings']['logs'] @@ -116,6 +116,22 @@ async def start(message: types.Message, state: FSMContext): disable_web_page_preview=not PREVIEW_LINK) +@voice_router.message( + ChatTypeFilter(chat_type=["private"]), + Command("refresh") +) +async def refresh_listen_function(message: types.Message, state: FSMContext): + await message.forward(chat_id=GROUP_FOR_LOGS) + await update_user_info('voice', message) + markup = get_main_keyboard() + check_user_emoji(message.from_user.id) + BotDB.delete_listen_count_for_user(message.from_user.id) + await message.answer( + text='Прослушивания очищены. Можешь начать слушать заново🤗', disable_web_page_preview=not PREVIEW_LINK, + markup=markup) + await state.set_state('START') + + @voice_router.message( StateFilter("START"), ChatTypeFilter(chat_type=["private"]), @@ -125,47 +141,33 @@ async def standup_write(message: types.Message, state: FSMContext): await message.forward(chat_id=GROUP_FOR_LOGS) markup = types.ReplyKeyboardRemove() await message.answer(text='Хорошо, теперь пришли мне свое голосовое сообщение', reply_markup=markup) - message_with_date = last_message() - await message.answer(text=message_with_date, parse_mode="html") + try: + message_with_date = last_message() + await message.answer(text=message_with_date, parse_mode="html") + except Exception as e: + logger.error(f'Не удалось получить дату последнего сообщения - {e}') await state.set_state('STANDUP_WRITE') @voice_router.message( StateFilter("STANDUP_WRITE"), - ChatTypeFilter(chat_type=["private"]) + ChatTypeFilter(chat_type=["private"]), ) -async def save_voice_message(message: types.Message, state: FSMContext): +async def suggest_voice(message: types.Message, state: FSMContext): + logger.info( + f"Вызов функции suggest_voice. Пользователь: {message.from_user.id} Имя автора сообщения: {message.from_user.full_name}") + await message.forward(chat_id=GROUP_FOR_LOGS) markup = get_main_keyboard() if message.content_type == 'voice': - await message.forward(chat_id=GROUP_FOR_LOGS) - file_name = '' - file_id = 1 - # Проверяем что запись о файле есть в базе данных - is_having_audio_from_user = BotDB.get_last_user_audio_record(user_id=message.from_user.id) - if is_having_audio_from_user is False: - # Если нет, то генерируем имя файла - file_name = f'message_from_{message.from_user.id}_number_{file_id}' - else: - # Иначе берем последнюю запись из БД, добавляем к ней 1, и создаем новую запись - file_name = BotDB.get_path_for_audio_record(user_id=message.from_user.id) - file_id = BotDB.get_id_for_audio_record(message.from_user.id) + 1 - path = Path(f'voice_users/{file_name}.ogg') - if path.exists(): - file_name = f'message_from_{message.from_user.id}_number_{file_id}' - else: - pass - # Собираем инфо о сообщении - author_id = message.from_user.id - time_UTC = int(time.time()) - date_added = datetime.fromtimestamp(time_UTC) + markup_for_voice = get_reply_keyboard_for_voice() + # Отправляем аудио в приватный канал + sent_message = await send_voice_message(GROUP_FOR_POST, message, + message.voice.file_id, markup_for_voice) - # Сохраняем в базку - BotDB.add_audio_record(file_name, author_id, date_added, 0, file_id) + # Сохраняем в базу инфо о посте + BotDB.set_user_id_and_message_id_for_voice_bot(sent_message.message_id, message.from_user.id) - file_info = await message.bot.get_file(file_id=message.voice.file_id) - downloaded_file = await message.bot.download_file(file_path=file_info.file_path) - with open(f'voice_users/{file_name}.ogg', 'wb') as new_file: - new_file.write(downloaded_file.read()) + # Отправляем юзеру ответ и возвращаем его в меню await message.answer(text='Окей, сохранил!👌', reply_markup=markup) await state.set_state('START') else: @@ -180,23 +182,33 @@ async def save_voice_message(message: types.Message, state: FSMContext): ChatTypeFilter(chat_type=["private"]), F.text == '🎧Послушать' ) -async def standup_listen_audio(message: types.Message, state: FSMContext): +async def standup_listen_audio(message: types.Message): check_audio = BotDB.check_listen_audio(user_id=message.from_user.id) list_audio = list(check_audio) markup = get_main_keyboard() if not list_audio: await message.answer(text='Прости, ты прослушал все аудио😔. Возвращайся позже, возможно наша база пополнится', reply_markup=markup) - message_with_date = last_message() - await message.answer(text=message_with_date, parse_mode="html") + try: + message_with_date = last_message() + await message.answer(text=message_with_date, parse_mode="html") + except Exception as e: + logger.error(f'Не удалось получить последнюю дату {e}') else: + # Получаем эмоджи пользователя number_element = random.randint(0, len(list_audio) - 1) audio_for_user = check_audio[number_element] + # Получаем автора записи + эмодзи по нему + user_id = BotDB.get_user_id_by_file_name(audio_for_user) + user_emoji = BotDB.check_emoji_for_user(user_id) + path = Path(f'voice_users/{audio_for_user}.ogg') # voice = open(path, 'rb') voice = FSInputFile(path) # Маркируем сообщение как прослушанное BotDB.mark_listened_audio(audio_for_user, user_id=message.from_user.id) - await message.bot.send_voice(message.chat.id, voice=voice, reply_markup=markup) - await message.answer(text=f'Осталось непрослушанных: {len(check_audio)}', reply_markup=markup) - await state.set_state('START') + if user_emoji: + await message.bot.send_voice(chat_id=message.chat.id, voice=voice, caption=user_emoji, reply_markup=markup) + else: + await message.bot.send_voice(chat_id=message.chat.id, voice=voice, reply_markup=markup) + await message.answer(text=f'Осталось непрослушанных: {len(check_audio) - 1}', reply_markup=markup) diff --git a/voice_bot/keyboards/keyboards.py b/voice_bot/keyboards/keyboards.py index 0726fea..592d3dc 100644 --- a/voice_bot/keyboards/keyboards.py +++ b/voice_bot/keyboards/keyboards.py @@ -1,5 +1,5 @@ from aiogram import types -from aiogram.utils.keyboard import ReplyKeyboardBuilder +from aiogram.utils.keyboard import ReplyKeyboardBuilder, InlineKeyboardBuilder def get_main_keyboard(): @@ -8,3 +8,15 @@ def get_main_keyboard(): builder.add(types.KeyboardButton(text="🎧Послушать")) markup = builder.as_markup(resize_keyboard=True) return markup + + +def get_reply_keyboard_for_voice(): + builder = InlineKeyboardBuilder() + builder.row(types.InlineKeyboardButton( + text="Сохранить", callback_data="save") + ) + builder.row(types.InlineKeyboardButton( + text="Удалить", callback_data="delete") + ) + markup = builder.as_markup(resize_keyboard=True, one_time_keyboard=True) + return markup diff --git a/voice_bot/main.py b/voice_bot/main.py index c2990f2..0b181ce 100644 --- a/voice_bot/main.py +++ b/voice_bot/main.py @@ -3,6 +3,7 @@ from aiogram.client.default import DefaultBotProperties from aiogram.fsm.storage.memory import MemoryStorage from aiogram.fsm.strategy import FSMStrategy +from voice_bot.handlers.callback_handler import callback_router from voice_bot.handlers.voice_handler import voice_router @@ -13,6 +14,6 @@ async def start_bot(bdf): link_preview_is_disabled=bdf.settings['Telegram']['preview_link'] )) dp = Dispatcher(storage=MemoryStorage(), fsm_strategy=FSMStrategy.GLOBAL_USER) - dp.include_routers(voice_router) + dp.include_routers(voice_router, callback_router) await bot.delete_webhook(drop_pending_updates=True) await dp.start_polling(bot, skip_updates=True) diff --git a/voice_bot/utils/helper_func.py b/voice_bot/utils/helper_func.py index ca23b25..88bd3b9 100644 --- a/voice_bot/utils/helper_func.py +++ b/voice_bot/utils/helper_func.py @@ -11,6 +11,9 @@ BotDB = bdf.get_db() def last_message(): # функция с отображением сообщения "Последнее сообщение было записано" date_from_db = BotDB.last_date_audio() + if date_from_db is None: + return None + parse_date = datetime.strptime(date_from_db, "%Y-%m-%d %H:%M:%S") last_voice_time_timestamp = time.mktime(parse_date.timetuple()) time_now_timestamp = time.time()