Update bot_manager.py
Этот коммит содержится в:
+206
-505
@@ -6,13 +6,7 @@ import aiofiles
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import json
|
import json
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
import pytz
|
|
||||||
import zipfile
|
|
||||||
import ipaddress
|
|
||||||
import humanize
|
|
||||||
import shutil
|
|
||||||
import uuid
|
import uuid
|
||||||
from aiogram import Bot, types
|
from aiogram import Bot, types
|
||||||
from aiogram.dispatcher import Dispatcher
|
from aiogram.dispatcher import Dispatcher
|
||||||
@@ -21,7 +15,6 @@ from aiogram.utils import executor
|
|||||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||||
from apscheduler.triggers.interval import IntervalTrigger
|
|
||||||
from yoomoney import Client, Quickpay
|
from yoomoney import Client, Quickpay
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
@@ -66,7 +59,7 @@ class AdminMessageDeletionMiddleware(BaseMiddleware):
|
|||||||
asyncio.create_task(delete_message_after_delay(message.chat.id, message.message_id))
|
asyncio.create_task(delete_message_after_delay(message.chat.id, message.message_id))
|
||||||
|
|
||||||
dp = Dispatcher(bot)
|
dp = Dispatcher(bot)
|
||||||
scheduler = AsyncIOScheduler(timezone=pytz.UTC)
|
scheduler = AsyncIOScheduler(timezone=pytz.utc)
|
||||||
scheduler.start()
|
scheduler.start()
|
||||||
dp.middleware.setup(AdminMessageDeletionMiddleware())
|
dp.middleware.setup(AdminMessageDeletionMiddleware())
|
||||||
|
|
||||||
@@ -80,13 +73,12 @@ def get_main_menu_markup(user_id):
|
|||||||
)
|
)
|
||||||
markup.add(
|
markup.add(
|
||||||
InlineKeyboardButton("🔑 Получить конфиг", callback_data="get_config"),
|
InlineKeyboardButton("🔑 Получить конфиг", callback_data="get_config"),
|
||||||
InlineKeyboardButton("ℹ️ Инструкция", callback_data="instructions")
|
InlineKeyboardButton("🎟️ Управление промокодами", callback_data="manage_promocodes")
|
||||||
)
|
)
|
||||||
markup.add(
|
markup.add(
|
||||||
InlineKeyboardButton("🎟️ Управление промокодами", callback_data="manage_promocodes"),
|
InlineKeyboardButton("⚙️ Настройки", callback_data="settings"),
|
||||||
InlineKeyboardButton("⚙️ Настройки", callback_data="settings")
|
InlineKeyboardButton("🏠 Домой", callback_data="home")
|
||||||
)
|
)
|
||||||
markup.add(InlineKeyboardButton("🏠 Домой", callback_data="home"))
|
|
||||||
elif user_id in moderators:
|
elif user_id in moderators:
|
||||||
markup.add(
|
markup.add(
|
||||||
InlineKeyboardButton("➕ Добавить пользователя", callback_data="add_user"),
|
InlineKeyboardButton("➕ Добавить пользователя", callback_data="add_user"),
|
||||||
@@ -94,35 +86,43 @@ def get_main_menu_markup(user_id):
|
|||||||
)
|
)
|
||||||
markup.add(
|
markup.add(
|
||||||
InlineKeyboardButton("🔑 Получить конфиг", callback_data="get_config"),
|
InlineKeyboardButton("🔑 Получить конфиг", callback_data="get_config"),
|
||||||
InlineKeyboardButton("ℹ️ Инструкция", callback_data="instructions")
|
InlineKeyboardButton("🏠 Домой", callback_data="home")
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
markup.add(
|
markup.add(
|
||||||
InlineKeyboardButton("💳 Купить ключ", callback_data="buy_key"),
|
InlineKeyboardButton("💳 Купить ключ", callback_data="buy_key"),
|
||||||
InlineKeyboardButton("🎟️ Использовать промокод", callback_data="use_promocode")
|
InlineKeyboardButton("🎟️ Получить ключ по промокоду", callback_data="use_promocode")
|
||||||
)
|
)
|
||||||
return markup
|
return markup
|
||||||
|
|
||||||
|
# Меню покупки ключа
|
||||||
|
def get_buy_key_menu(user_id):
|
||||||
|
markup = InlineKeyboardMarkup(row_width=2)
|
||||||
|
markup.add(
|
||||||
|
InlineKeyboardButton("📅 Выбрать период", callback_data="select_subscription_period"),
|
||||||
|
InlineKeyboardButton("🎟️ Ввести промокод", callback_data="enter_promocode_for_buy")
|
||||||
|
)
|
||||||
|
markup.add(InlineKeyboardButton("🏠 Домой", callback_data="home"))
|
||||||
|
discount = user_main_messages.get(user_id, {}).get('promocode_discount', 0)
|
||||||
|
if discount > 0:
|
||||||
|
markup.add(InlineKeyboardButton(f"Сбросить скидку ({discount}%)", callback_data="reset_promocode"))
|
||||||
|
return markup
|
||||||
|
|
||||||
# Меню настроек
|
# Меню настроек
|
||||||
def get_settings_menu():
|
def get_settings_menu():
|
||||||
markup = InlineKeyboardMarkup(row_width=2)
|
markup = InlineKeyboardMarkup(row_width=2)
|
||||||
markup.add(
|
markup.add(
|
||||||
InlineKeyboardButton("🔄 Проверить обновления", callback_data="check_updates"),
|
InlineKeyboardButton("💾 Создать бэкап", callback_data="create_backup"),
|
||||||
InlineKeyboardButton("🔄 Перезагрузить VPN", callback_data="restart_vpn")
|
InlineKeyboardButton("👥 Список админов", callback_data="list_admins")
|
||||||
)
|
)
|
||||||
markup.add(
|
markup.add(
|
||||||
InlineKeyboardButton("🗑️ Очистить старые ключи", callback_data="clear_old_keys"),
|
InlineKeyboardButton("👤 Добавить админа", callback_data="add_admin"),
|
||||||
InlineKeyboardButton("💾 Создать бэкап", callback_data="create_backup")
|
InlineKeyboardButton("💸 Настройки YooMoney", callback_data="yoomoney_settings")
|
||||||
)
|
)
|
||||||
markup.add(
|
markup.add(
|
||||||
InlineKeyboardButton("👥 Список админов", callback_data="list_admins"),
|
InlineKeyboardButton("💰 Настройки цен", callback_data="pricing_settings"),
|
||||||
InlineKeyboardButton("👤 Добавить админа", callback_data="add_admin")
|
InlineKeyboardButton("⬅️ Назад", callback_data="home")
|
||||||
)
|
)
|
||||||
markup.add(
|
|
||||||
InlineKeyboardButton("💸 Настройки YooMoney", callback_data="yoomoney_settings"),
|
|
||||||
InlineKeyboardButton("💰 Настройки цен", callback_data="pricing_settings")
|
|
||||||
)
|
|
||||||
markup.add(InlineKeyboardButton("⬅️ Назад", callback_data="home"))
|
|
||||||
return markup
|
return markup
|
||||||
|
|
||||||
# Меню настроек YooMoney
|
# Меню настроек YooMoney
|
||||||
@@ -159,65 +159,15 @@ def get_renewal_period_keyboard(username):
|
|||||||
("1 месяц", "1_month"),
|
("1 месяц", "1_month"),
|
||||||
("3 месяца", "3_months"),
|
("3 месяца", "3_months"),
|
||||||
("6 месяцев", "6_months"),
|
("6 месяцев", "6_months"),
|
||||||
("12 месяцев", "12_months")
|
("12 месяцев", "12_months"),
|
||||||
|
("Кастомная дата", "custom_date")
|
||||||
]
|
]
|
||||||
for period_name, period_key in periods:
|
for period_name, period_key in periods:
|
||||||
markup.add(InlineKeyboardButton(period_name, callback_data=f"renew_period_{username}_{period_key}"))
|
markup.add(InlineKeyboardButton(period_name, callback_data=f"renew_period_{username}_{period_key}"))
|
||||||
markup.add(InlineKeyboardButton("Отмена", callback_data="home"))
|
markup.add(InlineKeyboardButton("Отмена", callback_data="home"))
|
||||||
return markup
|
return markup
|
||||||
|
|
||||||
# Клавиатура для выбора даты очистки ключей
|
|
||||||
def get_clear_keys_date_keyboard():
|
|
||||||
markup = InlineKeyboardMarkup(row_width=2)
|
|
||||||
dates = [
|
|
||||||
("1 месяц назад", (datetime.now(pytz.UTC) - timedelta(days=30)).isoformat()),
|
|
||||||
("3 месяца назад", (datetime.now(pytz.UTC) - timedelta(days=90)).isoformat()),
|
|
||||||
("6 месяцев назад", (datetime.now(pytz.UTC) - timedelta(days=180)).isoformat()),
|
|
||||||
("1 год назад", (datetime.now(pytz.UTC) - timedelta(days=365)).isoformat())
|
|
||||||
]
|
|
||||||
for date_name, date_iso in dates:
|
|
||||||
markup.add(InlineKeyboardButton(date_name, callback_data=f"clear_keys_date_{date_iso}"))
|
|
||||||
markup.add(InlineKeyboardButton("Отмена", callback_data="home"))
|
|
||||||
return markup
|
|
||||||
|
|
||||||
user_main_messages = {}
|
user_main_messages = {}
|
||||||
isp_cache = {}
|
|
||||||
ISP_CACHE_FILE = 'files/isp_cache.json'
|
|
||||||
CACHE_TTL = 24 * 3600
|
|
||||||
|
|
||||||
def get_interface_name():
|
|
||||||
return os.path.basename(WG_CONFIG_FILE).split('.')[0]
|
|
||||||
|
|
||||||
async def load_isp_cache():
|
|
||||||
global isp_cache
|
|
||||||
if os.path.exists(ISP_CACHE_FILE):
|
|
||||||
async with aiofiles.open(ISP_CACHE_FILE, 'r') as f:
|
|
||||||
isp_cache = json.loads(await f.read())
|
|
||||||
|
|
||||||
async def save_isp_cache():
|
|
||||||
async with aiofiles.open(ISP_CACHE_FILE, 'w') as f:
|
|
||||||
await f.write(json.dumps(isp_cache))
|
|
||||||
|
|
||||||
async def get_isp_info(ip: str) -> str:
|
|
||||||
now = datetime.now(pytz.UTC).timestamp()
|
|
||||||
if ip in isp_cache and (now - isp_cache[ip]['timestamp']) < CACHE_TTL:
|
|
||||||
return isp_cache[ip]['isp']
|
|
||||||
|
|
||||||
try:
|
|
||||||
if ipaddress.ip_address(ip).is_private:
|
|
||||||
return "Private Range"
|
|
||||||
except:
|
|
||||||
return "Invalid IP"
|
|
||||||
|
|
||||||
async with aiohttp.ClientSession() as session:
|
|
||||||
async with session.get(f"http://ip-api.com/json/{ip}?fields=isp") as resp:
|
|
||||||
if resp.status == 200:
|
|
||||||
data = await resp.json()
|
|
||||||
isp = data.get('isp', 'Unknown ISP')
|
|
||||||
isp_cache[ip] = {'isp': isp, 'timestamp': now}
|
|
||||||
await save_isp_cache()
|
|
||||||
return isp
|
|
||||||
return "Unknown ISP"
|
|
||||||
|
|
||||||
async def delete_message_after_delay(chat_id: int, message_id: int, delay: int = 2):
|
async def delete_message_after_delay(chat_id: int, message_id: int, delay: int = 2):
|
||||||
await asyncio.sleep(delay)
|
await asyncio.sleep(delay)
|
||||||
@@ -226,40 +176,6 @@ async def delete_message_after_delay(chat_id: int, message_id: int, delay: int =
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def parse_relative_time(relative_str: str) -> datetime:
|
|
||||||
if not isinstance(relative_str, str) or not relative_str.strip():
|
|
||||||
logger.error(f"Некорректный relative_str: {relative_str}")
|
|
||||||
return datetime.now(pytz.UTC)
|
|
||||||
try:
|
|
||||||
relative_str = relative_str.lower().replace(' ago', '')
|
|
||||||
delta = 0
|
|
||||||
for part in relative_str.split(', '):
|
|
||||||
num, unit = part.split()
|
|
||||||
num = int(num)
|
|
||||||
if 'minute' in unit:
|
|
||||||
delta += num * 60
|
|
||||||
elif 'hour' in unit:
|
|
||||||
delta += num * 3600
|
|
||||||
elif 'day' in unit:
|
|
||||||
delta += num * 86400
|
|
||||||
elif 'week' in unit:
|
|
||||||
delta += num * 604800
|
|
||||||
elif 'month' in unit:
|
|
||||||
delta += num * 2592000
|
|
||||||
return datetime.now(pytz.UTC) - timedelta(seconds=delta)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Ошибка в parse_relative_time: {str(e)}")
|
|
||||||
return datetime.now(pytz.UTC)
|
|
||||||
|
|
||||||
def parse_transfer(transfer_str: str) -> tuple:
|
|
||||||
try:
|
|
||||||
incoming, outgoing = transfer_str.split('/')
|
|
||||||
incoming_bytes = humanize.parse_bytes(incoming.strip())
|
|
||||||
outgoing_bytes = humanize.parse_bytes(outgoing.strip())
|
|
||||||
return incoming_bytes, outgoing_bytes
|
|
||||||
except:
|
|
||||||
return 0, 0
|
|
||||||
|
|
||||||
async def generate_vpn_key(conf_path: str) -> str:
|
async def generate_vpn_key(conf_path: str) -> str:
|
||||||
process = await asyncio.create_subprocess_exec(
|
process = await asyncio.create_subprocess_exec(
|
||||||
'python3.11', '/root/amnezia-bot/awg/awg-decode.py', '--encode', conf_path,
|
'python3.11', '/root/amnezia-bot/awg/awg-decode.py', '--encode', conf_path,
|
||||||
@@ -276,8 +192,8 @@ async def issue_vpn_key(user_id: int, period: str) -> bool:
|
|||||||
username = f"user_{user_id}_{uuid.uuid4().hex[:8]}"
|
username = f"user_{user_id}_{uuid.uuid4().hex[:8]}"
|
||||||
success = db.root_add(username, ipv6=False)
|
success = db.root_add(username, ipv6=False)
|
||||||
if success:
|
if success:
|
||||||
months = {'1_month': 1, '3_months': 3, '6_months': 6, '12_months': 12}[period]
|
months = {'1_month': 1, '3_months': 3, '6_months': 6, '12_months': 12}.get(period, 1)
|
||||||
expiration = datetime.now(pytz.UTC) + timedelta(days=30 * months)
|
expiration = datetime.now(pytz.utc) + timedelta(days=30 * months)
|
||||||
db.set_user_expiration(username, expiration, "Неограниченно")
|
db.set_user_expiration(username, expiration, "Неограниченно")
|
||||||
db.set_user_telegram_id(username, user_id)
|
db.set_user_telegram_id(username, user_id)
|
||||||
conf_path = os.path.join('users', username, f'{username}.conf')
|
conf_path = os.path.join('users', username, f'{username}.conf')
|
||||||
@@ -325,7 +241,7 @@ async def add_admin_command(message: types.Message):
|
|||||||
|
|
||||||
@dp.message_handler()
|
@dp.message_handler()
|
||||||
async def handle_messages(message: types.Message):
|
async def handle_messages(message: types.Message):
|
||||||
global PRICING # Объявляем PRICING глобальной в начале функции
|
global PRICING
|
||||||
user_id = message.from_user.id
|
user_id = message.from_user.id
|
||||||
user_state = user_main_messages.get(user_id, {}).get('state')
|
user_state = user_main_messages.get(user_id, {}).get('state')
|
||||||
|
|
||||||
@@ -369,7 +285,6 @@ async def handle_messages(message: types.Message):
|
|||||||
promocode = message.text.strip()
|
promocode = message.text.strip()
|
||||||
promocode_data = db.apply_promocode(promocode)
|
promocode_data = db.apply_promocode(promocode)
|
||||||
if promocode_data:
|
if promocode_data:
|
||||||
discount = promocode_data.get('discount', 0)
|
|
||||||
subscription_period = promocode_data.get('subscription_period')
|
subscription_period = promocode_data.get('subscription_period')
|
||||||
if subscription_period:
|
if subscription_period:
|
||||||
success = await issue_vpn_key(user_id, subscription_period)
|
success = await issue_vpn_key(user_id, subscription_period)
|
||||||
@@ -378,11 +293,25 @@ async def handle_messages(message: types.Message):
|
|||||||
else:
|
else:
|
||||||
await message.reply("Ошибка при выдаче ключа. Обратитесь к администратору.")
|
await message.reply("Ошибка при выдаче ключа. Обратитесь к администратору.")
|
||||||
else:
|
else:
|
||||||
|
await message.reply("Промокод не предоставляет ключ.")
|
||||||
|
else:
|
||||||
|
await message.reply("Неверный или истёкший промокод.")
|
||||||
|
sent_message = await message.answer("Выберите действие:", reply_markup=get_main_menu_markup(user_id))
|
||||||
|
user_main_messages[user_id] = {
|
||||||
|
'chat_id': sent_message.chat.id,
|
||||||
|
'message_id': sent_message.message_id,
|
||||||
|
'state': None
|
||||||
|
}
|
||||||
|
elif user_state == 'waiting_for_promocode_for_buy':
|
||||||
|
promocode = message.text.strip()
|
||||||
|
promocode_data = db.apply_promocode(promocode)
|
||||||
|
if promocode_data:
|
||||||
|
discount = promocode_data.get('discount', 0)
|
||||||
user_main_messages[user_id]['promocode_discount'] = discount
|
user_main_messages[user_id]['promocode_discount'] = discount
|
||||||
await message.reply(f"Промокод активирован! Скидка: {discount}%")
|
await message.reply(f"Промокод активирован! Скидка: {discount}%")
|
||||||
else:
|
else:
|
||||||
await message.reply("Неверный или истёкший промокод.")
|
await message.reply("Неверный или истёкший промокод.")
|
||||||
sent_message = await message.answer("Выберите действие:", reply_markup=get_main_menu_markup(user_id))
|
sent_message = await message.answer("Выберите действие:", reply_markup=get_buy_key_menu(user_id))
|
||||||
user_main_messages[user_id] = {
|
user_main_messages[user_id] = {
|
||||||
'chat_id': sent_message.chat.id,
|
'chat_id': sent_message.chat.id,
|
||||||
'message_id': sent_message.message_id,
|
'message_id': sent_message.message_id,
|
||||||
@@ -397,20 +326,21 @@ async def handle_messages(message: types.Message):
|
|||||||
discount = float(discount)
|
discount = float(discount)
|
||||||
days_valid = int(days_valid)
|
days_valid = int(days_valid)
|
||||||
max_uses = int(max_uses) if max_uses.lower() != 'none' else None
|
max_uses = int(max_uses) if max_uses.lower() != 'none' else None
|
||||||
if subscription_period not in PRICING and subscription_period.lower() != 'none':
|
if subscription_period not in ['none', '1_month', '3_months', '6_months', '12_months']:
|
||||||
raise ValueError("Неверный период подписки")
|
raise ValueError("Неверный период подписки")
|
||||||
subscription_period = None if subscription_period.lower() == 'none' else subscription_period
|
subscription_period = None if subscription_period.lower() == 'none' else subscription_period
|
||||||
expires_at = datetime.now(pytz.UTC) + timedelta(days=days_valid) if days_valid > 0 else None
|
expires_at = datetime.now(pytz.utc) + timedelta(days=days_valid) if days_valid > 0 else None
|
||||||
if db.add_promocode(code, discount, expires_at, max_uses, subscription_period):
|
if db.add_promocode(code, discount, expires_at, max_uses, subscription_period):
|
||||||
await message.reply(
|
await message.reply(
|
||||||
f"Промокод {code} добавлен: скидка {discount}%, действует {days_valid} дней, "
|
f"Промокод {code} добавлен: скидка {discount}%, действует {days_valid} дней, "
|
||||||
f"макс. использований: {max_uses or 'неограничено'}, подписка: {subscription_period or 'нет'}"
|
f"макс. использований: {max_uses or 'неограничено'}, период подписки: {subscription_period or 'нет'}"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
await message.reply("Промокод уже существует.")
|
await message.reply("Промокод уже существует.")
|
||||||
except:
|
except:
|
||||||
await message.reply(
|
await message.reply(
|
||||||
"Формат: <код> <скидка%> <дней_действия> <макс_использований|none> <период_подписки|none>"
|
"Формат: <код> <скидка%> <дней_действия> <макс_использований|none> <период_подписки|none>\n"
|
||||||
|
"Пример: PROMO1 10 30 none 1_month"
|
||||||
)
|
)
|
||||||
sent_message = await message.answer("Выберите действие:", reply_markup=get_main_menu_markup(user_id))
|
sent_message = await message.answer("Выберите действие:", reply_markup=get_main_menu_markup(user_id))
|
||||||
user_main_messages[user_id] = {
|
user_main_messages[user_id] = {
|
||||||
@@ -464,6 +394,24 @@ async def handle_messages(message: types.Message):
|
|||||||
'message_id': sent_message.message_id,
|
'message_id': sent_message.message_id,
|
||||||
'state': None
|
'state': None
|
||||||
}
|
}
|
||||||
|
elif user_state.startswith('waiting_for_custom_date_') and user_id in admins:
|
||||||
|
username = user_state.split('waiting_for_custom_date_')[1]
|
||||||
|
try:
|
||||||
|
expiration = datetime.strptime(message.text.strip(), '%d-%m-%Y').replace(tzinfo=pytz.utc)
|
||||||
|
if expiration < datetime.now(pytz.utc):
|
||||||
|
await message.reply("Дата должна быть в будущем.")
|
||||||
|
return
|
||||||
|
db.set_user_expiration(username, expiration, "Неограниченно")
|
||||||
|
await message.reply(f"Подписка для {username} продлена до {expiration.strftime('%d-%m-%Y')}.")
|
||||||
|
except:
|
||||||
|
await message.reply("Введите дату в формате ДД-ММ-ГГГГ (например, 31-12-2025).")
|
||||||
|
return
|
||||||
|
sent_message = await message.answer("Выберите действие:", reply_markup=get_main_menu_markup(user_id))
|
||||||
|
user_main_messages[user_id] = {
|
||||||
|
'chat_id': sent_message.chat.id,
|
||||||
|
'message_id': sent_message.message_id,
|
||||||
|
'state': None
|
||||||
|
}
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == "settings")
|
@dp.callback_query_handler(lambda c: c.data == "settings")
|
||||||
async def settings_menu_callback(callback_query: types.CallbackQuery):
|
async def settings_menu_callback(callback_query: types.CallbackQuery):
|
||||||
@@ -616,57 +564,6 @@ async def set_price_callback(callback_query: types.CallbackQuery):
|
|||||||
}
|
}
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == "clear_old_keys")
|
|
||||||
async def clear_old_keys_callback(callback_query: types.CallbackQuery):
|
|
||||||
user_id = callback_query.from_user.id
|
|
||||||
if user_id not in admins:
|
|
||||||
await callback_query.answer("Нет прав.", show_alert=True)
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
await bot.delete_message(
|
|
||||||
chat_id=callback_query.message.chat.id,
|
|
||||||
message_id=callback_query.message.message_id
|
|
||||||
)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
sent_message = await bot.send_message(
|
|
||||||
chat_id=callback_query.message.chat.id,
|
|
||||||
text="Выберите дату, до которой удалить ключи:",
|
|
||||||
reply_markup=get_clear_keys_date_keyboard()
|
|
||||||
)
|
|
||||||
user_main_messages[user_id] = {
|
|
||||||
'chat_id': sent_message.chat.id,
|
|
||||||
'message_id': sent_message.message_id,
|
|
||||||
'state': None
|
|
||||||
}
|
|
||||||
await callback_query.answer()
|
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data.startswith('clear_keys_date_'))
|
|
||||||
async def clear_keys_date_callback(callback_query: types.CallbackQuery):
|
|
||||||
user_id = callback_query.from_user.id
|
|
||||||
if user_id not in admins:
|
|
||||||
await callback_query.answer("Нет прав.", show_alert=True)
|
|
||||||
return
|
|
||||||
before_date = callback_query.data.split('clear_keys_date_')[1]
|
|
||||||
try:
|
|
||||||
if db.clear_old_keys(before_date):
|
|
||||||
await bot.send_message(user_id, f"Старые ключи до {before_date} удалены.", parse_mode="Markdown")
|
|
||||||
else:
|
|
||||||
await bot.send_message(user_id, "Не найдено ключей для удаления.", parse_mode="Markdown")
|
|
||||||
except Exception as e:
|
|
||||||
await bot.send_message(user_id, f"Ошибка при удалении ключей: {str(e)}")
|
|
||||||
sent_message = await bot.send_message(
|
|
||||||
chat_id=callback_query.message.chat.id,
|
|
||||||
text="Выберите действие:",
|
|
||||||
reply_markup=get_main_menu_markup(user_id)
|
|
||||||
)
|
|
||||||
user_main_messages[user_id] = {
|
|
||||||
'chat_id': sent_message.chat.id,
|
|
||||||
'message_id': sent_message.message_id,
|
|
||||||
'state': None
|
|
||||||
}
|
|
||||||
await callback_query.answer()
|
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == "add_user")
|
@dp.callback_query_handler(lambda c: c.data == "add_user")
|
||||||
async def prompt_for_user_name(callback_query: types.CallbackQuery):
|
async def prompt_for_user_name(callback_query: types.CallbackQuery):
|
||||||
user_id = callback_query.from_user.id
|
user_id = callback_query.from_user.id
|
||||||
@@ -733,48 +630,25 @@ async def client_selected_callback(callback_query: types.CallbackQuery):
|
|||||||
return
|
return
|
||||||
|
|
||||||
status = "🔴 Офлайн"
|
status = "🔴 Офлайн"
|
||||||
incoming_traffic = "↓—"
|
|
||||||
outgoing_traffic = "↑—"
|
|
||||||
ipv4_address = "—"
|
|
||||||
expiration = db.get_user_expiration(username)
|
expiration = db.get_user_expiration(username)
|
||||||
expiration_text = expiration.strftime("%Y-%m-%d %H:%M UTC") if expiration else "Не установлен"
|
expiration_text = expiration.strftime("%Y-%m-%d %H:%M UTC") if expiration else "Не установлен"
|
||||||
telegram_id = db.get_user_telegram_id(username) or "Не указан"
|
|
||||||
|
|
||||||
if isinstance(client_info, (tuple, list)) and len(client_info) > 2 and client_info[2] is not None:
|
|
||||||
ip_match = re.search(r'(\d{1,3}\.){3}\d{1,3}/\d+', str(client_info[2]))
|
|
||||||
ipv4_address = ip_match.group(0) if ip_match else "—"
|
|
||||||
|
|
||||||
active_clients = db.get_active_list()
|
active_clients = db.get_active_list()
|
||||||
active_info = next((ac for ac in active_clients if ac[0] == username), None)
|
active_info = next((ac for ac in active_clients if ac[0] == username), None)
|
||||||
|
if active_info and active_info[1] and active_info[1].lower() not in ['never', 'нет данных', '-']:
|
||||||
if active_info and isinstance(active_info, (tuple, list)) and len(active_info) > 2:
|
|
||||||
if active_info[1] and active_info[1].lower() not in ['never', 'нет данных', '-']:
|
|
||||||
try:
|
try:
|
||||||
last_handshake = parse_relative_time(active_info[1])
|
last_handshake = datetime.strptime(active_info[1], "%Y-%m-%d %H:%M:%S")
|
||||||
status = "🟢 Онлайн" if (datetime.now(pytz.UTC) - last_handshake).total_seconds() <= 60 else "❌ Офлайн"
|
status = "🟢 Онлайн" if (datetime.now(pytz.utc) - last_handshake).total_seconds() <= 60 else "❌ Офлайн"
|
||||||
except:
|
|
||||||
pass
|
|
||||||
if active_info[2]:
|
|
||||||
try:
|
|
||||||
incoming_bytes, outgoing_bytes = parse_transfer(active_info[2])
|
|
||||||
incoming_traffic = f"↓{humanize.naturalsize(incoming_bytes)}"
|
|
||||||
outgoing_traffic = f"↑{humanize.naturalsize(outgoing_bytes)}"
|
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
text = (
|
text = (
|
||||||
f"📧 *Имя:* {username}\n"
|
f"📧 *Имя:* {username}\n"
|
||||||
f"👤 *Пользователь:* {telegram_id}\n"
|
|
||||||
f"🌐 *IPv4:* {ipv4_address}\n"
|
|
||||||
f"🌐 *Статус:* {status}\n"
|
f"🌐 *Статус:* {status}\n"
|
||||||
f"🔼 *Исходящий:* {incoming_traffic}\n"
|
|
||||||
f"🔽 *Входящий:* {outgoing_traffic}\n"
|
|
||||||
f"⏰ *Срок действия:* {expiration_text}"
|
f"⏰ *Срок действия:* {expiration_text}"
|
||||||
)
|
)
|
||||||
|
|
||||||
keyboard = InlineKeyboardMarkup(row_width=2).add(
|
keyboard = InlineKeyboardMarkup(row_width=2).add(
|
||||||
InlineKeyboardButton("ℹ️ IP info", callback_data=f"ip_info_{username}"),
|
|
||||||
InlineKeyboardButton("🔗 Подключения", callback_data=f"connections_{username}"),
|
|
||||||
InlineKeyboardButton("🗑️ Удалить", callback_data=f"delete_user_{username}"),
|
InlineKeyboardButton("🗑️ Удалить", callback_data=f"delete_user_{username}"),
|
||||||
InlineKeyboardButton("🔄 Продлить", callback_data=f"renew_user_{username}"),
|
InlineKeyboardButton("🔄 Продлить", callback_data=f"renew_user_{username}"),
|
||||||
InlineKeyboardButton("⬅️ Назад", callback_data="list_users"),
|
InlineKeyboardButton("⬅️ Назад", callback_data="list_users"),
|
||||||
@@ -940,90 +814,6 @@ async def remove_admin_callback(callback_query: types.CallbackQuery):
|
|||||||
await bot.send_message(admin_id, "Вы удалены из администраторов.")
|
await bot.send_message(admin_id, "Вы удалены из администраторов.")
|
||||||
await list_admins_callback(callback_query)
|
await list_admins_callback(callback_query)
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data.startswith('connections_'))
|
|
||||||
async def client_connections_callback(callback_query: types.CallbackQuery):
|
|
||||||
user_id = callback_query.from_user.id
|
|
||||||
if user_id not in admins and user_id not in moderators:
|
|
||||||
await callback_query.answer("Нет прав.", show_alert=True)
|
|
||||||
return
|
|
||||||
username = callback_query.data.split('connections_')[1]
|
|
||||||
file_path = os.path.join('files', 'connections', f'{username}_ip.json')
|
|
||||||
if not os.path.exists(file_path):
|
|
||||||
await callback_query.answer("Нет данных о подключениях.", show_alert=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
async with aiofiles.open(file_path, 'r') as f:
|
|
||||||
data = json.loads(await f.read())
|
|
||||||
last_connections = sorted(data.items(), key=lambda x: datetime.strptime(x[1], '%d.%m.%Y %H:%M'), reverse=True)[:5]
|
|
||||||
isp_results = await asyncio.gather(*(get_isp_info(ip) for ip, _ in last_connections))
|
|
||||||
|
|
||||||
text = f"*Последние подключения {username}:*\n" + "\n".join(f"{ip} ({isp}) - {time}" for (ip, time), isp in zip(last_connections, isp_results))
|
|
||||||
keyboard = InlineKeyboardMarkup(row_width=2).add(
|
|
||||||
InlineKeyboardButton("⬅️ Назад", callback_data=f"client_{username}"),
|
|
||||||
InlineKeyboardButton("🏠 Домой", callback_data="home")
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
await bot.delete_message(
|
|
||||||
chat_id=callback_query.message.chat.id,
|
|
||||||
message_id=callback_query.message.message_id
|
|
||||||
)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
sent_message = await bot.send_message(
|
|
||||||
chat_id=callback_query.message.chat.id,
|
|
||||||
text=text,
|
|
||||||
parse_mode="Markdown",
|
|
||||||
reply_markup=keyboard
|
|
||||||
)
|
|
||||||
user_main_messages[user_id] = {
|
|
||||||
'chat_id': sent_message.chat.id,
|
|
||||||
'message_id': sent_message.message_id,
|
|
||||||
'state': None
|
|
||||||
}
|
|
||||||
await callback_query.answer()
|
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data.startswith('ip_info_'))
|
|
||||||
async def ip_info_callback(callback_query: types.CallbackQuery):
|
|
||||||
user_id = callback_query.from_user.id
|
|
||||||
if user_id not in admins and user_id not in moderators:
|
|
||||||
await callback_query.answer("Нет прав.", show_alert=True)
|
|
||||||
return
|
|
||||||
username = callback_query.data.split('ip_info_')[1]
|
|
||||||
active_info = next((ac for ac in db.get_active_list() if ac[0] == username), None)
|
|
||||||
if not active_info:
|
|
||||||
await callback_query.answer("Нет данных о подключении.", show_alert=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
ip_address = active_info[3].split(':')[0]
|
|
||||||
async with aiohttp.ClientSession() as session:
|
|
||||||
async with session.get(f"http://ip-api.com/json/{ip_address}") as resp:
|
|
||||||
data = await resp.json() if resp.status == 200 else {}
|
|
||||||
|
|
||||||
text = f"*IP info {username}:*\n" + "\n".join(f"{k.capitalize()}: {v}" for k, v in data.items())
|
|
||||||
keyboard = InlineKeyboardMarkup(row_width=2).add(
|
|
||||||
InlineKeyboardButton("⬅️ Назад", callback_data=f"client_{username}"),
|
|
||||||
InlineKeyboardButton("🏠 Домой", callback_data="home")
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
await bot.delete_message(
|
|
||||||
chat_id=callback_query.message.chat.id,
|
|
||||||
message_id=callback_query.message.message_id
|
|
||||||
)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
sent_message = await bot.send_message(
|
|
||||||
chat_id=callback_query.message.chat.id,
|
|
||||||
text=text,
|
|
||||||
parse_mode="Markdown",
|
|
||||||
reply_markup=keyboard
|
|
||||||
)
|
|
||||||
user_main_messages[user_id] = {
|
|
||||||
'chat_id': sent_message.chat.id,
|
|
||||||
'message_id': sent_message.message_id,
|
|
||||||
'state': None
|
|
||||||
}
|
|
||||||
await callback_query.answer()
|
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data.startswith('delete_user_'))
|
@dp.callback_query_handler(lambda c: c.data.startswith('delete_user_'))
|
||||||
async def client_delete_callback(callback_query: types.CallbackQuery):
|
async def client_delete_callback(callback_query: types.CallbackQuery):
|
||||||
user_id = callback_query.from_user.id
|
user_id = callback_query.from_user.id
|
||||||
@@ -1080,7 +870,7 @@ async def renew_user_callback(callback_query: types.CallbackQuery):
|
|||||||
pass
|
pass
|
||||||
sent_message = await bot.send_message(
|
sent_message = await bot.send_message(
|
||||||
chat_id=callback_query.message.chat.id,
|
chat_id=callback_query.message.chat.id,
|
||||||
text="Выберите период продления:",
|
text="Выберите период продления или укажите дату:",
|
||||||
reply_markup=get_renewal_period_keyboard(username)
|
reply_markup=get_renewal_period_keyboard(username)
|
||||||
)
|
)
|
||||||
user_main_messages[user_id] = {
|
user_main_messages[user_id] = {
|
||||||
@@ -1097,15 +887,10 @@ async def renew_period_callback(callback_query: types.CallbackQuery):
|
|||||||
await callback_query.answer("Нет прав.", show_alert=True)
|
await callback_query.answer("Нет прав.", show_alert=True)
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
username, period = callback_query.data.split('renew_period_')[1].split('_', 1)
|
parts = callback_query.data.split('renew_period_')[1].split('_', 1)
|
||||||
months = {'1_month': 1, '3_months': 3, '6_months': 6, '12_months': 12}[period]
|
username = parts[0]
|
||||||
expiration = datetime.now(pytz.UTC) + timedelta(days=30 * months)
|
period = parts[1] if len(parts) > 1 else 'custom_date'
|
||||||
db.set_user_expiration(username, expiration, "Неограниченно")
|
if period == 'custom_date':
|
||||||
text = f"Подписка для {username} продлена до {expiration.strftime('%Y-%m-%d %H:%M UTC')}."
|
|
||||||
logger.info(f"Подписка для {username} продлена на {period} до {expiration}.")
|
|
||||||
except Exception as e:
|
|
||||||
text = f"Ошибка при продлении: {str(e)}"
|
|
||||||
logger.error(f"Ошибка при продлении подписки для {username}: {str(e)}")
|
|
||||||
try:
|
try:
|
||||||
await bot.delete_message(
|
await bot.delete_message(
|
||||||
chat_id=callback_query.message.chat.id,
|
chat_id=callback_query.message.chat.id,
|
||||||
@@ -1113,6 +898,44 @@ async def renew_period_callback(callback_query: types.CallbackQuery):
|
|||||||
)
|
)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
sent_message = await bot.send_message(
|
||||||
|
chat_id=callback_query.message.chat.id,
|
||||||
|
text="Введите дату продления в формате ДД-ММ-ГГГГ (например, 31-12-2025):",
|
||||||
|
reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("Отмена", callback_data="home"))
|
||||||
|
)
|
||||||
|
user_main_messages[user_id] = {
|
||||||
|
'chat_id': sent_message.chat.id,
|
||||||
|
'message_id': sent_message.message_id,
|
||||||
|
'state': f'waiting_for_custom_date_{username}'
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
months = {'1_month': 1, '3_months': 3, '6_months': 6, '12_months': 12}[period]
|
||||||
|
expiration = datetime.now(pytz.utc) + timedelta(days=30 * months)
|
||||||
|
db.set_user_expiration(username, expiration, "Неограниченно")
|
||||||
|
text = f"Подписка для {username} продлена до {expiration.strftime('%Y-%m-%d %H:%M UTC')}."
|
||||||
|
logger.info(f"Подписка для {username} продлена на {period} до {expiration}.")
|
||||||
|
try:
|
||||||
|
await bot.delete_message(
|
||||||
|
chat_id=callback_query.message.chat.id,
|
||||||
|
message_id=callback_query.message.message_id
|
||||||
|
)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
sent_message = await bot.send_message(
|
||||||
|
chat_id=callback_query.message.chat.id,
|
||||||
|
text=text,
|
||||||
|
parse_mode="Markdown",
|
||||||
|
reply_markup=get_main_menu_markup(user_id)
|
||||||
|
)
|
||||||
|
user_main_messages[user_id] = {
|
||||||
|
'chat_id': sent_message.chat.id,
|
||||||
|
'message_id': sent_message.message_id,
|
||||||
|
'state': None
|
||||||
|
}
|
||||||
|
await callback_query.answer()
|
||||||
|
except Exception as e:
|
||||||
|
text = f"Ошибка при продлении: {str(e)}"
|
||||||
|
logger.error(f"Ошибка при продлении подписки для {username}: {str(e)}")
|
||||||
sent_message = await bot.send_message(
|
sent_message = await bot.send_message(
|
||||||
chat_id=callback_query.message.chat.id,
|
chat_id=callback_query.message.chat.id,
|
||||||
text=text,
|
text=text,
|
||||||
@@ -1242,124 +1065,40 @@ async def create_backup_callback(callback_query: types.CallbackQuery):
|
|||||||
}
|
}
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == "instructions")
|
|
||||||
async def show_instructions(callback_query: types.CallbackQuery):
|
|
||||||
user_id = callback_query.from_user.id
|
|
||||||
if user_id not in admins and user_id not in moderators:
|
|
||||||
await callback_query.answer("Нет прав.", show_alert=True)
|
|
||||||
return
|
|
||||||
keyboard = InlineKeyboardMarkup(row_width=2).add(
|
|
||||||
InlineKeyboardButton("📱 Для мобильных", callback_data="mobile_instructions"),
|
|
||||||
InlineKeyboardButton("💻 Для компьютеров", callback_data="pc_instructions"),
|
|
||||||
InlineKeyboardButton("🏠 Домой", callback_data="home")
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
await bot.delete_message(
|
|
||||||
chat_id=callback_query.message.chat.id,
|
|
||||||
message_id=callback_query.message.message_id
|
|
||||||
)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
sent_message = await bot.send_message(
|
|
||||||
chat_id=callback_query.message.chat.id,
|
|
||||||
text="Выберите тип устройства для инструкции:",
|
|
||||||
reply_markup=keyboard
|
|
||||||
)
|
|
||||||
user_main_messages[user_id] = {
|
|
||||||
'chat_id': sent_message.chat.id,
|
|
||||||
'message_id': sent_message.message_id,
|
|
||||||
'state': None
|
|
||||||
}
|
|
||||||
await callback_query.answer()
|
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == "mobile_instructions")
|
|
||||||
async def mobile_instructions(callback_query: types.CallbackQuery):
|
|
||||||
user_id = callback_query.from_user.id
|
|
||||||
if user_id not in admins and user_id not in moderators:
|
|
||||||
await callback_query.answer("Нет прав.", show_alert=True)
|
|
||||||
return
|
|
||||||
instruction_text = (
|
|
||||||
"📱 *Инструкция для мобильных устройств:*\n\n"
|
|
||||||
"1. Скачайте приложение AmneziaVPN:\n"
|
|
||||||
" - [Google Play](https://play.google.com/store/apps/details?id=org.amnezia.vpn&hl=ru)\n"
|
|
||||||
" - Или через [GitHub](https://github.com/amnezia-vpn/amnezia-client)\n"
|
|
||||||
"2. Откройте приложение и выберите 'Добавить конфигурацию'.\n"
|
|
||||||
"3. Скопируйте VPN ключ из сообщения с файлом .conf.\n"
|
|
||||||
"4. Вставьте ключ в приложение и нажмите 'Подключить'.\n"
|
|
||||||
"5. Готово! Вы подключены к VPN."
|
|
||||||
)
|
|
||||||
keyboard = InlineKeyboardMarkup().add(
|
|
||||||
InlineKeyboardButton("⬅️ Назад", callback_data="instructions"),
|
|
||||||
InlineKeyboardButton("🏠 Домой", callback_data="home")
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
await bot.delete_message(
|
|
||||||
chat_id=callback_query.message.chat.id,
|
|
||||||
message_id=callback_query.message.message_id
|
|
||||||
)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
sent_message = await bot.send_message(
|
|
||||||
chat_id=callback_query.message.chat.id,
|
|
||||||
text=instruction_text,
|
|
||||||
parse_mode="Markdown",
|
|
||||||
reply_markup=keyboard
|
|
||||||
)
|
|
||||||
user_main_messages[user_id] = {
|
|
||||||
'chat_id': sent_message.chat.id,
|
|
||||||
'message_id': sent_message.message_id,
|
|
||||||
'state': None
|
|
||||||
}
|
|
||||||
await callback_query.answer()
|
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == "pc_instructions")
|
|
||||||
async def pc_instructions(callback_query: types.CallbackQuery):
|
|
||||||
user_id = callback_query.from_user.id
|
|
||||||
if user_id not in admins and user_id not in moderators:
|
|
||||||
await callback_query.answer("Нет прав.", show_alert=True)
|
|
||||||
return
|
|
||||||
instruction_text = (
|
|
||||||
"💻 *Инструкция для компьютеров:*\n\n"
|
|
||||||
"1. Скачайте клиент AmneziaVPN с [GitHub](https://github.com/amnezia-vpn/amnezia-client).\n"
|
|
||||||
"2. Установите программу на ваш компьютер.\n"
|
|
||||||
"3. Откройте AmneziaVPN и выберите 'Импорт конфигурации'.\n"
|
|
||||||
"4. Укажите путь к скачанному файлу .conf.\n"
|
|
||||||
"5. Нажмите 'Подключить' для активации VPN.\n"
|
|
||||||
"6. Готово! VPN активен."
|
|
||||||
)
|
|
||||||
keyboard = InlineKeyboardMarkup().add(
|
|
||||||
InlineKeyboardButton("⬅️ Назад", callback_data="instructions"),
|
|
||||||
InlineKeyboardButton("🏠 Домой", callback_data="home")
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
await bot.delete_message(
|
|
||||||
chat_id=callback_query.message.chat.id,
|
|
||||||
message_id=callback_query.message.message_id
|
|
||||||
)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
sent_message = await bot.send_message(
|
|
||||||
chat_id=callback_query.message.chat.id,
|
|
||||||
text=instruction_text,
|
|
||||||
parse_mode="Markdown",
|
|
||||||
reply_markup=keyboard
|
|
||||||
)
|
|
||||||
user_main_messages[user_id] = {
|
|
||||||
'chat_id': sent_message.chat.id,
|
|
||||||
'message_id': sent_message.message_id,
|
|
||||||
'state': None
|
|
||||||
}
|
|
||||||
await callback_query.answer()
|
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == "buy_key")
|
@dp.callback_query_handler(lambda c: c.data == "buy_key")
|
||||||
async def buy_key_callback(callback_query: types.CallbackQuery):
|
async def buy_key_callback(callback_query: types.CallbackQuery):
|
||||||
|
user_id = callback_query.from_user.id
|
||||||
|
try:
|
||||||
|
await bot.delete_message(
|
||||||
|
chat_id=callback_query.message.chat.id,
|
||||||
|
message_id=callback_query.message.message_id
|
||||||
|
)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
sent_message = await bot.send_message(
|
||||||
|
chat_id=callback_query.message.chat.id,
|
||||||
|
text="Меню покупки ключа:",
|
||||||
|
reply_markup=get_buy_key_menu(user_id)
|
||||||
|
)
|
||||||
|
user_main_messages[user_id] = {
|
||||||
|
'chat_id': sent_message.chat.id,
|
||||||
|
'message_id': sent_message.message_id,
|
||||||
|
'state': None
|
||||||
|
}
|
||||||
|
await callback_query.answer()
|
||||||
|
|
||||||
|
@dp.callback_query_handler(lambda c: c.data == "select_subscription_period")
|
||||||
|
async def select_period_callback(callback_query: types.CallbackQuery):
|
||||||
user_id = callback_query.from_user.id
|
user_id = callback_query.from_user.id
|
||||||
keyboard = InlineKeyboardMarkup(row_width=2)
|
keyboard = InlineKeyboardMarkup(row_width=2)
|
||||||
|
discount = user_main_messages.get(user_id, {}).get('promocode_discount', 0)
|
||||||
for period, price in PRICING.items():
|
for period, price in PRICING.items():
|
||||||
|
final_price = price * (1 - discount / 100)
|
||||||
keyboard.add(InlineKeyboardButton(
|
keyboard.add(InlineKeyboardButton(
|
||||||
f"{period.replace('_', ' ')} - ₽{price:.2f}",
|
f"{period.replace('_', ' ')} - ₽{final_price:.2f}{' (-' + str(discount) + '%)' if discount > 0 else ''}",
|
||||||
callback_data=f"select_period_{period}"
|
callback_data=f"confirm_period_{period}"
|
||||||
))
|
))
|
||||||
|
keyboard.add(InlineKeyboardButton("⬅️ Назад", callback_data="buy_key"))
|
||||||
keyboard.add(InlineKeyboardButton("🏠 Домой", callback_data="home"))
|
keyboard.add(InlineKeyboardButton("🏠 Домой", callback_data="home"))
|
||||||
try:
|
try:
|
||||||
await bot.delete_message(
|
await bot.delete_message(
|
||||||
@@ -1380,10 +1119,10 @@ async def buy_key_callback(callback_query: types.CallbackQuery):
|
|||||||
}
|
}
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data.startswith('select_period_'))
|
@dp.callback_query_handler(lambda c: c.data.startswith('confirm_period_'))
|
||||||
async def select_period_callback(callback_query: types.CallbackQuery):
|
async def confirm_period_callback(callback_query: types.CallbackQuery):
|
||||||
user_id = callback_query.from_user.id
|
user_id = callback_query.from_user.id
|
||||||
period = callback_query.data.split('select_period_')[1]
|
period = callback_query.data.split('confirm_period_')[1]
|
||||||
price = PRICING[period]
|
price = PRICING[period]
|
||||||
discount = user_main_messages.get(user_id, {}).get('promocode_discount', 0)
|
discount = user_main_messages.get(user_id, {}).get('promocode_discount', 0)
|
||||||
final_price = price * (1 - discount / 100)
|
final_price = price * (1 - discount / 100)
|
||||||
@@ -1407,7 +1146,7 @@ async def select_period_callback(callback_query: types.CallbackQuery):
|
|||||||
|
|
||||||
keyboard = InlineKeyboardMarkup().add(
|
keyboard = InlineKeyboardMarkup().add(
|
||||||
InlineKeyboardButton("💳 Оплатить", url=payment_url),
|
InlineKeyboardButton("💳 Оплатить", url=payment_url),
|
||||||
InlineKeyboardButton("⬅️ Назад", callback_data="buy_key"),
|
InlineKeyboardButton("⬅️ Назад", callback_data="select_subscription_period"),
|
||||||
InlineKeyboardButton("🏠 Домой", callback_data="home")
|
InlineKeyboardButton("🏠 Домой", callback_data="home")
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
@@ -1434,6 +1173,52 @@ async def select_period_callback(callback_query: types.CallbackQuery):
|
|||||||
}
|
}
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
|
|
||||||
|
@dp.callback_query_handler(lambda c: c.data == "enter_promocode_for_buy")
|
||||||
|
async def enter_promocode_for_buy_callback(callback_query: types.CallbackQuery):
|
||||||
|
user_id = callback_query.from_user.id
|
||||||
|
try:
|
||||||
|
await bot.delete_message(
|
||||||
|
chat_id=callback_query.message.chat.id,
|
||||||
|
message_id=callback_query.message.message_id
|
||||||
|
)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
sent_message = await bot.send_message(
|
||||||
|
chat_id=callback_query.message.chat.id,
|
||||||
|
text="Введите промокод для скидки:",
|
||||||
|
reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("⬅️ Назад", callback_data="buy_key"))
|
||||||
|
)
|
||||||
|
user_main_messages[user_id] = {
|
||||||
|
'chat_id': sent_message.chat.id,
|
||||||
|
'message_id': sent_message.message_id,
|
||||||
|
'state': 'waiting_for_promocode_for_buy'
|
||||||
|
}
|
||||||
|
await callback_query.answer()
|
||||||
|
|
||||||
|
@dp.callback_query_handler(lambda c: c.data == "reset_promocode")
|
||||||
|
async def reset_promocode_callback(callback_query: types.CallbackQuery):
|
||||||
|
user_id = callback_query.from_user.id
|
||||||
|
if 'promocode_discount' in user_main_messages.get(user_id, {}):
|
||||||
|
del user_main_messages[user_id]['promocode_discount']
|
||||||
|
try:
|
||||||
|
await bot.delete_message(
|
||||||
|
chat_id=callback_query.message.chat.id,
|
||||||
|
message_id=callback_query.message.message_id
|
||||||
|
)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
sent_message = await bot.send_message(
|
||||||
|
chat_id=callback_query.message.chat.id,
|
||||||
|
text="Скидка сброшена. Меню покупки ключа:",
|
||||||
|
reply_markup=get_buy_key_menu(user_id)
|
||||||
|
)
|
||||||
|
user_main_messages[user_id] = {
|
||||||
|
'chat_id': sent_message.chat.id,
|
||||||
|
'message_id': sent_message.message_id,
|
||||||
|
'state': None
|
||||||
|
}
|
||||||
|
await callback_query.answer()
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == "use_promocode")
|
@dp.callback_query_handler(lambda c: c.data == "use_promocode")
|
||||||
async def use_promocode_callback(callback_query: types.CallbackQuery):
|
async def use_promocode_callback(callback_query: types.CallbackQuery):
|
||||||
user_id = callback_query.from_user.id
|
user_id = callback_query.from_user.id
|
||||||
@@ -1446,7 +1231,7 @@ async def use_promocode_callback(callback_query: types.CallbackQuery):
|
|||||||
pass
|
pass
|
||||||
sent_message = await bot.send_message(
|
sent_message = await bot.send_message(
|
||||||
chat_id=callback_query.message.chat.id,
|
chat_id=callback_query.message.chat.id,
|
||||||
text="Введите промокод:",
|
text="Введите промокод для получения ключа:",
|
||||||
reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("🏠 Домой", callback_data="home"))
|
reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("🏠 Домой", callback_data="home"))
|
||||||
)
|
)
|
||||||
user_main_messages[user_id] = {
|
user_main_messages[user_id] = {
|
||||||
@@ -1464,7 +1249,7 @@ async def manage_promocodes_callback(callback_query: types.CallbackQuery):
|
|||||||
return
|
return
|
||||||
promocodes = db.get_promocodes()
|
promocodes = db.get_promocodes()
|
||||||
text = "Промокоды:\n" + "\n".join(
|
text = "Промокоды:\n" + "\n".join(
|
||||||
f"{code}: {info['discount']}% (использовано {info['uses']}/{info['max_uses'] or '∞'}, до {info['expires_at'] or 'неограничено'}, подписка: {info['subscription_period'] or 'нет'})"
|
f"{code}: {info['discount']}% (использовано {info['uses']}/{info['max_uses'] or '∞'}, до {info['expires_at'].strftime('%Y-%m-%d %H:%M UTC') if info['expires_at'] else 'неограничено'}, период подписки: {info['subscription_period'] or 'нет'})"
|
||||||
for code, info in promocodes.items()
|
for code, info in promocodes.items()
|
||||||
) if promocodes else "Промокоды отсутствуют."
|
) if promocodes else "Промокоды отсутствуют."
|
||||||
keyboard = InlineKeyboardMarkup(row_width=2).add(
|
keyboard = InlineKeyboardMarkup(row_width=2).add(
|
||||||
@@ -1506,7 +1291,7 @@ async def add_promocode_callback(callback_query: types.CallbackQuery):
|
|||||||
pass
|
pass
|
||||||
sent_message = await bot.send_message(
|
sent_message = await bot.send_message(
|
||||||
chat_id=callback_query.message.chat.id,
|
chat_id=callback_query.message.chat.id,
|
||||||
text="Введите промокод в формате: <код> <скидка%> <дней_действия> <макс_использований|none> <период_подписки|none>",
|
text="Введите промокод в формате: <код> <скидка%> <дней_действия> <макс_использований|none> <период_подписки|none>\nПример: PROMO1 10 30 none 1_month",
|
||||||
reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("🏠 Домой", callback_data="home"))
|
reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("🏠 Домой", callback_data="home"))
|
||||||
)
|
)
|
||||||
user_main_messages[user_id] = {
|
user_main_messages[user_id] = {
|
||||||
@@ -1559,109 +1344,25 @@ async def remove_promocode_callback(callback_query: types.CallbackQuery):
|
|||||||
await callback_query.answer(f"Промокод {code} не найден.", show_alert=True)
|
await callback_query.answer(f"Промокод {code} не найден.", show_alert=True)
|
||||||
await manage_promocodes_callback(callback_query)
|
await manage_promocodes_callback(callback_query)
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == "check_updates")
|
async def check_pending_payments():
|
||||||
async def check_updates_callback(callback_query: types.CallbackQuery):
|
|
||||||
user_id = callback_query.from_user.id
|
|
||||||
if user_id not in admins:
|
|
||||||
await callback_query.answer("Нет прав.", show_alert=True)
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
process = await asyncio.create_subprocess_exec(
|
|
||||||
'/root/install.sh', '--check-update',
|
|
||||||
stdout=asyncio.subprocess.PIPE,
|
|
||||||
stderr=asyncio.subprocess.PIPE
|
|
||||||
)
|
|
||||||
stdout, stderr = await process.communicate()
|
|
||||||
output = stdout.decode().strip() + stderr.decode().strip()
|
|
||||||
if "Репозиторий актуален" in output:
|
|
||||||
await bot.send_message(user_id, "Репозиторий актуален, обновления не требуются.", parse_mode="Markdown")
|
|
||||||
elif "Обновление репозитория... Done!" in output:
|
|
||||||
await bot.send_message(user_id, "Репозиторий успешно обновлён и служба перезапущена.", parse_mode="Markdown")
|
|
||||||
else:
|
|
||||||
await bot.send_message(user_id, f"Ошибка проверки обновлений:\n```\n{output}\n```", parse_mode="Markdown")
|
|
||||||
except Exception as e:
|
|
||||||
await bot.send_message(user_id, f"Ошибка при проверке обновлений: {str(e)}")
|
|
||||||
sent_message = await bot.send_message(
|
|
||||||
chat_id=callback_query.message.chat.id,
|
|
||||||
text="Выберите действие:",
|
|
||||||
reply_markup=get_main_menu_markup(user_id)
|
|
||||||
)
|
|
||||||
user_main_messages[user_id] = {
|
|
||||||
'chat_id': sent_message.chat.id,
|
|
||||||
'message_id': sent_message.message_id,
|
|
||||||
'state': None
|
|
||||||
}
|
|
||||||
await callback_query.answer()
|
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == "restart_vpn")
|
|
||||||
async def restart_vpn_callback(callback_query: types.CallbackQuery):
|
|
||||||
user_id = callback_query.from_user.id
|
|
||||||
if user_id not in admins:
|
|
||||||
await callback_query.answer("Нет прав.", show_alert=True)
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
process = await asyncio.create_subprocess_exec(
|
|
||||||
'docker', 'ps', '-q', '-f', f'name={DOCKER_CONTAINER}',
|
|
||||||
stdout=asyncio.subprocess.PIPE,
|
|
||||||
stderr=asyncio.subprocess.PIPE
|
|
||||||
)
|
|
||||||
stdout, stderr = await process.communicate()
|
|
||||||
if not stdout.decode().strip():
|
|
||||||
await bot.send_message(user_id, f"Контейнер {DOCKER_CONTAINER} не найден.", parse_mode="Markdown")
|
|
||||||
else:
|
|
||||||
process = await asyncio.create_subprocess_exec(
|
|
||||||
'docker', 'restart', DOCKER_CONTAINER,
|
|
||||||
stdout=asyncio.subprocess.PIPE,
|
|
||||||
stderr=asyncio.subprocess.PIPE
|
|
||||||
)
|
|
||||||
stdout, stderr = await process.communicate()
|
|
||||||
if process.returncode == 0:
|
|
||||||
await bot.send_message(user_id, f"VPN-контейнер {DOCKER_CONTAINER} успешно перезапущен.", parse_mode="Markdown")
|
|
||||||
else:
|
|
||||||
await bot.send_message(user_id, f"Ошибка при перезапуске VPN:\n```\n{stderr.decode().strip()}\n```", parse_mode="Markdown")
|
|
||||||
except Exception as e:
|
|
||||||
await bot.send_message(user_id, f"Ошибка при перезапуске VPN: {str(e)}")
|
|
||||||
sent_message = await bot.send_message(
|
|
||||||
chat_id=callback_query.message.chat.id,
|
|
||||||
text="Выберите действие:",
|
|
||||||
reply_markup=get_main_menu_markup(user_id)
|
|
||||||
)
|
|
||||||
user_main_messages[user_id] = {
|
|
||||||
'chat_id': sent_message.chat.id,
|
|
||||||
'message_id': sent_message.message_id,
|
|
||||||
'state': None
|
|
||||||
}
|
|
||||||
await callback_query.answer()
|
|
||||||
|
|
||||||
async def check_payment_status():
|
|
||||||
payments = db.get_pending_payments()
|
payments = db.get_pending_payments()
|
||||||
for user_id, payment_id, amount, _ in payments:
|
for payment in payments:
|
||||||
|
user_id, payment_id, amount, period = payment
|
||||||
try:
|
try:
|
||||||
if yoomoney_client:
|
history = yoomoney_client.operation_history(label=payment_id)
|
||||||
operation = yoomoney_client.operation_history(label=payment_id)
|
for operation in history.operations:
|
||||||
for op in operation.operations:
|
if operation.status == "success" and operation.amount == amount:
|
||||||
if op.label == payment_id and op.status == "success":
|
|
||||||
db.update_payment_status(payment_id, 'completed')
|
db.update_payment_status(payment_id, 'completed')
|
||||||
pending_payment = user_main_messages.get(user_id, {}).get('pending_payment', {})
|
|
||||||
if pending_payment and pending_payment['payment_id'] == payment_id:
|
|
||||||
period = pending_payment['period']
|
|
||||||
success = await issue_vpn_key(user_id, period)
|
success = await issue_vpn_key(user_id, period)
|
||||||
if success:
|
if success:
|
||||||
await bot.send_message(user_id, "Оплата подтверждена! Ваш VPN ключ отправлен.")
|
await bot.send_message(user_id, f"Оплата подтверждена! VPN ключ на {period.replace('_', ' ')} выдан.")
|
||||||
else:
|
else:
|
||||||
await bot.send_message(user_id, "Ошибка при создании пользователя. Обратитесь к администратору.")
|
await bot.send_message(user_id, "Ошибка при выдаче ключа. Обратитесь к администратору.")
|
||||||
user_main_messages[user_id].pop('pending_payment', None)
|
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка проверки платежа {payment_id}: {str(e)}")
|
logger.error(f"Ошибка при проверке платежа {payment_id}: {str(e)}")
|
||||||
|
|
||||||
|
scheduler.add_job(check_pending_payments, IntervalTrigger(minutes=5))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
import asyncio
|
executor.start_polling(dp, skip_updates=True)
|
||||||
loop = asyncio.new_event_loop()
|
|
||||||
asyncio.set_event_loop(loop)
|
|
||||||
try:
|
|
||||||
loop.run_until_complete(load_isp_cache())
|
|
||||||
scheduler.add_job(check_payment_status, IntervalTrigger(minutes=5))
|
|
||||||
executor.start_polling(dp, skip_updates=True, loop=loop)
|
|
||||||
finally:
|
|
||||||
loop.close()
|
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user