Загрузка данных
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
STALCRAFT Auction Bot - Рабочая версия
Версия: 1.0.1
"""
import sys
import os
import json
import time
import threading
import logging
from datetime import datetime
from typing import Optional, Dict, List, Tuple
from dataclasses import dataclass, asdict
# Установка зависимостей
try:
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import pyautogui
import pytesseract
from PIL import ImageGrab, Image, ImageEnhance, ImageFilter
import cv2
import numpy as np
except ImportError as e:
print(f"Ошибка импорта: {e}")
print("Устанавливаю зависимости...")
import subprocess
subprocess.check_call([sys.executable, "-m", "pip", "install", "pytesseract", "opencv-python", "Pillow", "pyautogui", "keyboard"])
print("Зависимости установлены. Перезапустите программу.")
sys.exit(1)
# Настройка логирования
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('stalcraft_bot.log', encoding='utf-8'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
# ============= КЛАСС НАСТРОЕК =============
class Config:
"""Класс для хранения всех настроек"""
def __init__(self):
self.search_text = ""
self.max_price = 100000
self.pages_count = 3
self.use_pages = True
self.auto_buy = False
self.test_mode = True
self.coordinates = {
'input_field': {'x': 0, 'y': 0},
'refresh_button': {'x': 0, 'y': 0},
'slider': {'x': 0, 'y': 0},
'ok_button': {'x': 0, 'y': 0}
}
self.ocr_areas = {
'prices': {'x1': 0, 'y1': 0, 'x2': 0, 'y2': 0},
'buy_button': {'x1': 0, 'y1': 0, 'x2': 0, 'y2': 0},
'pages': {'x1': 0, 'y1': 0, 'x2': 0, 'y2': 0}
}
self.ocr_settings = {
'lang': 'rus',
'scale': 200,
'psm': 6,
'attempts': 3,
'confidence_threshold': 70,
'preprocessing': {
'increase': True,
'bw': True,
'contrast': True,
'binarization': False,
'denoise': True,
'sharpness': False
}
}
def save(self, filename: str = "config.json"):
"""Сохранение настроек в файл"""
try:
data = {
'search_text': self.search_text,
'max_price': self.max_price,
'pages_count': self.pages_count,
'use_pages': self.use_pages,
'auto_buy': self.auto_buy,
'test_mode': self.test_mode,
'coordinates': self.coordinates,
'ocr_areas': self.ocr_areas,
'ocr_settings': self.ocr_settings
}
with open(filename, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=4, ensure_ascii=False)
logger.info(f"Настройки сохранены в {filename}")
return True
except Exception as e:
logger.error(f"Ошибка сохранения настроек: {e}")
return False
def load(self, filename: str = "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)
logger.info(f"Настройки загружены из {filename}")
return True
except Exception as e:
logger.error(f"Ошибка загрузки настроек: {e}")
return False
# ============= КЛАСС OCR ОБРАБОТЧИКА =============
class OCRProcessor:
"""Обработка OCR с настраиваемыми параметрами"""
def __init__(self, settings: Dict):
self.settings = settings
self.last_results = []
# Проверка наличия Tesseract
try:
pytesseract.get_tesseract_version()
logger.info("Tesseract найден")
except:
logger.error("Tesseract не найден!")
messagebox.showerror("Ошибка",
"Tesseract OCR не найден!\n\n"
"Установите Tesseract:\n"
"1. Скачайте с https://github.com/UB-Mannheim/tesseract/wiki\n"
"2. Установите в C:\\Program Files\\Tesseract-OCR\\\n"
"3. Добавьте в PATH или укажите путь в коде")
def preprocess_image(self, image: Image.Image) -> Image.Image:
"""Предобработка изображения"""
try:
# Конвертация в numpy array
img = np.array(image)
# Масштабирование
if self.settings['scale'] != 100:
scale = self.settings['scale'] / 100
width = int(img.shape[1] * scale)
height = int(img.shape[0] * scale)
img = cv2.resize(img, (width, height), interpolation=cv2.INTER_CUBIC)
# Чёрно-белое
if self.settings['preprocessing']['bw'] and len(img.shape) == 3:
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Повышение контрастности
if self.settings['preprocessing']['contrast']:
if len(img.shape) == 3:
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
alpha = 1.5
beta = 30
img = cv2.convertScaleAbs(img, alpha=alpha, beta=beta)
# Бинаризация
if self.settings['preprocessing']['binarization']:
if len(img.shape) == 3:
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, img = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
# Удаление шума
if self.settings['preprocessing']['denoise']:
if len(img.shape) == 3:
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img = cv2.medianBlur(img, 3)
# Резкость
if self.settings['preprocessing']['sharpness']:
if len(img.shape) == 3:
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
kernel = np.array([[-1,-1,-1], [-1, 9,-1], [-1,-1,-1]])
img = cv2.filter2D(img, -1, kernel)
return Image.fromarray(img)
except Exception as e:
logger.error(f"Ошибка предобработки: {e}")
return image
def extract_prices(self, image: Image.Image) -> List[Dict]:
"""Извлечение цен из изображения"""
results = []
for attempt in range(self.settings['attempts']):
try:
# Предобработка
processed = self.preprocess_image(image)
# Настройка Tesseract для цен
custom_config = f'--oem 3 --psm {self.settings["psm"]} -c tessedit_char_whitelist=0123456789 '
# Распознавание
data = pytesseract.image_to_data(
processed,
lang=self.settings['lang'],
config=custom_config,
output_type=pytesseract.Output.DICT
)
# Парсинг результатов
prices = self._parse_prices(data)
if prices:
results.append(prices)
except Exception as e:
logger.error(f"Ошибка OCR (попытка {attempt+1}): {e}")
continue
# Валидация результатов
return self._validate_results(results)
def _parse_prices(self, data: Dict) -> List[Dict]:
"""Парсинг цен из данных Tesseract"""
prices = []
n_boxes = len(data['text'])
for i in range(n_boxes):
try:
text = data['text'][i].strip()
if not text:
continue
conf = int(data['conf'][i])
# Проверка уверенности
if conf < self.settings['confidence_threshold']:
continue
# Очистка текста
clean_text = ''.join(filter(str.isdigit, text))
if clean_text and len(clean_text) >= 4:
price = int(clean_text)
if 1000 <= price <= 10000000:
prices.append({
'price': price,
'confidence': conf,
'bbox': (
data['left'][i],
data['top'][i],
data['width'][i],
data['height'][i]
),
'text': text
})
except (ValueError, IndexError) as e:
continue
return prices
def _validate_results(self, results: List[List[Dict]]) -> List[Dict]:
"""Валидация результатов OCR"""
if not results:
return []
# Если только одна попытка
if len(results) == 1:
return results[0]
# Проверка стабильности
stable_prices = []
for i in range(len(results[0])):
price = results[0][i]['price']
is_stable = all(
r[i]['price'] == price if i < len(r) else False
for r in results[1:]
)
if is_stable:
stable_prices.append(results[0][i])
return stable_prices
def extract_text(self, image: Image.Image) -> Dict:
"""Извлечение текста (для кнопок)"""
try:
processed = self.preprocess_image(image)
custom_config = f'--oem 3 --psm 6'
data = pytesseract.image_to_data(
processed,
lang=self.settings['lang'],
config=custom_config,
output_type=pytesseract.Output.DICT
)
# Поиск текста
texts = {}
for i, text in enumerate(data['text']):
if text.strip():
texts[text.strip().lower()] = {
'bbox': (data['left'][i], data['top'][i],
data['width'][i], data['height'][i]),
'conf': int(data['conf'][i])
}
return texts
except Exception as e:
logger.error(f"Ошибка извлечения текста: {e}")
return {}
# ============= КЛАСС БОТА =============
class AuctionBot:
"""Основной движок бота"""
def __init__(self, config: Config):
self.config = config
self.ocr = OCRProcessor(config.ocr_settings)
self.is_running = False
self.current_page = 1
self.purchases_count = 0
self.total_spent = 0
self.last_status = "Готов к работе"
# Настройка pyautogui
pyautogui.FAILSAFE = True
pyautogui.PAUSE = 0.1
def start(self):
"""Запуск бота"""
if self.is_running:
return
# Проверка настроек
if not self._validate_config():
self.last_status = "Ошибка: проверьте настройки"
return
self.is_running = True
self.current_page = 1
# Запуск в отдельном потоке
self.bot_thread = threading.Thread(target=self._run)
self.bot_thread.daemon = True
self.bot_thread.start()
logger.info("Бот запущен")
self.last_status = "Бот запущен"
def stop(self):
"""Остановка бота"""
self.is_running = False
logger.info("Бот остановлен")
self.last_status = "Бот остановлен"
def _run(self):
"""Основной цикл бота"""
while self.is_running:
try:
logger.info(f"Цикл #{self.purchases_count + 1}")
self.last_status = "Ввод товара..."
# 1. Ввод товара
if not self._input_search_text():
self.last_status = "Ошибка ввода товара"
continue
# 2. Обновление списка
self.last_status = "Обновление списка..."
if not self._refresh_auction():
self.last_status = "Ошибка обновления"
continue
# 3. Поиск лотов
self.last_status = "Поиск лотов..."
if self._search_lots():
continue
time.sleep(1)
except Exception as e:
logger.error(f"Ошибка: {e}")
self.last_status = f"Ошибка: {str(e)[:30]}"
time.sleep(3)
def _validate_config(self) -> bool:
"""Проверка настроек"""
if not self.config.search_text:
logger.warning("Не указан товар")
return False
if self.config.max_price <= 0:
logger.warning("Некорректная цена")
return False
# Проверка координат
for key, coords in self.config.coordinates.items():
if coords['x'] == 0 and coords['y'] == 0:
logger.warning(f"Не настроены координаты: {key}")
return False
# Проверка OCR областей
for key, area in self.config.ocr_areas.items():
if area['x1'] == area['x2'] or area['y1'] == area['y2']:
logger.warning(f"Не настроена OCR область: {key}")
return False
return True
def _input_search_text(self) -> bool:
"""Ввод текста поиска"""
try:
coords = self.config.coordinates['input_field']
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)
logger.info(f"Введён текст: {self.config.search_text}")
return True
except Exception as e:
logger.error(f"Ошибка ввода: {e}")
return False
def _refresh_auction(self) -> bool:
"""Обновление списка"""
try:
coords = self.config.coordinates['refresh_button']
pyautogui.click(coords['x'], coords['y'])
time.sleep(2)
logger.info("Список обновлён")
return True
except Exception as e:
logger.error(f"Ошибка обновления: {e}")
return False
def _search_lots(self) -> bool:
"""Поиск лотов с прокруткой"""
# Проверка видимой области
if self._check_and_buy():
return True
# Прокрутка
for step in range(80, 401, 80):
if not self.is_running:
return False
self._scroll_down(step)
if self._check_and_buy():
return True
# Переключение страницы
if self.config.use_pages and self.current_page < self.config.pages_count:
self.current_page += 1
if self._switch_page(self.current_page):
return self._search_lots()
return False
def _check_and_buy(self) -> bool:
"""Проверка лотов"""
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_prices = [p for p in prices if p['price'] <= self.config.max_price]
if not valid_prices:
return False
best = min(valid_prices, key=lambda x: x['price'])
logger.info(f"Найден лот: {best['price']:,} (уверенность: {best['confidence']}%)")
self.last_status = f"Найден лот: {best['price']:,}"
# Тестовый режим
if self.config.test_mode:
self.last_status += " (ТЕСТ)"
return False
# Покупка
if self.config.auto_buy:
return self._buy_item(best)
return False
except Exception as e:
logger.error(f"Ошибка проверки: {e}")
return False
def _buy_item(self, price_data: Dict) -> bool:
"""Покупка лота"""
try:
# Клик по лоту
bbox = price_data['bbox']
area = self.config.ocr_areas['prices']
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._find_buy_button():
return False
# Подтверждение
coords = self.config.coordinates['ok_button']
pyautogui.click(coords['x'], coords['y'])
time.sleep(0.5)
self.purchases_count += 1
self.total_spent += price_data['price']
logger.info(f"Куплено за {price_data['price']:,}")
self.last_status = f"Куплено за {price_data['price']:,}"
return True
except Exception as e:
logger.error(f"Ошибка покупки: {e}")
return False
def _find_buy_button(self) -> bool:
"""Поиск кнопки выкупа"""
try:
area = self.config.ocr_areas['buy_button']
screenshot = ImageGrab.grab(bbox=(
area['x1'], area['y1'],
area['x2'], area['y2']
))
texts = self.ocr.extract_text(screenshot)
# Поиск ключевых слов
keywords = ['выкупить', 'купить', 'выкуп', 'лот', 'приобрести']
for text, data in texts.items():
text_lower = text.lower()
for keyword in keywords:
if keyword in text_lower:
bbox = 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)
logger.info(f"Кнопка найдена: {text}")
return True
return False
except Exception as e:
logger.error(f"Ошибка поиска кнопки: {e}")
return False
def _scroll_down(self, steps: int):
"""Прокрутка"""
try:
slider = self.config.coordinates['slider']
pyautogui.moveTo(slider['x'], slider['y'])
pyautogui.mouseDown()
pyautogui.moveTo(slider['x'], slider['y'] + steps, duration=0.2)
pyautogui.mouseUp()
time.sleep(0.3)
except Exception as e:
logger.error(f"Ошибка прокрутки: {e}")
def _switch_page(self, page_number: int) -> bool:
"""Переключение страницы"""
try:
area = self.config.ocr_areas['pages']
screenshot = ImageGrab.grab(bbox=(
area['x1'], area['y1'],
area['x2'], area['y2']
))
processed = self.ocr.preprocess_image(screenshot)
custom_config = '--oem 3 --psm 6 -c tessedit_char_whitelist=0123456789'
data = pytesseract.image_to_data(
processed,
lang='rus',
config=custom_config,
output_type=pytesseract.Output.DICT
)
for i, text in enumerate(data['text']):
if text.strip() == str(page_number):
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)
logger.info(f"Страница {page_number}")
return True
return False
except Exception as e:
logger.error(f"Ошибка переключения: {e}")
return False
def get_stats(self) -> Dict:
"""Получение статистики"""
return {
'purchases': self.purchases_count,
'spent': self.total_spent,
'status': self.last_status,
'running': self.is_running
}
# ============= КЛАСС ГРАФИЧЕСКОГО ИНТЕРФЕЙСА =============
class BotGUI:
"""Главное окно приложения"""
def __init__(self):
self.root = tk.Tk()
self.root.title("STALCRAFT Auction Bot")
self.root.geometry("700x800")
self.root.resizable(False, False)
# Конфигурация
self.config = Config()
self.config.load()
# Бот
self.bot = AuctionBot(self.config)
# Переменные для координат
self.coord_vars = {}
self.area_vars = {}
# Создание интерфейса
self._setup_ui()
# Обработка закрытия
self.root.protocol("WM_DELETE_WINDOW", self._on_close)
# Обновление статуса
self._update_status()
def _setup_ui(self):
"""Настройка интерфейса"""
main_frame = ttk.Frame(self.root, padding="10")
main_frame.grid(row=0, column=0, sticky="nsew")
# Заголовок
title = ttk.Label(main_frame, text="STALCRAFT AUCTION BOT",
font=('Arial', 16, 'bold'))
title.grid(row=0, column=0, columnspan=3, pady=(0, 10))
# Основные настройки
basic_frame = ttk.LabelFrame(main_frame, text="Основные настройки", padding="10")
basic_frame.grid(row=1, column=0, columnspan=3, sticky="ew", pady=5)
# Товар
ttk.Label(basic_frame, text="Товар:").grid(row=0, column=0, sticky="w")
self.search_var = tk.StringVar(value=self.config.search_text)
entry = ttk.Entry(basic_frame, textvariable=self.search_var, width=30)
entry.grid(row=0, column=1, padx=5, sticky="w")
# Цена
ttk.Label(basic_frame, text="Макс. цена:").grid(row=1, column=0, sticky="w", pady=5)
self.price_var = tk.StringVar(value=str(self.config.max_price))
entry_price = ttk.Entry(basic_frame, textvariable=self.price_var, width=15)
entry_price.grid(row=1, column=1, padx=5, sticky="w")
# Страницы
ttk.Label(basic_frame, text="Страниц:").grid(row=2, column=0, sticky="w")
self.pages_var = tk.StringVar(value=str(self.config.pages_count))
entry_pages = ttk.Entry(basic_frame, textvariable=self.pages_var, width=8)
entry_pages.grid(row=2, column=1, padx=5, sticky="w")
self.use_pages_var = tk.BooleanVar(value=self.config.use_pages)
ttk.Checkbutton(basic_frame, text="Использовать страницы",
variable=self.use_pages_var).grid(row=2, column=2, padx=5)
# Координаты
coord_frame = ttk.LabelFrame(main_frame, text="Координаты", padding="10")
coord_frame.grid(row=2, column=0, columnspan=3, sticky="ew", pady=5)
coord_items = [
("Поле ввода", "input_field"),
("Обновить", "refresh_button"),
("Ползунок", "slider"),
("ОК", "ok_button")
]
for i, (label, key) in enumerate(coord_items):
ttk.Label(coord_frame, text=f"{label}:").grid(row=i, column=0, sticky="w", padx=5)
coords = self.config.coordinates[key]
self.coord_vars[key] = tk.StringVar(
value=f"X={coords['x']} Y={coords['y']}"
)
ttk.Label(coord_frame, textvariable=self.coord_vars[key]).grid(
row=i, column=1, padx=5, sticky="w"
)
ttk.Button(coord_frame, text="Настроить",
command=lambda k=key: self._set_coordinate(k)).grid(
row=i, column=2, padx=5
)
# OCR области
ocr_frame = ttk.LabelFrame(main_frame, text="OCR области", padding="10")
ocr_frame.grid(row=3, column=0, columnspan=3, sticky="ew", pady=5)
ocr_items = [
("Цены", "prices"),
("Кнопка выкупа", "buy_button"),
("Страницы", "pages")
]
for i, (label, key) in enumerate(ocr_items):
ttk.Label(ocr_frame, text=f"{label}:").grid(row=i, column=0, sticky="w", padx=5)
area = self.config.ocr_areas[key]
self.area_vars[key] = tk.StringVar(
value=f"({area['x1']},{area['y1']}) → ({area['x2']},{area['y2']})"
)
ttk.Label(ocr_frame, textvariable=self.area_vars[key]).grid(
row=i, column=1, padx=5, sticky="w"
)
ttk.Button(ocr_frame, text="Настроить",
command=lambda k=key: self._set_ocr_area(k)).grid(
row=i, column=2, padx=5
)
# Кнопки управления
control_frame = ttk.Frame(main_frame)
control_frame.grid(row=4, column=0, columnspan=3, pady=10)
# Режим
ttk.Label(control_frame, text="Режим:").grid(row=0, column=0, padx=5)
self.mode_var = tk.StringVar(value="ТЕСТ" if self.config.test_mode else "ВКЛ")
mode_combo = ttk.Combobox(control_frame, textvariable=self.mode_var,
values=['ТЕСТ', 'ВКЛ'], width=8, state='readonly')
mode_combo.grid(row=0, column=1, padx=5)
ttk.Button(control_frame, text="Проверить OCR",
command=self._test_ocr).grid(row=0, column=2, padx=5)
self.start_btn = ttk.Button(control_frame, text="▶ ЗАПУСК",
command=self._start_bot)
self.start_btn.grid(row=0, column=3, padx=5)
self.stop_btn = ttk.Button(control_frame, text="■ СТОП",
command=self._stop_bot, state='disabled')
self.stop_btn.grid(row=0, column=4, padx=5)
# Статус
status_frame = ttk.LabelFrame(main_frame, text="Статус", padding="5")
status_frame.grid(row=5, column=0, columnspan=3, sticky="ew", 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_frame, text="Сохранить настройки",
command=self._save_config).grid(row=6, column=0, columnspan=3, pady=10)
def _set_coordinate(self, key: str):
"""Установка координаты через клик"""
self.root.iconify()
time.sleep(0.3)
# Получаем позицию мыши
x, y = pyautogui.position()
# Обновляем настройки
self.config.coordinates[key]['x'] = x
self.config.coordinates[key]['y'] = y
# Обновляем отображение
self.coord_vars[key].set(f"X={x} Y={y}")
self.root.deiconify()
logger.info(f"Координата {key}: X={x} Y={y}")
def _set_ocr_area(self, key: str):
"""Установка OCR области через два клика"""
self.root.iconify()
time.sleep(0.3)
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_vars[key].set(f"({area['x1']},{area['y1']}) → ({area['x2']},{area['y2']})")
logger.info(f"OCR область {key}: {self.area_vars[key].get()}")
except Exception as e:
logger.error(f"Ошибка установки области: {e}")
self.root.deiconify()
def _test_ocr(self):
"""Тест OCR"""
try:
# Проверяем настройки областей
area = self.config.ocr_areas['prices']
if area['x1'] == area['x2'] or area['y1'] == area['y2']:
messagebox.showwarning("Ошибка", "Сначала настройте область цен!")
return
# Делаем скриншот
screenshot = ImageGrab.grab(bbox=(
area['x1'], area['y1'],
area['x2'], area['y2']
))
# Распознаём
prices = self.bot.ocr.extract_prices(screenshot)
# Показываем результат
result_text = "РЕЗУЛЬТАТЫ OCR ТЕСТА\n"
result_text += "=" * 40 + "\n"
result_text += f"Область: Цены\n"
result_text += f"Язык: {self.config.ocr_settings['lang']}\n"
result_text += f"Масштаб: {self.config.ocr_settings['scale']}%\n"
result_text += f"PSM: {self.config.ocr_settings['psm']}\n"
result_text += f"Попыток: {self.config.ocr_settings['attempts']}\n"
result_text += f"Порог: {self.config.ocr_settings['confidence_threshold']}%\n"
result_text += "=" * 40 + "\n\n"
if prices:
result_text += f"Найдено цен: {len(prices)}\n\n"
for p in prices:
result_text += 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_text += f"\nПодходящих: {len(valid)}\n"
result_text += f"Минимальная: {best['price']:,}"
else:
result_text += f"\nПодходящих: 0"
else:
result_text += "Цены не найдены!\n"
result_text += "Проверьте настройки OCR и область распознавания."
messagebox.showinfo("Тест OCR", result_text)
except Exception as e:
messagebox.showerror("Ошибка", f"Ошибка теста OCR: {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 ValueError:
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:
app = BotGUI()
app.root.mainloop()
except Exception as e:
logger.error(f"Ошибка запуска: {e}")
messagebox.showerror("Ошибка", f"Ошибка запуска приложения: {e}")
sys.exit(1)