@@ -5,3 +5,4 @@
|
||||
/database.db
|
||||
/bot_old.py
|
||||
/bot_old_2.py
|
||||
/database.db
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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.
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
@@ -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
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user