diff --git a/admin/routes.py b/admin/routes.py index 902adfa..19eab4d 100644 --- a/admin/routes.py +++ b/admin/routes.py @@ -86,6 +86,7 @@ class FlowConfigIn(BaseModel): target_item_ids: list[str] = [] target_url: str = "" scenario: str = "dynamic" + stop_on_first_click: bool = False def _to_db_dict(body: FlowConfigIn) -> dict[str, Any]: @@ -113,6 +114,7 @@ def _to_db_dict(body: FlowConfigIn) -> dict[str, Any]: "target_item_ids_json": body.target_item_ids, "target_url": body.target_url, "scenario": body.scenario, + "stop_on_first_click": int(body.stop_on_first_click), } diff --git a/admin/static/dashboard.html b/admin/static/dashboard.html index 2052851..caa7238 100644 --- a/admin/static/dashboard.html +++ b/admin/static/dashboard.html @@ -186,6 +186,11 @@ +
+ + +
+
@@ -1028,6 +1033,7 @@ function openNewForm() { document.getElementById('cfg-target-url').value = ''; document.getElementById('cfg-search-texts').value = ''; document.getElementById('cfg-item-ids').value = ''; + document.getElementById('cfg-stop-on-first-click').checked = false; _resetAdvanced(); document.getElementById('adv-details').removeAttribute('open'); _applyScenario('dynamic'); @@ -1069,6 +1075,7 @@ function openEditForm(id) { // DigiPay fields const dpMatch = (scenario === 'digipay') ? (_ist || 'onclick') : 'onclick'; document.getElementById('cfg-digipay-match').value = dpMatch; + document.getElementById('cfg-stop-on-first-click').checked = c.stop_on_first_click !== 0; document.getElementById('cfg-dp-max-scrolls').value = c.max_scrolls ?? 20; document.getElementById('cfg-dp-scroll-pause').value = c.scroll_pause_ms ?? 1500; document.getElementById('digipay-onclick-hint').style.display = dpMatch === 'onclick' ? '' : 'none'; @@ -1090,6 +1097,7 @@ function collectForm() { target_url: document.getElementById('cfg-target-url').value.trim(), search_texts: document.getElementById('cfg-search-texts').value.split('\n').map(s => s.trim()).filter(Boolean), target_item_ids: document.getElementById('cfg-item-ids').value.split('\n').map(s => s.trim()).filter(Boolean), + stop_on_first_click: document.getElementById('cfg-stop-on-first-click').checked, }; if (scenario === 'digipay') { diff --git a/crawler/dynamic_flow.py b/crawler/dynamic_flow.py index c680fa8..7320743 100644 --- a/crawler/dynamic_flow.py +++ b/crawler/dynamic_flow.py @@ -136,6 +136,9 @@ class DynamicFlow: click_step = self._run_click(item_id, scroll_step.data.get("element")) result.steps.append(click_step) + if click_step.success and self.cfg.get("stop_on_first_click", 0): + log.info("Item '%s' clicked — stopping flow", item_id) + return result human_delay(1.0, 2.5) log.info( diff --git a/db/connection.py b/db/connection.py index cf2def3..cf9538e 100644 --- a/db/connection.py +++ b/db/connection.py @@ -97,7 +97,8 @@ def _migrate(conn: sqlite3.Connection) -> None: "max_pages": "INTEGER NOT NULL DEFAULT 10", "pagination_wait_ms": "INTEGER NOT NULL DEFAULT 1500", "target_url": "TEXT NOT NULL DEFAULT ''", - "scenario": "TEXT NOT NULL DEFAULT 'dynamic'", + "scenario": "TEXT NOT NULL DEFAULT 'dynamic'", + "stop_on_first_click": "INTEGER NOT NULL DEFAULT 0", } for col, definition in additions.items(): if col not in existing: diff --git a/db/repositories/flow_configs.py b/db/repositories/flow_configs.py index 881aee6..10b214b 100644 --- a/db/repositories/flow_configs.py +++ b/db/repositories/flow_configs.py @@ -17,7 +17,7 @@ _FIELDS = [ "max_scrolls", "scroll_pause_ms", "no_new_content_timeout_ms", "pagination_selector", "pagination_selector_type", "max_pages", "pagination_wait_ms", "item_selector_template", "item_selector_type", "item_url_template", "target_item_ids_json", - "target_url", "scenario", + "target_url", "scenario", "stop_on_first_click", ]