From 22b1342d43682611687bab893c1007f02bde27dc Mon Sep 17 00:00:00 2001 From: Vlad Date: Wed, 18 Sep 2024 04:09:38 +0300 Subject: [PATCH 01/12] =?UTF-8?q?=D0=BE=D0=BF=D1=82=D0=B8=D0=BC=D0=B8?= =?UTF-8?q?=D0=B7=D0=B0=D1=86=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- handlers/__pycache__/keys.cpython-310.pyc | Bin 1624 -> 1921 bytes handlers/__pycache__/pay.cpython-310.pyc | Bin 4504 -> 4673 bytes handlers/__pycache__/profile.cpython-310.pyc | Bin 2645 -> 2680 bytes handlers/__pycache__/start.cpython-310.pyc | Bin 1308 -> 4654 bytes handlers/keys.py | 9 +- handlers/pay.py | 90 +++++++++-------- handlers/profile.py | 20 ++-- handlers/start.py | 97 +++++++++++++++---- key_management.py | 68 ++++++++----- 9 files changed, 188 insertions(+), 96 deletions(-) diff --git a/handlers/__pycache__/keys.cpython-310.pyc b/handlers/__pycache__/keys.cpython-310.pyc index 1c9d0b6eb2f9270d303421b605076ebcad9fae56..5d50a89d45f9ac129745940175bb3839b92a0512 100644 GIT binary patch delta 693 zcmaJ-&1(}u6rVTQt;uHd9k4011eM}?kzi5CQ4m|S9#kw!VcBH1Azz!YJ0VTFv5A6s zFrv&sQH10_@Z(^pR1vQpb`OH?$&&{?37*x7X+%BvhR2)td++_`_vXFai-c>Tkc7D9 z7C&!%+xH|~fzvWi$JiV0)=faD2R1695_F(_m*Rns3PV^zD~Ot1(u z+#Alw8Ks?9&danja(m;8^TNI5Jm>ML^Vr$t=xt9f8WuGhd0MO)mV!Nj>YLOH<+VyB zr{&8k(Ws_?7bzN*qM@s0y_KtJCh>&ji>qgOzf{*uOHZiP8qq!3)a#X&TGh;Qqt3qi z4({ipi3vz+YE`!^t)Qz#!jeK3D+$&T)-xvN+P&n;uLsWT@t`(#RAd$x&o3nYTu9ALc>Gh{EH55|EW0PpABaQP7lk=2;5oqEBVP{x f5W3-62^PWM#oyzCnPG3m6r|WE@dRYp!c6uj5D3>6 delta 419 zcmYjNKTE@K5WTznX_BU?+Ny&JA_$F>h$wV(kk&=S4k8F8n6$REsic8uNoBDCVR7OFkCrARs88#Z?@R970$00Z#jRXxN|vEw{g zqH5UswK$H05JEpW_zj2rwg6^gxAmkg{;S7jVGh}=xWsfR%kIVLnUv?8*3OH5qgZ#E zO{?q_E4Deo^3qZKL)|_*c250D!)0%hsmhky3>?qY*p$4_F6FhoRsJaIC`6P-8AVC| zCdojN6jU(HA=AoNW+--y$r)5SG@KWv(97^6Q~!url}6dK>K<|J38R)~$=D`lST2_R E0cOKqOaK4? diff --git a/handlers/__pycache__/pay.cpython-310.pyc b/handlers/__pycache__/pay.cpython-310.pyc index 544e555dd4d8acda4ae9bcae0cb53752ac207ff6..e6aecc6131e2e42031651bf7c217c6cbebe30f2e 100644 GIT binary patch delta 2064 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 Fe*ut9T15Z= literal 1308 zcmZ`(&2G~`5Z+z?CO9bw5PuRfH!M}n3m}Am0|-$eKpcFD-0U`l+HtgYT7pukP$ZCO zK|&n4aO74Hg+ggL@B-`WUOB-d00L&$2~-Fic{2O$%r`UZnN4f8DuSnKf0?BYLZ8%P z=VQU*6ujA05EM}yqXdUIAtAwPO=2T4LsRQUY$bMRCr;=jZs;bJu!0dI%P6eU3bh!% zv5VTwn>DFJ-Mdy;qvjP9R6heV3W&7wL62q9zMS@R#se&!Gij1UT^g8j-}!Fb>9PxK zFq1}{p6=&)+Wi~781cD&Pwu_UdhuXvIs)tCb?G`SorGms)Mm2QisE=CYRxrilt(S1 zvKc*aAV?j+M|iU#h})RrMUq3=)S%`nSu{YmsI7G~$3vryhh`hM33XQSqLq`}pf0Vf z;x-1&DmB=x(p}(f577;CNQM^mR>^rd&T|MOz${uD+B-3d;GpW${i7Hm@kDHk7sZm; z_QeNKUyC)d3I8v}T5+dXIp&MU#RFfw*IVnw9kK2gw_)){yechx@lI?O4~u&~Ae+Ub zlIGPI73*S4Yyf=i()1+ksYpNod_cb>fU#HsMj(cGV5qbW@vd0*)k3GVS*$=fol?m) z=@-j@Y>g8rGXalnh`KGdihEE*8TEG>YrqGEZTv3DqzrSBOJq4$?fAL9gzQY!pJ)X{XVg@}q(b0{lD0CqlnSvKFelE!IsCe0hyVGd))vqqLjJfG?fWKC;Lh_7Z;<(nM`L6!(1KRw4Yh+$&W zaK96`N~)OrAg29oU&X`(e!GA0BVZih>7Xv1sFSvNlyEhB+yEgbM$i--J6r}CHx*hN zqxz=!=V{qo-yYJrI2vyw%ejuF3)~?ND@ON3lG1+6PQqZJOdaZO{key}\nДата окончания: {expiry_date}" + + # Добавляем кнопку с инструкциями + instructions_button = types.InlineKeyboardButton(text='Инструкции по использованию', callback_data='instructions') + keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[instructions_button]]) + + await bot.send_message(tg_id, response_message, parse_mode="HTML", reply_markup=keyboard) else: response_message = "У вас нет ключей." + await bot.send_message(tg_id, response_message, reply_to_message_id=callback_query.message.message_id) except Exception as e: response_message = f"Ошибка при получении ключей: {e}" + await bot.send_message(tg_id, response_message, reply_to_message_id=callback_query.message.message_id) - await bot.send_message(tg_id, response_message, parse_mode="HTML", reply_to_message_id=callback_query.message.message_id) await callback_query.answer() diff --git a/handlers/pay.py b/handlers/pay.py index 9c06aeea..bf99bba6 100644 --- a/handlers/pay.py +++ b/handlers/pay.py @@ -17,100 +17,98 @@ class ReplenishBalanceState(StatesGroup): async def process_callback_replenish_balance(callback_query: types.CallbackQuery, state: FSMContext): await state.set_state(ReplenishBalanceState.choosing_transfer_method) keyboard = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text='По реквизитам', callback_data='transfer_method_requisites')], - [InlineKeyboardButton(text='По СБП', callback_data='transfer_method_sbp')] + [InlineKeyboardButton(text='По реквизитам', callback_data='transfer_method_requisites')] ]) - await callback_query.message.answer("Выберите метод перевода:", reply_markup=keyboard) - await callback_query.answer() + await bot.send_message(callback_query.from_user.id, "Выберите метод перевода:", reply_markup=keyboard) + await callback_query.answer() # Уведомляем Telegram, что запрос обработан @router.callback_query(lambda c: c.data.startswith('transfer_method_')) async def process_transfer_method_selection(callback_query: types.CallbackQuery, state: FSMContext): data = callback_query.data.split('_', 2) if len(data) != 3: - await callback_query.answer("Неверные данные для выбора метода перевода.") + await bot.send_message(callback_query.from_user.id, "Неверные данные для выбора метода перевода.") return - transfer_method = data[2] # 'requisites' или 'sbp' + transfer_method = data[2] # 'requisites' if transfer_method == 'requisites': message = ( - "Реквизиты для пополнения:\n" - f'\n' - "Банк: Пример Банк\n" - "Расчетный счет: 12345678901234567890\n" - "Получатель: ИП Иванов Иван Иванович" - f'\n' - "после перевода отправьте чек и ждите подтверждения" + "Банк: Т-Банк\n" + "2200701036597224\n" + "Получатель: Лисицын В.Д.\n" + f"\n" + "После перевода отправьте чек и дождитесь подтверждения." ) - elif transfer_method == 'sbp': - message = ( - "СБП для пополнения:\n" - f'\n' - "Номер банка: 1234567890\n" - "ФИО: Иванов Иван Иванович\n" - f'\n' - "после перевода отправьте чек и ждите подтверждения" + + # Удаляем старое сообщение о выборе метода + await bot.edit_message_text( + text="Вы выбрали метод перевода. Теперь вы увидите реквизиты.", + chat_id=callback_query.from_user.id, + message_id=callback_query.message.message_id ) + + # Отправляем реквизиты в новом сообщении + await bot.send_message(callback_query.from_user.id, message) + else: - await callback_query.answer("Неверный метод перевода.") + await bot.send_message(callback_query.from_user.id, "Неверный метод перевода.") return - await callback_query.message.answer(message) + await state.update_data(transfer_method=transfer_method) + await state.set_state(ReplenishBalanceState.waiting_for_admin_confirmation) + + admin_keyboard = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text='Подтвердить', callback_data=f'admin_confirm_{callback_query.from_user.id}_{transfer_method}')], + [InlineKeyboardButton(text='Отклонить', callback_data=f'admin_decline_{callback_query.from_user.id}_{transfer_method}')] + ]) + + admin_message = ( + f"Пользователь {callback_query.from_user.full_name} запросил пополнение баланса.\n" + f"Метод перевода: {transfer_method}\n" + "Пожалуйста, подтвердите или отклоните запрос." + ) + + await bot.send_message(ADMIN_ID, admin_message, reply_markup=admin_keyboard) await callback_query.answer() @router.callback_query(lambda c: c.data.startswith('admin_')) async def process_admin_confirmation(callback_query: types.CallbackQuery, state: FSMContext): if callback_query.from_user.id != ADMIN_ID: - await callback_query.answer("Вы не являетесь администратором.") + await bot.send_message(callback_query.from_user.id, "Вы не являетесь администратором.") return data = callback_query.data.split('_', 4) if len(data) < 4: - await callback_query.answer("Неверные данные для обработки запроса.") + await bot.send_message(callback_query.from_user.id, "Неверные данные для обработки запроса.") return action = data[1] # 'confirm' или 'decline' - user_id_str = data[3] + user_id_str = data[2] + transfer_method = data[3] try: user_id = int(user_id_str) # Преобразование user_id в целое число if action == 'confirm': - - amount_str = data[4].split('_')[0] - - # Убедимся, что amount_str действительно является числом - if amount_str.isdigit(): - amount = int(amount_str) # Преобразование amount в целое число - else: - await callback_query.answer("Неверная сумма для пополнения. Проверьте формат данных.") - print(f"Ошибка преобразования суммы: amount_str='{amount_str}'") # Отладочное сообщение в консоль - return + amount = 100 # Пример суммы, на которую нужно пополнить баланс async with aiosqlite.connect(DATABASE_PATH) as db: - # Обновляем баланс пользователя await update_balance(user_id, amount) await db.commit() balance = await get_balance(user_id) - await callback_query.message.answer(f"Баланс пользователя успешно пополнен на {amount} единиц.\nТекущий баланс: {balance}") - - # Сообщаем пользователю о пополнении + await bot.send_message(callback_query.from_user.id, f"Баланс пользователя успешно пополнен на {amount} RUB.\nТекущий баланс: {balance}") await bot.send_message(user_id, f"Ваш баланс был успешно пополнен на {amount} RUB.") elif action == 'decline': - await callback_query.message.answer("Пополнение баланса отклонено.") - - # Сообщаем пользователю об отклонении + await bot.send_message(callback_query.from_user.id, "Пополнение баланса отклонено.") await bot.send_message(user_id, "Ваш запрос на пополнение баланса был отклонен.") except Exception as e: - # Отладочное сообщение - await callback_query.message.answer(f"Ошибка при пополнении баланса: {e}") + await bot.send_message(callback_query.from_user.id, f"Ошибка при пополнении баланса: {e}") print(f"Ошибка при пополнении баланса: {e}") # Отладочное сообщение в консоль - # Очистка состояния await state.clear() await callback_query.answer() diff --git a/handlers/profile.py b/handlers/profile.py index 03eefefd..b39c6a8e 100644 --- a/handlers/profile.py +++ b/handlers/profile.py @@ -35,24 +35,32 @@ async def process_callback_view_profile(callback_query: types.CallbackQuery, sta balance = await get_balance(tg_id) profile_message = ( f"Профиль \n\n" - f"ID: @{tg_id}\n" - f"Ключ: {email if email else 'Нет активного ключа'}\n" + f"ID: {tg_id}\n" + f"Ключ: {email if expiry_date else 'Нет активного ключа'}\n" f"Баланс: {balance} RUB\n" ) # Кнопки для действий в профиле + button_create_key = InlineKeyboardButton(text='Создать ключ', callback_data='create_key') button_view_keys = InlineKeyboardButton(text='Мои ключи', callback_data='view_keys') button_replenish_balance = InlineKeyboardButton(text='Пополнить баланс', callback_data='replenish_balance') - keyboard = InlineKeyboardMarkup(inline_keyboard=[[button_view_keys], [button_replenish_balance]]) + keyboard = InlineKeyboardMarkup(inline_keyboard=[ + [button_create_key], # Добавляем кнопку "Создать ключ" + [button_view_keys], + [button_replenish_balance] + ]) - profile_message += "\nВы можете просмотреть ваши ключи или пополнить баланс ниже:" - except Exception as e: profile_message = f"Ошибка при получении данных профиля: {e}" keyboard = None # Отправляем сообщение с клавиатурой - await callback_query.message.reply(profile_message, reply_markup=keyboard) + await callback_query.message.bot.send_message( + chat_id=callback_query.message.chat.id, + text=profile_message, + parse_mode='Markdown', + reply_markup=keyboard + ) await callback_query.answer() @router.callback_query(lambda c: c.data == 'view_profile') diff --git a/handlers/start.py b/handlers/start.py index ddac6a2e..b9b9dab0 100644 --- a/handlers/start.py +++ b/handlers/start.py @@ -1,31 +1,37 @@ -from aiogram import types, Router +from aiogram import Bot, Dispatcher, Router, types from aiogram.filters import Command -from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, ReplyKeyboardMarkup, KeyboardButton +from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, CallbackQuery, Message, ReplyKeyboardMarkup, KeyboardButton +from aiogram.fsm.storage.memory import MemoryStorage +from aiogram.fsm.context import FSMContext +from aiogram.fsm.state import State, StatesGroup +from config import API_TOKEN, ADMIN_ID +from bot import dp, bot # Убедитесь, что bot и dp импортируются из правильного места router = Router() -async def start_command(message: types.Message, callback_data: str = None): +class FeedbackState(StatesGroup): + waiting_for_feedback = State() + +async def start_command(message: types.Message): welcome_text = ( - "Добро пожаловать! Вы можете создать ключ для подключения VPN, " - "просмотреть статистику использования, узнать дату окончания ключа или " - "просмотреть ваш профиль." + "*Добро пожаловать в наш сервис!*\n\n" + "Вы можете воспользоваться следующими функциями:\n\n" + "🔑 *Создать ключ для подключения VPN* - Получите уникальный ключ для доступа к VPN.\n" + "📅 *Узнать дату окончания ключа* - Проверьте, когда истекает срок действия вашего ключа.\n" + "👤 *Просмотреть ваш профиль* - Получите информацию о вашем аккаунте и балансе.\n\n" + "Нажмите на соответствующую кнопку ниже, чтобы начать!" ) - # Создаем кнопки для команд - button_create_key = InlineKeyboardButton(text='Создать ключ', callback_data='create_key') -# button_view_stats = InlineKeyboardButton(text='Посмотреть статистику', callback_data='view_stats') -# button_view_expiry = InlineKeyboardButton(text='Дата окончания ключа', callback_data='view_expiry') button_view_profile = InlineKeyboardButton(text='Мой профиль', callback_data='view_profile') + button_about_vpn = InlineKeyboardButton(text='О VPN', callback_data='about_vpn') + button_feedback = InlineKeyboardButton(text='Обратная связь', callback_data='feedback') - # Создаем клавиатуру с кнопками inline_keyboard = InlineKeyboardMarkup(inline_keyboard=[ - [button_create_key], -# [button_view_stats], -# [button_view_expiry], - [button_view_profile] + [button_view_profile], + [button_about_vpn], + [button_feedback] ]) - # Создаем кнопку для возврата в начало reply_keyboard = ReplyKeyboardMarkup( keyboard=[ [KeyboardButton(text="В начало")], @@ -34,9 +40,58 @@ async def start_command(message: types.Message, callback_data: str = None): resize_keyboard=True ) - # Отправляем сообщение с кнопками - await message.reply(welcome_text, reply_markup=inline_keyboard) - await message.answer( - "", + await message.bot.send_message( + chat_id=message.chat.id, + text=welcome_text, + parse_mode='Markdown', + reply_markup=inline_keyboard + ) + + await message.bot.send_message( + chat_id=message.chat.id, + text="Выберите действие:", reply_markup=reply_keyboard - ) \ No newline at end of file + ) + +@router.callback_query(lambda c: c.data == 'about_vpn') +async def handle_about_vpn(callback_query: CallbackQuery): + info_message = ( + "*О VPN*\n\n" + "Мы используем VLESS для обеспечения безопасного и надежного подключения к интернету. " + "Каждому пользователю предоставляется индивидуальный ключ для подключения. " + "Этот ключ необходим для использования нашего VPN-сервиса." + ) + await callback_query.message.answer( + info_message, + parse_mode='Markdown' + ) + await callback_query.answer() + +@router.callback_query(lambda c: c.data == 'feedback') +async def handle_feedback(callback_query: CallbackQuery, state: FSMContext): + await callback_query.message.answer( + "Напишите нам, если у вас возникли трудности с подключением, есть отзыв или предложение. Ваше сообщение будет отправлено админу.", + parse_mode='Markdown' + ) + await state.set_state(FeedbackState.waiting_for_feedback) + await state.update_data(user_id=callback_query.from_user.id) + await callback_query.answer() + +@router.message(FeedbackState.waiting_for_feedback) +async def receive_feedback(message: types.Message, state: FSMContext): + user_data = await state.get_data() + user_id = user_data.get('user_id') + + feedback_message = ( + f"Обратная связь от пользователя {user_id}:\n\n" + f"{message.text}" + ) + + try: + await bot.send_message(ADMIN_ID, feedback_message) + await message.answer("Спасибо за ваше сообщение! Мы свяжемся с вами, если это будет необходимо.") + except Exception as e: + await message.answer("Произошла ошибка при отправке вашего сообщения.") + print(f"Ошибка при отправке обратной связи: {e}") # Логирование ошибок + + await state.clear() diff --git a/key_management.py b/key_management.py index 5ba71195..98ca6ba7 100644 --- a/key_management.py +++ b/key_management.py @@ -1,6 +1,6 @@ from aiogram import Bot, Dispatcher, Router, F, types from aiogram.filters import Command -from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, CallbackQuery, Message, ReplyKeyboardMarkup, KeyboardButton +from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, CallbackQuery, Message from aiogram.fsm.storage.memory import MemoryStorage from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup @@ -19,7 +19,6 @@ from handlers.profile import process_callback_view_profile router = Router() - def escape_markdown(text: str) -> str: """ Экранирование специальных символов для Markdown. @@ -27,21 +26,26 @@ def escape_markdown(text: str) -> str: escape_chars = r'_*[]()~`>#+-=|{}.!' return re.sub(r'([%s])' % re.escape(escape_chars), r'\\\1', text) +def sanitize_key_name(key_name: str) -> str: + """ + Очищает имя ключа от недопустимых символов и приводит его к нижнему регистру. + """ + # Оставляем только строчные буквы, цифры, символы @._- + return re.sub(r'[^a-z0-9@._-]', '', key_name.lower()) + class Form(StatesGroup): waiting_for_key_name = State() waiting_for_admin_confirmation = State() waiting_for_statistics = State() waiting_for_expiry_date = State() - viewing_profile = State() - + viewing_profile = State() @dp.callback_query(F.data == 'create_key') async def process_callback_create_key(callback_query: types.CallbackQuery, state: FSMContext): - await callback_query.message.reply("Введите имя вашего профиля VPN:") + await bot.send_message(callback_query.from_user.id, "Введите имя вашего профиля VPN:") await state.set_state(Form.waiting_for_key_name) await callback_query.answer() - @dp.message() async def handle_text(message: types.Message, state: FSMContext): current_state = await state.get_state() @@ -68,16 +72,16 @@ async def handle_text(message: types.Message, state: FSMContext): if current_state == Form.waiting_for_key_name.state: tg_id = message.from_user.id - key_name = message.text + key_name = sanitize_key_name(message.text) tg_name = message.from_user.username if not key_name: - await message.reply("Имя клиента не указано.") + await bot.send_message(tg_id, "Имя клиента не указано.") await state.clear() return if await has_active_key(tg_id): - await message.reply("У вас уже есть активный ключ. Вы не можете создать новый.") + await bot.send_message(tg_id, "У вас уже есть активный ключ. Вы не можете создать новый.") await state.clear() return @@ -98,8 +102,7 @@ async def handle_text(message: types.Message, state: FSMContext): ]) await bot.send_message(ADMIN_CHAT_ID, admin_message, reply_markup=keyboard) - await state.set_state(Form.waiting_for_admin_confirmation) - await message.reply("Ожидайте подтверждения администратора.") + await bot.send_message(tg_id, "Ожидайте подтверждения администратора.") await state.clear() @dp.callback_query(F.data.startswith('confirm_key_')) @@ -114,7 +117,7 @@ async def handle_admin_confirmation(callback_query: CallbackQuery, state: FSMCon try: client_id = str(uuid.uuid4()) - email = key_name + email = key_name.lower() # Приведение email к нижнему регистру limit_ip = 1 total_gb = 0 current_time = datetime.utcnow() @@ -123,25 +126,46 @@ async def handle_admin_confirmation(callback_query: CallbackQuery, state: FSMCon flow = "xtls-rprx-vision" balance = 0 + # Проверка данных перед созданием ключа + print(f"Creating key with client_id={client_id}, email={email}, tg_id={tg_id}") + add_client(session, client_id, email, tg_id, limit_ip, total_gb, expiry_time, enable, flow) connection_link = link(session, client_id, email) await add_connection(tg_id, client_id, email, expiry_time, balance) await store_key(client_id, email, connection_link) - await bot.send_message(tg_id, f"Ключ создан:\n
{connection_link}
", parse_mode="HTML") - await callback_query.message.reply("Ключ создан и отправлен клиенту.") + + # Создаем кнопки + instructions_button = InlineKeyboardButton(text='Инструкции по использованию', callback_data='instructions') + keyboard = InlineKeyboardMarkup(inline_keyboard=[[instructions_button]]) + + # Отправляем сообщение с ключом и кнопкой + key_message = f"Ключ создан:\n
{connection_link}
" + await bot.send_message(tg_id, key_message, parse_mode="HTML", reply_markup=keyboard) + await bot.send_message(callback_query.message.chat.id, "Ключ создан и отправлен клиенту.") except Exception as e: - await callback_query.message.reply(f"Ошибка при создании ключа: {e}") + await bot.send_message(callback_query.message.chat.id, f"Ошибка при создании ключа: {e}") + print(f"Ошибка при создании ключа: {e}") # Отладочное сообщение в консоль await callback_query.answer() -@dp.callback_query(F.data.startswith('reject_key_')) -async def handle_rejection(callback_query: CallbackQuery, state: FSMContext): - if callback_query.message.chat.id != int(ADMIN_CHAT_ID): - return - tg_id = int(callback_query.data.split('_', 3)[2]) - await bot.send_message(tg_id, "Создание ключа отклонено.") - await callback_query.message.reply("Запрос отклонен.") + +@dp.callback_query(F.data == 'instructions') +async def handle_instructions(callback_query: CallbackQuery): + instructions_message = ( + "*Инструкции по использованию вашего ключа:*\n\n" + "1. Скачайте приложение для вашего устройства:\n" + " - Для Android: [V2Ray](https://play.google.com/store/apps/details?id=com.v2ray.ang&hl=ru&pli=1)\n" + " - Для iPhone: [Streisand](https://apps.apple.com/ru/app/streisand/id6450534064)\n\n" + "2. Скопируйте предоставленный ключ, который вы получили ранее.\n" + "3. Откройте приложение и нажмите на плюсик сверху справа.\n" + "4. Выберите 'Вставить из буфера обмена' для добавления ключа.\n\n" + "Если у вас возникнут вопросы, не стесняйтесь обращаться в поддержку." + ) + await callback_query.message.answer( + instructions_message, + parse_mode='Markdown' + ) await callback_query.answer() From a6d2203e78761b6f5b9bd3e71a3c4ea5bc0ef19b Mon Sep 17 00:00:00 2001 From: Vlad Date: Thu, 19 Sep 2024 03:41:44 +0300 Subject: [PATCH 02/12] =?UTF-8?q?=D0=B0=D0=BF=D0=B4=D0=B5=D0=B9=D1=82=20?= =?UTF-8?q?=D1=8E=D0=B7=D0=B5=D1=80=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- auth.py | 2 + client.py | 67 +++++++++++++++++++++- handlers/__pycache__/keys.cpython-310.pyc | Bin 1921 -> 4032 bytes handlers/keys.py | 63 ++++++++++++++++++-- key_management.py | 42 +++++++++++++- path_to_your_database | 0 6 files changed, 164 insertions(+), 10 deletions(-) create mode 100644 path_to_your_database diff --git a/auth.py b/auth.py index c1d0880e..2c0f1443 100644 --- a/auth.py +++ b/auth.py @@ -1,6 +1,7 @@ import requests from config import ADMIN_USERNAME, ADMIN_PASSWORD, GET_INBOUNDS_URL import json +import httpx session = None @@ -19,6 +20,7 @@ def login_with_credentials(username, password): else: raise Exception(f"Ошибка авторизации: {response.status_code}, {response.text}") + def get_clients(session): response = session.get(GET_INBOUNDS_URL) if response.status_code == 200: diff --git a/client.py b/client.py index 308ad879..c490b222 100644 --- a/client.py +++ b/client.py @@ -1,7 +1,10 @@ import requests import json -from config import ADMIN_USERNAME, ADMIN_PASSWORD, GET_INBOUNDS_URL +from config import ADMIN_USERNAME, ADMIN_PASSWORD, GET_INBOUNDS_URL, DATABASE_PATH import uuid +from auth import login_with_credentials +from datetime import datetime, timedelta +import aiosqlite def add_client(session, client_id: str, email: str, tg_id: str, limit_ip: int, total_gb: int, expiry_time: int, enable: bool, flow: str): url = 'https://solonet.pocomacho.ru:62553/solonet/panel/api/inbounds/addClient' @@ -48,7 +51,65 @@ def add_client(session, client_id: str, email: str, tg_id: str, limit_ip: int, t else: print(f"Ошибка при добавлении клиента: {response.status_code}, {response.text}") +import json +def extend_client_key(session, tg_id, client_id, email: str, new_expiry_time: int) -> bool: + # Получаем текущие данные клиента + response = session.get(f"https://solonet.pocomacho.ru:62553/solonet/panel/api/inbounds/getClientTraffics/{email}") + print(f"GET {response.url} Status: {response.status_code}") + print(f"GET Response: {response.text}") + + if response.status_code != 200: + print(f"Ошибка при получении данных клиента: {response.status_code} - {response.text}") + return False + + client_data = response.json().get("obj", {}) + print(client_data) -def generate_client_id(): - return str(uuid.uuid4()) + if not client_data: + print("Не удалось получить данные клиента.") + return False + + # Обновляем данные клиента + client_data['expiryTime'] = new_expiry_time + + # Формируем данные для обновления + payload = { + "id": 1, # Если id динамически изменяется, замените это значение + "settings": json.dumps({ + "clients": [ + { + "id": client_id, + "alterId": 0, + "email": client_data['email'], + "limitIp": 1, + # "totalGB": 2, + "expiryTime": new_expiry_time, + "enable": client_data['enable'], + "tgId": tg_id, + "subId": '', + "flow": 'xtls-rprx-vision' + } + ] + }) + } + + headers = { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + } + + try: + response = session.post(f"https://solonet.pocomacho.ru:62553/solonet/panel/api/inbounds/updateClient/{client_id}", json=payload, headers=headers) + print(f"POST {response.url} Status: {response.status_code}") + print(f"POST Request Data: {json.dumps(payload, indent=2)}") + print(f"POST Response: {response.text}") + + if response.status_code == 200: + return True + else: + print(f"Ошибка при продлении ключа: {response.status_code} - {response.text}") + return False + except Exception as e: + print(f"Ошибка запроса: {e}") + return False \ No newline at end of file diff --git a/handlers/__pycache__/keys.cpython-310.pyc b/handlers/__pycache__/keys.cpython-310.pyc index 5d50a89d45f9ac129745940175bb3839b92a0512..a374ce33836ba21656884d68b5f6769e4f231385 100644 GIT binary patch literal 4032 zcmbW4U2Ggz6@cf?&g{Jn zdFZV6-2XG@o_p{4?%4T!Mu6u^>(jXx%7XAWEc8AS6uu9C=Z_$CK_`Y_k}5I9sz|Ub z8i{HG(u5(IQdQ!0k|CRNRW_5=BrnTG%2cX~nXabIOf_R>t65&3G;(IXn&^1Y0@x_Qm2Ni zjYwg6p1L)y;WjVQ+9F-9QB!LgVHRuanxR<@=%AYoVI$CeYqbFS*pxYYn4G zEf-ach%5`XgoB1X-?VB=P4_~r!Qh3K+tdsP-l0?j$zzkVmD-ufvnLw_CmBDO7vX;#{{O}W_k{&m%SH5tI31=hHt7g35?btC5`t4wjcWb_t4k{+oWR`4NV_AK`)NY+J;>NM~z+3EZv~Y8N;EYt>sXe zFf_BSYe#eVB79>;U{66378;si)V0Q9?KO|GeAD&ch=Loyg}M2J?YEu3VO;{MNcP zR`%<${m#`TLInJshyN3M{&IIu>p4kq`%1|a@2-;dK&b`2d1g3FM;}QY0vm`Co3}*=;6-Sc;AyYrSB+zsw^-s!wKo|$Md zdel1zO%r37%>+M#9#?|fYVZJhJqYf1-VAOB_k%l~>*MM~{pgA`F;+k7y@<+w0j-xi zS30Ymwcu{&jo?mjM-4s>9;nd!GG^c{ZwJ4J^n+N(J7KPAIWF@eA3!*gTnHRtzI%l5 zgnD?ag5PvjVdRIwJj(wIYQ zrmfR3$7st~u9+HJ^jhAtsQtGp8r8Y1!rV4HSGwAR&EOBCpm!h|;@UNnI*v9^!Ry{P zapbQ%Z^2C82k(HBJ_aMBNyA*%z*n2-E3ArLjQYmak%2Hh$859aIh2KBQxDTx({^4n znl24f@vdM+P#>n~CE9?P9V&CwZCtP|%4DoBdTs+%;LdYg&1{7VIC2h)45Lw4KE*xp zBf88Tam;gF+j_Kiio1Xz`of%p0Xv>vnA*-{b|xIAE0-Fyh5I#3YL>G^*+`x}je}*r z4?|I}hslP=9GitnMjJM)KmtxcnC&?z-2yTrEHdh}-~hndM4k-?diO-F9vKd6hN~Qx z6wn#1FQGhgM`8#~InZKu160+zo2VP}F;+XrK!~e~D2ip0B$Ax_pC}iIBq~H9Wl|!A z1ePQs5#_T3vLqfL;_BhQAKH>u3(sW8SBbJJepZqgs@WCWd&$H4p%r+;z5*#5K(P-6 zLT1NQ&I>#`>;Q;e$_h|{^x~kX!-DRB;s#j}_n_j_&q2kXAA^d&JcKMS=Z1)|vT&I{VzB+rK6Pu1bCua3%XmKfNZmbAFEf$sO?FGPNddTgwp( zp!4N9aih1kyaR;Tp8SqI)<9fW=&4J`b$vT^d*|H)zJKn<*&amZwh?)QIO6WU)|UZ& z`)-isg8)CcSgw@fcNhIUBCyv24!r#9c@8{CW8md?=Es58KfO|k`PSvm5tZNkAn5@e z=UOkjA{cd1#OcGn1l#u^h9dS1<}l1cAcZ^Evg1ub;Su<#K#ku8n;aHEoXy}aWO+DN zgO4EcLmS5B_d7p{!as)EhzAg3M<42-F;aUA#(o$)KooIyJ%BKLA1DMBtJuUFK2Q-Q zo$FD^?&?9V?ZoQaqs?D!ZEdZLzj_8PoIu+iT+dd{?()e?>dR`(} zvpy6U9{J}4!ypOBu}!n-GK{4o1x_UPE$s4b6zBo=T@YcqyRSo;?`!rwtj+AhJznDe;;2?8;6COV^eD2^K?oKFh2+2$L$OSX LIdDnt;2`-w)IHr# delta 800 zcmZuvO=wd=5Z>9h?d+?<0zP6y?EHkq^yYtQP&AuJp$fN_$vyqhI z*!S|DxsyH)bZ7kJnqJTWQAA1IXcz?}+3R()VHQk43~!=>#jIY68r0lEyFe`lHM@*y zirQOb6ABK+b13J0Q_wja>tW|UYsJRxU^!%bCI_)Mm7mXF&Cgw1yp^AymI198gwk2m z4_SD#+F&J6$p@nLSn8zImTEXcHAG=aRlzfH>OqyY7nfM4B_W7}@TG^+OT=w4iZ6O8 zm8SA-#9hJg$T>GTAoO0{JeKeqpdf3|g_WHU_q34g!Uc3gk}!>)u8L3Au_J}97HK_W z_aNFO5#e{mXMFv#8>K2x0qSl;*ABf}I)cceUS!ksHrdAj)XdOK*E+nF)iSj#&2DQV zuusFPc&_Evp0ojwT6d%_pynxp!oJQMKwSsW$^h+)oM-3-{vXO=l?9ng<@-lYQ{VS( zXiK_!{fB+4VM^NiXI5U@Bman)BCnt-ev)_Mi~eztNN!@2J!B<0wb&}NuyikIGH!?u z#w_UKggK$P+!FWk~j7)h8XzlwY6;7$7V tdeJW}%`P+ENf7^P#~xQ%j(TWaY6Nt-&aSAh)yhhfMHaZpGO&w}!XK$UwlM$z diff --git a/handlers/keys.py b/handlers/keys.py index b545e2aa..2df51c0e 100644 --- a/handlers/keys.py +++ b/handlers/keys.py @@ -1,8 +1,11 @@ from aiogram import types, Router import aiosqlite -from config import DATABASE_PATH from bot import bot -from datetime import datetime +from datetime import datetime, timedelta +from database import get_active_key_email, get_balance, store_key, update_balance +from client import extend_client_key +from client import extend_client_key, login_with_credentials +from config import ADMIN_ID, ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_PATH router = Router() @@ -26,9 +29,14 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery): expiry_date = datetime.utcfromtimestamp(expiry_time / 1000).strftime("%Y-%m-%d %H:%M:%S") response_message = f"Ваш ключ:\n
{key}
\nДата окончания: {expiry_date}" - # Добавляем кнопку с инструкциями + # Кнопки для инструкций и продления instructions_button = types.InlineKeyboardButton(text='Инструкции по использованию', callback_data='instructions') - keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[instructions_button]]) + renew_button = types.InlineKeyboardButton(text='Продлить ключ', callback_data='renew_key') + + keyboard = types.InlineKeyboardMarkup(inline_keyboard=[ + [instructions_button], + [renew_button] + ]) await bot.send_message(tg_id, response_message, parse_mode="HTML", reply_markup=keyboard) else: @@ -40,3 +48,50 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery): await bot.send_message(tg_id, response_message, reply_to_message_id=callback_query.message.message_id) await callback_query.answer() + +@router.callback_query(lambda c: c.data == 'renew_key') +async def process_callback_renew_key(callback_query: types.CallbackQuery): + tg_id = callback_query.from_user.id + + try: + async with aiosqlite.connect(DATABASE_PATH) as db: + async with db.execute('SELECT client_id, email, expiry_time FROM connections WHERE tg_id = ?', (tg_id,)) as cursor: + record = await cursor.fetchone() + + if record: + client_id = record[0] + email = record[1] # Получаем email + expiry_time = record[2] + current_time = datetime.utcnow().timestamp() * 1000 + new_expiry_time = int((datetime.utcnow() + timedelta(days=30)).timestamp() * 1000) + + if expiry_time <= current_time: + await callback_query.message.answer("Ваш ключ уже истек и не может быть продлен.") + return + + # Проверка баланса + balance = await get_balance(tg_id) + if balance < 100: + await callback_query.message.answer("Недостаточно средств для продления ключа.") + return + + # Создаем сессию для API-запросов + session = login_with_credentials(ADMIN_USERNAME, ADMIN_PASSWORD) + + # Обновляем ключ через API + success = extend_client_key(session, tg_id, client_id, email, new_expiry_time) + + if success: + await update_balance(tg_id, -100) # Списание 100 рублей с баланса + await db.execute('UPDATE connections SET expiry_time = ? WHERE client_id = ?', (new_expiry_time, client_id)) + await db.commit() + await callback_query.message.answer("Ваш ключ был успешно продлен на месяц.") + else: + await callback_query.message.answer("Ошибка при продлении ключа.") + else: + await callback_query.message.answer("У вас нет ключей для продления.") + + except Exception as e: + await callback_query.message.answer(f"Ошибка при продлении ключа: {e}") + + await callback_query.answer() \ No newline at end of file diff --git a/key_management.py b/key_management.py index 98ca6ba7..5efb7877 100644 --- a/key_management.py +++ b/key_management.py @@ -8,7 +8,8 @@ from auth import login_with_credentials, link from client import add_client from datetime import datetime, timedelta from config import API_TOKEN, ADMIN_PASSWORD, ADMIN_USERNAME, ADMIN_CHAT_ID, DATABASE_PATH -from database import add_connection, has_active_key, get_active_key_email, get_balance, store_key +from database import add_connection, has_active_key, get_active_key_email, get_balance, store_key, update_balance +from client import extend_client_key import uuid import aiosqlite import re @@ -16,6 +17,8 @@ from bot import dp from handlers.start import start_command from bot import bot from handlers.profile import process_callback_view_profile +import asyncio + router = Router() @@ -49,6 +52,7 @@ async def process_callback_create_key(callback_query: types.CallbackQuery, state @dp.message() async def handle_text(message: types.Message, state: FSMContext): current_state = await state.get_state() + print(f"Received message: {message.text}, Current state: {current_state}") if message.text == "Мой профиль": # Создайте фейковый объект CallbackQuery для вызова функции @@ -121,7 +125,15 @@ async def handle_admin_confirmation(callback_query: CallbackQuery, state: FSMCon limit_ip = 1 total_gb = 0 current_time = datetime.utcnow() - expiry_time = int((current_time + timedelta(days=30)).timestamp() * 1000) + + # Определяем время истечения ключа + if await has_active_key(tg_id): + # Если есть активный ключ, устанавливаем срок действия на 30 дней + expiry_time = int((current_time + timedelta(days=30)).timestamp() * 1000) + else: + # Первый ключ устанавливаем на 1 день + expiry_time = int((current_time + timedelta(days=1)).timestamp() * 1000) + enable = True flow = "xtls-rprx-vision" balance = 0 @@ -151,7 +163,6 @@ async def handle_admin_confirmation(callback_query: CallbackQuery, state: FSMCon await callback_query.answer() - @dp.callback_query(F.data == 'instructions') async def handle_instructions(callback_query: CallbackQuery): instructions_message = ( @@ -169,3 +180,28 @@ async def handle_instructions(callback_query: CallbackQuery): parse_mode='Markdown' ) await callback_query.answer() + +async def renew_expired_keys(): + while True: + current_time = datetime.utcnow() + async with aiosqlite.connect(DATABASE_PATH) as db: + async with db.execute('SELECT tg_id, client_id FROM connections WHERE expiry_time <= ?', (int(current_time.timestamp() * 1000),)) as cursor: + expired_keys = await cursor.fetchall() + + for tg_id, client_id in expired_keys: + balance = await get_balance(tg_id) + if balance >= 100: + new_expiry_time = int((current_time + timedelta(days=30)).timestamp() * 1000) + + async with aiosqlite.connect(DATABASE_PATH) as db: + await db.execute('UPDATE connections SET expiry_time = ? WHERE tg_id = ? AND client_id = ?', (new_expiry_time, tg_id, client_id)) + await db.commit() + + await update_balance(tg_id, -100) + await extend_client_key(client_id) + + print(f"Ключ для клиента {client_id} продлен на месяц и списано 100 рублей.") + else: + print(f"Недостаточно средств на балансе для клиента {client_id}.") + + await asyncio.sleep(3600) \ No newline at end of file diff --git a/path_to_your_database b/path_to_your_database new file mode 100644 index 00000000..e69de29b From f8015ced70bd26c8108dcd9d8965a2b8496c72ca Mon Sep 17 00:00:00 2001 From: Vlad Date: Fri, 20 Sep 2024 03:48:41 +0300 Subject: [PATCH 03/12] =?UTF-8?q?=D1=80=D0=B5=D0=B4=D0=B8=D0=B7=D0=B0?= =?UTF-8?q?=D0=B9=D0=BD,=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BA=D0=B8=20=D0=BD?= =?UTF-8?q?=D0=B0=D0=B7=D0=B0=D0=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client.py | 27 ++- database.py | 8 +- handlers/__pycache__/keys.cpython-310.pyc | Bin 4032 -> 6180 bytes handlers/__pycache__/pay.cpython-310.pyc | Bin 4673 -> 8690 bytes handlers/__pycache__/profile.cpython-310.pyc | Bin 2680 -> 2420 bytes handlers/__pycache__/start.cpython-310.pyc | Bin 4654 -> 5584 bytes handlers/keys.py | 141 +++++++++---- handlers/pay.py | 182 +++++++++++++---- handlers/profile.py | 45 ++-- handlers/start.py | 85 ++++++-- key_management.py | 204 ++++++++----------- 11 files changed, 446 insertions(+), 246 deletions(-) diff --git a/client.py b/client.py index c490b222..7b699364 100644 --- a/client.py +++ b/client.py @@ -69,10 +69,17 @@ def extend_client_key(session, tg_id, client_id, email: str, new_expiry_time: in if not client_data: print("Не удалось получить данные клиента.") return False + + # Получаем текущий срок действия + current_expiry_time = client_data.get('expiryTime', 0) - # Обновляем данные клиента - client_data['expiryTime'] = new_expiry_time - + # Если нет текущего срока, используем новый срок + if current_expiry_time == 0: + current_expiry_time = new_expiry_time + + # Определяем новый срок окончания + updated_expiry_time = max(current_expiry_time, new_expiry_time) + # Формируем данные для обновления payload = { "id": 1, # Если id динамически изменяется, замените это значение @@ -81,14 +88,14 @@ def extend_client_key(session, tg_id, client_id, email: str, new_expiry_time: in { "id": client_id, "alterId": 0, - "email": client_data['email'], - "limitIp": 1, - # "totalGB": 2, - "expiryTime": new_expiry_time, - "enable": client_data['enable'], + "email": email.lower(), # Приведение email к нижнему регистру + "limitIp": 2, + "totalGB": 429496729600000, + "expiryTime": updated_expiry_time, + "enable": True, "tgId": tg_id, - "subId": '', - "flow": 'xtls-rprx-vision' + "subId": "", + "flow": "xtls-rprx-vision" } ] }) diff --git a/database.py b/database.py index 68e7752d..eae9071c 100644 --- a/database.py +++ b/database.py @@ -89,4 +89,10 @@ async def update_balance(tg_id: int, amount: float): SET balance = balance + ? WHERE tg_id = ? ''', (amount, tg_id)) - await db.commit() \ No newline at end of file + await db.commit() + +async def get_key_count(tg_id: int) -> int: + async with aiosqlite.connect(DATABASE_PATH) as db: + async with db.execute('SELECT COUNT(*) FROM keys k JOIN connections c ON k.client_id = c.client_id WHERE c.tg_id = ?', (tg_id,)) as cursor: + count = await cursor.fetchone() + return count[0] if count else 0 diff --git a/handlers/__pycache__/keys.cpython-310.pyc b/handlers/__pycache__/keys.cpython-310.pyc index a374ce33836ba21656884d68b5f6769e4f231385..db258c309a002ec63c7261460326dbb5bd03a626 100644 GIT binary patch literal 6180 zcmb7IU2NOd73QTViION;mSx9r)`n>7ElLx+>Cd)$Xewq;9{ zdr2m-K^0CNwB6eLtlh)VBCWi=tmqz+B3aX9NjD6`UiLukk=WZhpa%9f6a%s!@0?4C zlI)~tCGe2Gi={v|0QW#iDoa_3=SihtIhYNWL)j2d2TS3yoR!OwY=oynrD!>pjg^(G!qeeW zyqw4;%3ay6ax$AFf+nsA*>0`7mZCC^oEBCIjcR1BXIi8&s+^WqNH)z&;~Pp7w2PPa z^3vpn(r%jKrG0*DJv4n<%5I^(wC}W#?Wf|TklC^ZBNZ~D8#EUeG{X%YsaU4QCNjhg zOjS%MkZIo3%wkz{BWS5sGV_ofo6*eNRKAqg3!0m-7O++`4aMEsNmJ8lu23p!x|y5T z7Tvy5Wu~a-P87}AT!BGDx>?MZj7-2yjP4&lIFWmPa_q>&==hi$-#>bE^vTi5vE1R& zqX(MBheszTj~zO)zd+FC#t)xGxSoRRpZIZYVGh3AJdRDAa3jZy+6nC4_<*<)_?U+~ zDSRNXG}OJdV|cbw)`pLl@^pB=c6@lUQmW)&*u%4Vot89a4CAXq3yZG2x0EkW(fmFI zJHj5L0_%gDn<(TNpsSpi&shI$k&ycFPbk+>kLS}fCJS-f9h&&*?bFvz+12u>on@p2c(a%3t zm29aNS#8v-2JIlz?cj@IHB=3o(OS$7)95ycwyF#%WmTv~Y}t70R*2^r5zORVw@qw? z_Shm#pOKpR$G~$5{wslIjvm|7@Y8tv1R(!cxlY zfxZRWYsYZ}t-g*y%M|Lo9M^;1{RBAyaZ^h-=L**;#82OZ^)P%m=jYCi`lRp)B`6h1iD>u)+A!~G-YqH_as&$>!cH%w*~%wk11J`i2WgazTo z{aC?Jw6AdA! ztiSHhnJEbktq z>fnKq!SRv7$)(`l;i-L5>u&7emnwek`Y9D1s=wlQh29Oh(t)Gn&%$SqF65b^<;oSR zxeC)3N{hL2p3Pee)+5-+d-c~Lnl6C@;K!#B2wq^ppjN=0D;P_d)rbgh;F(@GGR-RG zoMmXt6^oQ18oB9%JO-X=gupoOWn=D>mT6XWH!Wr+1@&JkOHA2eVBnB(Kcie=>B z1tUMBF{D;kF_04)S#D(PWIOD$EV#b>oD_G6~j?Zv}a*xNtjn}Qg;6&ErEaV(to;NsldC{=SWjk-ry}TeuB1(yZnyvF`;}yxu>i{!zMJQ~ zOEk_Gp6NxO24M9Spfu;NXrF0tCXEAN#AR8O0tteD`M+W)O+rKpD*qOP2_lIy=8`Ds zgD06t>!K+CPYNc)0kU&#$GWtf7`Ttf%i?+}z@CQ2I(Vg>^%#;2a2>k~?ktHWg$CD0 z%VEw{-^HvgU^*I$Lk0p2C#k=7kkr!eO|cd*r8c_xuqxVOEl9}*y7>vGp->Y-k$l*@ zw)A448svl%uF0zc(h(KYLRGfIwmeVR^FW6{MKrMLlSbq=pud?0FlSOQmAN=v2~L|K zPNJ9!!$zu=iqNQ+0uqe@iAI2=NRy$b%izT?Vu>vQ7cA)o=+sAEo55&wD<6t&`tqL^x8|O+J zCc`6#N!9r+AT5+3US6ud;qe{fo=1QHcth3$*y1Kqya5Qg2j9N}JaDf5s=8y(0}rU2 zk}nNq+_=Y;djM&5bCwN28`gt&Z0Mo3hM?UY4?fb?RKjcOA+ITeA=TdvQUqQe|*1_Gtbz~0@#QniQ>A8`p^HWY>P+UL7sl^(FHk!V3N&zwg+TdLG5kUXh z`i}wlKkZc8g4ahGp`%Pn`Eiz8~LkL|#> zGiY|9!6rBzupxXJMuRNC_JHw3fX4yhCbn~2K^9^=(cF(Fj0V3Ydk76~q&NpKBtG_S zFqt@qclIb|d=JfDG>?ICBYteUK^~h7=e>pE$Te&Pv%(Dx!uI3KV`w^&?>12FZ>-R9 zAF4$$P5J?pB{52pL>5y-B&mP(uS@YJgeO8Gekh6@w}sR0E*jZm^mJc$AXhTFW1$hx7_jl$eA(?FGUM;!kVR5WnDmCnA9nS3Pw z4^(K$4d@k+9DAkWR-p=#!<1}sg#Z=@&Xc84bAzlnPu|IJ0U=YV6X~HOgkqviT7>M- zdBV2aAsU3X!*ItwWVnam9zn4I*bfqehM~Wx9kHW;{W|y*2O%N1Q0!}Azhonl2WaFB zsU^L(w}AMl#z1UD=S6mk#z3|xAWA@l_;??;4wbwn=Sy$f<;_a^UWYGks zOLO_=c{E_BIn(*0Z!lf@jlzBH78nZw-+3Q+iOV}K>bN+&)WUMN8#nZtUNmN#2OpexJjUpP20X^# z6nOyv{xTOapgDjuZo>Zl21rQ2d_ZL9vWNYsZ?4zR-G)YdE^samfx{?dmPVdG3B5i~LwP&1Ws zxoB==$lex1y3zuiwYdo{SIkPzJ73$tn+#)(O`t)(WQVZrE~J;Xot-NcDxlX6V;M*t zKZHGP7~@A-Q#{dNSOatpinOp{6;SisnQFIfQbiuQq3fKqaDkyLN)iQlwvP+|--fsV zl3SenMgRIhS2Bidc$W8LApQOc8dL akRkSx{11X$R01iY+z;1aiX`_b!T$qk-6Ssn literal 4032 zcmbW4U2Ggz6@cf?&g{Jn zdFZV6-2XG@o_p{4?%4T!Mu6u^>(jXx%7XAWEc8AS6uu9C=Z_$CK_`Y_k}5I9sz|Ub z8i{HG(u5(IQdQ!0k|CRNRW_5=BrnTG%2cX~nXabIOf_R>t65&3G;(IXn&^1Y0@x_Qm2Ni zjYwg6p1L)y;WjVQ+9F-9QB!LgVHRuanxR<@=%AYoVI$CeYqbFS*pxYYn4G zEf-ach%5`XgoB1X-?VB=P4_~r!Qh3K+tdsP-l0?j$zzkVmD-ufvnLw_CmBDO7vX;#{{O}W_k{&m%SH5tI31=hHt7g35?btC5`t4wjcWb_t4k{+oWR`4NV_AK`)NY+J;>NM~z+3EZv~Y8N;EYt>sXe zFf_BSYe#eVB79>;U{66378;si)V0Q9?KO|GeAD&ch=Loyg}M2J?YEu3VO;{MNcP zR`%<${m#`TLInJshyN3M{&IIu>p4kq`%1|a@2-;dK&b`2d1g3FM;}QY0vm`Co3}*=;6-Sc;AyYrSB+zsw^-s!wKo|$Md zdel1zO%r37%>+M#9#?|fYVZJhJqYf1-VAOB_k%l~>*MM~{pgA`F;+k7y@<+w0j-xi zS30Ymwcu{&jo?mjM-4s>9;nd!GG^c{ZwJ4J^n+N(J7KPAIWF@eA3!*gTnHRtzI%l5 zgnD?ag5PvjVdRIwJj(wIYQ zrmfR3$7st~u9+HJ^jhAtsQtGp8r8Y1!rV4HSGwAR&EOBCpm!h|;@UNnI*v9^!Ry{P zapbQ%Z^2C82k(HBJ_aMBNyA*%z*n2-E3ArLjQYmak%2Hh$859aIh2KBQxDTx({^4n znl24f@vdM+P#>n~CE9?P9V&CwZCtP|%4DoBdTs+%;LdYg&1{7VIC2h)45Lw4KE*xp zBf88Tam;gF+j_Kiio1Xz`of%p0Xv>vnA*-{b|xIAE0-Fyh5I#3YL>G^*+`x}je}*r z4?|I}hslP=9GitnMjJM)KmtxcnC&?z-2yTrEHdh}-~hndM4k-?diO-F9vKd6hN~Qx z6wn#1FQGhgM`8#~InZKu160+zo2VP}F;+XrK!~e~D2ip0B$Ax_pC}iIBq~H9Wl|!A z1ePQs5#_T3vLqfL;_BhQAKH>u3(sW8SBbJJepZqgs@WCWd&$H4p%r+;z5*#5K(P-6 zLT1NQ&I>#`>;Q;e$_h|{^x~kX!-DRB;s#j}_n_j_&q2kXAA^d&JcKMS=Z1)|vT&I{VzB+rK6Pu1bCua3%XmKfNZmbAFEf$sO?FGPNddTgwp( zp!4N9aih1kyaR;Tp8SqI)<9fW=&4J`b$vT^d*|H)zJKn<*&amZwh?)QIO6WU)|UZ& z`)-isg8)CcSgw@fcNhIUBCyv24!r#9c@8{CW8md?=Es58KfO|k`PSvm5tZNkAn5@e z=UOkjA{cd1#OcGn1l#u^h9dS1<}l1cAcZ^Evg1ub;Su<#K#ku8n;aHEoXy}aWO+DN zgO4EcLmS5B_d7p{!as)EhzAg3M<42-F;aUA#(o$)KooIyJ%BKLA1DMBtJuUFK2Q-Q zo$FD^?&?9V?ZoQaqs?D!ZEdZLzj_8PoIu+iT+dd{?()e?>dR`(} zvpy6U9{J}4!ypOBu}!n-GK{4o1x_UPE$s4b6zBo=T@YcqyRSo;?`!rwtj+AhJznDe;;2?8;6COV^eD2^K?oKFh2+2$L$OSX LIdDnt;2`-w)IHr# diff --git a/handlers/__pycache__/pay.cpython-310.pyc b/handlers/__pycache__/pay.cpython-310.pyc index e6aecc6131e2e42031651bf7c217c6cbebe30f2e..78eea876806acff4b90e65e08a43eb6beb293f06 100644 GIT binary patch literal 8690 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 diff --git a/handlers/__pycache__/profile.cpython-310.pyc b/handlers/__pycache__/profile.cpython-310.pyc index 17b82b22256b9106088f1a4e3723612d5f1d806d..a59a06fb99098030a88b72ee01aff68155fbb456 100644 GIT binary patch delta 945 zcmY*X+e;Kt7(eICW#_tH(jbG{mwk|sE-aCvVmDB_ffk`XEUsr<*4kN`J%cGTsa**) zkqL@I()S*=OwuBI=pQ)yI*&n51@+KF4_bX^jV|-?edoLVzRU1^Z~E3~mu%ZW@U5(W z9vyma`#5Rm9hSZ9Ok_t&zROk?@IE|cL54_s4_sC;LNho(V`vPAWbzy%0lA6$5JkNx zl$i97B?mG^KJr>94rK~?PY{&?2_!^7DokEdmeG<*)nyEJVo?tjmY^EdsWC@F6?BtY zQ#B(2hE0<-T?4(2*4K2MX@Q!@K>}C<*7Gew6o93jxsoHKB0!!91+TIj{ZveG6pY`B( zAioDhXRez+txofe>Mg#;-&AK){2kvA3V#Lf3tp+-;HzNIcka)18^+hC57tuv>O0XP z94Y?*6#3)jj|G;mRqyht)#?mDU+|3-e+~Hi;9KSEzfrsYLoM(Mpep=rb>?^c5X4jc z7mod{{Gc{TB?Yst@)c+S)j_XR@4(z^+px)o^k7@M*p{ZLwBFm1KGTsNh;ejDd34-w zfQlcU-TtyRpW=(q>lN_J*vbu$T+Xsmw&=KiD~{_5?v;Y;h+E2)hCS+^60pUG0v4I- zNnP$ef97~Zao zHL^+9BJG7DkYCWMYKiy<1{2b>Y88lozk4BnXZ--(a_H&d3#Fu=zuMAK> zKR}c#p5$1wiUmc*{Nl+<}*dn-SfkgS!;PNyY;C_J$14ywhhsCoO$FE2%fCNv2{z_7Tkm0^13A2U83;DUe~84|1X67pX$? zRNW(C9(auwGF}B7dxmxW zn$5PVzTL8nH<#aCGHR~lSTH88;~A?9mF0@zH>x%@E*P`Mr7QFQakyyA+P`oJF_;|G z9V@S$E4R*-sZm~-DKE{GSK1RPp4-Fi_-ObUKsg+KZYav){EX4ggGMwUaw`xbbPDre~rk>srF zR;>+U&USnzry{ZK*BrN#$Z4}Y-)vn=q`KwTHeAO_il9t0J)!e_rqt6rON)(O7oYuz zTX+3L^(=>0A%tf(tb|ViC!*j#Wv;Dp-OKkou?~D!7QHZSq(WAX-N1U*jU1i+Z_r?J^|t2y6;s^U=fH r1w0qMES=XgIiH~@5mt#@sU@&H?4_QaMKIee zq}A|JwTzD-MM(_C2nG!(;RT<3Tl8%n^-&W^v=2rM{{Pu_>28d1H|N_k=l|zi{_p$$ zvtO*Q4n_*$a6p02raQk*{Lwcb=_See!Rbn_bF7mnOkrlWE7v{No$DIwA_`Y$l(C+@ z3iGqTtil33HlvR9vLM$o!3oMjEIg}?_3@sCeimUZv&tpHqC5bWR@OGF&5*G*($?N! zi?LPG7MHdTX?s<^$X4^#35|8KF6poq90quAW*tl`mt3-UvmR+*FYP+(ogvZ#x75e_ zrELRSb3{qR-Cg9zb*kszIXYs!d}#l0(ldANIXIHEM)r8Y-TQVXlfwtBqlXW;gLGc+ zfv|`k_YU2>tpmCtXhjXcR02fch-c(@v1m{76oIGZj|m+e+IP^3m)$}2@d^aYAuKRQ zr+p4B(@9cRp=r>3lVnn3!8tOmJF25$2%cEg75U7WuhcR$eA4PBw0=J1n_7f#vP z?6{peZZX@j7lYF4!*lWKNAXuilb+umFO(eX#8iGUE=}%*xO+9ybIu6tU&JWfyXbx# zuU@ELyL&cX{kD3odK=oUD^v`C(Njwzdxdx{>3lYw=hkt4YP?_zR>CM%&sV>$-oE=@ zf`|cki?NjqxH)6*;1ghyFN>$3m+%V%2ig(vErB}}$c9+udPc8miaz&GW2E9m#!HS< z$Xlsnc0SLu;xX{?B7#q4Pt_L|HCFNMUNnb`jx8K3RmkP+JZpiW;x+(!no{~8K*St@S09dG||D~Hcq4FELuN%61)ilYFd(+&!bpy-*ht@El@Jmw7tC~YuJ2%ek z?YP$O6d<68U2eZW{u(OG2TjW7Md+L%j7$?pVU(#%15HqepoTyrKG2N{8lV{!w4s+( zW<2D-tSo4klm<1J*DL<>q_Wrw)Bg1NJHOq0UlLcn0gQd+g`^0af|cX>l2{8qVh~|7 z0MPj)_9Y{}8Y7a7J{a})S4MTGv^+TDhq)o!u-k7XYP^E zCzTyj1t=PJ>R3Sx!(M74YfPFNJH#_eMJ}u~O9Yz|M{?x(i`=o~apkQdP~2XyGzHaz z%bM)@CqyA<;Vfbct_7@Cl!UKF>mhUxPjJp^TtQ72`_bzFLZjHdmL*!3-7(-rEQIv5 zMnmdSF8S&l>K&wwh@;@pK!vWjzl3j*(d(Z^-X#?*^kNml(+F({C_K+Ud@{wSoOB`Y z`KClV?|8mcmfHdpDScrQ=3_(!Px15#ZlSLa&wbYYGMcEo zjuW7Cgwv48ct4|*WKoSl=L{6ovNBU=T3(hU2{+LYSm8(RuPJ!`RE{B0!Z{C4^1lE* z-A2V5@RUq!^bGgs==T+QQORiFLY^+*`UK+es+5!0SUZT1NdydTZ8;=f;1T4EE6yzU zA+IZQC|6*mEZ>eNDn1I(sf0*e-cnH#BT>?=(7%Q6y7(xS=;ONPi!akAFk6vi;0vpAk;!js;Y$&m6!++NS4ZKlbN`2{Zqy^ z5Xm8J6Tt-^(&31zM4Wm-g|q^RHWVRoK}c(H+Zz%WK;pugmN%RKva5MB^WK}8_kHg@ zXZXt}!;MhLQ1D9+{W(ixE8#4}R)!ZAL7@uuSCh3=DOF3BlAthkNhzhzD>OijWrZ3n zv80wVG|03{aF$S$hL*KbmZevppkW$WR@Q(M(%4$wi* zeh}?>mZ=Qk$&7dHmcs#>^c)V+)SF5nEk1%@2ZS%lbmnM#;hQhBHzz4*nnhN`HtK;}l+HQ*90)t{Clw5=kDMLrap_QSm z(m)p$`dXx=QiBG&PzGF)%~jb$+$9ToN2zF)K9qXJ*8v*t^wCIHn?U|vRY2KmrO}Q~ z`@8D4jVcHyR&0Mm6y+J`GiUv%wodxmGHCI;et5J%T&=}EY`LKsyIP&LXD(aRZrN=a zxpOoB_Egag*wc-6%XOYdU%vB4j-Ut6N%1s_f}cy0xL=B(6p}}t5EuPf)yK1<xmf`Ip`qX%018Hg8!ojatpFQyE9|1PXGM5c0?jm|y}S8pz-OFVPKR z0C_0J0vQ++lY!C2JUZV3SCi0jhMC77@Sm#MwP|XP%WMBZE)_%J$8mDQJG{NuW_*4I zP`Uj{KUo!C35?+48c`#P8$tzNSLOci`xU`ReH&B0V3q0Kgzlj}I zu>~sH8f@e5z;?k?t-GsL{>M>YvHgdzB3}^ahRDTIcHa4_x8&S$?)I+dosHgg=lkAO zEXJ>~9PeUjZsc)u$GPWx>)h`xjd;}Z7cn%fXkWX@xHU(yFeS(QBnmgsWGxHptHrqN z8lDmtquC3?^28?hd|ayc6X8a-D4%7F%C3^6Y%#8rZMFv?2K=n}JNgTpUccA>F${RU zk&yQlq=-pzQoIvCIOqi=UvdLpl(JO$Y4K@172NZ*xE&vWDe+Uh;Cl^yi-p6Yn)rFq zyD=^g>l}NT@l7aW@_Jc5UfTItx$@pg$(6id4=nE$uiN~mY7N@1vT<4KP1&DG#e_UI XnF&$v*GZH_RkIKUGn4>B4W!{ewH{^k diff --git a/handlers/keys.py b/handlers/keys.py index 2df51c0e..689b49da 100644 --- a/handlers/keys.py +++ b/handlers/keys.py @@ -2,16 +2,23 @@ from aiogram import types, Router import aiosqlite from bot import bot from datetime import datetime, timedelta -from database import get_active_key_email, get_balance, store_key, update_balance -from client import extend_client_key +from database import get_balance, update_balance from client import extend_client_key, login_with_credentials -from config import ADMIN_ID, ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_PATH +from config import ADMIN_USERNAME, DATABASE_PATH, ADMIN_PASSWORD router = Router() +# Обработка запроса на просмотр ключей @router.callback_query(lambda c: c.data == 'view_keys') async def process_callback_view_keys(callback_query: types.CallbackQuery): tg_id = callback_query.from_user.id + + # Создаем клавиатуру + keyboard = types.InlineKeyboardMarkup(inline_keyboard=[ + [types.InlineKeyboardButton(text='Инструкции по использованию', callback_data='instructions')], + [types.InlineKeyboardButton(text='Продлить ключ', callback_data='renew_key')], + [types.InlineKeyboardButton(text='Назад', callback_data='view_profile')] + ]) try: async with aiosqlite.connect(DATABASE_PATH) as db: @@ -22,33 +29,30 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery): WHERE c.tg_id = ? ''', (tg_id,)) as cursor: record = await cursor.fetchone() - + if record: - key = record[0] - expiry_time = record[1] - expiry_date = datetime.utcfromtimestamp(expiry_time / 1000).strftime("%Y-%m-%d %H:%M:%S") - response_message = f"Ваш ключ:\n
{key}
\nДата окончания: {expiry_date}" - - # Кнопки для инструкций и продления - instructions_button = types.InlineKeyboardButton(text='Инструкции по использованию', callback_data='instructions') - renew_button = types.InlineKeyboardButton(text='Продлить ключ', callback_data='renew_key') - - keyboard = types.InlineKeyboardMarkup(inline_keyboard=[ - [instructions_button], - [renew_button] - ]) - - await bot.send_message(tg_id, response_message, parse_mode="HTML", reply_markup=keyboard) + key, expiry_time = record + expiry_date = datetime.utcfromtimestamp(expiry_time / 1000) + current_date = datetime.utcnow() + days_left = (expiry_date - current_date).days + + days_left_message = f"Осталось дней: {days_left}" if days_left > 0 else "Ключ истек." + response_message = (f"Ваш ключ:\n
{key}
\n" + f"Дата окончания: {expiry_date.strftime('%Y-%m-%d %H:%M:%S')}\n" + f"{days_left_message}") + else: response_message = "У вас нет ключей." - await bot.send_message(tg_id, response_message, reply_to_message_id=callback_query.message.message_id) - + + await delete_previous_message(callback_query) + await bot.send_message(tg_id, response_message, parse_mode="HTML", reply_markup=keyboard) + except Exception as e: - response_message = f"Ошибка при получении ключей: {e}" - await bot.send_message(tg_id, response_message, reply_to_message_id=callback_query.message.message_id) - + await handle_error(tg_id, callback_query, f"Ошибка при получении ключей: {e}") + await callback_query.answer() +# Обработка запроса на продление ключа @router.callback_query(lambda c: c.data == 'renew_key') async def process_callback_renew_key(callback_query: types.CallbackQuery): tg_id = callback_query.from_user.id @@ -57,22 +61,71 @@ async def process_callback_renew_key(callback_query: types.CallbackQuery): async with aiosqlite.connect(DATABASE_PATH) as db: async with db.execute('SELECT client_id, email, expiry_time FROM connections WHERE tg_id = ?', (tg_id,)) as cursor: record = await cursor.fetchone() - + if record: - client_id = record[0] - email = record[1] # Получаем email - expiry_time = record[2] + client_id, email, expiry_time = record current_time = datetime.utcnow().timestamp() * 1000 - new_expiry_time = int((datetime.utcnow() + timedelta(days=30)).timestamp() * 1000) if expiry_time <= current_time: await callback_query.message.answer("Ваш ключ уже истек и не может быть продлен.") return + # Создаем клавиатуру для выбора плана продления + keyboard = types.InlineKeyboardMarkup(inline_keyboard=[ + [types.InlineKeyboardButton(text='Продлить на 1 месяц (100 руб.)', callback_data='renew_1_month')], + [types.InlineKeyboardButton(text='Продлить на 3 месяца (250 руб.)', callback_data='renew_3_months')], + [types.InlineKeyboardButton(text='Назад', callback_data='view_profile')] + ]) + + balance = await get_balance(tg_id) + response_message = (f"Выберите план продления:\n" + f"Баланс: {balance} руб.\n" + f"Действующий ключ истекает {datetime.utcfromtimestamp(expiry_time / 1000).strftime('%Y-%m-%d %H:%M:%S')}") + + await delete_previous_message(callback_query) + await bot.send_message(tg_id, response_message, parse_mode="HTML", reply_markup=keyboard) + + except Exception as e: + await callback_query.message.answer(f"Ошибка при выборе плана: {e}") + + await callback_query.answer() + +# Обработка выбора плана продления +@router.callback_query(lambda c: c.data.startswith('renew_')) +async def process_callback_renew_plan(callback_query: types.CallbackQuery): + tg_id = callback_query.from_user.id + plan = callback_query.data.split('_')[1] # '1' или '3' + days_to_extend = 30 * int(plan) # 30 дней или 90 дней + + try: + async with aiosqlite.connect(DATABASE_PATH) as db: + async with db.execute('SELECT client_id, email, expiry_time FROM connections WHERE tg_id = ?', (tg_id,)) as cursor: + record = await cursor.fetchone() + + if record: + client_id = record[0] + email = record[1] + expiry_time = record[2] + current_time = datetime.utcnow().timestamp() * 1000 + + if expiry_time <= current_time: + await callback_query.message.answer("Ваш ключ уже истек и не может быть продлен.") + return + + # Рассчитываем новый срок окончания, добавляя дни в зависимости от выбранного плана + new_expiry_time = int(expiry_time + timedelta(days=days_to_extend).total_seconds() * 1000) + + # Определяем стоимость продления + cost = 100 if plan == '1' else 250 + # Проверка баланса balance = await get_balance(tg_id) - if balance < 100: - await callback_query.message.answer("Недостаточно средств для продления ключа.") + if balance < cost: + replenish_button = types.InlineKeyboardButton(text='Пополнить баланс', callback_data='replenish_balance') + back_button = types.InlineKeyboardButton(text='Назад', callback_data='view_keys') + keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[replenish_button], [back_button]]) + + await callback_query.message.answer("Недостаточно средств для продления ключа.", reply_markup=keyboard) return # Создаем сессию для API-запросов @@ -82,16 +135,30 @@ async def process_callback_renew_key(callback_query: types.CallbackQuery): success = extend_client_key(session, tg_id, client_id, email, new_expiry_time) if success: - await update_balance(tg_id, -100) # Списание 100 рублей с баланса + await update_balance(tg_id, -cost) # Списание средств с баланса await db.execute('UPDATE connections SET expiry_time = ? WHERE client_id = ?', (new_expiry_time, client_id)) await db.commit() - await callback_query.message.answer("Ваш ключ был успешно продлен на месяц.") + response_message = f"Ваш ключ был успешно продлен на {days_to_extend // 30} месяц(-)." + back_button = types.InlineKeyboardButton(text='Назад', callback_data='view_keys') + keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]]) + await bot.send_message(tg_id, response_message, reply_markup=keyboard) else: - await callback_query.message.answer("Ошибка при продлении ключа.") + await bot.send_message(tg_id, "Ошибка при продлении ключа.") else: - await callback_query.message.answer("У вас нет ключей для продления.") + await bot.send_message(tg_id, "У вас нет ключей для продления.") except Exception as e: - await callback_query.message.answer(f"Ошибка при продлении ключа: {e}") + await bot.send_message(tg_id, f"Ошибка при продлении ключа: {e}") - await callback_query.answer() \ No newline at end of file + await callback_query.answer() + + +# Удаление предыдущего сообщения +async def delete_previous_message(callback_query: types.CallbackQuery): + if callback_query.message.message_id: + await bot.delete_message(chat_id=callback_query.from_user.id, message_id=callback_query.message.message_id) + +# Обработка ошибок +async def handle_error(tg_id, callback_query, message): + await delete_previous_message(callback_query) + await bot.send_message(tg_id, message) diff --git a/handlers/pay.py b/handlers/pay.py index bf99bba6..cb6d4376 100644 --- a/handlers/pay.py +++ b/handlers/pay.py @@ -4,111 +4,209 @@ from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup import aiosqlite from config import DATABASE_PATH, ADMIN_ID -from database import get_balance, update_balance +from database import get_balance, update_balance, get_key_count from bot import bot +from handlers.profile import process_callback_view_profile router = Router() +# Определение состояний class ReplenishBalanceState(StatesGroup): choosing_transfer_method = State() + choosing_amount = State() waiting_for_admin_confirmation = State() +async def send_message_with_deletion(chat_id, text, reply_markup=None, state=None, message_key='last_message_id'): + """ + Отправляет сообщение и удаляет предыдущее, если оно существует. + """ + if state: + try: + state_data = await state.get_data() + previous_message_id = state_data.get(message_key) + + if previous_message_id: + await bot.delete_message(chat_id=chat_id, message_id=previous_message_id) + + sent_message = await bot.send_message(chat_id=chat_id, text=text, reply_markup=reply_markup) + await state.update_data({message_key: sent_message.message_id}) + + except Exception as e: + print(f"Ошибка при удалении/отправке сообщения: {e}") + return None + + return sent_message + @router.callback_query(lambda c: c.data == 'replenish_balance') async def process_callback_replenish_balance(callback_query: types.CallbackQuery, state: FSMContext): + tg_id = callback_query.from_user.id + + key_count = await get_key_count(tg_id) + if key_count <= 0: + await send_message_with_deletion( + tg_id, + "У вас нет ключей. Пополнение баланса возможно только при наличии ключа.", + state=state, + message_key='key_error_message_id' + ) + create_key_button = InlineKeyboardButton(text='Создать ключ', callback_data='create_key') + keyboard = InlineKeyboardMarkup(inline_keyboard=[[create_key_button]]) + + await callback_query.message.edit_text( + text="Пожалуйста, создайте ключ для продолжения.", + reply_markup=keyboard + ) + return + await state.set_state(ReplenishBalanceState.choosing_transfer_method) keyboard = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text='По реквизитам', callback_data='transfer_method_requisites')] + [InlineKeyboardButton(text='По реквизитам', callback_data='transfer_method_requisites')], + [InlineKeyboardButton(text='⬅️ Назад', callback_data='back_to_profile')] ]) - await bot.send_message(callback_query.from_user.id, "Выберите метод перевода:", reply_markup=keyboard) - await callback_query.answer() # Уведомляем Telegram, что запрос обработан + + await callback_query.message.edit_text( + text="Выберите метод перевода:", + reply_markup=keyboard + ) + await callback_query.answer() + +@router.callback_query(lambda c: c.data == 'back_to_profile') +async def back_to_profile_handler(callback_query: types.CallbackQuery, state: FSMContext): + await process_callback_view_profile(callback_query, state) @router.callback_query(lambda c: c.data.startswith('transfer_method_')) async def process_transfer_method_selection(callback_query: types.CallbackQuery, state: FSMContext): data = callback_query.data.split('_', 2) if len(data) != 3: - await bot.send_message(callback_query.from_user.id, "Неверные данные для выбора метода перевода.") + await send_message_with_deletion(callback_query.from_user.id, "Неверные данные для выбора метода перевода.", state=state, message_key='transfer_method_error_message_id') return - transfer_method = data[2] # 'requisites' + transfer_method = data[2] if transfer_method == 'requisites': - message = ( - "Банк: Т-Банк\n" - "2200701036597224\n" - "Получатель: Лисицын В.Д.\n" - f"\n" - "После перевода отправьте чек и дождитесь подтверждения." - ) + amount_keyboard = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text='100 RUB', callback_data='amount_100')], + [InlineKeyboardButton(text='300 RUB', callback_data='amount_300')], + [InlineKeyboardButton(text='500 RUB', callback_data='amount_500')], + [InlineKeyboardButton(text='⬅️ Назад', callback_data='back_to_profile')] + ]) - # Удаляем старое сообщение о выборе метода - await bot.edit_message_text( - text="Вы выбрали метод перевода. Теперь вы увидите реквизиты.", - chat_id=callback_query.from_user.id, - message_id=callback_query.message.message_id + await callback_query.message.edit_text( + text="Выберите сумму пополнения:", + reply_markup=amount_keyboard ) - - # Отправляем реквизиты в новом сообщении - await bot.send_message(callback_query.from_user.id, message) - + await state.update_data(transfer_method=transfer_method) + await state.set_state(ReplenishBalanceState.choosing_amount) else: - await bot.send_message(callback_query.from_user.id, "Неверный метод перевода.") + await send_message_with_deletion(callback_query.from_user.id, "Неверный метод перевода.", state=state, message_key='transfer_method_error_message_id') + +@router.callback_query(lambda c: c.data.startswith('amount_')) +async def process_amount_selection(callback_query: types.CallbackQuery, state: FSMContext): + data = callback_query.data.split('_', 1) + + if len(data) != 2: + await send_message_with_deletion(callback_query.from_user.id, "Неверные данные для выбора суммы.", state=state, message_key='amount_error_message_id') return - - await state.update_data(transfer_method=transfer_method) + + amount_str = data[1] + amount = int(amount_str) + + # Удаляем сообщение с кнопками выбора суммы + state_data = await state.get_data() + amount_selection_message_id = state_data.get('amount_selection_message_id') + + if amount_selection_message_id: + try: + await bot.delete_message(chat_id=callback_query.from_user.id, message_id=amount_selection_message_id) + except Exception as e: + print(f"Ошибка при удалении сообщения: {e}") + + await state.update_data(amount=amount) await state.set_state(ReplenishBalanceState.waiting_for_admin_confirmation) + transfer_method = state_data.get('transfer_method') + message = ( + "Банк: Т-Банк\n" + "2200701036597224\n" + "Получатель: Лисицын В.Д.\n" + "\n" + "После перевода отправьте чек и дождитесь подтверждения." + ) + + await callback_query.message.edit_text(text=message, reply_markup=None) + admin_keyboard = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text='Подтвердить', callback_data=f'admin_confirm_{callback_query.from_user.id}_{transfer_method}')], - [InlineKeyboardButton(text='Отклонить', callback_data=f'admin_decline_{callback_query.from_user.id}_{transfer_method}')] + [InlineKeyboardButton(text='Подтвердить', callback_data=f'admin_confirm_{callback_query.from_user.id}_{transfer_method}_{amount}')], + [InlineKeyboardButton(text='Отклонить', callback_data=f'admin_decline_{callback_query.from_user.id}_{transfer_method}_{amount}')] ]) - + admin_message = ( f"Пользователь {callback_query.from_user.full_name} запросил пополнение баланса.\n" f"Метод перевода: {transfer_method}\n" + f"Сумма пополнения: {amount} RUB\n" "Пожалуйста, подтвердите или отклоните запрос." ) - - await bot.send_message(ADMIN_ID, admin_message, reply_markup=admin_keyboard) + + await send_message_with_deletion(ADMIN_ID, admin_message, reply_markup=admin_keyboard, state=state, message_key='admin_request_message_id') await callback_query.answer() @router.callback_query(lambda c: c.data.startswith('admin_')) async def process_admin_confirmation(callback_query: types.CallbackQuery, state: FSMContext): if callback_query.from_user.id != ADMIN_ID: - await bot.send_message(callback_query.from_user.id, "Вы не являетесь администратором.") + await send_message_with_deletion(callback_query.from_user.id, "Вы не являетесь администратором.", state=state, message_key='admin_error_message_id') return data = callback_query.data.split('_', 4) if len(data) < 4: - await bot.send_message(callback_query.from_user.id, "Неверные данные для обработки запроса.") + await send_message_with_deletion(callback_query.from_user.id, "Неверные данные для обработки запроса.", state=state, message_key='admin_error_message_id') return - action = data[1] # 'confirm' или 'decline' - user_id_str = data[2] + action = data[1] + user_id = int(data[2]) transfer_method = data[3] + amount = int(data[4]) try: - user_id = int(user_id_str) # Преобразование user_id в целое число - if action == 'confirm': - amount = 100 # Пример суммы, на которую нужно пополнить баланс - async with aiosqlite.connect(DATABASE_PATH) as db: await update_balance(user_id, amount) await db.commit() balance = await get_balance(user_id) - await bot.send_message(callback_query.from_user.id, f"Баланс пользователя успешно пополнен на {amount} RUB.\nТекущий баланс: {balance}") + + await send_message_with_deletion(callback_query.from_user.id, f"Баланс пользователя успешно пополнен на {amount} RUB.\nТекущий баланс: {balance}", state=state, message_key='admin_confirm_message_id') await bot.send_message(user_id, f"Ваш баланс был успешно пополнен на {amount} RUB.") + # Удаляем сообщение с реквизитами + state_data = await state.get_data() + requisites_message_id = state_data.get('requisites_message_id') + if requisites_message_id: + try: + await bot.delete_message(chat_id=user_id, message_id=requisites_message_id) + except Exception as e: + print(f"Ошибка при удалении сообщения с реквизитами: {e}") + + profile_button = InlineKeyboardButton(text='Профиль', callback_data='view_profile') + profile_keyboard = InlineKeyboardMarkup(inline_keyboard=[[profile_button]]) + await bot.send_message(user_id, "После пополнения баланса вы можете просмотреть свой профиль.", reply_markup=profile_keyboard) + elif action == 'decline': - await bot.send_message(callback_query.from_user.id, "Пополнение баланса отклонено.") + await send_message_with_deletion(callback_query.from_user.id, "Пополнение баланса отклонено.", state=state, message_key='admin_decline_message_id') await bot.send_message(user_id, "Ваш запрос на пополнение баланса был отклонен.") except Exception as e: - await bot.send_message(callback_query.from_user.id, f"Ошибка при пополнении баланса: {e}") - print(f"Ошибка при пополнении баланса: {e}") # Отладочное сообщение в консоль + await send_message_with_deletion(callback_query.from_user.id, f"Ошибка при пополнении баланса: {e}", state=state, message_key='admin_error_message_id') + print(f"Ошибка при пополнении баланса: {e}") await state.clear() await callback_query.answer() + +@router.message(lambda m: m.text and m.text.startswith('Недостаточно средств для продления')) +async def handle_insufficient_funds(message: types.Message): + user_id = message.from_user.id + replenish_keyboard = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text='Пополнить баланс', callback_data='replenish_balance')] + ]) + await send_message_with_deletion(user_id, "Ваш баланс недостаточен для продления ключа. Пожалуйста, пополните баланс.", reply_markup=replenish_keyboard, state=None, message_key='insufficient_funds_message_id') diff --git a/handlers/profile.py b/handlers/profile.py index b39c6a8e..c5fb2432 100644 --- a/handlers/profile.py +++ b/handlers/profile.py @@ -5,7 +5,7 @@ from aiogram.fsm.state import State, StatesGroup from datetime import datetime import aiosqlite from config import DATABASE_PATH -from database import get_balance, has_active_key +from database import get_balance, get_key_count router = Router() @@ -15,50 +15,45 @@ class ReplenishBalanceState(StatesGroup): async def process_callback_view_profile(callback_query: types.CallbackQuery, state: FSMContext): tg_id = callback_query.from_user.id - username = callback_query.from_user.username try: - # Проверяем наличие активного ключа - email = await has_active_key(tg_id) - expiry_date = "Нет активного ключа" - - if email: - async with aiosqlite.connect(DATABASE_PATH) as db: - async with db.execute( - "SELECT expiry_time FROM connections WHERE tg_id = ? AND expiry_time > ?", - (tg_id, int(datetime.utcnow().timestamp() * 1000)) - ) as cursor: - record = await cursor.fetchone() - if record: - expiry_date = datetime.utcfromtimestamp(record[0] / 1000).strftime("%Y-%m-%d %H:%M:%S") + # Получаем количество ключей + key_count = await get_key_count(tg_id) + # Получаем баланс balance = await get_balance(tg_id) + if balance is None: + balance = 0 # По умолчанию 0, если баланс неизвестен + profile_message = ( - f"Профиль \n\n" - f"ID: {tg_id}\n" - f"Ключ: {email if expiry_date else 'Нет активного ключа'}\n" - f"Баланс: {balance} RUB\n" + f"Профиль пользователя\n\n" + f"🔹 ID: {tg_id}\n" + f"🔹 Количество ключей: {key_count}\n" + f"🔹 Баланс: {balance} RUB\n" + f"🔹 Дата регистрации: {datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')}\n" ) # Кнопки для действий в профиле button_create_key = InlineKeyboardButton(text='Создать ключ', callback_data='create_key') button_view_keys = InlineKeyboardButton(text='Мои ключи', callback_data='view_keys') button_replenish_balance = InlineKeyboardButton(text='Пополнить баланс', callback_data='replenish_balance') + button_back = InlineKeyboardButton(text='⬅️ Назад', callback_data='back_to_menu') # Добавляем кнопку "Назад" + keyboard = InlineKeyboardMarkup(inline_keyboard=[ [button_create_key], # Добавляем кнопку "Создать ключ" [button_view_keys], - [button_replenish_balance] + [button_replenish_balance], + [button_back] # Добавляем кнопку "Назад" в клавиатуру ]) except Exception as e: - profile_message = f"Ошибка при получении данных профиля: {e}" + profile_message = f"❗️ Ошибка при получении данных профиля: {e}" keyboard = None - # Отправляем сообщение с клавиатурой - await callback_query.message.bot.send_message( - chat_id=callback_query.message.chat.id, + # Редактируем существующее сообщение, а не отправляем новое + await callback_query.message.edit_text( text=profile_message, - parse_mode='Markdown', + parse_mode='HTML', reply_markup=keyboard ) await callback_query.answer() diff --git a/handlers/start.py b/handlers/start.py index b9b9dab0..af3521cc 100644 --- a/handlers/start.py +++ b/handlers/start.py @@ -4,7 +4,7 @@ from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, CallbackQu from aiogram.fsm.storage.memory import MemoryStorage from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup -from config import API_TOKEN, ADMIN_ID +from config import API_TOKEN, ADMIN_ID, CHANNEL_URL from bot import dp, bot # Убедитесь, что bot и dp импортируются из правильного места router = Router() @@ -22,20 +22,22 @@ async def start_command(message: types.Message): "Нажмите на соответствующую кнопку ниже, чтобы начать!" ) - button_view_profile = InlineKeyboardButton(text='Мой профиль', callback_data='view_profile') - button_about_vpn = InlineKeyboardButton(text='О VPN', callback_data='about_vpn') - button_feedback = InlineKeyboardButton(text='Обратная связь', callback_data='feedback') + button_view_profile = InlineKeyboardButton(text='👤 Мой профиль', callback_data='view_profile') + button_about_vpn = InlineKeyboardButton(text='🔒 О VPN', callback_data='about_vpn') + button_feedback = InlineKeyboardButton(text='📝 Обратная связь', callback_data='feedback') + button_channel = InlineKeyboardButton(text='📢 Наш канал', url=CHANNEL_URL) inline_keyboard = InlineKeyboardMarkup(inline_keyboard=[ [button_view_profile], [button_about_vpn], - [button_feedback] + [button_feedback], + [button_channel] ]) reply_keyboard = ReplyKeyboardMarkup( keyboard=[ - [KeyboardButton(text="В начало")], - [KeyboardButton(text="Мой профиль")] + [KeyboardButton(text="Меню")], + # [KeyboardButton(text="Мой профиль")] ], resize_keyboard=True ) @@ -61,18 +63,72 @@ async def handle_about_vpn(callback_query: CallbackQuery): "Каждому пользователю предоставляется индивидуальный ключ для подключения. " "Этот ключ необходим для использования нашего VPN-сервиса." ) - await callback_query.message.answer( + + # Кнопка "Назад" + button_back = InlineKeyboardButton(text='⬅️ Назад', callback_data='back_to_menu') + + # Обновлённая клавиатура с кнопкой "Назад" + inline_keyboard_back = InlineKeyboardMarkup(inline_keyboard=[ + [button_back] + ]) + + await callback_query.message.edit_text( info_message, - parse_mode='Markdown' + parse_mode='Markdown', + reply_markup=inline_keyboard_back # Добавление клавиатуры с кнопкой "Назад" + ) + await callback_query.answer() + +@router.callback_query(lambda c: c.data == 'back_to_menu') +async def handle_back_to_menu(callback_query: CallbackQuery): + welcome_text = ( + "*Добро пожаловать в наш сервис!*\n\n" + "Вы можете воспользоваться следующими функциями:\n\n" + "🔑 *Создать ключ для подключения VPN* - Получите уникальный ключ для доступа к VPN.\n" + "📅 *Узнать дату окончания ключа* - Проверьте, когда истекает срок действия вашего ключа.\n" + "👤 *Просмотреть ваш профиль* - Получите информацию о вашем аккаунте и балансе.\n\n" + "Нажмите на соответствующую кнопку ниже, чтобы начать!" + ) + + button_view_profile = InlineKeyboardButton(text='👤 Мой профиль', callback_data='view_profile') + button_about_vpn = InlineKeyboardButton(text='🔒 О VPN', callback_data='about_vpn') + button_feedback = InlineKeyboardButton(text='📝 Обратная связь', callback_data='feedback') + button_channel = InlineKeyboardButton(text='📢 Наш канал', url=CHANNEL_URL) + + inline_keyboard = InlineKeyboardMarkup(inline_keyboard=[ + [button_view_profile], + [button_about_vpn], + [button_feedback], + [button_channel] + ]) + + # Редактирование сообщения на главное меню + await callback_query.message.edit_text( + welcome_text, + parse_mode='Markdown', + reply_markup=inline_keyboard ) await callback_query.answer() @router.callback_query(lambda c: c.data == 'feedback') async def handle_feedback(callback_query: CallbackQuery, state: FSMContext): - await callback_query.message.answer( - "Напишите нам, если у вас возникли трудности с подключением, есть отзыв или предложение. Ваше сообщение будет отправлено админу.", - parse_mode='Markdown' + feedback_text = "Напишите нам, если у вас возникли трудности с подключением, есть отзыв или предложение. @pocomacho" + + # Кнопка "Назад" + button_back = InlineKeyboardButton(text='⬅️ Назад', callback_data='back_to_menu') + + # Обновлённая клавиатура с кнопкой "Назад" + inline_keyboard_back = InlineKeyboardMarkup(inline_keyboard=[ + [button_back] + ]) + + # Редактирование сообщения с кнопкой "Назад" + await callback_query.message.edit_text( + feedback_text, + parse_mode='Markdown', + reply_markup=inline_keyboard_back # Клавиатура с кнопкой "Назад" ) + await state.set_state(FeedbackState.waiting_for_feedback) await state.update_data(user_id=callback_query.from_user.id) await callback_query.answer() @@ -95,3 +151,8 @@ async def receive_feedback(message: types.Message, state: FSMContext): print(f"Ошибка при отправке обратной связи: {e}") # Логирование ошибок await state.clear() + +# Пример обработки команды старт +@router.message(Command('start')) +async def cmd_start(message: Message): + await start_command(message) \ No newline at end of file diff --git a/key_management.py b/key_management.py index 5efb7877..3f45b3f5 100644 --- a/key_management.py +++ b/key_management.py @@ -1,63 +1,52 @@ -from aiogram import Bot, Dispatcher, Router, F, types -from aiogram.filters import Command -from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, CallbackQuery, Message -from aiogram.fsm.storage.memory import MemoryStorage -from aiogram.fsm.context import FSMContext -from aiogram.fsm.state import State, StatesGroup -from auth import login_with_credentials, link -from client import add_client from datetime import datetime, timedelta -from config import API_TOKEN, ADMIN_PASSWORD, ADMIN_USERNAME, ADMIN_CHAT_ID, DATABASE_PATH -from database import add_connection, has_active_key, get_active_key_email, get_balance, store_key, update_balance -from client import extend_client_key +import asyncio +import re import uuid import aiosqlite -import re +from aiogram import Bot, Dispatcher, Router, types, F +from aiogram.filters import Command +from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, CallbackQuery, Message +from aiogram.fsm.context import FSMContext +from aiogram.fsm.state import State, StatesGroup + +from auth import login_with_credentials, link +from client import add_client, extend_client_key +from config import API_TOKEN, ADMIN_PASSWORD, ADMIN_USERNAME, ADMIN_CHAT_ID, DATABASE_PATH +from database import add_connection, has_active_key, get_balance, store_key, update_balance from bot import dp from handlers.start import start_command -from bot import bot from handlers.profile import process_callback_view_profile -import asyncio - +from handlers.keys import process_callback_view_keys +from bot import bot router = Router() -def escape_markdown(text: str) -> str: - """ - Экранирование специальных символов для Markdown. - """ - escape_chars = r'_*[]()~`>#+-=|{}.!' - return re.sub(r'([%s])' % re.escape(escape_chars), r'\\\1', text) - +# Удаляем специальные символы из имени ключа def sanitize_key_name(key_name: str) -> str: - """ - Очищает имя ключа от недопустимых символов и приводит его к нижнему регистру. - """ - # Оставляем только строчные буквы, цифры, символы @._- return re.sub(r'[^a-z0-9@._-]', '', key_name.lower()) class Form(StatesGroup): waiting_for_key_name = State() - waiting_for_admin_confirmation = State() - waiting_for_statistics = State() waiting_for_expiry_date = State() viewing_profile = State() +# Обработка нажатия кнопки создания ключа @dp.callback_query(F.data == 'create_key') -async def process_callback_create_key(callback_query: types.CallbackQuery, state: FSMContext): - await bot.send_message(callback_query.from_user.id, "Введите имя вашего профиля VPN:") +async def process_callback_create_key(callback_query: CallbackQuery, state: FSMContext): + await callback_query.message.edit_text("Вам будет выдан пробный ключ. Пожалуйста, выберите имя для вашего ключа:") await state.set_state(Form.waiting_for_key_name) await callback_query.answer() +# Обработка текстовых сообщений @dp.message() -async def handle_text(message: types.Message, state: FSMContext): +async def handle_text(message: Message, state: FSMContext): current_state = await state.get_state() print(f"Received message: {message.text}, Current state: {current_state}") + # Обработка команд и переходов if message.text == "Мой профиль": - # Создайте фейковый объект CallbackQuery для вызова функции callback_query = types.CallbackQuery( - id="1", # Используйте уникальный идентификатор + id="1", from_user=message.from_user, chat_instance='', data='view_profile', @@ -66,103 +55,59 @@ async def handle_text(message: types.Message, state: FSMContext): await process_callback_view_profile(callback_query, state) return - if message.text == "/start": - await start_command(message) - return - - if message.text == "В начало": + if message.text in ["/start", "Меню"]: await start_command(message) return + # Если ожидается имя ключа if current_state == Form.waiting_for_key_name.state: - tg_id = message.from_user.id - key_name = sanitize_key_name(message.text) - tg_name = message.from_user.username + await handle_key_name_input(message, state) - if not key_name: - await bot.send_message(tg_id, "Имя клиента не указано.") - await state.clear() - return +# Обработка ввода имени ключа +async def handle_key_name_input(message: Message, state: FSMContext): + tg_id = message.from_user.id + key_name = sanitize_key_name(message.text) - if await has_active_key(tg_id): - await bot.send_message(tg_id, "У вас уже есть активный ключ. Вы не можете создать новый.") - await state.clear() - return - - # Сохраняем информацию о запросе в контексте состояния - await state.update_data(key_name=key_name, tg_id=tg_id) - - # Отправляем запрос на подтверждение админу - admin_message = ( - f"Новый запрос на создание ключа:\n" - f"Телеграм-ID: {tg_id}\n" - f"Имя ключа: {key_name}\n" - f"Имя пользователя Telegram: @{tg_name}\n" - f"Введите 'yes' для подтверждения или 'no' для отклонения." - ) - keyboard = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text='Да', callback_data=f'confirm_key_{tg_id}_{key_name}')], - [InlineKeyboardButton(text='Нет', callback_data=f'reject_key_{tg_id}')] - ]) - await bot.send_message(ADMIN_CHAT_ID, admin_message, reply_markup=keyboard) - - await bot.send_message(tg_id, "Ожидайте подтверждения администратора.") + if not key_name: + await message.reply("Имя профиля не указано. Введите имя на английском языке.") await state.clear() - -@dp.callback_query(F.data.startswith('confirm_key_')) -async def handle_admin_confirmation(callback_query: CallbackQuery, state: FSMContext): - if callback_query.message.chat.id != int(ADMIN_CHAT_ID): return - + + if await has_active_key(tg_id): + await message.reply("У вас уже есть активный ключ. Вы не можете создать новый.") + await state.clear() + return + + await state.update_data(key_name=key_name, tg_id=tg_id) + session = login_with_credentials(ADMIN_USERNAME, ADMIN_PASSWORD) - data = callback_query.data.split('_', 3) - tg_id = int(data[2]) - key_name = data[3] + client_id = str(uuid.uuid4()) + email = key_name.lower() + current_time = datetime.utcnow() + expiry_time = int((current_time + timedelta(days=1)).timestamp() * 1000) try: - client_id = str(uuid.uuid4()) - email = key_name.lower() # Приведение email к нижнему регистру - limit_ip = 1 - total_gb = 0 - current_time = datetime.utcnow() - - # Определяем время истечения ключа - if await has_active_key(tg_id): - # Если есть активный ключ, устанавливаем срок действия на 30 дней - expiry_time = int((current_time + timedelta(days=30)).timestamp() * 1000) - else: - # Первый ключ устанавливаем на 1 день - expiry_time = int((current_time + timedelta(days=1)).timestamp() * 1000) - - enable = True - flow = "xtls-rprx-vision" - balance = 0 - - # Проверка данных перед созданием ключа - print(f"Creating key with client_id={client_id}, email={email}, tg_id={tg_id}") - - add_client(session, client_id, email, tg_id, limit_ip, total_gb, expiry_time, enable, flow) - + # Создание клиента и получение ссылки + add_client(session, client_id, email, tg_id, limit_ip=1, total_gb=0, expiry_time=expiry_time, enable=True, flow="xtls-rprx-vision") connection_link = link(session, client_id, email) - await add_connection(tg_id, client_id, email, expiry_time, balance) - await store_key(client_id, email, connection_link) + await add_connection(tg_id, client_id, email, expiry_time, 0) + await store_key(client_id, email, connection_link) - # Создаем кнопки - instructions_button = InlineKeyboardButton(text='Инструкции по использованию', callback_data='instructions') - keyboard = InlineKeyboardMarkup(inline_keyboard=[[instructions_button]]) + keyboard = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text='Инструкции по использованию', callback_data='instructions')], + [InlineKeyboardButton(text='Перейти в профиль', callback_data='view_profile')] + ]) - # Отправляем сообщение с ключом и кнопкой key_message = f"Ключ создан:\n
{connection_link}
" - await bot.send_message(tg_id, key_message, parse_mode="HTML", reply_markup=keyboard) - await bot.send_message(callback_query.message.chat.id, "Ключ создан и отправлен клиенту.") + await message.reply(key_message, parse_mode="HTML", reply_markup=keyboard) except Exception as e: - await bot.send_message(callback_query.message.chat.id, f"Ошибка при создании ключа: {e}") - print(f"Ошибка при создании ключа: {e}") # Отладочное сообщение в консоль - - await callback_query.answer() + await message.reply(f"Ошибка при создании ключа: {e}") + print(f"Ошибка при создании ключа: {e}") + await state.clear() +# Обработка кнопки "Инструкции" @dp.callback_query(F.data == 'instructions') async def handle_instructions(callback_query: CallbackQuery): instructions_message = ( @@ -175,12 +120,29 @@ async def handle_instructions(callback_query: CallbackQuery): "4. Выберите 'Вставить из буфера обмена' для добавления ключа.\n\n" "Если у вас возникнут вопросы, не стесняйтесь обращаться в поддержку." ) - await callback_query.message.answer( - instructions_message, - parse_mode='Markdown' - ) + + back_button = InlineKeyboardButton(text='Назад', callback_data='back_to_main') + keyboard = InlineKeyboardMarkup(inline_keyboard=[[back_button]]) + + await callback_query.message.edit_text(instructions_message, parse_mode='Markdown', reply_markup=keyboard) await callback_query.answer() +# Обработка кнопки "Назад" +@dp.callback_query(F.data == 'back_to_main') +async def handle_back_to_main(callback_query: CallbackQuery): + tg_id = callback_query.from_user.id + new_callback_query = CallbackQuery( + id=callback_query.id, + from_user=callback_query.from_user, + chat_instance=callback_query.chat_instance, + data='view_keys', + message=callback_query.message + ) + + await process_callback_view_keys(new_callback_query) + await callback_query.answer() + +# Фоновая задача для продления ключей async def renew_expired_keys(): while True: current_time = datetime.utcnow() @@ -192,16 +154,20 @@ async def renew_expired_keys(): balance = await get_balance(tg_id) if balance >= 100: new_expiry_time = int((current_time + timedelta(days=30)).timestamp() * 1000) - + async with aiosqlite.connect(DATABASE_PATH) as db: await db.execute('UPDATE connections SET expiry_time = ? WHERE tg_id = ? AND client_id = ?', (new_expiry_time, tg_id, client_id)) await db.commit() await update_balance(tg_id, -100) await extend_client_key(client_id) - + print(f"Ключ для клиента {client_id} продлен на месяц и списано 100 рублей.") else: - print(f"Недостаточно средств на балансе для клиента {client_id}.") + print(f"Недостаточно средств на балансе для клиента {client_id}. Предложение пополнить баланс.") + replenish_keyboard = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text='Пополнить баланс', callback_data='replenish_balance')] + ]) + await bot.send_message(tg_id, "Ваш баланс недостаточен для продления ключа. Пожалуйста, пополните баланс.", reply_markup=replenish_keyboard) - await asyncio.sleep(3600) \ No newline at end of file + await asyncio.sleep(3600) From fe6fef3174cbf31e5d217e5f7b45a4366c629719 Mon Sep 17 00:00:00 2001 From: Vlad Date: Fri, 20 Sep 2024 05:48:09 +0300 Subject: [PATCH 04/12] =?UTF-8?q?=D0=B4=D0=BE=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=BA=D0=B0=20=D0=BA=D0=BE=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + database.py | 14 ++-- handlers/__pycache__/keys.cpython-310.pyc | Bin 6180 -> 7656 bytes handlers/__pycache__/pay.cpython-310.pyc | Bin 8690 -> 8705 bytes handlers/keys.py | 66 +++++++++++---- handlers/pay.py | 64 +++++++++----- key_management.py | 97 +++++++++++++++++++--- 7 files changed, 190 insertions(+), 52 deletions(-) diff --git a/.gitignore b/.gitignore index 0b7bcc7b..fb38d4ca 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ /database.db /bot_old.py /bot_old_2.py +/database.db \ No newline at end of file diff --git a/database.py b/database.py index eae9071c..4c9ea929 100644 --- a/database.py +++ b/database.py @@ -11,17 +11,18 @@ async def init_db(): client_id TEXT NOT NULL, email TEXT NOT NULL, expiry_time INTEGER NOT NULL, - balance REAL NOT NULL DEFAULT 0.0, -- Добавлено поле для баланса + balance REAL NOT NULL DEFAULT 0.0, PRIMARY KEY (tg_id, client_id) ) ''') await db.execute(''' CREATE TABLE IF NOT EXISTS keys ( + tg_id INTEGER NOT NULL, -- Добавлено поле tg_id client_id TEXT NOT NULL, email TEXT NOT NULL, created_at INTEGER NOT NULL, key TEXT NOT NULL, - PRIMARY KEY (client_id) + PRIMARY KEY (tg_id, client_id) -- Изменено на (tg_id, client_id) ) ''') await db.commit() @@ -34,12 +35,12 @@ async def add_connection(tg_id: int, client_id: str, email: str, expiry_time: in ''', (tg_id, client_id, email, expiry_time, balance)) await db.commit() -async def store_key(client_id: str, email: str, key: str): +async def store_key(tg_id: int, client_id: str, email: str, key: str): async with aiosqlite.connect(DATABASE_PATH) as db: await db.execute(''' - INSERT INTO keys (client_id, email, created_at, key) - VALUES (?, ?, ?, ?) - ''', (client_id, email, int(datetime.utcnow().timestamp() * 1000), key)) + INSERT INTO keys (tg_id, client_id, email, created_at, key) + VALUES (?, ?, ?, ?, ?) + ''', (tg_id, client_id, email, int(datetime.utcnow().timestamp() * 1000), key)) await db.commit() async def get_keys(tg_id: int): @@ -51,7 +52,6 @@ async def get_keys(tg_id: int): ''', (tg_id,)) as cursor: return await cursor.fetchall() - async def has_active_key(tg_id: int) -> bool: async with aiosqlite.connect(DATABASE_PATH) as db: async with db.execute("SELECT COUNT(*) FROM connections WHERE tg_id = ? AND expiry_time > ?", diff --git a/handlers/__pycache__/keys.cpython-310.pyc b/handlers/__pycache__/keys.cpython-310.pyc index db258c309a002ec63c7261460326dbb5bd03a626..c2051196a6f9e2643a96414a38fb276a95a85631 100644 GIT binary patch delta 2942 zcmZ{mO>7&-6@Yh!yUXQrwGw|sk&3NQj_sPZVmU#765DX?I*z62$8F*?mK&R0aaJm6 zie!h&+7fmxIT6}JD>u$uiUtO#peSIpXze;~ou*C?xdiBi>aE)XIR&VT^iUK%6^Q%Z zQa_fUt}u@?@4cCwee=FI`q`nsA2F*P9l8Y1H~;e4)S;(um^-LWZVdc#l6A4zqI5oG z$C_~#XOTr|nK0cZrNk6v36@-x-z4XI0&DjcYl`&**7Ul6nq?N{^BLCLUX#&Rq}&sC zljH)KH{jj4fsdfo&k;$wMqH^Rm54{?<{jD$Ir1|3qBJZqX-N8M(W5SHDvY>fiY(K0 z{g4-OLrryg)z6b%nGa7(p5n?cNEcF`>Z*)pBroiQOPU*IAvgSvbdxgq=o0lbS7}CE zZJI0-B7HXpF1o8QWr=tZrh0mXvaqK+(URfnE^e-)WV#WiEy?0eq*qk+C@Bgn_N1uk z`ux?)um=83KzmK9^vRObIr}b=yeN_8;!YQ?z#?uGU;k#P{aNTa0Uzi^uSP3M`}Z=r zEV)uMw)RN`><~45d~QU~Zh2lgHG1mg*{nTd(PCK)MEmDAxp0W8s`e5by$`7)w_n`CRZ0nC;`f%l1>klB`fd1`NwspVtU}X`;3!uHd@_iWJKP+BU zpQ5>tIG*Yjo;utW_ajrhI+JhIZSK<|6X9@9gDr6iuND7NdrqoWt!7u4pBxT0HnO^} zF^gYn)VPe_6|-5<$_nO7s_$UdYt0U(FB9lT0|HdfaLiICGgt>Rfy9nr)Csi*N2W7@#u$_z9Cm`S%k7vZ zt_iJknoNgS0wC>jVx_oi1ZXBoNwzE!P>Kq?wOZk0hWQ4rQO)J zSjMi#0xaQo70aG2SZ2n3l7EN?HTSXjhx`j8dt&^BY~dgv2}IsdekG{SE=(TRPZnu61?Y(U4H)|9v0x2J?{Kz)~Tx+~KRV>?lC%F6a(AlxkoXnquL6F+K zAJuxz;&nSeQ)PDJE4b`WwxWlOez=Y;5d4MK1LXJty!(BC_#XBS-U>ao@@ZJ8F(UGb zjQF`4KeG+Zo0mMC&87J++}KknVjz5_UMm+Jz8e*LP++a+dqId?`1zLu%K1Lj?MHz$ z^8+XbP-IbH{_}$<20`TF0ml3gDv&|`42ow_dq9U`^y8!rzp5Djr3%8Y0rncv zNh&A;@*_&s&rPtHBu%n(2c--9KMjgO-+D2afW-<3|Js9n71~G8>KM*z#DTlerLN=- zkrFKhU@ysQ$sODYp>h5qxN_VYq{lXKgrf~&hDDGMh&yH?fkDOB*U_CWWC=eg-q-e! zlj0MtpIp34B4d=C5ND#Frv8JKW+PeoDH!sd;<#}={Wa*$$wvn#j|E%EQI3hj`^6{5 zbNP*Q*d_(|OX#Se7zN=+oFY_h$C|0Nj~~ywogqPT1ZfgR-3W?t5MnYsa3RQ$T*qU1 zu{kCDNTFP`D-M=@C7_Xi9p?H5lz&JOCy%e?=TH@%w8~bcU(Uz-_i5jy;sB;Eb9*%un%UxndAto#r0PT}7| zfkS>A#R(LzgUAs+CVJyz(L8E8QS1~vevb6t{Zsq|IlU2K1diiv?T3n$ZJFB=aQMofLV29fbiX3f^~PBjnzJHVy91wLNDsSE{1nA;%y zPK<(mrB>v#L8N`TPz4gF(ALH6TG_4?>)%-=lD{TpVPg9*>Tsb0t$w)PC={T-dMaYP zdyp)OtKBnXR(#fdHnCB&a_VZ~5EoM!wH+GarFwfTcv}FJ1|49RWQqKUTjE!#u`aZ= zi3z^^JhHJL+4vY*9Z{qUCP|T8uOGIG)oE_cw6hXP<-5g1Pq`PbbdD^wD-~u4pG9E_ bzh^JmD@ZyW%f^Xzz)<4E*b^sR#|-6voVN4z delta 1695 zcma)*O>7%Q6vyZ7hrM2}o%kc)O$cd2>n1cM6dKylsz5)YG@(&TODh^MB=~&Bo3v_bDXsF#ID|N-@KW9 zGxMJ}za9KwZ?qJN=tMlucfU{Z?boB*q+iJO{@cT5tP-bB9o;QX$Z%6_;2K=3^;>>RPW6yn4$e-Ffq09lMX3Go-9O>P7yzarNYijn8V zuD!^c5lh|pF2DkDNhP_tO%VO!0Gh}7ANq0ry3)eG(Nlwe;wh}%T>EV8D`8*y#v6Ha zAzfyrseIAA1%5-`D&{RSJ!@XhmNJYke$CH@KISp~3;v_hlk}AnXU0z6l6<|KVYZo` zD^Zij)sZ7jerSr7=F;=F$$TkKnIz`%lOwfkqxEEC88$m#el+^RyyKKCKC7nqq%p|P zCi?kbYENI3wPFg@nNH`auWc^KmuS`xFfEt*%AKBDMbk9ZPc zAod^z5Gvv+0YBvA=SatPH zcM>tg2itp~kH6ABF#15UrVYtZF<&VQA^a%KT(;9rDQ#YIOp89OBc>Tcj3ZD`_A>vW zz2nF!;rfltEEi2HZ_lK&wR-l$*-UP>-l&UhixbY5EI(+^=W@c|RDFBL08H}J9dj@v zR6n!i2XiwS2g_cs7lrUsfsL&caArJvu~@TcI^q)63uj9#p(@@TmfS)UdRR3#h1r^b zxF*mI%Kch2hv&Tc$o%bX!;}9lv6ReebyZ7SQ}mJC`^Dey3$p_P(sC4v)FVD-3i1DqBi4x{!Zr;(9Q33wubxSVD!eJ@sOeZ3xs)@<^TWy diff --git a/handlers/__pycache__/pay.cpython-310.pyc b/handlers/__pycache__/pay.cpython-310.pyc index 78eea876806acff4b90e65e08a43eb6beb293f06..7845c031a1b9d03c58c8fb0f418413d975a02694 100644 GIT binary patch delta 1282 zcmZuxO>7%Q6rMLb-d(TPYuB|M$3KnTKk5>Ms{Ydhphi$C9GKDj=I#6D z&3kV&Zy%ps9&_e`L5tw{{P#bne|_POQ`I0S>)I>!H)vc2PZMt~o{&Fl=cqU-Lo97i z63>_*jJU?aggnBILr2cAjMe$Bv);LjvL>7CrC7GRjQV=#PWO|}8gdUhYuQ8cA=`g6 zKf1|vuXP)pZ;?@~@?Fbz?)I7G?$yqE_xh&SgY0V)=aPyQ4u?*9s% z#hVBvim)|8sHi*s0j2ht9FNZ}d6b*Oq|R{lkJ^1cori} zjts?@uFeAxHQzyTL!QxuL8yQm<~9#PBXFdj{XVxhHyC^MX9KqHVGLvX%zHcGl$=e=pkT9K{;^JifAB z#U!a7$0rwC_zOtg0kRl$<9x_-FgR^u8IxE9N9KxC&GpxLm?zZ~-c;fOy#eCC#D_82 zBqn=iN471MZEAIAxWg*k4Oloe2$T;!3Nz)Y3hx=Ex$1O9T#`?cqw?$F-;6wn5qu-^%jA3QRw@UE$l|BS z$@TP$OH8Rg1&)G!3ic{Epg{FS&sVxwE(q~D>iLj3q3q8oIHE|sP;x7C)n4Fott>G7 zRLvFQT}5+68w0b*E0a(r%8NatSh!fImdZU@&Fp2Wv*h*6^SXNIwQ2c%Cib=oCiWN| zG>WN3A&m3TGl$ul0|zzLI+kjZr;dS*gj!;|k?2!PPeKG(BTTdX=0d*+h^G7?l$NLW zT!TmLhkIOrEAm+O)uBf!tc^e$CdDJc+(>KUWBFNz# S7%Q6rMM`UT@axwd?pN{tFx0CQTI~q5=}C1VxENdI*);LyCHc9FH40Z5_Ja zC_ft4PJ$3WwG?T%(5S-Z!i7UxD+p9*g`ys~L~8*TAGmS>AvkeB4R7N72&prA-_E`_ z^WOK~%v&A*d(4^-1`UGa+0);zzkK$ARZ_qb73G5Y1u9p8E6$~rQSqZPVK;nS<^DOX z2u-+17)%f#Q-u4k5S6(8<#)t+^;M{f+iF&ProK)`Z1J0Vs`Z9%2C}{At863CnyON; zzyOs-Ax`^1)ml5+c{TOgzE!6d)qx&O^IuaFR-gzM#Zq7i@vi`f$yUjXE83)VdPaO4 z8dm$MGcDR$R9xuSTfc;o8jOoSWAQYlkilpv&}(!rqC~);IQHT?rD8BXbLt+ta4sX{ zDZ=5fa3O25gHrxbd=;NJTtBBwPm*;I1Bt}w4p*3QgF1h>dZWiRn(zw2ijWC%FIDlo z0j!g@PP-bw3N=jeQDUg-E&^c9fV_+12D!Q}>xic+x~p(9N9KS9xVA}{$@NWgU**7p zOsT7fo?NRD;wrQD_k`{%&Epu*2saNnRC;(%;{*#gp<-~u`FX{3t+@zStK48G>PJVW zNGXXI*Sq`^kV*i^O3;n62oGRjx{gGrkS0URoj}Rg{Vu{Habv8PMOmy)E2bOg<}Azt zi`StN#2}Jff(Kb&T{)y&N@J;#H|Pd9)#EHvm|6NcnPrYMLV9N*Y=N~vb~0DRJZTixcedQ`u5QNzW0sysD1aP zVQ^l+Fs)D110bGcMym2V$w_!r6rN2YpBnwT_EHq-IX{expiry_date.strftime('%Y-%m-%d %H:%M:%S')}\n" f"{days_left_message}") - else: - response_message = "У вас нет ключей." + # Кнопки для продления и инструкций + renew_button = types.InlineKeyboardButton(text='Продлить ключ', callback_data='renew_key') + instructions_button = types.InlineKeyboardButton(text='Инструкции по использованию', callback_data='instructions') + back_button = types.InlineKeyboardButton(text='Назад в профиль', callback_data='view_profile') + keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[renew_button], [instructions_button], [back_button]]) - await delete_previous_message(callback_query) - await bot.send_message(tg_id, response_message, parse_mode="HTML", reply_markup=keyboard) + await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=keyboard, parse_mode="HTML") + else: + await bot.edit_message_text("Информация о ключе не найдена.", chat_id=tg_id, message_id=callback_query.message.message_id, parse_mode="HTML") except Exception as e: - await handle_error(tg_id, callback_query, f"Ошибка при получении ключей: {e}") + await handle_error(tg_id, callback_query, f"Ошибка при получении информации о ключе: {e}") await callback_query.answer() +# Остальные функции остаются без изменений... + +# Обработка ошибок +async def handle_error(tg_id, callback_query, message): + await bot.edit_message_text(message, chat_id=tg_id, message_id=callback_query.message.message_id, parse_mode="HTML") + + # Обработка запроса на продление ключа @router.callback_query(lambda c: c.data == 'renew_key') async def process_callback_renew_key(callback_query: types.CallbackQuery): diff --git a/handlers/pay.py b/handlers/pay.py index cb6d4376..2ad6a0e9 100644 --- a/handlers/pay.py +++ b/handlers/pay.py @@ -10,7 +10,6 @@ from handlers.profile import process_callback_view_profile router = Router() -# Определение состояний class ReplenishBalanceState(StatesGroup): choosing_transfer_method = State() choosing_amount = State() @@ -18,7 +17,7 @@ class ReplenishBalanceState(StatesGroup): async def send_message_with_deletion(chat_id, text, reply_markup=None, state=None, message_key='last_message_id'): """ - Отправляет сообщение и удаляет предыдущее, если оно существует. + Отправляет новое сообщение и удаляет предыдущее сообщение (если оно существует), сохраненное в состоянии FSM. """ if state: try: @@ -29,7 +28,8 @@ async def send_message_with_deletion(chat_id, text, reply_markup=None, state=Non await bot.delete_message(chat_id=chat_id, message_id=previous_message_id) sent_message = await bot.send_message(chat_id=chat_id, text=text, reply_markup=reply_markup) - await state.update_data({message_key: sent_message.message_id}) + if state: + await state.update_data({message_key: sent_message.message_id}) except Exception as e: print(f"Ошибка при удалении/отправке сообщения: {e}") @@ -43,12 +43,7 @@ async def process_callback_replenish_balance(callback_query: types.CallbackQuery key_count = await get_key_count(tg_id) if key_count <= 0: - await send_message_with_deletion( - tg_id, - "У вас нет ключей. Пополнение баланса возможно только при наличии ключа.", - state=state, - message_key='key_error_message_id' - ) + await send_message_with_deletion(tg_id, "У вас нет ключей. Пополнение баланса возможно только при наличии ключа.", state=state, message_key='key_error_message_id') create_key_button = InlineKeyboardButton(text='Создать ключ', callback_data='create_key') keyboard = InlineKeyboardMarkup(inline_keyboard=[[create_key_button]]) @@ -61,7 +56,7 @@ async def process_callback_replenish_balance(callback_query: types.CallbackQuery await state.set_state(ReplenishBalanceState.choosing_transfer_method) keyboard = InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text='По реквизитам', callback_data='transfer_method_requisites')], - [InlineKeyboardButton(text='⬅️ Назад', callback_data='back_to_profile')] + [InlineKeyboardButton(text='⬅️ Назад', callback_data='back_to_profile')] # Кнопка "Назад" ]) await callback_query.message.edit_text( @@ -74,6 +69,8 @@ async def process_callback_replenish_balance(callback_query: types.CallbackQuery async def back_to_profile_handler(callback_query: types.CallbackQuery, state: FSMContext): await process_callback_view_profile(callback_query, state) + + @router.callback_query(lambda c: c.data.startswith('transfer_method_')) async def process_transfer_method_selection(callback_query: types.CallbackQuery, state: FSMContext): data = callback_query.data.split('_', 2) @@ -89,7 +86,7 @@ async def process_transfer_method_selection(callback_query: types.CallbackQuery, [InlineKeyboardButton(text='100 RUB', callback_data='amount_100')], [InlineKeyboardButton(text='300 RUB', callback_data='amount_300')], [InlineKeyboardButton(text='500 RUB', callback_data='amount_500')], - [InlineKeyboardButton(text='⬅️ Назад', callback_data='back_to_profile')] + [InlineKeyboardButton(text='⬅️ Назад', callback_data='back_to_profile')] # Кнопка "Назад" ]) await callback_query.message.edit_text( @@ -98,8 +95,11 @@ async def process_transfer_method_selection(callback_query: types.CallbackQuery, ) await state.update_data(transfer_method=transfer_method) await state.set_state(ReplenishBalanceState.choosing_amount) + else: await send_message_with_deletion(callback_query.from_user.id, "Неверный метод перевода.", state=state, message_key='transfer_method_error_message_id') + return + @router.callback_query(lambda c: c.data.startswith('amount_')) async def process_amount_selection(callback_query: types.CallbackQuery, state: FSMContext): @@ -112,16 +112,18 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F amount_str = data[1] amount = int(amount_str) - # Удаляем сообщение с кнопками выбора суммы + # Получаем ID сообщения с кнопками выбора суммы state_data = await state.get_data() amount_selection_message_id = state_data.get('amount_selection_message_id') + # Удаляем сообщение с кнопками выбора суммы if amount_selection_message_id: try: await bot.delete_message(chat_id=callback_query.from_user.id, message_id=amount_selection_message_id) except Exception as e: print(f"Ошибка при удалении сообщения: {e}") + # Обновляем состояние и сохраняем выбранную сумму await state.update_data(amount=amount) await state.set_state(ReplenishBalanceState.waiting_for_admin_confirmation) @@ -134,7 +136,11 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F "После перевода отправьте чек и дождитесь подтверждения." ) - await callback_query.message.edit_text(text=message, reply_markup=None) + # Редактируем текущее сообщение + await callback_query.message.edit_text( + text=message, + reply_markup=None # Или добавьте клавиатуру, если нужно + ) admin_keyboard = InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text='Подтвердить', callback_data=f'admin_confirm_{callback_query.from_user.id}_{transfer_method}_{amount}')], @@ -151,6 +157,8 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F await send_message_with_deletion(ADMIN_ID, admin_message, reply_markup=admin_keyboard, state=state, message_key='admin_request_message_id') await callback_query.answer() + + @router.callback_query(lambda c: c.data.startswith('admin_')) async def process_admin_confirmation(callback_query: types.CallbackQuery, state: FSMContext): if callback_query.from_user.id != ADMIN_ID: @@ -164,20 +172,38 @@ async def process_admin_confirmation(callback_query: types.CallbackQuery, state: return action = data[1] - user_id = int(data[2]) + user_id_str = data[2] transfer_method = data[3] amount = int(data[4]) try: + user_id = int(user_id_str) + + # Получаем данные из состояния для удаления сообщения с реквизитами + state_data = await state.get_data() + requisites_message_id = state_data.get('requisites_message_id') + + if action == 'confirm': async with aiosqlite.connect(DATABASE_PATH) as db: await update_balance(user_id, amount) await db.commit() balance = await get_balance(user_id) - + + # Создаем клавиатуру с кнопкой "Профиль" + profile_button = InlineKeyboardButton(text='Профиль', callback_data='view_profile') + profile_keyboard = InlineKeyboardMarkup(inline_keyboard=[[profile_button]]) + + # Отправляем уведомление с кнопкой "Профиль" администратору await send_message_with_deletion(callback_query.from_user.id, f"Баланс пользователя успешно пополнен на {amount} RUB.\nТекущий баланс: {balance}", state=state, message_key='admin_confirm_message_id') - await bot.send_message(user_id, f"Ваш баланс был успешно пополнен на {amount} RUB.") + + # Отправляем уведомление пользователю с кнопкой "Профиль" + await bot.send_message( + user_id, + f"Ваш баланс был успешно пополнен на {amount} RUB.", + reply_markup=profile_keyboard + ) # Удаляем сообщение с реквизитами state_data = await state.get_data() @@ -188,10 +214,6 @@ async def process_admin_confirmation(callback_query: types.CallbackQuery, state: except Exception as e: print(f"Ошибка при удалении сообщения с реквизитами: {e}") - profile_button = InlineKeyboardButton(text='Профиль', callback_data='view_profile') - profile_keyboard = InlineKeyboardMarkup(inline_keyboard=[[profile_button]]) - await bot.send_message(user_id, "После пополнения баланса вы можете просмотреть свой профиль.", reply_markup=profile_keyboard) - elif action == 'decline': await send_message_with_deletion(callback_query.from_user.id, "Пополнение баланса отклонено.", state=state, message_key='admin_decline_message_id') await bot.send_message(user_id, "Ваш запрос на пополнение баланса был отклонен.") @@ -203,6 +225,8 @@ async def process_admin_confirmation(callback_query: types.CallbackQuery, state: await state.clear() await callback_query.answer() + + @router.message(lambda m: m.text and m.text.startswith('Недостаточно средств для продления')) async def handle_insufficient_funds(message: types.Message): user_id = message.from_user.id diff --git a/key_management.py b/key_management.py index 3f45b3f5..e0c3443d 100644 --- a/key_management.py +++ b/key_management.py @@ -33,10 +33,44 @@ class Form(StatesGroup): # Обработка нажатия кнопки создания ключа @dp.callback_query(F.data == 'create_key') async def process_callback_create_key(callback_query: CallbackQuery, state: FSMContext): - await callback_query.message.edit_text("Вам будет выдан пробный ключ. Пожалуйста, выберите имя для вашего ключа:") - await state.set_state(Form.waiting_for_key_name) + tg_id = callback_query.from_user.id + + if await has_active_key(tg_id): + await callback_query.message.edit_text( + "У вас уже есть активный ключ. Вы можете создать новый ключ за дополнительную плату в размере 100 рублей. " + "Хотите продолжить?", + reply_markup=InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text='Да, создать новый ключ', callback_data='confirm_create_new_key')], + [InlineKeyboardButton(text='Назад', callback_data='cancel_create_key')] # Изменили текст на "Назад" + ]) + ) + await state.update_data(creating_new_key=True) # Сохраняем состояние создания нового ключа + else: + await callback_query.message.edit_text("Вам будет выдан пробный ключ. Пожалуйста, выберите имя для вашего ключа:") + await state.set_state(Form.waiting_for_key_name) + await callback_query.answer() +@dp.callback_query(F.data == 'cancel_create_key') +async def cancel_create_key(callback_query: CallbackQuery, state: FSMContext): + await process_callback_view_profile(callback_query, state) # Возвращаем в профиль + await callback_query.answer() + + + +@dp.callback_query(F.data == 'confirm_create_new_key') +async def confirm_create_new_key(callback_query: CallbackQuery, state: FSMContext): + await callback_query.message.edit_text("Пожалуйста, выберите имя для вашего нового ключа:") + await state.set_state(Form.waiting_for_key_name) + await state.update_data(creating_new_key=True) # Убедитесь, что это состояние сохранено + await callback_query.answer() + +@dp.callback_query(F.data == 'cancel_create_key') +async def cancel_create_key(callback_query: CallbackQuery): + await callback_query.message.edit_text("Создание нового ключа отменено.") + await callback_query.answer() + + # Обработка текстовых сообщений @dp.message() async def handle_text(message: Message, state: FSMContext): @@ -63,22 +97,18 @@ async def handle_text(message: Message, state: FSMContext): if current_state == Form.waiting_for_key_name.state: await handle_key_name_input(message, state) -# Обработка ввода имени ключа async def handle_key_name_input(message: Message, state: FSMContext): tg_id = message.from_user.id key_name = sanitize_key_name(message.text) if not key_name: - await message.reply("Имя профиля не указано. Введите имя на английском языке.") + await message.reply("Имя ключа не указано. Введите имя на английском языке.") await state.clear() return - if await has_active_key(tg_id): - await message.reply("У вас уже есть активный ключ. Вы не можете создать новый.") - await state.clear() - return - - await state.update_data(key_name=key_name, tg_id=tg_id) + # Получаем данные состояния + data = await state.get_data() + creating_new_key = data.get('creating_new_key', False) session = login_with_credentials(ADMIN_USERNAME, ADMIN_PASSWORD) client_id = str(uuid.uuid4()) @@ -86,13 +116,24 @@ async def handle_key_name_input(message: Message, state: FSMContext): current_time = datetime.utcnow() expiry_time = int((current_time + timedelta(days=1)).timestamp() * 1000) + if creating_new_key: + balance = await get_balance(tg_id) + if balance < 100: + replenish_button = InlineKeyboardButton(text='Перейти в профиль', callback_data='view_profile') + keyboard = InlineKeyboardMarkup(inline_keyboard=[[replenish_button]]) + await message.reply("Недостаточно средств на балансе для создания нового ключа.", reply_markup=keyboard) + await state.clear() + return + + await update_balance(tg_id, -100) # Списание 100 рублей за новый ключ + try: # Создание клиента и получение ссылки add_client(session, client_id, email, tg_id, limit_ip=1, total_gb=0, expiry_time=expiry_time, enable=True, flow="xtls-rprx-vision") connection_link = link(session, client_id, email) await add_connection(tg_id, client_id, email, expiry_time, 0) - await store_key(client_id, email, connection_link) + await store_key(tg_id, client_id, email, connection_link) keyboard = InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text='Инструкции по использованию', callback_data='instructions')], @@ -107,6 +148,40 @@ async def handle_key_name_input(message: Message, state: FSMContext): await state.clear() + +# Обработка нажатия кнопки создания ключа +@dp.callback_query(F.data == 'create_key') +async def process_callback_create_key(callback_query: CallbackQuery, state: FSMContext): + tg_id = callback_query.from_user.id + + if await has_active_key(tg_id): + await callback_query.message.edit_text( + "У вас уже есть активный ключ. Вы можете создать новый ключ за дополнительную плату в размере 100 рублей. " + "Хотите продолжить?", + reply_markup=InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text='Да, создать новый ключ', callback_data='confirm_create_new_key')], + [InlineKeyboardButton(text='Нет, оставить текущий', callback_data='cancel_create_key')] + ]) + ) + await state.update_data(creating_new_key=True) # Сохраняем состояние создания нового ключа + else: + await callback_query.message.edit_text("Вам будет выдан пробный ключ. Пожалуйста, выберите имя для вашего ключа:") + await state.set_state(Form.waiting_for_key_name) + + await callback_query.answer() + + +@dp.callback_query(F.data == 'confirm_create_new_key') +async def confirm_create_new_key(callback_query: CallbackQuery, state: FSMContext): + await callback_query.message.edit_text("Пожалуйста, выберите имя для вашего нового ключа:") + await state.set_state(Form.waiting_for_key_name) + await callback_query.answer() + +@dp.callback_query(F.data == 'cancel_create_key') +async def cancel_create_key(callback_query: CallbackQuery): + await callback_query.message.edit_text("Создание нового ключа отменено.") + await callback_query.answer() + # Обработка кнопки "Инструкции" @dp.callback_query(F.data == 'instructions') async def handle_instructions(callback_query: CallbackQuery): From 470f9a2f38ef6ff9451446a999f8e6d36ecc2887 Mon Sep 17 00:00:00 2001 From: Vlad Date: Fri, 20 Sep 2024 14:25:22 +0300 Subject: [PATCH 05/12] =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=B2=20=D0=BC=D0=BE=D0=B8=D1=85=20=D0=BA=D0=BB=D1=8E?= =?UTF-8?q?=D1=87=D0=B0=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- handlers/__pycache__/keys.cpython-310.pyc | Bin 7656 -> 7786 bytes handlers/keys.py | 12 ++++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/handlers/__pycache__/keys.cpython-310.pyc b/handlers/__pycache__/keys.cpython-310.pyc index c2051196a6f9e2643a96414a38fb276a95a85631..59464d1ffd1cebec7dc797e4b8d8d2836184a9dc 100644 GIT binary patch delta 1533 zcmZvbT}&KR6vyYx%zm)UjO<5kfi0c#y~TF1wHm@lV_QloU0U2m(t>V=oq_HO1GzJV zLT42U?F4&@Tr6b?s?(nY*Tl)^Pc%8+(EStX4{~XM5 z4!08CIsmZwG?$PD7Y0IV*^}-)+k4`IYD{TV6+fqciOuvovA%V7H&d{1Bo~ph-K%v$C(S6}-u$Rft3HKMy< z(EHJP`i@_L9{Rq&K^37+5Cui3f;i7F9{90!Q(RQ)8X>sIZ$<>tP4Csl=UXYIWC(vv|h=?Z; z2N5db5F&|aXGlcI9<-lCJcBrlID&YZ-j~in2TjUH0;Cf?UDT8h!clr#9)c12i`A=Oh==ae>FitCDLy*IB*9gpg0;hTy*x6k_TfmshkYn_EEbd>zsb|+djO`cN zjx+lKyY<|II`qDPz#;MSPoniALjuTo`seNxXls3Y-h;kvZLpEZSq4|gng8u2oc*^{ zWDI@YXKbg3YwG=_PNSD<4h-lx<;k*ZTNYQGVW)K!U7NQu_H@R$VjHHuv&AsWK#U_$ zH8MfJt*Py}%uKh67&DWGnX@iGykz7OMqn?x0n4zg9J_Y`Yc`u@_Lig#wMkf_>Dno{ z$|TjE_v%$K4||LiV@w+CKC^FbX%hx{qz4nqYtyn0&9bC#*lPtD_{AWoAa0T@y%JBg z;#{fiz2lGKuq+)Lyhu0VjnG6RdnaqUF=hne-N77MR}oqI_TFRR)(8aKBwqxirU+E^ HNWOmnFmqTS delta 1351 zcmZvcO>7%Q6vyYS*WYU=cAVno!#Lk@g5&Vf(zI?$TeV3jDTGRCL!+3ry$KG^+M3-c zI9-<@Dx@B`@Twj_l^g^Ij#VnomAFu;*CMfixKacXLgF0et!Wbiw&v$IGjC>||MTDd zHT%nna3vH9F!7oH{8c{SH1>^X#uZ@f1F+aCTZKAozHdqmmnm<WSm17>hlu>uxt9a;@r z{-O_){vjyg{b!!Vlm38ueFK2ecR{qvOi#$nY^i+?tR4im88usJZP9N9KVx^K4_(c2 zD04v+Ru5he>h9+625c~kH6l++0Wp7xr2?3hzr&Z^eHe8Q!V$cG_%Pma{{#wN^DIiB zp&IMOxHk+(amd>j_kb)Vq>gfNxM-OKaM}gy6_|KIY{q3^nN146y}uq z!Cs*q?;}J6Y`0M@mrUMI0|Nxom=EF)%9)vA3P%VsVU+M3AxTIR#t0dKRFual9w!_n zJWqIma0Hh_ufs9?I`pDq``*=R`HISmd;jhkQgm+&`uFmvmly5^-5bw;A0qQ8Gy4m-qHh0J8!nkN^lw*k-y*^-i~718IiXs zXEXUpPG_eiK84w~lZ$&I9TY}n@mC3M!fOJy-z=?ZhN-Sqo9d7pTp~EyIVyUodx3Bf zAGM8lk$9=lKFRj0)e}23^oB^VmBKpbnr`Ok1b&5vym_^(>IID-qs=C8DK-X6_;G9w zR>XaGM^4bj(nZ@N)@d6#p2bjmm*)m0Pwr&edt9TZMEqDhJM3|SMz0d6%U>a!CM*i1 zfY0K!_$=gh9>l+ZsXa-^H+)9Gc9r!1T8C!;tq^~k#+(bw;JuDsPt#cVW5>vaJWV-j zwC|~5%T@6w?Ql`889B3()7~>Ry|~{|w5mWT5=bnr;aq3u#IgwOR<2dcnqD&A$>rBg zv!e6Ml+hH>&UMPt8iIQJ-X5a=s>Rf{wp6ycS_nb~{62)j`&D3(@stt8T z<4^SvLOF&!;6+s|a^6!u5lCA;NP@h}RpIj(hPx+*X})Q4M}oJgdteK)^S4;(?uUN7 m)m`p5PC4Z7=3}=)QH`KtN8(f@83o^{;*Nqc5QWxB#r-#aog9V$ diff --git a/handlers/keys.py b/handlers/keys.py index c76f5b83..85ceac7c 100644 --- a/handlers/keys.py +++ b/handlers/keys.py @@ -31,10 +31,18 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery): # Создаем клавиатуру с кнопками inline_keyboard = types.InlineKeyboardMarkup(inline_keyboard=buttons) response_message = "Выберите ключ для просмотра информации:" + + # Редактируем сообщение с клавиатурой + await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=inline_keyboard) else: + # Если нет ключей, добавляем кнопку "Создать ключ" response_message = "У вас нет ключей." - - await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=inline_keyboard) + + # Кнопка "Создать ключ" + create_key_button = types.InlineKeyboardButton(text='Создать ключ', callback_data='create_key') + keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[create_key_button]]) + + await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=keyboard) except Exception as e: await handle_error(tg_id, callback_query, f"Ошибка при получении ключей: {e}") From 4316f4439015641951de7f60b0762e93f471bfcd Mon Sep 17 00:00:00 2001 From: Vlad Date: Fri, 20 Sep 2024 23:24:39 +0300 Subject: [PATCH 06/12] =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=B2=20=D0=BC=D0=BE=D0=B8=D1=85=20=D0=BA=D0=BB=D1=8E?= =?UTF-8?q?=D1=87=D0=B0=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot.py | 4 +- client.py | 36 ++++---- database.py | 62 +++++-------- handlers/__pycache__/keys.cpython-310.pyc | Bin 7786 -> 7777 bytes handlers/expiry.py | 33 ------- handlers/keys.py | 19 ++-- handlers/stats.py | 45 --------- key_management.py | 107 ++++++---------------- 8 files changed, 82 insertions(+), 224 deletions(-) delete mode 100644 handlers/expiry.py delete mode 100644 handlers/stats.py diff --git a/bot.py b/bot.py index cdc9dc8b..3a8f1b84 100644 --- a/bot.py +++ b/bot.py @@ -8,7 +8,7 @@ storage = MemoryStorage() dp = Dispatcher(bot=bot, storage=storage) router = Router() -from handlers import start, profile, keys, stats, expiry, balance, pay +from handlers import start, profile, keys, balance, pay from key_management import router as key_management_router @@ -16,8 +16,6 @@ from key_management import router as key_management_router dp.include_router(start.router) dp.include_router(profile.router) dp.include_router(keys.router) -dp.include_router(stats.router) -dp.include_router(expiry.router) dp.include_router(balance.router) dp.include_router(key_management_router) dp.include_router(pay.router) diff --git a/client.py b/client.py index 7b699364..a6ae8247 100644 --- a/client.py +++ b/client.py @@ -9,10 +9,8 @@ import aiosqlite def add_client(session, client_id: str, email: str, tg_id: str, limit_ip: int, total_gb: int, expiry_time: int, enable: bool, flow: str): url = 'https://solonet.pocomacho.ru:62553/solonet/panel/api/inbounds/addClient' - # Преобразуем email в нижний регистр email = email.lower() - # Формируем данные клиента client_data = { "id": client_id, "alterId": 0, @@ -26,22 +24,19 @@ def add_client(session, client_id: str, email: str, tg_id: str, limit_ip: int, t "flow": flow, } - # Преобразуем данные клиента в строку JSON settings = json.dumps({"clients": [client_data]}) - # Формируем тело запроса data = { - "id": 1, # Если id динамически изменяется, замените это значение + "id": 1, "settings": settings } headers = { - 'Content-Type': 'application/json', # Если требуется токен авторизации + 'Content-Type': 'application/json', } response = session.post(url, json=data, headers=headers) - # Выводим информацию о запросе и ответе print(f"Запрос на добавление клиента: {data}") print(f"Статус ответа: {response.status_code}") print(f"Ответ от сервера: {response.text}") @@ -51,10 +46,7 @@ def add_client(session, client_id: str, email: str, tg_id: str, limit_ip: int, t else: print(f"Ошибка при добавлении клиента: {response.status_code}, {response.text}") -import json - def extend_client_key(session, tg_id, client_id, email: str, new_expiry_time: int) -> bool: - # Получаем текущие данные клиента response = session.get(f"https://solonet.pocomacho.ru:62553/solonet/panel/api/inbounds/getClientTraffics/{email}") print(f"GET {response.url} Status: {response.status_code}") print(f"GET Response: {response.text}") @@ -70,25 +62,21 @@ def extend_client_key(session, tg_id, client_id, email: str, new_expiry_time: in print("Не удалось получить данные клиента.") return False - # Получаем текущий срок действия current_expiry_time = client_data.get('expiryTime', 0) - # Если нет текущего срока, используем новый срок if current_expiry_time == 0: current_expiry_time = new_expiry_time - # Определяем новый срок окончания updated_expiry_time = max(current_expiry_time, new_expiry_time) - # Формируем данные для обновления payload = { - "id": 1, # Если id динамически изменяется, замените это значение + "id": 1, "settings": json.dumps({ "clients": [ { "id": client_id, "alterId": 0, - "email": email.lower(), # Приведение email к нижнему регистру + "email": email.lower(), "limitIp": 2, "totalGB": 429496729600000, "expiryTime": updated_expiry_time, @@ -117,6 +105,22 @@ def extend_client_key(session, tg_id, client_id, email: str, new_expiry_time: in else: print(f"Ошибка при продлении ключа: {response.status_code} - {response.text}") return False + except Exception as e: + print(f"Ошибка запроса: {e}") + return False + +def delete_client(session, client_id: str) -> bool: + url = f"https://solonet.pocomacho.ru:62553/solonet/panel/api/inbounds/1/delClient/{client_id}" + headers = { + 'Accept': 'application/json' + } + try: + response = session.post(url, headers=headers) + if response.status_code == 200: + return True + else: + print(f"Ошибка при удалении клиента: {response.status_code} - {response.text}") + return False except Exception as e: print(f"Ошибка запроса: {e}") return False \ No newline at end of file diff --git a/database.py b/database.py index 4c9ea929..4497275a 100644 --- a/database.py +++ b/database.py @@ -1,48 +1,46 @@ import aiosqlite from datetime import datetime from config import DATABASE_PATH -import aiosqlite async def init_db(): async with aiosqlite.connect(DATABASE_PATH) as db: await db.execute(''' CREATE TABLE IF NOT EXISTS connections ( - tg_id INTEGER NOT NULL, - client_id TEXT NOT NULL, - email TEXT NOT NULL, - expiry_time INTEGER NOT NULL, + tg_id INTEGER PRIMARY KEY NOT NULL, balance REAL NOT NULL DEFAULT 0.0, - PRIMARY KEY (tg_id, client_id) + trial INTEGER NOT NULL DEFAULT 0 ) ''') await db.execute(''' CREATE TABLE IF NOT EXISTS keys ( - tg_id INTEGER NOT NULL, -- Добавлено поле tg_id + tg_id INTEGER NOT NULL, client_id TEXT NOT NULL, email TEXT NOT NULL, created_at INTEGER NOT NULL, + expiry_time INTEGER NOT NULL, key TEXT NOT NULL, - PRIMARY KEY (tg_id, client_id) -- Изменено на (tg_id, client_id) + PRIMARY KEY (tg_id, client_id) ) ''') await db.commit() -async def add_connection(tg_id: int, client_id: str, email: str, expiry_time: int, balance: float = 0.0): +async def add_connection(tg_id: int, balance: float = 0.0, trial: int = 0): async with aiosqlite.connect(DATABASE_PATH) as db: await db.execute(''' - INSERT INTO connections (tg_id, client_id, email, expiry_time, balance) - VALUES (?, ?, ?, ?, ?) - ''', (tg_id, client_id, email, expiry_time, balance)) + INSERT INTO connections (tg_id, balance, trial) + VALUES (?, ?, ?) + ''', (tg_id, balance, trial)) await db.commit() -async def store_key(tg_id: int, client_id: str, email: str, key: str): +async def store_key(tg_id: int, client_id: str, email: str, expiry_time: int, key: str): async with aiosqlite.connect(DATABASE_PATH) as db: await db.execute(''' - INSERT INTO keys (tg_id, client_id, email, created_at, key) - VALUES (?, ?, ?, ?, ?) - ''', (tg_id, client_id, email, int(datetime.utcnow().timestamp() * 1000), key)) + INSERT INTO keys (tg_id, client_id, email, created_at, expiry_time, key) + VALUES (?, ?, ?, ?, ?, ?) + ''', (tg_id, client_id, email, int(datetime.utcnow().timestamp() * 1000), expiry_time, key)) await db.commit() + async def get_keys(tg_id: int): async with aiosqlite.connect(DATABASE_PATH) as db: async with db.execute(''' @@ -54,33 +52,15 @@ async def get_keys(tg_id: int): async def has_active_key(tg_id: int) -> bool: async with aiosqlite.connect(DATABASE_PATH) as db: - async with db.execute("SELECT COUNT(*) FROM connections WHERE tg_id = ? AND expiry_time > ?", - (tg_id, int(datetime.utcnow().timestamp() * 1000))) as cursor: + async with db.execute("SELECT COUNT(*) FROM keys WHERE tg_id = ?", (tg_id,)) as cursor: count = await cursor.fetchone() return count[0] > 0 -async def get_active_key_email(tg_id: int) -> str: - async with aiosqlite.connect(DATABASE_PATH) as db: - async with db.execute("SELECT email FROM connections WHERE tg_id = ? AND expiry_time > ?", - (tg_id, int(datetime.utcnow().timestamp() * 1000))) as cursor: - record = await cursor.fetchone() - return record[0] if record else None - -async def get_key_expiry_time(tg_id: int) -> datetime: - async with aiosqlite.connect(DATABASE_PATH) as db: - async with db.execute("SELECT expiry_time FROM connections WHERE tg_id = ? AND expiry_time > ?", - (tg_id, int(datetime.utcnow().timestamp() * 1000))) as cursor: - record = await cursor.fetchone() - if record: - expiry_time = record[0] - return datetime.utcfromtimestamp(expiry_time / 1000) - return None - -async def get_balance(tg_id: int) -> str: +async def get_balance(tg_id: int) -> float: async with aiosqlite.connect(DATABASE_PATH) as db: async with db.execute("SELECT balance FROM connections WHERE tg_id = ?", (tg_id,)) as cursor: record = await cursor.fetchone() - return record[0] if record else "Неизвестно" + return record[0] if record else 0.0 async def update_balance(tg_id: int, amount: float): async with aiosqlite.connect(DATABASE_PATH) as db: @@ -91,8 +71,14 @@ async def update_balance(tg_id: int, amount: float): ''', (amount, tg_id)) await db.commit() +async def get_trial(tg_id: int) -> int: + async with aiosqlite.connect(DATABASE_PATH) as db: + async with db.execute("SELECT trial FROM connections WHERE tg_id = ?", (tg_id,)) as cursor: + record = await cursor.fetchone() + return record[0] if record else 0 + async def get_key_count(tg_id: int) -> int: async with aiosqlite.connect(DATABASE_PATH) as db: - async with db.execute('SELECT COUNT(*) FROM keys k JOIN connections c ON k.client_id = c.client_id WHERE c.tg_id = ?', (tg_id,)) as cursor: + async with db.execute('SELECT COUNT(*) FROM keys WHERE tg_id = ?', (tg_id,)) as cursor: count = await cursor.fetchone() return count[0] if count else 0 diff --git a/handlers/__pycache__/keys.cpython-310.pyc b/handlers/__pycache__/keys.cpython-310.pyc index 59464d1ffd1cebec7dc797e4b8d8d2836184a9dc..458ddd96f3d32c6a69673f65cbff9c395013d75d 100644 GIT binary patch delta 1880 zcmZ{k&u<$=6vsVl$9C*p|BCH6cI<8fO>tB5gG6a5ZbE*MgfwXr5(*|@+1`z_iPuhN zH-v7VOAK9G{c_n>$BuI|Zth)uC6&azh*v!X6#B|dhQeY!vE*8|yr z9?S-bO%?9hvLQ9}uuX9*t%tTRiQ-YoW_Vptyoz|}xJR-P?v#GpUv@ zvt#lViLfDIlC+=f3f~bj#l{>j*tgWZld|JOLL~NU%WcxdR-G3}2m8c1NzSovog>}| zWQuY`ncymsQTB7Vlihbk2IJs)B+w9iERSL4scGzL<_2v$ji`m;upKaR^28Mo$Tx6q z%)QkICts4c*x#+om(5a6Q{-!Mv5#9Gx#_tzJH0$DZ`Hi8ZcH9uoPyY6&u=8bOz(ay zg_m&5t}I+nJE)8&b|Jc%Q(W}c$MY;FzTNX8y3Ql`-CK<9vS!jAoVb9H5HGQx#EH3! zXkJ1%5HBP85PbW7v<47^fOLpdD3OrV9Yeu2r6|aPM`fDA_v-3UFrq&0yiX`41_-9)*Vrnj_7TT0y)%h_G03tl^& zT~nG0B~sHoRguc@0s;vnRH@IA_zDGq5Dz?{@;m{?Qdu1oVo1z zpEJMZ{>sTUS@yfw@AoTD%1<818SpKydITr1q}#>CQl(T?bthO~ z)=IYif1iEut^^d z^dlt1Uizyvbux?10fZaz2I5VGg4mB3L<})xqa=y#VZ;%{0mMPXVOo~Y!5IBUJ`_KO z13ARb06&#`V1)Jr=iw?{4xYGh5*p{nk(+y;Nm#JPsq;Ek$;#fIept9>h5Yr`ioePXq)Pic99j?i^yC{@5|Jib`eW9s2B$hMAo9X7V{6-7Fauash`WwN418 zC~iqmILEtPY-Eh}o|6$h(#xc#>XubhOO?uENqxUa=tnUhl<7un9t>7QnS;FYr_HE7Um$3GRvNO1V>}y&=)?4@ zQ?2sBYnTKOGNK*fV{k;ZW}2-0c#dm0%(aSdYeoHC>0{4|MRfGy*@+^NC0r(iVbnid zHpp!*mg81ymQ8NZVpn3gfNl--EG7)Q_#CfQS5c%F%Saz~DR7@Y?lR#H9qGOlZC)W+ zal8KM=iR9z%~-K`^Ge)Rgeu$kd-hoPslu8M6mUN!Dl72V$CEY)gtTPOUSjy%1c zoAY&@+$P7F1G$R8{fL3MgWxA@9f`N%%;5NPW=*SChV^mGKC7Q0?DByGVg8SSC`fHl TkoHBvHz0Xft1k-Samn)^i$;~c diff --git a/handlers/expiry.py b/handlers/expiry.py deleted file mode 100644 index 71295447..00000000 --- a/handlers/expiry.py +++ /dev/null @@ -1,33 +0,0 @@ -from aiogram import types, Router -from database import get_active_key_email -from datetime import datetime -import aiosqlite -from config import DATABASE_PATH - -router = Router() - -@router.callback_query(lambda c: c.data == 'view_expiry') -async def process_callback_view_expiry(callback_query: types.CallbackQuery): - tg_id = callback_query.from_user.id - - try: - email = await get_active_key_email(tg_id) - if email: - async with aiosqlite.connect(DATABASE_PATH) as db: - async with db.execute("SELECT expiry_time FROM connections WHERE tg_id = ? AND expiry_time > ?", - (tg_id, int(datetime.utcnow().timestamp() * 1000))) as cursor: - record = await cursor.fetchone() - if record: - expiry_time = record[0] - expiry_date = datetime.utcfromtimestamp(expiry_time / 1000).strftime("%Y-%m-%d %H:%M:%S") - message_text = f"Дата окончания вашего ключа: {expiry_date}" - else: - message_text = "У вас нет активных ключей." - else: - message_text = "У вас нет активных ключей." - - except Exception as e: - message_text = f"Ошибка при получении даты окончания ключа: {e}" - - await callback_query.message.reply(message_text) - await callback_query.answer() diff --git a/handlers/keys.py b/handlers/keys.py index 85ceac7c..eddccbcc 100644 --- a/handlers/keys.py +++ b/handlers/keys.py @@ -2,7 +2,7 @@ from aiogram import types, Router import aiosqlite from bot import bot from datetime import datetime, timedelta -from database import get_balance, update_balance +from database import get_balance, update_balance, get_trial from client import extend_client_key, login_with_credentials from config import ADMIN_USERNAME, DATABASE_PATH, ADMIN_PASSWORD @@ -16,7 +16,7 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery): try: async with aiosqlite.connect(DATABASE_PATH) as db: async with db.execute(''' - SELECT email FROM connections WHERE tg_id = ? + SELECT email FROM keys WHERE tg_id = ? ''', (tg_id,)) as cursor: records = await cursor.fetchall() @@ -49,6 +49,7 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery): await callback_query.answer() + # Обработка запроса на просмотр информации о ключе @router.callback_query(lambda c: c.data.startswith('view_key_')) async def process_callback_view_key(callback_query: types.CallbackQuery): @@ -58,10 +59,10 @@ async def process_callback_view_key(callback_query: types.CallbackQuery): try: async with aiosqlite.connect(DATABASE_PATH) as db: async with db.execute(''' - SELECT k.key, c.expiry_time + SELECT k.key, k.expiry_time FROM keys k - JOIN connections c ON k.client_id = c.client_id - WHERE c.tg_id = ? AND c.email = ? + JOIN connections c ON k.tg_id = c.tg_id + WHERE k.tg_id = ? AND k.email = ? ''', (tg_id, key_name)) as cursor: record = await cursor.fetchone() @@ -105,7 +106,7 @@ async def process_callback_renew_key(callback_query: types.CallbackQuery): try: async with aiosqlite.connect(DATABASE_PATH) as db: - async with db.execute('SELECT client_id, email, expiry_time FROM connections WHERE tg_id = ?', (tg_id,)) as cursor: + async with db.execute('SELECT client_id, email, expiry_time FROM keys WHERE tg_id = ?', (tg_id,)) as cursor: record = await cursor.fetchone() if record: @@ -145,7 +146,7 @@ async def process_callback_renew_plan(callback_query: types.CallbackQuery): try: async with aiosqlite.connect(DATABASE_PATH) as db: - async with db.execute('SELECT client_id, email, expiry_time FROM connections WHERE tg_id = ?', (tg_id,)) as cursor: + async with db.execute('SELECT client_id, email, expiry_time FROM keys WHERE tg_id = ?', (tg_id,)) as cursor: record = await cursor.fetchone() if record: @@ -182,7 +183,7 @@ async def process_callback_renew_plan(callback_query: types.CallbackQuery): if success: await update_balance(tg_id, -cost) # Списание средств с баланса - await db.execute('UPDATE connections SET expiry_time = ? WHERE client_id = ?', (new_expiry_time, client_id)) + await db.execute('UPDATE keys SET expiry_time = ? WHERE client_id = ?', (new_expiry_time, client_id)) await db.commit() response_message = f"Ваш ключ был успешно продлен на {days_to_extend // 30} месяц(-)." back_button = types.InlineKeyboardButton(text='Назад', callback_data='view_keys') @@ -207,4 +208,4 @@ async def delete_previous_message(callback_query: types.CallbackQuery): # Обработка ошибок async def handle_error(tg_id, callback_query, message): await delete_previous_message(callback_query) - await bot.send_message(tg_id, message) + await bot.send_message(tg_id, message) \ No newline at end of file diff --git a/handlers/stats.py b/handlers/stats.py deleted file mode 100644 index c0b1ae12..00000000 --- a/handlers/stats.py +++ /dev/null @@ -1,45 +0,0 @@ -from aiogram import types, Router -import re -from database import get_active_key_email -from auth import link, login_with_credentials -from datetime import datetime -import aiosqlite -from config import DATABASE_PATH -from config import ADMIN_USERNAME, ADMIN_PASSWORD - - -router = Router() -session = login_with_credentials(ADMIN_USERNAME, ADMIN_PASSWORD) - -@router.callback_query(lambda c: c.data == 'view_stats') -async def process_callback_view_stats(callback_query: types.CallbackQuery): - tg_id = callback_query.from_user.id - - try: - email = await get_active_key_email(tg_id) - if email: - async with aiosqlite.connect(DATABASE_PATH) as db: - async with db.execute("SELECT client_id FROM connections WHERE tg_id = ? AND expiry_time > ?", - (tg_id, int(datetime.utcnow().timestamp() * 1000))) as cursor: - record = await cursor.fetchone() - if record: - client_id = record[0] - connection_link = link(session, client_id, email) - - up_match = re.search(r'up=(\d+)', connection_link) - down_match = re.search(r'down=(\d+)', connection_link) - - up = up_match.group(1) if up_match else "Неизвестно" - down = down_match.group(1) if down_match else "Неизвестно" - - statistics = f"Статистика вашего ключа:\nЗагрузка: {up} MB\nВыгрузка: {down} MB" - else: - statistics = "У вас нет активных ключей." - else: - statistics = "У вас нет активных ключей." - - except Exception as e: - statistics = f"Ошибка при получении статистики: {e}" - - await callback_query.message.reply(f"Ваша статистика:\n{statistics}") - await callback_query.answer() diff --git a/key_management.py b/key_management.py index e0c3443d..417326e7 100644 --- a/key_management.py +++ b/key_management.py @@ -10,13 +10,12 @@ from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup from auth import login_with_credentials, link -from client import add_client, extend_client_key +from client import add_client from config import API_TOKEN, ADMIN_PASSWORD, ADMIN_USERNAME, ADMIN_CHAT_ID, DATABASE_PATH from database import add_connection, has_active_key, get_balance, store_key, update_balance from bot import dp from handlers.start import start_command from handlers.profile import process_callback_view_profile -from handlers.keys import process_callback_view_keys from bot import bot router = Router() @@ -27,7 +26,6 @@ def sanitize_key_name(key_name: str) -> str: class Form(StatesGroup): waiting_for_key_name = State() - waiting_for_expiry_date = State() viewing_profile = State() # Обработка нажатия кнопки создания ключа @@ -41,10 +39,10 @@ async def process_callback_create_key(callback_query: CallbackQuery, state: FSMC "Хотите продолжить?", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text='Да, создать новый ключ', callback_data='confirm_create_new_key')], - [InlineKeyboardButton(text='Назад', callback_data='cancel_create_key')] # Изменили текст на "Назад" + [InlineKeyboardButton(text='Назад', callback_data='cancel_create_key')] ]) ) - await state.update_data(creating_new_key=True) # Сохраняем состояние создания нового ключа + await state.update_data(creating_new_key=True) else: await callback_query.message.edit_text("Вам будет выдан пробный ключ. Пожалуйста, выберите имя для вашего ключа:") await state.set_state(Form.waiting_for_key_name) @@ -53,31 +51,21 @@ async def process_callback_create_key(callback_query: CallbackQuery, state: FSMC @dp.callback_query(F.data == 'cancel_create_key') async def cancel_create_key(callback_query: CallbackQuery, state: FSMContext): - await process_callback_view_profile(callback_query, state) # Возвращаем в профиль + await process_callback_view_profile(callback_query, state) await callback_query.answer() - - @dp.callback_query(F.data == 'confirm_create_new_key') async def confirm_create_new_key(callback_query: CallbackQuery, state: FSMContext): await callback_query.message.edit_text("Пожалуйста, выберите имя для вашего нового ключа:") await state.set_state(Form.waiting_for_key_name) - await state.update_data(creating_new_key=True) # Убедитесь, что это состояние сохранено + await state.update_data(creating_new_key=True) await callback_query.answer() -@dp.callback_query(F.data == 'cancel_create_key') -async def cancel_create_key(callback_query: CallbackQuery): - await callback_query.message.edit_text("Создание нового ключа отменено.") - await callback_query.answer() - - # Обработка текстовых сообщений @dp.message() async def handle_text(message: Message, state: FSMContext): current_state = await state.get_state() - print(f"Received message: {message.text}, Current state: {current_state}") - - # Обработка команд и переходов + if message.text == "Мой профиль": callback_query = types.CallbackQuery( id="1", @@ -93,7 +81,6 @@ async def handle_text(message: Message, state: FSMContext): await start_command(message) return - # Если ожидается имя ключа if current_state == Form.waiting_for_key_name.state: await handle_key_name_input(message, state) @@ -106,7 +93,6 @@ async def handle_key_name_input(message: Message, state: FSMContext): await state.clear() return - # Получаем данные состояния data = await state.get_data() creating_new_key = data.get('creating_new_key', False) @@ -125,15 +111,23 @@ async def handle_key_name_input(message: Message, state: FSMContext): await state.clear() return - await update_balance(tg_id, -100) # Списание 100 рублей за новый ключ + await update_balance(tg_id, -100) try: - # Создание клиента и получение ссылки add_client(session, client_id, email, tg_id, limit_ip=1, total_gb=0, expiry_time=expiry_time, enable=True, flow="xtls-rprx-vision") connection_link = link(session, client_id, email) - await add_connection(tg_id, client_id, email, expiry_time, 0) - await store_key(tg_id, client_id, email, connection_link) + # Проверка существующей записи + async with aiosqlite.connect(DATABASE_PATH) as db: + cursor = await db.execute('SELECT * FROM connections WHERE tg_id = ?', (tg_id,)) + existing_connection = await cursor.fetchone() + + if existing_connection: + await db.execute('UPDATE connections SET trial = 1 WHERE tg_id = ?', (tg_id,)) + else: + await add_connection(tg_id, 0, 1) + + await store_key(tg_id, client_id, email, expiry_time, connection_link) keyboard = InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text='Инструкции по использованию', callback_data='instructions')], @@ -144,45 +138,9 @@ async def handle_key_name_input(message: Message, state: FSMContext): await message.reply(key_message, parse_mode="HTML", reply_markup=keyboard) except Exception as e: await message.reply(f"Ошибка при создании ключа: {e}") - print(f"Ошибка при создании ключа: {e}") await state.clear() - -# Обработка нажатия кнопки создания ключа -@dp.callback_query(F.data == 'create_key') -async def process_callback_create_key(callback_query: CallbackQuery, state: FSMContext): - tg_id = callback_query.from_user.id - - if await has_active_key(tg_id): - await callback_query.message.edit_text( - "У вас уже есть активный ключ. Вы можете создать новый ключ за дополнительную плату в размере 100 рублей. " - "Хотите продолжить?", - reply_markup=InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text='Да, создать новый ключ', callback_data='confirm_create_new_key')], - [InlineKeyboardButton(text='Нет, оставить текущий', callback_data='cancel_create_key')] - ]) - ) - await state.update_data(creating_new_key=True) # Сохраняем состояние создания нового ключа - else: - await callback_query.message.edit_text("Вам будет выдан пробный ключ. Пожалуйста, выберите имя для вашего ключа:") - await state.set_state(Form.waiting_for_key_name) - - await callback_query.answer() - - -@dp.callback_query(F.data == 'confirm_create_new_key') -async def confirm_create_new_key(callback_query: CallbackQuery, state: FSMContext): - await callback_query.message.edit_text("Пожалуйста, выберите имя для вашего нового ключа:") - await state.set_state(Form.waiting_for_key_name) - await callback_query.answer() - -@dp.callback_query(F.data == 'cancel_create_key') -async def cancel_create_key(callback_query: CallbackQuery): - await callback_query.message.edit_text("Создание нового ключа отменено.") - await callback_query.answer() - -# Обработка кнопки "Инструкции" @dp.callback_query(F.data == 'instructions') async def handle_instructions(callback_query: CallbackQuery): instructions_message = ( @@ -202,47 +160,36 @@ async def handle_instructions(callback_query: CallbackQuery): await callback_query.message.edit_text(instructions_message, parse_mode='Markdown', reply_markup=keyboard) await callback_query.answer() -# Обработка кнопки "Назад" @dp.callback_query(F.data == 'back_to_main') -async def handle_back_to_main(callback_query: CallbackQuery): - tg_id = callback_query.from_user.id - new_callback_query = CallbackQuery( - id=callback_query.id, - from_user=callback_query.from_user, - chat_instance=callback_query.chat_instance, - data='view_keys', - message=callback_query.message - ) - - await process_callback_view_keys(new_callback_query) +async def handle_back_to_main(callback_query: CallbackQuery, state: FSMContext): + await process_callback_view_profile(callback_query, state) await callback_query.answer() -# Фоновая задача для продления ключей async def renew_expired_keys(): while True: current_time = datetime.utcnow() async with aiosqlite.connect(DATABASE_PATH) as db: - async with db.execute('SELECT tg_id, client_id FROM connections WHERE expiry_time <= ?', (int(current_time.timestamp() * 1000),)) as cursor: - expired_keys = await cursor.fetchall() + async with db.execute('SELECT tg_id FROM connections WHERE trial > 0') as cursor: + active_keys = await cursor.fetchall() - for tg_id, client_id in expired_keys: + for tg_id, in active_keys: balance = await get_balance(tg_id) if balance >= 100: new_expiry_time = int((current_time + timedelta(days=30)).timestamp() * 1000) async with aiosqlite.connect(DATABASE_PATH) as db: - await db.execute('UPDATE connections SET expiry_time = ? WHERE tg_id = ? AND client_id = ?', (new_expiry_time, tg_id, client_id)) + await db.execute('UPDATE keys SET expiry_time = ? WHERE tg_id = ?', (new_expiry_time, tg_id)) await db.commit() await update_balance(tg_id, -100) - await extend_client_key(client_id) - print(f"Ключ для клиента {client_id} продлен на месяц и списано 100 рублей.") + print(f"Ключ для пользователя {tg_id} продлен на месяц и списано 100 рублей.") else: - print(f"Недостаточно средств на балансе для клиента {client_id}. Предложение пополнить баланс.") + print(f"Недостаточно средств на балансе для пользователя {tg_id}. Предложение пополнить баланс.") replenish_keyboard = InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text='Пополнить баланс', callback_data='replenish_balance')] ]) await bot.send_message(tg_id, "Ваш баланс недостаточен для продления ключа. Пожалуйста, пополните баланс.", reply_markup=replenish_keyboard) await asyncio.sleep(3600) + From 51b46bfe656473a9f24de1377c8148fd8ee64b60 Mon Sep 17 00:00:00 2001 From: Vlad Date: Sat, 21 Sep 2024 00:21:23 +0300 Subject: [PATCH 07/12] =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D1=85=D0=BE?= =?UTF-8?q?=D0=B4=20=D0=BD=D0=B0=20postresql?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client.py | 6 - database.py | 130 ++++----- handlers/__pycache__/keys.cpython-310.pyc | Bin 7777 -> 7272 bytes handlers/__pycache__/pay.cpython-310.pyc | Bin 8705 -> 8680 bytes handlers/__pycache__/profile.cpython-310.pyc | Bin 2420 -> 2355 bytes handlers/keys.py | 276 ++++++++++--------- handlers/pay.py | 55 ++-- handlers/profile.py | 2 - key_management.py | 48 ++-- 9 files changed, 266 insertions(+), 251 deletions(-) diff --git a/client.py b/client.py index a6ae8247..8522dc46 100644 --- a/client.py +++ b/client.py @@ -1,10 +1,4 @@ -import requests import json -from config import ADMIN_USERNAME, ADMIN_PASSWORD, GET_INBOUNDS_URL, DATABASE_PATH -import uuid -from auth import login_with_credentials -from datetime import datetime, timedelta -import aiosqlite def add_client(session, client_id: str, email: str, tg_id: str, limit_ip: int, total_gb: int, expiry_time: int, enable: bool, flow: str): url = 'https://solonet.pocomacho.ru:62553/solonet/panel/api/inbounds/addClient' diff --git a/database.py b/database.py index 4497275a..983762fe 100644 --- a/database.py +++ b/database.py @@ -1,84 +1,84 @@ -import aiosqlite +import asyncpg from datetime import datetime -from config import DATABASE_PATH +from config import DATABASE_URL # Example: "postgresql://user:password@localhost:5432/dbname" async def init_db(): - async with aiosqlite.connect(DATABASE_PATH) as db: - await db.execute(''' - CREATE TABLE IF NOT EXISTS connections ( - tg_id INTEGER PRIMARY KEY NOT NULL, - balance REAL NOT NULL DEFAULT 0.0, - trial INTEGER NOT NULL DEFAULT 0 - ) - ''') - await db.execute(''' - CREATE TABLE IF NOT EXISTS keys ( - tg_id INTEGER NOT NULL, - client_id TEXT NOT NULL, - email TEXT NOT NULL, - created_at INTEGER NOT NULL, - expiry_time INTEGER NOT NULL, - key TEXT NOT NULL, - PRIMARY KEY (tg_id, client_id) - ) - ''') - await db.commit() + conn = await asyncpg.connect(DATABASE_URL) + await conn.execute(''' + CREATE TABLE IF NOT EXISTS connections ( + tg_id BIGINT PRIMARY KEY NOT NULL, + balance REAL NOT NULL DEFAULT 0.0, + trial INTEGER NOT NULL DEFAULT 0 + ) + ''') + await conn.execute(''' + CREATE TABLE IF NOT EXISTS keys ( + tg_id BIGINT NOT NULL, + client_id TEXT NOT NULL, + email TEXT NOT NULL, + created_at BIGINT NOT NULL, + expiry_time BIGINT NOT NULL, + key TEXT NOT NULL, + PRIMARY KEY (tg_id, client_id) + ) + ''') + await conn.close() async def add_connection(tg_id: int, balance: float = 0.0, trial: int = 0): - async with aiosqlite.connect(DATABASE_PATH) as db: - await db.execute(''' - INSERT INTO connections (tg_id, balance, trial) - VALUES (?, ?, ?) - ''', (tg_id, balance, trial)) - await db.commit() + conn = await asyncpg.connect(DATABASE_URL) + await conn.execute(''' + INSERT INTO connections (tg_id, balance, trial) + VALUES ($1, $2, $3) + ''', tg_id, balance, trial) + await conn.close() async def store_key(tg_id: int, client_id: str, email: str, expiry_time: int, key: str): - async with aiosqlite.connect(DATABASE_PATH) as db: - await db.execute(''' - INSERT INTO keys (tg_id, client_id, email, created_at, expiry_time, key) - VALUES (?, ?, ?, ?, ?, ?) - ''', (tg_id, client_id, email, int(datetime.utcnow().timestamp() * 1000), expiry_time, key)) - await db.commit() - + conn = await asyncpg.connect(DATABASE_URL) + await conn.execute(''' + INSERT INTO keys (tg_id, client_id, email, created_at, expiry_time, key) + VALUES ($1, $2, $3, $4, $5, $6) + ''', tg_id, client_id, email, int(datetime.utcnow().timestamp() * 1000), expiry_time, key) + await conn.close() async def get_keys(tg_id: int): - async with aiosqlite.connect(DATABASE_PATH) as db: - async with db.execute(''' - SELECT client_id, email, created_at, key - FROM keys - WHERE tg_id = ? - ''', (tg_id,)) as cursor: - return await cursor.fetchall() + conn = await asyncpg.connect(DATABASE_URL) + records = await conn.fetch(''' + SELECT client_id, email, created_at, key + FROM keys + WHERE tg_id = $1 + ''', tg_id) + await conn.close() + return records async def has_active_key(tg_id: int) -> bool: - async with aiosqlite.connect(DATABASE_PATH) as db: - async with db.execute("SELECT COUNT(*) FROM keys WHERE tg_id = ?", (tg_id,)) as cursor: - count = await cursor.fetchone() - return count[0] > 0 + conn = await asyncpg.connect(DATABASE_URL) + count = await conn.fetchval("SELECT COUNT(*) FROM keys WHERE tg_id = $1", tg_id) + await conn.close() + return count > 0 async def get_balance(tg_id: int) -> float: - async with aiosqlite.connect(DATABASE_PATH) as db: - async with db.execute("SELECT balance FROM connections WHERE tg_id = ?", (tg_id,)) as cursor: - record = await cursor.fetchone() - return record[0] if record else 0.0 + conn = await asyncpg.connect(DATABASE_URL) + balance = await conn.fetchval("SELECT balance FROM connections WHERE tg_id = $1", tg_id) + await conn.close() + return balance if balance is not None else 0.0 async def update_balance(tg_id: int, amount: float): - async with aiosqlite.connect(DATABASE_PATH) as db: - await db.execute(''' - UPDATE connections - SET balance = balance + ? - WHERE tg_id = ? - ''', (amount, tg_id)) - await db.commit() + conn = await asyncpg.connect(DATABASE_URL) + await conn.execute(''' + UPDATE connections + SET balance = balance + $1 + WHERE tg_id = $2 + ''', amount, tg_id) + await conn.close() async def get_trial(tg_id: int) -> int: - async with aiosqlite.connect(DATABASE_PATH) as db: - async with db.execute("SELECT trial FROM connections WHERE tg_id = ?", (tg_id,)) as cursor: - record = await cursor.fetchone() - return record[0] if record else 0 + conn = await asyncpg.connect(DATABASE_URL) + trial = await conn.fetchval("SELECT trial FROM connections WHERE tg_id = $1", tg_id) + await conn.close() + return trial if trial is not None else 0 async def get_key_count(tg_id: int) -> int: - async with aiosqlite.connect(DATABASE_PATH) as db: - async with db.execute('SELECT COUNT(*) FROM keys WHERE tg_id = ?', (tg_id,)) as cursor: - count = await cursor.fetchone() - return count[0] if count else 0 + conn = await asyncpg.connect(DATABASE_URL) + count = await conn.fetchval('SELECT COUNT(*) FROM keys WHERE tg_id = $1', tg_id) + await conn.close() + return count if count is not None else 0 diff --git a/handlers/__pycache__/keys.cpython-310.pyc b/handlers/__pycache__/keys.cpython-310.pyc index 458ddd96f3d32c6a69673f65cbff9c395013d75d..5b23c2fdb32b603107ef2bacfa09ff55692bc3ab 100644 GIT binary patch delta 3616 zcmZveU2q#$6@c&EU9DED)oLZJWl6SUCvj3#LK3H?*saoW=tHMwC>I7wni;tRJn_IxAK{7e!VJ?l7)H!6!%Jx%VCe9`6b8yUSJt12 zX7=o!d+r|Hz4tpme?I;3adR~i(Fy$i`O#l*UODu(c`%+Mj4)}Lu1Gm)Mb62TynXg$ zo#JS4IkIBr%#~;^x?<%lF{UlYR^quhB?WntCT@GveZ2#mtkp^xkqwCjLwnl z&_n#jnXJ?dxEpJQYEzwEz3mnpevMvui8f<}J8q%E@{7x*LdDH56*jVR)0~{TbYUic zZEkvYX7bW>)0mpPI{ED6+;slh?8UbA%H-VK{N>rHENv#2SBs@ee!b-0$}c(v2A)a_ z%T*qUrnxN@XoT0KL%bM`)Ssh-yiPq*CS~f=+8tMF$gZ+YFTft3C%#lGcv2(4s7JS@ zoiW*yor^`{E1o<>ZY2GH7hqDF_^PXxgPzJ{PrXkbNKDz1{Gg{aG%r}B+mw=5_rQ#h zr?9{l^);sYA-ArV!(Pb41sLUsr?KFc()t@pDx*w;Dc}?jQMl8E1HDL#R5%H{{qKi+ z-=8P2baR_@{AvCAI+-V9l|2e^qZ{`r@eN99ao57du&`&~H*Cs$Jhs=4b)7H*M`+B{ zrumHWRC0VmAM5!w1BI1^((?O1P@bV9!G-EZWpS<83@)x#DuqSYG2q;rftv+)@fN4* z_3%ESFD|cE3;bnuUn1h9#fV$XmsnH5*Z6()3v`VCMt%821Z>sgAf&sRBuN&Ps7fVC z|F5Dbl14RZQ9})bv~Y;8^{KuF?p&YKH{5_AO6t1mSe4CvD3+EGVAhya6Yw!1)L8WCaIWBLp0% zLlAd<(A66HHt~T1PfL+<*bjLj7F<%CX{LE0+~o>oAzx>@7r2i*A8Z)RsBcqHBAyNt zR6p!S$|f@zgeO2{2rb6qLW>A3!EB+KphZ2SNWBPLCXgbt0l+QlTIHA*7No_?2`|c$ zY;Y^kVZ%f_OSSE+o@D7Rihz>bu)7pgw0<2?5+n_5{2EAtYy>1F1xXptYLV3bt9kMw z#FlTpW>o^M`#N1G9%&4A{WB89Ofl1>`GWUKm7I;dTUsgbpXeXbVeW=&sZsFNRL)<$bTO+qdqHuKL1#Z#jpxm* z+%t5E=ZzN*A3*Ct6mbyEKy_`oJ$hBJr0PsP*(!_zm=dy`Dt^XiCm9U?XL7$ zTz0N=J$is%;76l->m_uR0@24euCv4UTm32To&QUDZ#**Pz2(Uw%|Y5@vcO#y*hAWD z1h+{;+a@efw+ZAt)eA0B=PV?(P(x>(7@|N4xDI#Ec^*iZBQB&qxJ(vg8VhYnMJhC% zg?lupTP+QMHZgy?QIBr6^K^(sF_u`}g4I|Md;tu;MvTQo9K|05eBF(Er2PUVZlY`h zpgK!H*0uXk?^`0E-lzAV{!2hTjHvH?=zteKg{-%827a$#J^Wf&unzYf)*&Ob<3VK9 zkG%4nznin;w3V$jYEgkVwd2Ys{AYHi zAL=6igZ42OjPL;d~uM56P&WL-7)bEOlnNn>ue{C^`!$EEJ>sx2dnG`{A+nMgDkt z&mh#P`Sa7W(_@`z7PadA8)=E2>jos&6=w!SQ!Z6@mzG`e>AZrIdn5CwnL{T`91Nqt z4#~l|a5NCjVBt<-5j$%IhZqVGy7o@!a(yWC)FWdTrxwsfv{G2l_mom>%Brq&ljnz$ zG{?U&bfu0FTFrsL5VxqYQ?DKo<_9u=vY;{3piMKOSxZV1wFmUSS+W9E&W3bg!j+=7 z6c>$~)h%!v;Jz;b;9=oceL(o-;ZyOw@F*&IR&8z1KK|}-T5atz|HW`-a1mzw9<-vi}xS#Emgc_(j*lHJv%IV0RHNT&EFh( za-^w3uX?joY#jp@TX7amW#P7ai~nxqv4QJ2Rz!i{;)ws7;ITcAAJhnLngttRh)b0W SIZlm3a28 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 delta 864 zcmZvaO-vI(6vyZ7lz!|NrL^0HZlR?h_sg`yNS%9H06+(JlWn8XbXjmn6$Qgf#DnKs;fkWsWQtQT1@uj@7yG80>DORr z2rBO5Q;KfWUGWgU3Pq3U$?qrt%PF<=E%o!tY_bw%PU$ uV4F@xVi2QNB-M~#U`^?6WSCxx&IkHf;zeE%S#LMnein)JQ&hu)3j76>Ce~H} diff --git a/handlers/__pycache__/profile.cpython-310.pyc b/handlers/__pycache__/profile.cpython-310.pyc index a59a06fb99098030a88b72ee01aff68155fbb456..a86006737c40724ac834555bf52e612ac99e00d0 100644 GIT binary patch delta 465 zcmZ8cF-yZh6u!I0OH7kCO;Zs;2SKoih;&e}c5x6o6pFe8p`_jwtErWjR1pV-W^ob9 z`2%!w?|*UV;4Y|>Kft?osUFgQiulDVAuBdWseV z(1-NILFv4p`ZCUlqJD%+;!dxwOx0w=$S8+Fr^$lKj#LqmU?SKO-}-K~Bt?hhq3ptE!G}%q=Jp$X3)QSZW8|_}A`a>zP@9h^bbB!e zPDXfhZa;wYf(gO#dvToWs{kd=&sw&CI?Rn z$;D%sKhXY$oC>{p=`ZMEPu@zwqlX?llU(A!d-HvK?|ttLGY%dCno{Z_ea1iTJ8VRs z!Ozj!(G~MJ;&x1vGAjep*oo&8pZQ6JRR9S*KrGlre2%+A#9iSI3arXY0?tD23Ak!K~rhHL-aJpA>Q8&%L#@(LCNjtshbU#b;1G&SaUet;*Axp;3 zET0pZ_IpA~^VFQai{iKywa<_GLS9O?Bme#)|cTwlPzvj diff --git a/handlers/keys.py b/handlers/keys.py index eddccbcc..f03a708e 100644 --- a/handlers/keys.py +++ b/handlers/keys.py @@ -1,10 +1,11 @@ +import asyncpg from aiogram import types, Router -import aiosqlite from bot import bot from datetime import datetime, timedelta from database import get_balance, update_balance, get_trial -from client import extend_client_key, login_with_credentials -from config import ADMIN_USERNAME, DATABASE_PATH, ADMIN_PASSWORD +from client import extend_client_key +from config import ADMIN_USERNAME, DATABASE_URL, ADMIN_PASSWORD +from auth import login_with_credentials router = Router() @@ -14,35 +15,38 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery): tg_id = callback_query.from_user.id try: - async with aiosqlite.connect(DATABASE_PATH) as db: - async with db.execute(''' - SELECT email FROM keys WHERE tg_id = ? - ''', (tg_id,)) as cursor: - records = await cursor.fetchall() + conn = await asyncpg.connect(DATABASE_URL) + try: + records = await conn.fetch(''' + SELECT email FROM keys WHERE tg_id = $1 + ''', tg_id) - if records: - # Создаем кнопки для каждого ключа - buttons = [] - for record in records: - key_name = record[0] # Предполагается, что email - это название ключа - button = types.InlineKeyboardButton(text=key_name, callback_data=f'view_key_{key_name}') - buttons.append([button]) + if records: + # Создаем кнопки для каждого ключа + buttons = [] + for record in records: + key_name = record['email'] # Предполагается, что email - это название ключа + button = types.InlineKeyboardButton(text=key_name, callback_data=f'view_key_{key_name}') + buttons.append([button]) - # Создаем клавиатуру с кнопками - inline_keyboard = types.InlineKeyboardMarkup(inline_keyboard=buttons) - response_message = "Выберите ключ для просмотра информации:" - - # Редактируем сообщение с клавиатурой - await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=inline_keyboard) - else: - # Если нет ключей, добавляем кнопку "Создать ключ" - response_message = "У вас нет ключей." - - # Кнопка "Создать ключ" - create_key_button = types.InlineKeyboardButton(text='Создать ключ', callback_data='create_key') - keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[create_key_button]]) - - await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=keyboard) + # Создаем клавиатуру с кнопками + inline_keyboard = types.InlineKeyboardMarkup(inline_keyboard=buttons) + response_message = "Выберите ключ для просмотра информации:" + + # Редактируем сообщение с клавиатурой + await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=inline_keyboard) + else: + # Если нет ключей, добавляем кнопку "Создать ключ" + response_message = "У вас нет ключей." + + # Кнопка "Создать ключ" + create_key_button = types.InlineKeyboardButton(text='Создать ключ', callback_data='create_key') + keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[create_key_button]]) + + await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=keyboard) + + finally: + await conn.close() except Exception as e: await handle_error(tg_id, callback_query, f"Ошибка при получении ключей: {e}") @@ -57,146 +61,152 @@ async def process_callback_view_key(callback_query: types.CallbackQuery): key_name = callback_query.data.split('_', 2)[2] # Получаем имя ключа try: - async with aiosqlite.connect(DATABASE_PATH) as db: - async with db.execute(''' + conn = await asyncpg.connect(DATABASE_URL) + try: + record = await conn.fetchrow(''' SELECT k.key, k.expiry_time FROM keys k JOIN connections c ON k.tg_id = c.tg_id - WHERE k.tg_id = ? AND k.email = ? - ''', (tg_id, key_name)) as cursor: - record = await cursor.fetchone() + WHERE k.tg_id = $1 AND k.email = $2 + ''', tg_id, key_name) - if record: - key, expiry_time = record - expiry_date = datetime.utcfromtimestamp(expiry_time / 1000) - current_date = datetime.utcnow() - days_left = (expiry_date - current_date).days + if record: + key = record['key'] + expiry_time = record['expiry_time'] + expiry_date = datetime.utcfromtimestamp(expiry_time / 1000) + current_date = datetime.utcnow() + days_left = (expiry_date - current_date).days - days_left_message = f"Осталось дней: {days_left}" if days_left > 0 else "Ключ истек." - response_message = (f"Ваш ключ:\n
{key}
\n" - f"Дата окончания: {expiry_date.strftime('%Y-%m-%d %H:%M:%S')}\n" - f"{days_left_message}") + days_left_message = f"Осталось дней: {days_left}" if days_left > 0 else "Ключ истек." + response_message = (f"Ваш ключ:\n
{key}
\n" + f"Дата окончания: {expiry_date.strftime('%Y-%m-%d %H:%M:%S')}\n" + f"{days_left_message}") - # Кнопки для продления и инструкций - renew_button = types.InlineKeyboardButton(text='Продлить ключ', callback_data='renew_key') - instructions_button = types.InlineKeyboardButton(text='Инструкции по использованию', callback_data='instructions') - back_button = types.InlineKeyboardButton(text='Назад в профиль', callback_data='view_profile') - keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[renew_button], [instructions_button], [back_button]]) + # Кнопки для продления и инструкций + renew_button = types.InlineKeyboardButton(text='Продлить ключ', callback_data='renew_key') + instructions_button = types.InlineKeyboardButton(text='Инструкции по использованию', callback_data='instructions') + back_button = types.InlineKeyboardButton(text='Назад в профиль', callback_data='view_profile') + keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[renew_button], [instructions_button], [back_button]]) - await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=keyboard, parse_mode="HTML") - else: - await bot.edit_message_text("Информация о ключе не найдена.", chat_id=tg_id, message_id=callback_query.message.message_id, parse_mode="HTML") + await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=keyboard, parse_mode="HTML") + else: + await bot.edit_message_text("Информация о ключе не найдена.", chat_id=tg_id, message_id=callback_query.message.message_id, parse_mode="HTML") + + finally: + await conn.close() except Exception as e: await handle_error(tg_id, callback_query, f"Ошибка при получении информации о ключе: {e}") await callback_query.answer() -# Остальные функции остаются без изменений... - -# Обработка ошибок -async def handle_error(tg_id, callback_query, message): - await bot.edit_message_text(message, chat_id=tg_id, message_id=callback_query.message.message_id, parse_mode="HTML") - # Обработка запроса на продление ключа @router.callback_query(lambda c: c.data == 'renew_key') async def process_callback_renew_key(callback_query: types.CallbackQuery): tg_id = callback_query.from_user.id - + try: - async with aiosqlite.connect(DATABASE_PATH) as db: - async with db.execute('SELECT client_id, email, expiry_time FROM keys WHERE tg_id = ?', (tg_id,)) as cursor: - record = await cursor.fetchone() + conn = await asyncpg.connect(DATABASE_URL) + try: + record = await conn.fetchrow('SELECT client_id, email, expiry_time FROM keys WHERE tg_id = $1', tg_id) - if record: - client_id, email, expiry_time = record - current_time = datetime.utcnow().timestamp() * 1000 - - if expiry_time <= current_time: - await callback_query.message.answer("Ваш ключ уже истек и не может быть продлен.") - return - - # Создаем клавиатуру для выбора плана продления - keyboard = types.InlineKeyboardMarkup(inline_keyboard=[ - [types.InlineKeyboardButton(text='Продлить на 1 месяц (100 руб.)', callback_data='renew_1_month')], - [types.InlineKeyboardButton(text='Продлить на 3 месяца (250 руб.)', callback_data='renew_3_months')], - [types.InlineKeyboardButton(text='Назад', callback_data='view_profile')] - ]) + if record: + client_id = record['client_id'] + email = record['email'] + expiry_time = record['expiry_time'] + current_time = datetime.utcnow().timestamp() * 1000 - balance = await get_balance(tg_id) - response_message = (f"Выберите план продления:\n" - f"Баланс: {balance} руб.\n" - f"Действующий ключ истекает {datetime.utcfromtimestamp(expiry_time / 1000).strftime('%Y-%m-%d %H:%M:%S')}") + if expiry_time <= current_time: + await callback_query.message.answer("Ваш ключ уже истек и не может быть продлен.") + return - await delete_previous_message(callback_query) - await bot.send_message(tg_id, response_message, parse_mode="HTML", reply_markup=keyboard) + # Создаем клавиатуру для выбора плана продления + keyboard = types.InlineKeyboardMarkup(inline_keyboard=[ + [types.InlineKeyboardButton(text='Продлить на 1 месяц (100 руб.)', callback_data='renew_1_month')], + [types.InlineKeyboardButton(text='Продлить на 3 месяца (250 руб.)', callback_data='renew_3_months')], + [types.InlineKeyboardButton(text='Назад', callback_data='view_profile')] + ]) + + balance = await get_balance(tg_id) + response_message = (f"Выберите план продления:\n" + f"Баланс: {balance} руб.\n" + f"Действующий ключ истекает {datetime.utcfromtimestamp(expiry_time / 1000).strftime('%Y-%m-%d %H:%M:%S')}") + + await delete_previous_message(callback_query) + await bot.send_message(tg_id, response_message, parse_mode="HTML", reply_markup=keyboard) + + finally: + await conn.close() except Exception as e: await callback_query.message.answer(f"Ошибка при выборе плана: {e}") await callback_query.answer() + # Обработка выбора плана продления @router.callback_query(lambda c: c.data.startswith('renew_')) async def process_callback_renew_plan(callback_query: types.CallbackQuery): tg_id = callback_query.from_user.id plan = callback_query.data.split('_')[1] # '1' или '3' days_to_extend = 30 * int(plan) # 30 дней или 90 дней - + try: - async with aiosqlite.connect(DATABASE_PATH) as db: - async with db.execute('SELECT client_id, email, expiry_time FROM keys WHERE tg_id = ?', (tg_id,)) as cursor: - record = await cursor.fetchone() - - if record: - client_id = record[0] - email = record[1] - expiry_time = record[2] - current_time = datetime.utcnow().timestamp() * 1000 - - if expiry_time <= current_time: - await callback_query.message.answer("Ваш ключ уже истек и не может быть продлен.") - return - - # Рассчитываем новый срок окончания, добавляя дни в зависимости от выбранного плана - new_expiry_time = int(expiry_time + timedelta(days=days_to_extend).total_seconds() * 1000) - - # Определяем стоимость продления - cost = 100 if plan == '1' else 250 - - # Проверка баланса - balance = await get_balance(tg_id) - if balance < cost: - replenish_button = types.InlineKeyboardButton(text='Пополнить баланс', callback_data='replenish_balance') - back_button = types.InlineKeyboardButton(text='Назад', callback_data='view_keys') - keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[replenish_button], [back_button]]) - - await callback_query.message.answer("Недостаточно средств для продления ключа.", reply_markup=keyboard) - return - - # Создаем сессию для API-запросов - session = login_with_credentials(ADMIN_USERNAME, ADMIN_PASSWORD) - - # Обновляем ключ через API - success = extend_client_key(session, tg_id, client_id, email, new_expiry_time) - - if success: - await update_balance(tg_id, -cost) # Списание средств с баланса - await db.execute('UPDATE keys SET expiry_time = ? WHERE client_id = ?', (new_expiry_time, client_id)) - await db.commit() - response_message = f"Ваш ключ был успешно продлен на {days_to_extend // 30} месяц(-)." - back_button = types.InlineKeyboardButton(text='Назад', callback_data='view_keys') - keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]]) - await bot.send_message(tg_id, response_message, reply_markup=keyboard) - else: - await bot.send_message(tg_id, "Ошибка при продлении ключа.") + conn = await asyncpg.connect(DATABASE_URL) + try: + record = await conn.fetchrow('SELECT client_id, email, expiry_time FROM keys WHERE tg_id = $1', tg_id) + + if record: + client_id = record['client_id'] + email = record['email'] + expiry_time = record['expiry_time'] + current_time = datetime.utcnow().timestamp() * 1000 + + if expiry_time <= current_time: + await callback_query.message.answer("Ваш ключ уже истек и не может быть продлен.") + return + + # Рассчитываем новый срок окончания + new_expiry_time = int(expiry_time + timedelta(days=days_to_extend).total_seconds() * 1000) + + # Определяем стоимость продления + cost = 100 if plan == '1' else 250 + + # Проверка баланса + balance = await get_balance(tg_id) + if balance < cost: + replenish_button = types.InlineKeyboardButton(text='Пополнить баланс', callback_data='replenish_balance') + back_button = types.InlineKeyboardButton(text='Назад', callback_data='view_keys') + keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[replenish_button], [back_button]]) + + await callback_query.message.answer("Недостаточно средств для продления ключа.", reply_markup=keyboard) + return + + # Создаем сессию для API-запросов + session = login_with_credentials(ADMIN_USERNAME, ADMIN_PASSWORD) + + # Обновляем ключ через API + success = extend_client_key(session, tg_id, client_id, email, new_expiry_time) + + if success: + await update_balance(tg_id, -cost) # Списание средств с баланса + await conn.execute('UPDATE keys SET expiry_time = $1 WHERE client_id = $2', new_expiry_time, client_id) + response_message = f"Ваш ключ был успешно продлен на {days_to_extend // 30} месяц(-)." + back_button = types.InlineKeyboardButton(text='Назад', callback_data='view_keys') + keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]]) + await bot.send_message(tg_id, response_message, reply_markup=keyboard) else: - await bot.send_message(tg_id, "У вас нет ключей для продления.") - + await bot.send_message(tg_id, "Ошибка при продлении ключа.") + else: + await bot.send_message(tg_id, "У вас нет ключей для продления.") + + finally: + await conn.close() + except Exception as e: await bot.send_message(tg_id, f"Ошибка при продлении ключа: {e}") - + await callback_query.answer() @@ -208,4 +218,4 @@ async def delete_previous_message(callback_query: types.CallbackQuery): # Обработка ошибок async def handle_error(tg_id, callback_query, message): await delete_previous_message(callback_query) - await bot.send_message(tg_id, message) \ No newline at end of file + await bot.send_message(tg_id, message) diff --git a/handlers/pay.py b/handlers/pay.py index 2ad6a0e9..5d7be13c 100644 --- a/handlers/pay.py +++ b/handlers/pay.py @@ -3,10 +3,11 @@ from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup import aiosqlite -from config import DATABASE_PATH, ADMIN_ID +from config import ADMIN_ID, DATABASE_URL from database import get_balance, update_balance, get_key_count from bot import bot from handlers.profile import process_callback_view_profile +import asyncpg router = Router() @@ -183,36 +184,41 @@ async def process_admin_confirmation(callback_query: types.CallbackQuery, state: state_data = await state.get_data() requisites_message_id = state_data.get('requisites_message_id') - if action == 'confirm': - async with aiosqlite.connect(DATABASE_PATH) as db: + # Подключаемся к базе данных PostgreSQL + conn = await asyncpg.connect(DATABASE_URL) + try: + # Обновляем баланс пользователя await update_balance(user_id, amount) - await db.commit() - balance = await get_balance(user_id) + # Получаем обновленный баланс + balance = await get_balance(user_id) - # Создаем клавиатуру с кнопкой "Профиль" - profile_button = InlineKeyboardButton(text='Профиль', callback_data='view_profile') - profile_keyboard = InlineKeyboardMarkup(inline_keyboard=[[profile_button]]) + # Создаем клавиатуру с кнопкой "Профиль" + profile_button = InlineKeyboardButton(text='Профиль', callback_data='view_profile') + profile_keyboard = InlineKeyboardMarkup(inline_keyboard=[[profile_button]]) - # Отправляем уведомление с кнопкой "Профиль" администратору - await send_message_with_deletion(callback_query.from_user.id, f"Баланс пользователя успешно пополнен на {amount} RUB.\nТекущий баланс: {balance}", state=state, message_key='admin_confirm_message_id') + # Отправляем уведомление с кнопкой "Профиль" администратору + await send_message_with_deletion(callback_query.from_user.id, f"Баланс пользователя успешно пополнен на {amount} RUB.\nТекущий баланс: {balance}", state=state, message_key='admin_confirm_message_id') - # Отправляем уведомление пользователю с кнопкой "Профиль" - await bot.send_message( - user_id, - f"Ваш баланс был успешно пополнен на {amount} RUB.", - reply_markup=profile_keyboard - ) + # Отправляем уведомление пользователю с кнопкой "Профиль" + await bot.send_message( + user_id, + f"Ваш баланс был успешно пополнен на {amount} RUB.", + reply_markup=profile_keyboard + ) - # Удаляем сообщение с реквизитами - state_data = await state.get_data() - requisites_message_id = state_data.get('requisites_message_id') - if requisites_message_id: - try: - await bot.delete_message(chat_id=user_id, message_id=requisites_message_id) - except Exception as e: - print(f"Ошибка при удалении сообщения с реквизитами: {e}") + # Удаляем сообщение с реквизитами + requisites_message_id = state_data.get('requisites_message_id') + if requisites_message_id: + try: + await bot.delete_message(chat_id=user_id, message_id=requisites_message_id) + except Exception as e: + print(f"Ошибка при удалении сообщения с реквизитами: {e}") + + finally: + # Закрываем соединение с базой данных + await conn.close() elif action == 'decline': await send_message_with_deletion(callback_query.from_user.id, "Пополнение баланса отклонено.", state=state, message_key='admin_decline_message_id') @@ -226,7 +232,6 @@ async def process_admin_confirmation(callback_query: types.CallbackQuery, state: await callback_query.answer() - @router.message(lambda m: m.text and m.text.startswith('Недостаточно средств для продления')) async def handle_insufficient_funds(message: types.Message): user_id = message.from_user.id diff --git a/handlers/profile.py b/handlers/profile.py index c5fb2432..6089ff66 100644 --- a/handlers/profile.py +++ b/handlers/profile.py @@ -3,8 +3,6 @@ from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup from datetime import datetime -import aiosqlite -from config import DATABASE_PATH from database import get_balance, get_key_count router = Router() diff --git a/key_management.py b/key_management.py index 417326e7..6a595d6c 100644 --- a/key_management.py +++ b/key_management.py @@ -1,8 +1,7 @@ -from datetime import datetime, timedelta -import asyncio import re import uuid -import aiosqlite +import asyncio +from datetime import datetime, timedelta from aiogram import Bot, Dispatcher, Router, types, F from aiogram.filters import Command from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, CallbackQuery, Message @@ -11,12 +10,13 @@ from aiogram.fsm.state import State, StatesGroup from auth import login_with_credentials, link from client import add_client -from config import API_TOKEN, ADMIN_PASSWORD, ADMIN_USERNAME, ADMIN_CHAT_ID, DATABASE_PATH +from config import API_TOKEN, ADMIN_PASSWORD, ADMIN_USERNAME, ADMIN_CHAT_ID, DATABASE_URL from database import add_connection, has_active_key, get_balance, store_key, update_balance -from bot import dp -from handlers.start import start_command +from bot import dp, bot from handlers.profile import process_callback_view_profile -from bot import bot +from handlers.start import start_command + +import asyncpg router = Router() @@ -118,15 +118,18 @@ async def handle_key_name_input(message: Message, state: FSMContext): connection_link = link(session, client_id, email) # Проверка существующей записи - async with aiosqlite.connect(DATABASE_PATH) as db: - cursor = await db.execute('SELECT * FROM connections WHERE tg_id = ?', (tg_id,)) - existing_connection = await cursor.fetchone() + conn = await asyncpg.connect(DATABASE_URL) + try: + existing_connection = await conn.fetchrow('SELECT * FROM connections WHERE tg_id = $1', tg_id) if existing_connection: - await db.execute('UPDATE connections SET trial = 1 WHERE tg_id = ?', (tg_id,)) + await conn.execute('UPDATE connections SET trial = 1 WHERE tg_id = $1', tg_id) else: await add_connection(tg_id, 0, 1) + finally: + await conn.close() + await store_key(tg_id, client_id, email, expiry_time, connection_link) keyboard = InlineKeyboardMarkup(inline_keyboard=[ @@ -168,20 +171,25 @@ async def handle_back_to_main(callback_query: CallbackQuery, state: FSMContext): async def renew_expired_keys(): while True: current_time = datetime.utcnow() - async with aiosqlite.connect(DATABASE_PATH) as db: - async with db.execute('SELECT tg_id FROM connections WHERE trial > 0') as cursor: - active_keys = await cursor.fetchall() + conn = await asyncpg.connect(DATABASE_URL) + try: + active_keys = await conn.fetch('SELECT tg_id FROM connections WHERE trial > 0') - for tg_id, in active_keys: + finally: + await conn.close() + + for record in active_keys: + tg_id = record['tg_id'] balance = await get_balance(tg_id) if balance >= 100: new_expiry_time = int((current_time + timedelta(days=30)).timestamp() * 1000) - async with aiosqlite.connect(DATABASE_PATH) as db: - await db.execute('UPDATE keys SET expiry_time = ? WHERE tg_id = ?', (new_expiry_time, tg_id)) - await db.commit() - - await update_balance(tg_id, -100) + conn = await asyncpg.connect(DATABASE_URL) + try: + await conn.execute('UPDATE keys SET expiry_time = $1 WHERE tg_id = $2', new_expiry_time, tg_id) + await update_balance(tg_id, -100) + finally: + await conn.close() print(f"Ключ для пользователя {tg_id} продлен на месяц и списано 100 рублей.") else: From 16a497a8df3f870b0f68b61559432aa42a74de1c Mon Sep 17 00:00:00 2001 From: Vlad Date: Sat, 21 Sep 2024 00:56:08 +0300 Subject: [PATCH 08/12] =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D0=B0=D1=8F=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B0?= =?UTF-8?q?=20=D0=BF=D1=80=D0=BE=D0=B4=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=BA=D0=BB=D1=8E=D1=87=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- handlers/__pycache__/keys.cpython-310.pyc | Bin 7272 -> 7362 bytes handlers/keys.py | 27 +++++++++++----------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/handlers/__pycache__/keys.cpython-310.pyc b/handlers/__pycache__/keys.cpython-310.pyc index 5b23c2fdb32b603107ef2bacfa09ff55692bc3ab..2099e2e5737ad00eb459de1dace594a9c00ca068 100644 GIT binary patch delta 3464 zcmaJ@TWnlM89sAvdv?#BvsdrU*H~%LDt23wC~0o3(lnR0iAhOC(v)afdlqNc&f3h5 z@sbB5+92@^NQj3@$s5utAo@fjfvP+pqzF;|fA(hQ(v{9P z_kU(*zVHA4&tCe&0e{)^A{zXrKL6LZ_f3D~hxF$g*der6M<^Q?h^Bp)pt%|e$a<6O zErXkz`=0cD8WwUDr> zDeRjXwM6KqcFUyp4Lxv#*>ak~SsQ<**BWrN&tpu1*cS8W-`u*AUvvM|^ljEl4PEE`& zRV#H~s#5tyY*@ZxW-|Sr!84WGRykj;vvGON z-sh&Eiz!IUhslh*Yy0G&{F^;&IHVvS*%Rc5ET=}~{?O1&9_HTGSsblI8obPSW39?B zvqaxU#}GD$01n9@l#L~!hLlot_?XChsX`MT0C+&*neat*JQ3r<0Po3y`%gR8$x$Q(Kb)Ledcu0WE0R}I}j%2hCAyH+Nb4x_ZuTS zap*Y&#Tl!yvQ*`47j}F_&PGpOC}MRtf{8GNFpZ#$K998*5MD%>0VpO}2J2r%*pGmm zW_u9!A-slgTz(ckLJr7X-rk`@*l`#^k+Z{4x$KSY#gPWi*WY*aNuujQ$GZ82uG{}h%intiauF0U`r^y55}YGDg|=Toc$N;TICxYR{ZYMz{2ysP z!F2<;ZUEN}V0zCFn!;p11NUVD!!0Nkwg0FyU70Tzs|^C#ELtkga4|So$WB2y$aX==%sVVPai+_w)|&oXQA&gCyq?) zoMBmfp5q87|0I5BSFF>!r_4e_KTqVFIksIM>6LDpt5w*e3&L_c{sI`R8UQOx@v0MoooJbLH!grJSsm*jgk-K-PG_?;|VpnDfA5j zDlxewbYTc{!vNMi;&v;YK0+_F#6iFVTHW`NrbRJB#a=jG0s34g3{rU zmJ2Eb#!(0CH&v(s8B!pm*Yat&XI#n9YQ+ZB?8?v-4x|PqD2rgoM>APa34|)0x{Eqn zqfv-aUf}ZN7PUnolDLZ+`V={Oou)vvF+3Oqw6utU z=DHr{nTN~pEYEG|5WMmN^A|&eL}bGX5+cF=!wb;ky4ptHifCG`VVZ%=%IJk@NM5q& zx(XWjhTJV9NIvC=5Gf(EQpe*XwNWseRLAQlN%a&TX+b~D_Z|lFv~ahtFz7o7zHZwk zgYVeZR=#5#e8<>_4BWv<_ajZllJ+ve7#ZP%caUSUa8Y9A#?DT=H-$6@F@zW z+HqD{S*p~ljmxEOzW5IA&=G#4(r8qd>+Miub$%Y|&+q`P{Bs4opiX>^l4#!>IK~d0^85Qf5^h;V{w+})SCj$%0$;-cfQ`rmglbln z_X}s<#Ia6dfwxmMI$49VqHgjrQe>8iLwar7VLTR4py1My6xaNfO0xcap@9My>w?nuzqr delta 3431 zcmZ`*O>7&-6`olxce%?YwIszqNw!)4)L|?;vEwGL8{26V|0TAZM6O+=ja7=nu%sl) z9gbp)7+NXZx&;~mMF&M1xCY3g7as!DaDiTWY1&(X!lxeCTMub{D9|7%`j`H^H!G5o z;w-S=&U-Wac6Q$T-h1zV{?A9^OR<roin$fnU*@;NIO1qSjZ)}GZ&J}60@EUDLnALWKTaQXd;s(5WOh%Z!W(J?3 zq_KlVU7wxEug2P6ISzW*)rJxX_~ zL)K&{#`CIV9d>)x+_Df$4PH<`wVw9VaLQam21dOxwHC|UP4q$Yh;r?5I;tMC3-q`; zYahHc4(huGPvT0qDN0;4uhqoYc?uMPk%1+i1>I4&wR1gE21gFA1dK3Dw2ObN9@yzs zW^@hhCZqKuVRpj88aO%B`nC*pY;YWvMmMzXW>cE{7Wm2p*M`w&+QpHM11`44Ntd>5 z5$U*H(gp|1NRHH_ZAUuHo;UeP7Lg7HL4z`r=}&R=4={sNfJ52Nfdfqx$zMnNO8S-wUtlg4(Fi1f;DAgX*FRT&*sW75Z{iuyNv z9aZpDOHS4spYGAwhse3s`|4fiwY@K1I6GY^FEtvKvZyUJnuT)V!gOK&fT+&aSm9Wq z9E|S&(aBKH;gv?fWJz z7C{G5xOsJs71}N`)|{Z&I(CF6-zE<>)JAp#O0+;L{J2y@nqD%Fz9qJgxkMUTY-5rRLCSyYTjdb6Ld9X5iz zV?J!g2cCwJr=0{#Xv!t;gxju2O446XiAdE)A(ma1g4{d0i4~i+2&^b>Qf#2bFM$?O zq{aH5rqyppihCe2^ckoVYi9aiC)V?5d7)Nm2nhX2mGgJ2Y17U2oPm;$q4Q25oK~0o z@1t{7AQ$!FE^l)ocjTaucZT@mV2%|SYvG(U{fxLQo@ zrd6mw6Epk-yoo)96`U)74y}J${rMqIJ`b>oXQQAP@T_jAQhZ{SL02J-n-rK4YW-7$ zJC?8q1;}iDfPDfpH*^`$Rqzj?v(RM+o)&P;hNlhBhyX?b*MLJT0u~(!Tqm0;kvSpeuF;Mkn8^Y!+O9NMbl%`q=0WX>L3v7S;Fl*o8J#Em zCjtu1plHRk)Gu0bkVXe3%3vuhQC864s4u2dtGJ)7+ewRz$lf&CzQ{>zSs-^v_hz`A zkO}@Lkq1pQN^Y)qV3fdVjj@b$rPVLjl&~yjq%i{Y&)o^NlaJUG)=at@>`08)C%|kH z%4K?Oz2-yDl@j9}(6fBM-26n_0k5qwWeok@8p7uQF$ROa{D!;3Mo%`RteJ zuKPTDl%CzbfxuV{`7}T;RBQC>A?hB%XFi7$x+VrpRv+hfTAIL;ci9%;Ip& Date: Sat, 21 Sep 2024 01:13:12 +0300 Subject: [PATCH 09/12] =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B1=D0=BD=D0=B8?= =?UTF-8?q?=D0=BA=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=B5=D1=82=D1=81=D1=8F?= =?UTF-8?q?=20=D0=B2=D1=81=D0=B5=D0=B3=D0=BE=20=D0=BE=D0=B4=D0=B8=D0=BD=20?= =?UTF-8?q?=D1=80=D0=B0=D0=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- handlers/__pycache__/keys.cpython-310.pyc | Bin 7362 -> 9110 bytes handlers/keys.py | 63 +++++++++++++++++++--- key_management.py | 36 ++++++++++--- 3 files changed, 85 insertions(+), 14 deletions(-) diff --git a/handlers/__pycache__/keys.cpython-310.pyc b/handlers/__pycache__/keys.cpython-310.pyc index 2099e2e5737ad00eb459de1dace594a9c00ca068..f452fd24cb1c475621ef2c0cf1a98c38622492fc 100644 GIT binary patch delta 3531 zcmaJ^TWnlM89sCN?EQAUyWWd0>rI-FE>2uKlmw#0A$IK~B(YPHreF~^>z!kFoxN%H zY#m$ctd#_%qBT;O3MAS{jzB`KTGhn_LJN_&Nfi(1?CbJ^c;f*H#0w9F^8a(zyW6da zrTKPd{+WN~%>T_dzkmDX@1OP+y5#~N&j<(FEHAee{wO?5KyQ~9rP*?|A8;#SUJIdT3P0&4eq`5dHm!#xg zK29EolQP3YXn3iX({wGB$z`>I&Oav8B*Z7>qh23a%QApVV&Uz(^4k`&mmju`kO)uP zzUYhLWZQg2*NX*~;6Jtn-{ zttqRoSCvgWRjQ;aKd>}=tyL?#&xaka)vA^^r28`M*^t-#x_8M(;owC}g_ghST_jZ> z6h9Pokt`}SxH(M1QeY`q?V%8m4Xf##NCh9-Y5$HLtOFa$j`c+eHWYdw zi8hZrvtROW<0NJD=9wf9*qvc;D&PM@`dwBh3$a` z+l%lN|ABkP-EPnDf4Hv<>_^k*5JZfuM7?&RkD5iFOB^E7ij7);VzS_pbwgexqR&R{RJ0al>HywYbo-K{N~p7Hp{|W4 z8HrStHl0mANCapLm(vZQ#yv~|RPY;(*Nv@%iTdr%XgBr*PQ!>aU{LpTg*M994f6R=Epv9w+MG7L$Qk z#>1|aTRf|se2cKBz){l0K1~`niwohUHF+W!gD2SVl(hm)<+XA-y{MVwP&&vV6s0#~ zepp)177HmL@q97OXlJz1Q-A3wD`vn^DwEFT1VP%g#~79|!1B67R*51b9~}P;TLXm6 zBOzv6g!mSWdNLti#u1+(gcEvO`m>K;^TmT#yY$Dh`c;0<*IS#dDpZL{YgXN%J9XD> zdCjJ~tG22PHG9?0rmB`r54D0u$vZ-!dpCVR7F)Zg5N!6@JNlf>K1aI`B*wDoZ&Rw^ z0G;dXSe9Ot3UNi!)slf2%;lU_2eP=+**AcbYmS=^Ax7}KOqP+eJ?#YqXI&kf zT^+tOb$)8{aze|ev$@1HKcvPz$jjCJ&w=6c6dzWH>=jfHw|U||b*OU7EaQ#Y$Po7vBmv7T7f$GzyUPJRzPq2#=uQ$y6AF6f1>0Vfc_W?DOedx{%QrK3=F<|FhyvHjQJd-5p8=5LrtvFETB$J!!7 zN2IX%5=KEFC0o!N))IrCX6k_R+Lxvk z6_!JTNFU62@Qvo~yG6zBiK_np8uqJ{KY6qdAl;B)NqS5ft^h!CU7gO2| z9iE@gtA2`0*&U8znW%Rz!>%N~T!WS&_dV3n#ycgE44~B4`M^MvebFmLMPT zSf57Ld9Ck-`BsXRv~^@k;z9H^$+hz&whj}{UL;K&Ne(WU2XI07%6JHHsY!w;pRhbX z(tqI`+S*5ww3rE(@FEVl%N6ElesqO-jlAw7VIp$2R(bx(b#}%(4%p)uyln~Yt#C}8;*j`!G#Qz44P delta 1984 zcmaJ?TTdHD7~NT~*9LYM+xTvC6(O|<0TKlS0U}BQv;>E=joLU>T;o}Ysj;ouwYbRC zRPfM7B@&&cFQ^ho?L(V~O69!|MSZR+RjRbAREeiXRn=Dg0qJ~Wb17%)G|hH&H2VgVf7loDYa!r@IJ5y9wn zAr_$z(YN%zIF1_V7h=Dvvh5opgIa<3_MeJh*-$I}M81gp^gzC%1sP+>vqjU)=SVY^ z9eu6_)QN4zZ8iJ=vu=}5z|=8m=B2ic;@kR;$wYR zsp<@99u|R`2do+2&>44xPCI>_JEpX2H|0I_ihzYlVNF{7pd{@&uv9`NanDxmwU_K< zn{GKjn!)ZZajo7|7wSvuJSu6d)z71O8}@IZHJ9mHaF-M|Rao#WG?ZMJX|!dpBtb$> z10IKtc*ch5`!!eMKOkYvjf7Q?=?4x+O70!uGjXH3JQCg%?y~Kzx!-f=?3L#dS`tda zZeUM1%8mu5e>6_Q`%>*HLJ?Z7o1*{JbqAZ+7hBTg8`C#q5@}&Cq!plz4k~fi$@@*# zS)8Ij+B@li(h+TkG3Nld&~~Gc&6uPEI?hwOI`LKv!cG7Q-~vDw0MC9AqHcg5fL;bM z5Aj2M37{VU>?fB2`T(v2+@PPU!)SmWs^@7?Q+-#Va|nRDTCtW6YE3dQ#&vpM>kVos zh+4#`D6Y5LG_f5y*TrWZ8%fZ6;l`d3HZCT{$s{DX2JpO-k^ypb-|PDx$zh0JroZ|} z>H-kn02rq)1$N{J3yYU%IS`_UfnoVoV4CQ;;G@O~7OqM|-LoTPiiI(efU2a8L9vTT z8>GJlBXj?imQ2Htim(tV0C502zzhSc#>_10?5HmmDiVX7gi8#yRdIp);egj$0I$(Z zsI_b)eo-a>|A?4jg}}rKF35=s7b9D-)2~a?GZ(sa#qo1Cal$B`_jf zAo(;~Dw$4Yv$Lu6yB~*ZQHIuqZy_D5xLmcul38kfDfwbI9>X%D`%l7Oi#_1RQv@Aj z5W$fq_W+J}Ovd5$Z3Zz!ZqtX2lP>UyBmwFHn&@HU9f`4R`vXlwDE>b-Shh^gwCn}K z9xt|pyaOrud9Rp351QNLN=?w;o1^1YDay;h(r{|U zNSgVizHBnfPL;*UC#Knas~(iomkSxP!p*Uyblxxt2F^wFLRQaZj0Yph>aBVg7Owbb zFl#dToaHczX|U<}Mn>pgp@1DW_n7|FqN8Ox*m}p)0;!<7V{*09>0WE7|5!&REidp> zg$grL41Q+Ka550rou~y#&q#{?-8wx5+=)OjTjg@#S-7@mQo&vF3#5{HDh>G&zh7ce z(m_9LTP(Aal?ToFMl5x#dJbLZAa gHK0bFf(t3gg}e-*$)aNOAf>C)oJyw$xkeTHziUgLMF0Q* diff --git a/handlers/keys.py b/handlers/keys.py index 0e1e46e8..a3281f4c 100644 --- a/handlers/keys.py +++ b/handlers/keys.py @@ -6,6 +6,7 @@ from database import get_balance, update_balance, get_trial from client import extend_client_key from config import ADMIN_USERNAME, DATABASE_URL, ADMIN_PASSWORD from auth import login_with_credentials +from client import extend_client_key, delete_client router = Router() @@ -54,12 +55,10 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery): await callback_query.answer() - -# Обработка запроса на просмотр информации о ключе @router.callback_query(lambda c: c.data.startswith('view_key_')) async def process_callback_view_key(callback_query: types.CallbackQuery): tg_id = callback_query.from_user.id - key_name, client_id = callback_query.data.split('_')[2], callback_query.data.split('_')[3] # Получаем имя ключа и client_id + key_name, client_id = callback_query.data.split('_')[2], callback_query.data.split('_')[3] try: conn = await asyncpg.connect(DATABASE_URL) @@ -82,11 +81,12 @@ async def process_callback_view_key(callback_query: types.CallbackQuery): f"Дата окончания: {expiry_date.strftime('%Y-%m-%d %H:%M:%S')}\n" f"{days_left_message}") - # Кнопки для продления и инструкций + # Кнопки для продления, инструкций и удаления renew_button = types.InlineKeyboardButton(text='Продлить ключ', callback_data=f'renew_key_{client_id}') instructions_button = types.InlineKeyboardButton(text='Инструкции по использованию', callback_data='instructions') + delete_button = types.InlineKeyboardButton(text='Удалить ключ', callback_data=f'delete_key_{client_id}') back_button = types.InlineKeyboardButton(text='Назад в профиль', callback_data='view_profile') - keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[renew_button], [instructions_button], [back_button]]) + keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[renew_button], [instructions_button], [delete_button], [back_button]]) await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=keyboard, parse_mode="HTML") else: @@ -101,7 +101,58 @@ async def process_callback_view_key(callback_query: types.CallbackQuery): await callback_query.answer() -# Обработка запроса на продление ключа +# Обработка запроса на удаление ключа +@router.callback_query(lambda c: c.data.startswith('delete_key_')) +async def process_callback_delete_key(callback_query: types.CallbackQuery): + tg_id = callback_query.from_user.id + client_id = callback_query.data.split('_')[2] # Получаем client_id + + confirmation_keyboard = types.InlineKeyboardMarkup(inline_keyboard=[ + [types.InlineKeyboardButton(text='Да, удалить', callback_data=f'confirm_delete_{client_id}')], + [types.InlineKeyboardButton(text='Нет, отменить', callback_data='view_keys')] + ]) + + await bot.send_message(tg_id, "Вы уверены, что хотите удалить ключ?", reply_markup=confirmation_keyboard) + await callback_query.answer() + +@router.callback_query(lambda c: c.data.startswith('confirm_delete_')) +async def process_callback_confirm_delete(callback_query: types.CallbackQuery): + tg_id = callback_query.from_user.id + client_id = callback_query.data.split('_')[2] # Получаем client_id + + try: + conn = await asyncpg.connect(DATABASE_URL) + try: + # Получаем email для удаления через API + record = await conn.fetchrow('SELECT email FROM keys WHERE client_id = $1', client_id) + + if record: + email = record['email'] + + # Создаем сессию для API-запросов + session = login_with_credentials(ADMIN_USERNAME, ADMIN_PASSWORD) + + # Удаляем клиента через API + success = delete_client(session, client_id) + + if success: + # Удаляем ключ из базы данных + await conn.execute('DELETE FROM keys WHERE client_id = $1', client_id) + await bot.send_message(tg_id, "Ключ был успешно удален.") + else: + await bot.send_message(tg_id, "Ошибка при удалении клиента через API.") + + else: + await bot.send_message(tg_id, "Ключ не найден или уже удален.") + + finally: + await conn.close() + + except Exception as e: + await bot.send_message(tg_id, f"Ошибка при удалении ключа: {e}") + + await callback_query.answer() + @router.callback_query(lambda c: c.data.startswith('renew_key_')) async def process_callback_renew_key(callback_query: types.CallbackQuery): tg_id = callback_query.from_user.id diff --git a/key_management.py b/key_management.py index 6a595d6c..f6a3d9df 100644 --- a/key_management.py +++ b/key_management.py @@ -28,12 +28,20 @@ class Form(StatesGroup): waiting_for_key_name = State() viewing_profile = State() -# Обработка нажатия кнопки создания ключа @dp.callback_query(F.data == 'create_key') async def process_callback_create_key(callback_query: CallbackQuery, state: FSMContext): tg_id = callback_query.from_user.id - - if await has_active_key(tg_id): + + # Получаем данные о trial из базы данных + conn = await asyncpg.connect(DATABASE_URL) + try: + existing_connection = await conn.fetchrow('SELECT trial FROM connections WHERE tg_id = $1', tg_id) + finally: + await conn.close() + + trial_status = existing_connection['trial'] if existing_connection else 0 + + if trial_status == 1: await callback_query.message.edit_text( "У вас уже есть активный ключ. Вы можете создать новый ключ за дополнительную плату в размере 100 рублей. " "Хотите продолжить?", @@ -49,16 +57,28 @@ async def process_callback_create_key(callback_query: CallbackQuery, state: FSMC await callback_query.answer() -@dp.callback_query(F.data == 'cancel_create_key') -async def cancel_create_key(callback_query: CallbackQuery, state: FSMContext): - await process_callback_view_profile(callback_query, state) - await callback_query.answer() - @dp.callback_query(F.data == 'confirm_create_new_key') async def confirm_create_new_key(callback_query: CallbackQuery, state: FSMContext): + tg_id = callback_query.from_user.id + + # Проверяем баланс перед созданием нового ключа + balance = await get_balance(tg_id) + if balance < 100: + replenish_button = InlineKeyboardButton(text='Перейти в профиль', callback_data='view_profile') + keyboard = InlineKeyboardMarkup(inline_keyboard=[[replenish_button]]) + await callback_query.message.edit_text("Недостаточно средств на балансе для создания нового ключа.", reply_markup=keyboard) + await state.clear() + return + await callback_query.message.edit_text("Пожалуйста, выберите имя для вашего нового ключа:") await state.set_state(Form.waiting_for_key_name) await state.update_data(creating_new_key=True) + + await callback_query.answer() + +@dp.callback_query(F.data == 'cancel_create_key') +async def cancel_create_key(callback_query: CallbackQuery, state: FSMContext): + await process_callback_view_profile(callback_query, state) await callback_query.answer() # Обработка текстовых сообщений From b1e9b257c6e9d72eb1c84c99086ea9d82a2caa64 Mon Sep 17 00:00:00 2001 From: Vlad Date: Sat, 21 Sep 2024 01:21:35 +0300 Subject: [PATCH 10/12] =?UTF-8?q?=D0=BE=D1=81=D0=BD=D0=BE=D0=B2=D0=BD?= =?UTF-8?q?=D1=8B=D0=B5=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=B8=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=B2=D0=B5=D1=80=D1=88=D0=B5=D0=BD=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- handlers/__pycache__/keys.cpython-310.pyc | Bin 9110 -> 9025 bytes handlers/keys.py | 74 ++++++++-------------- 2 files changed, 28 insertions(+), 46 deletions(-) diff --git a/handlers/__pycache__/keys.cpython-310.pyc b/handlers/__pycache__/keys.cpython-310.pyc index f452fd24cb1c475621ef2c0cf1a98c38622492fc..cebee691462da035fdc74c4d6f5a4f3ba4e29513 100644 GIT binary patch delta 2957 zcma)8S!^3c7@irg*K2#dUY|)E+qv3M6T*>}t1Ue!v}sF251|MK0FpR7CY)qSa`+EQo4EJa_Fq#TrxyIY!?FvtJHdX%x$grg7D;S^>J`WU9|`EgZR!32nm42 z;Pi>7k9uMa3Q8&%+wtfSPKOZ;gqsm=LD1T7#o28LBM55&N-5ES^K}S!BXl9$j&K*k zc7z@3V(cEeL46t9X5NE>O={~vN^Nq=bX+~2D>z#~SYl)10erYsxrzMhr*OIrV1fcP zK4_K+<0_2lXNlUE$gA7pBkOorQc23;azqArWX{-0I2k3BNK%W=lg5e@2xfFlbi;0O zpE@6Z^8wKZ^O7x=heqtfHyDDBhK|chA<;1~mSUiMcXub+J)Hz#XDsGYgQclMtv~bX z3%9@N=ONUr{aketr`P#ugA{3@{oJVbCW@&^&!`1GbDq|{@dGpa)!9UbZdI=(da9db zpl0)+Wb>@)t?65pAP)gDZuyoMD?2>QBRo3C{4lKIGCW16D7WXRAMxCBLRvbC$#P0Y zc&ru0g9NmvTXC#d%h#vKjR#4k-ymM*^edG3QA%dAsE|7{`Z~G58ftlbE}-7Z_H|`j z%5+lPMp72eCS5x4EBb!z!nG^c-iPs?a4|@EgdPOUnL3}kW9=o@B7do4#U$3*jk_?W zV!!%6)wfe;_WzPy=h$xJc^k#m=5)8a0Tn-i&_?b`y4S?qPLC)zm+6nN2yh`uO=>bD z-~cmg&$#>Pr3KpH#waL4=0@7Q^1rl8>S#xhObmZ{Uhbt44LUXd-W`F4FrS|~|yI1EZqiNUp zIZbs_`ol39NQ}Rd%=)uvurr#FYJYApu?6%)LtZpN|KzFhIu*?i@vpb3%eex($e{P; z)E+CXe#_a_b9Bb?f;H0%HTzR==2sc4SG+;nkM6E;C@^i zn31#!XAf8?fLX!mkU(MsB~QYLl5U1wyaTlkXUm=2LJ6D+%e*}W{#FK{o(t#b=~1ZYls*(Nqj{NX9tul&RfC1T(PA^7x;2#w z^@{!mF970xfNzR8JBrY(0x^oqG6L3liHe8Si-kROLVZ>^MEe%*E^egzR;cF1z|l(M z?t`1_rvuKKB`uEQa;0PcR&}+f->e@X>f4^e_DOuRRln(0`7RnKpd$6PXwl(^Pfw3~ z&x}t#=S^04+cl}~jylkLXRdXSJc&xD5ZbEw9XENrUX-X8D{}f#0K9GVQyb107fKq= z8D>$7y3}I2=!?0pT4nKz>X9;)OcF`UtQ_F9RhYUJ!%a@-!dNMIr9l0*sPB#Xa+wC5LPG{;c)9!o#xzEr zohec=f{n6N6l>HA{V(-whJ_eMID&u|E^z{(iZG%!4h)Xq&DA0<1jGgRY&~2EiXBK& RdkDrb3>(HEj5RRs{2Tv1Kq3GD delta 2977 zcmaJ@O>7(25#D+G!zH;~E|Py;HwBZQn>FVha8p`Qo2f{`AC$b_r&GfA9p9ts1N6t}s5rM{jAf zWj@9$x0Klt4(FBX_~t77O@->_OZpVR_~tDm2XI_YW=or&MQWN|)kFnVigAKH@_;qt z44!9DxO_(5(+|o`YhP(E?b%0=Lg?zvrE80x*iVA*$Saw8+zXir^`NA%gD`JV|hp;FSDj;usu}e@#pqM@eu@4%;obfL@gC@1vl~ zX2cnK@r<0!mi8{v@&v*>AoTypBm=KM;?-PIgo%= z+KF)0XB(_1(|k>wlkcWRPoBhg7S{*wK=BeC8-rlN4dN8%b>iX`~<_=GJj?^^zgm{D-`0?hSCo>SCFZ(88J*g zPj9~GIw0?*pV(h@cMW;SINECwyd)1~DlZ0%yGYLio&^+(Zll-^b`@}YH`2sBE#7G0 zVtIZZ*GR8FWTw7H31|{~*_z25V03}YG-g6fjj0-QSq3u9RoU8vYO@J2*VxytDy}?~ z(dWq`%4h{nNIzSK4koS9e!*AjaZE&{pEe2l?BJx5FI^Do@^qmg%K@Snrb~${jy?M|qW}JhS8a1~-P44d|q~y8(9;p1!NM zVqV5q`X3-WvUMyGKgKh@$+Ne$e$IB9+c3#|`C&9YoWJ>JWJR1<(Q`BB#{*V?%;qaY z*1*Xi^Z9<}<+=Q{af%{#TK+o!GFv_*j~9kyqOg+sl30KL?)~50{|v8B#f$P61zC6w zg{#`p$;-#ZSzK0`pq7PwY;x$S_HNa>lRC6pC-@OUH|i~7(R!DzFNJN3_!H7;N0mqD z#SaO7OyA{s?OM&N%`J)An~OJ=n?dQ%Qtb~|NsX^+eLEKg`QEkWmxZV`yg9N)P{Zp+ zE?>HKsWD#@NAL;x`ABgJPRWbK;r%X>GUIArAQiy|^S`#iP@;Y9GV_17R)8KkWv93= z)oYbq1GU#y=E6o}{DoAWkpC{0+0Pkv#iBfBX4+iyG@q?QMrmmtwhLeNwRII!`XA5e zL_D+4+m^oi4`1&@v8AZ}X3;Y63u7IylNuj@0HWED(u&|cf_Kx4b>bUJ2Wh}f)Q>I0 zk6s1Aku2DXcocQFd#xB*rgxG9zVwPh^G(n3H6%JvA0ah)z!R;w z=WZjvFaSt}7drMMeB+4z+86YT}??FCq1JdHJ!B+KeHmOv6q zpx0D`irvk)oR{|s>#XH?L%xHhqxum)wyw8QeoB1h6;Tq8E9+ZY(7|Dz^J5sltrLj1 z^XQ#cRInxsch#1=i;ZL+gywmrdKuNafq7d>u#1;EDCa}{#9(KS_GHC@Y-fzIlJ&#e zgT8{jsWGZ4-pB`V^Jy&YbkOia<->kDZ20oMa%I{cq$()wQdrs#=pYt9KzJ}hs|5n; zz5(OKXSDv|>PrG|y$OVLgy#XRwu)L@ zqsL$h3Cmedjg1@Oq#}PhRxZuc`yF+f>7Ul`kBvHmX(Y5*em?eO@iqGDC;=y^5%h{P zS($88Q8j4~9UkLg;eYP{0TT>7LQHVLWI^@rE&kn6ZPmp3wXuqG3@bVt`s4uzRqX$SKGIsFzOca=JbtjFpquC=-zN;nLdOt}j3#b&UF zZViA{W83&fdb@V7)AeVYt<+%Vf=MoeyF_M;FiUV9A%U=@9p)?B=aj%en5n9P(m~2l5X_Ik?+1wvjXNd1D+EgfYXszjcw9ah-v@`Jvu7;*mHi0%n^9T- diff --git a/handlers/keys.py b/handlers/keys.py index a3281f4c..e67205ee 100644 --- a/handlers/keys.py +++ b/handlers/keys.py @@ -26,7 +26,7 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery): # Создаем кнопки для каждого ключа buttons = [] for record in records: - key_name = record['email'] # Предполагается, что email - это название ключа + key_name = record['email'] client_id = record['client_id'] button = types.InlineKeyboardButton(text=key_name, callback_data=f'view_key_{key_name}_{client_id}') buttons.append([button]) @@ -100,63 +100,63 @@ async def process_callback_view_key(callback_query: types.CallbackQuery): await callback_query.answer() - # Обработка запроса на удаление ключа @router.callback_query(lambda c: c.data.startswith('delete_key_')) async def process_callback_delete_key(callback_query: types.CallbackQuery): tg_id = callback_query.from_user.id - client_id = callback_query.data.split('_')[2] # Получаем client_id + client_id = callback_query.data.split('_')[2] confirmation_keyboard = types.InlineKeyboardMarkup(inline_keyboard=[ [types.InlineKeyboardButton(text='Да, удалить', callback_data=f'confirm_delete_{client_id}')], [types.InlineKeyboardButton(text='Нет, отменить', callback_data='view_keys')] ]) - await bot.send_message(tg_id, "Вы уверены, что хотите удалить ключ?", reply_markup=confirmation_keyboard) + await bot.edit_message_text("Вы уверены, что хотите удалить ключ?", chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=confirmation_keyboard) await callback_query.answer() @router.callback_query(lambda c: c.data.startswith('confirm_delete_')) async def process_callback_confirm_delete(callback_query: types.CallbackQuery): tg_id = callback_query.from_user.id - client_id = callback_query.data.split('_')[2] # Получаем client_id + client_id = callback_query.data.split('_')[2] try: conn = await asyncpg.connect(DATABASE_URL) try: - # Получаем email для удаления через API record = await conn.fetchrow('SELECT email FROM keys WHERE client_id = $1', client_id) if record: email = record['email'] - # Создаем сессию для API-запросов session = login_with_credentials(ADMIN_USERNAME, ADMIN_PASSWORD) - - # Удаляем клиента через API success = delete_client(session, client_id) if success: - # Удаляем ключ из базы данных await conn.execute('DELETE FROM keys WHERE client_id = $1', client_id) - await bot.send_message(tg_id, "Ключ был успешно удален.") + response_message = "Ключ был успешно удален." else: - await bot.send_message(tg_id, "Ошибка при удалении клиента через API.") + response_message = "Ошибка при удалении клиента через API." else: - await bot.send_message(tg_id, "Ключ не найден или уже удален.") + response_message = "Ключ не найден или уже удален." + + back_button = types.InlineKeyboardButton(text='Назад', callback_data='view_keys') + keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]]) + + await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=keyboard) finally: await conn.close() except Exception as e: - await bot.send_message(tg_id, f"Ошибка при удалении ключа: {e}") + await bot.edit_message_text(f"Ошибка при удалении ключа: {e}", chat_id=tg_id, message_id=callback_query.message.message_id) await callback_query.answer() + @router.callback_query(lambda c: c.data.startswith('renew_key_')) async def process_callback_renew_key(callback_query: types.CallbackQuery): tg_id = callback_query.from_user.id - client_id = callback_query.data.split('_')[2] # Получаем client_id + client_id = callback_query.data.split('_')[2] try: conn = await asyncpg.connect(DATABASE_URL) @@ -169,10 +169,9 @@ async def process_callback_renew_key(callback_query: types.CallbackQuery): current_time = datetime.utcnow().timestamp() * 1000 if expiry_time <= current_time: - await callback_query.message.answer("Ваш ключ уже истек и не может быть продлен.") + await bot.edit_message_text("Ваш ключ уже истек и не может быть продлен.", chat_id=tg_id, message_id=callback_query.message.message_id) return - # Создаем клавиатуру для выбора плана продления keyboard = types.InlineKeyboardMarkup(inline_keyboard=[ [types.InlineKeyboardButton(text='Продлить на 1 месяц (100 руб.)', callback_data=f'renew_1_month_{client_id}')], [types.InlineKeyboardButton(text='Продлить на 3 месяца (250 руб.)', callback_data=f'renew_3_months_{client_id}')], @@ -184,31 +183,27 @@ async def process_callback_renew_key(callback_query: types.CallbackQuery): f"Баланс: {balance} руб.\n" f"Действующий ключ истекает {datetime.utcfromtimestamp(expiry_time / 1000).strftime('%Y-%m-%d %H:%M:%S')}") - await delete_previous_message(callback_query) - await bot.send_message(tg_id, response_message, parse_mode="HTML", reply_markup=keyboard) + await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=keyboard, parse_mode="HTML") finally: await conn.close() except Exception as e: - await callback_query.message.answer(f"Ошибка при выборе плана: {e}") + await bot.edit_message_text(f"Ошибка при выборе плана: {e}", chat_id=tg_id, message_id=callback_query.message.message_id) await callback_query.answer() - # Обработка выбора плана продления @router.callback_query(lambda c: c.data.startswith('renew_')) async def process_callback_renew_plan(callback_query: types.CallbackQuery): tg_id = callback_query.from_user.id plan, client_id = callback_query.data.split('_')[1], callback_query.data.split('_')[3] # '1' или '3' и client_id - days_to_extend = 30 * int(plan) # 30 дней или 90 дней - print(plan, client_id, days_to_extend) + days_to_extend = 30 * int(plan) try: conn = await asyncpg.connect(DATABASE_URL) try: record = await conn.fetchrow('SELECT email, expiry_time FROM keys WHERE client_id = $1', client_id) - print(record) if record: email = record['email'] @@ -216,58 +211,45 @@ async def process_callback_renew_plan(callback_query: types.CallbackQuery): current_time = datetime.utcnow().timestamp() * 1000 if expiry_time <= current_time: - await callback_query.message.answer("Ваш ключ уже истек и не может быть продлен.") + await bot.edit_message_text("Ваш ключ уже истек и не может быть продлен.", chat_id=tg_id, message_id=callback_query.message.message_id) return - # Рассчитываем новый срок окончания new_expiry_time = int(expiry_time + timedelta(days=days_to_extend).total_seconds() * 1000) - - # Определяем стоимость продления cost = 100 if plan == '1' else 250 - # Проверка баланса balance = await get_balance(tg_id) if balance < cost: replenish_button = types.InlineKeyboardButton(text='Пополнить баланс', callback_data='replenish_balance') back_button = types.InlineKeyboardButton(text='Назад', callback_data='view_keys') keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[replenish_button], [back_button]]) - await callback_query.message.answer("Недостаточно средств для продления ключа.", reply_markup=keyboard) + await bot.edit_message_text("Недостаточно средств для продления ключа.", chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=keyboard) return - # Создаем сессию для API-запросов session = login_with_credentials(ADMIN_USERNAME, ADMIN_PASSWORD) - - # Обновляем ключ через API success = extend_client_key(session, tg_id, client_id, email, new_expiry_time) if success: - await update_balance(tg_id, -cost) # Списание средств с баланса + await update_balance(tg_id, -cost) await conn.execute('UPDATE keys SET expiry_time = $1 WHERE client_id = $2', new_expiry_time, client_id) response_message = f"Ваш ключ был успешно продлен на {days_to_extend // 30} месяц(-)." back_button = types.InlineKeyboardButton(text='Назад', callback_data='view_keys') keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]]) - await bot.send_message(tg_id, response_message, reply_markup=keyboard) + await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=keyboard) else: - await bot.send_message(tg_id, "Ошибка при продлении ключа.") + await bot.edit_message_text("Ошибка при продлении ключа.", chat_id=tg_id, message_id=callback_query.message.message_id) else: - await bot.send_message(tg_id, "У вас нет ключей для продления.") + await bot.edit_message_text("У вас нет ключей для продления.", chat_id=tg_id, message_id=callback_query.message.message_id) finally: await conn.close() except Exception as e: - await bot.send_message(tg_id, f"Ошибка при продлении ключа: {e}") + await bot.edit_message_text(f"Ошибка при продлении ключа: {e}", chat_id=tg_id, message_id=callback_query.message.message_id) await callback_query.answer() - -# Удаление предыдущего сообщения -async def delete_previous_message(callback_query: types.CallbackQuery): - if callback_query.message.message_id: - await bot.delete_message(chat_id=callback_query.from_user.id, message_id=callback_query.message.message_id) - # Обработка ошибок async def handle_error(tg_id, callback_query, message): - await delete_previous_message(callback_query) - await bot.send_message(tg_id, message) + await bot.edit_message_text(message, chat_id=tg_id, message_id=callback_query.message.message_id) + From b95013eb8f2a24a74dc2ce444dde9420509e00b9 Mon Sep 17 00:00:00 2001 From: Vlad Date: Sat, 21 Sep 2024 01:29:33 +0300 Subject: [PATCH 11/12] =?UTF-8?q?=D1=83=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D1=8B=20=D0=BB=D0=B8=D1=88=D0=BD=D0=B8=D0=B5=20=D0=B8=D0=BC?= =?UTF-8?q?=D0=BF=D0=BE=D1=80=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- auth.py | 1 - database.py | 2 +- handlers/keys.py | 2 +- handlers/pay.py | 1 - handlers/start.py | 7 +++---- key_management.py | 3 +-- 6 files changed, 6 insertions(+), 10 deletions(-) diff --git a/auth.py b/auth.py index 2c0f1443..8827e109 100644 --- a/auth.py +++ b/auth.py @@ -1,7 +1,6 @@ import requests from config import ADMIN_USERNAME, ADMIN_PASSWORD, GET_INBOUNDS_URL import json -import httpx session = None diff --git a/database.py b/database.py index 983762fe..4e620f18 100644 --- a/database.py +++ b/database.py @@ -1,6 +1,6 @@ import asyncpg from datetime import datetime -from config import DATABASE_URL # Example: "postgresql://user:password@localhost:5432/dbname" +from config import DATABASE_URL async def init_db(): conn = await asyncpg.connect(DATABASE_URL) diff --git a/handlers/keys.py b/handlers/keys.py index e67205ee..21f8dc55 100644 --- a/handlers/keys.py +++ b/handlers/keys.py @@ -2,7 +2,7 @@ import asyncpg from aiogram import types, Router from bot import bot from datetime import datetime, timedelta -from database import get_balance, update_balance, get_trial +from database import get_balance, update_balance from client import extend_client_key from config import ADMIN_USERNAME, DATABASE_URL, ADMIN_PASSWORD from auth import login_with_credentials diff --git a/handlers/pay.py b/handlers/pay.py index 5d7be13c..5cf2db2e 100644 --- a/handlers/pay.py +++ b/handlers/pay.py @@ -2,7 +2,6 @@ from aiogram import types, Router from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup -import aiosqlite from config import ADMIN_ID, DATABASE_URL from database import get_balance, update_balance, get_key_count from bot import bot diff --git a/handlers/start.py b/handlers/start.py index af3521cc..40ae355f 100644 --- a/handlers/start.py +++ b/handlers/start.py @@ -1,11 +1,10 @@ -from aiogram import Bot, Dispatcher, Router, types +from aiogram import Router, types from aiogram.filters import Command from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, CallbackQuery, Message, ReplyKeyboardMarkup, KeyboardButton -from aiogram.fsm.storage.memory import MemoryStorage from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup -from config import API_TOKEN, ADMIN_ID, CHANNEL_URL -from bot import dp, bot # Убедитесь, что bot и dp импортируются из правильного места +from config import ADMIN_ID, CHANNEL_URL +from bot import bot # Убедитесь, что bot и dp импортируются из правильного места router = Router() diff --git a/key_management.py b/key_management.py index f6a3d9df..d10d57fb 100644 --- a/key_management.py +++ b/key_management.py @@ -2,8 +2,7 @@ import re import uuid import asyncio from datetime import datetime, timedelta -from aiogram import Bot, Dispatcher, Router, types, F -from aiogram.filters import Command +from aiogram import Router, types, F from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, CallbackQuery, Message from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup From 9c2b732d55a330872cf543e730e3d41ac977dbb8 Mon Sep 17 00:00:00 2001 From: Vlad Date: Sat, 21 Sep 2024 02:59:25 +0300 Subject: [PATCH 12/12] =?UTF-8?q?=D0=B2=D0=B0=D0=BB=D0=B8=D0=B4=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8F=20email?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- handlers/__pycache__/keys.cpython-310.pyc | Bin 9025 -> 9040 bytes handlers/__pycache__/pay.cpython-310.pyc | Bin 8680 -> 8628 bytes handlers/__pycache__/start.cpython-310.pyc | Bin 5584 -> 5250 bytes handlers/keys.py | 6 +++-- handlers/pay.py | 11 +++++---- handlers/start.py | 14 ------------ key_management.py | 25 +++++++++++++++------ 7 files changed, 29 insertions(+), 27 deletions(-) diff --git a/handlers/__pycache__/keys.cpython-310.pyc b/handlers/__pycache__/keys.cpython-310.pyc index cebee691462da035fdc74c4d6f5a4f3ba4e29513..f4b2f054414f9405281d8bc7ed7a59b6bc58d22e 100644 GIT binary patch delta 2596 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 B7%Tt) diff --git a/handlers/__pycache__/pay.cpython-310.pyc b/handlers/__pycache__/pay.cpython-310.pyc index 1dfa390509e5daae35e1216151c21ac9c9adab83..8cf0dcc0314b7f3cc24099965415699984628c85 100644 GIT binary patch delta 1154 zcmZvZ%WE4~6vpRVNi(B)Xr!@5engTb*(P!8hJ>_`rj+WlcCxkFiR3}FgmNjxS zmY@YWYLU8WYeG0F>C#@9v~*Pz=%z2|U(l_0rBGU^`^ciZK<^zQN?H$`kB>9I`<Jxq~=?CdRtp4wX`S!)Rf=EA0a&N>Cj^ zyQGxEIDpA+xC3tcM1VNQASoOSkaR!{9YYM`NI+x);>0mT8Z!Zr4T$XjiDz&Wa{)5S zksRiqAO-%d`7*;X91q?WFiLw+Ba^)aE4>{i1DL&TXe zZGS^;rO-ax2qD7|hIiRcnEMFIfsZu8t#B(+VHH4S7k1>9RE<<+R9iAeciC(FI$o8; zqOV9T*i#y6HM$S|ZbKyTm5Zbb{F_>V3VSNen0Pv_CGBbZQa?t7Pns(}(BJ9|{S-av zdQ`eC%}Z+XS$cF8|v8|a&KRl5iZ z-P6uQ^c(9pH|Z;FK7UH=V2TTSS7kA#g24=kgUlE(W;_l@D6c!PMBiCS`b}0f|1@VO z*^2)Xy`kq}k=pv()}jar2O&x3U*d3@3dR~N_nO8Xpd;0wXsiI|X)1PFzQ8$t`pww> z__VMyLLx#$fICE{N~ ze2x2GK{T##X}(N%%ooVCC*WG|oP87EeDD5%2lBKaW=M0U^m$=)y30R=2 za{*@Qz*&M#`pdbM=#Q4y(c7r^Cm)DotG$m?KQsB7@N4wmuq%tv{#E*Mc!AG5IWo`s z^KPYY^Xxy<58!}4$vlGB>BDT=JmUB^<6`r_r-#`O_@XY3ex05cG5^`lbG$bh;@D8` z6*!_|ZVbk#lS6ux^GBA%flES)Lh3>~LT2f9{(1cjw{a;B{C?@%d}<(@0wcmJ!|O#} Hiz)aAcr^~2 delta 1281 zcmZvaPiP}m9LMMPGBcS>GL!#F(Z>MJ*l9JFsdy0UKtsYF#L zE9R|fPTY-KanA>wgqyUIo>!cdm9oi!WTi_I)oJihqCs0f@L6N@3^k~E2v){UAC1#6 zjT}nHK%+M7OwgF^ugY{kP7{an0a#fNNuGkFXxbxk9x-+bk)h)rG3gN#rx011^N74h zOg<#$A9RwZ1rI48GDWBEBh%RHbd}Hqq{Me|rC^wy#Sl=>qjpxhKDbV}4NrsV!55s?^^j*X6d-m1|G~ zs`cQy(v^199X}0p6{`2hh^NxF)P*Ch9oW&2VYJ#-FegYuBBuuEaqL51Bg3@$`*Gp_ z$91Kn;ITA{5#e)@4F=eD=L3FQ`!GL9yluZ%wi#=(vTL_mm6~0y(_4g{MTR}XpVKan z-GV{5-f-#-yNvLg%?hKP4SdZn1wP?>+FAZf;K_7o5U8@ITkf=MHXwD%3=wdiXY}Vm z<*)0H^H23>pP9qO);SdNzD7)=geXEp2wN5+GTE$Bjn9H-o< zxHbz2ro@+$i?Gc1l50@sKPPviqlVN9b?c2$KQ{%u)o-MJBWh9jZEj^O^{TM1@!Odd zSp0f?nT)!5IlBiX{!{h~xXE{O{{%LXn}Y$k{7yJw+{339L5X3Lyf^s~PH`;%UG}um zoflvF5c@m73io)iFaxvvjRMsJm3p(a>(tve|Gv-~Uq_Bz5mFY?5ON@7nXgY>e0m;j iM2g@Mh=7Kb7#;^aA`zl1p%fmN2`mLGhqZuJO2L25%OzU? diff --git a/handlers/__pycache__/start.cpython-310.pyc b/handlers/__pycache__/start.cpython-310.pyc index e27b2c4f3b408ecf926ff65bdb87e5d12e958f49..63472a324a77a9a5aca15b29e0bfc86132ff731e 100644 GIT binary patch delta 1678 zcmaJ?O=u)V6t3#&>7JhH?&(R-O#Wt)$*!*3Mv`@ZRCZ%DoBfH0tb(vgLl`=#o^@i^ z%u>@)R{{|eM8Q2sfmijo2Vo&s4}u^f2%h^Q9v5K|yom?jt4TBo;xzrud+&Qy^?s_V zf1dp?aQlu^B=GyieB7C7eC9TG&Qn4dVKgC}hLmfeMp4(29M?l#$+;xYjnLp`X!1f> z;8tkyVp!y*u%viBvAGjEO3o)PFNbCBg`U!lq{2tS5$=b+(#@pGt6`OrnC_CWc5{cY z0<-oAvtn;I7uH!Z);6t<#!AfI)5FoY_T?CJn7c>5qO2Sjfbm#mPv52CxZ*~RaXzam zZbEUje>ql4-Bcnnu?odQ?HX? z`Vkf~(B$XzV>z##qMBUO#weBVYdvbqM_R+EgM+BcQf}U_fLMZepaT?0k&233&5gSz zs0O^>At&UoR$b0H*P3KAziDh!W^WtJxv#$hEmw_Z|dFLNE1e6xh;`Apjo-w zOS(PzquHWO`J37F15@OI7i9#TUwCq@(8!r$Ol}v#vrnOE7#>Qa0GTsLBayZ@`kY5S z=7MQ(M!HthHB5tQ>hb;(7v-uo|9lHrSXXCJ8o@u^I5}BiA&K}pixy|W^A8Q6NW|s+ zeQQy>+5k-0jWEPwdn5y8PO1p1XvTYq!KAuJjzj3kb)j5SPKWjP@(?Py36V zaTF&ICJ|KF(BKP`Mh|Tjs^Hhq z^LRJ1@AI@4TFz3D<3<%L z@>74jbYvO%(67;Z@_Rp^x8?8t)t!qlLqLTZeZ&u}nYFBMqL?&1>R>{NxTRj5i-Ej>=B!`s7*b1Y-(tp=H=`r$fNL1RcWza Jr?y|Ee*;ADS%?4t delta 2113 zcmaJ?O>Epm6!zE~+x!2U|K0qxX)!5n0hN|0L~WYVMs2oH+HkN?-A%@uZtB@h$KIAy zK`BjyKqyGm*N7hp4n3g~B9#Li;Ec36A$;Y8gt)*B5WF{=v`r4|%Aeo7?|Ek4_r}ln zjmA)-7LP|H_#OWKxALEbj}rNnB9SC2QQ}C9n1sou%oI~$Au~kKFFP$vHC16Nj>dFT zXJIqUB4&g|%_xhRF%~!Df)6Qzf0~U;*&P3@ue$XjhE(a|93KIC_(tl)P!~Q^2Kw-KtD4+IZ?DG#)IhD z3ulVObC;}{=}U$j$aK+vL9UXdzai(F2AiUOA9RvDxkKYFN3(tDowA4Cg4O|kcjjwOEb$F z&8{oE9F-vd+~&thMHwG}WdfznorfeyR;o^=YFjt$rP*4E(>m^@vC-ISe7pUjK{&=< zg_QK&fFQ|jw{q9-)cVMPKdcpI>KM~_u>C>fQDbZSUgPoh{l->b<7=4Xo9%aC@Kxh+ zV{6z@_;CmnGxC?fGQwO#M-~B>bK&{)0)nT7U50FXN zbK&@FFqa`0`RlmSD8gxky;JZp@J*xttp7{&la(GcdlC8&#I_Hjh1v5%2!|1#Log7Y zB*o9e&`|f64O8M55GIh<_&D0HAe=-vh43l@$TlQ#Ea$#W2HBlFXG?Q8Ew5%VyIP<2 zkHn9W;+|B+*8VruTR8jK&4=+xvT_b>q0ZjwG!r3#R(EY~Rj3i&S0Ry35O!_Pf^!9( z0pDES1rS8*i*P<{3lB*UF7q0*a29?7BZL4gO#v=TBX};$woRK^LAm)wjJkxdw@N{B zM}Xb%TmGZuAer%hO8!WuHor){OIqo6)BhXSossm=2erj6H*=kW?YGT$$%- z3D$4wESi+iJ|`l$_+B)R1xnN``6OCJgc$@O8Xg*c4dFV%Qw@cc2VbC%uo|s9_9%Sx qCHHxN4kk^cawlkC_4 diff --git a/handlers/keys.py b/handlers/keys.py index 21f8dc55..64875e1e 100644 --- a/handlers/keys.py +++ b/handlers/keys.py @@ -38,12 +38,14 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery): # Редактируем сообщение с клавиатурой await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=inline_keyboard) else: - # Если нет ключей, добавляем кнопку "Создать ключ" + # Если нет ключей, добавляем кнопку "Создать ключ" и "Назад" response_message = "У вас нет ключей." # Кнопка "Создать ключ" create_key_button = types.InlineKeyboardButton(text='Создать ключ', callback_data='create_key') - keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[create_key_button]]) + back_button = types.InlineKeyboardButton(text='Назад', callback_data='view_profile') # Измените на правильное значение для кнопки "Назад" + + keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[create_key_button], [back_button]]) await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=keyboard) diff --git a/handlers/pay.py b/handlers/pay.py index 5cf2db2e..0e1e5101 100644 --- a/handlers/pay.py +++ b/handlers/pay.py @@ -43,12 +43,14 @@ async def process_callback_replenish_balance(callback_query: types.CallbackQuery key_count = await get_key_count(tg_id) if key_count <= 0: - await send_message_with_deletion(tg_id, "У вас нет ключей. Пополнение баланса возможно только при наличии ключа.", state=state, message_key='key_error_message_id') + # Создаем кнопки "Создать ключ" и "Назад" create_key_button = InlineKeyboardButton(text='Создать ключ', callback_data='create_key') - keyboard = InlineKeyboardMarkup(inline_keyboard=[[create_key_button]]) - + back_button = InlineKeyboardButton(text='⬅️ Назад', callback_data='back_to_profile') + keyboard = InlineKeyboardMarkup(inline_keyboard=[[create_key_button, back_button]]) + await callback_query.message.edit_text( - text="Пожалуйста, создайте ключ для продолжения.", + text="У вас нет ключей. Пополнение баланса возможно только при наличии ключа.\n" + "Пожалуйста, создайте ключ для продолжения.", reply_markup=keyboard ) return @@ -65,6 +67,7 @@ async def process_callback_replenish_balance(callback_query: types.CallbackQuery ) await callback_query.answer() + @router.callback_query(lambda c: c.data == 'back_to_profile') async def back_to_profile_handler(callback_query: types.CallbackQuery, state: FSMContext): await process_callback_view_profile(callback_query, state) diff --git a/handlers/start.py b/handlers/start.py index 40ae355f..b7e0fc55 100644 --- a/handlers/start.py +++ b/handlers/start.py @@ -33,26 +33,12 @@ async def start_command(message: types.Message): [button_channel] ]) - reply_keyboard = ReplyKeyboardMarkup( - keyboard=[ - [KeyboardButton(text="Меню")], - # [KeyboardButton(text="Мой профиль")] - ], - resize_keyboard=True - ) - await message.bot.send_message( chat_id=message.chat.id, text=welcome_text, parse_mode='Markdown', reply_markup=inline_keyboard ) - - await message.bot.send_message( - chat_id=message.chat.id, - text="Выберите действие:", - reply_markup=reply_keyboard - ) @router.callback_query(lambda c: c.data == 'about_vpn') async def handle_about_vpn(callback_query: CallbackQuery): diff --git a/key_management.py b/key_management.py index d10d57fb..95b35bfa 100644 --- a/key_management.py +++ b/key_management.py @@ -42,7 +42,7 @@ async def process_callback_create_key(callback_query: CallbackQuery, state: FSMC if trial_status == 1: await callback_query.message.edit_text( - "У вас уже есть активный ключ. Вы можете создать новый ключ за дополнительную плату в размере 100 рублей. " + "У вас уже был пробный ключ. Вы можете создать новый за 100 рублей. " "Хотите продолжить?", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text='Да, создать новый ключ', callback_data='confirm_create_new_key')], @@ -105,12 +105,12 @@ async def handle_text(message: Message, state: FSMContext): async def handle_key_name_input(message: Message, state: FSMContext): tg_id = message.from_user.id + key_name = sanitize_key_name(message.text) if not key_name: - await message.reply("Имя ключа не указано. Введите имя на английском языке.") - await state.clear() - return + await message.bot.send_message(tg_id, "Назовите профиль на английском языке.") + return # Прерываем выполнение функции data = await state.get_data() creating_new_key = data.get('creating_new_key', False) @@ -122,11 +122,21 @@ async def handle_key_name_input(message: Message, state: FSMContext): expiry_time = int((current_time + timedelta(days=1)).timestamp() * 1000) if creating_new_key: + # Проверяем, занято ли имя ключа + conn = await asyncpg.connect(DATABASE_URL) + try: + existing_key = await conn.fetchrow('SELECT * FROM keys WHERE email = $1', email) + if existing_key: + await message.bot.send_message(tg_id, "Это имя уже занято. Пожалуйста, выберите другое имя.") + return # Прерываем выполнение функции + finally: + await conn.close() + balance = await get_balance(tg_id) if balance < 100: replenish_button = InlineKeyboardButton(text='Перейти в профиль', callback_data='view_profile') keyboard = InlineKeyboardMarkup(inline_keyboard=[[replenish_button]]) - await message.reply("Недостаточно средств на балансе для создания нового ключа.", reply_markup=keyboard) + await message.bot.send_message(tg_id, "Недостаточно средств на балансе для создания нового ключа.", reply_markup=keyboard) await state.clear() return @@ -157,12 +167,13 @@ async def handle_key_name_input(message: Message, state: FSMContext): ]) key_message = f"Ключ создан:\n
{connection_link}
" - await message.reply(key_message, parse_mode="HTML", reply_markup=keyboard) + await message.bot.send_message(tg_id, key_message, parse_mode="HTML", reply_markup=keyboard) except Exception as e: - await message.reply(f"Ошибка при создании ключа: {e}") + await message.bot.send_message(tg_id, f"Ошибка при создании ключа: {e}") await state.clear() + @dp.callback_query(F.data == 'instructions') async def handle_instructions(callback_query: CallbackQuery): instructions_message = (