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


#NoEnv
#SingleInstance Force
#Persistent
SendMode Input
SetWorkingDir, %A_ScriptDir%
SetBatchLines, -1

CoordMode, Mouse, Screen
CoordMode, Pixel, Screen


; ============================================================
; GDI+
; ============================================================
;
; GDI+ оставляем подключённым.
; В этой версии GDI+ НЕ используется для создания скриншота,
; потому что Windows BitBlt у тебя работает надёжнее.
;
; ============================================================

#Include %A_ScriptDir%\Gdip_All.ahk


; ============================================================
; TESSERACT OCR
; ============================================================

; Путь к Tesseract.
; Если у тебя он установлен в другом месте — измени эту строку.

TesseractPath := "C:\Program Files\Tesseract-OCR\tesseract.exe"


; Языки OCR.
;
; rus = русский
; eng = английский
;
OCRLanguage := "rus+eng"


; ============================================================
; НАСТРОЙКИ ЦЕНЫ
; ============================================================

; Максимальная допустимая цена.
;
; Скрипт будет принимать только цены:
;
; МЕНЬШЕ 50000
;
MaxPrice := 50000


; ============================================================
; 1. ПОЛЕ ПОИСКА
; ============================================================

; Координаты поля, куда вводится текст.

InputX := 500
InputY := 300


; Что именно вводить.

SearchText := "НУЖНЫЙ ТЕКСТ"


; ============================================================
; 2. КНОПКА ОБНОВЛЕНИЯ
; ============================================================

RefreshX := 700
RefreshY := 300


; ============================================================
; 3. ОБЛАСТЬ OCR
; ============================================================
;
; Именно эта область экрана будет передаваться Tesseract.
;
; X/Y = левый верхний угол.
;
; Width/Height = размер области.
;
; Пример:
;
; X = 400
; Y = 400
; Width = 700
; Height = 500
;
; Значит будет анализироваться область:
;
; от 400 до 1100 по X
; от 400 до 900 по Y
;
; ============================================================

OCRX := 400
OCRY := 400

OCRWidth := 700
OCRHeight := 500


; ============================================================
; 4. КНОПКА ПОДТВЕРЖДЕНИЯ
; ============================================================

ConfirmX := 1000
ConfirmY := 800


; ============================================================
; 5. ПОЛЗУНОК
; ============================================================

ScrollBarX := 1200
ScrollBarY := 500


; На сколько пикселей опускать ползунок.

ScrollPixels := 150


; Сколько раз прокручивать одну страницу.

MaxScrollSteps := 5


; ============================================================
; 6. ВТОРАЯ СТРАНИЦА
; ============================================================

Page2X := 1100
Page2Y := 850


; ============================================================
; ЗАДЕРЖКИ
; ============================================================

MoveDelay := 80

ClickDelay := 250

RefreshDelay := 600

InputDelay := 250

ScrollDelay := 350

PageDelay := 600

SelectDelay := 300


; ============================================================
; СЛУЖЕБНЫЕ ПЕРЕМЕННЫЕ
; ============================================================

ScriptRunning := false

CurrentPage := 1

CurrentScrollStep := 0


; ============================================================
; F3 — ЗАПУСК
; ============================================================

F3::

if (ScriptRunning)
    return

ScriptRunning := true

CurrentPage := 1
CurrentScrollStep := 0

ToolTip, АУКЦИОН: ЗАПУЩЕН
Sleep, 600
ToolTip

SetTimer, MainLoop, -10

return


; ============================================================
; F6 — ОСТАНОВКА
; ============================================================

F6::

ScriptRunning := false

ToolTip, АУКЦИОН: ОСТАНОВЛЕН
Sleep, 600
ToolTip

return


; ============================================================
; ОСНОВНОЙ ЦИКЛ
; ============================================================

MainLoop:

if (!ScriptRunning)
    return


; ============================================================
; ПУНКТ 1
; ВВОДИМ НАЗВАНИЕ ТОВАРА
; ============================================================

MouseMove, %InputX%, %InputY%, 0

Sleep, %MoveDelay%

Click

Sleep, %ClickDelay%

Send, ^a

Sleep, 80

SendInput, %SearchText%

Sleep, %InputDelay%


; ============================================================
; ПУНКТ 2
; ОБНОВЛЯЕМ СПИСОК
; ============================================================

MouseMove, %RefreshX%, %RefreshY%, 0

Sleep, %MoveDelay%

Click

Sleep, %RefreshDelay%


; Начинаем с первой страницы.

CurrentPage := 1

CurrentScrollStep := 0


Goto, SearchPage1

return



; ============================================================
; ПОИСК НА ПЕРВОЙ СТРАНИЦЕ
; ============================================================

SearchPage1:

if (!ScriptRunning)
    return


