From 1c19bf62059d36e9730402e89f77ec3a9b5d5ffb Mon Sep 17 00:00:00 2001 From: Vladless Date: Sun, 27 Oct 2024 03:16:58 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BE=D0=BB=D0=BD=D0=B0=D1=8F=20=D1=80?= =?UTF-8?q?=D0=B5=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=82=D1=83=D1=80=D0=B8?= =?UTF-8?q?=D0=B7=D0=B0=D1=86=D0=B8=D1=8F.=20=D0=A2=D0=B5=D0=BA=D1=81?= =?UTF-8?q?=D1=82=D1=8B=20=D0=B2=D1=81=D0=B5=20=D0=B2=20texts.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +- bot.py | 7 ++-- handlers/__pycache__/commands.cpython-310.pyc | Bin 5094 -> 5510 bytes handlers/__pycache__/pay.cpython-310.pyc | Bin 7845 -> 8199 bytes handlers/__pycache__/profile.cpython-310.pyc | Bin 3236 -> 3258 bytes handlers/__pycache__/start.cpython-310.pyc | Bin 4332 -> 4356 bytes handlers/__pycache__/texts.cpython-310.pyc | Bin 8100 -> 9316 bytes handlers/{ => admin}/admin.py | 2 +- handlers/backup_handler.py | 17 --------- handlers/commands.py | 17 +++++++-- handlers/{ => instructions}/instructions.jpg | Bin handlers/{ => instructions}/instructions.py | 0 handlers/{ => keys}/key_management.py | 8 ++-- handlers/{ => keys}/keys.py | 35 +++++++----------- handlers/{ => keys}/trial_key.py | 1 - handlers/pay.py | 29 ++++++++++----- handlers/profile.py | 9 +++-- handlers/start.py | 6 +-- handlers/texts.py | 32 +++++++++++++++- 19 files changed, 95 insertions(+), 71 deletions(-) rename handlers/{ => admin}/admin.py (93%) delete mode 100644 handlers/backup_handler.py rename handlers/{ => instructions}/instructions.jpg (100%) rename handlers/{ => instructions}/instructions.py (100%) rename handlers/{ => keys}/key_management.py (96%) rename handlers/{ => keys}/keys.py (90%) rename handlers/{ => keys}/trial_key.py (99%) diff --git a/.gitignore b/.gitignore index 6710dc9d..101388c8 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ /database.db /backup_pg.sh /config copy.py -/docker-compose.yml \ No newline at end of file +/docker-compose.yml +__pycache__ diff --git a/bot.py b/bot.py index 2c685583..7dc9c5cf 100644 --- a/bot.py +++ b/bot.py @@ -8,8 +8,10 @@ storage = MemoryStorage() dp = Dispatcher(bot=bot, storage=storage) router = Router() -from handlers import (backup_handler, key_management, keys, notifications, pay, - profile, start, admin, commands) +from handlers.admin import admin +from handlers.keys import key_management, keys +from handlers import (notifications, pay, + profile, start, commands) dp.include_router(commands.router) dp.include_router(start.router) @@ -18,5 +20,4 @@ dp.include_router(keys.router) dp.include_router(key_management.router) dp.include_router(pay.router) dp.include_router(notifications.router) -dp.include_router(backup_handler.router) dp.include_router(admin.router) \ No newline at end of file diff --git a/handlers/__pycache__/commands.cpython-310.pyc b/handlers/__pycache__/commands.cpython-310.pyc index f62c7e6164bea5206f9cbe0e99c5cc6709ffebf2..a093d143d8e058708edb30874483a6b8fd3e202f 100644 GIT binary patch delta 1562 zcmZWpO>7%g5Z6BCr$_n^+LrFiJ51|O~_jN?Yx<9X5P%3 z=Rc-CP6bMSzej=R->spIz5YfZMt*Cg3f@9wEkYEkPq;^y{v?}%skgmJV)wkl z3`5^wP2!j)Dc5y9yIJ0{dkSrBmSEpHj|yK0yuZL}ToYeX$0f_Lq*SZzAz5`+3+Hc)zeUwh8%S-6kc+9&8=5MXNR;~4k zwP)?et^0B7r`87$w`T2D!Sq90y$gYUwC+QodOGQ{-Rrzm$dnDnZ8b+Z&XDcWi^e;Q z^EfQ{I6zY6qX#k{MoGd5NOqu8QMObl=tX)ObVdk3t@+dt(Fmfik3nV;q=9386-gaoW!Az{jWV|1h zLkP3tZ|@T)uYhRy01jFhQu_&CX`b_4BO#m@eiq>z!qd&K{qv;y6w2ojohqcGT{F|U8vJ==xy+4BjvD+0^pp8tMCs5Rh&O_RWKFys zJWE!a-vpP5OIAA}9)=c^a&If>frn=MFX{O_&dilExHyg*wp0pT)&Y?&O3Ra{;bwcfni XPRP3jx9iZ)rG`|W3v?dwYwy5+>m!>D delta 1146 zcmZXSO-vI(6vsQuc1!JUyZxd-i+q$8N>i)?8o&4mN+O9dYPfK)Svx~1-L_0 zGm9goZ;_Tifj+geRUdL;7xtQ=YIw8tZhNn9X99_d{1E&19((M2`QlQLHp}3&nc1YSb{RNpIT&ebXr5wrqpij;Bg2 z%P0|+Q*LTH@7V{5j`I(lU@a}oUMpH)h?_?;hx9xo(KLIn%;-swOaj7yln@^&Vc!Ut zQ-BN$dXoPL;qZdJZ(RfQx`j?c0Go+RMXw4lob!0TuvWz-7P{ zz*PXGrPn#&7Q~>vTYyWo$y1xRK(wFU0GDEy3T153Mc60uFT?7552Nfrli43VhVC&v zxPaE%Yr$ot+ycQWdlQ<^i2C3n6kg2HRt?jH5xCzVv`JSX5CCZGS16I4fTbTGIueIf z6rciv92xnz@%iu~DzSQa32m^C;R>2#@ksnmtn-xAn(Yo+*A^!@YA?ELG^qxMLN-S~ z@zeTN*(5Z;j7Ve;GCKZV5ISVh&}pZEtK}Mvfb%4Ig)|F%zWE^YnX<(l9EH{!{H;4z zgE4bFce~wAbBksGS-=W_H%;pf#X8s!mES0r6h6=p_< F{{n;=6odc( diff --git a/handlers/__pycache__/pay.cpython-310.pyc b/handlers/__pycache__/pay.cpython-310.pyc index 7a539eca28377ad786a485d2a92409cae8a82edf..e1e3da10ccbb084d8646406d93d68cbbf14dafdb 100644 GIT binary patch delta 3009 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 delta 1237 zcmZWo&u<$=7@co+y}Mp}V<+CEcJreoq98XA1Fez}B}KFcN>v#`1xO3R+GHk)Q^&T( zlaz#A8gL}IWd*G&{@*Ltt9 zE7b+~2B`WOW);xSAW$(aAU>co7jt+_X!s{z3Y*Q+R#;~>E?T9gXlzt#e6G1KGU^G< zK}IEvEBQqrhvRFWLY~3VHBU=hJu%J|hL0ikJ|y*mNu;y?Igoe=$brX3 z)U?0R>%s)1t>JjO9C#M)eyGF|b=OVjz)mP?feoM+` zYpGO{b344$YSbEaE-QP@y86xh18w^?@gw5C^(7S4zt$8Csd4*~x@ljxMzxt^O4+Z! z6AZ8IO4+Da)&&nGuiWALL3SYRRry^lw1WIV5xiOB^=fOYvKH3D`Z^E1L8V&Xt4bcI zai^4emX+|;`_2qx)u+xB=K8={f_e4Wolt)|xrGUA;s@xOER-Rxu-=Qe4?fnq&T=i> zS!3bVYqtHtExy+hnQOBWbK@Gbd{##GV>+R!&JHl%^J8f_Yz(qxnw%*22a zG7%*j0zln-XlWN;fZD0BJ2yXDGS zL%z|Hp^z(rZ*U>PT18A@*WGl_)xoP3d6dKj4bvKC5Q-`Bg61!(jGLdnr0J}Nq=uI? zyo^w^MK8rUb;G?pe_6{{G@SAtx80$BE|tOGMLXGg4uKvz)Kx#Z6L1X!nS5mdwIVHx ztGJ3q4NDr%-X@k&`$+36YCN@aa~^4t_WKz7|M9MWdeo;a=EjI=^`YxC=@CNp`v1KwGp%jGi;+svz3L&W3JGy19R z=2~CJKN?1WD47dP&YssF0rA5uw?&ticRH3`UgkUHs^|z-7vAA=;2tG2^38~oAM7@% zqNRBi0P)=S?jtKRp)X`keh(F*IShP3L&MwU}f3X@(*lMXr(+$&9SL5l!W$ zwa-3LLEG{yJ(9-IUN@Q{>xZwS*Ga3OH^N0k9#KS;H1;XX z!^FoHh-X2V^crqeI4w7*K~XN~NBR$f!l-QN%WQUgM;~EW%x?S> z=9P(=^RpNyRLeCtuoSpLh6!Rt00?k`s^e9nOx=}iCNHo#Ih~v=97Xpn7OE9DaPi5? z-EQ(_a+ob{zfE3Z)p2x$3BobLDZ&}V7Kce%Pa&*CC%sk?brA@-P8&A}Il@iCVM3gc zKv)_q<0I7DYgnvTvE^kWf54Lj#G>t0hhtKxadccJ2x-16!liNkcwZC2{S zaZ7$XaGtiBRK?mLg1<;}l^Gn>@&mPf@DR6p3jC+q2QeJm|K-*(5G={h=97(%3bM0= z-@~Ny77f$lk+8px7w(7IpAtx(6ZlR;G}{z>jXo<G(v$j(QM<>*{7S&{4UQp3w_wW|%ql1MJwo Ar~m)} diff --git a/handlers/__pycache__/texts.cpython-310.pyc b/handlers/__pycache__/texts.cpython-310.pyc index a93955dd079e1c8103346997b6ce9d5a9b045dde..aa3a631de74660c4b94d572d53532d4cf13bd48c 100644 GIT binary patch delta 2048 zcmZ`(-E$LF6yM!!lWm%$pK0iKfFcAW=?9`#1WTLJNSbz%l#iX!oiu0LW=NCHW>cZS z1Oybv8Nlv1<0$Ch_~MKYsV!rL0`ed1zB%`S2lc_esLzam=Wa@AJLB%`J@9jNWJSwzIytk8Key7U%M?mEr{BnQc0F1~03mjN(a==5GDXm~Lr2yro zv_pj{9pI#L!YZ=hf=Z~uoNA~sWi8a1vfze#bK<$liyUkbEzltH&?uHclW2uz(FQG| z0If3f6$5RzEMhr$MLTR2D`1=GfbF6a+C>-a5G&you?lvI)v!yf0iRe4yTv-#Bf8;L zu^#q{9`HlpG9zw*4(LSL0A0|1nJI{kun%5CLlf+W*HJdZ8}KH|7U+pFzPDz}I{n@L zu31}WM~62$dYIS~Q`vk*l~DI_gjeO$Dsd(g>GW74c}jwWnwYh9Z;C~I*0z6>5%pQ2 zfG{Q8ikG+@`5oBnf4-S3di6Q|NBxd*(fCkb@){RWd0al`!(vnEu-jSN>kQ?$vMUMg1rI7D=VaP|ZryN_ISzmRDGY2{9{x!MGCZR5qhd z=1AGJl1j=e7PO{XFk;0@XU!bW9CY?4dVD-#I|@4g6|H$K(x zd5ufBPZjx{lH+f_!>9 zrJSJ$Ni^J2)MP~O*s_JyE zClFBmQ*t1dO=lyrdTb;jjb+tCIW?iEZ!7Y+tSE`J^gZKai7lVQ^OORm4zbMPi7zmj z#pIcE0s_7L@{9>X0RoeW45Vcx7ogW5=bt`9swZSsT0=~yGN;Z_Vw9nsRXixPbM?RS z6j&Se^tjflDD~7=^tc-eG{J=f$8jwGoa3y|xaYi8XrwT6;s6en|w$g+eAD5x^|orIqEDyZlM4{G2=A84q95B;EH0Q?w) z0EQrl281vS1`a?N2Vnq*U=Xtrj2=obgfWbhjbH+Y$wu)Sj*yMvC??6qajYbz$A6Lp zDJ}Pc_t(nSQ?ppgtrfbz)z5MF_2^gmqr08FJ1O*X0e=#iN~e?AuC%bhxSes>o@38+ z_T5j(_(+uk>@b0%D6;27QMA9xiAUpl_b8c?QzAd@?WsF#AL6w(Z&npx;A^oL7S-p5$h=&r zXOP3ju7Z)LlM%o<5)RoksveTyr^A+&Dhm)0JcR@vMrgZpbL zBZGTAl{kP%u23o$pDdN>9h9tcF_+Cl_@TLJnH!ID#ZBw>?CK>xcV`uX!e2BC=DN8I zk#myGTMx3iJpJnjESq_=Oe87-`h;4XMR1)|(XQtS#S`LUWL!wUFy;P2#iQuy^UXHy gHrRqp%w8l2Ns8)DPv49i9xq*4TwT%qI-QXJA8bY5lmGw# diff --git a/handlers/admin.py b/handlers/admin/admin.py similarity index 93% rename from handlers/admin.py rename to handlers/admin/admin.py index 4bfafe10..86bfa3f1 100644 --- a/handlers/admin.py +++ b/handlers/admin/admin.py @@ -1,6 +1,6 @@ from aiogram import Router, types from aiogram.filters import Command -from database import add_balance_to_client, get_balance, check_connection_exists # Импорт необходимых функций +from database import add_balance_to_client, get_balance, check_connection_exists from config import ADMIN_ID router = Router() diff --git a/handlers/backup_handler.py b/handlers/backup_handler.py deleted file mode 100644 index 0dcbe436..00000000 --- a/handlers/backup_handler.py +++ /dev/null @@ -1,17 +0,0 @@ -from aiogram import Router -from aiogram.filters import Command -from aiogram.types import Message -from config import ADMIN_ID - -router = Router() - -@router.message(Command('backup')) -async def backup_command(message: Message): - if message.from_user.id != ADMIN_ID: - await message.answer("У вас нет прав для выполнения этой команды.") - return - - from backup import backup_database - await message.answer("Запускаю бэкап базы данных...") - await backup_database() - await message.answer("Бэкап завершен и отправлен админу.") \ No newline at end of file diff --git a/handlers/commands.py b/handlers/commands.py index 603febe9..8473d65e 100644 --- a/handlers/commands.py +++ b/handlers/commands.py @@ -6,13 +6,13 @@ import asyncpg from bot import bot from config import ADMIN_ID, DATABASE_URL -from handlers.backup_handler import backup_command from handlers.pay import ReplenishBalanceState, process_custom_amount_input from handlers.profile import process_callback_view_profile from handlers.start import start_command from handlers.texts import TRIAL -from handlers.admin import cmd_add_balance -from handlers.key_management import handle_key_name_input +from handlers.admin.admin import cmd_add_balance +from handlers.keys.key_management import handle_key_name_input +from aiogram.types import Message router = Router() @@ -22,6 +22,17 @@ class Form(StatesGroup): viewing_profile = State() waiting_for_message = State() +@router.message(Command('backup')) +async def backup_command(message: Message): + if message.from_user.id != ADMIN_ID: + await message.answer("У вас нет прав для выполнения этой команды.") + return + + from backup import backup_database + await message.answer("Запускаю бэкап базы данных...") + await backup_database() + await message.answer("Бэкап завершен и отправлен админу.") + @router.message(Command('start')) async def handle_start(message: types.Message, state: FSMContext): await start_command(message) diff --git a/handlers/instructions.jpg b/handlers/instructions/instructions.jpg similarity index 100% rename from handlers/instructions.jpg rename to handlers/instructions/instructions.jpg diff --git a/handlers/instructions.py b/handlers/instructions/instructions.py similarity index 100% rename from handlers/instructions.py rename to handlers/instructions/instructions.py diff --git a/handlers/key_management.py b/handlers/keys/key_management.py similarity index 96% rename from handlers/key_management.py rename to handlers/keys/key_management.py index 86843e95..1badcba6 100644 --- a/handlers/key_management.py +++ b/handlers/keys/key_management.py @@ -1,6 +1,7 @@ import uuid from datetime import datetime, timedelta +from bot import dp import asyncpg from aiogram import F, Router from aiogram.fsm.context import FSMContext @@ -9,12 +10,11 @@ from aiogram.types import (CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, Message) from auth import link, login_with_credentials -from bot import bot, dp from client import add_client from config import (ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL, SERVERS) from database import add_connection, get_balance, store_key, update_balance -from handlers.instructions import send_instructions +from handlers.instructions.instructions import send_instructions from handlers.profile import process_callback_view_profile from handlers.texts import KEY, KEY_TRIAL, NULL_BALANCE, key_message_success from handlers.utils import sanitize_key_name @@ -121,7 +121,7 @@ async def handle_key_name_input(message: Message, state: FSMContext): key_name = sanitize_key_name(message.text) if not key_name: - await message.bot.send_message(tg_id, "📝 Пожалуйста, назовите профиль на английском языке.") + await message.bot.send_message(tg_id, "📝 Пожалуйста, назовите ключ устройства на английском языке.") return data = await state.get_data() @@ -164,7 +164,7 @@ async def handle_key_name_input(message: Message, state: FSMContext): if not response.get("success", True): error_msg = response.get("msg", "Неизвестная ошибка.") if "Duplicate email" in error_msg: - await message.bot.send_message(tg_id, "❌ Этот email уже используется. Пожалуйста, выберите другое имя для ключа.") + await message.bot.send_message(tg_id, "❌ Это имя уже используется. Пожалуйста, выберите другое имя для ключа.") await state.set_state(Form.waiting_for_key_name) return else: diff --git a/handlers/keys.py b/handlers/keys/keys.py similarity index 90% rename from handlers/keys.py rename to handlers/keys/keys.py index 275175e6..a7030cfb 100644 --- a/handlers/keys.py +++ b/handlers/keys/keys.py @@ -11,6 +11,7 @@ from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL, SERVERS from database import get_balance, update_balance from handlers.texts import NO_KEYS from handlers.texts import key_message, key_relocated +from handlers.texts import RENEWAL_PLANS, INSUFFICIENT_FUNDS_MSG, KEY_NOT_FOUND_MSG, SUCCESS_RENEWAL_MSG, ERROR_RENEWAL_MSG, PLAN_SELECTION_MSG locale.setlocale(locale.LC_TIME, 'ru_RU.UTF-8') @@ -159,17 +160,15 @@ async def process_callback_renew_key(callback_query: types.CallbackQuery): expiry_time = record['expiry_time'] current_time = datetime.utcnow().timestamp() * 1000 keyboard = types.InlineKeyboardMarkup(inline_keyboard=[ - [types.InlineKeyboardButton(text='📅 1 месяц (100 руб.)', callback_data=f'renew_plan|1|{client_id}')], - [types.InlineKeyboardButton(text='📅 3 месяца (285 руб.)', callback_data=f'renew_plan|3|{client_id}')], - [types.InlineKeyboardButton(text='📅 6 месяцев (540 руб.)', callback_data=f'renew_plan|6|{client_id}')], - [types.InlineKeyboardButton(text='📅 12 месяцев (1000 руб.)', callback_data=f'renew_plan|12|{client_id}')], + [types.InlineKeyboardButton(text=f'📅 1 месяц ({RENEWAL_PLANS["1"]["price"]} руб.)', callback_data=f'renew_plan|1|{client_id}')], + [types.InlineKeyboardButton(text=f'📅 3 месяца ({RENEWAL_PLANS["3"]["price"]} руб.)', callback_data=f'renew_plan|3|{client_id}')], + [types.InlineKeyboardButton(text=f'📅 6 месяцев ({RENEWAL_PLANS["6"]["price"]} руб.)', callback_data=f'renew_plan|6|{client_id}')], + [types.InlineKeyboardButton(text=f'📅 12 месяцев ({RENEWAL_PLANS["12"]["price"]} руб.)', callback_data=f'renew_plan|12|{client_id}')], [types.InlineKeyboardButton(text='🔙 Назад', callback_data='view_profile')] ]) balance = await get_balance(tg_id) - response_message = (f"Выберите план продления:\n\n" - f"💰 Баланс: {balance} руб.\n\n" - f"📅 Текущая дата истечения ключа: {datetime.utcfromtimestamp(expiry_time / 1000).strftime('%Y-%m-%d %H:%M:%S')}") + response_message = PLAN_SELECTION_MSG.format(balance=balance, expiry_date=datetime.utcfromtimestamp(expiry_time / 1000).strftime('%Y-%m-%d %H:%M:%S')) await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=keyboard, parse_mode="HTML") @@ -181,6 +180,7 @@ async def process_callback_renew_key(callback_query: types.CallbackQuery): 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 @@ -222,7 +222,7 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery): @router.callback_query(lambda c: c.data.startswith('renew_plan|')) 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('|')[2] + plan, client_id = callback_query.data.split('|')[1], callback_query.data.split('|')[2] days_to_extend = 30 * int(plan) try: @@ -241,14 +241,7 @@ async def process_callback_renew_plan(callback_query: types.CallbackQuery): else: new_expiry_time = int(expiry_time + timedelta(days=days_to_extend).total_seconds() * 1000) - if plan == '1': - cost = 100 - elif plan == '3': - cost = 285 - elif plan == '6': - cost = 540 - elif plan == '12': - cost = 1000 + cost = RENEWAL_PLANS[plan]['price'] balance = await get_balance(tg_id) if balance < cost: @@ -256,7 +249,7 @@ async def process_callback_renew_plan(callback_query: types.CallbackQuery): back_button = types.InlineKeyboardButton(text='Назад', callback_data='view_profile') keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[replenish_button], [back_button]]) - await bot.edit_message_text("Недостаточно средств для продления ключа.", chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=keyboard) + await bot.edit_message_text(INSUFFICIENT_FUNDS_MSG, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=keyboard) return session = await login_with_credentials(server_id, ADMIN_USERNAME, ADMIN_PASSWORD) @@ -265,14 +258,14 @@ async def process_callback_renew_plan(callback_query: types.CallbackQuery): 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} месяц(-а)." + response_message = SUCCESS_RENEWAL_MSG.format(months=RENEWAL_PLANS[plan]['months']) back_button = types.InlineKeyboardButton(text='Назад', callback_data='view_profile') 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) else: - await bot.edit_message_text("Ошибка при продлении ключа.", chat_id=tg_id, message_id=callback_query.message.message_id) + await bot.edit_message_text(ERROR_RENEWAL_MSG, chat_id=tg_id, message_id=callback_query.message.message_id) else: - await bot.edit_message_text("Ключ не найден.", chat_id=tg_id, message_id=callback_query.message.message_id) + await bot.edit_message_text(KEY_NOT_FOUND_MSG, chat_id=tg_id, message_id=callback_query.message.message_id) finally: await conn.close() @@ -294,7 +287,7 @@ async def process_callback_change_location(callback_query: types.CallbackQuery): try: for server_id, server in SERVERS.items(): count = await conn.fetchval('SELECT COUNT(*) FROM keys WHERE server_id = $1', server_id) - percent_full = (count / 100) * 100 + percent_full = (count / 60) * 100 if count <= 60 else 100 server_name = f"{server['name']} ({percent_full:.1f}%)" server_buttons.append([types.InlineKeyboardButton(text=server_name, callback_data=f'select_server&{server_id}&{client_id}')]) finally: diff --git a/handlers/trial_key.py b/handlers/keys/trial_key.py similarity index 99% rename from handlers/trial_key.py rename to handlers/keys/trial_key.py index 1d57a599..111ea657 100644 --- a/handlers/trial_key.py +++ b/handlers/keys/trial_key.py @@ -1,4 +1,3 @@ -import random import asyncpg import uuid from config import DATABASE_URL, SERVERS, ADMIN_USERNAME, ADMIN_PASSWORD diff --git a/handlers/pay.py b/handlers/pay.py index 2dfefe25..8b81498f 100644 --- a/handlers/pay.py +++ b/handlers/pay.py @@ -13,6 +13,7 @@ from config import YOOKASSA_SECRET_KEY, YOOKASSA_SHOP_ID from database import (add_connection, check_connection_exists, get_key_count, update_balance) from handlers.profile import process_callback_view_profile +from handlers.texts import PAYMENT_OPTIONS router = Router() @@ -59,10 +60,20 @@ async def process_callback_replenish_balance(callback_query: types.CallbackQuery await add_connection(tg_id, balance=0.0, trial=0) amount_keyboard = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text='100 RUB', callback_data='amount_100'), InlineKeyboardButton(text='300 RUB', callback_data='amount_300')], - [InlineKeyboardButton(text='600 RUB', callback_data='amount_600'), InlineKeyboardButton(text='1000 RUB', callback_data='amount_1000')], - [InlineKeyboardButton(text='💰 Ввести свою сумму', callback_data='enter_custom_amount')], - [InlineKeyboardButton(text='⬅️ Назад', callback_data='back_to_profile')] + [ + InlineKeyboardButton(text=PAYMENT_OPTIONS[0]['text'], callback_data=PAYMENT_OPTIONS[0]['callback_data']), + InlineKeyboardButton(text=PAYMENT_OPTIONS[1]['text'], callback_data=PAYMENT_OPTIONS[1]['callback_data']) + ], + [ + InlineKeyboardButton(text=PAYMENT_OPTIONS[2]['text'], callback_data=PAYMENT_OPTIONS[2]['callback_data']), + InlineKeyboardButton(text=PAYMENT_OPTIONS[3]['text'], callback_data=PAYMENT_OPTIONS[3]['callback_data']) + ], + [ + InlineKeyboardButton(text=PAYMENT_OPTIONS[4]['text'], callback_data=PAYMENT_OPTIONS[4]['callback_data']) + ], + [ + InlineKeyboardButton(text=PAYMENT_OPTIONS[5]['text'], callback_data=PAYMENT_OPTIONS[5]['callback_data']) + ] ]) await callback_query.message.edit_text( @@ -72,6 +83,7 @@ async def process_callback_replenish_balance(callback_query: types.CallbackQuery await state.set_state(ReplenishBalanceState.choosing_amount) 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) @@ -126,9 +138,6 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F "currency": "RUB" }, "vat_code": 6 - ## Раскоментируйте следующие строки, если у вас регистрация как ИП, а не самозанятость - ## "payment_subject": "payment", - ## "payment_mode": "full_payment", } ] }, @@ -260,7 +269,9 @@ async def process_custom_amount_input(message: types.Message, state: FSMContext) ) else: await message.answer("Ошибка при создании платежа.") + except Exception as e: - await message.answer(f"Произошла ошибка при обработке платежа: {str(e)}") + logging.error(f"Ошибка при создании платежа: {e}") + await message.answer("Произошла ошибка при создании платежа.") else: - await message.answer("Некорректный ввод. Пожалуйста, введите сумму числом:") \ No newline at end of file + await message.answer("Некорректная сумма. Пожалуйста, введите сумму еще раз:") diff --git a/handlers/profile.py b/handlers/profile.py index cdf736c3..7cee93a8 100644 --- a/handlers/profile.py +++ b/handlers/profile.py @@ -5,7 +5,7 @@ from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup from bot import bot from database import get_balance, get_key_count, get_referral_stats -from handlers.texts import profile_message_send, invite_message_send +from handlers.texts import profile_message_send, invite_message_send, CHANNEL_LINK, get_referral_link class ReplenishBalanceState(StatesGroup): @@ -13,6 +13,7 @@ class ReplenishBalanceState(StatesGroup): waiting_for_admin_confirmation = State() router = Router() + async def process_callback_view_profile(callback_query: types.CallbackQuery, state: FSMContext): tg_id = callback_query.from_user.id username = callback_query.from_user.full_name @@ -28,7 +29,7 @@ async def process_callback_view_profile(callback_query: types.CallbackQuery, sta ) profile_message += ( - f"Обязательно подпишитесь на канал здесь\n" + f"Обязательно подпишитесь на канал здесь\n" ) if key_count == 0: @@ -66,7 +67,7 @@ async def process_callback_view_profile(callback_query: types.CallbackQuery, sta @router.callback_query(lambda c: c.data == 'invite') async def invite_handler(callback_query: types.CallbackQuery): tg_id = callback_query.from_user.id - referral_link = f"https://t.me/SoloNetVPN_bot?start=referral_{tg_id}" + referral_link = get_referral_link(tg_id) referral_stats = await get_referral_stats(tg_id) @@ -90,4 +91,4 @@ async def invite_handler(callback_query: types.CallbackQuery): @router.callback_query(lambda c: c.data == 'view_profile') async def view_profile_handler(callback_query: types.CallbackQuery, state: FSMContext): - await process_callback_view_profile(callback_query, state) \ No newline at end of file + await process_callback_view_profile(callback_query, state) diff --git a/handlers/start.py b/handlers/start.py index 549822bf..d3d140d8 100644 --- a/handlers/start.py +++ b/handlers/start.py @@ -8,7 +8,7 @@ from handlers.texts import ABOUT_VPN, WELCOME_TEXT from bot import bot from config import CHANNEL_URL, SUPPORT_CHAT_URL from database import add_connection, add_referral, check_connection_exists, get_trial -from handlers.trial_key import create_trial_key +from handlers.keys.trial_key import create_trial_key from handlers.texts import INSTRUCTIONS_TRIAL router = Router() @@ -65,23 +65,19 @@ async def handle_connect_vpn(callback_query: CallbackQuery): await callback_query.message.delete() user_id = callback_query.from_user.id - # Создаём триальный ключ trial_key_info = await create_trial_key(user_id) if 'error' in trial_key_info: await callback_query.message.answer(trial_key_info['error']) else: - # Формируем сообщение с ключом и инструкциями key_message = ( f"Ваш ключ доступа:\n
{trial_key_info['key']}
\n\n" f"Инструкции:\n{INSTRUCTIONS_TRIAL}" ) - # Кнопка "В профиль" button_profile = InlineKeyboardButton(text='👤 Мой профиль', callback_data='view_profile') inline_keyboard_profile = InlineKeyboardMarkup(inline_keyboard=[[button_profile]]) - # Отправляем текст с ключом и инструкциями в виде цитаты await callback_query.message.answer( key_message, parse_mode='HTML', diff --git a/handlers/texts.py b/handlers/texts.py index 173bda4f..c840fa45 100644 --- a/handlers/texts.py +++ b/handlers/texts.py @@ -1,5 +1,29 @@ -from config import BOT_VERSION +BOT_VERSION = '1.3.2' +### Функции образования цен и кнопок продления ключа +PAYMENT_OPTIONS = [ + {'text': '100 RUB', 'callback_data': 'amount_100'}, + {'text': '300 RUB', 'callback_data': 'amount_300'}, + {'text': '600 RUB', 'callback_data': 'amount_600'}, + {'text': '1000 RUB', 'callback_data': 'amount_1000'}, + {'text': '💰 Ввести свою сумму', 'callback_data': 'enter_custom_amount'}, + {'text': '⬅️ Назад', 'callback_data': 'back_to_profile'}, +] + +RENEWAL_PLANS = { + '1': {'months': 1, 'price': 100}, + '3': {'months': 3, 'price': 285}, + '6': {'months': 6, 'price': 540}, + '12': {'months': 12, 'price': 1000}, +} + +INSUFFICIENT_FUNDS_MSG = "Недостаточно средств для продления ключа." +KEY_NOT_FOUND_MSG = "Ключ не найден." +SUCCESS_RENEWAL_MSG = "Ваш ключ был успешно продлен на {months} месяц(-а)." +ERROR_RENEWAL_MSG = "Ошибка при продлении ключа." +PLAN_SELECTION_MSG = "Выберите план продления:\n\n💰 Баланс: {balance} руб.\n\n📅 Текущая дата истечения ключа: {expiry_date}" + +### Текст главного меню WELCOME_TEXT = ( "🎉 SoloNet — твой доступ в свободный интернет! 🌐✨\n\n" "Наши преимущества:\n" @@ -80,7 +104,6 @@ INSTRUCTIONS_TRIAL = ( "💬 Если у вас возникнут вопросы, не стесняйтесь обращаться в поддержку." ) - KEY_EXPIRY_10H = "🔔 Уведомление: Ваш ключ {email} для сервера {server_id} истекает через 10 часов.\n" \ "Дата истечения: {expiry_date}"\ "Перейдите в профиль и пополните баланс, всего 100 рублей на целый месяц"\ @@ -96,6 +119,11 @@ KEY_RENEWAL_FAILED = "Не удалось продлить ключ на пан KEY_DELETED = "Ваш ключ был удален из-за недостаточного баланса." KEY_DELETION_FAILED = "Не удалось удалить ключ с панели, обратитесь в поддержку." +CHANNEL_LINK = "https://t.me/solonet_vpn" + +def get_referral_link(user_id): + return f"https://t.me/SoloNetVPN_bot?start=referral_{user_id}" + def key_message_success(connection_link, remaining_time_message): key_message = ( "✅ Ключ успешно создан:\n"