Этот коммит содержится в:
stevefoxru
2025-04-24 14:29:00 +03:00
коммит произвёл GitHub
родитель eac63031e9
Коммит ba056994a1
+209 -575
Просмотреть файл
@@ -1,620 +1,254 @@
import json
import os import os
import subprocess import subprocess
import configparser
import json
import pytz
import socket
import logging import logging
import tempfile from datetime import datetime
from datetime import datetime, timedelta import pytz
import shutil
EXPIRATIONS_FILE = 'files/expirations.json'
PAYMENTS_FILE = 'files/payments.json'
ADMINS_FILE = 'files/admins.json'
PROMOCODES_FILE = 'files/promocodes.json'
USER_TELEGRAM_IDS_FILE = 'files/user_telegram_ids.json'
CONFIG_FILE = 'files/setting.ini'
UTC = pytz.UTC
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def get_amnezia_container(): CONFIG_FILE = 'files/config.json'
cmd = "docker ps --filter 'name=amnezia-awg' --format '{{.Names}}'" USER_EXPIRATION_FILE = 'files/user_expiration.json'
USER_TELEGRAM_FILE = 'files/user_telegram.json'
PROMOCODES_FILE = 'files/promocodes.json'
PAYMENTS_FILE = 'files/payments.json'
def load_json(file_path, default=None):
"""Загружает JSON-файл, возвращает default при ошибке или отсутствии файла."""
try: try:
output = subprocess.check_output(cmd, shell=True).decode().strip() if os.path.exists(file_path):
if output: with open(file_path, 'r') as f:
return output return json.load(f)
else:
logger.error("Docker-контейнер 'amnezia-awg' не найден или не запущен.")
exit(1)
except subprocess.CalledProcessError:
logger.error("Не удалось выполнить Docker-команду для поиска контейнера 'amnezia-awg'.")
exit(1)
def create_config(path=CONFIG_FILE):
os.makedirs(os.path.dirname(path), exist_ok=True)
config = configparser.ConfigParser()
config.add_section("setting")
bot_token = input('Введите токен Telegram бота: ').strip()
admin_ids_input = input('Введите Telegram ID администраторов через запятую (например, 12345, 67890): ').strip()
admin_ids = [admin_id.strip() for admin_id in admin_ids_input.split(',')]
yoomoney_token = input('Введите токен YooMoney: ').strip()
yoomoney_wallet = input('Введите номер кошелька YooMoney (15-18 цифр): ').strip()
pricing = {
'1_month': 1000.0,
'3_months': 2500.0,
'6_months': 4500.0,
'12_months': 8000.0
}
docker_container = get_amnezia_container()
logger.info(f"Найден Docker-контейнер: {docker_container}")
cmd = f"docker exec {docker_container} find / -name wg0.conf"
try:
wg_config_file = subprocess.check_output(cmd, shell=True).decode().strip()
if not wg_config_file:
logger.warning("Не удалось найти файл конфигурации WireGuard 'wg0.conf'. Используется путь по умолчанию.")
wg_config_file = '/opt/amnezia/awg/wg0.conf'
except subprocess.CalledProcessError:
logger.warning("Ошибка при определении пути к файлу конфигурации WireGuard. Используется путь по умолчанию.")
wg_config_file = '/opt/amnezia/awg/wg0.conf'
try:
endpoint = subprocess.check_output("curl -s https://api.ipify.org", shell=True).decode().strip()
socket.inet_aton(endpoint)
except (subprocess.CalledProcessError, socket.error):
logger.error("Ошибка при определении внешнего IP-адреса сервера.")
endpoint = input('Не удалось автоматически определить внешний IP-адрес. Введите его вручную: ').strip()
config.set("setting", "bot_token", bot_token)
config.set("setting", "admin_ids", ','.join(admin_ids))
config.set("setting", "docker_container", docker_container)
config.set("setting", "wg_config_file", wg_config_file)
config.set("setting", "endpoint", endpoint)
config.set("setting", "yoomoney_token", yoomoney_token)
config.set("setting", "yoomoney_wallet", yoomoney_wallet)
config.set("setting", "pricing", json.dumps(pricing))
with open(path, "w") as config_file:
config.write(config_file)
logger.info(f"Конфигурация сохранена в {path}")
save_admins(admin_ids)
def ensure_peer_names():
setting = get_config()
wg_config_file = setting['wg_config_file']
docker_container = setting['docker_container']
clientsTable = get_full_clients_table()
clients_dict = {client['clientId']: client['userData'] for client in clientsTable}
try:
cmd = f"docker exec -i {docker_container} cat {wg_config_file}"
config_content = subprocess.check_output(cmd, shell=True).decode('utf-8')
lines = config_content.splitlines()
new_config_lines = []
i = 0
modified = False
updated_clientsTable = False
while i < len(lines):
line = lines[i]
if line.strip().startswith('[Peer]'):
peer_block = [line]
i += 1
has_name_comment = False
client_public_key = ''
while i < len(lines) and lines[i].strip() != '':
peer_line = lines[i]
if peer_line.strip().startswith('#'):
has_name_comment = True
elif peer_line.strip().startswith('PublicKey ='):
client_public_key = peer_line.strip().split('=', 1)[1].strip()
peer_block.append(peer_line)
i += 1
if not has_name_comment:
if client_public_key in clients_dict:
client_name = clients_dict[client_public_key].get('clientName', f"client_{client_public_key[:6]}")
else:
client_name = f"client_{client_public_key[:6]}"
clients_dict[client_public_key] = {
'clientName': client_name,
'creationDate': datetime.now().isoformat()
}
updated_clientsTable = True
peer_block.insert(1, f'# {client_name}')
modified = True
new_config_lines.extend(peer_block)
if i < len(lines):
new_config_lines.append(lines[i])
i += 1
else:
new_config_lines.append(line)
i += 1
if modified:
new_config_content = '\n'.join(new_config_lines)
with tempfile.NamedTemporaryFile(mode='w', delete=False) as temp_config:
temp_config.write(new_config_content)
temp_config_path = temp_config.name
docker_cmd = f"docker cp {temp_config_path} {docker_container}:{wg_config_file}"
subprocess.check_call(docker_cmd, shell=True)
os.remove(temp_config_path)
logger.info("Конфигурационный файл WireGuard обновлён с добавлением комментариев # name_client.")
if updated_clientsTable:
clientsTable_list = [{'clientId': key, 'userData': value} for key, value in clients_dict.items()]
with tempfile.NamedTemporaryFile(mode='w', delete=False) as temp_clientsTable:
json.dump(clientsTable_list, temp_clientsTable)
temp_clientsTable_path = temp_clientsTable.name
docker_cmd = f"docker cp {temp_clientsTable_path} {docker_container}:/opt/amnezia/awg/clientsTable"
subprocess.check_call(docker_cmd, shell=True)
os.remove(temp_clientsTable_path)
logger.info("clientsTable обновлён с новыми клиентами.")
except Exception as e: except Exception as e:
logger.error(f"Ошибка при обновлении комментариев в конфигурации WireGuard: {e}") logger.error(f"Ошибка загрузки {file_path}: {str(e)}")
return default if default is not None else {}
def get_config(path=CONFIG_FILE): def save_json(file_path, data):
if not os.path.exists(path): """Сохраняет данные в JSON-файл."""
create_config(path)
config = configparser.ConfigParser()
config.read(path)
out = {}
for key in config['setting']:
if key == 'admin_ids':
out[key] = config['setting'][key].split(',')
elif key == 'pricing':
out[key] = json.loads(config['setting'][key])
else:
out[key] = config['setting'][key]
return out
def set_yoomoney_config(token=None, wallet=None, path=CONFIG_FILE):
config = configparser.ConfigParser()
config.read(path)
if token:
config.set("setting", "yoomoney_token", token)
if wallet:
config.set("setting", "yoomoney_wallet", wallet)
with open(path, 'w') as configfile:
config.write(configfile)
logger.info(f"YooMoney settings updated: token={'***' if token else 'unchanged'}, wallet={wallet or 'unchanged'}")
def set_pricing(period, price, path=CONFIG_FILE):
config = configparser.ConfigParser()
config.read(path)
pricing = json.loads(config.get(
"setting", "pricing",
fallback='{"1_month": 1000.0, "3_months": 2500.0, "6_months": 4500.0, "12_months": 8000.0}'
))
pricing[period] = float(price)
config.set("setting", "pricing", json.dumps(pricing))
with open(path, 'w') as configfile:
config.write(configfile)
logger.info(f"Pricing updated: {period} set to ₽{price}")
def save_client_endpoint(username, endpoint):
os.makedirs('files/connections', exist_ok=True)
file_path = os.path.join('files', 'connections', f'{username}_ip.json')
timestamp = datetime.now().strftime('%d.%m.%Y %H:%M')
ip_address = endpoint.split(':')[0]
if os.path.exists(file_path):
with open(file_path, 'r') as f:
try:
data = json.load(f)
except json.JSONDecodeError:
data = {}
else:
data = {}
data[ip_address] = timestamp
with open(file_path, 'w') as f:
json.dump(data, f)
def root_add(id_user, ipv6=False):
setting = get_config()
endpoint = setting['endpoint']
wg_config_file = setting['wg_config_file']
docker_container = setting['docker_container']
clients = get_client_list()
client_entry = next((c for c in clients if c[0] == id_user), None)
if client_entry:
logger.info(f"Пользователь {id_user} уже существует. Генерация конфигурации невозможна без приватного ключа.")
return False
else:
cmd = ["./newclient.sh", id_user, endpoint, wg_config_file, docker_container]
if subprocess.call(cmd) == 0:
return True
return False
def get_clients_from_clients_table():
setting = get_config()
docker_container = setting['docker_container']
clients_table_path = '/opt/amnezia/awg/clientsTable'
try: try:
cmd = f"docker exec -i {docker_container} cat {clients_table_path}" os.makedirs(os.path.dirname(file_path), exist_ok=True)
call = subprocess.check_output(cmd, shell=True) with open(file_path, 'w') as f:
clients_table = json.loads(call.decode('utf-8')) json.dump(data, f, indent=4, default=str)
client_map = {client['clientId']: client['userData']['clientName'] for client in clients_table}
return client_map
except subprocess.CalledProcessError as e:
logger.error(f"Ошибка при получении clientsTable: {e}")
return {}
except json.JSONDecodeError:
logger.error("Ошибка при разборе clientsTable JSON.")
return {}
def parse_client_name(full_name):
return full_name.split('[')[0].strip()
def get_client_list():
setting = get_config()
wg_config_file = setting['wg_config_file']
docker_container = setting['docker_container']
client_map = get_clients_from_clients_table()
try:
cmd = f"docker exec -i {docker_container} cat {wg_config_file}"
call = subprocess.check_output(cmd, shell=True)
config_content = call.decode('utf-8')
clients = []
lines = config_content.splitlines()
i = 0
while i < len(lines):
line = lines[i].strip()
if line.startswith('[Peer]'):
client_public_key = ''
allowed_ips = ''
client_name = 'Unknown'
i += 1
while i < len(lines):
peer_line = lines[i].strip()
if peer_line == '':
break
if peer_line.startswith('#'):
full_client_name = peer_line[1:].strip()
client_name = parse_client_name(full_client_name)
elif peer_line.startswith('PublicKey ='):
client_public_key = peer_line.split('=', 1)[1].strip()
elif peer_line.startswith('AllowedIPs ='):
allowed_ips = peer_line.split('=', 1)[1].strip()
i += 1
client_name = client_map.get(client_public_key, client_name if 'client_name' in locals() else 'Unknown')
clients.append([client_name, client_public_key, allowed_ips])
else:
i += 1
return clients
except subprocess.CalledProcessError as e:
logger.error(f"Ошибка при получении списка клиентов: {e}")
return []
def get_active_list():
setting = get_config()
docker_container = setting['docker_container']
client_map = get_clients_from_clients_table()
try:
clients = get_client_list()
client_key_map = {client[1]: client[0] for client in clients}
cmd = f"docker exec -i {docker_container} wg show"
call = subprocess.check_output(cmd, shell=True)
wg_output = call.decode('utf-8')
active_clients = []
current_peer = {}
for line in wg_output.splitlines():
line = line.strip()
if line.startswith('peer:'):
peer_public_key = line.split('peer: ')[1].strip()
current_peer = {'public_key': peer_public_key}
elif line.startswith('endpoint:') and 'public_key' in current_peer:
current_peer['endpoint'] = line.split('endpoint: ')[1].strip()
elif line.startswith('latest handshake:') and 'public_key' in current_peer:
current_peer['latest_handshake'] = line.split('latest handshake: ')[1].strip()
elif line.startswith('transfer:') and 'public_key' in current_peer:
current_peer['transfer'] = line.split('transfer: ')[1].strip()
elif line == '' and 'public_key' in current_peer:
last_handshake = current_peer.get('latest_handshake', '').lower()
if last_handshake not in ['never', 'нет данных', '-']:
peer_public_key = current_peer.get('public_key')
if peer_public_key in client_key_map:
username = client_key_map[peer_public_key]
last_time = current_peer.get('latest_handshake', 'Нет данных')
transfer = current_peer.get('transfer', 'Нет данных')
endpoint = current_peer.get('endpoint', 'Нет данных')
save_client_endpoint(username, endpoint)
active_clients.append([username, last_time, transfer, endpoint])
current_peer = {}
if 'public_key' in current_peer:
last_handshake = current_peer.get('latest_handshake', '').lower()
if last_handshake not in ['never', 'нет данных', '-']:
peer_public_key = current_peer.get('public_key')
if peer_public_key in client_key_map:
username = client_key_map[peer_public_key]
last_time = current_peer.get('latest_handshake', 'Нет данных')
transfer = current_peer.get('transfer', 'Нет данных')
endpoint = current_peer.get('endpoint', 'Нет данных')
save_client_endpoint(username, endpoint)
active_clients.append([username, last_time, transfer, endpoint])
return active_clients
except subprocess.CalledProcessError as e:
logger.error(f"Ошибка при получении активных клиентов: {e}")
return []
def deactive_user_db(username):
setting = get_config()
docker_container = setting['docker_container']
try:
cmd = f"./removeclient.sh {username} {docker_container}"
subprocess.check_call(cmd, shell=True)
logger.info(f"Пользователь {username} удалён.")
return True return True
except subprocess.CalledProcessError as e: except Exception as e:
logger.error(f"Ошибка при удалении пользователя {username}: {e}") logger.error(f"Ошибка сохранения {file_path}: {str(e)}")
return False return False
def save_admins(admin_ids): def get_config():
os.makedirs(os.path.dirname(ADMINS_FILE), exist_ok=True) """Возвращает конфигурацию из config.json."""
with open(ADMINS_FILE, 'w') as f: return load_json(CONFIG_FILE, {})
json.dump(admin_ids, f)
def add_admin(admin_id): def add_admin(admin_id):
admin_ids = get_admins() """Добавляет ID администратора в конфигурацию."""
config = get_config()
admin_ids = config.get('admin_ids', [])
if str(admin_id) not in admin_ids: if str(admin_id) not in admin_ids:
admin_ids.append(str(admin_id)) admin_ids.append(str(admin_id))
save_admins(admin_ids) config['admin_ids'] = admin_ids
save_json(CONFIG_FILE, config)
def remove_admin(admin_id): def remove_admin(admin_id):
admin_ids = get_admins() """Удаляет ID администратора из конфигурации."""
if str(admin_id) in admin_ids: config = get_config()
admin_ids.remove(str(admin_id)) admin_ids = config.get('admin_ids', [])
save_admins(admin_ids) admin_id_str = str(admin_id)
if admin_id_str in admin_ids:
admin_ids.remove(admin_id_str)
config['admin_ids'] = admin_ids
save_json(CONFIG_FILE, config)
def get_admins(): def set_yoomoney_config(token=None, wallet=None):
if os.path.exists(ADMINS_FILE): """Обновляет настройки YooMoney в конфигурации."""
with open(ADMINS_FILE, 'r') as f: config = get_config()
try: if token:
return json.load(f) config['yoomoney_token'] = token
except json.JSONDecodeError: if wallet:
logger.error("Ошибка при разборе файла admins.json.") config['yoomoney_wallet'] = wallet
return [] save_json(CONFIG_FILE, config)
return []
def get_user_expiration(username): def set_pricing(period, price):
if os.path.exists(EXPIRATIONS_FILE): """Устанавливает цену для указанного периода подписки."""
with open(EXPIRATIONS_FILE, 'r') as f: config = get_config()
try: config['pricing'] = config.get('pricing', {})
expirations = json.load(f) config['pricing'][period] = price
expiration_str = expirations.get(username) save_json(CONFIG_FILE, config)
if expiration_str:
return datetime.fromisoformat(expiration_str).astimezone(UTC)
except json.JSONDecodeError:
logger.error("Ошибка при разборе файла expirations.json.")
return None
def set_user_expiration(username, expiration_date, traffic_limit): def root_add(name, ipv6=False):
os.makedirs(os.path.dirname(EXPIRATIONS_FILE), exist_ok=True) """Добавляет нового пользователя через newclient.sh."""
expirations = {}
if os.path.exists(EXPIRATIONS_FILE):
with open(EXPIRATIONS_FILE, 'r') as f:
try:
expirations = json.load(f)
except json.JSONDecodeError:
logger.error("Ошибка при разборе файла expirations.json.")
expirations[username] = expiration_date.isoformat()
with open(EXPIRATIONS_FILE, 'w') as f:
json.dump(expirations, f)
def remove_user_expiration(username):
if os.path.exists(EXPIRATIONS_FILE):
with open(EXPIRATIONS_FILE, 'r') as f:
try:
expirations = json.load(f)
if username in expirations:
del expirations[username]
with open(EXPIRATIONS_FILE, 'w') as f:
json.dump(expirations, f)
except json.JSONDecodeError:
logger.error("Ошибка при разборе файла expirations.json.")
def set_user_telegram_id(username, telegram_id):
os.makedirs(os.path.dirname(USER_TELEGRAM_IDS_FILE), exist_ok=True)
telegram_ids = {}
if os.path.exists(USER_TELEGRAM_IDS_FILE):
with open(USER_TELEGRAM_IDS_FILE, 'r') as f:
try:
telegram_ids = json.load(f)
except json.JSONDecodeError:
logger.error("Ошибка при разборе файла user_telegram_ids.json.")
telegram_ids[username] = str(telegram_id)
with open(USER_TELEGRAM_IDS_FILE, 'w') as f:
json.dump(telegram_ids, f)
def get_user_telegram_id(username):
if os.path.exists(USER_TELEGRAM_IDS_FILE):
with open(USER_TELEGRAM_IDS_FILE, 'r') as f:
try:
telegram_ids = json.load(f)
return telegram_ids.get(username)
except json.JSONDecodeError:
logger.error("Ошибка при разборе файла user_telegram_ids.json.")
return None
def clear_old_keys(before_date):
try: try:
before = datetime.fromisoformat(before_date).astimezone(UTC) cmd = ['./newclient.sh', name]
except ValueError: if not ipv6:
logger.error(f"Неверный формат даты: {before_date}") cmd.append('--no-ipv6')
process = subprocess.run(cmd, capture_output=True, text=True)
if process.returncode == 0:
return True
logger.error(f"Ошибка добавления пользователя {name}: {process.stderr}")
return False
except Exception as e:
logger.error(f"Исключение при добавлении пользователя {name}: {str(e)}")
return False return False
clients = get_client_list() def deactive_user_db(name):
expirations = {} """Деактивирует пользователя через removeclient.sh."""
if os.path.exists(EXPIRATIONS_FILE): try:
with open(EXPIRATIONS_FILE, 'r') as f: process = subprocess.run(['./removeclient.sh', name], capture_output=True, text=True)
try: if process.returncode == 0:
expirations = json.load(f) return True
except json.JSONDecodeError: logger.error(f"Ошибка удаления пользователя {name}: {process.stderr}")
logger.error("Ошибка при разборе файла expirations.json.") return False
except Exception as e:
logger.error(f"Исключение при удалении пользователя {name}: {str(e)}")
return False
removed = False def get_client_list():
for client in clients: """Возвращает список клиентов (имя и конфигурация)."""
username = client[0] clients = []
expiration_str = expirations.get(username) users_dir = 'users'
if expiration_str: if os.path.exists(users_dir):
try: for user_dir in os.listdir(users_dir):
expiration = datetime.fromisoformat(expiration_str).astimezone(UTC) user_path = os.path.join(users_dir, user_dir)
if expiration < before: if os.path.isdir(user_path):
if deactive_user_db(username): conf_file = os.path.join(user_path, f"{user_dir}.conf")
remove_user_expiration(username) if os.path.exists(conf_file):
user_path = os.path.join('users', username) with open(conf_file, 'r') as f:
if os.path.exists(user_path): config = f.read()
import shutil clients.append((user_dir, config))
shutil.rmtree(user_path) return clients
logger.info(f"Удалён старый ключ для {username} с истёкшей датой {expiration_str}")
removed = True
except ValueError:
logger.error(f"Неверный формат даты истечения для {username}: {expiration_str}")
return removed
def add_payment(user_id, payment_id, amount, status): def get_active_list():
os.makedirs(os.path.dirname(PAYMENTS_FILE), exist_ok=True) """Возвращает список активных клиентов с последним handshake."""
payments = {} active = []
if os.path.exists(PAYMENTS_FILE): users_dir = 'users'
with open(PAYMENTS_FILE, 'r') as f: if os.path.exists(users_dir):
try: for user_dir in os.listdir(users_dir):
payments = json.load(f) user_path = os.path.join(users_dir, user_dir)
except json.JSONDecodeError: if os.path.isdir(user_path):
logger.error("Ошибка при разборе файла payments.json.") status_file = os.path.join(user_path, 'status.json')
payments[payment_id] = { if os.path.exists(status_file):
'user_id': user_id, with open(status_file, 'r') as f:
'amount': amount, status = json.load(f)
'status': status, last_handshake = status.get('last_handshake', 'never')
'created_at': datetime.now(UTC).isoformat() active.append((user_dir, last_handshake))
return active
def set_user_expiration(username, expiration, transfer_limit):
"""Устанавливает срок действия и лимит трафика для пользователя."""
data = load_json(USER_EXPIRATION_FILE, {})
data[username] = {
'expiration': expiration.isoformat() if expiration else None,
'transfer_limit': transfer_limit
} }
with open(PAYMENTS_FILE, 'w') as f: save_json(USER_EXPIRATION_FILE, data)
json.dump(payments, f)
def update_payment_status(payment_id, status): def get_user_expiration(username):
if os.path.exists(PAYMENTS_FILE): """Получает срок действия подписки пользователя."""
with open(PAYMENTS_FILE, 'r') as f: data = load_json(USER_EXPIRATION_FILE, {})
try: user_data = data.get(username, {})
payments = json.load(f) expiration = user_data.get('expiration')
if payment_id in payments: return datetime.fromisoformat(expiration) if expiration else None
payments[payment_id]['status'] = status
with open(PAYMENTS_FILE, 'w') as f:
json.dump(payments, f)
return True
except json.JSONDecodeError:
logger.error("Ошибка при разборе файла payments.json.")
return False
def get_pending_payments(): def remove_user_expiration(username):
if os.path.exists(PAYMENTS_FILE): """Удаляет информацию о сроке действия подписки пользователя."""
with open(PAYMENTS_FILE, 'r') as f: data = load_json(USER_EXPIRATION_FILE, {})
try: if username in data:
payments = json.load(f) del data[username]
return [ save_json(USER_EXPIRATION_FILE, data)
(p['user_id'], payment_id, p['amount'], p['status'])
for payment_id, p in payments.items()
if p['status'] == 'pending'
]
except json.JSONDecodeError:
logger.error("Ошибка при разборе файла payments.json.")
return []
def add_promocode(code, discount, expires_at, max_uses): def set_user_telegram_id(username, telegram_id):
os.makedirs(os.path.dirname(PROMOCODES_FILE), exist_ok=True) """Связывает имя пользователя с Telegram ID."""
promocodes = {} data = load_json(USER_TELEGRAM_FILE, {})
if os.path.exists(PROMOCODES_FILE): data[username] = telegram_id
with open(PROMOCODES_FILE, 'r') as f: save_json(USER_TELEGRAM_FILE, data)
try:
promocodes = json.load(f) def get_user_telegram_id(username):
except json.JSONDecodeError: """Получает Telegram ID пользователя по имени."""
logger.error("Ошибка при разборе файла promocodes.json.") data = load_json(USER_TELEGRAM_FILE, {})
return data.get(username)
def add_promocode(code, discount, expires_at, max_uses, subscription_period):
"""Добавляет новый промокод."""
promocodes = load_json(PROMOCODES_FILE, {})
if code in promocodes: if code in promocodes:
return False return False
promocodes[code] = { promocodes[code] = {
'discount': discount, 'discount': discount,
'expires_at': expires_at.isoformat() if expires_at else None, 'expires_at': expires_at.isoformat() if expires_at else None,
'max_uses': max_uses, 'max_uses': max_uses,
'uses': 0 'uses': 0,
'subscription_period': subscription_period
} }
with open(PROMOCODES_FILE, 'w') as f: save_json(PROMOCODES_FILE, promocodes)
json.dump(promocodes, f)
return True return True
def apply_promocode(code): def apply_promocode(code):
if os.path.exists(PROMOCODES_FILE): """Применяет промокод, увеличивает счетчик использований."""
with open(PROMOCODES_FILE, 'r') as f: promocodes = load_json(PROMOCODES_FILE, {})
try: now = datetime.now(pytz.utc)
promocodes = json.load(f) promo = promocodes.get(code)
if code in promocodes: if not promo:
promo = promocodes[code] return None
now = datetime.now(UTC) if promo['expires_at'] and datetime.fromisoformat(promo['expires_at']) < now:
expires_at = datetime.fromisoformat(promo['expires_at']).astimezone(UTC) if promo['expires_at'] else None return None
if (not expires_at or now < expires_at) and (promo['max_uses'] is None or promo['uses'] < promo['max_uses']): if promo['max_uses'] is not None and promo['uses'] >= promo['max_uses']:
promo['uses'] += 1 return None
with open(PROMOCODES_FILE, 'w') as f: promo['uses'] += 1
json.dump(promocodes, f) save_json(PROMOCODES_FILE, promocodes)
return promo['discount'] return {
except json.JSONDecodeError: 'discount': promo['discount'],
logger.error("Ошибка при разборе файла promocodes.json.") 'subscription_period': promo['subscription_period']
return 0 }
def remove_promocode(code):
if os.path.exists(PROMOCODES_FILE):
with open(PROMOCODES_FILE, 'r') as f:
try:
promocodes = json.load(f)
if code in promocodes:
del promocodes[code]
with open(PROMOCODES_FILE, 'w') as f:
json.dump(promocodes, f)
return True
except json.JSONDecodeError:
logger.error("Ошибка при разборе файла promocodes.json.")
return False
def get_promocodes(): def get_promocodes():
if os.path.exists(PROMOCODES_FILE): """Возвращает список всех промокодов."""
with open(PROMOCODES_FILE, 'r') as f: promocodes = load_json(PROMOCODES_FILE, {})
try: result = {}
return json.load(f) for code, info in promocodes.items():
except json.JSONDecodeError: result[code] = {
logger.error("Ошибка при разборе файла promocodes.json.") 'discount': info['discount'],
return {} 'expires_at': datetime.fromisoformat(info['expires_at']) if info['expires_at'] else None,
'max_uses': info['max_uses'],
'uses': info['uses'],
'subscription_period': info['subscription_period']
}
return result
def get_full_clients_table(): def remove_promocode(code):
setting = get_config() """Удаляет промокод."""
docker_container = setting['docker_container'] promocodes = load_json(PROMOCODES_FILE, {})
clients_table_path = '/opt/amnezia/awg/clientsTable' if code in promocodes:
try: del promocodes[code]
cmd = f"docker exec -i {docker_container} cat {clients_table_path}" save_json(PROMOCODES_FILE, promocodes)
call = subprocess.check_output(cmd, shell=True) return True
return json.loads(call.decode('utf-8')) return False
except subprocess.CalledProcessError as e:
logger.error(f"Ошибка при получении clientsTable: {e}") def add_payment(user_id, payment_id, amount, status, period=None):
return [] """Добавляет информацию о платеже."""
except json.JSONDecodeError: payments = load_json(PAYMENTS_FILE, {})
logger.error("Ошибка при разборе clientsTable JSON.") payments[payment_id] = {
return [] 'user_id': user_id,
'amount': amount,
'status': status,
'period': period,
'created_at': datetime.now(pytz.utc).isoformat()
}
save_json(PAYMENTS_FILE, payments)
def update_payment_status(payment_id, status):
"""Обновляет статус платежа."""
payments = load_json(PAYMENTS_FILE, {})
if payment_id in payments:
payments[payment_id]['status'] = status
save_json(PAYMENTS_FILE, payments)
return True
return False
def get_pending_payments():
"""Возвращает список незавершенных платежей."""
payments = load_json(PAYMENTS_FILE, {})
return [
(p['user_id'], payment_id, p['amount'], p['period'])
for payment_id, p in payments.items()
if p['status'] == 'pending'
]