; ============================================================
; OCR
; ============================================================

OCRResult := OCR_GetPrices(
    OCRX,
    OCRY,
    OCRWidth,
    OCRHeight,
    TesseractPath,
    OCRLanguage,
    MaxPrice
)


; ============================================================
; НАШЛИ ЦЕНУ
; ============================================================

if (OCRResult != "")
{
    StringSplit, Found, OCRResult, |

    FoundX := Found1
    FoundY := Found2
    FoundPrice := Found3


    ToolTip, НАЙДЕНА ЦЕНА: %FoundPrice%

    Sleep, 500

    ToolTip


    ; Кликаем по найденной цене.

    MouseMove, %FoundX%, %FoundY%, 0

    Sleep, %MoveDelay%

    Click

    Sleep, %SelectDelay%


    ; Нажимаем подтверждение.

    MouseMove, %ConfirmX%, %ConfirmY%, 0

    Sleep, %MoveDelay%

    Click

    Sleep, %RefreshDelay%


    ; Начинаем новый цикл.

    CurrentPage := 1

    CurrentScrollStep := 0

    SetTimer, MainLoop, -10

    return
}


; ============================================================
; ЦЕНА НЕ НАЙДЕНА
; ============================================================

CurrentScrollStep++


if (CurrentScrollStep <= MaxScrollSteps)
{
    ScrollListDown()

    Sleep, %ScrollDelay%

    Goto, SearchPage1
}


; ============================================================
; КОНЕЦ ПЕРВОЙ СТРАНИЦЫ
; ПЕРЕХОДИМ НА ВТОРУЮ
; ============================================================

CurrentPage := 2

CurrentScrollStep := 0


MouseMove, %Page2X%, %Page2Y%, 0

Sleep, %MoveDelay%

Click

Sleep, %PageDelay%


Goto, SearchPage2

return



; ============================================================
; ПОИСК НА ВТОРОЙ СТРАНИЦЕ
; ============================================================

SearchPage2:

if (!ScriptRunning)
    return


; ============================================================
; OCR
; ============================================================

OCRResult := OCR_GetPrices(
    OCRX,
    OCRY,
    OCRWidth,
    OCRHeight,
    TesseractPath,
    OCRLanguage,
    MaxPrice
)


; ============================================================
; НАШЛИ ЦЕНУ
; ============================================================

if (OCRResult != "")
{
    StringSplit, Found, OCRResult, |

    FoundX := Found1
    FoundY := Found2
    FoundPrice := Found3


    ToolTip, НАЙДЕНА ЦЕНА: %FoundPrice%

    Sleep, 500

    ToolTip


    ; Кликаем по найденной цене.

    MouseMove, %FoundX%, %FoundY%, 0

    Sleep, %MoveDelay%

    Click

    Sleep, %SelectDelay%


    ; Подтверждаем.

    MouseMove, %ConfirmX%, %ConfirmY%, 0

    Sleep, %MoveDelay%

    Click

    Sleep, %RefreshDelay%


    ; Новый цикл.

    CurrentPage := 1

    CurrentScrollStep := 0

    SetTimer, MainLoop, -10

    return
}


; ============================================================
; ЦЕНА НЕ НАЙДЕНА
; ============================================================

CurrentScrollStep++


if (CurrentScrollStep <= MaxScrollSteps)
{
    ScrollListDown()

    Sleep, %ScrollDelay%

    Goto, SearchPage2
}


; ============================================================
; ВТОРАЯ СТРАНИЦА ЗАКОНЧИЛАСЬ
; ============================================================
;
; Ничего не нашли.
;
; Возвращаемся к обновлению списка.
;
; ============================================================

CurrentPage := 1

CurrentScrollStep := 0


MouseMove, %RefreshX%, %RefreshY%, 0

Sleep, %MoveDelay%

Click

Sleep, %RefreshDelay%


Goto, SearchPage1

return



; ============================================================
; ФУНКЦИЯ ПРОКРУТКИ
; ============================================================

ScrollListDown()
{
    global ScrollBarX
    global ScrollBarY
    global ScrollPixels
    global MoveDelay


    MouseMove, %ScrollBarX%, %ScrollBarY%, 0

    Sleep, %MoveDelay%


    ; Зажимаем левую кнопку.

    Click, Down

    Sleep, 80


    ; Перемещаем ползунок.

    NewScrollY := ScrollBarY + ScrollPixels

    MouseMove, %ScrollBarX%, %NewScrollY%, 300


    ; Отпускаем кнопку.

    Click, Up
}



