From a9a137b766d31f15f87348bef55fb7618041fe7b Mon Sep 17 00:00:00 2001 From: Disledg Date: Tue, 7 Jan 2025 08:28:59 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9C=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=BF=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 +- Lark_VPN_Bot.code-workspace | 3 + .../anti_spam_middleware.cpython-312.pyc | Bin 1538 -> 0 bytes handlers/__init__.py | 7 + handlers/__pycache__/handlers.cpython-312.pyc | Bin 5979 -> 0 bytes handlers/handlers.py | 417 ------------------ handlers/profile.py | 170 +++++++ handlers/start.py | 95 ++++ handlers/subscriptions.py | 138 ++++++ handlers/support.py | 160 +++++++ .../__pycache__/db_config.cpython-312.pyc | Bin 3012 -> 0 bytes .../__pycache__/postgresql.cpython-312.pyc | Bin 7978 -> 0 bytes instences/config.py | 7 +- instences/model.py | 60 --- .../__pycache__/keyboards.cpython-312.pyc | Bin 2532 -> 0 bytes keyboard/keyboards.py | 16 +- main.py | 7 +- temp_scripts/ca.pem | 37 -- temp_scripts/insert_server_in_mongodb.py | 70 --- temp_scripts/insert_subscriptions.py | 74 ---- temp_scripts/ser.json | 68 --- temp_scripts/subs/sub1.json | 8 - temp_scripts/subs/sub2.json | 8 - temp_scripts/subs/sub3.json | 8 - temp_scripts/subs/sub4.json | 8 - temp_scripts/subs/sub5.json | 8 - temp_scripts/subs/sub6.json | 8 - utils/LogCon.py | 4 +- utils/__pycache__/LogCon.cpython-312.pyc | Bin 1623 -> 0 bytes 29 files changed, 604 insertions(+), 782 deletions(-) delete mode 100644 Middleware/__pycache__/anti_spam_middleware.cpython-312.pyc create mode 100644 handlers/__init__.py delete mode 100644 handlers/__pycache__/handlers.cpython-312.pyc delete mode 100644 handlers/handlers.py create mode 100644 handlers/profile.py create mode 100644 handlers/start.py create mode 100644 handlers/subscriptions.py create mode 100644 handlers/support.py delete mode 100644 instences/__pycache__/db_config.cpython-312.pyc delete mode 100644 instences/__pycache__/postgresql.cpython-312.pyc delete mode 100644 instences/model.py delete mode 100644 keyboard/__pycache__/keyboards.cpython-312.pyc delete mode 100644 temp_scripts/ca.pem delete mode 100644 temp_scripts/insert_server_in_mongodb.py delete mode 100644 temp_scripts/insert_subscriptions.py delete mode 100644 temp_scripts/ser.json delete mode 100644 temp_scripts/subs/sub1.json delete mode 100644 temp_scripts/subs/sub2.json delete mode 100644 temp_scripts/subs/sub3.json delete mode 100644 temp_scripts/subs/sub4.json delete mode 100644 temp_scripts/subs/sub5.json delete mode 100644 temp_scripts/subs/sub6.json delete mode 100644 utils/__pycache__/LogCon.cpython-312.pyc diff --git a/.gitignore b/.gitignore index 4ab118b..1fb7ad4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ TBot/ logs/* -.code-workspace \ No newline at end of file +*.code-workspace +__pycache__/ +handlers.py +*.pyc \ No newline at end of file diff --git a/Lark_VPN_Bot.code-workspace b/Lark_VPN_Bot.code-workspace index 15bbdbb..baa3550 100644 --- a/Lark_VPN_Bot.code-workspace +++ b/Lark_VPN_Bot.code-workspace @@ -5,6 +5,9 @@ }, { "path": "../Bot" + }, + { + "path": "../bot/Lark_VPN_Bot" } ], "settings": {} diff --git a/Middleware/__pycache__/anti_spam_middleware.cpython-312.pyc b/Middleware/__pycache__/anti_spam_middleware.cpython-312.pyc deleted file mode 100644 index 07eca318d643f2874e1d12a2374912ad74f908a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1538 zcmZ`3O>Y}T^v!aTHfAt0AvCT3J(I0xWF>ES-^U zHPS8XORAB0AU6gHx{IdA-m@*sVT&eb@??rRY?hnu`_olc5tEfjEyCze2z$qne2E?~ zC=(0HilsMThFD~!qpVsA(=3(gGaA0Fl@ks4l$4VtFcLxbt(ve)^QPN=vZAzXN+Nxf zYi|tEw`2_j(g;=+A1-73+pRqTpZGxcwQay%D@K4N3~tiU0^M7fXFNzzYS(N*X^`fo zVAQc)TLk)o$GB&xf#xx1hRd`E9h6oa)AK0xq_N@Q3s0sBb9I*$&N`-rYP>LBuPnH% zCcHwiF6=Xfsfps~M;}a%q7jGj(doJl38H|FHB|xdCzTj&oQB;u~ zr1)wa|3#dk&#>s!PD8iZ%?)RR`<470^$yM;(H-Vmb4EAU8B^?}`VuYP3f<6S`VjG eAfjEB5ON>J?!(Bh(D^WtBS+R>cmzm7-G2ZEWMh~B diff --git a/handlers/__init__.py b/handlers/__init__.py new file mode 100644 index 0000000..697552f --- /dev/null +++ b/handlers/__init__.py @@ -0,0 +1,7 @@ +from .start import router as start_router +from .profile import router as profile_router +from .subscriptions import router as subscriptions_router +from .support import router as support_router + +# Экспортируем все маршрутизаторы +routers = [start_router,profile_router,subscriptions_router,support_router] diff --git a/handlers/__pycache__/handlers.cpython-312.pyc b/handlers/__pycache__/handlers.cpython-312.pyc deleted file mode 100644 index 376f7799ed655888243833cecaa3512c42822d1f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5979 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 diff --git a/handlers/handlers.py b/handlers/handlers.py deleted file mode 100644 index 74f7681..0000000 --- a/handlers/handlers.py +++ /dev/null @@ -1,417 +0,0 @@ -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,ticket_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): - """ - Выполняет 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" - - -user_state = {} - -async def start_ticket_creation(message: types.Message): - """ - Начинает процесс создания тикета. - """ - await message.answer( - "Введите тему тикета (или нажмите 'Отмена', чтобы выйти):", - reply_markup=ticket_keyboard() - ) - user_state[message.from_user.id] = {"step": "subject"} - -async def handle_ticket_input(message: types.Message): - """ - Обрабатывает ввод данных для тикета. - """ - user_id = message.from_user.id - text = message.text.lower() - - if text == "отмена": - await message.answer("Создание тикета отменено.", reply_markup=types.ReplyKeyboardRemove()) - user_state.pop(user_id, None) - return - - state = user_state.get(user_id) - if not state: - await message.answer("Нет активного процесса создания тикета. Введите /support для начала.") - return - - if state["step"] == "subject": - state["subject"] = message.text - state["step"] = "message" - await message.answer("Введите описание проблемы:") - elif state["step"] == "message": - state["message"] = message.text - await create_ticket(message, state) - user_state.pop(user_id, None) - -async def create_ticket(message: types.Message, state): - """ - Отправляет запрос на создание тикета через FastAPI. - """ - user_id = message.from_user.id - subject = state["subject"] - ticket_message = state["message"] - - try: - logger.info(f"Создание тикета для пользователя {user_id}: Тема - {subject}, Сообщение - {ticket_message}") - user_data = await call_api("GET", f"/user/{message.from_user.id}") - if not user_data: - await message.edit_text("Вы еще не зарегистрированы.") - await message.answer() - return - # Отправляем запрос через call_api - ticket_data = await call_api( - "POST", - f"/support/tickets?user_id={user_data['id']}", # Передаём user_id как query-параметр - data={"subject": subject, "message": ticket_message} - ) - - - if ticket_data != "ERROR": - await message.answer( - f"✅ Тикет успешно создан!\n" - f"📌 Тема: {ticket_data['subject']}\n" - f"📊 Статус: {ticket_data['status']}\n" - f"📅 Дата создания: {ticket_data['created_at']}" - ) - else: - await message.answer("❌ Ошибка создания тикета. Попробуйте позже.") - except Exception as e: - logger.exception(f"Ошибка при создании тикета для пользователя {user_id}: {e}") - await message.answer("❌ Произошла ошибка при создании тикета.") - - - - -async def subhist_command(message: types.Message): - """ - Обработчик команды для отправки истории подписок. - """ - user_data = await call_api("GET", f"/user/{message.from_user.id}") - if not user_data: - await message.edit_text("Вы еще не зарегистрированы.") - await message.answer() - return - - subscriptions = await call_api("GET", f"/subscriptions/{user_data['id']}") - if not subscriptions: - await message.edit_text("У вас нет активных подписок.", reply_markup=account_keyboard()) - await message.answer() - return - - result = "Ваши подписки:\n" - for count, sub in enumerate(subscriptions, start=1): - result += f"{count}. Тариф: {sub['plan']}, Истекает: {sub['expiry_date']}\n" - await message.edit_text(result, reply_markup=account_keyboard()) - await message.answer() - - await message.answer("subhist", reply_markup=subhist_keyboard()) - -async def start_command(message: types.Message): - """ - Обработчик команды /start. - """ - await message.answer(f"""Приветствуем в рядах птенец {message.from_user.username}🐣 - \nСкорее пополни баланс, приобрети подписку и получай доступ к TikTok, YouTube, Discord, Instagram на всех устройствах без ограничений ❕❕❕ - \nОзнакомься с руководством по пользованию и выбери подходящий тариф 🦅 - \nСледи за акциями, спец-предложениями, а также розыгрышами по ссылке ниже - \n👇👇👇 - \nhttps://t.me/+0z5xqn3F1m02OTJi - \nС любовью ваши пернатые разработчики 🤍🤍🤍""", reply_markup=main_keyboard()) - -async def start_callback_handler(callback: types.CallbackQuery): - """ - Обработчик callback_query с data="base". - """ - await callback.message.edit_text( - f"""Приветствуем в рядах птенец {callback.from_user.username}🐣 - \nСкорее пополни баланс, приобрети подписку и получай доступ к TikTok, YouTube, Discord, Instagram на всех устройствах без ограничений ❕❕❕ - \nОзнакомься с руководством по пользованию и выбери подходящий тариф 🦅 - \nСледи за акциями, спец-предложениями, а также розыгрышами по ссылке ниже - \n👇👇👇 - \nhttps://t.me/+0z5xqn3F1m02OTJi - \nС любовью ваши пернатые разработчики 🤍🤍🤍""", - reply_markup=main_keyboard() - ) - -async def profile_callback_handler(callback: types.CallbackQuery): - 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() - - - -async def balance_callback_handler(callback: types.CallbackQuery): - user_data = await call_api("GET", f"/user/{callback.from_user.id}") - if not user_data: - await callback.message.answer("Вы еще не зарегистрированы.") - await callback.answer() - return - - await callback.message.edit_text( - f"Ваш баланс: {user_data['balance']} ₽. Выберите сумму для пополнения 🐥", - reply_markup=balance_keyboard() - ) - await callback.answer() - - -async def popup_callback_handler(callback: types.CallbackQuery): - """ - Обработчик callback_query с data="popup". - """ - user = await call_api("POST", "/user/create", {"telegram_id": callback.from_user.id}) - if user == "ERROR": - await callback.message.answer( - "Произошла ошибка, попробуйте позже или свяжитесь с администрацией." - ) - await callback.answer() - return - - if user: - await callback.message.edit_text( - f"Работает в режиме теста!!!", - reply_markup=popup_keyboard() - ) - else: - await callback.message.edit_text("Вы еще не зарегистрированы.") - await callback.answer() - -async def tranhist_callback_handler(callback: types.CallbackQuery): - user_data = await call_api("GET", f"/user/{callback.from_user.id}") - if not user_data: - await callback.message.edit_text("Вы еще не зарегистрированы.") - await callback.answer() - return - - transactions = await call_api("GET", f"/user/{user_data['id']}/transactions") - if not transactions: - await callback.message.edit_text("У вас нет транзакций.", reply_markup=tranhist_keyboard()) - await callback.answer() - return - - result = "Ваши транзакции:\n" - for count, tran in enumerate(transactions, start=1): - result += f"{count}. Сумма: {tran['amount']}, Дата: {tran['created_at']}\n" - await callback.message.edit_text(result, reply_markup=tranhist_keyboard()) - await callback.answer() - -async def buy_subscription_callback_handler(callback: types.CallbackQuery): - """ - Обработчик callback_query с data="buy_subscription". - """ - await callback.message.edit_text( - f"Ознакомься с условиями в вкладке \"О тарифах\" и выбери подходящий 🦅", - reply_markup=buy_keyboard() - ) - -async def guide_callback_handler(callback:types.CallbackQuery): - """ - Обработчик callback_query с data="guide". - """ - await callback.message.edit_text( - f"Руководство по использованию продкута что мы высрали;)", - reply_markup=guide_keyboard() - ) - -async def subs_callback_handler(callback: types.CallbackQuery): - """ - Обработчик callback_query с data="subs". - """ - await callback.message.edit_text( - f"Подписки птенчик", - reply_markup=tarif_Lark_keyboard() - ) - -async def subs_pro_callback_handler(callback: types.CallbackQuery): - """ - Обработчик callback_query с data="subs_pro". - """ - await callback.message.edit_text( - f"Подписки птенчик ПРО", - reply_markup=tarif_Lark_pro_keyboard() - ) - -# async def about_tarifs_callback_handler(callback: types.CallbackQuery): -# """ -# Обработчик callback_query с data="about_tarifs". -# """ -# await callback.message.edit_text( -# f"Бла бла бла, хуйня на хуйне", -# reply_markup=about_tarifs_keyboard() -# ) - -async def faq_callback_handler(callback:types.CallbackQuery): - """ - Обработчик callback_query с data="faq". - """ - await callback.message.edit_text( - f"FAQ YOU", - reply_markup=faq_keyboard() - ) - -async def lark_tariff_callback_handler(callback: types.CallbackQuery): - """ - Обработчик для выбора тарифа Lark. - """ - data = callback.data.split(":") - tariff_name = data[0] - tariff_class = data[1] - tariff_time = int(data[2]) - - # Определение окончания для месяцев - if tariff_time == 1: - months = f"{tariff_time} месяц" - elif 2 <= tariff_time <= 4: - months = f"{tariff_time} месяца" - else: - months = f"{tariff_time} месяцев" - - text = f"Тариф {tariff_name} на {months}. Продолжите покупку..." - - # Рендеринг клавиатуры - keyboard = tarif_confirm_keyboard(tariff_name, tariff_time, tariff_class) - await callback.message.edit_text(text=text, reply_markup=keyboard) - -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/{float(popup_info)}") - if result == "ERROR": - await callback.message.answer( - "Произошла ошибка, попробуйте позже или свяжитесь с администрацией." - ) - await callback.answer() - return - text = f"Вы пополнили свой баланс на {popup_info}. P.S. Мы завтра закрываемся" - await callback.message.edit_text(text=text, reply_markup=confirm_popup_keyboard()) - -async def confirm_callback_handler(callback: types.CallbackQuery): - data = callback.data.split(":")[1] - 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 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): - """ - Регистрация хэндлеров в диспетчере. - """ - dp.callback_query.register(popup_callback_handler, lambda c: c.data == "popup") - dp.callback_query.register(start_callback_handler, lambda c: c.data == "base") - dp.callback_query.register(subs_callback_handler, lambda c: c.data == "subs") - dp.callback_query.register(subs_pro_callback_handler, lambda c: c.data == "subs_pro") - dp.callback_query.register(faq_callback_handler, lambda c: c.data == "faq") - dp.callback_query.register(profile_callback_handler, lambda c: c.data == "profile") - dp.callback_query.register(tranhist_callback_handler, lambda c: c.data == "tranhist") - dp.callback_query.register(buy_subscription_callback_handler, lambda c: c.data == "buy_subscription") - dp.message.register(start_command, Command("start")) - dp.message.register(start_command, Command("subscriptions")) - dp.message.register(start_ticket_creation, Command("support")) - dp.message.register(handle_ticket_input) # Для любых сообщений - dp.callback_query.register(balance_callback_handler, lambda c: c.data == "balance") - dp.callback_query.register(guide_callback_handler, lambda c: c.data == "guide") - # dp.callback_query.register(about_tarifs_callback_handler, lambda c: c.data == "about_tarifs") - dp.callback_query.register(lark_tariff_callback_handler, lambda c: c.data.startswith("Lark:")) - dp.callback_query.register(confirm_callback_handler, lambda c: c.data.startswith("confirm:")) - dp.callback_query.register(popup_confirm_callback_handler, lambda c: c.data.startswith("popup:")) \ No newline at end of file diff --git a/handlers/profile.py b/handlers/profile.py new file mode 100644 index 0000000..dc348d7 --- /dev/null +++ b/handlers/profile.py @@ -0,0 +1,170 @@ +from aiogram import Router, types +from aiogram.types import CallbackQuery +import logging +from datetime import datetime +from aiogram.enums.parse_mode import ParseMode +import locale +from instences.config import BASE_URL_FASTAPI +import aiohttp +from keyboard.keyboards import account_keyboard, popup_keyboard, tranhist_keyboard, confirm_popup_keyboard, guide_keyboard, balance_keyboard + +locale.setlocale(locale.LC_TIME, "ru_RU.UTF-8") + +router = Router() +logger = logging.getLogger(__name__) + +async def call_api(method, endpoint, data=None): + """ + Выполняет HTTP-запрос к FastAPI. + """ + 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 + if response.status == 404: + logger.debug(f"Код {response.status}, возвращаю ничего") + return None + logger.error(f"Ошибка в запросе: статус {response.status}, причина {response.reason}") + return "ERROR" + except Exception as e: + logger.exception(f"Исключение при выполнении запроса к {url}: {e}") + return "ERROR" + +@router.callback_query(lambda callback: callback.data == "profile") +async def profile_callback_handler(callback: CallbackQuery): + """ + Обработчик callback_query для профиля. + """ + try: + user_data = await call_api("GET", f"/user/{callback.from_user.id}") + if not user_data: + await callback.message.answer("Произошла ошибка, попробуйте позже или свяжитесь с администрацией.") + await callback.answer() + return + + 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 = datetime.fromisoformat(expiry_date).strftime("%d %B %Y г.") if expiry_date else None + is_expired = datetime.fromisoformat(expiry_date) < datetime.now() if expiry_date else True + status_icon = "✖️" if is_expired else "☑️" + profile_status = "🦅" if "Pro" in sub_data.get("plan", "") else "🐣" + 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 Exception as e: + logger.exception(f"Ошибка в обработчике профиля: {e}") + await callback.message.answer("Произошла ошибка. Попробуйте позже.") + finally: + await callback.answer() + +@router.callback_query(lambda callback: callback.data == "balance") +async def balance_callback_handler(callback: CallbackQuery): + """ + Обработчик callback_query для баланса. + """ + user_data = await call_api("GET", f"/user/{callback.from_user.id}") + if not user_data: + await callback.message.answer("Произошла ошибка, попробуйте позже или свяжитесь с администрацией.") + await callback.answer() + return + + await callback.message.edit_text( + f"Ваш баланс: {user_data['balance']} ₽. Выберите сумму для пополнения 🐥", + reply_markup=balance_keyboard() + ) + await callback.answer() + +@router.callback_query(lambda callback: callback.data == "popup") +async def popup_callback_handler(callback: CallbackQuery): + """ + Обработчик callback_query для пополнения. + """ + user = await call_api("GET", f"/user/{callback.from_user.id}") + if not user: + await callback.message.answer("Произошла ошибка, попробуйте позже или свяжитесь с администрацией.") + await callback.answer() + return + + await callback.message.edit_text("Работает в режиме теста!!!", reply_markup=popup_keyboard()) + await callback.answer() + +@router.callback_query(lambda callback: callback.data == "tranhist") +async def tranhist_callback_handler(callback: CallbackQuery): + """ + Обработчик callback_query для истории транзакций. + """ + user_data = await call_api("GET", f"/user/{callback.from_user.id}") + if not user_data: + await callback.message.edit_text("Вы еще не зарегистрированы.") + await callback.answer() + return + + try: + transactions = await call_api("GET", f"/user/{user_data['id']}/transactions") + if not transactions: + await callback.message.edit_text("У вас нет транзакций.", reply_markup=tranhist_keyboard()) + await callback.answer() + return + + result = "Ваши транзакции:```\n" + for count, tran in enumerate(transactions, start=1): + dt = datetime.fromisoformat(tran['created_at']).strftime("%d.%m.%Y %H:%M:%S") + result += f"{count}. Сумма: {tran['amount']}, Дата: {dt}\n" + if len(result) > 4000: + result += "...\nСлишком много транзакций для отображения." + break + result += "```" + await callback.message.edit_text(result,parse_mode=ParseMode.MARKDOWN_V2, reply_markup=tranhist_keyboard()) + except Exception as e: + logger.error(f"Ошибка обработки транзакций: {e}") + await callback.message.edit_text("Произошла ошибка. Попробуйте позже.") + finally: + await callback.answer() + +@router.callback_query(lambda callback: callback.data.startswith("popup:")) +async def popup_confirm_callback_handler(callback: CallbackQuery): + """ + Обработчик подтверждения пополнения баланса. + """ + data = callback.data.split(":") + popup_info = data[1] + result = await call_api("POST", f"/user/{callback.from_user.id}/balance/{float(popup_info)}") + if result == "ERROR": + await callback.message.answer("Произошла ошибка, попробуйте позже или свяжитесь с администрацией.") + await callback.answer() + return + + text = f"Вы пополнили свой баланс на {popup_info} ₽. Спасибо!" + await callback.message.edit_text(text=text, reply_markup=confirm_popup_keyboard()) + await callback.answer() + +@router.callback_query(lambda callback: callback.data == "guide") +async def guide_callback_handler(callback: CallbackQuery): + """ + Обработчик callback_query для руководства. + """ + await callback.message.edit_text( + "Руководство по использованию продукта:", + reply_markup=guide_keyboard() + ) + await callback.answer() diff --git a/handlers/start.py b/handlers/start.py new file mode 100644 index 0000000..47bbee8 --- /dev/null +++ b/handlers/start.py @@ -0,0 +1,95 @@ +from aiogram import Router, types +from aiogram.filters import Command +from aiogram.types import Message, CallbackQuery +import logging +from instences.config import BASE_URL_FASTAPI +import aiohttp +from keyboard.keyboards import main_keyboard + +router = Router() +logger = logging.getLogger(__name__) + +async def call_api(method, endpoint, data=None): + """ + Выполняет 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 + if response.status == 404: + logger.debug(f"Код {response.status}, возвращаю ничего") + return None + logger.error(f"Ошибка в запросе: статус {response.status}, причина {response.reason}") + return "ERROR" + except Exception as e: + logger.exception(f"Исключение при выполнении запроса к {url}: {e}") + return "ERROR" + +@router.message(Command("start")) +async def start_command(message: Message): + """ + Обработчик команды /start. + """ + logger.info(f"Получена команда /start от пользователя: {message.from_user.id} ({message.from_user.username})") + + try: + user_data = await call_api("GET", f"/user/{message.from_user.id}") + if not user_data: + logger.debug("Пользователь не найден в базе, создаем новую запись.") + await call_api("POST", "/user/create", {"telegram_id": message.from_user.id}) + + logger.debug("Отправка приветственного сообщения пользователю.") + await message.answer( + f"""Приветствуем в рядах птенец {message.from_user.username}🐣 + \nСкорее пополни баланс, приобрети подписку и получай доступ к TikTok, YouTube, Discord, Instagram на всех устройствах без ограничений ❕❕❕ + \nОзнакомься с руководством по пользованию и выбери подходящий тариф 🦅 + \nСледи за акциями, спец-предложениями, а также розыгрышами по ссылке ниже + \n👇👇👇 + \nhttps://t.me/+0z5xqn3F1m02OTJi + \nС любовью ваши пернатые разработчики 🤍🤍🤍""", + reply_markup=main_keyboard() + ) + logger.info("Приветственное сообщение отправлено.") + + except Exception as e: + logger.exception(f"Ошибка при обработке команды /start для пользователя {message.from_user.id}: {e}") + await message.answer("Произошла ошибка. Попробуйте позже.") + +@router.callback_query(lambda callback: callback.data == "base") +async def start_callback_handler(callback: CallbackQuery): + """ + Обработчик callback_query с data="base". + """ + try: + user_data = await call_api("GET", f"/user/{callback.from_user.id}") + if not user_data: + await call_api("POST", "/user/create", {"telegram_id": callback.from_user.id}) + + await callback.message.edit_text( + f"""Приветствуем в рядах птенец {callback.from_user.username}🐣 + \nСкорее пополни баланс, приобрети подписку и получай доступ к TikTok, YouTube, Discord, Instagram на всех устройствах без ограничений ❕❕❕ + \nОзнакомься с руководством по пользованию и выбери подходящий тариф 🦅 + \nСледи за акциями, спец-предложениями, а также розыгрышами по ссылке ниже + \n👇👇👇 + \nhttps://t.me/+0z5xqn3F1m02OTJi + \nС любовью ваши пернатые разработчики 🤍🤍🤍""", + reply_markup=main_keyboard() + ) + except Exception as e: + logger.exception(f"Ошибка при обработке callback с data='base': {e}") + await callback.message.answer("Произошла ошибка. Попробуйте позже.") + finally: + await callback.answer() diff --git a/handlers/subscriptions.py b/handlers/subscriptions.py new file mode 100644 index 0000000..bc85850 --- /dev/null +++ b/handlers/subscriptions.py @@ -0,0 +1,138 @@ +from aiogram import Router, types +import logging +from instences.config import BASE_URL_FASTAPI +import aiohttp +from aiogram.enums.parse_mode import ParseMode +from aiogram.filters import Command +from keyboard.keyboards import tarif_Lark_pro_keyboard, tarif_Lark_keyboard, tarif_confirm_keyboard,buy_keyboard + +router = Router() +logger = logging.getLogger(__name__) + +async def call_api(method, endpoint, data=None): + """ + Выполняет HTTP-запрос к FastAPI. + """ + 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 + if response.status in {404,400}: + logger.debug(f"Код {response.status}, возвращаю ничего") + return await response.json() + logger.error(f"Ошибка в запросе: статус {response.status}, причина {response.reason}") + return "ERROR" + except Exception as e: + logger.exception(f"Исключение при выполнении запроса к {url}: {e}") + return "ERROR" + +@router.message(Command("subscriptions")) +async def supp(message: types.Message): + """ + Меню сапп системы + """ + text = "" + result = await call_api("GET", f"/uri?telegram_id={message.from_user.id}") + uri = result.get('detail',"Error") + if uri == "SUB_ERROR": + text = f"Вы ещё не приобрели подписки!!" + elif "vless" in uri: + text = f"Ваша подписка:```{uri}```" + else: + text = "Произошла ошибка при получении URI" + await message.answer( + text, + parse_mode=ParseMode.MARKDOWN_V2 + ) + + +@router.callback_query(lambda callback: callback.data == "buy_subscription") +async def buy_subscription_callback_handler(callback: types.CallbackQuery): + """ + Обработчик callback_query с data="buy_subscription". + """ + await callback.message.edit_text( + f"Ознакомься с условиями в вкладке \"О тарифах\" и выбери подходящий 🦅", + reply_markup=buy_keyboard() + ) + +@router.callback_query(lambda callback: callback.data == "subs") +async def subs_callback_handler(callback: types.CallbackQuery): + """ + Обработчик callback_query с data="subs". + """ + await callback.message.edit_text( + "Подписки птенчик", + reply_markup=tarif_Lark_keyboard() + ) + +@router.callback_query(lambda callback: callback.data == "subs_pro") +async def subs_pro_callback_handler(callback: types.CallbackQuery): + """ + Обработчик callback_query с data="subs_pro". + """ + await callback.message.edit_text( + "Подписки птенчик ПРО", + reply_markup=tarif_Lark_pro_keyboard() + ) + +@router.callback_query(lambda callback: callback.data.startswith("Lark:")) +async def lark_tariff_callback_handler(callback: types.CallbackQuery): + """ + Обработчик для выбора тарифа Lark. + """ + data = callback.data.split(":") + tariff_name = data[0] + tariff_class = data[1] + tariff_time = int(data[2]) + + # Определение окончания для месяцев + if tariff_time == 1: + months = f"{tariff_time} месяц" + elif 2 <= tariff_time <= 4: + months = f"{tariff_time} месяца" + else: + months = f"{tariff_time} месяцев" + + text = f"Тариф {tariff_name} на {months}. Продолжите покупку..." + + # Рендеринг клавиатуры + keyboard = tarif_confirm_keyboard(tariff_name, tariff_time, tariff_class) + await callback.message.edit_text(text=text, reply_markup=keyboard) + +@router.callback_query(lambda callback: callback.data.startswith("confirm:")) +async def confirm_callback_handler(callback: types.CallbackQuery): + """ + Обработчик подтверждения подписки. + """ + try: + data = callback.data.split(":")[1] + 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}) + detail = result.get("detail", {}) + + if detail == "ERROR": + await callback.message.edit_text("Произошла ошибка при оформлении подписки.") + elif detail == "INSUFFICIENT_FUNDS": + await callback.message.edit_text("Денег на вашем балансе не достаточно.") + elif detail == "TARIFF_NOT_FOUND": + await callback.message.edit_text("Ваш тариф не найден.") + elif detail == "ACTIVE_SUBSCRIPTION_EXISTS": + await callback.message.edit_text("Вы уже имеете активную подписку.") + else: + uri = result.get("message", {}) + await callback.message.edit_text(f"Подписка успешно оформлена!\nВаш конфиг для подключения: ```{uri}```") + except Exception as e: + logger.exception(f"Ошибка при обработке подтверждения подписки: {e}") + await callback.message.edit_text("Произошла ошибка при оформлении подписки.") + finally: + await callback.answer() diff --git a/handlers/support.py b/handlers/support.py new file mode 100644 index 0000000..5c4dcc0 --- /dev/null +++ b/handlers/support.py @@ -0,0 +1,160 @@ +from aiogram import Router, types +from aiogram.filters import Command +import logging +from datetime import datetime +from instences.config import BASE_URL_FASTAPI +import aiohttp +from aiogram.fsm.context import FSMContext +from aiogram.fsm.state import State, StatesGroup +from keyboard.keyboards import faq_keyboard, sup_keyboard, ticket_list_keyboard, ticket_keyboard + +logger = logging.getLogger(__name__) +router = Router() + +class TicketState(StatesGroup): + subject = State() + message = State() + +async def call_api(method, endpoint, data=None): + """ + Выполняет HTTP-запрос к FastAPI. + """ + 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 + if response.status == 404: + logger.debug(f"Код {response.status}, возвращаю ничего") + return None + logger.error(f"Ошибка в запросе: статус {response.status}, причина {response.reason}") + return "ERROR" + except Exception as e: + logger.exception(f"Исключение при выполнении запроса к {url}: {e}") + return "ERROR" + +@router.callback_query(lambda callback: callback.data == "faq") +async def faq_callback_handler(callback: types.CallbackQuery): + """ + Обработчик callback_query с data="faq". + """ + await callback.message.edit_text( + "FAQ YOU", + reply_markup=faq_keyboard() + ) + +@router.message(Command("support")) +async def supp(message: types.Message): + """ + Меню сапп системы + """ + await message.answer( + "Добро пожаловать в саппорт систему!", + reply_markup=sup_keyboard() + ) + +@router.callback_query(lambda callback: callback.data == "main_sup") +async def supp_callback(callback: types.CallbackQuery): + """ + Меню сапп системы (callback версия) + """ + await callback.message.answer( + "Добро пожаловать в саппорт систему!", + reply_markup=sup_keyboard() + ) + +@router.callback_query(lambda callback: callback.data == "my_tickets") +async def list_tickets_callback(callback: types.CallbackQuery): + user_id = callback.from_user.id + user_data = await call_api("GET", f"/user/{user_id}") + if not user_data: + user_data = await call_api("POST", f"/user/create", {"telegram_id": f"{user_id}"}) + + tickets = await call_api("GET", f"/support/tickets?user_id={user_data['id']}") + + if tickets == "ERROR" or not tickets: + await callback.message.edit_text("У вас нет тикетов.", reply_markup=sup_keyboard()) + return + + await callback.message.edit_text( + "Ваши тикеты:", + reply_markup=ticket_list_keyboard(tickets) + ) + +@router.callback_query(lambda callback: callback.data == "make_ticket") +async def start_ticket_creation(callback: types.CallbackQuery, state: FSMContext): + """ + Начинает процесс создания тикета. + """ + await callback.message.answer( + "Введите тему тикета (или нажмите 'Отмена', чтобы выйти):", + reply_markup=ticket_keyboard() + ) + await state.set_state(TicketState.subject) + +@router.message() +async def handle_ticket_input(message: types.Message, state: FSMContext): + """ + Обрабатывает ввод данных для тикета. + """ + current_state = await state.get_state() + if current_state == TicketState.subject: + await state.update_data(subject=message.text) + await message.answer("Введите описание проблемы:", reply_markup=ticket_keyboard()) + await state.set_state(TicketState.message) + + elif current_state == TicketState.message: + user_data = await state.get_data() + subject = user_data.get("subject") + message_text = message.text + await create_ticket(message, subject, message_text, state) + await state.clear() + +async def create_ticket(message: types.Message, subject: str, message_text: str, state: FSMContext): + """ + Отправляет запрос на создание тикета через FastAPI. + """ + user_id = message.from_user.id + + try: + logger.info(f"Создание тикета для пользователя {user_id}: Тема - {subject}, Сообщение - {message_text}") + + user_data = await call_api("GET", f"/user/{user_id}") + if not user_data: + await message.answer("Вы еще не зарегистрированы.") + return + + ticket_data = await call_api( + "POST", + f"/support/tickets?user_id={user_data['id']}", + data={"subject": subject, "message": message_text} + ) + + if ticket_data != "ERROR": + await message.answer( + f"✅ Тикет успешно создан!\n" + f"📌 Тема: {ticket_data['subject']}\n" + f"📊 Статус: {ticket_data['status']}\n" + f"📅 Дата создания: {ticket_data['created_at']}" + ) + else: + await message.answer("❌ Ошибка создания тикета. Попробуйте позже.") + + except Exception as e: + logger.exception(f"Ошибка при создании тикета для пользователя {user_id}: {e}") + await message.answer("❌ Произошла ошибка при создании тикета.") + +@router.callback_query(lambda callback: callback.data == "cancel_ticket") +async def cancel_ticket_creation(callback: types.CallbackQuery, state: FSMContext): + """ + Отмена создания тикета. + """ + await state.clear() + await callback.message.answer("Создание тикета отменено.", reply_markup=types.ReplyKeyboardRemove()) diff --git a/instences/__pycache__/db_config.cpython-312.pyc b/instences/__pycache__/db_config.cpython-312.pyc deleted file mode 100644 index 29fa4710de572c6d8e1909626cdea454c0b7554e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3012 zcmbVOU2NOd6~0#_CF+-wV>^k5Eb>q6XiFqJHQ**|(~%KD!`xrjt0Vg;!^z>d*P9&MEQ8DJE@A6I0lY(grCGOLxmqLoUL?t~SsAm)o&`6qJ82~{-1Ea}qtj}AM0 z)sXaZL6w|vrC=D7nl9!?56nE=2r*76-=&+Wl=fvZr%CXceW$(1Gv8OVVnI0p=`t@= z%J~V+d=fUqO2rdeigDNpxhy*Vq5_HYPF$L*$hwr*%CHYrmCc#0s#Y#(;sj)F=GnJr zWN>K238~ZhhPUW`N>>6Z&>W2QcR~CRVL*cEdO)6RRHA7B#jEu&n!_SK4H3WcU1VP3 zICKV?9;N_0Uc~@05GP`<)^om%t1z0zBk1KMj6g3MLe$SfR)%C9K^!vhB;fHl?2W`u zF~`v)eutUFDW>`qY$%k3$GT+QvX<!$UA{fhlF z>naTd^UdV3X4TR($=FQC15*-eTb7EZB&OlZ3tss=3~>_@V9DVsx(w)m5|MC&pfvf_ z4>Bh-Rm!}e6hv8-^~|VNtf~MaBXdAA<>xcc96Yf5xnpCy4M|ZXeK!<2BNoiUL;-M^ z5hog`N>`>GK`zT?-uvxSioO|spbducJrI9GU)InUJc@6x3w^fGR~PzMh5jpjpYTyD zwqup={|AHO17Nj<{x2;Upt`gB#(r2oyEWT^FXKo2Z!n;|8Ob7MAq?6zyq~@92W1(9 zSV8`5TVTbHv%$zpAVBn30PGuJL<&z~vZpK!}2i z!hnO)n*^%#5g9KE0O14R#Cws+d)_{zZviMih-P0zm(g!p0C^m8pFux`weEWoPofrB zan(m5mkX;m?HZuxJ?kpGZ(57r0H!14=#3t5k>s!yxm+5XX=_+pz%=viCkwJ7iAhuQ ztVC@D?n|aPCqfTuzDSwJ;R>P(NDGiNUMP!tJ8aGgJ~364DyGX{SaN}VjcB3xWdXF3)kppU1lDv7{YK)?+*E*v>1Rw_}5Ke$e6vDR_E5=6mllC^+=b z&%^Ea5ax!7Ii9|9>Ppdy?Wyw*+5AKQy32s$zA*xsXK{8LKG(Asy@f~Ew}rjv8ZlhQ zd(n+xwu@Qu#j+jDN}L1ZN(VqgT01Em%Mltp{iTDEIRh`VZ^rO`LOPBuUq ziPlDRm@q#W1C4IfTGRHD^neEPjR3tDw$u;97y{8GgYLS}vnuq=m)2q(7ovm$dck4~ zJwO3HKmk4O?7s0ZteO?F9R4NV<$sL<<#qocb2$vkTR6?W?FZ#u3}Vrr^#!isy^-qy zP?iH=+E6$K63xL_e*?t-(nH{oZ|ER!$eX0l+CfY|B&N^Fpr<`}dmU-R>Q;&%{Ut*? z23vg&*12W82%X}lU9&GhwU(@_N$WjYuk)mA%TUYrNs--l0p+#NCavGY3g|1=ht^UO z+>79b+S}*7?y>Fb-2)D2vZ!bVbdi~^{~&`FUy9QSt2+TvHh}FVddPKhZXJ#;|1DZ4 zf7m_Y32Gv3f;1>dW9TVxrePS5fpDP^LgB7@sM`*8-wyTG*w@`Ec$Lzq}Fyi{YG6cvmyCoaJzsA`RdwCGFvD9X#6YeerrFcS8r_<7u znWonmS<^}Rb?Dg0iM2#%x~h|6I-68-jY?DL-os!GDX0^vnzCY~4{N2zwX#l*zfM>j zE;#PBiY2!rhxaZ=PUP6p@n;S`{ls{FbbO3{Fuj|^2ag;bJ9sqz?9+$bPsY3a3G7GO zC8a=mR_IsK2Z(Ja5tJ_ZM^sI$D$;&^7;J=ZjB_A>+c5qNjed#-K1F*zLqmTFwqMv* z^RES>KUeI)w=O+p2L@{FS~&6QE<3z^K5B>4HGWMvREykc+cB>!b*#2MRuk^DC+D># z#rpBYYJ1T_i9bgMYQZ}~_k8NMu&WkX3va#@vBNuR{5?O12kym#c=+BH4tK7{LwG0M G<-Y*}fI}|; diff --git a/instences/__pycache__/postgresql.cpython-312.pyc b/instences/__pycache__/postgresql.cpython-312.pyc deleted file mode 100644 index 732526f6434c7d7b214eaac91578e7ae5bcda36e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7978 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