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