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


#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
STALCRAFT Auction Bot - Полностью рабочая версия
"""

import sys
import os
import json
import time
import threading
import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext
from datetime import datetime

# Проверка зависимостей
def install_package(package):
    import subprocess
    subprocess.check_call([sys.executable, "-m", "pip", "install", package])

try:
    import pyautogui
    import pytesseract
    from PIL import ImageGrab, Image
    import keyboard
except ImportError as e:
    print(f"Устанавливаю зависимости...")
    install_package("pyautogui")
    install_package("pytesseract")
    install_package("Pillow")
    install_package("keyboard")
    print("Зависимости установлены. Перезапустите программу.")
    sys.exit(1)

# Настройка Tesseract
def setup_tesseract():
    """Настройка пути к Tesseract"""
    possible_paths = [
        r'C:\Program Files\Tesseract-OCR\tesseract.exe',
        r'C:\Program Files (x86)\Tesseract-OCR\tesseract.exe',
        r'C:\Users\Administrator\AppData\Local\Programs\Tesseract-OCR\tesseract.exe',
    ]
    
    for path in possible_paths:
        if os.path.exists(path):
            pytesseract.pytesseract.tesseract_cmd = path
            return True
    
    # Проверка в PATH
    try:
        pytesseract.get_tesseract_version()
        return True
    except:
        pass
    
    return False

# Настройка Tesseract при запуске
TESSERACT_FOUND = setup_tesseract()
if not TESSERACT_FOUND:
    print("Tesseract не найден! Попробуйте установить вручную.")

class Logger:
    """Класс для логирования в GUI"""
    def __init__(self, text_widget):
        self.text_widget = text_widget
        self.text_widget.config(state='disabled')
    
    def log(self, message, level="INFO"):
        timestamp = datetime.now().strftime("%H:%M:%S")
        colors = {
            "INFO": "#00AA00",
            "WARNING": "#FFAA00", 
            "ERROR": "#FF0000",
            "SUCCESS": "#0066FF"
        }
        
        self.text_widget.config(state='normal')
        self.text_widget.insert(tk.END, f"[{timestamp}] ", "timestamp")
        self.text_widget.insert(tk.END, f"{level}: ", level)
        self.text_widget.insert(tk.END, f"{message}\n", "message")
        self.text_widget.see(tk.END)
        self.text_widget.config(state='disabled')
        
        # Также в консоль (для отладки)
        print(f"[{timestamp}] {level}: {message}")

class Config:
    def __init__(self):
        self.search_text = ""
        self.max_price = 100000
        self.pages_count = 3
        self.use_pages = True
        self.test_mode = True
        self.auto_buy = False
        self.scroll_position = 0  # Запоминаем позицию ползунка
        
        # Координаты
        self.coords = {
            'input': {'x': 0, 'y': 0},
            'refresh': {'x': 0, 'y': 0},
            'slider': {'x': 0, 'y': 0},
            'ok': {'x': 0, 'y': 0},
            'start': {'x': 0, 'y': 0},  # Добавляем кнопку запуска
            'stop': {'x': 0, 'y': 0}    # Добавляем кнопку остановки
        }
        
        # OCR области
        self.ocr_areas = {
            'prices': {'x1': 0, 'y1': 0, 'x2': 0, 'y2': 0},
            'buy_btn': {'x1': 0, 'y1': 0, 'x2': 0, 'y2': 0},
            'pages': {'x1': 0, 'y1': 0, 'x2': 0, 'y2': 0}
        }
        
        self.ocr_lang = 'rus'
        self.ocr_scale = 200
        self.ocr_attempts = 3
        self.confidence_threshold = 70
        
    def save(self, filename='config.json'):
        try:
            with open(filename, 'w', encoding='utf-8') as f:
                json.dump(self.__dict__, f, indent=4, ensure_ascii=False)
            return True
        except Exception as e:
            print(f"Ошибка сохранения: {e}")
            return False
            
    def load(self, filename='config.json'):
        try:
            if os.path.exists(filename):
                with open(filename, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                    for key, value in data.items():
                        if hasattr(self, key):
                            setattr(self, key, value)
                return True
        except Exception as e:
            print(f"Ошибка загрузки: {e}")
        return False

class CoordinatePicker:
    """Класс для выбора координат и областей на экране"""
    
    def __init__(self, root):
        self.root = root
        self.result = None
        self.running = False
        self.start_x = 0
        self.start_y = 0
        
    def pick_coordinate(self, callback):
        """Выбор одной координаты кликом"""
        self.root.iconify()
        time.sleep(0.5)
        
        picker = tk.Toplevel(self.root)
        picker.attributes('-fullscreen', True)
        picker.attributes('-alpha', 0.3)
        picker.configure(bg='black')
        picker.focus_set()
        
        label = tk.Label(picker, text="КЛИКНИТЕ ЛЕВОЙ КНОПКОЙ МЫШИ\nв нужном месте для установки координаты",
                        font=('Arial', 24, 'bold'),
                        fg='white', bg='black',
                        wraplength=800)
        label.place(relx=0.5, rely=0.1, anchor='center')
        
        result = {'x': 0, 'y': 0}
        
        def on_click(event):
            result['x'] = event.x_root
            result['y'] = event.y_root
            picker.destroy()
            self.root.deiconify()
            callback(result['x'], result['y'])
        
        def on_key(event):
            if event.keysym == 'Escape':
                picker.destroy()
                self.root.deiconify()
                callback(None, None)
        
        picker.bind('<Button-1>', on_click)
        picker.bind('<Key>', on_key)
        
        self.root.wait_window(picker)
    
    def pick_area(self, callback):
        """Выбор области двумя кликами"""
        self.root.iconify()
        time.sleep(0.5)
        
        picker = tk.Toplevel(self.root)
        picker.attributes('-fullscreen', True)
        picker.attributes('-alpha', 0.2)
        picker.configure(bg='black')
        picker.focus_set()
        
        label = tk.Label(picker, 
                        text="КЛИКНИТЕ ЛЕВОЙ КНОПКОЙ МЫШИ\nв ЛЕВОМ ВЕРХНЕМ УГЛУ области\n\n"
                             "Затем кликните в ПРАВОМ НИЖНЕМ УГЛУ области\n\n"
                             "Нажмите ESC для отмены",
                        font=('Arial', 20, 'bold'),
                        fg='white', bg='black',
                        wraplength=800)
        label.place(relx=0.5, rely=0.1, anchor='center')
        
        canvas = tk.Canvas(picker, bg='white', highlightthickness=0)
        canvas.place(relx=0, rely=0, relwidth=1, relheight=1)
        
        points = []
        rect = None
        
        def on_click(event):
            nonlocal rect, points
            
            x, y = event.x_root, event.y_root
            points.append((x, y))
            
            if len(points) == 2:
                x1, y1 = points[0]
                x2, y2 = points[1]
                
                if rect:
                    canvas.delete(rect)
                
                result = {
                    'x1': min(x1, x2),
                    'y1': min(y1, y2),
                    'x2': max(x1, x2),
                    'y2': max(y1, y2)
                }
                
                picker.destroy()
                self.root.deiconify()
                callback(result)
                
            else:
                if rect:
                    canvas.delete(rect)
                rect = canvas.create_rectangle(x-5, y-5, x+5, y+5, outline='red', width=3)
        
        def on_motion(event):
            nonlocal rect
            if len(points) == 1:
                if rect:
                    canvas.delete(rect)
                x1, y1 = points[0]
                x2, y2 = event.x_root, event.y_root
                rect = canvas.create_rectangle(x1, y1, x2, y2, outline='red', width=2, fill='red', stipple='gray25')
        
        def on_key(event):
            if event.keysym == 'Escape':
                picker.destroy()
                self.root.deiconify()
                callback(None)
        
        picker.bind('<Button-1>', on_click)
        picker.bind('<Motion>', on_motion)
        picker.bind('<Key>', on_key)
        
        self.root.wait_window(picker)

class OCRSimple:
    def __init__(self, config, logger):
        self.config = config
        self.logger = logger
        
        # Проверка Tesseract
        if not TESSERACT_FOUND:
            self.logger.log("Tesseract не найден! OCR не будет работать.", "ERROR")
    
    def extract_prices(self, image):
        """Извлечение цен из изображения"""
        if not TESSERACT_FOUND:
            return []
            
        try:
            if self.config.ocr_scale != 100:
                scale = self.config.ocr_scale / 100
                width = int(image.width * scale)
                height = int(image.height * scale)
                image = image.resize((width, height), Image.Resampling.LANCZOS)
            
            custom_config = f'--psm 6 -c tessedit_char_whitelist=0123456789 '
            
            data = pytesseract.image_to_data(
                image,
                lang=self.config.ocr_lang,
                config=custom_config,
                output_type=pytesseract.Output.DICT
            )
            
            prices = []
            for i, text in enumerate(data['text']):
                text = text.strip()
                if text:
                    try:
                        conf = int(data['conf'][i])
                        if conf >= self.config.confidence_threshold:
                            clean = ''.join(filter(str.isdigit, text))
                            if clean and len(clean) >= 4:
                                price = int(clean)
                                if 1000 <= price <= 10000000:
                                    prices.append({
                                        'price': price,
                                        'confidence': conf,
                                        'bbox': (
                                            data['left'][i],
                                            data['top'][i],
                                            data['width'][i],
                                            data['height'][i]
                                        )
                                    })
                    except:
                        continue
            
            return prices
        except Exception as e:
            self.logger.log(f"OCR ошибка: {e}", "ERROR")
            return []
    
    def find_text(self, image, keywords):
        """Поиск текста в изображении"""
        if not TESSERACT_FOUND:
            return None
            
        try:
            data = pytesseract.image_to_data(
                image,
                lang=self.config.ocr_lang,
                output_type=pytesseract.Output.DICT
            )
            
            for i, text in enumerate(data['text']):
                text_lower = text.lower().strip()
                for keyword in keywords:
                    if keyword in text_lower:
                        return {
                            'text': text,
                            'bbox': (
                                data['left'][i],
                                data['top'][i],
                                data['width'][i],
                                data['height'][i]
                            )
                        }
            return None
        except Exception as e:
            self.logger.log(f"Ошибка поиска текста: {e}", "ERROR")
            return None

class AuctionBot:
    def __init__(self, config, logger):
        self.config = config
        self.logger = logger
        self.ocr = OCRSimple(config, logger)
        self.running = False
        self.purchases = 0
        self.spent = 0
        self.status = "Готов"
        self.scroll_position = config.scroll_position  # Восстанавливаем позицию
        
        pyautogui.FAILSAFE = True
        pyautogui.PAUSE = 0.2
    
    def start(self):
        if self.running:
            return
        
        if not self.config.search_text:
            self.logger.log("Ошибка: не указан товар", "ERROR")
            self.status = "Ошибка: не указан товар"
            return
        
        # Проверка координат
        for key, coord in self.config.coords.items():
            if coord['x'] == 0 and coord['y'] == 0:
                self.logger.log(f"Ошибка: не настроена координата {key}", "ERROR")
                self.status = f"Ошибка: не настроена координата {key}"
                return
        
        # Проверка OCR областей
        for key, area in self.config.ocr_areas.items():
            if area['x1'] == area['x2'] or area['y1'] == area['y2']:
                self.logger.log(f"Ошибка: не настроена область {key}", "ERROR")
                self.status = f"Ошибка: не настроена область {key}"
                return
        
        self.running = True
        self.status = "Запущен"
        self.logger.log("Бот запущен", "SUCCESS")
        
        thread = threading.Thread(target=self._run)
        thread.daemon = True
        thread.start()
    
    def stop(self):
        self.running = False
        self.status = "Остановлен"
        self.config.scroll_position = self.scroll_position  # Сохраняем позицию
        self.logger.log("Бот остановлен", "WARNING")
    
    def _run(self):
        while self.running:
            try:
                self.status = "Ввод товара..."
                if not self._input_text():
                    time.sleep(2)
                    continue
                
                self.status = "Обновление списка..."
                if not self._refresh():
                    time.sleep(2)
                    continue
                
                self.status = "Поиск лотов..."
                if self._search_lots():
                    continue
                
                time.sleep(1)
                
            except Exception as e:
                self.logger.log(f"Ошибка: {e}", "ERROR")
                self.status = f"Ошибка: {str(e)[:30]}"
                time.sleep(3)
    
    def _input_text(self):
        try:
            coords = self.config.coords['input']
            pyautogui.click(coords['x'], coords['y'])
            time.sleep(0.3)
            
            pyautogui.hotkey('ctrl', 'a')
            time.sleep(0.1)
            pyautogui.press('delete')
            time.sleep(0.1)
            
            pyautogui.write(self.config.search_text)
            time.sleep(0.2)
            return True
        except Exception as e:
            self.logger.log(f"Ошибка ввода текста: {e}", "ERROR")
            return False
    
    def _refresh(self):
        try:
            coords = self.config.coords['refresh']
            pyautogui.click(coords['x'], coords['y'])
            time.sleep(2)
            self.scroll_position = 0  # Сброс позиции после обновления
            return True
        except Exception as e:
            self.logger.log(f"Ошибка обновления: {e}", "ERROR")
            return False
    
    def _search_lots(self):
        if self._check_page():
            return True
        
        # Прокрутка с запоминанием позиции
        for step in range(80, 401, 80):
            if not self.running:
                return False
            
            self._scroll(step)
            if self._check_page():
                return True
        
        if self.config.use_pages:
            for page in range(2, self.config.pages_count + 1):
                if not self.running:
                    return False
                
                if self._switch_page(page):
                    if self._check_page():
                        return True
        
        return False
    
    def _check_page(self):
        try:
            area = self.config.ocr_areas['prices']
            screenshot = ImageGrab.grab(bbox=(
                area['x1'], area['y1'],
                area['x2'], area['y2']
            ))
            
            prices = self.ocr.extract_prices(screenshot)
            
            if not prices:
                return False
            
            valid = [p for p in prices if p['price'] <= self.config.max_price]
            
            if not valid:
                return False
            
            best = min(valid, key=lambda x: x['price'])
            
            self.status = f"Найден лот: {best['price']:,}"
            self.logger.log(f"Найден лот за {best['price']:,} (уверенность: {best['confidence']}%)", "INFO")
            
            if self.config.test_mode:
                self.logger.log("ТЕСТОВЫЙ РЕЖИМ: покупка не совершается", "WARNING")
                return False
            
            if self.config.auto_buy:
                return self._buy(best)
            
            return False
            
        except Exception as e:
            self.logger.log(f"Ошибка проверки страницы: {e}", "ERROR")
            return False
    
    def _buy(self, price_data):
        try:
            area = self.config.ocr_areas['prices']
            bbox = price_data['bbox']
            
            click_x = area['x1'] + bbox[0] + bbox[2] // 2
            click_y = area['y1'] + bbox[1] + bbox[3] // 2
            
            pyautogui.click(click_x, click_y)
            time.sleep(0.5)
            
            if not self._click_buy_button():
                self.logger.log("Кнопка выкупа не найдена", "WARNING")
                return False
            
            coords = self.config.coords['ok']
            pyautogui.click(coords['x'], coords['y'])
            time.sleep(0.5)
            
            self.purchases += 1
            self.spent += price_data['price']
            self.status = f"Куплено за {price_data['price']:,}"
            self.logger.log(f"✅ КУПЛЕНО за {price_data['price']:,} (всего: {self.purchases})", "SUCCESS")
            
            return True
            
        except Exception as e:
            self.logger.log(f"Ошибка покупки: {e}", "ERROR")
            return False
    
    def _click_buy_button(self):
        try:
            area = self.config.ocr_areas['buy_btn']
            screenshot = ImageGrab.grab(bbox=(
                area['x1'], area['y1'],
                area['x2'], area['y2']
            ))
            
            result = self.ocr.find_text(screenshot, ['выкупить', 'купить', 'выкуп', 'лот'])
            
            if result:
                bbox = result['bbox']
                click_x = area['x1'] + bbox[0] + bbox[2] // 2
                click_y = area['y1'] + bbox[1] + bbox[3] // 2
                
                pyautogui.click(click_x, click_y)
                self.logger.log(f"Клик по кнопке: {result['text']}", "INFO")
                return True
            
            return False
            
        except Exception as e:
            self.logger.log(f"Ошибка поиска кнопки: {e}", "ERROR")
            return False
    
    def _scroll(self, steps):
        try:
            coords = self.config.coords['slider']
            
            # Прокручиваем от текущей позиции
            new_position = self.scroll_position + steps
            pyautogui.moveTo(coords['x'], coords['y'])
            pyautogui.mouseDown()
            pyautogui.moveTo(coords['x'], coords['y'] + new_position, duration=0.3)
            pyautogui.mouseUp()
            
            self.scroll_position = new_position
            time.sleep(0.5)
            
            self.logger.log(f"Прокрутка на {new_position}px", "INFO")
            
        except Exception as e:
            self.logger.log(f"Ошибка прокрутки: {e}", "ERROR")
    
    def _switch_page(self, page):
        try:
            area = self.config.ocr_areas['pages']
            screenshot = ImageGrab.grab(bbox=(
                area['x1'], area['y1'],
                area['x2'], area['y2']
            ))
            
            custom_config = '--psm 6 -c tessedit_char_whitelist=0123456789'
            data = pytesseract.image_to_data(
                screenshot,
                config=custom_config,
                output_type=pytesseract.Output.DICT
            )
            
            for i, text in enumerate(data['text']):
                if text.strip() == str(page):
                    bbox = (
                        data['left'][i],
                        data['top'][i],
                        data['width'][i],
                        data['height'][i]
                    )
                    
                    click_x = area['x1'] + bbox[0] + bbox[2] // 2
                    click_y = area['y1'] + bbox[1] + bbox[3] // 2
                    
                    pyautogui.click(click_x, click_y)
                    time.sleep(1.5)
                    self.logger.log(f"Переключение на страницу {page}", "INFO")
                    self.scroll_position = 0  # Сброс позиции при смене страницы
                    return True
            
            return False
            
        except Exception as e:
            self.logger.log(f"Ошибка переключения страницы: {e}", "ERROR")
            return False
    
    def get_stats(self):
        return {
            'purchases': self.purchases,
            'spent': self.spent,
            'status': self.status,
            'running': self.running
        }

class MainWindow:
    def __init__(self):
        self.root = tk.Tk()
        self.root.title("STALCRAFT Auction Bot v2.0")
        self.root.geometry("750x850")
        self.root.resizable(False, False)
        
        # Конфиг и бот
        self.config = Config()
        self.config.load()
        self.picker = CoordinatePicker(self.root)
        
        # Создаем логгер
        self.logger = None
        
        # Бот создадим после инициализации логгера
        self.bot = None
        
        # Переменные для GUI
        self.coord_labels = {}
        self.area_labels = {}
        
        self._create_ui()
        self._update_status()
        
        self.root.protocol("WM_DELETE_WINDOW", self._on_close)
    
    def _create_ui(self):
        main = ttk.Frame(self.root, padding="10")
        main.pack(fill=tk.BOTH, expand=True)
        
        # Заголовок
        title_frame = ttk.Frame(main)
        title_frame.pack(fill=tk.X, pady=(0, 10))
        
        ttk.Label(title_frame, text="STALCRAFT AUCTION BOT", 
                 font=('Arial', 16, 'bold')).pack(side=tk.LEFT)
        
        status_label = ttk.Label(title_frame, text="⚙️", font=('Arial', 14))
        status_label.pack(side=tk.RIGHT)
        
        # Создаем фрейм для скроллинга
        canvas = tk.Canvas(main)
        scrollbar = ttk.Scrollbar(main, orient="vertical", command=canvas.yview)
        scrollable_frame = ttk.Frame(canvas)
        
        scrollable_frame.bind(
            "<Configure>",
            lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
        )
        
        canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
        canvas.configure(yscrollcommand=scrollbar.set)
        
        # Основные настройки
        basic = ttk.LabelFrame(scrollable_frame, text="Основные настройки", padding="10")
        basic.pack(fill=tk.X, pady=5)
        
        # Товар
        row1 = ttk.Frame(basic)
        row1.pack(fill=tk.X, pady=2)
        ttk.Label(row1, text="Товар:", width=12).pack(side=tk.LEFT)
        self.search_var = tk.StringVar(value=self.config.search_text)
        ttk.Entry(row1, textvariable=self.search_var, width=30).pack(side=tk.LEFT, padx=5)
        
        # Цена
        row2 = ttk.Frame(basic)
        row2.pack(fill=tk.X, pady=2)
        ttk.Label(row2, text="Макс. цена:", width=12).pack(side=tk.LEFT)
        self.price_var = tk.StringVar(value=str(self.config.max_price))
        ttk.Entry(row2, textvariable=self.price_var, width=15).pack(side=tk.LEFT, padx=5)
        
        # Страницы
        row3 = ttk.Frame(basic)
        row3.pack(fill=tk.X, pady=2)
        ttk.Label(row3, text="Страниц:", width=12).pack(side=tk.LEFT)
        self.pages_var = tk.StringVar(value=str(self.config.pages_count))
        ttk.Entry(row3, textvariable=self.pages_var, width=8).pack(side=tk.LEFT, padx=5)
        
        self.use_pages_var = tk.BooleanVar(value=self.config.use_pages)
        ttk.Checkbutton(row3, text="Использовать страницы", 
                       variable=self.use_pages_var).pack(side=tk.LEFT, padx=10)
        
        # Координаты
        coord_frame = ttk.LabelFrame(scrollable_frame, text="Координаты (кликните для установки)", padding="10")
        coord_frame.pack(fill=tk.X, pady=5)
        
        coord_items = [
            ("Поле ввода", "input", "Кликните на поле ввода товара"),
            ("Обновить", "refresh", "Кликните на кнопку 'Обновить'"),
            ("Ползунок", "slider", "Кликните на ползунок прокрутки"),
            ("ОК", "ok", "Кликните на кнопку 'ОК' подтверждения"),
            ("Кнопка СТАРТ", "start", "Кликните на кнопку 'СТАРТ' в игре"),
            ("Кнопка СТОП", "stop", "Кликните на кнопку 'СТОП' в игре")
        ]
        
        for i, (label, key, hint) in enumerate(coord_items):
            row = ttk.Frame(coord_frame)
            row.pack(fill=tk.X, pady=2)
            
            ttk.Label(row, text=f"{label}:", width=14).pack(side=tk.LEFT)
            
            coords = self.config.coords[key]
            if coords['x'] != 0 or coords['y'] != 0:
                display = f"X={coords['x']} Y={coords['y']}"
            else:
                display = "Не установлена"
            
            self.coord_labels[key] = tk.StringVar(value=display)
            ttk.Label(row, textvariable=self.coord_labels[key], width=20).pack(side=tk.LEFT, padx=5)
            
            ttk.Button(row, text="Кликнуть", 
                      command=lambda k=key, h=hint: self._set_coord(k, h)).pack(side=tk.LEFT, padx=2)
        
        # OCR области
        ocr_frame = ttk.LabelFrame(scrollable_frame, text="OCR области (выделите область)", padding="10")
        ocr_frame.pack(fill=tk.X, pady=5)
        
        ocr_items = [
            ("Цены", "prices", "Выделите область с ценами на аукционе"),
            ("Кнопка выкупа", "buy_btn", "Выделите область кнопки 'Выкупить лот'"),
            ("Страницы", "pages", "Выделите область с номерами страниц")
        ]
        
        for i, (label, key, hint) in enumerate(ocr_items):
            row = ttk.Frame(ocr_frame)
            row.pack(fill=tk.X, pady=2)
            
            ttk.Label(row, text=f"{label}:", width=14).pack(side=tk.LEFT)
            
            area = self.config.ocr_areas[key]
            if area['x1'] != 0 or area['x2'] != 0:
                display = f"({area['x1']},{area['y1']}) → ({area['x2']},{area['y2']})"
            else:
                display = "Не установлена"
            
            self.area_labels[key] = tk.StringVar(value=display)
            ttk.Label(row, textvariable=self.area_labels[key], width=28).pack(side=tk.LEFT, padx=5)
            
            ttk.Button(row, text="Выделить", 
                      command=lambda k=key, h=hint: self._set_area(k, h)).pack(side=tk.LEFT, padx=2)
        
        # Управление
        control = ttk.Frame(scrollable_frame)
        control.pack(pady=10)
        
        ttk.Label(control, text="Режим:").pack(side=tk.LEFT, padx=5)
        self.mode_var = tk.StringVar(value="ТЕСТ" if self.config.test_mode else "ВКЛ")
        mode_combo = ttk.Combobox(control, textvariable=self.mode_var,
                                 values=['ТЕСТ', 'ВКЛ'], width=8, state='readonly')
        mode_combo.pack(side=tk.LEFT, padx=5)
        
        ttk.Button(control, text="Проверить OCR", 
                  command=self._test_ocr).pack(side=tk.LEFT, padx=5)
        
        self.start_btn = ttk.Button(control, text="▶ ЗАПУСК", 
                                   command=self._start_bot)
        self.start_btn.pack(side=tk.LEFT, padx=5)
        
        self.stop_btn = ttk.Button(control, text="■ СТОП", 
                                  command=self._stop_bot, state='disabled')
        self.stop_btn.pack(side=tk.LEFT, padx=5)
        
        # Лог
        log_frame = ttk.LabelFrame(scrollable_frame, text="Журнал событий", padding="5")
        log_frame.pack(fill=tk.BOTH, expand=True, pady=5)
        
        self.log_text = scrolledtext.ScrolledText(log_frame, height=10, font=('Consolas', 9))
        self.log_text.pack(fill=tk.BOTH, expand=True)
        
        # Настройка тегов для цветов
        self.log_text.tag_config("timestamp", foreground="#666666")
        self.log_text.tag_config("INFO", foreground="#00AA00")
        self.log_text.tag_config("WARNING", foreground="#FFAA00")
        self.log_text.tag_config("ERROR", foreground="#FF0000")
        self.log_text.tag_config("SUCCESS", foreground="#0066FF")
        self.log_text.tag_config("message", foreground="#000000")
        
        # Создаем логгер
        self.logger = Logger(self.log_text)
        self.logger.log("Программа запущена", "INFO")
        
        # Проверка Tesseract
        if not TESSERACT_FOUND:
            self.logger.log("⚠️ Tesseract не найден! OCR не будет работать.", "ERROR")
            self.logger.log("Установите Tesseract OCR с https://github.com/UB-Mannheim/tesseract/wiki", "WARNING")
        else:
            self.logger.log("✅ Tesseract найден", "SUCCESS")
        
        # Создаем бота
        self.bot = AuctionBot(self.config, self.logger)
        
        # Размещаем canvas и scrollbar
        canvas.pack(side="left", fill="both", expand=True)
        scrollbar.pack(side="right", fill="y")
    
    def _set_coord(self, key, hint):
        """Установка координаты через клик"""
        messagebox.showinfo("Настройка координаты", 
            f"Наведите мышь на элемент '{key}' и кликните левой кнопкой\n\n{hint}\n\nНажмите ESC для отмены")
        
        def callback(x, y):
            if x is not None and y is not None:
                self.config.coords[key]['x'] = x
                self.config.coords[key]['y'] = y
                self.coord_labels[key].set(f"X={x} Y={y}")
                self.logger.log(f"Координата {key} установлена: X={x} Y={y}", "SUCCESS")
                messagebox.showinfo("Успех", f"Координата {key} установлена!\nX={x} Y={y}")
            else:
                self.logger.log(f"Настройка координаты {key} отменена", "WARNING")
                messagebox.showinfo("Отмена", "Настройка отменена")
        
        self.picker.pick_coordinate(callback)
    
    def _set_area(self, key, hint):
        """Установка OCR области выделением"""
        messagebox.showinfo("Настройка OCR области", 
            f"Выделите область '{key}'\n\n{hint}\n\n"
            f"1. Кликните в ЛЕВОМ ВЕРХНЕМ углу\n"
            f"2. Кликните в ПРАВОМ НИЖНЕМ углу\n"
            f"Нажмите ESC для отмены")
        
        def callback(result):
            if result:
                self.config.ocr_areas[key] = result
                self.area_labels[key].set(
                    f"({result['x1']},{result['y1']}) → ({result['x2']},{result['y2']})"
                )
                self.logger.log(f"OCR область {key} установлена", "SUCCESS")
                messagebox.showinfo("Успех", f"Область {key} установлена!")
            else:
                self.logger.log(f"Настройка области {key} отменена", "WARNING")
                messagebox.showinfo("Отмена", "Настройка отменена")
        
        self.picker.pick_area(callback)
    
    def _test_ocr(self):
        """Тест OCR"""
        area = self.config.ocr_areas['prices']
        if area['x1'] == area['x2'] or area['y1'] == area['y2']:
            self.logger.log("Ошибка: сначала настройте область цен!", "ERROR")
            messagebox.showwarning("Ошибка", "Сначала настройте область цен!")
            return
        
        try:
            self.logger.log("Запуск теста OCR...", "INFO")
            screenshot = ImageGrab.grab(bbox=(
                area['x1'], area['y1'],
                area['x2'], area['y2']
            ))
            
            prices = self.bot.ocr.extract_prices(screenshot)
            
            result = "=== РЕЗУЛЬТАТЫ OCR ТЕСТА ===\n\n"
            result += f"Область: {area['x1']},{area['y1']} → {area['x2']},{area['y2']}\n"
            result += f"Язык: {self.config.ocr_lang}\n"
            result += f"Масштаб: {self.config.ocr_scale}%\n"
            result += f"Попыток: {self.config.ocr_attempts}\n"
            result += f"Порог уверенности: {self.config.confidence_threshold}%\n\n"
            
            if prices:
                result += f"✅ Найдено цен: {len(prices)}\n\n"
                for p in prices[:10]:
                    result += f"{p['price']:,}  — {p['confidence']}%\n"
                
                if len(prices) > 10:
                    result += f"... и еще {len(prices) - 10} цен\n"
                
                valid = [p for p in prices if p['price'] <= self.config.max_price]
                if valid:
                    best = min(valid, key=lambda x: x['price'])
                    result += f"\n✅ Подходящих: {len(valid)}\n"
                    result += f"Минимальная цена: {best['price']:,}"
                    self.logger.log(f"Тест OCR: найдено {len(prices)} цен, минимальная {best['price']:,}", "SUCCESS")
                else:
                    result += f"\n❌ Подходящих: 0\n"
                    result += f"Макс. цена: {self.config.max_price:,}"
                    self.logger.log("Тест OCR: цены найдены, но все выше максимальной", "WARNING")
            else:
                result += "❌ Цены не найдены!\n\n"
                result += "Возможные проблемы:\n"
                result += "1. Неправильно настроена область\n"
                result += "2. Некорректные параметры OCR\n"
                result += "3. Tesseract не установлен\n"
                result += "4. Плохое качество изображения"
                self.logger.log("Тест OCR: цены не найдены", "ERROR")
            
            messagebox.showinfo("Тест OCR", result)
            
        except Exception as e:
            error_msg = f"Ошибка теста: {e}"
            self.logger.log(error_msg, "ERROR")
            messagebox.showerror("Ошибка", error_msg)
    
    def _start_bot(self):
        # Обновляем настройки
        self.config.search_text = self.search_var.get()
        try:
            self.config.max_price = int(self.price_var.get())
            self.config.pages_count = int(self.pages_var.get())
        except:
            self.logger.log("Некорректное значение цены или страниц!", "ERROR")
            messagebox.showerror("Ошибка", "Некорректное значение!")
            return
        
        self.config.use_pages = self.use_pages_var.get()
        self.config.test_mode = self.mode_var.get() == "ТЕСТ"
        self.config.auto_buy = self.mode_var.get() == "ВКЛ"
        
        self.logger.log(f"Запуск бота в режиме: {'ТЕСТ' if self.config.test_mode else 'ВКЛ'}", "INFO")
        self.logger.log(f"Товар: {self.config.search_text}, Макс. цена: {self.config.max_price:,}", "INFO")
        
        self.bot.start()
        
        self.start_btn.config(state='disabled')
        self.stop_btn.config(state='normal')
    
    def _stop_bot(self):
        self.bot.stop()
        self.start_btn.config(state='normal')
        self.stop_btn.config(state='disabled')
    
    def _save_config(self):
        self.config.search_text = self.search_var.get()
        try:
            self.config.max_price = int(self.price_var.get())
            self.config.pages_count = int(self.pages_var.get())
        except:
            pass
        
        self.config.use_pages = self.use_pages_var.get()
        self.config.test_mode = self.mode_var.get() == "ТЕСТ"
        self.config.auto_buy = self.mode_var.get() == "ВКЛ"
        
        if self.config.save():
            self.logger.log("Настройки сохранены в config.json", "SUCCESS")
            messagebox.showinfo("Успех", "Настройки сохранены в config.json!")
        else:
            self.logger.log("Не удалось сохранить настройки!", "ERROR")
            messagebox.showerror("Ошибка", "Не удалось сохранить настройки!")
    
    def _load_config(self):
        if self.config.load():
            # Обновляем GUI
            self.search_var.set(self.config.search_text)
            self.price_var.set(str(self.config.max_price))
            self.pages_var.set(str(self.config.pages_count))
            self.use_pages_var.set(self.config.use_pages)
            self.mode_var.set("ТЕСТ" if self.config.test_mode else "ВКЛ")
            
            # Обновляем координаты
            for key, coords in self.config.coords.items():
                if coords['x'] != 0 or coords['y'] != 0:
                    self.coord_labels[key].set(f"X={coords['x']} Y={coords['y']}")
                else:
                    self.coord_labels[key].set("Не установлена")
            
            # Обновляем OCR области
            for key, area in self.config.ocr_areas.items():
                if area['x1'] != 0 or area['x2'] != 0:
                    self.area_labels[key].set(
                        f"({area['x1']},{area['y1']}) → ({area['x2']},{area['y2']})"
                    )
                else:
                    self.area_labels[key].set("Не установлена")
            
            self.logger.log("Настройки загружены из config.json", "SUCCESS")
            messagebox.showinfo("Успех", "Настройки загружены!")
        else:
            self.logger.log("Не удалось загрузить настройки!", "ERROR")
            messagebox.showwarning("Ошибка", "Не удалось загрузить настройки!")
    
    def _update_status(self):
        try:
            if self.bot:
                stats = self.bot.get_stats()
                # Обновляем заголовок
                status_icon = "▶️" if stats['running'] else "⏹️"
                self.root.title(f"STALCRAFT Auction Bot v2.0 {status_icon}")
        except:
            pass
        
        self.root.after(1000, self._update_status)
    
    def _on_close(self):
        if self.bot:
            self.bot.stop()
        self.config.save()
        self.logger.log("Программа завершена", "INFO")
        self.root.destroy()

if __name__ == "__main__":
    try:
        # Скрываем консоль на Windows
        if sys.platform == "win32":
            import ctypes
            ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 0)
        
        app = MainWindow()
        app.root.mainloop()
        
    except Exception as e:
        print(f"Ошибка: {e}")
        import traceback
        traceback.print_exc()
        input("Нажмите Enter для выхода...")