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


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
OBSIDIAN by LIFTIV — Minecraft Name Lab v3
Fast, reliable 3-char (and more) username availability checker.
Primary: official Mojang API (accurate, no CAPTCHA).
Optional: browser mode via mcchecker.net (manual CAPTCHA).
"""

from __future__ import annotations

import json
import random
import re
import threading
import time
import queue
import urllib.request
import urllib.error
from pathlib import Path
from datetime import datetime

import tkinter as tk
from tkinter import ttk, messagebox, filedialog

# Optional deps
try:
    from playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeoutError
except ImportError:
    sync_playwright = None
    PlaywrightTimeoutError = Exception

try:
    from pynput import keyboard as pynput_keyboard
except ImportError:
    pynput_keyboard = None

# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------
SITE = "https://mcchecker.net/"
MOJANG_API = "https://api.mojang.com/users/profiles/minecraft/{}"
ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789"
MAX_PER_RUN = 200
DEFAULT_DELAY_API = 0.35
DEFAULT_DELAY_BROWSER = 4.0
APP_DIR = Path(__file__).resolve().parent
SETTINGS_FILE = APP_DIR / "obsidian_settings.json"
USER_AGENT = "OBSIDIAN/3.0 (Minecraft Name Lab by LIFTIV)"

# Palette
BG = "#07080a"
PANEL = "#0e1014"
PANEL_2 = "#14171c"
EDGE = "#252930"
EDGE_SOFT = "#1c1f25"
TEXT = "#eef0f3"
MUTED = "#7a808c"
ACCENT = "#d8ff3e"
ACCENT_DIM = "#9bb82a"
RED = "#ff5c70"
AMBER = "#ffc15e"
GREEN = "#b8f55a"
CYAN = "#5ce1e6"

# ---------------------------------------------------------------------------
# App
# ---------------------------------------------------------------------------
class CheckerApp:
    def __init__(self, root: tk.Tk):
        self.root = root
        self.root.title("OBSIDIAN  ·  Minecraft Name Lab")
        self.root.geometry("1120x860")
        self.root.minsize(960, 720)
        self.root.configure(bg=BG)

        self.stop_event = threading.Event()
        self.pause_event = threading.Event()
        self.pause_event.set()
        self.worker: threading.Thread | None = None
        self.results: list[tuple[str, str]] = []  # (name, status)
        self.available_names: list[str] = []
        self.hotkey_listener = None
        self._pulse_job = None
        self._notify_queue: queue.Queue = queue.Queue()

        self.settings = self.load_settings()
        self._style()
        self._build()
        self.install_hotkeys()
        self.root.protocol("WM_DELETE_WINDOW", self.on_close)
        self.root.after(200, self._poll_notify_queue)

    # ------------------------------------------------------------------
    # Settings
    # ------------------------------------------------------------------
    def load_settings(self) -> dict:
        defaults = {
            "mode": "api",  # api | browser
            "delay_api": "0.4",
            "delay_browser": "4",
            "sound": True,
            "windows_notify": True,
            "notify_available": True,
            "captcha_pause": True,
            "keep_browser_open": True,
            "minimize_browser": True,
            "hide_search": False,
            "hotkey_pause": "F8",
            "hotkey_stop": "F9",
            "hotkey_generate": "F10",
            "gen_charset": "mixed",  # mixed | letters | digits
        }
        try:
            saved = json.loads(SETTINGS_FILE.read_text(encoding="utf-8"))
            defaults.update({k: v for k, v in saved.items() if k in defaults})
        except Exception:
            pass
        return defaults

    def save_settings(self):
        try:
            self.settings.update({
                "mode": self.mode_var.get(),
                "delay_api": self.delay_api_var.get(),
                "delay_browser": self.delay_browser_var.get(),
                "sound": self.sound_var.get(),
                "windows_notify": self.notify_var.get(),
                "notify_available": self.notify_avail_var.get(),
                "captcha_pause": self.captcha_var.get(),
                "keep_browser_open": self.keep_browser_var.get(),
                "minimize_browser": self.minimize_browser_var.get(),
                "hide_search": self.hide_search_var.get(),
                "hotkey_pause": self.pause_key_var.get(),
                "hotkey_stop": self.stop_key_var.get(),
                "hotkey_generate": self.generate_key_var.get(),
                "gen_charset": self.gen_charset_var.get(),
            })
            SETTINGS_FILE.write_text(json.dumps(self.settings, indent=2), encoding="utf-8")
        except Exception:
            pass

    # ------------------------------------------------------------------
    # Styling
    # ------------------------------------------------------------------
    def _style(self):
        s = ttk.Style()
        try:
            s.theme_use("clam")
        except Exception:
            pass

        s.configure(".", background=BG, foreground=TEXT, font=("Segoe UI", 10))
        s.configure("TFrame", background=BG)
        s.configure("Panel.TFrame", background=PANEL)
        s.configure("Panel2.TFrame", background=PANEL_2)
        s.configure("TLabel", background=BG, foreground=TEXT)
        s.configure("Muted.TLabel", background=BG, foreground=MUTED, font=("Segoe UI", 9))
        s.configure("Panel.TLabel", background=PANEL, foreground=TEXT)
        s.configure("PanelMuted.TLabel", background=PANEL, foreground=MUTED, font=("Segoe UI", 9))
        s.configure("Title.TLabel", background=BG, foreground=TEXT, font=("Segoe UI Semibold", 26))
        s.configure("Section.TLabel", background=PANEL, foreground=TEXT, font=("Segoe UI Semibold", 11))
        s.configure("Accent.TLabel", background=PANEL, foreground=ACCENT, font=("Segoe UI Semibold", 11))

        s.configure("TButton", font=("Segoe UI Semibold", 9), padding=(14, 10), borderwidth=0)
        s.configure("Accent.TButton", background=ACCENT, foreground="#0d0f08")
        s.map("Accent.TButton",
              background=[("active", "#e8ff7a"), ("disabled", "#3a3f2a")],
              foreground=[("disabled", "#6a6e55")])
        s.configure("Ghost.TButton", background=PANEL_2, foreground=TEXT)
        s.map("Ghost.TButton", background=[("active", EDGE), ("disabled", PANEL)])
        s.configure("Stop.TButton", background="#2a1418", foreground=RED)
        s.map("Stop.TButton", background=[("active", "#3d1c22")])
        s.configure("Small.TButton", padding=(8, 5), font=("Segoe UI", 8))

        s.configure("TCheckbutton", background=PANEL, foreground="#d0d3d9", padding=5)
        s.map("TCheckbutton", background=[("active", PANEL)])
        s.configure("TRadiobutton", background=PANEL, foreground="#d0d3d9", padding=4)
        s.map("TRadiobutton", background=[("active", PANEL)])

        s.configure("Horizontal.TProgressbar",
                    troughcolor=EDGE, background=ACCENT, bordercolor=EDGE,
                    lightcolor=ACCENT, darkcolor=ACCENT, thickness=8)
        s.configure("Horizontal.TScale", background=PANEL, troughcolor=EDGE)
        s.configure("TEntry", fieldbackground="#0a0c10", foreground=TEXT,
                    insertcolor=ACCENT, bordercolor=EDGE, padding=8)
        s.configure("TCombobox", fieldbackground="#0a0c10", foreground=TEXT,
                    background=PANEL, arrowcolor=MUTED)

    def panel(self, parent, **kwargs):
        return ttk.Frame(parent, style="Panel.TFrame", padding=kwargs.get("padding", 16))