Merge pull request #7 from Vladless/new

New
This commit is contained in:
Vladislav Lisitsyn
2024-09-18 01:45:13 +03:00
committed by GitHub
12 changed files with 226 additions and 47 deletions
+4 -1
View File
@@ -8,7 +8,7 @@ storage = MemoryStorage()
dp = Dispatcher(bot=bot, storage=storage)
router = Router()
from handlers import start, profile, keys, stats, expiry, balance
from handlers import start, profile, keys, stats, expiry, balance, pay
from key_management import router as key_management_router
@@ -20,6 +20,9 @@ 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)
async def on_startup(dispatcher: Dispatcher):
await login_with_credentials()
+9
View File
@@ -81,3 +81,12 @@ async def get_balance(tg_id: int) -> str:
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 "Неизвестно"
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()
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+6 -2
View File
@@ -2,6 +2,8 @@ from aiogram import types, Router
import aiosqlite
from config import DATABASE_PATH
from bot import bot
from datetime import datetime
router = Router()
@router.callback_query(lambda c: c.data == 'view_keys')
@@ -11,7 +13,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 k.key
SELECT k.key, c.expiry_time
FROM keys k
JOIN connections c ON k.client_id = c.client_id
WHERE c.tg_id = ?
@@ -20,7 +22,9 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery):
if record:
key = record[0]
response_message = f"Ваш ключ:\n<pre>{key}</pre>"
expiry_time = record[1]
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000).strftime("%Y-%m-%d %H:%M:%S")
response_message = f"Ваш ключ:\n<pre>{key}</pre>\nДата окончания: <b>{expiry_date}</b>"
else:
response_message = "У вас нет ключей."
+116
View File
@@ -0,0 +1,116 @@
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 DATABASE_PATH, ADMIN_ID
from database import get_balance, update_balance
from bot import bot
router = Router()
class ReplenishBalanceState(StatesGroup):
choosing_transfer_method = State()
waiting_for_admin_confirmation = State()
@router.callback_query(lambda c: c.data == 'replenish_balance')
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')]
])
await callback_query.message.answer("Выберите метод перевода:", reply_markup=keyboard)
await callback_query.answer()
@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("Неверные данные для выбора метода перевода.")
return
transfer_method = data[2] # 'requisites' или 'sbp'
if transfer_method == 'requisites':
message = (
"Реквизиты для пополнения:\n"
f'\n'
"Банк: Пример Банк\n"
"Расчетный счет: 12345678901234567890\n"
"Получатель: ИП Иванов Иван Иванович"
f'\n'
"после перевода отправьте чек и ждите подтверждения"
)
elif transfer_method == 'sbp':
message = (
"СБП для пополнения:\n"
f'\n'
"Номер банка: 1234567890\n"
"ФИО: Иванов Иван Иванович\n"
f'\n'
"после перевода отправьте чек и ждите подтверждения"
)
else:
await callback_query.answer("Неверный метод перевода.")
return
await callback_query.message.answer(message)
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("Вы не являетесь администратором.")
return
data = callback_query.data.split('_', 4)
if len(data) < 4:
await callback_query.answer("Неверные данные для обработки запроса.")
return
action = data[1] # 'confirm' или 'decline'
user_id_str = 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
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(user_id, f"Ваш баланс был успешно пополнен на {amount} RUB.")
elif action == 'decline':
await callback_query.message.answer("Пополнение баланса отклонено.")
# Сообщаем пользователю об отклонении
await bot.send_message(user_id, "Ваш запрос на пополнение баланса был отклонен.")
except Exception as e:
# Отладочное сообщение
await callback_query.message.answer(f"Ошибка при пополнении баланса: {e}")
print(f"Ошибка при пополнении баланса: {e}") # Отладочное сообщение в консоль
# Очистка состояния
await state.clear()
await callback_query.answer()
+33 -24
View File
@@ -1,51 +1,60 @@
from aiogram import types, Router
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from database import get_active_key_email, get_balance
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from datetime import datetime
import aiosqlite
from aiogram.fsm.context import FSMContext
from config import DATABASE_PATH
from database import get_balance, has_active_key
router = Router()
@router.callback_query(lambda c: c.data == 'view_profile')
class ReplenishBalanceState(StatesGroup):
choosing_transfer_method = State()
waiting_for_admin_confirmation = State()
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 get_active_key_email(tg_id)
# Проверяем наличие активного ключа
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:
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")
else:
expiry_date = "Неизвестно"
expiry_date = datetime.utcfromtimestamp(record[0] / 1000).strftime("%Y-%m-%d %H:%M:%S")
balance = await get_balance(tg_id)
profile_message = (
f"Профиль клиента:\n"
f"Никнейм: @{username}\n"
f"Email: {email}\n"
f"Дата окончания ключа: {expiry_date}\n"
f"Баланс: {balance}\n"
)
balance = await get_balance(tg_id)
profile_message = (
f"Профиль \n\n"
f"ID: @{tg_id}\n"
f"Ключ: {email if email else 'Нет активного ключа'}\n"
f"Баланс: {balance} RUB\n"
)
button_view_keys = InlineKeyboardButton(text='Мои ключи', callback_data='view_keys')
keyboard = InlineKeyboardMarkup(inline_keyboard=[[button_view_keys]])
# Кнопки для действий в профиле
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]])
profile_message += "Вы можете просмотреть ваши ключи ниже:"
else:
profile_message = "У вас нет активных ключей."
keyboard = None
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.answer()
@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)
+28 -12
View File
@@ -1,26 +1,42 @@
from aiogram import types, Router
from aiogram.filters import Command
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, ReplyKeyboardMarkup, KeyboardButton
from config import ADMIN_CHAT_ID
router = Router()
@router.message(Command("start"))
async def start_command(message: types.Message):
welcome_text = "Добро пожаловать! Вы можете создать ключ для подключения VPN, просмотреть статистику использования, узнать дату окончания ключа или просмотреть ваш профиль."
async def start_command(message: types.Message, callback_data: str = None):
welcome_text = (
"Добро пожаловать! Вы можете создать ключ для подключения VPN, "
"просмотреть статистику использования, узнать дату окончания ключа или "
"просмотреть ваш профиль."
)
# Создаем кнопки для команд
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_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')
keyboard = InlineKeyboardMarkup(inline_keyboard=[
# Создаем клавиатуру с кнопками
inline_keyboard = InlineKeyboardMarkup(inline_keyboard=[
[button_create_key],
[button_view_stats],
[button_view_expiry],
# [button_view_stats],
# [button_view_expiry],
[button_view_profile]
])
main_menu_keyboard = ReplyKeyboardMarkup(keyboard=[[KeyboardButton(text="В начало")]], resize_keyboard=True)
# Создаем кнопку для возврата в начало
reply_keyboard = ReplyKeyboardMarkup(
keyboard=[
[KeyboardButton(text="В начало")],
[KeyboardButton(text="Мой профиль")]
],
resize_keyboard=True
)
await message.reply(welcome_text, reply_markup=keyboard)
await message.answer("Чтобы вернуться в начало, нажмите кнопку ниже:", reply_markup=main_menu_keyboard)
# Отправляем сообщение с кнопками
await message.reply(welcome_text, reply_markup=inline_keyboard)
await message.answer(
"",
reply_markup=reply_keyboard
)
+20 -1
View File
@@ -15,10 +15,11 @@ import re
from bot import dp
from handlers.start import start_command
from bot import bot
from handlers.stats import session
from handlers.profile import process_callback_view_profile
router = Router()
def escape_markdown(text: str) -> str:
"""
Экранирование специальных символов для Markdown.
@@ -40,10 +41,27 @@ async def process_callback_create_key(callback_query: types.CallbackQuery, state
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()
if message.text == "Мой профиль":
# Создайте фейковый объект CallbackQuery для вызова функции
callback_query = types.CallbackQuery(
id="1", # Используйте уникальный идентификатор
from_user=message.from_user,
chat_instance='',
data='view_profile',
message=message
)
await process_callback_view_profile(callback_query, state)
return
if message.text == "/start":
await start_command(message)
return
if message.text == "В начало":
await start_command(message)
return
@@ -89,6 +107,7 @@ async def handle_admin_confirmation(callback_query: CallbackQuery, state: FSMCon
if callback_query.message.chat.id != int(ADMIN_CHAT_ID):
return
session = login_with_credentials(ADMIN_USERNAME, ADMIN_PASSWORD)
data = callback_query.data.split('_', 3)
tg_id = int(data[2])
key_name = data[3]
+3
View File
@@ -2,11 +2,14 @@ aiofiles==23.2.1
aiogram==3.2.0
aiohttp==3.9.5
aiosignal==1.3.1
aiosqlite==0.20.0
annotated-types==0.7.0
anyio==4.4.0
async-timeout==4.0.3
attrs==24.2.0
certifi==2024.8.30
charset-normalizer==3.3.2
exceptiongroup==1.2.2
frozenlist==1.4.1
greenlet==3.1.0
h11==0.14.0