from __future__ import annotations import time from dataclasses import dataclass from typing import Optional import pyautogui from core.ocr_client import UmiClient from utils.screenshot import capture_screen @dataclass(frozen=True) class MatchResult: # 屏幕坐标(可直接点击) x: int y: int # 命中的文字(当前实现为目标文字本身;若后续做模糊匹配可返回实际匹配串) text: str class ActionRunner: def __init__(self, ocr: UmiClient, *, prefer_mss: bool = True, default_region: Optional[tuple[int, int, int, int]] = None): self.ocr = ocr self.prefer_mss = prefer_mss self.default_region = default_region def locate_text( self, text: str, *, region: Optional[tuple[int, int, int, int]] = None, exact: bool = True, case_sensitive: bool = False, ) -> Optional[MatchResult]: cap = capture_screen(region or self.default_region, prefer_mss=self.prefer_mss) items = self.ocr.ocr_bytes(cap.image_bytes) pt = self.ocr.find_text(text, items, exact=exact, case_sensitive=case_sensitive) if pt is None: return None img_x, img_y = pt left, top, _, _ = cap.region # OCR 坐标是“截图图片像素坐标”,需先缩放到屏幕坐标,再加上截图区域偏移 scr_x = int(left + img_x * cap.scale_x) scr_y = int(top + img_y * cap.scale_y) return MatchResult(x=scr_x, y=scr_y, text=text) def click_text( self, text: str, *, region: Optional[tuple[int, int, int, int]] = None, exact: bool = True, case_sensitive: bool = False, clicks: int = 1, interval: float = 0.05, button: str = "left", move_duration: float = 0.0, pause: float = 0.05, ) -> MatchResult: pyautogui.PAUSE = pause m = self.locate_text(text, region=region, exact=exact, case_sensitive=case_sensitive) if m is None: raise TimeoutError(f"未找到文字:{text}") pyautogui.moveTo(m.x, m.y, duration=move_duration) pyautogui.click(x=m.x, y=m.y, clicks=clicks, interval=interval, button=button) return m def wait_for_text( self, text: str, *, timeout: float = 20.0, interval: float = 0.5, region: Optional[tuple[int, int, int, int]] = None, exact: bool = True, case_sensitive: bool = False, ) -> MatchResult: end = time.time() + timeout while time.time() < end: m = self.locate_text(text, region=region, exact=exact, case_sensitive=case_sensitive) if m is not None: return m time.sleep(interval) raise TimeoutError(f"等待超时:{timeout}s 内未出现文字:{text}")