Keyss / app.py
Kgshop's picture
Update app.py
e02387e verified
from flask import Flask, render_template_string, request, jsonify
import json
import os
import logging
import uuid
from datetime import datetime
from huggingface_hub import HfApi, hf_hub_download
from huggingface_hub.utils import HfHubHTTPError
# --- КОНФИГУРАЦИЯ ---
app = Flask(__name__)
# Установите секретный ключ для безопасности, если планируете использовать сессии
app.secret_key = os.getenv("FLASK_SECRET_KEY", "zzirix_secret_key_for_keys")
# Имя файла для хранения ключей
DATA_FILE = 'keys.json'
# ID репозитория на Hugging Face для хранения ключей
REPO_ID = "Kgshop/Keyspub"
# Токены доступа к Hugging Face (ВАЖНО: установите их как переменные окружения)
HF_TOKEN_WRITE = os.getenv("HF_TOKEN")
HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") or HF_TOKEN_WRITE
# Настройка логирования
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
# --- ФУНКЦИИ ДЛЯ РАБОТЫ С ДАННЫМИ И HUGGING FACE ---
def load_keys():
"""Загружает список ключей с Hugging Face или из локального файла."""
try:
# Сначала пытаемся скачать последнюю версию с HF
download_keys_from_hf()
if os.path.exists(DATA_FILE) and os.path.getsize(DATA_FILE) > 0:
with open(DATA_FILE, 'r', encoding='utf-8') as f:
data = json.load(f)
# Убеждаемся, что структура корректна
if isinstance(data, dict) and 'keys' in data and isinstance(data['keys'], list):
logging.info(f"Загружено {len(data['keys'])} ключей из файла.")
return data['keys']
else:
logging.warning("Файл с ключами имеет неверный формат. Возвращается пустой список.")
return []
else:
logging.info("Файл с ключами не найден или пуст. Возвращается пустой список.")
return []
except Exception as e:
logging.error(f"Ошибка при загрузке ключей: {e}. Возвращается пустой список.")
return []
def save_keys(keys_list):
"""Сохраняет список ключей локально и выгружает на Hugging Face."""
try:
# Создаем словарь для сохранения в JSON
data_to_save = {'keys': sorted(list(set(keys_list)))} # Сортируем и удаляем дубликаты
temp_file = DATA_FILE + '.tmp'
with open(temp_file, 'w', encoding='utf-8') as f:
json.dump(data_to_save, f, ensure_ascii=False, indent=4)
os.replace(temp_file, DATA_FILE)
logging.info(f"Сохранено {len(data_to_save['keys'])} ключей. Попытка выгрузки в HF.")
upload_keys_to_hf()
except Exception as e:
logging.error(f"Ошибка при сохранении ключей: {e}")
if os.path.exists(temp_file):
os.remove(temp_file)
raise
def upload_keys_to_hf():
"""Выгружает файл с ключами в репозиторий Hugging Face."""
if not HF_TOKEN_WRITE:
logging.warning("HF_TOKEN_WRITE не установлен. Пропуск выгрузки ключей в HF.")
return
if not os.path.exists(DATA_FILE):
logging.warning(f"Файл {DATA_FILE} для выгрузки не найден.")
return
try:
api = HfApi()
api.upload_file(
path_or_fileobj=DATA_FILE,
path_in_repo=DATA_FILE,
repo_id=REPO_ID,
repo_type="dataset",
token=HF_TOKEN_WRITE,
commit_message=f"Обновление списка ключей {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
)
logging.info("Файл с ключами успешно выгружен на Hugging Face.")
except Exception as e:
logging.error(f"Ошибка при выгрузке ключей на Hugging Face: {e}")
def download_keys_from_hf():
"""Загружает файл с ключами из репозитория Hugging Face."""
if not HF_TOKEN_READ:
logging.warning("HF_TOKEN_READ не установлен. Пропуск загрузки ключей с HF.")
return
try:
hf_hub_download(
repo_id=REPO_ID,
filename=DATA_FILE,
repo_type="dataset",
token=HF_TOKEN_READ,
local_dir=".",
local_dir_use_symlinks=False,
force_download=True
)
logging.info("Файл с ключами успешно загружен с Hugging Face.")
except HfHubHTTPError as e:
# Если файл не найден (ошибка 404), это не критично, особенно при первом запуске
if e.response.status_code == 404:
logging.info(f"Файл {DATA_FILE} не найден в репозитории {REPO_ID}. Будет создан новый.")
else:
logging.error(f"HTTP ошибка при загрузке ключей с Hugging Face: {e}")
except Exception as e:
logging.error(f"Неизвестная ошибка при загрузке ключей с Hugging Face: {e}")
def generate_key():
"""Генерирует уникальный лицензионный ключ в формате KEY-XXXX-XXXX-XXXX."""
parts = str(uuid.uuid4()).upper().split('-')
return f"KEY-{parts[1]}-{parts[2]}-{parts[3]}"
# --- HTML И СТИЛИ ДЛЯ АДМИН-ПАНЕЛИ ---
ADMIN_TEMPLATE = '''
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Генератор ключей</title>
<style>
:root {
--background-color: #f0f2f5;
--card-background: #ffffff;
--text-color: #1f2937;
--primary-color: #3B82F6;
--primary-dark-color: #2563eb;
--border-color: #e5e7eb;
--shadow: 0 4px 6px rgba(0,0,0,0.1);
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background-color: var(--background-color);
color: var(--text-color);
margin: 0;
padding: 20px;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.container {
max-width: 800px;
width: 100%;
background: var(--card-background);
padding: 30px;
border-radius: 12px;
box-shadow: var(--shadow);
}
h1 {
font-size: 2rem;
text-align: center;
margin-bottom: 25px;
color: var(--primary-color);
}
h2 {
font-size: 1.5rem;
margin-top: 30px;
border-bottom: 2px solid var(--border-color);
padding-bottom: 10px;
}
form {
display: flex;
gap: 15px;
align-items: center;
margin-bottom: 20px;
}
label {
font-weight: 500;
}
input[type="number"] {
padding: 10px;
border-radius: 8px;
border: 1px solid var(--border-color);
font-size: 1rem;
width: 80px;
text-align: center;
}
button {
padding: 10px 20px;
border: none;
border-radius: 8px;
background-color: var(--primary-color);
color: white;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s;
}
button:hover {
background-color: var(--primary-dark-color);
}
.keys-list {
margin-top: 20px;
}
textarea {
width: 100%;
height: 300px;
padding: 10px;
border-radius: 8px;
border: 1px solid var(--border-color);
font-family: "Courier New", Courier, monospace;
font-size: 0.9rem;
resize: vertical;
}
.message {
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
font-weight: 500;
}
.message.success {
background-color: #d1fae5;
color: #065f46;
}
.message.error {
background-color: #fee2e2;
color: #991b1b;
}
.key-count {
font-weight: bold;
color: var(--primary-color);
}
</style>
</head>
<body>
<div class="container">
<h1>Панель управления ключами</h1>
{% if message %}
<div class="message {{ message_category }}">{{ message }}</div>
{% endif %}
<h2>Генерировать новые ключи</h2>
<form method="POST">
<label for="num_keys">Количество:</label>
<input type="number" id="num_keys" name="num_keys" min="1" max="1000" value="1" required>
<button type="submit">Сгенерировать</button>
</form>
<h2>Список существующих ключей (<span class="key-count">{{ keys|length }}</span>)</h2>
<div class="keys-list">
<textarea readonly>{{ '\\n'.join(keys) }}</textarea>
</div>
</div>
</body>
</html>
'''
# --- МАРШРУТЫ ПРИЛОЖЕНИЯ ---
@app.route('/admin', methods=['GET', 'POST'])
def admin_panel():
"""Основная страница для генерации и просмотра ключей."""
message = None
message_category = None
if request.method == 'POST':
try:
num_to_generate = request.form.get('num_keys', '1', type=int)
if not 1 <= num_to_generate <= 1000:
raise ValueError("Количество ключей должно быть от 1 до 1000.")
current_keys = load_keys()
new_keys = [generate_key() for _ in range(num_to_generate)]
# Добавляем новые ключи к существующим
all_keys = current_keys + new_keys
save_keys(all_keys)
message = f"Успешно сгенерировано и сохранено {num_to_generate} новых ключей."
message_category = 'success'
except Exception as e:
logging.error(f"Ошибка при генерации ключей: {e}")
message = f"Произошла ошибка: {e}"
message_category = 'error'
# Загружаем актуальный список ключей для отображения
keys_list = load_keys()
return render_template_string(
ADMIN_TEMPLATE,
keys=keys_list,
message=message,
message_category=message_category
)
@app.route('/keys')
def get_keys_json():
"""Возвращает список всех ключей в формате JSON."""
keys_list = load_keys()
return jsonify(keys_list)
@app.route('/')
def index():
"""Перенаправляет на админ-панель."""
# Для удобства можно сделать редирект на /admin
# from flask import redirect, url_for
# return redirect(url_for('admin_panel'))
return '<h1>Сервис ключей</h1><p>Перейдите на <a href="/admin">/admin</a> для управления ключами или на <a href="/keys">/keys</a> для получения списка в JSON.</p>'
if __name__ == '__main__':
# При запуске приложения пытаемся загрузить актуальные ключи
logging.info("Запуск приложения и первоначальная синхронизация ключей...")
load_keys()
app.run(debug=True, host='0.0.0.0', port=7860)