commit c9f3aa4a20f92cf139f73edd6bfab273ff1b9354 Author: Vlad Date: Fri Sep 13 02:13:24 2024 +0300 Начальный этап бота с выдачей ключей diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..c9dff014 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/venv +/__pycache__ +/vpn_users.db +/config.py diff --git a/__init__.py b/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/auth.py b/auth.py new file mode 100644 index 00000000..607c0445 --- /dev/null +++ b/auth.py @@ -0,0 +1,65 @@ + +import requests +from config import GET_INBOUNDS_URL, AUTH_URL, DOMEN +import json + +def login_with_credentials(username, password): + auth_url = AUTH_URL + data = { + "username": username, + "password": password + } + response = requests.post(auth_url, json=data) + if response.status_code == 200: + session = requests.Session() + session.cookies.update(response.cookies) + return session + else: + raise Exception(f"Ошибка авторизации: {response.status_code}, {response.text}") + +def get_clients(session): + response = session.get(GET_INBOUNDS_URL) + if response.status_code == 200: + return response.json() # Возвращает данные по инбаундам и клиентам + else: + raise Exception(f"Ошибка при получении клиентов: {response.status_code}, {response.text}") + +def link(session, user_id: str): + """ + Получение ссылки для подключения по ID пользователя. + :param session: requests.Session - авторизованная сессия + :param user_id: str - идентификатор пользователя (email или другой параметр) + :return: str - ссылка для подключения + """ + client_id = '' + + # Получаем список клиентов через API + response = get_clients(session) + + if 'obj' not in response or len(response['obj']) == 0: + raise Exception("Не удалось получить данные клиентов.") + + # Извлекаем данные клиента + inbounds = response['obj'][0] # Убедитесь, что это правильный индекс + settings = json.loads(inbounds['settings']) + + # Ищем клиента по user_id + for client in settings["clients"]: + if client['email'] == user_id: + client_id = client["id"] + break + + if not client_id: + raise Exception(f"Клиент с идентификатором {user_id} не найден.") + + # Получаем streamSettings и генерируем ссылку + stream_settings = json.loads(inbounds['streamSettings']) + tcp = stream_settings.get('network', 'tcp') # Предположим, по умолчанию 'tcp' + reality = stream_settings.get('security', 'reality') # Предположим, по умолчанию 'reality' + flow = stream_settings.get('flow', 'xtls-rprx-vision') # Предположим, по умолчанию 'xtls-rprx-vision' + email = user_id + + # Создание ссылки для подключения 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 diff --git a/bot.py b/bot.py new file mode 100644 index 00000000..6e115079 --- /dev/null +++ b/bot.py @@ -0,0 +1,63 @@ +from aiogram import Bot, Dispatcher, Router, F, types +from aiogram.filters import Command +from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, Message +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 datetime import datetime, timedelta +from config import API_TOKEN, ADMIN_PASSWORD, ADMIN_USERNAME + + + +class Form(StatesGroup): + waiting_for_key_name = State() + +bot = Bot(token=API_TOKEN) +storage = MemoryStorage() +dp = Dispatcher(bot=bot, storage=storage) +router = Router() + +@dp.message(Command("start")) +async def start_command(message: Message): + button_create_key = InlineKeyboardButton(text='Создать ключ', callback_data='create_key') + keyboard = InlineKeyboardMarkup(inline_keyboard=[[button_create_key]]) + + await message.reply("Выберите действие:", 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.message() +async def handle_text(message: types.Message, state: FSMContext): + current_state = await state.get_state() + + if current_state == Form.waiting_for_key_name.state: + try: + session = login_with_credentials(ADMIN_USERNAME, ADMIN_PASSWORD) + + tg_id = message.from_user.id + + email = message.text + client_id = generate_client_id() + 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" + + add_client(session, client_id, email, tg_id, limit_ip, total_gb, expiry_time, enable, flow) + connection_link = link(session, email) + + await message.reply(f"Ключ создан:\n
{connection_link}
", parse_mode="HTML") + + await state.clear() + except Exception as e: + await message.reply(f"Произошла ошибка: {e}") diff --git a/bot_old.py b/bot_old.py new file mode 100644 index 00000000..32265eae --- /dev/null +++ b/bot_old.py @@ -0,0 +1,140 @@ +from aiogram import Bot, Dispatcher, Router, F, types +from aiogram.filters import Command +from aiogram.types import Message +from aiogram.filters import Command +import requests +from database import add_user, get_user, update_subscription +from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup +from aiogram.fsm.storage.memory import MemoryStorage +from aiogram.fsm.context import FSMContext +from aiogram.fsm.state import State, StatesGroup + +import asyncio +import json +from datetime import datetime, timedelta +import uuid + + +API_TOKEN = '7436953421:AAH9iUg86cLC8oxO_XZeqYEfSBhDGBodV5o' +API_URL = "https://vpn.pocomacho.ru:34268/solonet" +ADMIN_USERNAME = "paneladmin" +ADMIN_PASSWORD = "L7CMDASN9x7g" +ADD_CLIENT_URL = f"{API_URL}/panel/api/inbounds/addClient" + +bot = Bot(token=API_TOKEN) +storage = MemoryStorage() +dp = Dispatcher(storage=storage) +router = Router() + +class Form(StatesGroup): + waiting_for_key_name = State() + +def login_with_credentials(username, password): + auth_url = "https://vpn.pocomacho.ru:34268/solonet/login/" + data = { + "username": ADMIN_USERNAME, + "password": ADMIN_PASSWORD + } + response = requests.post(auth_url, json=data) + if response.status_code == 200: + # Если API возвращает токен, используйте его в заголовке + # return {"Authorization": f"Bearer {response.json().get('token')}"} + + # Если API использует сессии (например, с cookies) + session = requests.Session() + session.cookies.update(response.cookies) + return session + + else: + raise Exception(f"Ошибка авторизации: {response.status_code}, {response.text}") + +def add_client(session, client_id, email, tg_id, limit_ip, total_gb, expiry_time, enable, flow): + settings = { + "clients": [ + { + "id": client_id, + "alterId": 0, + "email": email, + "limitIp": limit_ip, + "totalGB": total_gb, + "expiryTime": expiry_time, + "enable": enable, + "tgId": tg_id, + "subId": "", + "flow": flow + } + ] + } + + data = { + "id": 1, # Можно изменить в зависимости от ваших требований + "settings": json.dumps(settings) + } + + headers = { + "Accept": "application/json" + } + + response = session.post(ADD_CLIENT_URL, headers=headers, json=data) + + if response.status_code == 200: + return response.json() + else: + raise Exception(f"Ошибка при добавлении клиента: {response.status_code}, {response.text}") + +# Обработчик команды /start +@dp.message(Command("start")) +async def start_command(message: Message): + # Создаем кнопки + button_create_key = InlineKeyboardButton(text='Создать ключ', callback_data='create_key') + keyboard = InlineKeyboardMarkup(inline_keyboard=[[button_create_key]]) + + await message.reply("Выберите действие:", 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("Введите имя для нового ключа:") + await state.set_state(Form.waiting_for_key_name) # Устанавливаем состояние + + await callback_query.answer() + +# Обработчик текстовых сообщений +@dp.message() +async def handle_text(message: types.Message, state: FSMContext): + # Получаем текущее состояние + current_state = await state.get_state() + + if current_state == Form.waiting_for_key_name.state: + try: + # Авторизация + session = login_with_credentials(ADMIN_USERNAME, ADMIN_PASSWORD) + + # Параметры клиента + client_id = str(uuid.uuid4()) # Пример UUID + email = message.text + limit_ip = 1 + tg_id = message.from_user.id + 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) + + await message.reply(f"Ключ создан: {result}") + + # Сброс состояния + await state.clear() + except Exception as e: + await message.reply(f"Произошла ошибка: {e}") + +# Запуск бота +async def main(): + dp.include_router(router) # Подключение роутера + await dp.start_polling(bot) # Запуск поллинга + +if __name__ == '__main__': + asyncio.run(main()) diff --git a/client.py b/client.py new file mode 100644 index 00000000..d0eec05f --- /dev/null +++ b/client.py @@ -0,0 +1,41 @@ + +import json +import uuid +from config import ADD_CLIENT_URL + +def add_client(session, client_id, email, tg_id, limit_ip, total_gb, expiry_time, enable, flow): + settings = { + "clients": [ + { + "id": client_id, + "alterId": 0, + "email": email, + "limitIp": limit_ip, + "totalGB": total_gb, + "expiryTime": expiry_time, + "enable": enable, + "tgId": tg_id, + "subId": "", + "flow": flow + } + ] + } + + data = { + "id": 1, # Можно изменить в зависимости от ваших требований + "settings": json.dumps(settings) + } + + headers = { + "Accept": "application/json" + } + + response = session.post(ADD_CLIENT_URL, headers=headers, json=data) + + if response.status_code == 200: + return response.json() + else: + raise Exception(f"Ошибка при добавлении клиента: {response.status_code}, {response.text}") + +def generate_client_id(): + return str(uuid.uuid4()) diff --git a/database.py b/database.py new file mode 100644 index 00000000..2836e3eb --- /dev/null +++ b/database.py @@ -0,0 +1,38 @@ +from sqlalchemy import create_engine, Column, Integer, String, DateTime +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker +import datetime + +DATABASE_URL = 'sqlite:///vpn_users.db' + +engine = create_engine(DATABASE_URL) +Session = sessionmaker(bind=engine) +session = Session() +Base = declarative_base() + +class VPNUser(Base): + __tablename__ = 'vpn_users' + + id = Column(Integer, primary_key=True) + telegram_id = Column(Integer, unique=True) + username = Column(String) + subscription_end = Column(DateTime) + access_key = Column(String) + +Base.metadata.create_all(engine) + +def add_user(telegram_id, username, access_key): + user = VPNUser(telegram_id=telegram_id, username=username, + subscription_end=datetime.datetime.now() + datetime.timedelta(days=30), + access_key=access_key) + session.add(user) + session.commit() + +def get_user(telegram_id): + return session.query(VPNUser).filter(VPNUser.telegram_id == telegram_id).first() + +def update_subscription(telegram_id, days): + user = get_user(telegram_id) + if user: + user.subscription_end += datetime.timedelta(days=days) + session.commit() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..0789639b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,26 @@ +aiofiles==23.2.1 +aiogram==3.2.0 +aiohttp==3.9.5 +aiosignal==1.3.1 +annotated-types==0.7.0 +anyio==4.4.0 +attrs==24.2.0 +certifi==2024.8.30 +charset-normalizer==3.3.2 +frozenlist==1.4.1 +greenlet==3.1.0 +h11==0.14.0 +httpcore==1.0.5 +httpx==0.27.2 +idna==3.8 +magic-filter==1.0.12 +multidict==6.1.0 +py3xui==0.2.3 +pydantic==2.5.3 +pydantic_core==2.14.6 +requests==2.32.3 +sniffio==1.3.1 +SQLAlchemy==2.0.34 +typing_extensions==4.12.2 +urllib3==2.2.3 +yarl==1.11.1