Загрузка данных


import os
import secrets
import string
from datetime import datetime

MIN_LEN = 6      # Минимальная длина пароля
MAX_LEN = 128    # Максимальная длина пароля
DEFAULT_LEN = 16 # Длина по умолчанию
HISTORY_FILE = "password_history.txt"  # Файл истории
HISTORY_SIZE = 5 # Максимум записей истории

SPECIALS = "!@#$%^&*()-_=+[]{}|;:,.<>?"  # Спецсимволы


def ask_yes_no(prompt, default="y"):
    """Запрос ответа y/n. Возвращает bool."""
    val = input(prompt).strip().lower()
    if not val:
        val = default
    return val == "y"


def ask_length():
    """Запрашивает корректную длину пароля."""
    while True:
        val = input(f"Длина пароля [{MIN_LEN}–{MAX_LEN}, по умолчанию {DEFAULT_LEN}]: ").strip()
        if not val:
            return DEFAULT_LEN
        try:
            num = int(val)
            if MIN_LEN <= num <= MAX_LEN:
                return num
            print(f"[ОШИБКА] Длина должна быть от {MIN_LEN} до {MAX_LEN}.")
        except ValueError:
            print("[ОШИБКА] Введите целое число.")


def select_sets():
    """Выбор наборов символов."""
    while True:
        sets = []
        enabled = []
        if ask_yes_no("Включить строчные буквы? (y/n) [y]: "):
            sets.append(string.ascii_lowercase)
            enabled.append("lower")
        if ask_yes_no("Включить прописные буквы? (y/n) [y]: "):
            sets.append(string.ascii_uppercase)
            enabled.append("upper")
        if ask_yes_no("Включить цифры? (y/n) [y]: "):
            sets.append(string.digits)
            enabled.append("digits")
        if ask_yes_no("Включить спецсимволы? (y/n) [y]: "):
            sets.append(SPECIALS)
            enabled.append("special")
        if sets:
            return sets, enabled
        print("[ОШИБКА] Необходимо выбрать хотя бы один набор символов")


def generate_password(length, sets):
    """Генерирует пароль с гарантией символа из каждого набора."""
    chars = [secrets.choice(s) for s in sets]  # гарантия включения
    pool = "".join(sets)
    chars.extend(secrets.choice(pool) for _ in range(length - len(chars)))
    secrets.SystemRandom().shuffle(chars)  # перемешивание
    return "".join(chars)


def copy_to_clipboard(password):
    """Копирует пароль в буфер обмена."""
    try:
        import tkinter as tk
        root = tk.Tk()
        root.withdraw()
        root.clipboard_clear()
        root.clipboard_append(password)
        root.update()
        root.destroy()
        print("[INFO] Пароль скопирован в буфер обмена.")
    except Exception:
        print("[INFO] Копирование недоступно: модуль tkinter не найден")


def save_history(password, length, enabled):
    """Сохраняет пароль в историю с ротацией до 5 записей."""
    try:
        lines = []
        if os.path.exists(HISTORY_FILE):
            with open(HISTORY_FILE, "r", encoding="utf-8") as f:
                lines = [x.rstrip("\n") for x in f]
        record = (
            f"{datetime.now():%Y-%m-%d %H:%M:%S} | длина: {length} | "
            f"наборы: {enabled} | пароль: {password}"
        )
        lines.append(record)
        lines = lines[-HISTORY_SIZE:]  # ротация истории
        with open(HISTORY_FILE, "w", encoding="utf-8") as f:
            f.write("\n".join(lines))
        print(f"[INFO] Пароль сохранён в {HISTORY_FILE}.")
    except Exception as e:
        print(f"[ОШИБКА] Не удалось сохранить файл: {e}")


def main():
    """Основной цикл программы."""
    print("Генератор безопасных паролей")
    print("=" * 29)
    while True:
        length = ask_length()
        sets, enabled = select_sets()
        password = generate_password(length, sets)

        print("\nВаш пароль:")
        print("-" * 29)
        print(password)
        print("-" * 29)

        if ask_yes_no("Скопировать в буфер обмена? (y/n) [y]: "):
            copy_to_clipboard(password)

        if ask_yes_no("Сохранить в историю? (y/n) [n]: ", "n"):
            save_history(password, length, enabled)

        if not ask_yes_no("Сгенерировать ещё один пароль? (y/n) [n]: ", "n"):
            print("До свидания!")
            break


if __name__ == "__main__":
    main()