diff --git a/.gitignore b/.gitignore index f96f1b6..4ab118b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ TBot/ -logs/* \ No newline at end of file +logs/* +.code-workspace \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index e4aa934..6d17d5c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,18 @@ FROM python:3.12-slim # Устанавливаем рабочую директорию WORKDIR /app +# Устанавливаем необходимые пакеты и локаль +RUN apt-get update && apt-get install -y --no-install-recommends \ + locales && \ + echo "ru_RU.UTF-8 UTF-8" > /etc/locale.gen && \ + locale-gen && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +# Устанавливаем переменные окружения для локали +ENV LANG ru_RU.UTF-8 +ENV LANGUAGE ru_RU:ru +ENV LC_ALL ru_RU.UTF-8 + # Копируем файлы проекта COPY . . diff --git a/handlers/handlers.py b/handlers/handlers.py index 8c887db..8f025c5 100644 --- a/handlers/handlers.py +++ b/handlers/handlers.py @@ -1,17 +1,44 @@ from aiogram import types, Dispatcher from aiogram.filters import Command import aiohttp +import logging +from datetime import datetime from instences.config import BASE_URL_FASTAPI +import locale +locale.setlocale(locale.LC_TIME, "ru_RU.UTF-8") from keyboard.keyboards import subhist_keyboard,confirm_popup_keyboard,tarif_confirm_keyboard, popup_keyboard, main_keyboard,faq_keyboard, account_keyboard, buy_keyboard,balance_keyboard,guide_keyboard,tarif_Lark_keyboard,tarif_Lark_pro_keyboard,tranhist_keyboard +logger = logging.getLogger(__name__) # Инициализируем менеджер базы данных + async def call_api(method, endpoint, data=None): - async with aiohttp.ClientSession() as session: - url = f"{BASE_URL_FASTAPI}{endpoint}" - async with session.request(method, url, json=data) as response: - if response.status in {200, 201}: - return await response.json() - return "ERROR" + """ + Выполняет HTTP-запрос к FastAPI. + + :param method: HTTP метод (GET, POST, и т.д.) + :param endpoint: конечная точка API + :param data: тело запроса (если необходимо) + :return: JSON-ответ или "ERROR" при неуспехе + """ + url = f"{BASE_URL_FASTAPI}{endpoint}" + logger.info(f"Инициализация запроса: {method} {url} с данными {data}") + + try: + async with aiohttp.ClientSession() as session: + async with session.request(method, url, json=data) as response: + logger.info(f"Получен ответ от {url}: статус {response.status}") + + if response.status in {200, 201}: + result = await response.json() + logger.debug(f"Ответ JSON: {result}") + return result + + logger.error(f"Ошибка в запросе: статус {response.status}, причина {response.reason}") + return "ERROR" + except Exception as e: + logger.exception(f"Исключение при выполнении запроса к {url}: {e}") + return "ERROR" + async def popup_command(message: types.Message): """ @@ -53,15 +80,62 @@ async def start_callback_handler(callback: types.CallbackQuery): ) async def profile_callback_handler(callback: types.CallbackQuery): - user_data = await call_api("POST", "/user/create", {"telegram_id": callback.from_user.id}) - if not user_data: - await callback.message.answer("Произошла ошибка, попробуйте позже.") - await callback.answer() - return + try: + # Создание пользователя + user_data = await call_api("POST", "/user/create", {"telegram_id": callback.from_user.id}) + if user_data == "ERROR" or not isinstance(user_data, dict): + raise ValueError("Не удалось создать пользователя.") + + # Получение подписки + sub_data = await call_api("GET", f"/subscription/{user_data['id']}/last") + if sub_data == "ERROR" or not isinstance(sub_data, dict): + sub_data = None + + # Формирование текста баланса + balance_text = f"Баланс: {user_data['balance']} ₽" + + # Если подписки нет + if not sub_data: + text = f"Профиль {callback.from_user.username}:\n{balance_text}\nПополните баланс и приобретите подписку чтобы получить активный статус (🐣,🦅)" + else: + # Получение даты окончания подписки + expiry_date = sub_data.get("expiry_date") + formatted_date = None + + # Преобразование времени в презентабельный формат + if expiry_date: + expiry_datetime = datetime.fromisoformat(expiry_date) + formatted_date = expiry_datetime.strftime("%d %B %Y г.") + + # Проверка на истечение подписки + is_expired = datetime.fromisoformat(expiry_date) < datetime.now() if expiry_date else True + plan_type = sub_data.get("plan", "") + status_icon = "✖️" if is_expired else "☑️" + + # Определение статуса + if "Pro" in plan_type: + profile_status = "🦅" + else: + profile_status = "🐣" + + # Формирование текста профиля + sub_text = ( + f"Ваша подписка действует до {formatted_date} {status_icon}" + if not is_expired else f"Статус подписки: {status_icon}" + ) + text = f"Профиль {callback.from_user.username} {profile_status}:\n{sub_text}\n{balance_text}" + + # Отправка текста пользователю + await callback.message.edit_text(text, reply_markup=account_keyboard()) + except ValueError as e: + await callback.message.answer(f"Ошибка: {e}") + except Exception as e: + logger.exception(f"Ошибка в обработчике профиля: {e}") + await callback.message.answer("Произошла ошибка. Попробуйте позже.") + finally: + await callback.answer() + - text = f"Ваш профиль:\nID: {user_data['username']}\nБаланс: {user_data['balance']} ₽" - await callback.message.edit_text(text, reply_markup=account_keyboard()) - await callback.answer() async def balance_callback_handler(callback: types.CallbackQuery): user_data = await call_api("GET", f"/user/{callback.from_user.id}") @@ -219,7 +293,7 @@ async def popup_confirm_callback_handler(callback: types.CallbackQuery): """ data = callback.data.split(":") popup_info = data[1] - result = await call_api("POST", f"/user/{callback.from_user.id}/balance", {"telegram_id": callback.from_user.id, "amount": popup_info}) + result = await call_api("POST", f"/user/{callback.from_user.id}/balance/{float(popup_info)}") if result == "ERROR": await callback.message.answer( "Произошла ошибка, попробуйте позже или свяжитесь с администрацией." @@ -234,11 +308,19 @@ async def confirm_callback_handler(callback: types.CallbackQuery): tariff_info = data.split("_") plan_id = f"{tariff_info[0]}_{tariff_info[1]}_{tariff_info[2]}" result = await call_api("POST", "/subscription/buy", {"telegram_id": callback.from_user.id, "plan_id": plan_id}) + error_detail = result.get("detail",{}) + error_code = error_detail.get("code", "") - if result and result.get("message"): - await callback.message.edit_text(f"Подписка успешно оформлена!") - else: + if error_code == "ERROR": await callback.message.edit_text("Произошла ошибка при оформлении подписки.") + if error_code == "INSUFFICIENT_FUNDS": + await callback.message.edit_text("Денег на вашем балансе не достаточно.") + if error_code == "TARIFF_NOT_FOUND": + await callback.message.edit_text("Ваш тариф не найден.") + if error_code == "ACTIVE_SUBSCRIPTION_EXISTS": + await callback.message.edit_text("Вы уже имеете активную подписку.") + else: + await callback.message.edit_text(f"Подписка успешно оформлена!") await callback.answer() def register_handlers(dp: Dispatcher): diff --git a/keyboard/keyboards.py b/keyboard/keyboards.py index b294012..3572e49 100644 --- a/keyboard/keyboards.py +++ b/keyboard/keyboards.py @@ -1,123 +1,141 @@ -from aiogram import types, Dispatcher -from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, CallbackQuery -from aiogram.filters import Command +from aiogram.utils.keyboard import InlineKeyboardBuilder +from aiogram.types import InlineKeyboardButton -# Главное меню клавиатура -def main_menu_keyboard(): - keyboard = InlineKeyboardMarkup() - keyboard.add(InlineKeyboardButton("Личный кабинет", callback_data="main:personal")) - keyboard.add(InlineKeyboardButton("FAQ", callback_data="main:faq")) - keyboard.add(InlineKeyboardButton("О нас", callback_data="main:about")) - return keyboard -# Личный кабинет клавиатура -def personal_menu_keyboard(): - keyboard = InlineKeyboardMarkup() - keyboard.add(InlineKeyboardButton("Баланс", callback_data="personal:balance")) - keyboard.add(InlineKeyboardButton("Приобрести подписку", callback_data="personal:subscribe")) - keyboard.add(InlineKeyboardButton("Руководство по использованию", callback_data="personal:guide")) - keyboard.add(InlineKeyboardButton("Назад", callback_data="back:main")) - return keyboard +def main_keyboard(): + """ + База + """ + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text="Профиль", callback_data="profile")) + builder.row(InlineKeyboardButton(text="FAQ", callback_data="faq")) + builder.row(InlineKeyboardButton(text="О нас", url="https://www.youtube.com/watch?v=Zirn-CKck-c")) + return builder.as_markup() -# Подписка меню клавиатура -def subscribe_menu_keyboard(): - keyboard = InlineKeyboardMarkup() - keyboard.add(InlineKeyboardButton("Lark", callback_data="subscribe:lark")) - keyboard.add(InlineKeyboardButton("Lark Pro", callback_data="subscribe:lark_pro")) - keyboard.add(InlineKeyboardButton("О тарифах", callback_data="subscribe:about_tariffs")) - keyboard.add(InlineKeyboardButton("Назад", callback_data="back:personal")) - return keyboard +def account_keyboard(): + """ + Аккаунт + """ + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text="Пополнение баланса", callback_data="popup")) + builder.row(InlineKeyboardButton(text="Приобрести подписку", callback_data="buy_subscription")) + builder.row(InlineKeyboardButton(text="Руководство по подключению", callback_data="guide")) + builder.row(InlineKeyboardButton(text="История транзакций", callback_data="tranhist")) + builder.row(InlineKeyboardButton(text="Назад", callback_data="base")) + return builder.as_markup() -# Тарифы Lark клавиатура -def lark_tariffs_keyboard(): - keyboard = InlineKeyboardMarkup() - keyboard.add(InlineKeyboardButton("Lark 1 месяц", callback_data="lark:1")) - keyboard.add(InlineKeyboardButton("Lark 6 месяцев", callback_data="lark:6")) - keyboard.add(InlineKeyboardButton("Lark 12 месяцев", callback_data="lark:12")) - keyboard.add(InlineKeyboardButton("Назад", callback_data="back:subscribe")) - return keyboard -# Тарифы Lark Pro клавиатура -def lark_pro_tariffs_keyboard(): - keyboard = InlineKeyboardMarkup() - keyboard.add(InlineKeyboardButton("Lark Pro 1 месяц", callback_data="lark_pro:1")) - keyboard.add(InlineKeyboardButton("Lark Pro 6 месяцев", callback_data="lark_pro:6")) - keyboard.add(InlineKeyboardButton("Lark Pro 12 месяцев", callback_data="lark_pro:12")) - keyboard.add(InlineKeyboardButton("Назад", callback_data="back:subscribe")) - return keyboard +def buy_keyboard(): + """ + Приобрести подписку + """ + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text="Тариф Lark", callback_data="subs")) + builder.row(InlineKeyboardButton(text="Тариф Lark Pro", callback_data="subs_pro")) + builder.row(InlineKeyboardButton(text="О тарифах", url="https://t.me/proxylark/19")) + builder.row(InlineKeyboardButton(text="Назад", callback_data="profile")) + return builder.as_markup() -# Руководство меню клавиатура -def guide_menu_keyboard(): - keyboard = InlineKeyboardMarkup() - keyboard.add(InlineKeyboardButton("iOS, Android", callback_data="guide:ios_android")) - keyboard.add(InlineKeyboardButton("Windows, Macintosh", callback_data="guide:windows_mac")) - keyboard.add(InlineKeyboardButton("Назад", callback_data="back:personal")) - return keyboard +def subhist_keyboard(): + """ + Подписки + """ + builder = InlineKeyboardBuilder() + builder.button(text="Назад", callback_data="profile") + return builder.as_markup() -async def start_command(message: types.Message): - """Обработчик команды /start.""" - await message.answer("Главное меню", reply_markup=main_menu_keyboard()) +def popup_keyboard(): + """ + Пополнение + """ + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text="200₽", callback_data="popup:200"),InlineKeyboardButton(text="500₽", callback_data="popup:500")) + builder.row(InlineKeyboardButton(text="1000₽", callback_data="popup:1000"),InlineKeyboardButton(text="2000₽", callback_data="popup:2000")) + builder.row(InlineKeyboardButton(text="3000₽", callback_data="popup:3000"),InlineKeyboardButton(text="5000₽", callback_data="popup:5000")) + builder.row(InlineKeyboardButton(text="Назад", callback_data="profile")) + return builder.as_markup() -async def callback_handler(callback: CallbackQuery): - """Обработчик callback.""" - data = callback.data.split(":") - action = data[0] - sub_action = data[1] if len(data) > 1 else None +def balance_keyboard(): + """ + Баланс + """ + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text="Пополнение", callback_data="popup")) + builder.row(InlineKeyboardButton(text="История транзакций", callback_data="tranhist")) + builder.row(InlineKeyboardButton(text="Назад", callback_data="profile")) + return builder.as_markup() - if action == "main": - if sub_action == "personal": - await callback.message.edit_text("Личный кабинет", reply_markup=personal_menu_keyboard()) - elif sub_action == "faq": - await callback.message.edit_text("FAQ", reply_markup=InlineKeyboardMarkup().add( - InlineKeyboardButton("Назад", callback_data="back:main"))) - elif sub_action == "about": - await callback.message.edit_text("Наш сайт: {URL}", reply_markup=InlineKeyboardMarkup().add( - InlineKeyboardButton("Назад", callback_data="back:main"))) +def tarif_Lark_keyboard(): + """ + Тариф Lark + """ + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text="Тариф Lark 1 Месяц", callback_data="Lark:Standart:1")) + builder.row(InlineKeyboardButton(text="Тариф Lark 6 Месяц", callback_data="Lark:Standart:6")) + builder.row(InlineKeyboardButton(text="Тариф Lark 12 Месяц", callback_data="Lark:Standart:12")) + builder.row(InlineKeyboardButton(text="Назад", callback_data="buy_subscription")) + return builder.as_markup() - elif action == "personal": - if sub_action == "balance": - await callback.message.edit_text("Баланс:", reply_markup=InlineKeyboardMarkup().add( - InlineKeyboardButton("Пополнение", callback_data="balance:topup"), - InlineKeyboardButton("История транзакций", callback_data="balance:history"), - InlineKeyboardButton("Назад", callback_data="back:personal") - )) - elif sub_action == "subscribe": - await callback.message.edit_text("Выберите подписку:", reply_markup=subscribe_menu_keyboard()) - elif sub_action == "guide": - await callback.message.edit_text("Руководство по использованию", reply_markup=guide_menu_keyboard()) +def tarif_Lark_pro_keyboard(): + """ + Тариф Lark Pro + """ + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text="Тариф Lark Pro 1 Месяц", callback_data="Lark:Pro:1")) + builder.row(InlineKeyboardButton(text="Тариф Lark Pro 6 Месяц", callback_data="Lark:Pro:6")) + builder.row(InlineKeyboardButton(text="Тариф Lark Pro 12 Месяц", callback_data="Lark:Pro:12")) + builder.row(InlineKeyboardButton(text="Назад", callback_data="buy_subscription")) + return builder.as_markup() - elif action == "subscribe": - if sub_action == "lark": - await callback.message.edit_text("Тарифы Lark", reply_markup=lark_tariffs_keyboard()) - elif sub_action == "lark_pro": - await callback.message.edit_text("Тарифы Lark Pro", reply_markup=lark_pro_tariffs_keyboard()) - elif sub_action == "about_tariffs": - await callback.message.edit_text("О тарифах", reply_markup=InlineKeyboardMarkup().add( - InlineKeyboardButton("Назад", callback_data="back:subscribe") - )) +def guide_keyboard(): + """ + Руководство по подключению + """ + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text="IOS,Android", callback_data="mob")) + builder.row(InlineKeyboardButton(text="Windows,MacOS", callback_data="pc")) + builder.row(InlineKeyboardButton(text="Назад", callback_data="profile")) + return builder.as_markup() - elif action == "lark": - await callback.message.edit_text(f"Вы выбрали тариф Lark {sub_action} месяцев.", reply_markup=InlineKeyboardMarkup().add( - InlineKeyboardButton("Подтвердить", callback_data=f"confirm:lark:{sub_action}"), - InlineKeyboardButton("Отменить", callback_data="back:subscribe") - )) - elif action == "lark_pro": - await callback.message.edit_text(f"Вы выбрали тариф Lark Pro {sub_action} месяцев.", reply_markup=InlineKeyboardMarkup().add( - InlineKeyboardButton("Подтвердить", callback_data=f"confirm:lark_pro:{sub_action}"), - InlineKeyboardButton("Отменить", callback_data="back:subscribe") - )) +# def about_tarifs_keyboard(): +# """ +# О тарифах +# """ +# builder = InlineKeyboardBuilder() +# builder.row(InlineKeyboardButton(text="Назад", callback_data="buy_subscription")) +# return builder.as_markup() - elif action == "back": - if sub_action == "main": - await callback.message.edit_text("Главное меню", reply_markup=main_menu_keyboard()) - elif sub_action == "personal": - await callback.message.edit_text("Личный кабинет", reply_markup=personal_menu_keyboard()) - elif sub_action == "subscribe": - await callback.message.edit_text("Выберите подписку:", reply_markup=subscribe_menu_keyboard()) - await callback.answer() +def faq_keyboard(): + """ + FAQ + """ + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text="Назад", callback_data="base")) + return builder.as_markup() -def register_handlers(dp: Dispatcher): - dp.message.register(start_command, Command("start")) - dp.callback_query.register(callback_handler) +def tranhist_keyboard(): + """ + История транзакций + """ + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text="Назад",callback_data="profile")) + return builder.as_markup() + +def tarif_confirm_keyboard(name,amount,classif): + """ + Подтверждение покупки тарифа + """ + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text="Подтвердить", callback_data=f"confirm:{name}_{classif}_{amount}")) + builder.row(InlineKeyboardButton(text="Отменить",callback_data="buy_subscription")) + return builder.as_markup() + +def confirm_popup_keyboard(): + """ + Подтверждение пополнения + """ + builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text="Теперь иди нахуй", callback_data="profile")) + return builder.as_markup()