PyClickerRPA 0.5.0__tar.gz → 0.9.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/PKG-INFO +22 -12
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/PyClickerRPA.egg-info/PKG-INFO +22 -12
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/PyClickerRPA.egg-info/SOURCES.txt +25 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/README.md +20 -10
- pyclickerrpa-0.9.0/autoclicker/actions.py +450 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/autoclicker/adb.py +36 -11
- pyclickerrpa-0.9.0/autoclicker/capture.py +165 -0
- pyclickerrpa-0.9.0/autoclicker/clicker.py +280 -0
- pyclickerrpa-0.9.0/autoclicker/controllers.py +594 -0
- pyclickerrpa-0.9.0/autoclicker/expr.py +177 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/autoclicker/gui/flow_connection.py +3 -1
- pyclickerrpa-0.9.0/autoclicker/gui/flow_editor.py +602 -0
- pyclickerrpa-0.9.0/autoclicker/gui/flow_node.py +149 -0
- pyclickerrpa-0.9.0/autoclicker/gui/hotkeys.py +125 -0
- pyclickerrpa-0.9.0/autoclicker/gui/log_panel.py +39 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/autoclicker/gui/main_window.py +1183 -1012
- pyclickerrpa-0.9.0/autoclicker/gui/property_panel.py +875 -0
- pyclickerrpa-0.9.0/autoclicker/gui/qt_runner.py +248 -0
- pyclickerrpa-0.9.0/autoclicker/gui/queue_panel.py +65 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/autoclicker/gui/recorder.py +222 -33
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/autoclicker/gui/snippet_library.py +13 -4
- pyclickerrpa-0.9.0/autoclicker/gui/step_list_widget.py +257 -0
- pyclickerrpa-0.9.0/autoclicker/gui/task_widget.py +9 -0
- pyclickerrpa-0.9.0/autoclicker/gui/var_panel.py +32 -0
- pyclickerrpa-0.9.0/autoclicker/lint.py +203 -0
- pyclickerrpa-0.9.0/autoclicker/matcher.py +430 -0
- pyclickerrpa-0.9.0/autoclicker/migrations.py +187 -0
- pyclickerrpa-0.9.0/autoclicker/models.py +1417 -0
- pyclickerrpa-0.9.0/autoclicker/ocr.py +300 -0
- pyclickerrpa-0.9.0/autoclicker/recognizers.py +523 -0
- pyclickerrpa-0.9.0/autoclicker/reports.py +150 -0
- pyclickerrpa-0.9.0/autoclicker/resources.py +86 -0
- pyclickerrpa-0.9.0/autoclicker/run.py +127 -0
- pyclickerrpa-0.9.0/autoclicker/runner.py +1350 -0
- pyclickerrpa-0.9.0/autoclicker/sandbox.py +443 -0
- pyclickerrpa-0.9.0/autoclicker/task_queue.py +351 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/autoclicker/testkit/__main__.py +11 -4
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/autoclicker/testkit/assertions.py +99 -23
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/autoclicker/testkit/case.py +25 -2
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/autoclicker/testkit/loader.py +5 -2
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/autoclicker/testkit/report.py +70 -22
- pyclickerrpa-0.9.0/autoclicker/testkit/runner.py +309 -0
- pyclickerrpa-0.9.0/autoclicker/testkit/visual.py +135 -0
- pyclickerrpa-0.9.0/autoclicker/web/run_manager.py +380 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/autoclicker/web/server.py +88 -45
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/autoclicker/web/static/app.js +25 -5
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/autoclicker/web/static/index.html +2 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/autoclicker/web/static/style.css +1 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/pyproject.toml +2 -2
- pyclickerrpa-0.9.0/tests/test_actions.py +612 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/tests/test_adb.py +23 -8
- pyclickerrpa-0.9.0/tests/test_engine_integration.py +357 -0
- pyclickerrpa-0.9.0/tests/test_engine_services.py +867 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/tests/test_flow_branch.py +26 -18
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/tests/test_lint.py +45 -0
- pyclickerrpa-0.9.0/tests/test_listener_lifecycle.py +384 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/tests/test_matcher.py +88 -1
- pyclickerrpa-0.9.0/tests/test_migrations.py +128 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/tests/test_models.py +4 -2
- pyclickerrpa-0.9.0/tests/test_phase4_integration.py +985 -0
- pyclickerrpa-0.9.0/tests/test_platform_recognition.py +1552 -0
- pyclickerrpa-0.9.0/tests/test_queue_entrypoints.py +289 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/tests/test_recorder_gui.py +188 -6
- pyclickerrpa-0.9.0/tests/test_sandbox.py +738 -0
- pyclickerrpa-0.9.0/tests/test_structured_flow.py +497 -0
- pyclickerrpa-0.9.0/tests/test_structured_gui.py +223 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/tests/test_testkit.py +34 -0
- pyclickerrpa-0.9.0/tests/test_testkit_contracts.py +393 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/tests/test_web.py +22 -10
- pyclickerrpa-0.5.0/autoclicker/capture.py +0 -110
- pyclickerrpa-0.5.0/autoclicker/clicker.py +0 -212
- pyclickerrpa-0.5.0/autoclicker/expr.py +0 -94
- pyclickerrpa-0.5.0/autoclicker/gui/flow_editor.py +0 -524
- pyclickerrpa-0.5.0/autoclicker/gui/flow_node.py +0 -156
- pyclickerrpa-0.5.0/autoclicker/gui/hotkeys.py +0 -40
- pyclickerrpa-0.5.0/autoclicker/gui/qt_runner.py +0 -91
- pyclickerrpa-0.5.0/autoclicker/gui/task_widget.py +0 -666
- pyclickerrpa-0.5.0/autoclicker/lint.py +0 -163
- pyclickerrpa-0.5.0/autoclicker/matcher.py +0 -262
- pyclickerrpa-0.5.0/autoclicker/models.py +0 -523
- pyclickerrpa-0.5.0/autoclicker/ocr.py +0 -107
- pyclickerrpa-0.5.0/autoclicker/run.py +0 -73
- pyclickerrpa-0.5.0/autoclicker/runner.py +0 -971
- pyclickerrpa-0.5.0/autoclicker/testkit/runner.py +0 -147
- pyclickerrpa-0.5.0/autoclicker/testkit/visual.py +0 -94
- pyclickerrpa-0.5.0/autoclicker/web/run_manager.py +0 -181
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/LICENSE +0 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/PyClickerRPA.egg-info/dependency_links.txt +0 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/PyClickerRPA.egg-info/entry_points.txt +0 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/PyClickerRPA.egg-info/requires.txt +0 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/PyClickerRPA.egg-info/top_level.txt +0 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/autoclicker/__init__.py +0 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/autoclicker/app.py +0 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/autoclicker/diagnose.py +0 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/autoclicker/dpi.py +0 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/autoclicker/gui/__init__.py +0 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/autoclicker/gui/lint_dialog.py +0 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/autoclicker/gui/monitor.py +0 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/autoclicker/gui/preview_label.py +0 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/autoclicker/gui/scheduler_widget.py +0 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/autoclicker/gui/testkit_widget.py +0 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/autoclicker/templatize.py +0 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/autoclicker/testkit/__init__.py +0 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/autoclicker/testkit/pytest_plugin.py +0 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/autoclicker/web/__init__.py +0 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/autoclicker/web/__main__.py +0 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/autoclicker/web/scheduler.py +0 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/autoclicker/web/serialize.py +0 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/autoclicker/windows.py +0 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/setup.cfg +0 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/tests/test_diagnose.py +0 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/tests/test_diagnose_gui.py +0 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/tests/test_runner.py +0 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/tests/test_templatize.py +0 -0
- {pyclickerrpa-0.5.0 → pyclickerrpa-0.9.0}/tests/test_testkit_gui.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyClickerRPA
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Visual UI automation & testing tool: Windows + Android(ADB) automation, robust/self-healing image matching, OCR,
|
|
3
|
+
Version: 0.9.0
|
|
4
|
+
Summary: Visual UI automation & testing tool: Windows + Android(ADB) automation, robust/self-healing image matching, OCR, structured conditions and loops with array iteration, node flow editor, smart recorder, task health checks and failure attribution, a visual test framework (assertions, visual regression, HTML/JUnit reports) and a web console.
|
|
5
5
|
Author: Equinox
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/Equinox/PyClickerRPA
|
|
@@ -44,8 +44,8 @@ ADB) input — then turn those flows into a **visual test framework** with
|
|
|
44
44
|
assertions, visual-regression baselines, HTML/JUnit reports, and a **web console**
|
|
45
45
|
for remote runs.
|
|
46
46
|
|
|
47
|
-
Python/PySide6, started as a port of a Qt/C++ AutoClicker
|
|
48
|
-
|
|
47
|
+
Python/PySide6, started as a port of a Qt/C++ AutoClicker. Legacy `.json` tasks
|
|
48
|
+
remain supported; structured control-flow blocks are a Python-only extension.
|
|
49
49
|
|
|
50
50
|
## Install
|
|
51
51
|
|
|
@@ -91,14 +91,24 @@ autoclicker-web --suites-dir examples # web console at http://127.0.0.1
|
|
|
91
91
|
|
|
92
92
|
## Authoring (desktop GUI)
|
|
93
93
|
|
|
94
|
-
Drag-and-drop **node flow graph**
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
94
|
+
Drag-and-drop **node flow graph** and an interoperable **step list**, with
|
|
95
|
+
nested **If/Else**, **While**, fixed-count loops, **ForEach** arrays, and
|
|
96
|
+
**Break/Continue** — no step numbers required. Double-click a block to edit its
|
|
97
|
+
body or branches. Legacy condition nodes retain their true/false ports.
|
|
98
|
+
|
|
99
|
+
Select consecutive steps and choose **包进… → 遍历数组** to reuse them for each
|
|
100
|
+
item. Enter a custom JSON array or generate a numeric range, then reference the
|
|
101
|
+
current item in text actions, assignments, and conditions. For example, iterate
|
|
102
|
+
`[1,2,3,4,5,6,7,8,9,10]` with variable `server`, click **`{server}服`**, perform
|
|
103
|
+
the actions, and return to the server-selection screen inside the same body.
|
|
104
|
+
Enable exact text matching so `1服` does not also match `11服`. A failed action
|
|
105
|
+
inside a block stops execution unless its failure policy explicitly skips it.
|
|
106
|
+
|
|
107
|
+
Also includes a snippet library, task packages (`.zip` with nested templates),
|
|
108
|
+
on-preview coordinate/color picking, template crop, single-step test run;
|
|
109
|
+
global hotkeys (F6 run/stop, ESC stop), scheduler, and an image/color
|
|
110
|
+
**monitor/trigger** with tray icon; a **Test** tab to load and run visual test
|
|
111
|
+
suites.
|
|
102
112
|
|
|
103
113
|
### Smart recorder
|
|
104
114
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyClickerRPA
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Visual UI automation & testing tool: Windows + Android(ADB) automation, robust/self-healing image matching, OCR,
|
|
3
|
+
Version: 0.9.0
|
|
4
|
+
Summary: Visual UI automation & testing tool: Windows + Android(ADB) automation, robust/self-healing image matching, OCR, structured conditions and loops with array iteration, node flow editor, smart recorder, task health checks and failure attribution, a visual test framework (assertions, visual regression, HTML/JUnit reports) and a web console.
|
|
5
5
|
Author: Equinox
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/Equinox/PyClickerRPA
|
|
@@ -44,8 +44,8 @@ ADB) input — then turn those flows into a **visual test framework** with
|
|
|
44
44
|
assertions, visual-regression baselines, HTML/JUnit reports, and a **web console**
|
|
45
45
|
for remote runs.
|
|
46
46
|
|
|
47
|
-
Python/PySide6, started as a port of a Qt/C++ AutoClicker
|
|
48
|
-
|
|
47
|
+
Python/PySide6, started as a port of a Qt/C++ AutoClicker. Legacy `.json` tasks
|
|
48
|
+
remain supported; structured control-flow blocks are a Python-only extension.
|
|
49
49
|
|
|
50
50
|
## Install
|
|
51
51
|
|
|
@@ -91,14 +91,24 @@ autoclicker-web --suites-dir examples # web console at http://127.0.0.1
|
|
|
91
91
|
|
|
92
92
|
## Authoring (desktop GUI)
|
|
93
93
|
|
|
94
|
-
Drag-and-drop **node flow graph**
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
94
|
+
Drag-and-drop **node flow graph** and an interoperable **step list**, with
|
|
95
|
+
nested **If/Else**, **While**, fixed-count loops, **ForEach** arrays, and
|
|
96
|
+
**Break/Continue** — no step numbers required. Double-click a block to edit its
|
|
97
|
+
body or branches. Legacy condition nodes retain their true/false ports.
|
|
98
|
+
|
|
99
|
+
Select consecutive steps and choose **包进… → 遍历数组** to reuse them for each
|
|
100
|
+
item. Enter a custom JSON array or generate a numeric range, then reference the
|
|
101
|
+
current item in text actions, assignments, and conditions. For example, iterate
|
|
102
|
+
`[1,2,3,4,5,6,7,8,9,10]` with variable `server`, click **`{server}服`**, perform
|
|
103
|
+
the actions, and return to the server-selection screen inside the same body.
|
|
104
|
+
Enable exact text matching so `1服` does not also match `11服`. A failed action
|
|
105
|
+
inside a block stops execution unless its failure policy explicitly skips it.
|
|
106
|
+
|
|
107
|
+
Also includes a snippet library, task packages (`.zip` with nested templates),
|
|
108
|
+
on-preview coordinate/color picking, template crop, single-step test run;
|
|
109
|
+
global hotkeys (F6 run/stop, ESC stop), scheduler, and an image/color
|
|
110
|
+
**monitor/trigger** with tray icon; a **Test** tab to load and run visual test
|
|
111
|
+
suites.
|
|
102
112
|
|
|
103
113
|
### Smart recorder
|
|
104
114
|
|
|
@@ -8,19 +8,27 @@ PyClickerRPA.egg-info/entry_points.txt
|
|
|
8
8
|
PyClickerRPA.egg-info/requires.txt
|
|
9
9
|
PyClickerRPA.egg-info/top_level.txt
|
|
10
10
|
autoclicker/__init__.py
|
|
11
|
+
autoclicker/actions.py
|
|
11
12
|
autoclicker/adb.py
|
|
12
13
|
autoclicker/app.py
|
|
13
14
|
autoclicker/capture.py
|
|
14
15
|
autoclicker/clicker.py
|
|
16
|
+
autoclicker/controllers.py
|
|
15
17
|
autoclicker/diagnose.py
|
|
16
18
|
autoclicker/dpi.py
|
|
17
19
|
autoclicker/expr.py
|
|
18
20
|
autoclicker/lint.py
|
|
19
21
|
autoclicker/matcher.py
|
|
22
|
+
autoclicker/migrations.py
|
|
20
23
|
autoclicker/models.py
|
|
21
24
|
autoclicker/ocr.py
|
|
25
|
+
autoclicker/recognizers.py
|
|
26
|
+
autoclicker/reports.py
|
|
27
|
+
autoclicker/resources.py
|
|
22
28
|
autoclicker/run.py
|
|
23
29
|
autoclicker/runner.py
|
|
30
|
+
autoclicker/sandbox.py
|
|
31
|
+
autoclicker/task_queue.py
|
|
24
32
|
autoclicker/templatize.py
|
|
25
33
|
autoclicker/windows.py
|
|
26
34
|
autoclicker/gui/__init__.py
|
|
@@ -29,15 +37,20 @@ autoclicker/gui/flow_editor.py
|
|
|
29
37
|
autoclicker/gui/flow_node.py
|
|
30
38
|
autoclicker/gui/hotkeys.py
|
|
31
39
|
autoclicker/gui/lint_dialog.py
|
|
40
|
+
autoclicker/gui/log_panel.py
|
|
32
41
|
autoclicker/gui/main_window.py
|
|
33
42
|
autoclicker/gui/monitor.py
|
|
34
43
|
autoclicker/gui/preview_label.py
|
|
44
|
+
autoclicker/gui/property_panel.py
|
|
35
45
|
autoclicker/gui/qt_runner.py
|
|
46
|
+
autoclicker/gui/queue_panel.py
|
|
36
47
|
autoclicker/gui/recorder.py
|
|
37
48
|
autoclicker/gui/scheduler_widget.py
|
|
38
49
|
autoclicker/gui/snippet_library.py
|
|
50
|
+
autoclicker/gui/step_list_widget.py
|
|
39
51
|
autoclicker/gui/task_widget.py
|
|
40
52
|
autoclicker/gui/testkit_widget.py
|
|
53
|
+
autoclicker/gui/var_panel.py
|
|
41
54
|
autoclicker/testkit/__init__.py
|
|
42
55
|
autoclicker/testkit/__main__.py
|
|
43
56
|
autoclicker/testkit/assertions.py
|
|
@@ -56,16 +69,28 @@ autoclicker/web/server.py
|
|
|
56
69
|
autoclicker/web/static/app.js
|
|
57
70
|
autoclicker/web/static/index.html
|
|
58
71
|
autoclicker/web/static/style.css
|
|
72
|
+
tests/test_actions.py
|
|
59
73
|
tests/test_adb.py
|
|
60
74
|
tests/test_diagnose.py
|
|
61
75
|
tests/test_diagnose_gui.py
|
|
76
|
+
tests/test_engine_integration.py
|
|
77
|
+
tests/test_engine_services.py
|
|
62
78
|
tests/test_flow_branch.py
|
|
63
79
|
tests/test_lint.py
|
|
80
|
+
tests/test_listener_lifecycle.py
|
|
64
81
|
tests/test_matcher.py
|
|
82
|
+
tests/test_migrations.py
|
|
65
83
|
tests/test_models.py
|
|
84
|
+
tests/test_phase4_integration.py
|
|
85
|
+
tests/test_platform_recognition.py
|
|
86
|
+
tests/test_queue_entrypoints.py
|
|
66
87
|
tests/test_recorder_gui.py
|
|
67
88
|
tests/test_runner.py
|
|
89
|
+
tests/test_sandbox.py
|
|
90
|
+
tests/test_structured_flow.py
|
|
91
|
+
tests/test_structured_gui.py
|
|
68
92
|
tests/test_templatize.py
|
|
69
93
|
tests/test_testkit.py
|
|
94
|
+
tests/test_testkit_contracts.py
|
|
70
95
|
tests/test_testkit_gui.py
|
|
71
96
|
tests/test_web.py
|
|
@@ -6,8 +6,8 @@ ADB) input — then turn those flows into a **visual test framework** with
|
|
|
6
6
|
assertions, visual-regression baselines, HTML/JUnit reports, and a **web console**
|
|
7
7
|
for remote runs.
|
|
8
8
|
|
|
9
|
-
Python/PySide6, started as a port of a Qt/C++ AutoClicker
|
|
10
|
-
|
|
9
|
+
Python/PySide6, started as a port of a Qt/C++ AutoClicker. Legacy `.json` tasks
|
|
10
|
+
remain supported; structured control-flow blocks are a Python-only extension.
|
|
11
11
|
|
|
12
12
|
## Install
|
|
13
13
|
|
|
@@ -53,14 +53,24 @@ autoclicker-web --suites-dir examples # web console at http://127.0.0.1
|
|
|
53
53
|
|
|
54
54
|
## Authoring (desktop GUI)
|
|
55
55
|
|
|
56
|
-
Drag-and-drop **node flow graph**
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
56
|
+
Drag-and-drop **node flow graph** and an interoperable **step list**, with
|
|
57
|
+
nested **If/Else**, **While**, fixed-count loops, **ForEach** arrays, and
|
|
58
|
+
**Break/Continue** — no step numbers required. Double-click a block to edit its
|
|
59
|
+
body or branches. Legacy condition nodes retain their true/false ports.
|
|
60
|
+
|
|
61
|
+
Select consecutive steps and choose **包进… → 遍历数组** to reuse them for each
|
|
62
|
+
item. Enter a custom JSON array or generate a numeric range, then reference the
|
|
63
|
+
current item in text actions, assignments, and conditions. For example, iterate
|
|
64
|
+
`[1,2,3,4,5,6,7,8,9,10]` with variable `server`, click **`{server}服`**, perform
|
|
65
|
+
the actions, and return to the server-selection screen inside the same body.
|
|
66
|
+
Enable exact text matching so `1服` does not also match `11服`. A failed action
|
|
67
|
+
inside a block stops execution unless its failure policy explicitly skips it.
|
|
68
|
+
|
|
69
|
+
Also includes a snippet library, task packages (`.zip` with nested templates),
|
|
70
|
+
on-preview coordinate/color picking, template crop, single-step test run;
|
|
71
|
+
global hotkeys (F6 run/stop, ESC stop), scheduler, and an image/color
|
|
72
|
+
**monitor/trigger** with tray icon; a **Test** tab to load and run visual test
|
|
73
|
+
suites.
|
|
64
74
|
|
|
65
75
|
### Smart recorder
|
|
66
76
|
|
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
"""Action registry for the task engine.
|
|
2
|
+
|
|
3
|
+
Every built-in step has its own handler. Structured flow and recognition helpers
|
|
4
|
+
remain on the runner; handlers lazily import its result enum to avoid a cycle.
|
|
5
|
+
Third-party handlers can use enum values or custom strings, and replacing an
|
|
6
|
+
existing handler requires ``replace=True``.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import random
|
|
11
|
+
import threading
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from typing import TYPE_CHECKING, Any, Callable, Optional
|
|
14
|
+
|
|
15
|
+
from autoclicker.models import Step, StepType
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from autoclicker.runner import StepResult
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class ExecutionContext:
|
|
23
|
+
"""Shared backends and runner helpers available to an action."""
|
|
24
|
+
runner: Any
|
|
25
|
+
screenshot: Optional[Callable] = None
|
|
26
|
+
clicker: Any = None
|
|
27
|
+
matcher: Any = None
|
|
28
|
+
vars: dict[str, str] = field(default_factory=dict)
|
|
29
|
+
log: Callable[[str], None] = lambda msg: None
|
|
30
|
+
stop_event: Any = None
|
|
31
|
+
allow_unsafe_scripts: bool = False
|
|
32
|
+
load_template: Optional[Callable] = None
|
|
33
|
+
|
|
34
|
+
def sleep_ms(self, ms: int) -> None:
|
|
35
|
+
self.runner._sleep_ms(ms)
|
|
36
|
+
|
|
37
|
+
def interp(self, template: str) -> str:
|
|
38
|
+
return self.runner._interp_required(template)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
ActionHandler = Callable[[ExecutionContext, Step], "StepResult"]
|
|
42
|
+
_ACTION_REGISTRY: dict[StepType | str, ActionHandler] = {}
|
|
43
|
+
_registry_lock = threading.RLock()
|
|
44
|
+
_installed = False
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _action_key(step_type: StepType | str) -> StepType | str:
|
|
48
|
+
if isinstance(step_type, StepType):
|
|
49
|
+
return step_type
|
|
50
|
+
if not isinstance(step_type, str):
|
|
51
|
+
raise TypeError("step_type must be a StepType or str")
|
|
52
|
+
# Do not call StepType(value): its legacy _missing_ maps unknowns to Wait.
|
|
53
|
+
return StepType._value2member_map_.get(step_type, step_type)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def register_action(step_type: StepType | str, *, replace: bool = False):
|
|
57
|
+
"""Register a handler; reject duplicates unless explicitly replacing them."""
|
|
58
|
+
key = _action_key(step_type)
|
|
59
|
+
|
|
60
|
+
def decorator(fn: ActionHandler) -> ActionHandler:
|
|
61
|
+
if not callable(fn):
|
|
62
|
+
raise TypeError("action handler must be callable")
|
|
63
|
+
_install()
|
|
64
|
+
with _registry_lock:
|
|
65
|
+
if key in _ACTION_REGISTRY and not replace:
|
|
66
|
+
raise ValueError(f"Action already registered: {key}")
|
|
67
|
+
_ACTION_REGISTRY[key] = fn
|
|
68
|
+
return fn
|
|
69
|
+
|
|
70
|
+
return decorator
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def registered_actions() -> list[StepType | str]:
|
|
74
|
+
"""Return installed built-in and custom types in deterministic value order."""
|
|
75
|
+
_install()
|
|
76
|
+
with _registry_lock:
|
|
77
|
+
return sorted(_ACTION_REGISTRY,
|
|
78
|
+
key=lambda t: t.value if isinstance(t, StepType) else t)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def get_action(step_type: StepType | str) -> Optional[ActionHandler]:
|
|
82
|
+
"""Return a handler, or None for an unknown type (never a Wait fallback)."""
|
|
83
|
+
key = _action_key(step_type)
|
|
84
|
+
_install()
|
|
85
|
+
with _registry_lock:
|
|
86
|
+
return _ACTION_REGISTRY.get(key)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def exec_click_image(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
90
|
+
return ctx.runner._exec_image_poll(step, StepType.ClickImage)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def exec_click_text(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
94
|
+
return ctx.runner._exec_click_text(step)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def exec_click_position(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
98
|
+
from autoclicker.runner import StepResult
|
|
99
|
+
cx, cy = step.x, step.y
|
|
100
|
+
if step.varName == "use_var":
|
|
101
|
+
cx = int(ctx.vars.get("last_x", "0"))
|
|
102
|
+
cy = int(ctx.vars.get("last_y", "0"))
|
|
103
|
+
cx = ctx.runner._rand_offset(cx, step.randomOffset)
|
|
104
|
+
cy = ctx.runner._rand_offset(cy, step.randomOffset)
|
|
105
|
+
ctx.log(f" 点击 ({cx}, {cy})")
|
|
106
|
+
ctx.clicker.click(ctx.runner.target_hwnd, cx, cy)
|
|
107
|
+
return StepResult.Success
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def exec_wait(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
111
|
+
from autoclicker.runner import StepResult
|
|
112
|
+
ctx.log(f" 等待 {step.waitMs}ms")
|
|
113
|
+
ctx.sleep_ms(step.waitMs)
|
|
114
|
+
return StepResult.Success
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def exec_loop(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
118
|
+
from autoclicker.runner import StepResult
|
|
119
|
+
return StepResult.Success
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def exec_if_image_found(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
123
|
+
return ctx.runner._exec_if_image(step)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def exec_if_text_found(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
127
|
+
result, ctx.runner._last_condition = ctx.runner._evaluate_condition(step, StepType.IfTextFound)
|
|
128
|
+
return result
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def exec_if_else(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
132
|
+
from autoclicker.runner import StepResult
|
|
133
|
+
ctx.log(f" If/Else 分支 (上次条件: {'true' if ctx.runner._last_condition else 'false'})")
|
|
134
|
+
return StepResult.Success
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def exec_call_sub(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
138
|
+
from autoclicker.runner import StepResult
|
|
139
|
+
if not step.subTaskPath:
|
|
140
|
+
ctx.log(" 错误: 未指定子任务路径")
|
|
141
|
+
return StepResult.Fail
|
|
142
|
+
# T1-4: the outputs mapping belongs to THIS CallSub step; the runner must
|
|
143
|
+
# not scan the task for "the first CallSub" (wrong for multiple/nested subs).
|
|
144
|
+
return ctx.runner._run_subtask(step.subTaskPath, step.params.get("outputs"))
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def exec_set_var(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
148
|
+
from autoclicker.runner import StepResult
|
|
149
|
+
value = step.varValue
|
|
150
|
+
typed = getattr(ctx.runner.task, "variable_mode", "legacy") == "typed"
|
|
151
|
+
if typed and isinstance(value, (bool, int, float, list)):
|
|
152
|
+
pass # native value assigned as-is
|
|
153
|
+
elif value.startswith("++"):
|
|
154
|
+
value = int(ctx.vars.get(step.varName, 0)) + 1
|
|
155
|
+
if not typed:
|
|
156
|
+
value = str(value)
|
|
157
|
+
elif value.startswith("--"):
|
|
158
|
+
value = int(ctx.vars.get(step.varName, 0)) - 1
|
|
159
|
+
if not typed:
|
|
160
|
+
value = str(value)
|
|
161
|
+
elif value.startswith("="):
|
|
162
|
+
from autoclicker import expr
|
|
163
|
+
try:
|
|
164
|
+
value = expr.safe_eval(value[1:], ctx.vars, typed=typed)
|
|
165
|
+
except Exception as e: # noqa: BLE001 - recoverable step failure
|
|
166
|
+
ctx.log(f" 表达式错误: {e}")
|
|
167
|
+
return StepResult.Fail
|
|
168
|
+
else:
|
|
169
|
+
value = ctx.interp(value)
|
|
170
|
+
ctx.vars[step.varName] = value
|
|
171
|
+
ctx.runner.cb.var_changed(step.varName, value)
|
|
172
|
+
ctx.log(f" 变量 {step.varName} = {value}")
|
|
173
|
+
return StepResult.Success
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def exec_random_wait(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
177
|
+
from autoclicker.runner import StepResult
|
|
178
|
+
lo, hi = step.waitMs, step.waitMsMax
|
|
179
|
+
if hi <= lo:
|
|
180
|
+
hi = lo + 1
|
|
181
|
+
actual = random.randint(lo, hi - 1)
|
|
182
|
+
ctx.log(f" 随机等待 {actual}ms (范围 {lo}~{hi})")
|
|
183
|
+
ctx.sleep_ms(actual)
|
|
184
|
+
return StepResult.Success
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def exec_key_press(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
188
|
+
from autoclicker.runner import StepResult
|
|
189
|
+
from autoclicker.clicker import parse_vk
|
|
190
|
+
vk = parse_vk(step.keyName)
|
|
191
|
+
if vk == 0:
|
|
192
|
+
ctx.log(f' 错误: 无效按键 "{step.keyName}"')
|
|
193
|
+
return StepResult.Fail
|
|
194
|
+
ctx.log(f" 按键: {step.keyName}")
|
|
195
|
+
ctx.clicker.key_press(vk)
|
|
196
|
+
return StepResult.Success
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def exec_type_text(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
200
|
+
from autoclicker.runner import StepResult
|
|
201
|
+
text = ctx.interp(step.text)
|
|
202
|
+
ctx.log(f' 输入文字: "{text}"')
|
|
203
|
+
ctx.clicker.type_text(text)
|
|
204
|
+
return StepResult.Success
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def exec_hotkey(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
208
|
+
from autoclicker.runner import StepResult
|
|
209
|
+
from autoclicker.clicker import parse_vk
|
|
210
|
+
mod, vk = parse_vk(step.modifierKey), parse_vk(step.keyName)
|
|
211
|
+
if vk == 0:
|
|
212
|
+
ctx.log(" 错误: 无效按键")
|
|
213
|
+
return StepResult.Fail
|
|
214
|
+
ctx.log(f" 快捷键: {step.modifierKey}+{step.keyName}")
|
|
215
|
+
ctx.clicker.hotkey(mod, vk)
|
|
216
|
+
return StepResult.Success
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def exec_long_press(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
220
|
+
from autoclicker.runner import StepResult
|
|
221
|
+
ctx.log(f" 长按 ({step.x}, {step.y}) {step.holdMs}ms")
|
|
222
|
+
ctx.clicker.long_press(ctx.runner.target_hwnd, step.x, step.y, step.holdMs)
|
|
223
|
+
return StepResult.Success
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def exec_drag(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
227
|
+
from autoclicker.runner import StepResult
|
|
228
|
+
ctx.log(f" 拖拽 ({step.x},{step.y}) → ({step.x2},{step.y2}) {step.durationMs}ms")
|
|
229
|
+
ctx.clicker.drag(ctx.runner.target_hwnd, step.x, step.y, step.x2, step.y2, step.durationMs)
|
|
230
|
+
return StepResult.Success
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def exec_pinch(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
234
|
+
from autoclicker.runner import StepResult
|
|
235
|
+
ctx.log(f" 捏合 中心({step.x},{step.y}) {step.pinchStartDist}→{step.pinchEndDist}")
|
|
236
|
+
ctx.clicker.pinch(ctx.runner.target_hwnd, step.x, step.y,
|
|
237
|
+
step.pinchStartDist, step.pinchEndDist, step.durationMs)
|
|
238
|
+
return StepResult.Success
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def exec_wait_image(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
242
|
+
return ctx.runner._exec_image_poll(step, StepType.WaitImage)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def exec_wait_image_gone(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
246
|
+
return ctx.runner._exec_image_poll(step, StepType.WaitImageGone)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def exec_scroll(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
250
|
+
from autoclicker.runner import StepResult
|
|
251
|
+
ctx.log(f" 滚轮 ({step.x},{step.y}) {step.scrollAmount}格")
|
|
252
|
+
ctx.clicker.scroll(ctx.runner.target_hwnd, step.x, step.y, step.scrollAmount)
|
|
253
|
+
return StepResult.Success
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def exec_if_color_match(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
257
|
+
from autoclicker.runner import StepResult
|
|
258
|
+
screen = ctx.screenshot()
|
|
259
|
+
if screen.size == 0 or step.x >= screen.shape[1] or step.y >= screen.shape[0]:
|
|
260
|
+
ctx.runner._last_condition = False
|
|
261
|
+
return StepResult.Success
|
|
262
|
+
ctx.runner._last_condition = ctx.runner._color_at_matches(
|
|
263
|
+
screen, step.x, step.y, step.targetColor, step.colorTolerance)
|
|
264
|
+
ctx.log(f" 条件(颜色): ({step.x},{step.y}) vs {step.targetColor} → "
|
|
265
|
+
f"{'true' if ctx.runner._last_condition else 'false'}")
|
|
266
|
+
return StepResult.Success
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def exec_do_until(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
270
|
+
from autoclicker.runner import StepResult
|
|
271
|
+
return StepResult.Success
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def exec_merge_image(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
275
|
+
return ctx.runner._exec_image_poll(step, StepType.MergeImage)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def exec_multi_drag(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
279
|
+
return ctx.runner._exec_multidrag(step)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def exec_goto(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
283
|
+
from autoclicker.runner import StepResult
|
|
284
|
+
return StepResult.Success
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def exec_if_image_count(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
288
|
+
return ctx.runner._exec_if_image_count(step)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def exec_if_var(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
292
|
+
from autoclicker.runner import StepResult
|
|
293
|
+
val = ctx.vars.get(step.varName, "0")
|
|
294
|
+
ctx.runner._last_condition = ctx.runner._compare(val, step.compareOp, ctx.interp(step.compareValue))
|
|
295
|
+
ctx.log(f" 条件(变量): {step.varName}={val} {step.compareOp} {step.compareValue} → "
|
|
296
|
+
f"{'true' if ctx.runner._last_condition else 'false'}")
|
|
297
|
+
return StepResult.Success
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def exec_click_all(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
301
|
+
return ctx.runner._exec_click_all(step)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def exec_group_begin(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
305
|
+
from autoclicker.runner import StepResult
|
|
306
|
+
return StepResult.Success
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def exec_group_end(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
310
|
+
from autoclicker.runner import StepResult
|
|
311
|
+
return StepResult.Success
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def exec_while_condition(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
315
|
+
from autoclicker.runner import StepResult
|
|
316
|
+
return StepResult.Success
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def exec_read_text(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
320
|
+
return ctx.runner._exec_read_text(step)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def exec_click_relative(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
324
|
+
from autoclicker.runner import StepResult
|
|
325
|
+
bx = int(ctx.vars.get("last_x", "0")) + step.x
|
|
326
|
+
by = int(ctx.vars.get("last_y", "0")) + step.y
|
|
327
|
+
cx = ctx.runner._rand_offset(bx, step.randomOffset)
|
|
328
|
+
cy = ctx.runner._rand_offset(by, step.randomOffset)
|
|
329
|
+
ctx.log(f" 相对点击 last+({step.x},{step.y}) = ({cx}, {cy})")
|
|
330
|
+
ctx.clicker.click(ctx.runner.target_hwnd, cx, cy)
|
|
331
|
+
return StepResult.Success
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def exec_http_request(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
335
|
+
return ctx.runner._exec_http(step)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def exec_run_script(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
339
|
+
return ctx.runner._exec_script(step)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def exec_drag_image(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
343
|
+
return ctx.runner._exec_drag_image(step)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def exec_wait_stable(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
347
|
+
return ctx.runner._exec_wait_stable(step)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def exec_activate_window(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
351
|
+
return ctx.runner._exec_activate_window(step)
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def exec_stop(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
355
|
+
from autoclicker.runner import StepResult
|
|
356
|
+
ctx.log(" 结束任务")
|
|
357
|
+
ctx.runner._running = False
|
|
358
|
+
return StepResult.Success
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def exec_if_block(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
362
|
+
return ctx.runner._exec_block(step)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def exec_repeat_block(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
366
|
+
return ctx.runner._exec_block(step)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def exec_while_block(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
370
|
+
return ctx.runner._exec_block(step)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def exec_for_each(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
374
|
+
return ctx.runner._exec_block(step)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def exec_break(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
378
|
+
from autoclicker.runner import StepResult
|
|
379
|
+
if ctx.runner._loop_depth == 0:
|
|
380
|
+
ctx.log(" 退出循环 / 下一轮只能放在循环体内")
|
|
381
|
+
return StepResult.Abort
|
|
382
|
+
return StepResult.Break
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def exec_continue(ctx: ExecutionContext, step: Step) -> StepResult:
|
|
386
|
+
from autoclicker.runner import StepResult
|
|
387
|
+
if ctx.runner._loop_depth == 0:
|
|
388
|
+
ctx.log(" 退出循环 / 下一轮只能放在循环体内")
|
|
389
|
+
return StepResult.Abort
|
|
390
|
+
return StepResult.Continue
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
_BUILTIN_ACTIONS: dict[StepType, ActionHandler] = {
|
|
394
|
+
StepType.ClickImage: exec_click_image,
|
|
395
|
+
StepType.ClickText: exec_click_text,
|
|
396
|
+
StepType.ClickPosition: exec_click_position,
|
|
397
|
+
StepType.Wait: exec_wait,
|
|
398
|
+
StepType.Loop: exec_loop,
|
|
399
|
+
StepType.IfImageFound: exec_if_image_found,
|
|
400
|
+
StepType.IfTextFound: exec_if_text_found,
|
|
401
|
+
StepType.IfElse: exec_if_else,
|
|
402
|
+
StepType.CallSub: exec_call_sub,
|
|
403
|
+
StepType.SetVar: exec_set_var,
|
|
404
|
+
StepType.RandomWait: exec_random_wait,
|
|
405
|
+
StepType.KeyPress: exec_key_press,
|
|
406
|
+
StepType.TypeText: exec_type_text,
|
|
407
|
+
StepType.Hotkey: exec_hotkey,
|
|
408
|
+
StepType.LongPress: exec_long_press,
|
|
409
|
+
StepType.Drag: exec_drag,
|
|
410
|
+
StepType.Pinch: exec_pinch,
|
|
411
|
+
StepType.WaitImage: exec_wait_image,
|
|
412
|
+
StepType.WaitImageGone: exec_wait_image_gone,
|
|
413
|
+
StepType.Scroll: exec_scroll,
|
|
414
|
+
StepType.IfColorMatch: exec_if_color_match,
|
|
415
|
+
StepType.DoUntil: exec_do_until,
|
|
416
|
+
StepType.MergeImage: exec_merge_image,
|
|
417
|
+
StepType.MultiDrag: exec_multi_drag,
|
|
418
|
+
StepType.Goto: exec_goto,
|
|
419
|
+
StepType.IfImageCount: exec_if_image_count,
|
|
420
|
+
StepType.IfVar: exec_if_var,
|
|
421
|
+
StepType.ClickAll: exec_click_all,
|
|
422
|
+
StepType.GroupBegin: exec_group_begin,
|
|
423
|
+
StepType.GroupEnd: exec_group_end,
|
|
424
|
+
StepType.WhileCondition: exec_while_condition,
|
|
425
|
+
StepType.ReadText: exec_read_text,
|
|
426
|
+
StepType.ClickRelative: exec_click_relative,
|
|
427
|
+
StepType.HttpRequest: exec_http_request,
|
|
428
|
+
StepType.RunScript: exec_run_script,
|
|
429
|
+
StepType.DragImage: exec_drag_image,
|
|
430
|
+
StepType.WaitStable: exec_wait_stable,
|
|
431
|
+
StepType.ActivateWindow: exec_activate_window,
|
|
432
|
+
StepType.Stop: exec_stop,
|
|
433
|
+
StepType.IfBlock: exec_if_block,
|
|
434
|
+
StepType.RepeatBlock: exec_repeat_block,
|
|
435
|
+
StepType.WhileBlock: exec_while_block,
|
|
436
|
+
StepType.ForEach: exec_for_each,
|
|
437
|
+
StepType.Break: exec_break,
|
|
438
|
+
StepType.Continue: exec_continue,
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def _install() -> None:
|
|
443
|
+
"""Install built-ins once, atomically, without overwriting user handlers."""
|
|
444
|
+
global _installed
|
|
445
|
+
with _registry_lock:
|
|
446
|
+
if _installed:
|
|
447
|
+
return
|
|
448
|
+
for step_type, handler in _BUILTIN_ACTIONS.items():
|
|
449
|
+
_ACTION_REGISTRY.setdefault(step_type, handler)
|
|
450
|
+
_installed = True
|