Этот коммит содержится в:
stevefoxru
2025-04-23 15:47:23 +03:00
коммит произвёл GitHub
родитель a94c2a535f
Коммит e5829d2b1c
+238 -29
Просмотреть файл
@@ -22,7 +22,7 @@ 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 apscheduler.triggers.interval import IntervalTrigger
from yoomoney import Client, Quickpay # Добавляем YooMoney from yoomoney import Client, Quickpay
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -35,28 +35,30 @@ moderator_ids = setting.get('moderator_ids', [])
wg_config_file = setting.get('wg_config_file') wg_config_file = setting.get('wg_config_file')
docker_container = setting.get('docker_container') docker_container = setting.get('docker_container')
endpoint = setting.get('endpoint') endpoint = setting.get('endpoint')
yoomoney_token = setting.get('yoomoney_token') # Токен YooMoney yoomoney_token = setting.get('yoomoney_token')
yoomoney_wallet = setting.get('yoomoney_wallet') # Номер кошелька YooMoney yoomoney_wallet = setting.get('yoomoney_wallet')
pricing = setting.get('pricing', {
'1_month': 1000.0,
'3_months': 2500.0,
'6_months': 4500.0,
'12_months': 8000.0
})
if not all([bot_token, admin_ids, wg_config_file, docker_container, endpoint, yoomoney_token, yoomoney_walletseverity=ERROR]): if not all([bot_token, admin_ids, wg_config_file, docker_container, endpoint]):
logger.error("Некоторые обязательные настройки отсутствуют.") logger.error("Некоторые обязательные настройки отсутствуют.")
sys.exit(1) sys.exit(1)
if not all([yoomoney_token, yoomoney_wallet]):
logger.warning("Настройки YooMoney отсутствуют. Установите их через бот.")
admins = [int(admin_id) for admin_id in admin_ids] admins = [int(admin_id) for admin_id in admin_ids]
moderators = [int(mod_id) for mod_id in moderator_ids] moderators = [int(mod_id) for mod_id in moderator_ids]
bot = Bot(bot_token) bot = Bot(bot_token)
WG_CONFIG_FILE = wg_config_file WG_CONFIG_FILE = wg_config_file
DOCKER_CONTAINER = docker_container DOCKER_CONTAINER = docker_container
ENDPOINT = endpoint ENDPOINT = endpoint
yoomoney_client = Client(yoomoney_token) # Инициализация YooMoney yoomoney_client = Client(yoomoney_token) if yoomoney_token else None
PRICING = pricing
# Цены за периоды (в рублях для YooMoney)
PRICING = {
'1_month': 1000.0, # Изменено на рубли
'3_months': 2500.0,
'6_months': 4500.0,
'12_months': 8000.0
}
class AdminMessageDeletionMiddleware(BaseMiddleware): class AdminMessageDeletionMiddleware(BaseMiddleware):
async def on_process_message(self, message: types.Message, data: dict): async def on_process_message(self, message: types.Message, data: dict):
@@ -68,7 +70,7 @@ scheduler = AsyncIOScheduler(timezone=pytz.UTC)
scheduler.start() scheduler.start()
dp.middleware.setup(AdminMessageDeletionMiddleware()) dp.middleware.setup(AdminMessageDeletionMiddleware())
# Главное меню (удалены "Список админов", "Добавить админа", "Создать бэкап") # Главное меню
def get_main_menu_markup(user_id): def get_main_menu_markup(user_id):
markup = InlineKeyboardMarkup(row_width=2) markup = InlineKeyboardMarkup(row_width=2)
if user_id in admins: if user_id in admins:
@@ -101,7 +103,7 @@ def get_main_menu_markup(user_id):
) )
return markup return markup
# Меню настроек (добавлены админы и бэкап) # Меню настроек
def get_settings_menu(): def get_settings_menu():
markup = InlineKeyboardMarkup(row_width=2) markup = InlineKeyboardMarkup(row_width=2)
markup.add( markup.add(
@@ -116,9 +118,40 @@ def get_settings_menu():
InlineKeyboardButton("👥 Список админов", callback_data="list_admins"), InlineKeyboardButton("👥 Список админов", callback_data="list_admins"),
InlineKeyboardButton("👤 Добавить админа", callback_data="add_admin") InlineKeyboardButton("👤 Добавить админа", callback_data="add_admin")
) )
markup.add(
InlineKeyboardButton("💸 Настройки YooMoney", callback_data="yoomoney_settings"),
InlineKeyboardButton("💰 Настройки цен", callback_data="pricing_settings")
)
markup.add(InlineKeyboardButton("⬅️ Назад", callback_data="home")) markup.add(InlineKeyboardButton("⬅️ Назад", callback_data="home"))
return markup return markup
# Меню настроек YooMoney
def get_yoomoney_settings_menu():
markup = InlineKeyboardMarkup(row_width=2)
markup.add(
InlineKeyboardButton("🔑 Установить токен", callback_data="set_yoomoney_token"),
InlineKeyboardButton("💼 Установить кошелёк", callback_data="set_yoomoney_wallet")
)
markup.add(InlineKeyboardButton("⬅️ Назад", callback_data="settings"))
return markup
# Меню настроек цен
def get_pricing_settings_menu():
markup = InlineKeyboardMarkup(row_width=2)
periods = [
("1 месяц", "1_month"),
("3 месяца", "3_months"),
("6 месяцев", "6_months"),
("12 месяцев", "12_months")
]
for period_name, period_key in periods:
markup.add(InlineKeyboardButton(
f"{period_name} - ₽{PRICING.get(period_key, 0):.2f}",
callback_data=f"set_price_{period_key}"
))
markup.add(InlineKeyboardButton("⬅️ Назад", callback_data="settings"))
return markup
# Клавиатура для выбора периода продления # Клавиатура для выбора периода продления
def get_renewal_period_keyboard(username): def get_renewal_period_keyboard(username):
markup = InlineKeyboardMarkup(row_width=2) markup = InlineKeyboardMarkup(row_width=2)
@@ -150,7 +183,7 @@ def get_clear_keys_date_keyboard():
user_main_messages = {} user_main_messages = {}
isp_cache = {} isp_cache = {}
ISP_CACHE_FILE = 'files/isp_cache.json' ISP_CACHE_FILE = 'files/isp_cache.json'
CACHE_TTL = 24 * 3600 # 24 часа в секундах CACHE_TTL = 24 * 3600
def get_interface_name(): def get_interface_name():
return os.path.basename(WG_CONFIG_FILE).split('.')[0] return os.path.basename(WG_CONFIG_FILE).split('.')[0]
@@ -228,7 +261,6 @@ def parse_transfer(transfer_str: str) -> tuple:
return 0, 0 return 0, 0
async def generate_vpn_key(conf_path: str) -> str: async def generate_vpn_key(conf_path: str) -> str:
"""Преобразует .conf в формат vpn:// с помощью awg-decode.py."""
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,
stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
@@ -241,7 +273,6 @@ async def generate_vpn_key(conf_path: str) -> str:
return "" return ""
async def issue_vpn_key(user_id: int, period: str) -> bool: async def issue_vpn_key(user_id: int, period: str) -> bool:
"""Выдача VPN-ключа пользователю."""
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:
@@ -251,7 +282,7 @@ async def issue_vpn_key(user_id: int, period: str) -> bool:
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')
if os.path.exists(conf_path): if os.path.exists(conf_path):
vpn_key = await generate_vpn various_key(conf_path) vpn_key = await generate_vpn_key(conf_path)
caption = f"Ваш VPN ключ ({period.replace('_', ' ')}):\nAmneziaVPN:\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```\n{vpn_key}\n```" caption = f"Ваш VPN ключ ({period.replace('_', ' ')}):\nAmneziaVPN:\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```\n{vpn_key}\n```"
with open(conf_path, 'rb') as config: with open(conf_path, 'rb') as config:
config_message = await bot.send_document(user_id, config, caption=caption, parse_mode="Markdown") config_message = await bot.send_document(user_id, config, caption=caption, parse_mode="Markdown")
@@ -339,13 +370,13 @@ async def handle_messages(message: types.Message):
if promocode_data: if promocode_data:
discount = promocode_data.get('discount', 0) 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)
if success: if success:
await message.reply(f"Промокод активирован! VPN ключ на {subscription_period.replace('_', ' ')} выдан.") await message.reply(f"Промокод активирован! VPN ключ на {subscription_period.replace('_', ' ')} выдан.")
else: else:
await message.reply("Ошибка при выдаче ключа. Обратитесь к администратору.") await message.reply("Ошибка при выдаче ключа. Обратитесь к администратору.")
else: # Промокод на скидку else:
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:
@@ -386,6 +417,53 @@ 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 == 'waiting_for_yoomoney_token' and user_id in admins:
token = message.text.strip()
db.set_yoomoney_config(token=token)
global yoomoney_client, yoomoney_token
yoomoney_token = token
yoomoney_client = Client(yoomoney_token)
await message.reply("Токен YooMoney успешно обновлён.")
sent_message = await message.answer("Настройки YooMoney:", reply_markup=get_yoomoney_settings_menu())
user_main_messages[user_id] = {
'chat_id': sent_message.chat.id,
'message_id': sent_message.message_id,
'state': None
}
elif user_state == 'waiting_for_yoomoney_wallet' and user_id in admins:
wallet = message.text.strip()
if not re.match(r'^\d{15,18}$', wallet):
await message.reply("Введите корректный номер кошелька YooMoney (15-18 цифр).")
return
db.set_yoomoney_config(wallet=wallet)
global yoomoney_wallet
yoomoney_wallet = wallet
await message.reply("Номер кошелька YooMoney успешно обновлён.")
sent_message = await message.answer("Настройки YooMoney:", reply_markup=get_yoomoney_settings_menu())
user_main_messages[user_id] = {
'chat_id': sent_message.chat.id,
'message_id': sent_message.message_id,
'state': None
}
elif user_state.startswith('waiting_for_price_') and user_id in admins:
period = user_state.split('waiting_for_price_')[1]
try:
price = float(message.text.strip())
if price <= 0:
raise ValueError("Цена должна быть положительной.")
db.set_pricing(period, price)
global PRICING
PRICING[period] = price
await message.reply(f"Цена для {period.replace('_', ' ')} обновлена: ₽{price:.2f}")
except:
await message.reply("Введите корректное число (например, 1000.00).")
return
sent_message = await message.answer("Настройки цен:", reply_markup=get_pricing_settings_menu())
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):
@@ -412,6 +490,132 @@ async def settings_menu_callback(callback_query: types.CallbackQuery):
} }
await callback_query.answer() await callback_query.answer()
@dp.callback_query_handler(lambda c: c.data == "yoomoney_settings")
async def yoomoney_settings_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="Настройки YooMoney:",
reply_markup=get_yoomoney_settings_menu()
)
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 == "set_yoomoney_token")
async def set_yoomoney_token_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="Введите токен YooMoney:",
reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("⬅️ Назад", callback_data="yoomoney_settings"))
)
user_main_messages[user_id] = {
'chat_id': sent_message.chat.id,
'message_id': sent_message.message_id,
'state': 'waiting_for_yoomoney_token'
}
await callback_query.answer()
@dp.callback_query_handler(lambda c: c.data == "set_yoomoney_wallet")
async def set_yoomoney_wallet_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="Введите номер кошелька YooMoney (15-18 цифр):",
reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("⬅️ Назад", callback_data="yoomoney_settings"))
)
user_main_messages[user_id] = {
'chat_id': sent_message.chat.id,
'message_id': sent_message.message_id,
'state': 'waiting_for_yoomoney_wallet'
}
await callback_query.answer()
@dp.callback_query_handler(lambda c: c.data == "pricing_settings")
async def pricing_settings_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_pricing_settings_menu()
)
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('set_price_'))
async def set_price_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
period = callback_query.data.split('set_price_')[1]
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=f"Введите новую цену для {period.replace('_', ' ')} в рублях (например, 1000.00):",
reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("⬅️ Назад", callback_data="pricing_settings"))
)
user_main_messages[user_id] = {
'chat_id': sent_message.chat.id,
'message_id': sent_message.message_id,
'state': f'waiting_for_price_{period}'
}
await callback_query.answer()
@dp.callback_query_handler(lambda c: c.data == "clear_old_keys") @dp.callback_query_handler(lambda c: c.data == "clear_old_keys")
async def clear_old_keys_callback(callback_query: types.CallbackQuery): async def clear_old_keys_callback(callback_query: types.CallbackQuery):
user_id = callback_query.from_user.id user_id = callback_query.from_user.id
@@ -1150,13 +1354,13 @@ async def pc_instructions(callback_query: types.CallbackQuery):
@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 user_id = callback_query.from_user.id
keyboard = InlineKeyboardMarkup(row_width=2).add( keyboard = InlineKeyboardMarkup(row_width=2)
InlineKeyboardButton("1 месяц - ₽1000", callback_data="select_period_1_month"), for period, price in PRICING.items():
InlineKeyboardButton("3 месяца - ₽2500", callback_data="select_period_3_months"), keyboard.add(InlineKeyboardButton(
InlineKeyboardButton("6 месяцев - ₽4500", callback_data="select_period_6_months"), f"{period.replace('_', ' ')} - ₽{price:.2f}",
InlineKeyboardButton("12 месяцев - ₽8000", callback_data="select_period_12_months"), callback_data=f"select_period_{period}"
InlineKeyboardButton("🏠 Домой", callback_data="home") ))
) keyboard.add(InlineKeyboardButton("🏠 Домой", callback_data="home"))
try: try:
await bot.delete_message( await bot.delete_message(
chat_id=callback_query.message.chat.id, chat_id=callback_query.message.chat.id,
@@ -1184,6 +1388,10 @@ async def select_period_callback(callback_query: types.CallbackQuery):
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)
if not yoomoney_client or not yoomoney_wallet:
await callback_query.answer("Платежи недоступны. Обратитесь к администратору.", show_alert=True)
return
payment_id = str(uuid.uuid4()) payment_id = str(uuid.uuid4())
db.add_payment(user_id, payment_id, final_price, 'pending') db.add_payment(user_id, payment_id, final_price, 'pending')
@@ -1250,7 +1458,7 @@ async def use_promocode_callback(callback_query: types.CallbackQuery):
@dp.callback_query_handler(lambda c: c.data == "manage_promocodes") @dp.callback_query_handler(lambda c: c.data == "manage_promocodes")
async def manage_promocodes_callback(callback_query: types.CallbackQuery): async def manage_promocodes_callback(callback_query: types.CallbackQuery):
user_id = callback(nameof): callback_query.from_user.id user_id = callback_query.from_user.id
if user_id not in admins: if user_id not in admins:
await callback_query.answer("Нет прав.", show_alert=True) await callback_query.answer("Нет прав.", show_alert=True)
return return
@@ -1429,6 +1637,7 @@ async def check_payment_status():
payments = db.get_pending_payments() payments = db.get_pending_payments()
for user_id, payment_id, amount, _ in payments: for user_id, payment_id, amount, _ in payments:
try: try:
if yoomoney_client:
operation = yoomoney_client.operation_history(label=payment_id) operation = yoomoney_client.operation_history(label=payment_id)
for op in operation.operations: for op in operation.operations:
if op.label == payment_id and op.status == "success": if op.label == payment_id and op.status == "success":