Merge pull request #4 from Vladless/new

рабочий вариант выдачи ключей через подтверждение
This commit is contained in:
Vladislav Lisitsyn
2024-09-17 00:40:01 +03:00
committed by GitHub
3 changed files with 138 additions and 145 deletions
+2 -2
View File
@@ -26,7 +26,7 @@ def get_clients(session):
else:
raise Exception(f"Ошибка при получении клиентов: {response.status_code}, {response.text}")
def link(session, client_id: str):
def link(session, client_id: str, email: str):
"""
Получение ссылки для подключения по ID клиента.
:param session: requests.Session - авторизованная сессия
@@ -48,6 +48,6 @@ def link(session, client_id: str):
flow = stream_settings.get('flow', 'xtls-rprx-vision')
# Создание ссылки для подключения VLESS
val = f"vless://{client_id}@solonet.pocomacho.ru:443?type={tcp}&security={reality}&fp=chrome&pbk=Ik3cfHdM5RXYCuzTxhaFfo58Fzwjvw11AtKsz5-izxA&fp=chrome&sni=discord.com&sid=6fc9&spx=%2F&flow={flow}#VPN_F-{client_id}"
val = f"vless://{client_id}@solonet.pocomacho.ru:443?type={tcp}&security={reality}&pbk=Ik3cfHdM5RXYCuzTxhaFfo58Fzwjvw11AtKsz5-izxA&fp=chrome&sni=discord.com&sid=6fc9&spx=%2F&flow={flow}#SoloNet_DE-{email}"
return val
+92 -118
View File
@@ -1,21 +1,23 @@
from aiogram import Bot, Dispatcher, Router, F, types
from aiogram.filters import Command
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, Message
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, CallbackQuery, 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, get_clients
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
from database import add_connection, has_active_key, get_active_key_email, get_key_expiry_time
from database import add_connection, has_active_key, get_active_key_email, DATABASE_PATH
import uuid
import aiosqlite
import re
class Form(StatesGroup):
waiting_for_key_name = State()
waiting_for_admin_confirmation = State()
key_to_create = State() # Состояние для хранения ключа, который будет создан
waiting_for_statistics = State()
waiting_for_expiry_date = State()
bot = Bot(token=API_TOKEN)
storage = MemoryStorage()
@@ -36,27 +38,10 @@ async def start_command(message: Message):
await message.reply(welcome_text, reply_markup=keyboard)
@router.callback_query(F.data == "view_expiry")
async def process_callback_view_expiry_date(callback_query: types.CallbackQuery):
tg_id = callback_query.from_user.id
# Получаем дату окончания ключа из базы данных
expiry_time = await get_key_expiry_time(tg_id)
if expiry_time:
expiry_date = expiry_time.strftime('%Y-%m-%d %H:%M:%S')
response_message = f"Дата окончания вашего ключа: {expiry_date}"
else:
response_message = "У вас нет активного ключа."
await callback_query.message.reply(response_message)
await callback_query.answer()
@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 state.update_data(tg_id=callback_query.from_user.id) # Сохраняем tg_id для дальнейшего использования
await callback_query.answer()
@dp.message()
@@ -65,61 +50,47 @@ async def handle_text(message: types.Message, state: FSMContext):
if current_state == Form.waiting_for_key_name.state:
tg_id = message.from_user.id
key_name = message.text
if not key_name:
await message.reply("Имя клиента не указано.")
await state.clear()
return
if await has_active_key(tg_id):
await message.reply("У вас уже есть активный ключ. Вы не можете создать новый.")
await state.clear()
return
# Сохраняем имя ключа и tg_id для подтверждения
await state.set_state(Form.waiting_for_admin_confirmation)
await state.update_data(key_name=message.text, tg_id=tg_id)
# Сохраняем информацию о запросе в контексте состояния
await state.update_data(key_name=key_name, tg_id=tg_id)
# Отправляем запрос на подтверждение админу
admin_message = f"Запрос на создание ключа от пользователя {message.from_user.username}. Имя ключа: {message.text}. Подтвердите создание ключа."
button_yes = InlineKeyboardButton(text='Подтвердить', callback_data='confirm_key')
button_no = InlineKeyboardButton(text='Отклонить', callback_data='reject_key')
keyboard = InlineKeyboardMarkup(inline_keyboard=[[button_yes, button_no]])
admin_message = (
f"Новый запрос на создание ключа:\n"
f"Имя клиента: {key_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)
await message.reply("Ваш запрос на создание ключа отправлен администратору. Ожидайте подтверждения.")
await state.clear()
@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:
tg_id = message.from_user.id
if await has_active_key(tg_id):
await message.reply("У вас уже есть активный ключ. Вы не можете создать новый.")
await state.clear()
return
await state.set_state(Form.waiting_for_admin_confirmation)
await state.update_data(key_name=message.text, tg_id=tg_id) # Сохраняем имя ключа и tg_id для подтверждения
# Отправляем запрос на подтверждение админу
admin_message = f"Запрос на создание ключа от пользователя {message.from_user.username}. Имя ключа: {message.text}. Подтвердите создание ключа."
button_yes = InlineKeyboardButton(text='Подтвердить', callback_data='confirm_key')
button_no = InlineKeyboardButton(text='Отклонить', callback_data='reject_key')
keyboard = InlineKeyboardMarkup(inline_keyboard=[[button_yes, button_no]])
await bot.send_message(476217106, admin_message, reply_markup=keyboard)
await message.reply("Ваш запрос на создание ключа отправлен администратору. Ожидайте подтверждения.")
await message.reply("Ожидайте подтверждения администратора.")
await state.clear()
@dp.callback_query(F.data == 'confirm_key')
async def process_admin_confirmation(callback_query: types.CallbackQuery, state: FSMContext):
user_data = await state.get_data()
key_name = user_data.get('key_name')
tg_id = user_data.get('tg_id')
if not key_name or not tg_id:
await callback_query.answer("Ошибка: не удалось получить данные для создания ключа.")
@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
data = callback_query.data.split('_', 3)
tg_id = int(data[2])
key_name = data[3]
try:
# Генерация уникального client_id и создание ключа
client_id = str(uuid.uuid4())
email = key_name
limit_ip = 1
@@ -131,72 +102,75 @@ async def process_admin_confirmation(callback_query: types.CallbackQuery, state:
add_client(session, client_id, email, tg_id, limit_ip, total_gb, expiry_time, enable, flow)
# Генерация ссылки для подключения
connection_link = link(session, email)
connection_link = link(session, client_id, email)
# Сохранение данных о подключении в базе данных
await add_connection(tg_id, client_id, email, expiry_time)
# Отправка ключа клиенту
await bot.send_message(tg_id, f"Ключ создан:\n<pre>{connection_link}</pre>", parse_mode="HTML")
await bot.send_message(callback_query.from_user.id, "Ключ успешно создан и отправлен клиенту.")
await callback_query.answer("Ключ создан и отправлен клиенту.")
await callback_query.message.reply("Ключ создан и отправлен клиенту.")
except Exception as e:
await bot.send_message(callback_query.from_user.id, f"Ошибка: {e}")
await state.clear()
await callback_query.message.reply(f"Ошибка при создании ключа: {e}")
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("Запрос отклонен.")
await callback_query.answer()
@dp.callback_query(F.data == 'view_stats')
async def process_callback_view_stats(callback_query: types.CallbackQuery):
tg_id = callback_query.from_user.id
# Проверяем, есть ли активный ключ для этого пользователя
email = await get_active_key_email(tg_id)
if not email:
await callback_query.message.reply("У вас нет активного ключа для просмотра статистики.")
return
# Получаем статистику через API сервера
try:
clients = get_clients(session) # Предположим, что get_clients возвращает список клиентов с их данными
client_data = next((client for client in clients if client['email'] == email), None)
if not client_data:
await callback_query.message.reply("Не удалось получить статистику для этого пользователя.")
return
# Подготавливаем статистику для отображения
used_gb = client_data.get('usedGB', 0) # Использованные данные
limit_gb = client_data.get('totalGB', 0) # Лимит данных
expiry_time = client_data.get('expiryTime', 0) # Время окончания подписки
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000).strftime('%Y-%m-%d %H:%M:%S')
stats_message = (f"Статистика использования:\n"
f"Использовано: {used_gb} ГБ\n"
f"Лимит: {limit_gb} ГБ\n"
f"Дата окончания ключа: {expiry_date}")
await callback_query.message.reply(stats_message)
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:
await callback_query.message.reply(f"Ошибка при получении статистики: {e}")
statistics = f"Ошибка при получении статистики: {e}"
await callback_query.message.reply(f"Ваша статистика:\n{statistics}")
await callback_query.answer()
@dp.callback_query(F.data == 'reject_key')
async def process_admin_rejection(callback_query: types.CallbackQuery, state: FSMContext):
await bot.send_message(callback_query.from_user.id, "Создание ключа отклонено.")
@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()
await state.clear()
async def main():
dp.include_router(router) # Подключение роутера
await dp.start_polling(bot) # Запуск поллинга
if __name__ == '__main__':
import asyncio
asyncio.run(main())
+44 -25
View File
@@ -1,41 +1,60 @@
import requests
import json
from config import ADMIN_USERNAME, ADMIN_PASSWORD, GET_INBOUNDS_URL
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
}
]
import requests
from config import ADMIN_USERNAME, ADMIN_PASSWORD
import requests
from config import ADMIN_USERNAME, ADMIN_PASSWORD
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,
"email": email,
"limitIp": limit_ip,
"totalGB": total_gb,
"expiryTime": expiry_time,
"enable": enable,
"tgId": tg_id,
"subId": "",
"flow": flow,
}
# Преобразуем данные клиента в строку JSON
settings = json.dumps({"clients": [client_data]})
# Формируем тело запроса
data = {
"id": 1, # Можно изменить в зависимости от ваших требований
"settings": json.dumps(settings)
"id": 1, # Если id динамически изменяется, замените это значение
"settings": settings
}
headers = {
"Accept": "application/json"
'Content-Type': 'application/json', # Если требуется токен авторизации
}
response = session.post(ADD_CLIENT_URL, headers=headers, json=data)
response = session.post(url, json=data, headers=headers)
# Выводим информацию о запросе и ответе
print(f"Запрос на добавление клиента: {data}")
print(f"Статус ответа: {response.status_code}")
print(f"Ответ от сервера: {response.text}")
if response.status_code == 200:
return response.json()
print(f"Клиент добавлен: email={email}")
else:
raise Exception(f"Ошибка при добавлении клиента: {response.status_code}, {response.text}")
print(f"Ошибка при добавлении клиента: {response.status_code}, {response.text}")
def generate_client_id():
return str(uuid.uuid4())