; ============================================================
; OCR_GetPrices
; ============================================================
;
; 1. Делает скриншот области через BitBlt.
;
; 2. Сохраняет его в BMP.
;
; 3. Передаёт BMP Tesseract.
;
; 4. Получает TSV.
;
; 5. Ищет числовое значение меньше MaxPrice.
;
; 6. Возвращает:
;
; X|Y|PRICE
;
; ============================================================

OCR_GetPrices(X, Y, Width, Height, TesseractPath, Language, MaxPrice)
{
    global


    ; ========================================================
    ; Временные файлы
    ; ========================================================

    TempImage := A_Temp "\auction_ocr.bmp"

    TempBase := A_Temp "\auction_ocr_result"

    TSVFile := TempBase ".tsv"


    ; Удаляем старые файлы.

    FileDelete, %TempImage%

    FileDelete, %TSVFile%


    ; ========================================================
    ; ДЕЛАЕМ BMP СКРИНШОТ
    ; ========================================================

    if (!CaptureScreenToBMP(X, Y, Width, Height, TempImage))
    {
        ToolTip, ОШИБКА: НЕ УДАЛОСЬ СДЕЛАТЬ СКРИНШОТ

        Sleep, 1000

        ToolTip

        return ""
    }


    ; ========================================================
    ; ПРОВЕРЯЕМ TESSERACT
    ; ========================================================

    if (!FileExist(TesseractPath))
    {
        MsgBox, 16, Ошибка Tesseract, Tesseract.exe не найден.`n`nПуть:`n%TesseractPath%

        return ""
    }


    ; ========================================================
    ; ЗАПУСК TESSERACT
    ; ========================================================
    ;
    ; BMP → TSV
    ;
    ; TSV содержит:
    ;
    ; текст
    ; X
    ; Y
    ; ширину
    ; высоту
    ;
    ; ========================================================

    RunWait, "%TesseractPath%" "%TempImage%" "%TempBase%" -l %Language% --psm 6 tsv,, Hide


    ; ========================================================
    ; ПРОВЕРЯЕМ TSV
    ; ========================================================

    if (!FileExist(TSVFile))
        return ""


    FileRead, TSV, %TSVFile%

    if (ErrorLevel)
        return ""


    ; ========================================================
    ; ОБРАБАТЫВАЕМ TSV
    ; ========================================================

    Loop, Parse, TSV, `n, `r
    {
        Line := A_LoopField


        if (Line = "")
            continue


        ; Пропускаем заголовок.

        if (InStr(Line, "level" . A_Tab . "page_num"))
            continue


        ; Разделяем TSV.

        StringSplit, Col, Line, %A_Tab%


        ; В Tesseract TSV:

        ; Col6  = X
        ; Col7  = Y
        ; Col8  = Width
        ; Col9  = Height
        ; Col12 = Text


        if (Col12 = "")
            continue


        Text := Trim(Col12)


        if (Text = "")
            continue


        ; ====================================================
        ; ПРОВЕРЯЕМ, ПОХОЖ ЛИ ТЕКСТ НА ЦЕНУ
        ; ====================================================

        ; Убираем валютные символы.

        CleanPrice := Text

        CleanPrice := StrReplace(CleanPrice, "₽", "")
        CleanPrice := StrReplace(CleanPrice, "$", "")
        CleanPrice := StrReplace(CleanPrice, "€", "")


        ; Убираем пробелы.

        CleanPrice := StrReplace(CleanPrice, " ", "")


        ; Убираем разделители тысяч.

        CleanPrice := StrReplace(CleanPrice, ".", "")
        CleanPrice := StrReplace(CleanPrice, ",", "")


        ; Оставляем только цифры.

        CleanPrice := RegExReplace(CleanPrice, "[^\d]")


        if (CleanPrice = "")
            continue


        ; ====================================================
        ; ПРОВЕРЯЕМ ЦЕНУ
        ; ====================================================

        Price := CleanPrice + 0


        if (Price <= 0)
            continue


        if (Price >= MaxPrice)
            continue


        ; ====================================================
        ; НАШЛИ ПОДХОДЯЩУЮ ЦЕНУ
        ; ====================================================

        FoundX := X + Col6 + (Col8 / 2)

        FoundY := Y + Col7 + (Col9 / 2)


        return FoundX "|" FoundY "|" Price
    }


    return ""
}



; ============================================================
; CaptureScreenToBMP
; ============================================================
;
; Делает скриншот через Windows BitBlt.
;
; Это тот способ, который мы уже проверили:
;
; BitBlt → HBITMAP → BMP
;
; GDI+ здесь НЕ используется.
;
; ============================================================

CaptureScreenToBMP(X, Y, Width, Height, FilePath)
{
    ; ========================================================
    ; Получаем DC экрана
    ; ========================================================

    hdcScreen := DllCall("GetDC", "Ptr", 0, "Ptr")


    if (!hdcScreen)
        return false


    ; ========================================================
    ; Создаём Memory DC
    ; ========================================================

    hdcMem := DllCall("gdi32\CreateCompatibleDC", "Ptr", hdcScreen, "Ptr")


    if (!hdcMem)
    {
        DllCall("ReleaseDC", "Ptr", 0, "Ptr", hdcScreen)

        return false
    }


    ; ========================================================
    ; Создаём Bitmap
    ; ========================================================

    hBitmap := DllCall("gdi32\CreateCompatibleBitmap", "Ptr", hdcScreen, "Int", Width, "Int", Height, "Ptr")


    if (!hBitmap)
    {
        DllCall("gdi32\DeleteDC", "Ptr", hdcMem)

        DllCall("ReleaseDC", "Ptr", 0, "Ptr", hdcScreen)

        return false
    }


    ; ========================================================
    ; Выбираем Bitmap
    ; ========================================================

    hOld := DllCall("gdi32\SelectObject", "Ptr", hdcMem, "Ptr", hBitmap, "Ptr")


    ; ========================================================
    ; Делаем захват
    ; ========================================================

    Result := DllCall("gdi32\BitBlt", "Ptr", hdcMem, "Int", 0, "Int", 0, "Int", Width, "Int", Height, "Ptr", hdcScreen, "Int", X, "Int", Y, "UInt", 0x00CC0020)


    if (!Result)
    {
        DllCall("gdi32\SelectObject", "Ptr", hdcMem, "Ptr", hOld)

        DllCall("gdi32\DeleteObject", "Ptr", hBitmap)

        DllCall("gdi32\DeleteDC", "Ptr", hdcMem)

        DllCall("ReleaseDC", "Ptr", 0, "Ptr", hdcScreen)

        return false
    }


    ; ========================================================
    ; Готовим BITMAPINFO
    ; ========================================================

    VarSetCapacity(BitmapInfo, 40, 0)

    NumPut(40, BitmapInfo, 0, "UInt")

    NumPut(Width, BitmapInfo, 4, "Int")

    NumPut(Height, BitmapInfo, 8, "Int")

    NumPut(1, BitmapInfo, 12, "UShort")

    NumPut(32, BitmapInfo, 14, "UShort")

    NumPut(0, BitmapInfo, 16, "UInt")

    ImageSize := Width * Height * 4

    NumPut(ImageSize, BitmapInfo, 20, "UInt")


    ; ========================================================
    ; Выделяем память под пиксели
    ; ========================================================

    VarSetCapacity(Pixels, ImageSize, 0)


    ; ========================================================
    ; Возвращаем старый Bitmap
    ; ========================================================

    DllCall("gdi32\SelectObject", "Ptr", hdcMem, "Ptr", hOld)


    ; ========================================================
    ; Получаем пиксели
    ; ========================================================

    Result := DllCall("gdi32\GetDIBits", "Ptr", hdcScreen, "Ptr", hBitmap, "UInt", 0, "UInt", Height, "Ptr", &Pixels, "Ptr", &BitmapInfo, "UInt", 0)


    if (!Result)
    {
        DllCall("gdi32\DeleteObject", "Ptr", hBitmap)

        DllCall("gdi32\DeleteDC", "Ptr", hdcMem)

        DllCall("ReleaseDC", "Ptr", 0, "Ptr", hdcScreen)

        return false
    }


    ; ========================================================
    ; Освобождаем Windows resources
    ; ========================================================

    DllCall("gdi32\DeleteObject", "Ptr", hBitmap)

    DllCall("gdi32\DeleteDC", "Ptr", hdcMem)

    DllCall("ReleaseDC", "Ptr", 0, "Ptr", hdcScreen)


    ; ========================================================
    ; BMP HEADER
    ; ========================================================

    FileSize := 54 + ImageSize


    VarSetCapacity(FileHeader, 14, 0)


    NumPut(0x4D42, FileHeader, 0, "UShort")

    NumPut(FileSize, FileHeader, 2, "UInt")

    NumPut(0, FileHeader, 6, "UShort")

    NumPut(0, FileHeader, 8, "UShort")

    NumPut(54, FileHeader, 10, "UInt")


    ; ========================================================
    ; СОЗДАЁМ BMP
    ; ========================================================

    FileDelete, %FilePath%


    File := FileOpen(FilePath, "w")


    if (!IsObject(File))
        return false


    File.RawWrite(FileHeader, 14)

    File.RawWrite(BitmapInfo, 40)

    File.RawWrite(Pixels, ImageSize)

    File.Close()


    ; ========================================================
    ; ПРОВЕРЯЕМ
    ; ========================================================

    if (!FileExist(FilePath))
        return false


    FileGetSize, CheckSize, %FilePath%


    if (CheckSize <= 54)
        return false


    return true
}