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 = ''' Генератор ключей

Панель управления ключами

{% if message %}
{{ message }}
{% endif %}

Генерировать новые ключи

Список существующих ключей ({{ keys|length }})

''' # --- МАРШРУТЫ ПРИЛОЖЕНИЯ --- @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 '

Сервис ключей

Перейдите на /admin для управления ключами или на /keys для получения списка в JSON.

' if __name__ == '__main__': # При запуске приложения пытаемся загрузить актуальные ключи logging.info("Запуск приложения и первоначальная синхронизация ключей...") load_keys() app.run(debug=True, host='0.0.0.0', port=7860)