Этот коммит содержится в:
stevefoxru
2025-04-24 14:29:00 +03:00
коммит произвёл GitHub
родитель eac63031e9
Коммит ba056994a1
+208 -574
Просмотреть файл
@@ -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 output:
return output
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:
logger.error(f"Ошибка при обновлении комментариев в конфигурации WireGuard: {e}")
def get_config(path=CONFIG_FILE):
if not os.path.exists(path):
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): if os.path.exists(file_path):
with open(file_path, 'r') as f: with open(file_path, 'r') as f:
return json.load(f)
except Exception as e:
logger.error(f"Ошибка загрузки {file_path}: {str(e)}")
return default if default is not None else {}
def save_json(file_path, data):
"""Сохраняет данные в JSON-файл."""
try: try:
data = json.load(f) os.makedirs(os.path.dirname(file_path), exist_ok=True)
except json.JSONDecodeError:
data = {}
else:
data = {}
data[ip_address] = timestamp
with open(file_path, 'w') as f: with open(file_path, 'w') as f:
json.dump(data, f) json.dump(data, f, indent=4, default=str)
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 True
except Exception as e:
logger.error(f"Ошибка сохранения {file_path}: {str(e)}")
return False return False
def get_clients_from_clients_table(): def get_config():
setting = get_config() """Возвращает конфигурацию из config.json."""
docker_container = setting['docker_container'] return load_json(CONFIG_FILE, {})
clients_table_path = '/opt/amnezia/awg/clientsTable'
try:
cmd = f"docker exec -i {docker_container} cat {clients_table_path}"
call = subprocess.check_output(cmd, shell=True)
clients_table = json.loads(call.decode('utf-8'))
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
except subprocess.CalledProcessError as e:
logger.error(f"Ошибка при удалении пользователя {username}: {e}")
return False
def save_admins(admin_ids):
os.makedirs(os.path.dirname(ADMINS_FILE), exist_ok=True)
with open(ADMINS_FILE, 'w') as f:
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()
if token:
config['yoomoney_token'] = token
if wallet:
config['yoomoney_wallet'] = wallet
save_json(CONFIG_FILE, config)
def set_pricing(period, price):
"""Устанавливает цену для указанного периода подписки."""
config = get_config()
config['pricing'] = config.get('pricing', {})
config['pricing'][period] = price
save_json(CONFIG_FILE, config)
def root_add(name, ipv6=False):
"""Добавляет нового пользователя через newclient.sh."""
try: try:
return json.load(f) cmd = ['./newclient.sh', name]
except json.JSONDecodeError: if not ipv6:
logger.error("Ошибка при разборе файла admins.json.") cmd.append('--no-ipv6')
return [] process = subprocess.run(cmd, capture_output=True, text=True)
return [] 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
def deactive_user_db(name):
"""Деактивирует пользователя через removeclient.sh."""
try:
process = subprocess.run(['./removeclient.sh', name], 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
def get_client_list():
"""Возвращает список клиентов (имя и конфигурация)."""
clients = []
users_dir = 'users'
if os.path.exists(users_dir):
for user_dir in os.listdir(users_dir):
user_path = os.path.join(users_dir, user_dir)
if os.path.isdir(user_path):
conf_file = os.path.join(user_path, f"{user_dir}.conf")
if os.path.exists(conf_file):
with open(conf_file, 'r') as f:
config = f.read()
clients.append((user_dir, config))
return clients
def get_active_list():
"""Возвращает список активных клиентов с последним handshake."""
active = []
users_dir = 'users'
if os.path.exists(users_dir):
for user_dir in os.listdir(users_dir):
user_path = os.path.join(users_dir, user_dir)
if os.path.isdir(user_path):
status_file = os.path.join(user_path, 'status.json')
if os.path.exists(status_file):
with open(status_file, 'r') as f:
status = json.load(f)
last_handshake = status.get('last_handshake', 'never')
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
}
save_json(USER_EXPIRATION_FILE, data)
def get_user_expiration(username): def get_user_expiration(username):
if os.path.exists(EXPIRATIONS_FILE): """Получает срок действия подписки пользователя."""
with open(EXPIRATIONS_FILE, 'r') as f: data = load_json(USER_EXPIRATION_FILE, {})
try: user_data = data.get(username, {})
expirations = json.load(f) expiration = user_data.get('expiration')
expiration_str = expirations.get(username) return datetime.fromisoformat(expiration) if expiration else None
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):
os.makedirs(os.path.dirname(EXPIRATIONS_FILE), exist_ok=True)
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): def remove_user_expiration(username):
if os.path.exists(EXPIRATIONS_FILE): """Удаляет информацию о сроке действия подписки пользователя."""
with open(EXPIRATIONS_FILE, 'r') as f: data = load_json(USER_EXPIRATION_FILE, {})
try: if username in data:
expirations = json.load(f) del data[username]
if username in expirations: save_json(USER_EXPIRATION_FILE, data)
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): def set_user_telegram_id(username, telegram_id):
os.makedirs(os.path.dirname(USER_TELEGRAM_IDS_FILE), exist_ok=True) """Связывает имя пользователя с Telegram ID."""
telegram_ids = {} data = load_json(USER_TELEGRAM_FILE, {})
if os.path.exists(USER_TELEGRAM_IDS_FILE): data[username] = telegram_id
with open(USER_TELEGRAM_IDS_FILE, 'r') as f: save_json(USER_TELEGRAM_FILE, data)
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): def get_user_telegram_id(username):
if os.path.exists(USER_TELEGRAM_IDS_FILE): """Получает Telegram ID пользователя по имени."""
with open(USER_TELEGRAM_IDS_FILE, 'r') as f: data = load_json(USER_TELEGRAM_FILE, {})
try: return data.get(username)
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): def add_promocode(code, discount, expires_at, max_uses, subscription_period):
try: """Добавляет новый промокод."""
before = datetime.fromisoformat(before_date).astimezone(UTC) promocodes = load_json(PROMOCODES_FILE, {})
except ValueError:
logger.error(f"Неверный формат даты: {before_date}")
return False
clients = get_client_list()
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.")
removed = False
for client in clients:
username = client[0]
expiration_str = expirations.get(username)
if expiration_str:
try:
expiration = datetime.fromisoformat(expiration_str).astimezone(UTC)
if expiration < before:
if deactive_user_db(username):
remove_user_expiration(username)
user_path = os.path.join('users', username)
if os.path.exists(user_path):
import shutil
shutil.rmtree(user_path)
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):
os.makedirs(os.path.dirname(PAYMENTS_FILE), exist_ok=True)
payments = {}
if os.path.exists(PAYMENTS_FILE):
with open(PAYMENTS_FILE, 'r') as f:
try:
payments = json.load(f)
except json.JSONDecodeError:
logger.error("Ошибка при разборе файла payments.json.")
payments[payment_id] = {
'user_id': user_id,
'amount': amount,
'status': status,
'created_at': datetime.now(UTC).isoformat()
}
with open(PAYMENTS_FILE, 'w') as f:
json.dump(payments, f)
def update_payment_status(payment_id, status):
if os.path.exists(PAYMENTS_FILE):
with open(PAYMENTS_FILE, 'r') as f:
try:
payments = json.load(f)
if payment_id in payments:
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():
if os.path.exists(PAYMENTS_FILE):
with open(PAYMENTS_FILE, 'r') as f:
try:
payments = json.load(f)
return [
(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):
os.makedirs(os.path.dirname(PROMOCODES_FILE), exist_ok=True)
promocodes = {}
if os.path.exists(PROMOCODES_FILE):
with open(PROMOCODES_FILE, 'r') as f:
try:
promocodes = json.load(f)
except json.JSONDecodeError:
logger.error("Ошибка при разборе файла promocodes.json.")
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']:
return None
promo['uses'] += 1 promo['uses'] += 1
with open(PROMOCODES_FILE, 'w') as f: save_json(PROMOCODES_FILE, promocodes)
json.dump(promocodes, f) return {
return promo['discount'] 'discount': promo['discount'],
except json.JSONDecodeError: 'subscription_period': promo['subscription_period']
logger.error("Ошибка при разборе файла promocodes.json.") }
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'
]