From f8015ced70bd26c8108dcd9d8965a2b8496c72ca Mon Sep 17 00:00:00 2001 From: Vlad Date: Fri, 20 Sep 2024 03:48:41 +0300 Subject: [PATCH] =?UTF-8?q?=D1=80=D0=B5=D0=B4=D0=B8=D0=B7=D0=B0=D0=B9?= =?UTF-8?q?=D0=BD,=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BA=D0=B8=20=D0=BD=D0=B0?= =?UTF-8?q?=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 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 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 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 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)