Add auto-advance: click Next, scroll to top, capture next page

After each capture the controller asks the vision model to locate a 'Next'
control (returned as normalized screen coordinates), clicks it via OS input,
scrolls to top, and continues the loop. Configurable via SCREEN_LEADS_AUTO_NEXT
/ _NEXT_LOAD_PAUSE / _MAX_AUTO_NEXT, with a per-run safety cap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 07:56:13 +05:30
parent c1eef4c6aa
commit 511e36b660
6 changed files with 93 additions and 0 deletions
+28
View File
@@ -44,6 +44,7 @@ class CaptureController:
self.last_event = ""
self.captured_urls: set[str] = set()
self.leads_captured = 0
self._advances = 0
self._run_id: Optional[int] = None
# --- introspection -------------------------------------------------------
@@ -55,6 +56,8 @@ class CaptureController:
"leads_captured": self.leads_captured,
"captured_urls": len(self.captured_urls),
"on_invalid_page": config.ON_INVALID_PAGE,
"auto_next": config.AUTO_NEXT,
"advances": self._advances,
}
def _set(self, state: State, event: str = "") -> None:
@@ -71,6 +74,7 @@ class CaptureController:
self._pause.clear()
self.reason = ""
self.leads_captured = 0
self._advances = 0
self.captured_urls.clear()
self._run_id = database.start_run()
self._set(State.RUNNING, "started")
@@ -149,6 +153,9 @@ class CaptureController:
if url:
self.captured_urls.add(url)
self.last_event = f"saved: {lead.full_name or url}"
# Auto-advance: click a Next control, scroll to top, capture again.
if config.AUTO_NEXT and not self._stop.is_set() and self._advance_next():
continue
time.sleep(config.WATCH_INTERVAL)
@@ -181,6 +188,27 @@ class CaptureController:
database.upsert_lead(lead)
return lead
def _advance_next(self) -> bool:
"""Look for a 'Next' control on screen; if found, click it and scroll to
top so the loop captures the next page. Returns True if it advanced."""
if self._advances >= config.MAX_AUTO_NEXT:
self.last_event = f"auto-next cap ({config.MAX_AUTO_NEXT}) reached"
return False
try:
shot = screenshot.capture(prefix="next")
info = extractor.find_next_button(shot)
if not info.get("found"):
return False
self.last_event = f"clicking '{info.get('label') or 'Next'}' → next page"
scroller.click_at_fraction(float(info.get("x", 0)), float(info.get("y", 0)))
time.sleep(config.NEXT_LOAD_PAUSE)
scroller.scroll_to_top()
self._advances += 1
return True
except Exception as e:
self.last_event = f"next-button step skipped: {e}"
return False
# module-level singleton shared by the API
controller = CaptureController()