diff --git a/main.py b/main.py new file mode 100644 index 0000000..ec16869 --- /dev/null +++ b/main.py @@ -0,0 +1,363 @@ +import asyncio +from asyncio import Lock +import asyncpg +from asyncpg import create_pool +from aiogram import Bot, Dispatcher, types, F +from aiogram.filters import Command +from aiogram.types import ReplyKeyboardMarkup, KeyboardButton, ReplyKeyboardRemove +from dotenv import load_dotenv +import uuid +import hashlib +import ast +import os +import logging + +logging.basicConfig(level=logging.DEBUG) # DEBUG показывает максимум информации + +# Загрузка данных из .env +load_dotenv() + +db_lock = Lock() + +db_pool2 = None + + +# Асинхронная функция для логирования состояния пула +async def log_pool_state(): + try: + active_connections = len(db_pool2._holders) # Занятые соединения + free_connections = db_pool2._queue.qsize() # Свободные соединения + logging.info( + f"Пул соединений db_pool2: Активных соединений: {active_connections}, Свободных соединений: {free_connections}" + ) + except Exception as e: + logging.error(f"Ошибка при логировании состояния пула db_pool2: {e}") + + +DATABASE_URL = os.getenv("DATABASE_URL") + + +# Асинхронная функция для создания пула подключения +async def get_db_pool2(): + try: + if not DATABASE_URL: + raise ValueError("DATABASE_URL не задана.") + + return await asyncpg.create_pool(DATABASE_URL, max_size=10) + except Exception as e: + logging.error(f"Ошибка при подключении к базе данных: {e}") + raise + + +BOT_TOKEN = os.getenv("BOT_TOKEN") +GROUP_ID = os.getenv("GROUP_ID") # Telegram ID администратора + +# Инициализация бота и диспетчера +bot = Bot(token=BOT_TOKEN) +dp = Dispatcher() + + +# Регистрация пользователя с созданием нового топика +async def register_user(telegram_id): + """ + Регистрирует пользователя, создавая анонимный ID и топик для взаимодействия. + """ + try: + logging.info(f"Регистрация пользователя с Telegram ID: {telegram_id}") + async with db_pool2.acquire() as conn: + # Проверяем, зарегистрирован ли пользователь + result = await conn.fetchrow( + "SELECT anon_id, topic_id FROM an_users WHERE telegram_id = $1", + telegram_id, + ) + if result: + logging.info(f"Пользователь {telegram_id} уже зарегистрирован.") + return result["anon_id"], result["topic_id"] + + # Генерация анонимного ID + anon_id = str(uuid.uuid4()) + + # Создание нового топика + topic_title = f"Чат {anon_id[:4]}" + topic_result = await bot.create_forum_topic( + chat_id=GROUP_ID, name=topic_title + ) + topic_id = topic_result.message_thread_id + + # Сохранение в базе данных + await conn.execute( + "INSERT INTO an_users (telegram_id, anon_id, topic_id) VALUES ($1, $2, $3)", + telegram_id, + anon_id, + topic_id, + ) + logging.info( + f"Создан новый топик с ID {topic_id} для пользователя {telegram_id}." + ) + return anon_id, topic_id + + except Exception as e: + logging.error(f"Ошибка при регистрации пользователя {telegram_id}: {e}") + return None, None + + +# Получение Telegram ID по анонимному ID +async def get_telegram_id(anon_id): + async with db_pool2.acquire() as conn: + result = await conn.fetchrow( + "SELECT telegram_id FROM an_users WHERE anon_id = $1", anon_id + ) + return result["telegram_id"] if result else None + + +@dp.message(Command("start")) +async def start_command(message: types.Message): + + anon_id, topic_id = await register_user(message.from_user.id) + + # Создаем кнопки + keyboard = ReplyKeyboardMarkup( + keyboard=[ + [KeyboardButton(text="Написать волонтер_ке")], + [KeyboardButton(text="Заполнить форму")], + ], + resize_keyboard=True, + ) + + await message.answer( + "Привет 👋! Выбери ниже то, что тебе нужно. ✨\n Если мы не ответили тебе втечение 24 ч, то напиши нам на почту: rloveplus@proton.me или в Matrix Element: @br:bark.lgbt \n А так же желаем хорошего тебе дня 💖", + reply_markup=keyboard, + ) + + +# Хэндлер для кнопки "Заполнить форму" +@dp.message(F.text == "Заполнить форму") +async def fill_form(message: types.Message): + form_url = "https://t.me/InstantFormsBot/form?startapp=9dd0908d-3c39-45cb-afe1-7f4004aa8fc6&startApp=9dd0908d-3c39-45cb-afe1-7f4004aa8fc6" # Ссылка на вашу форму + await message.answer(f"[Заполнить форму]({form_url})", parse_mode="Markdown") + + +@dp.message(F.text == "Написать волонтер_ке") +async def contact_volunteer(message: types.Message): + try: + + # Получаем номер строки в базе + async with db_pool2.acquire() as conn: + result = await conn.fetchrow( + "SELECT id FROM an_users WHERE telegram_id = $1", message.from_user.id + ) + + if result: + user_number = result["id"] + topic_name = f"{user_number}" + + await message.answer( + f"Добро пожаловать! Здесь ты можешь написать нам. Ты будешь общаться с администраторами через бота, поэтому ты останешься для них анонимными.", + reply_markup=ReplyKeyboardRemove(), + ) + except Exception as e: + logging.error(f"Ошибка при создании топика: {e}") + + +# Обработчик сообщений от пользователя +@dp.message(F.chat.type == "private") +async def handle_user_message(message: types.Message): + logging.info( + f"Сообщение от {message.from_user.id}: {message.text or 'мультимедиа'}" + ) + + # Регистрируем пользователя и получаем данные + anon_id, topic_id = await register_user(message.from_user.id) + + try: + # Формируем идентификатор для анонимности + user_tag = f"Сообщение от {str(anon_id)[:4]}:" + + # Отправляем разные типы сообщений + if message.text: + forward_message = f"{user_tag}\n{message.text}" + await bot.send_message( + chat_id=GROUP_ID, message_thread_id=topic_id, text=forward_message + ) + elif message.photo: + await bot.send_photo( + chat_id=GROUP_ID, + message_thread_id=topic_id, + photo=message.photo[-1].file_id, + caption=f"{user_tag}\n{message.caption or ''}", + ) + elif message.video: + await bot.send_video( + chat_id=GROUP_ID, + message_thread_id=topic_id, + video=message.video.file_id, + caption=f"{user_tag}\n{message.caption or ''}", + ) + elif message.document: + await bot.send_document( + chat_id=GROUP_ID, + message_thread_id=topic_id, + document=message.document.file_id, + caption=f"{user_tag}\n{message.caption or ''}", + ) + elif message.audio: + await bot.send_audio( + chat_id=GROUP_ID, + message_thread_id=topic_id, + audio=message.audio.file_id, + caption=f"{user_tag}\n{message.caption or ''}", + ) + elif message.voice: + await bot.send_voice( + chat_id=GROUP_ID, + message_thread_id=topic_id, + voice=message.voice.file_id, + caption=user_tag, + ) + else: + await bot.send_message( + chat_id=GROUP_ID, + message_thread_id=topic_id, + text=f"{user_tag}\nТип сообщения пока не поддерживается.", + ) + + # Уведомляем пользователя + await message.answer("Сообщение отправлено!") + logging.info(f"Сообщение от {message.from_user.id} успешно переслано в топик.") + + except Exception as e: + logging.error(f"Ошибка при обработке сообщения от пользователя: {e}") + await message.answer("Произошла ошибка при отправке сообщения.") + + +# Обработка новых сообщений администратора +@dp.message(F.chat.type.in_(["group", "supergroup"]) & ~F.text.startswith("/")) +async def handle_admin_reply(message: types.Message): + """ + Обрабатывает только текстовые сообщения от администратора в топиках группы, + игнорируя команды. + """ + if not any( + [ + message.text, + message.photo, + message.video, + message.document, + message.audio, + message.voice, + ] + ): + logging.info(f"Игнорирование пустого сообщения или команды: {message.text}") + return + + # Обрабатываем сообщение + await process_admin_message(message) + + +# Обработка редактирования сообщений администратора +@dp.edited_message(F.chat.type.in_(["group", "supergroup"])) +async def handle_admin_edited_message(message: types.Message): + """ + Обрабатывает редактированные сообщения от администратора в топиках группы. + """ + topic_id = message.message_thread_id + + # Проверяем, существует ли topic_id в базе + async with db_pool2.acquire() as conn: + result = await conn.fetchrow( + "SELECT telegram_id FROM an_users WHERE topic_id = $1", topic_id + ) + + if not result: + logging.warning( + f"Редактирование сообщения в несуществующем топике: {topic_id}. Игнорируем." + ) + return # Игнорируем редактирование, если пользователь не зарегистрирован + + # Если пользователь найден, продолжаем обработку + await process_admin_message(message) + + +# Общая функция обработки сообщений +async def process_admin_message(message: types.Message): + """ + Обрабатывает как новые, так и редактированные сообщения от администратора. + """ + logging.info( + f"Обработка сообщения от администратора: {message.text}, чат: {message.chat.id}" + ) + # Получаем topic_id из текущего чата + topic_id = message.message_thread_id + + # Находим telegram_id пользователя, связанного с этим topic_id + async with db_pool2.acquire() as conn: + result = await conn.fetchrow( + "SELECT telegram_id FROM an_users WHERE topic_id = $1", topic_id + ) + + try: + telegram_id = result["telegram_id"] + + # Пересылаем сообщение пользователю в зависимости от типа контента + if message.photo: + await bot.send_photo( + chat_id=telegram_id, + photo=message.photo[-1].file_id, + caption=message.caption, + ) + elif message.video: + await bot.send_video( + chat_id=telegram_id, + video=message.video.file_id, + caption=message.caption, + ) + elif message.document: + await bot.send_document( + chat_id=telegram_id, + document=message.document.file_id, + caption=message.caption, + ) + elif message.audio: + await bot.send_audio( + chat_id=telegram_id, + audio=message.audio.file_id, + caption=message.caption, + ) + elif message.voice: + await bot.send_voice( + chat_id=telegram_id, + voice=message.voice.file_id, + caption=message.caption, + ) + elif message.text: + await bot.send_message( + chat_id=telegram_id, text=f"Ответ волонтёр_ки:\n\n{message.text}" + ) + + logging.info(f"Ответ успешно отправлен пользователю с ID {telegram_id}") + + except Exception as e: + logging.error(f"Ошибка при обработке ответа администратора: {e}") + await message.reply("Произошла ошибка при отправке ответа пользователю.") + + +# Инициализация пула и запуск бота +async def main(): + global db_pool2 + try: + db_pool2 = await get_db_pool2() # Создание пула + logging.info("Пул db_pool2 успешно создан") + + # Тестовый запрос + async with db_pool2.acquire() as conn: + await conn.execute("SELECT 1") + logging.info("Подключение к базе данных успешно установлено") + + await log_pool_state() # Логирование состояния пула + await dp.start_polling(bot) + except Exception as e: + logging.error(f"Ошибка при подключении к базе данных: {e}") + + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file