Merge pull request #8 from Vladless/new

New
This commit is contained in:
Vladislav Lisitsyn
2024-09-21 03:00:53 +03:00
committed by GitHub
17 changed files with 926 additions and 409 deletions
+1
View File
@@ -5,3 +5,4 @@
/database.db
/bot_old.py
/bot_old_2.py
/database.db
+1
View File
@@ -19,6 +19,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:
+1 -3
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, 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)
+78 -12
View File
@@ -1,15 +1,10 @@
import requests
import json
from config import ADMIN_USERNAME, ADMIN_PASSWORD, GET_INBOUNDS_URL
import uuid
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,
@@ -23,22 +18,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}")
@@ -48,7 +40,81 @@ def add_client(session, client_id: str, email: str, tg_id: str, limit_ip: int, t
else:
print(f"Ошибка при добавлении клиента: {response.status_code}, {response.text}")
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)
if not client_data:
print("Не удалось получить данные клиента.")
return False
def generate_client_id():
return str(uuid.uuid4())
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,
"settings": json.dumps({
"clients": [
{
"id": client_id,
"alterId": 0,
"email": email.lower(),
"limitIp": 2,
"totalGB": 429496729600000,
"expiryTime": updated_expiry_time,
"enable": True,
"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
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
+72 -80
View File
@@ -1,92 +1,84 @@
import aiosqlite
import asyncpg
from datetime import datetime
from config import DATABASE_PATH
import aiosqlite
from config import DATABASE_URL
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,
balance REAL NOT NULL DEFAULT 0.0, -- Добавлено поле для баланса
PRIMARY KEY (tg_id, client_id)
)
''')
await db.execute('''
CREATE TABLE IF NOT EXISTS keys (
client_id TEXT NOT NULL,
email TEXT NOT NULL,
created_at INTEGER NOT NULL,
key TEXT NOT NULL,
PRIMARY KEY (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, client_id: str, email: str, expiry_time: int, balance: float = 0.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))
await db.commit()
async def add_connection(tg_id: int, balance: float = 0.0, trial: int = 0):
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(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))
await db.commit()
async def store_key(tg_id: int, client_id: str, email: str, expiry_time: int, key: str):
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 connections WHERE tg_id = ? AND expiry_time > ?",
(tg_id, int(datetime.utcnow().timestamp() * 1000))) 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_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 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 "Неизвестно"
async def get_balance(tg_id: int) -> float:
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:
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:
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
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-33
View File
@@ -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()
+245 -23
View File
@@ -1,35 +1,257 @@
import asyncpg
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_balance, update_balance
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()
# Обработка запроса на просмотр ключей
@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
try:
async with aiosqlite.connect(DATABASE_PATH) as db:
async with db.execute('''
SELECT k.key, c.expiry_time
FROM keys k
JOIN connections c ON k.client_id = c.client_id
WHERE c.tg_id = ?
''', (tg_id,)) as cursor:
record = await cursor.fetchone()
conn = await asyncpg.connect(DATABASE_URL)
try:
records = await conn.fetch('''
SELECT email, client_id FROM keys WHERE tg_id = $1
''', tg_id)
if records:
# Создаем кнопки для каждого ключа
buttons = []
for record in records:
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])
# Создаем клавиатуру с кнопками
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')
back_button = types.InlineKeyboardButton(text='Назад', callback_data='view_profile') # Измените на правильное значение для кнопки "Назад"
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<pre>{key}</pre>\nДата окончания: <b>{expiry_date}</b>"
else:
response_message = "У вас нет ключей."
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)
finally:
await conn.close()
except Exception as e:
response_message = f"Ошибка при получении ключей: {e}"
await bot.send_message(tg_id, response_message, parse_mode="HTML", 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.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]
try:
conn = await asyncpg.connect(DATABASE_URL)
try:
record = await conn.fetchrow('''
SELECT k.key, k.expiry_time
FROM keys k
WHERE k.tg_id = $1 AND k.email = $2
''', tg_id, key_name)
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<pre>{key}</pre>\n"
f"Дата окончания: <b>{expiry_date.strftime('%Y-%m-%d %H:%M:%S')}</b>\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], [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:
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()
# Обработка запроса на удаление ключа
@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]
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.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]
try:
conn = await asyncpg.connect(DATABASE_URL)
try:
record = await conn.fetchrow('SELECT email FROM keys WHERE client_id = $1', client_id)
if record:
email = record['email']
session = login_with_credentials(ADMIN_USERNAME, ADMIN_PASSWORD)
success = delete_client(session, client_id)
if success:
await conn.execute('DELETE FROM keys WHERE client_id = $1', client_id)
response_message = "Ключ был успешно удален."
else:
response_message = "Ошибка при удалении клиента через API."
else:
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.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]
try:
conn = await asyncpg.connect(DATABASE_URL)
try:
record = await conn.fetchrow('SELECT email, expiry_time FROM keys WHERE client_id = $1', client_id)
if record:
email = record['email']
expiry_time = record['expiry_time']
current_time = datetime.utcnow().timestamp() * 1000
if expiry_time <= current_time:
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}')],
[types.InlineKeyboardButton(text='Назад', callback_data='view_profile')]
])
balance = await get_balance(tg_id)
response_message = (f"Выберите план продления:\n"
f"Баланс: <b>{balance} руб.</b>\n"
f"Действующий ключ истекает <b>{datetime.utcfromtimestamp(expiry_time / 1000).strftime('%Y-%m-%d %H:%M:%S')}</b>")
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 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)
try:
conn = await asyncpg.connect(DATABASE_URL)
try:
record = await conn.fetchrow('SELECT email, expiry_time FROM keys WHERE client_id = $1', client_id)
if record:
email = record['email']
expiry_time = record['expiry_time']
current_time = datetime.utcnow().timestamp() * 1000
if expiry_time <= current_time:
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 bot.edit_message_text("Недостаточно средств для продления ключа.", chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=keyboard)
return
session = login_with_credentials(ADMIN_USERNAME, ADMIN_PASSWORD)
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.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)
else:
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.edit_message_text(f"Ошибка при продлении ключа: {e}", chat_id=tg_id, message_id=callback_query.message.message_id)
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)
+184 -57
View File
@@ -2,115 +2,242 @@ 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 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()
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'):
"""
Отправляет новое сообщение и удаляет предыдущее сообщение (если оно существует), сохраненное в состоянии FSM.
"""
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)
if state:
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:
# Создаем кнопки "Создать ключ" и "Назад"
create_key_button = InlineKeyboardButton(text='Создать ключ', callback_data='create_key')
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="У вас нет ключей. Пополнение баланса возможно только при наличии ключа.\n"
"Пожалуйста, создайте ключ для продолжения.",
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_sbp')]
[InlineKeyboardButton(text='⬅️ Назад', callback_data='back_to_profile')] # Кнопка "Назад"
])
await callback_query.message.answer("Выберите метод перевода:", reply_markup=keyboard)
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 callback_query.answer("Неверные данные для выбора метода перевода.")
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' или 'sbp'
transfer_method = data[2]
if transfer_method == 'requisites':
message = (
"Реквизиты для пополнения:\n"
f'\n'
"Банк: Пример Банк\n"
"Расчетный счет: 12345678901234567890\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 callback_query.message.edit_text(
text="Выберите сумму пополнения:",
reply_markup=amount_keyboard
)
elif transfer_method == 'sbp':
message = (
"СБП для пополнения:\n"
f'\n'
"Номер банка: 1234567890\n"
"ФИО: Иванов Иван Иванович\n"
f'\n'
"после перевода отправьте чек и ждите подтверждения"
)
else:
await callback_query.answer("Неверный метод перевода.")
return
await state.update_data(transfer_method=transfer_method)
await state.set_state(ReplenishBalanceState.choosing_amount)
await callback_query.message.answer(message)
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):
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
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)
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}_{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 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 callback_query.answer("Вы не являетесь администратором.")
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 callback_query.answer("Неверные данные для обработки запроса.")
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[3]
action = data[1]
user_id_str = data[2]
transfer_method = data[3]
amount = int(data[4])
try:
user_id = int(user_id_str) # Преобразование user_id в целое число
user_id = int(user_id_str)
# Получаем данные из состояния для удаления сообщения с реквизитами
state_data = await state.get_data()
requisites_message_id = state_data.get('requisites_message_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:
# Подключаемся к базе данных PostgreSQL
conn = await asyncpg.connect(DATABASE_URL)
try:
# Обновляем баланс пользователя
await update_balance(user_id, amount)
await db.commit()
balance = await get_balance(user_id)
await callback_query.message.answer(f"Баланс пользователя успешно пополнен на {amount} единиц.\nТекущий баланс: {balance}")
# Получаем обновленный баланс
balance = await get_balance(user_id)
# Сообщаем пользователю о пополнении
await bot.send_message(user_id, f"Ваш баланс был успешно пополнен на {amount} RUB.")
# Создаем клавиатуру с кнопкой "Профиль"
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.",
reply_markup=profile_keyboard
)
# Удаляем сообщение с реквизитами
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 callback_query.message.answer("Пополнение баланса отклонено.")
# Сообщаем пользователю об отклонении
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 callback_query.message.answer(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')
+28 -27
View File
@@ -3,9 +3,7 @@ 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, has_active_key
from database import get_balance, get_key_count
router = Router()
@@ -15,44 +13,47 @@ 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 email else 'Нет активного ключа'}\n"
f"Баланс: {balance} RUB\n"
f"<b>Профиль пользователя</b>\n\n"
f"🔹 <b>ID:</b> {tg_id}\n"
f"🔹 <b>Количество ключей:</b> {key_count}\n"
f"🔹 <b>Баланс:</b> {balance} RUB\n"
f"🔹 <b>Дата регистрации:</b> {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')
keyboard = InlineKeyboardMarkup(inline_keyboard=[[button_view_keys], [button_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_back] # Добавляем кнопку "Назад" в клавиатуру
])
profile_message += "\nВы можете просмотреть ваши ключи или пополнить баланс ниже:"
except Exception as e:
profile_message = f"Ошибка при получении данных профиля: {e}"
profile_message = f"❗️ Ошибка при получении данных профиля: {e}"
keyboard = None
# Отправляем сообщение с клавиатурой
await callback_query.message.reply(profile_message, reply_markup=keyboard)
# Редактируем существующее сообщение, а не отправляем новое
await callback_query.message.edit_text(
text=profile_message,
parse_mode='HTML',
reply_markup=keyboard
)
await callback_query.answer()
@router.callback_query(lambda c: c.data == 'view_profile')
+130 -29
View File
@@ -1,42 +1,143 @@
from aiogram import types, Router
from aiogram import 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.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from config import ADMIN_ID, CHANNEL_URL
from bot import 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_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_create_key],
# [button_view_stats],
# [button_view_expiry],
[button_view_profile]
[button_view_profile],
[button_about_vpn],
[button_feedback],
[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.reply(welcome_text, reply_markup=inline_keyboard)
await message.answer(
"",
reply_markup=reply_keyboard
)
@router.callback_query(lambda c: c.data == 'about_vpn')
async def handle_about_vpn(callback_query: CallbackQuery):
info_message = (
"*О VPN*\n\n"
"Мы используем VLESS для обеспечения безопасного и надежного подключения к интернету. "
"Каждому пользователю предоставляется индивидуальный ключ для подключения. "
"Этот ключ необходим для использования нашего VPN-сервиса."
)
# Кнопка "Назад"
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',
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):
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()
@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()
# Пример обработки команды старт
@router.message(Command('start'))
async def cmd_start(message: Message):
await start_command(message)
-45
View File
@@ -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()
+186 -100
View File
@@ -1,55 +1,93 @@
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.fsm.storage.memory import MemoryStorage
import re
import uuid
import asyncio
from datetime import datetime, timedelta
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 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
import uuid
import aiosqlite
import re
from bot import dp
from handlers.start import start_command
from bot import bot
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, bot
from handlers.profile import process_callback_view_profile
from handlers.start import start_command
import asyncpg
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()
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 state.set_state(Form.waiting_for_key_name)
async def process_callback_create_key(callback_query: CallbackQuery, state: FSMContext):
tg_id = callback_query.from_user.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 рублей. "
"Хотите продолжить?",
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):
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()
# Обработка текстовых сообщений
@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()
if message.text == "Мой профиль":
# Создайте фейковый объект CallbackQuery для вызова функции
callback_query = types.CallbackQuery(
id="1", # Используйте уникальный идентификатор
id="1",
from_user=message.from_user,
chat_instance='',
data='view_profile',
@@ -58,90 +96,138 @@ 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 = message.text
tg_name = message.from_user.username
await handle_key_name_input(message, state)
if not key_name:
await message.reply("Имя клиента не указано.")
await state.clear()
return
async def handle_key_name_input(message: Message, state: FSMContext):
tg_id = message.from_user.id
if await has_active_key(tg_id):
await message.reply("У вас уже есть активный ключ. Вы не можете создать новый.")
await state.clear()
return
key_name = sanitize_key_name(message.text)
# Сохраняем информацию о запросе в контексте состояния
await state.update_data(key_name=key_name, tg_id=tg_id)
if not key_name:
await message.bot.send_message(tg_id, "Назовите профиль на английском языке.")
return # Прерываем выполнение функции
# Отправляем запрос на подтверждение админу
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)
data = await state.get_data()
creating_new_key = data.get('creating_new_key', False)
await state.set_state(Form.waiting_for_admin_confirmation)
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
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)
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.bot.send_message(tg_id, "Недостаточно средств на балансе для создания нового ключа.", reply_markup=keyboard)
await state.clear()
return
await update_balance(tg_id, -100)
try:
client_id = str(uuid.uuid4())
email = key_name
limit_ip = 1
total_gb = 0
current_time = datetime.utcnow()
expiry_time = int((current_time + timedelta(days=30)).timestamp() * 1000)
enable = True
flow = "xtls-rprx-vision"
balance = 0
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 bot.send_message(tg_id, f"Ключ создан:\n<pre>{connection_link}</pre>", parse_mode="HTML")
await callback_query.message.reply("Ключ создан и отправлен клиенту.")
# Проверка существующей записи
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 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=[
[InlineKeyboardButton(text='Инструкции по использованию', callback_data='instructions')],
[InlineKeyboardButton(text='Перейти в профиль', callback_data='view_profile')]
])
key_message = f"Ключ создан:\n<pre>{connection_link}</pre>"
await message.bot.send_message(tg_id, key_message, parse_mode="HTML", reply_markup=keyboard)
except Exception as e:
await callback_query.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 = (
"*Инструкции по использованию вашего ключа:*\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"
"Если у вас возникнут вопросы, не стесняйтесь обращаться в поддержку."
)
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.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 == 'back_to_main')
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()
conn = await asyncpg.connect(DATABASE_URL)
try:
active_keys = await conn.fetch('SELECT tg_id FROM connections WHERE trial > 0')
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)
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:
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)
View File