Загрузка данных
#!/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 для выхода...")