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


#!/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

# Проверка и установка зависимостей
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
except ImportError as e:
    print(f"Устанавливаю зависимости...")
    install_package("pyautogui")
    install_package("pytesseract")
    install_package("Pillow")
    print("Зависимости установлены. Перезапустите программу.")
    sys.exit(1)

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.coords = {
            'input': {'x': 0, 'y': 0},
            'refresh': {'x': 0, 'y': 0},
            'slider': {'x': 0, 'y': 0},
            'ok': {'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:
            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:
            pass
        return False

class OCRSimple:
    def __init__(self, config):
        self.config = config
        
    def extract_prices(self, image):
        """Извлечение цен из изображения"""
        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)
            
            # Конфиг для Tesseract
            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:
            print(f"OCR ошибка: {e}")
            return []
    
    def find_text(self, image, keywords):
        """Поиск текста в изображении"""
        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:
            return None

class AuctionBot:
    def __init__(self, config):
        self.config = config
        self.ocr = OCRSimple(config)
        self.running = False
        self.purchases = 0
        self.spent = 0
        self.status = "Готов"
        
        # Настройка pyautogui
        pyautogui.FAILSAFE = True
        pyautogui.PAUSE = 0.2
    
    def start(self):
        if self.running:
            return
        
        # Проверка настроек
        if not self.config.search_text:
            self.status = "Ошибка: не указан товар"
            return
        
        # Проверка координат
        for key, coord in self.config.coords.items():
            if coord['x'] == 0 and coord['y'] == 0:
                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.status = f"Ошибка: не настроена область {key}"
                return
        
        self.running = True
        self.status = "Запущен"
        
        # Запуск в отдельном потоке
        thread = threading.Thread(target=self._run)
        thread.daemon = True
        thread.start()
    
    def stop(self):
        self.running = False
        self.status = "Остановлен"
    
    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.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:
            return False
    
    def _refresh(self):
        try:
            coords = self.config.coords['refresh']
            pyautogui.click(coords['x'], coords['y'])
            time.sleep(2)
            return True
        except:
            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']:,}"
            
            # Тестовый режим
            if self.config.test_mode:
                return False
            
            # Покупка
            if self.config.auto_buy:
                return self._buy(best)
            
            return False
            
        except Exception as e:
            print(f"Ошибка проверки: {e}")
            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():
                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']:,}"
            
            return True
            
        except Exception as e:
            print(f"Ошибка покупки: {e}")
            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)
                return True
            
            return False
            
        except:
            return False
    
    def _scroll(self, steps):
        try:
            coords = self.config.coords['slider']
            pyautogui.moveTo(coords['x'], coords['y'])
            pyautogui.mouseDown()
            pyautogui.moveTo(coords['x'], coords['y'] + steps, duration=0.3)
            pyautogui.mouseUp()
            time.sleep(0.5)
        except:
            pass
    
    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)
                    return True
            
            return False
            
        except:
            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")
        self.root.geometry("650x750")
        self.root.resizable(False, False)
        
        # Конфиг и бот
        self.config = Config()
        self.config.load()
        self.bot = AuctionBot(self.config)
        
        # Переменные для 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)
        
        # Заголовок
        ttk.Label(main, text="STALCRAFT AUCTION BOT", 
                 font=('Arial', 14, 'bold')).pack(pady=(0, 10))
        
        # Основные настройки
        basic = ttk.LabelFrame(main, 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(main, text="Координаты (наведите мышь и нажмите кнопку)", padding="10")
        coord_frame.pack(fill=tk.X, pady=5)
        
        coord_items = [
            ("Поле ввода", "input"),
            ("Обновить", "refresh"),
            ("Ползунок", "slider"),
            ("ОК", "ok")
        ]
        
        for i, (label, key) in enumerate(coord_items):
            row = ttk.Frame(coord_frame)
            row.pack(fill=tk.X, pady=2)
            
            ttk.Label(row, text=f"{label}:", width=12).pack(side=tk.LEFT)
            
            coords = self.config.coords[key]
            self.coord_labels[key] = tk.StringVar(value=f"X={coords['x']} Y={coords['y']}")
            ttk.Label(row, textvariable=self.coord_labels[key], width=15).pack(side=tk.LEFT, padx=5)
            
            ttk.Button(row, text="Настроить", 
                      command=lambda k=key: self._set_coord(k)).pack(side=tk.LEFT, padx=5)
        
        # OCR области
        ocr_frame = ttk.LabelFrame(main, text="OCR области (выберите область на экране)", padding="10")
        ocr_frame.pack(fill=tk.X, pady=5)
        
        ocr_items = [
            ("Цены", "prices"),
            ("Кнопка выкупа", "buy_btn"),
            ("Страницы", "pages")
        ]
        
        for i, (label, key) in enumerate(ocr_items):
            row = ttk.Frame(ocr_frame)
            row.pack(fill=tk.X, pady=2)
            
            ttk.Label(row, text=f"{label}:", width=12).pack(side=tk.LEFT)
            
            area = self.config.ocr_areas[key]
            self.area_labels[key] = tk.StringVar(
                value=f"({area['x1']},{area['y1']}) → ({area['x2']},{area['y2']})"
            )
            ttk.Label(row, textvariable=self.area_labels[key], width=25).pack(side=tk.LEFT, padx=5)
            
            ttk.Button(row, text="Настроить", 
                      command=lambda k=key: self._set_area(k)).pack(side=tk.LEFT, padx=5)
        
        # Управление
        control = ttk.Frame(main)
        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)
        
        # Статус
        status_frame = ttk.LabelFrame(main, text="Статус", padding="5")
        status_frame.pack(fill=tk.X, pady=5)
        
        self.status_var = tk.StringVar(value="Готов")
        ttk.Label(status_frame, textvariable=self.status_var, font=('Arial', 10)).pack()
        
        self.stats_var = tk.StringVar(value="Покупок: 0 | Потрачено: 0")
        ttk.Label(status_frame, textvariable=self.stats_var, font=('Arial', 9)).pack()
        
        # Сохранение
        ttk.Button(main, text="Сохранить настройки", 
                  command=self._save_config).pack(pady=10)
    
    def _set_coord(self, key):
        """Установка координаты"""
        self.root.iconify()
        time.sleep(0.5)
        
        x, y = pyautogui.position()
        
        self.config.coords[key]['x'] = x
        self.config.coords[key]['y'] = y
        self.coord_labels[key].set(f"X={x} Y={y}")
        
        self.root.deiconify()
        messagebox.showinfo("Успех", f"Координата {key} установлена: X={x} Y={y}")
    
    def _set_area(self, key):
        """Установка OCR области"""
        self.root.iconify()
        time.sleep(0.5)
        
        try:
            # Первая точка
            messagebox.showinfo("Область", 
                f"Наведите мышь на ЛЕВЫЙ ВЕРХНИЙ угол области '{key}' и нажмите ОК")
            x1, y1 = pyautogui.position()
            
            # Вторая точка
            messagebox.showinfo("Область", 
                f"Наведите мышь на ПРАВЫЙ НИЖНИЙ угол области '{key}' и нажмите ОК")
            x2, y2 = pyautogui.position()
            
            # Сохраняем
            self.config.ocr_areas[key] = {
                'x1': min(x1, x2),
                'y1': min(y1, y2),
                'x2': max(x1, x2),
                'y2': max(y1, y2)
            }
            
            area = self.config.ocr_areas[key]
            self.area_labels[key].set(f"({area['x1']},{area['y1']}) → ({area['x2']},{area['y2']})")
            
            self.root.deiconify()
            messagebox.showinfo("Успех", f"Область {key} установлена!")
            
        except Exception as e:
            self.root.deiconify()
            messagebox.showerror("Ошибка", f"Ошибка: {e}")
    
    def _test_ocr(self):
        """Тест OCR"""
        area = self.config.ocr_areas['prices']
        if area['x1'] == area['x2'] or area['y1'] == area['y2']:
            messagebox.showwarning("Ошибка", "Сначала настройте область цен!")
            return
        
        try:
            screenshot = ImageGrab.grab(bbox=(
                area['x1'], area['y1'],
                area['x2'], area['y2']
            ))
            
            prices = self.bot.ocr.extract_prices(screenshot)
            
            result = "=== РЕЗУЛЬТАТЫ OCR ===\n\n"
            
            if prices:
                result += f"Найдено цен: {len(prices)}\n\n"
                for p in prices:
                    result += f"{p['price']:,}  — {p['confidence']}%\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']:,}"
                else:
                    result += f"\nПодходящих: 0\n"
                    result += f"Макс. цена: {self.config.max_price:,}"
            else:
                result += "Цены не найдены!\n"
                result += "Проверьте настройки OCR и область."
            
            messagebox.showinfo("Тест OCR", result)
            
        except Exception as e:
            messagebox.showerror("Ошибка", f"Ошибка: {e}")
    
    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:
            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.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():
            messagebox.showinfo("Успех", "Настройки сохранены!")
        else:
            messagebox.showerror("Ошибка", "Не удалось сохранить!")
    
    def _update_status(self):
        try:
            stats = self.bot.get_stats()
            self.status_var.set(stats['status'])
            self.stats_var.set(f"Покупок: {stats['purchases']} | Потрачено: {stats['spent']:,}")
        except:
            pass
        
        self.root.after(1000, self._update_status)
    
    def _on_close(self):
        self.bot.stop()
        self.config.save()
        self.root.destroy()

if __name__ == "__main__":
    try:
        # Проверка Tesseract
        try:
            pytesseract.get_tesseract_version()
        except:
            messagebox.showwarning("Предупреждение", 
                "Tesseract не найден!\n\n"
                "Установите Tesseract OCR:\n"
                "1. Скачайте: https://github.com/UB-Mannheim/tesseract/wiki\n"
                "2. Установите в C:\\Program Files\\Tesseract-OCR\\\n"
                "3. Перезапустите программу")
        
        app = MainWindow()
        app.root.mainloop()
        
    except Exception as e:
        print(f"Ошибка: {e}")
        input("Нажмите Enter для выхода...")