diff --git a/.gitignore b/.gitignore index 42be1ec0..0b7bcc7b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ /vpn_users.db /config.py /database.db +/bot_old.py +/bot_old_2.py diff --git a/auth.py b/auth.py index 607c0445..daa5f98d 100644 --- a/auth.py +++ b/auth.py @@ -62,4 +62,15 @@ def link(session, user_id: str): # Создание ссылки для подключения VLESS val = f"vless://{client_id}@{DOMEN}?type={tcp}&security={reality}&fp=chrome&pbk=ZIMoEnEd8-qMJkReVU5JxbiEj8CCSrvpm_ckvJ-46TE&sni=yahoo.com&sid=0fb4b595&spx=%2F&flow={flow}#VPN_F-{email}" - return val \ No newline at end of file + return val + +def get_statistics(session, tg_id: int): + # Пример URL для получения статистики + STATS_URL = f"https://vpn.pocomacho.ru:34268/solonet/panel/api/inbounds/stats/{tg_id}" + response = session.get(STATS_URL) + if response.status_code == 200: + stats = response.json() + # Форматирование статистики для отображения + return f"Загрузка: {stats['upload']} MB\nВывод: {stats['download']} MB" + else: + raise Exception(f"Ошибка при получении статистики: {response.status_code}, {response.text}") diff --git a/bot.py b/bot.py index 808c1df8..808e1e51 100644 --- a/bot.py +++ b/bot.py @@ -5,35 +5,94 @@ from aiogram.fsm.storage.memory import MemoryStorage from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup from auth import login_with_credentials, link -from client import add_client, generate_client_id +from client import add_client from datetime import datetime, timedelta -from config import API_TOKEN, ADMIN_PASSWORD, ADMIN_USERNAME -from database import add_connection, DATABASE_PATH +from config import API_TOKEN, ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_PATH +from database import add_connection, has_active_key, get_active_key_email import uuid +import re import aiosqlite - - class Form(StatesGroup): waiting_for_key_name = State() + waiting_for_statistics = State() + waiting_for_expiry_date = State() bot = Bot(token=API_TOKEN) storage = MemoryStorage() dp = Dispatcher(bot=bot, storage=storage) router = Router() +# Создаем сессию при старте бота +session = login_with_credentials(ADMIN_USERNAME, ADMIN_PASSWORD) + @dp.message(Command("start")) async def start_command(message: Message): + welcome_text = "Добро пожаловать! Вы можете создать ключ для подключения VPN, просмотреть статистику использования или узнать дату окончания ключа." + button_create_key = InlineKeyboardButton(text='Создать ключ', callback_data='create_key') - keyboard = InlineKeyboardMarkup(inline_keyboard=[[button_create_key]]) + button_view_stats = InlineKeyboardButton(text='Посмотреть статистику', callback_data='view_stats') + button_view_expiry = InlineKeyboardButton(text='Дата окончания ключа', callback_data='view_expiry') + keyboard = InlineKeyboardMarkup(inline_keyboard=[[button_create_key], [button_view_stats], [button_view_expiry]]) - await message.reply("Выберите действие:", reply_markup=keyboard) + await message.reply(welcome_text, reply_markup=keyboard) @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) + await callback_query.answer() +@dp.callback_query(F.data == 'view_stats') +async def process_callback_view_stats(callback_query: types.CallbackQuery, state: FSMContext): + tg_id = callback_query.from_user.id + + try: + email = await get_active_key_email(tg_id) + if email: + connection_link = link(session, 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 = "У вас нет активных ключей." + + except Exception as e: + statistics = f"Ошибка при получении статистики: {e}" + + await callback_query.message.reply(f"Ваша статистика:\n{statistics}") + await callback_query.answer() + +@dp.callback_query(F.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() @dp.message() @@ -42,45 +101,43 @@ async def handle_text(message: types.Message, state: FSMContext): if current_state == Form.waiting_for_key_name.state: try: - session = login_with_credentials(ADMIN_USERNAME, ADMIN_PASSWORD) - - # Параметры клиента - email = message.text tg_id = message.from_user.id - - # Проверяем наличие активного ключа - async with aiosqlite.connect(DATABASE_PATH) as db: - async with db.execute(''' - SELECT * FROM connections - WHERE tg_id = ? AND expiry_time > ? - ''', (tg_id, int(datetime.utcnow().timestamp() * 1000))) as cursor: - existing_key = await cursor.fetchone() - if existing_key: - await message.reply("У вас уже есть активный ключ. Вы не можете создать больше одного ключа.") - else: - # Генерация нового ключа - client_id = str(uuid.uuid4()) - 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" + # Проверка на наличие активного ключа + if await has_active_key(tg_id): + await message.reply("У вас уже есть активный ключ. Один клиент может иметь только один активный ключ.") + return + + # Создание уникального ID клиента + client_id = str(uuid.uuid4()) + email = message.text + 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" - # Добавление клиента (функция для создания клиента) - result = 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, total_gb, expiry_time, enable, flow) - # Сохранение данных в базу данных - await add_connection(tg_id, client_id, email, expiry_time) + # Получение ссылки на подключение + connection_link = link(session, email) - # Получение ссылки на подключение - connection_link = link(session, email) + # Сохранение данных в базу данных + await add_connection(tg_id, client_id, email, expiry_time) - # Отправка ключа в виде цитаты - await message.reply(f"Ключ создан:\n
{connection_link}
", parse_mode="HTML") + # Отправка ключа в виде цитаты + await message.reply(f"Ключ создан:\n
{connection_link}
", parse_mode="HTML") # Сброс состояния await state.clear() except Exception as e: - await message.reply(f"Произошла ошибка: {e}") \ No newline at end of file + await message.reply(f"Ошибка: {e}") + +async def main(): + dp.include_router(router) # Подключение роутера + await dp.start_polling(bot) # Запуск поллинга + +if __name__ == '__main__': + import asyncio + asyncio.run(main()) diff --git a/database.py b/database.py index 3f02fd85..192bb1c8 100644 --- a/database.py +++ b/database.py @@ -1,7 +1,6 @@ import aiosqlite from datetime import datetime - -DATABASE_PATH = 'database.db' +from config import DATABASE_PATH async def init_db(): async with aiosqlite.connect(DATABASE_PATH) as db: @@ -18,10 +17,23 @@ async def init_db(): async def add_connection(tg_id: int, client_id: str, email: str, expiry_time: int): async with aiosqlite.connect(DATABASE_PATH) as db: - # Добавляем новый ключ await db.execute(''' INSERT INTO connections (tg_id, client_id, email, expiry_time) VALUES (?, ?, ?, ?) ''', (tg_id, client_id, email, expiry_time)) await db.commit() +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 + +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 + diff --git a/pic.png b/pic.png new file mode 100644 index 00000000..a6a548c5 Binary files /dev/null and b/pic.png differ