@reconcrap/boss-recommend-mcp 2.1.21 → 2.1.23
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.
- package/README.md +5 -2
- package/bin/boss-recommend-mcp.js +4 -4
- package/config/screening-config.example.json +33 -33
- package/package.json +2 -1
- package/scripts/install-macos.sh +280 -280
- package/scripts/postinstall.cjs +44 -44
- package/skills/boss-chat/README.md +42 -42
- package/skills/boss-chat/SKILL.md +106 -106
- package/skills/boss-recommend-pipeline/README.md +13 -13
- package/skills/boss-recommend-pipeline/SKILL.md +219 -214
- package/skills/boss-recruit-pipeline/README.md +19 -19
- package/skills/boss-recruit-pipeline/SKILL.md +89 -89
- package/src/chat-mcp.js +127 -127
- package/src/chat-runtime-config.js +775 -775
- package/src/cli.js +573 -573
- package/src/core/boss-cards/index.js +199 -199
- package/src/core/browser/index.js +2416 -2385
- package/src/core/capture/index.js +1201 -1201
- package/src/core/cv-acquisition/index.js +238 -238
- package/src/core/cv-capture-target/index.js +299 -299
- package/src/core/greet-quota/index.js +71 -71
- package/src/core/infinite-list/index.js +1315 -1306
- package/src/core/reporting/legacy-csv.js +334 -332
- package/src/core/run/index.js +44 -27
- package/src/core/run/timing.js +33 -33
- package/src/core/screening/index.js +2135 -2135
- package/src/core/self-heal/index.js +973 -973
- package/src/core/self-heal/viewport.js +564 -564
- package/src/detached-worker.js +99 -99
- package/src/domains/chat/cards.js +137 -137
- package/src/domains/chat/constants.js +9 -9
- package/src/domains/chat/detail.js +113 -113
- package/src/domains/chat/index.js +7 -7
- package/src/domains/chat/jobs.js +620 -620
- package/src/domains/chat/page-guard.js +122 -122
- package/src/domains/chat/roots.js +56 -56
- package/src/domains/chat/run-service.js +571 -571
- package/src/domains/common/account-rights-panel.js +314 -314
- package/src/domains/common/recovery-settle.js +159 -159
- package/src/domains/recommend/actions.js +472 -472
- package/src/domains/recommend/cards.js +243 -243
- package/src/domains/recommend/colleague-contact.js +333 -333
- package/src/domains/recommend/constants.js +228 -159
- package/src/domains/recommend/detail.js +655 -646
- package/src/domains/recommend/filters.js +748 -377
- package/src/domains/recommend/index.js +4 -3
- package/src/domains/recommend/jobs.js +542 -542
- package/src/domains/recommend/location.js +736 -0
- package/src/domains/recommend/refresh.js +575 -351
- package/src/domains/recommend/roots.js +80 -80
- package/src/domains/recommend/run-service.js +1616 -878
- package/src/domains/recommend/scopes.js +246 -246
- package/src/domains/recruit/actions.js +277 -277
- package/src/domains/recruit/cards.js +74 -74
- package/src/domains/recruit/constants.js +236 -236
- package/src/domains/recruit/detail.js +588 -588
- package/src/domains/recruit/index.js +9 -9
- package/src/domains/recruit/instruction-parser.js +866 -866
- package/src/domains/recruit/refresh.js +45 -45
- package/src/domains/recruit/roots.js +68 -68
- package/src/domains/recruit/run-service.js +1620 -1620
- package/src/domains/recruit/search.js +3229 -3229
- package/src/index.js +121 -4
- package/src/parser.js +376 -8
- package/src/recommend-mcp.js +1174 -914
- package/src/recommend-scheduler.js +535 -469
- package/src/recruit-mcp.js +2121 -2121
|
@@ -1,54 +1,83 @@
|
|
|
1
|
-
import {
|
|
2
|
-
clickNodeCenter,
|
|
3
|
-
countSelectors,
|
|
4
|
-
DETERMINISTIC_CLICK_OPTIONS,
|
|
5
|
-
findFirstNode,
|
|
6
|
-
getAttributesMap,
|
|
7
|
-
getNodeBox,
|
|
8
|
-
getOuterHTML,
|
|
9
|
-
pressKey,
|
|
10
|
-
querySelectorAll,
|
|
1
|
+
import {
|
|
2
|
+
clickNodeCenter,
|
|
3
|
+
countSelectors,
|
|
4
|
+
DETERMINISTIC_CLICK_OPTIONS,
|
|
5
|
+
findFirstNode,
|
|
6
|
+
getAttributesMap,
|
|
7
|
+
getNodeBox,
|
|
8
|
+
getOuterHTML,
|
|
9
|
+
pressKey,
|
|
10
|
+
querySelectorAll,
|
|
11
11
|
sleep,
|
|
12
12
|
waitForSelector
|
|
13
13
|
} from "../../core/browser/index.js";
|
|
14
14
|
import { htmlToText, normalizeText } from "../../core/screening/index.js";
|
|
15
15
|
import {
|
|
16
|
+
RECOMMEND_ACTIVITY_GROUP,
|
|
17
|
+
RECOMMEND_ACTIVITY_GROUP_HEADING,
|
|
18
|
+
RECOMMEND_ACTIVITY_LEVELS,
|
|
16
19
|
RECOMMEND_CARD_SELECTOR,
|
|
17
20
|
RECOMMEND_FILTER_GROUP_ORDER,
|
|
18
21
|
RECOMMEND_FILTER_SELECTORS
|
|
19
22
|
} from "./constants.js";
|
|
20
23
|
|
|
21
|
-
const SKIP_OPTION_LABELS = new Set(["不限", "全部", "all"]);
|
|
22
|
-
|
|
23
|
-
export function normalizeFilterOptionLabel(label) {
|
|
24
|
-
return normalizeText(label).replace(/\s+/g, "");
|
|
25
|
-
}
|
|
26
|
-
|
|
24
|
+
const SKIP_OPTION_LABELS = new Set(["不限", "全部", "all"]);
|
|
25
|
+
|
|
26
|
+
export function normalizeFilterOptionLabel(label) {
|
|
27
|
+
return normalizeText(label).replace(/\s+/g, "");
|
|
28
|
+
}
|
|
29
|
+
|
|
27
30
|
export function isSafeFilterOptionLabel(label) {
|
|
28
31
|
const normalized = normalizeFilterOptionLabel(label);
|
|
29
32
|
return Boolean(normalized) && !SKIP_OPTION_LABELS.has(normalized.toLowerCase());
|
|
30
33
|
}
|
|
31
34
|
|
|
35
|
+
function isSelectableFilterOptionLabel(label, { allowUnlimited = false } = {}) {
|
|
36
|
+
const normalized = normalizeFilterOptionLabel(label);
|
|
37
|
+
if (!normalized) return false;
|
|
38
|
+
return allowUnlimited || !SKIP_OPTION_LABELS.has(normalized.toLowerCase());
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function normalizeActivityGroupText(text) {
|
|
42
|
+
return normalizeFilterOptionLabel(text)
|
|
43
|
+
.replace(/【/g, "[")
|
|
44
|
+
.replace(/】/g, "]");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function isRecommendActivityGroupText(text) {
|
|
48
|
+
const normalized = normalizeActivityGroupText(text);
|
|
49
|
+
const heading = normalizeActivityGroupText(RECOMMEND_ACTIVITY_GROUP_HEADING);
|
|
50
|
+
const exactOptionCount = RECOMMEND_ACTIVITY_LEVELS.filter((label) => (
|
|
51
|
+
normalized.includes(normalizeFilterOptionLabel(label))
|
|
52
|
+
)).length;
|
|
53
|
+
const hasUnlimited = normalized.includes(normalizeFilterOptionLabel("不限"));
|
|
54
|
+
return (
|
|
55
|
+
(normalized.startsWith(heading) && exactOptionCount >= 2 && hasUnlimited)
|
|
56
|
+
|| (exactOptionCount === RECOMMEND_ACTIVITY_LEVELS.length && hasUnlimited)
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
32
60
|
export function isActiveOption(attributes = {}, outerHTML = "") {
|
|
33
61
|
const className = attributes.class || "";
|
|
34
62
|
return /\bactive\b/.test(className) || /\bactive\b/.test(String(outerHTML || "").split(">")[0] || "");
|
|
35
63
|
}
|
|
36
64
|
|
|
37
|
-
export function chooseFirstSafeFilterOption(options = [], groupOrder = RECOMMEND_FILTER_GROUP_ORDER) {
|
|
38
|
-
for (const group of groupOrder) {
|
|
39
|
-
const option = options.find((item) => (
|
|
40
|
-
item.group === group
|
|
41
|
-
&& !item.active
|
|
42
|
-
&& isSafeFilterOptionLabel(item.label)
|
|
43
|
-
));
|
|
44
|
-
if (option) return option;
|
|
45
|
-
}
|
|
65
|
+
export function chooseFirstSafeFilterOption(options = [], groupOrder = RECOMMEND_FILTER_GROUP_ORDER) {
|
|
66
|
+
for (const group of groupOrder) {
|
|
67
|
+
const option = options.find((item) => (
|
|
68
|
+
item.group === group
|
|
69
|
+
&& !item.active
|
|
70
|
+
&& isSafeFilterOptionLabel(item.label)
|
|
71
|
+
));
|
|
72
|
+
if (option) return option;
|
|
73
|
+
}
|
|
46
74
|
return null;
|
|
47
75
|
}
|
|
48
76
|
|
|
49
77
|
export function chooseFilterOptionByLabels(options = [], {
|
|
50
78
|
group = "",
|
|
51
|
-
labels = []
|
|
79
|
+
labels = [],
|
|
80
|
+
allowUnlimited = false
|
|
52
81
|
} = {}) {
|
|
53
82
|
const normalizedGroup = normalizeText(group);
|
|
54
83
|
const normalizedLabels = labels.map(normalizeFilterOptionLabel).filter(Boolean);
|
|
@@ -57,7 +86,7 @@ export function chooseFilterOptionByLabels(options = [], {
|
|
|
57
86
|
(!normalizedGroup || item.group === normalizedGroup)
|
|
58
87
|
&& !item.active
|
|
59
88
|
&& normalizeFilterOptionLabel(item.label) === label
|
|
60
|
-
&&
|
|
89
|
+
&& isSelectableFilterOptionLabel(item.label, { allowUnlimited })
|
|
61
90
|
));
|
|
62
91
|
if (option) return option;
|
|
63
92
|
}
|
|
@@ -66,7 +95,8 @@ export function chooseFilterOptionByLabels(options = [], {
|
|
|
66
95
|
|
|
67
96
|
export function chooseFilterOptionsByLabels(options = [], {
|
|
68
97
|
group = "",
|
|
69
|
-
labels = []
|
|
98
|
+
labels = [],
|
|
99
|
+
allowUnlimited = false
|
|
70
100
|
} = {}) {
|
|
71
101
|
const normalizedGroup = normalizeText(group);
|
|
72
102
|
const normalizedLabels = labels.map(normalizeFilterOptionLabel).filter(Boolean);
|
|
@@ -74,164 +104,209 @@ export function chooseFilterOptionsByLabels(options = [], {
|
|
|
74
104
|
const option = options.find((item) => (
|
|
75
105
|
(!normalizedGroup || item.group === normalizedGroup)
|
|
76
106
|
&& normalizeFilterOptionLabel(item.label) === label
|
|
77
|
-
&&
|
|
107
|
+
&& isSelectableFilterOptionLabel(item.label, { allowUnlimited })
|
|
78
108
|
));
|
|
79
109
|
return {
|
|
80
110
|
label,
|
|
81
111
|
option: option || null
|
|
82
112
|
};
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export async function getFilterPanelCount(client, frameNodeId) {
|
|
87
|
-
return (await querySelectorAll(client, frameNodeId, RECOMMEND_FILTER_SELECTORS.panel)).length;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export async function getFilterPanelCount(client, frameNodeId) {
|
|
117
|
+
return (await querySelectorAll(client, frameNodeId, RECOMMEND_FILTER_SELECTORS.panel)).length;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export async function getRecommendFilterCounts(client, frameNodeId) {
|
|
121
|
+
return countSelectors(client, frameNodeId, {
|
|
122
|
+
filter_trigger: RECOMMEND_FILTER_SELECTORS.trigger,
|
|
123
|
+
filter_panel: RECOMMEND_FILTER_SELECTORS.panel,
|
|
124
|
+
check_box: RECOMMEND_FILTER_SELECTORS.checkBox,
|
|
125
|
+
option: `.filter-panel ${RECOMMEND_FILTER_SELECTORS.option}`,
|
|
126
|
+
active_option: RECOMMEND_FILTER_SELECTORS.activeOption,
|
|
127
|
+
recommend_card: RECOMMEND_CARD_SELECTOR
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function findFilterTrigger(client, frameNodeId) {
|
|
132
|
+
return findFirstNode(client, frameNodeId, [
|
|
133
|
+
RECOMMEND_FILTER_SELECTORS.trigger,
|
|
134
|
+
".recommend-filter.op-filter"
|
|
135
|
+
]);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export async function ensureFilterPanelClosed(client, frameNodeId, triggerNodeId = 0) {
|
|
139
|
+
const attempts = [];
|
|
140
|
+
if (await getFilterPanelCount(client, frameNodeId) === 0) return attempts;
|
|
141
|
+
|
|
142
|
+
await pressKey(client, "Escape", {
|
|
143
|
+
code: "Escape",
|
|
144
|
+
windowsVirtualKeyCode: 27,
|
|
145
|
+
nativeVirtualKeyCode: 27
|
|
146
|
+
});
|
|
147
|
+
await sleep(400);
|
|
148
|
+
attempts.push("Escape");
|
|
149
|
+
|
|
150
|
+
if (await getFilterPanelCount(client, frameNodeId) > 0 && triggerNodeId) {
|
|
151
|
+
await clickNodeCenter(client, triggerNodeId, DETERMINISTIC_CLICK_OPTIONS);
|
|
152
|
+
await sleep(500);
|
|
153
|
+
attempts.push("filter-trigger-toggle");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return attempts;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function dismissRecommendControlOverlays(client, settleMs = 250) {
|
|
160
|
+
if (typeof client?.Input?.dispatchKeyEvent !== "function") {
|
|
161
|
+
return ["Escape-unavailable"];
|
|
162
|
+
}
|
|
163
|
+
await pressKey(client, "Escape", {
|
|
164
|
+
code: "Escape",
|
|
165
|
+
windowsVirtualKeyCode: 27,
|
|
166
|
+
nativeVirtualKeyCode: 27
|
|
167
|
+
});
|
|
168
|
+
if (settleMs > 0) await sleep(settleMs);
|
|
169
|
+
return ["Escape"];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function findFilterTriggerCandidates(client, frameNodeId) {
|
|
173
|
+
const candidates = [];
|
|
174
|
+
const seen = new Set();
|
|
175
|
+
for (const selector of [
|
|
176
|
+
RECOMMEND_FILTER_SELECTORS.trigger,
|
|
177
|
+
".recommend-filter.op-filter"
|
|
178
|
+
]) {
|
|
179
|
+
const candidate = await findFirstNode(client, frameNodeId, [selector]);
|
|
180
|
+
if (candidate && !seen.has(candidate.nodeId)) {
|
|
181
|
+
candidates.push(candidate);
|
|
182
|
+
seen.add(candidate.nodeId);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return candidates;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export async function openFilterPanel(client, frameNodeId) {
|
|
189
|
+
let triggerCandidates = await findFilterTriggerCandidates(client, frameNodeId);
|
|
190
|
+
if (!triggerCandidates.length) {
|
|
191
|
+
const error = new Error("Recommend filter trigger was not found");
|
|
192
|
+
error.code = "RECOMMEND_FILTER_TRIGGER_UNAVAILABLE";
|
|
193
|
+
throw error;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const preOpenDismissalAttempts = await dismissRecommendControlOverlays(client);
|
|
197
|
+
const existingPanelNodeId = await waitForSelector(client, frameNodeId, RECOMMEND_FILTER_SELECTORS.panel, {
|
|
198
|
+
timeoutMs: 300,
|
|
199
|
+
intervalMs: 100
|
|
200
|
+
});
|
|
201
|
+
if (existingPanelNodeId) {
|
|
202
|
+
const triggerBox = await getNodeBox(client, triggerCandidates[0].nodeId);
|
|
203
|
+
return {
|
|
204
|
+
trigger: triggerCandidates[0],
|
|
205
|
+
trigger_box: triggerBox,
|
|
206
|
+
panel_node_id: existingPanelNodeId,
|
|
207
|
+
initial_close_attempts: preOpenDismissalAttempts,
|
|
208
|
+
already_open: true
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const closeAttempts = await ensureFilterPanelClosed(client, frameNodeId, triggerCandidates[0].nodeId);
|
|
213
|
+
|
|
214
|
+
const attempts = [];
|
|
215
|
+
for (let round = 0; round < 3; round += 1) {
|
|
216
|
+
triggerCandidates = await findFilterTriggerCandidates(client, frameNodeId);
|
|
217
|
+
for (const trigger of triggerCandidates) {
|
|
218
|
+
const triggerBox = await getNodeBox(client, trigger.nodeId);
|
|
219
|
+
const clickBox = await clickNodeCenter(client, trigger.nodeId, DETERMINISTIC_CLICK_OPTIONS);
|
|
220
|
+
attempts.push({
|
|
221
|
+
selector: trigger.selector,
|
|
222
|
+
node_id: trigger.nodeId,
|
|
223
|
+
center: triggerBox.center,
|
|
224
|
+
click_target: clickBox.click_target,
|
|
225
|
+
click_result: clickBox.click_result
|
|
226
|
+
});
|
|
227
|
+
const panelNodeId = await waitForSelector(client, frameNodeId, RECOMMEND_FILTER_SELECTORS.panel, {
|
|
228
|
+
timeoutMs: 2500,
|
|
229
|
+
intervalMs: 200
|
|
230
|
+
});
|
|
231
|
+
if (panelNodeId) {
|
|
232
|
+
return {
|
|
233
|
+
trigger,
|
|
234
|
+
trigger_box: triggerBox,
|
|
235
|
+
panel_node_id: panelNodeId,
|
|
236
|
+
initial_close_attempts: [
|
|
237
|
+
...preOpenDismissalAttempts,
|
|
238
|
+
...closeAttempts
|
|
239
|
+
],
|
|
240
|
+
open_attempts: attempts
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
await sleep(500);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
throw new Error(`Recommend filter panel did not open after ${attempts.length} trigger attempts`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function readOptionNode(client, group, nodeId) {
|
|
251
|
+
const [attributes, outerHTML] = await Promise.all([
|
|
252
|
+
getAttributesMap(client, nodeId),
|
|
253
|
+
getOuterHTML(client, nodeId)
|
|
254
|
+
]);
|
|
255
|
+
const label = normalizeFilterOptionLabel(htmlToText(outerHTML));
|
|
256
|
+
return {
|
|
257
|
+
group,
|
|
258
|
+
node_id: nodeId,
|
|
259
|
+
label,
|
|
260
|
+
active: isActiveOption(attributes, outerHTML),
|
|
261
|
+
attributes: {
|
|
262
|
+
class: attributes.class || "",
|
|
263
|
+
value: attributes.value || "",
|
|
264
|
+
type: attributes.type || ""
|
|
265
|
+
}
|
|
266
|
+
};
|
|
88
267
|
}
|
|
89
268
|
|
|
90
|
-
|
|
91
|
-
return
|
|
92
|
-
filter_trigger: RECOMMEND_FILTER_SELECTORS.trigger,
|
|
93
|
-
filter_panel: RECOMMEND_FILTER_SELECTORS.panel,
|
|
94
|
-
check_box: RECOMMEND_FILTER_SELECTORS.checkBox,
|
|
95
|
-
option: `.filter-panel ${RECOMMEND_FILTER_SELECTORS.option}`,
|
|
96
|
-
active_option: RECOMMEND_FILTER_SELECTORS.activeOption,
|
|
97
|
-
recommend_card: RECOMMEND_CARD_SELECTOR
|
|
98
|
-
});
|
|
269
|
+
function uniqueNodeIds(nodeIds = []) {
|
|
270
|
+
return [...new Set(nodeIds.filter((nodeId) => Number(nodeId) > 0))];
|
|
99
271
|
}
|
|
100
272
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
".recommend-filter.op-filter"
|
|
105
|
-
]);
|
|
273
|
+
async function nodeMatchesActivityGroup(client, nodeId) {
|
|
274
|
+
const outerHTML = await getOuterHTML(client, nodeId);
|
|
275
|
+
return isRecommendActivityGroupText(htmlToText(outerHTML));
|
|
106
276
|
}
|
|
107
277
|
|
|
108
|
-
export async function
|
|
109
|
-
const
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
code: "Escape",
|
|
114
|
-
windowsVirtualKeyCode: 27,
|
|
115
|
-
nativeVirtualKeyCode: 27
|
|
116
|
-
});
|
|
117
|
-
await sleep(400);
|
|
118
|
-
attempts.push("Escape");
|
|
119
|
-
|
|
120
|
-
if (await getFilterPanelCount(client, frameNodeId) > 0 && triggerNodeId) {
|
|
121
|
-
await clickNodeCenter(client, triggerNodeId, DETERMINISTIC_CLICK_OPTIONS);
|
|
122
|
-
await sleep(500);
|
|
123
|
-
attempts.push("filter-trigger-toggle");
|
|
278
|
+
export async function resolveRecommendFilterGroupNodeIds(client, frameNodeId, group) {
|
|
279
|
+
const groupSelector = RECOMMEND_FILTER_SELECTORS.groups[group];
|
|
280
|
+
if (!groupSelector) return [];
|
|
281
|
+
if (group !== RECOMMEND_ACTIVITY_GROUP) {
|
|
282
|
+
return querySelectorAll(client, frameNodeId, groupSelector);
|
|
124
283
|
}
|
|
125
284
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (typeof client?.Input?.dispatchKeyEvent !== "function") {
|
|
131
|
-
return ["Escape-unavailable"];
|
|
285
|
+
const selectorCandidates = uniqueNodeIds(await querySelectorAll(client, frameNodeId, groupSelector));
|
|
286
|
+
const semanticMatches = [];
|
|
287
|
+
for (const nodeId of selectorCandidates) {
|
|
288
|
+
if (await nodeMatchesActivityGroup(client, nodeId)) semanticMatches.push(nodeId);
|
|
132
289
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
RECOMMEND_FILTER_SELECTORS.trigger,
|
|
147
|
-
".recommend-filter.op-filter"
|
|
148
|
-
]) {
|
|
149
|
-
const candidate = await findFirstNode(client, frameNodeId, [selector]);
|
|
150
|
-
if (candidate && !seen.has(candidate.nodeId)) {
|
|
151
|
-
candidates.push(candidate);
|
|
152
|
-
seen.add(candidate.nodeId);
|
|
290
|
+
if (semanticMatches.length) return uniqueNodeIds(semanticMatches);
|
|
291
|
+
|
|
292
|
+
const broadCandidates = uniqueNodeIds(await querySelectorAll(
|
|
293
|
+
client,
|
|
294
|
+
frameNodeId,
|
|
295
|
+
RECOMMEND_FILTER_SELECTORS.checkBox
|
|
296
|
+
));
|
|
297
|
+
for (const nodeId of broadCandidates) {
|
|
298
|
+
if (selectorCandidates.includes(nodeId)) continue;
|
|
299
|
+
try {
|
|
300
|
+
if (await nodeMatchesActivityGroup(client, nodeId)) semanticMatches.push(nodeId);
|
|
301
|
+
} catch {
|
|
302
|
+
// An unrelated stale filter row does not make the activity control visible.
|
|
153
303
|
}
|
|
154
304
|
}
|
|
155
|
-
return
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
export async function openFilterPanel(client, frameNodeId) {
|
|
159
|
-
let triggerCandidates = await findFilterTriggerCandidates(client, frameNodeId);
|
|
160
|
-
if (!triggerCandidates.length) {
|
|
161
|
-
throw new Error("Recommend filter trigger was not found");
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const preOpenDismissalAttempts = await dismissRecommendControlOverlays(client);
|
|
165
|
-
const existingPanelNodeId = await waitForSelector(client, frameNodeId, RECOMMEND_FILTER_SELECTORS.panel, {
|
|
166
|
-
timeoutMs: 300,
|
|
167
|
-
intervalMs: 100
|
|
168
|
-
});
|
|
169
|
-
if (existingPanelNodeId) {
|
|
170
|
-
const triggerBox = await getNodeBox(client, triggerCandidates[0].nodeId);
|
|
171
|
-
return {
|
|
172
|
-
trigger: triggerCandidates[0],
|
|
173
|
-
trigger_box: triggerBox,
|
|
174
|
-
panel_node_id: existingPanelNodeId,
|
|
175
|
-
initial_close_attempts: preOpenDismissalAttempts,
|
|
176
|
-
already_open: true
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const closeAttempts = await ensureFilterPanelClosed(client, frameNodeId, triggerCandidates[0].nodeId);
|
|
181
|
-
|
|
182
|
-
const attempts = [];
|
|
183
|
-
for (let round = 0; round < 3; round += 1) {
|
|
184
|
-
triggerCandidates = await findFilterTriggerCandidates(client, frameNodeId);
|
|
185
|
-
for (const trigger of triggerCandidates) {
|
|
186
|
-
const triggerBox = await getNodeBox(client, trigger.nodeId);
|
|
187
|
-
const clickBox = await clickNodeCenter(client, trigger.nodeId, DETERMINISTIC_CLICK_OPTIONS);
|
|
188
|
-
attempts.push({
|
|
189
|
-
selector: trigger.selector,
|
|
190
|
-
node_id: trigger.nodeId,
|
|
191
|
-
center: triggerBox.center,
|
|
192
|
-
click_target: clickBox.click_target,
|
|
193
|
-
click_result: clickBox.click_result
|
|
194
|
-
});
|
|
195
|
-
const panelNodeId = await waitForSelector(client, frameNodeId, RECOMMEND_FILTER_SELECTORS.panel, {
|
|
196
|
-
timeoutMs: 2500,
|
|
197
|
-
intervalMs: 200
|
|
198
|
-
});
|
|
199
|
-
if (panelNodeId) {
|
|
200
|
-
return {
|
|
201
|
-
trigger,
|
|
202
|
-
trigger_box: triggerBox,
|
|
203
|
-
panel_node_id: panelNodeId,
|
|
204
|
-
initial_close_attempts: [
|
|
205
|
-
...preOpenDismissalAttempts,
|
|
206
|
-
...closeAttempts
|
|
207
|
-
],
|
|
208
|
-
open_attempts: attempts
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
await sleep(500);
|
|
305
|
+
if (semanticMatches.length) return uniqueNodeIds(semanticMatches);
|
|
306
|
+
if (selectorCandidates.length) {
|
|
307
|
+
throw new Error("Recommend activity filter control was visible but its semantic row could not be verified");
|
|
213
308
|
}
|
|
214
|
-
|
|
215
|
-
throw new Error(`Recommend filter panel did not open after ${attempts.length} trigger attempts`);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
async function readOptionNode(client, group, nodeId) {
|
|
219
|
-
const [attributes, outerHTML] = await Promise.all([
|
|
220
|
-
getAttributesMap(client, nodeId),
|
|
221
|
-
getOuterHTML(client, nodeId)
|
|
222
|
-
]);
|
|
223
|
-
const label = normalizeFilterOptionLabel(htmlToText(outerHTML));
|
|
224
|
-
return {
|
|
225
|
-
group,
|
|
226
|
-
node_id: nodeId,
|
|
227
|
-
label,
|
|
228
|
-
active: isActiveOption(attributes, outerHTML),
|
|
229
|
-
attributes: {
|
|
230
|
-
class: attributes.class || "",
|
|
231
|
-
value: attributes.value || "",
|
|
232
|
-
type: attributes.type || ""
|
|
233
|
-
}
|
|
234
|
-
};
|
|
309
|
+
return [];
|
|
235
310
|
}
|
|
236
311
|
|
|
237
312
|
export async function listFilterOptions(client, frameNodeId, {
|
|
@@ -239,234 +314,337 @@ export async function listFilterOptions(client, frameNodeId, {
|
|
|
239
314
|
} = {}) {
|
|
240
315
|
const options = [];
|
|
241
316
|
for (const group of groupOrder) {
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
const groupNodeIds = await querySelectorAll(client, frameNodeId, groupSelector);
|
|
317
|
+
const groupNodeIds = await resolveRecommendFilterGroupNodeIds(client, frameNodeId, group);
|
|
318
|
+
let optionCount = 0;
|
|
245
319
|
for (const groupNodeId of groupNodeIds) {
|
|
246
320
|
const optionNodeIds = await querySelectorAll(client, groupNodeId, RECOMMEND_FILTER_SELECTORS.option);
|
|
247
321
|
for (const optionNodeId of optionNodeIds) {
|
|
248
322
|
options.push(await readOptionNode(client, group, optionNodeId));
|
|
323
|
+
optionCount += 1;
|
|
249
324
|
}
|
|
250
325
|
}
|
|
326
|
+
if (group === RECOMMEND_ACTIVITY_GROUP && groupNodeIds.length && optionCount === 0) {
|
|
327
|
+
throw new Error("Recommend activity filter control was visible but its options could not be read");
|
|
328
|
+
}
|
|
251
329
|
}
|
|
252
330
|
return options;
|
|
253
331
|
}
|
|
254
332
|
|
|
255
333
|
async function clickFirstAvailableNode(client, nodeIds) {
|
|
256
|
-
const errors = [];
|
|
257
|
-
for (const nodeId of nodeIds) {
|
|
258
|
-
try {
|
|
259
|
-
const box = await clickNodeCenter(client, nodeId, DETERMINISTIC_CLICK_OPTIONS);
|
|
260
|
-
return {
|
|
261
|
-
clicked: true,
|
|
262
|
-
node_id: nodeId,
|
|
263
|
-
box
|
|
264
|
-
};
|
|
265
|
-
} catch (error) {
|
|
266
|
-
errors.push({
|
|
267
|
-
node_id: nodeId,
|
|
268
|
-
message: error?.message || String(error)
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
return {
|
|
273
|
-
clicked: false,
|
|
274
|
-
errors
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
function normalizeButtonLabel(label) {
|
|
279
|
-
return normalizeFilterOptionLabel(label).toLowerCase();
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
function buttonRank(candidate) {
|
|
283
|
-
const label = normalizeButtonLabel(candidate.label);
|
|
284
|
-
if (/确定|确认|完成|ok|confirm/.test(label)) return 0;
|
|
285
|
-
if (/重置|清空|取消|reset|cancel/.test(label)) return 2;
|
|
286
|
-
return 1;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
async function readButtonCandidate(client, nodeId, index) {
|
|
290
|
-
const [attributes, outerHTML] = await Promise.all([
|
|
291
|
-
getAttributesMap(client, nodeId),
|
|
292
|
-
getOuterHTML(client, nodeId)
|
|
293
|
-
]);
|
|
294
|
-
return {
|
|
295
|
-
node_id: nodeId,
|
|
296
|
-
index,
|
|
297
|
-
label: normalizeButtonLabel(htmlToText(outerHTML)),
|
|
298
|
-
class_name: attributes.class || ""
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
async function readConfirmButtonCandidates(client, frameNodeId) {
|
|
303
|
-
const nodeIds = await querySelectorAll(client, frameNodeId, RECOMMEND_FILTER_SELECTORS.confirmButton);
|
|
304
|
-
const candidates = [];
|
|
305
|
-
for (let index = 0; index < nodeIds.length; index += 1) {
|
|
306
|
-
candidates.push(await readButtonCandidate(client, nodeIds[index], index));
|
|
307
|
-
}
|
|
308
|
-
return candidates.sort((left, right) => {
|
|
309
|
-
const rankDiff = buttonRank(left) - buttonRank(right);
|
|
310
|
-
if (rankDiff !== 0) return rankDiff;
|
|
311
|
-
return right.index - left.index;
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
export async function selectFirstSafeFilterOption(client, frameNodeId, {
|
|
316
|
-
groupOrder = RECOMMEND_FILTER_GROUP_ORDER
|
|
317
|
-
} = {}) {
|
|
318
|
-
const options = await listFilterOptions(client, frameNodeId, { groupOrder });
|
|
319
|
-
const selected = chooseFirstSafeFilterOption(options, groupOrder);
|
|
320
|
-
if (!selected) {
|
|
321
|
-
throw new Error("No safe non-active recommend filter option was found");
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
const box = await clickNodeCenter(client, selected.node_id, {
|
|
325
|
-
...DETERMINISTIC_CLICK_OPTIONS,
|
|
326
|
-
scrollIntoView: true
|
|
327
|
-
});
|
|
328
|
-
await sleep(300);
|
|
329
|
-
|
|
330
|
-
return {
|
|
331
|
-
selected_option: {
|
|
332
|
-
group: selected.group,
|
|
333
|
-
label: selected.label,
|
|
334
|
-
node_id: selected.node_id,
|
|
335
|
-
was_active: selected.active
|
|
336
|
-
},
|
|
337
|
-
option_box: box,
|
|
338
|
-
discovered_options: options.map((option) => ({
|
|
339
|
-
group: option.group,
|
|
340
|
-
label: option.label,
|
|
341
|
-
active: option.active,
|
|
342
|
-
node_id: option.node_id
|
|
343
|
-
}))
|
|
344
|
-
};
|
|
334
|
+
const errors = [];
|
|
335
|
+
for (const nodeId of nodeIds) {
|
|
336
|
+
try {
|
|
337
|
+
const box = await clickNodeCenter(client, nodeId, DETERMINISTIC_CLICK_OPTIONS);
|
|
338
|
+
return {
|
|
339
|
+
clicked: true,
|
|
340
|
+
node_id: nodeId,
|
|
341
|
+
box
|
|
342
|
+
};
|
|
343
|
+
} catch (error) {
|
|
344
|
+
errors.push({
|
|
345
|
+
node_id: nodeId,
|
|
346
|
+
message: error?.message || String(error)
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return {
|
|
351
|
+
clicked: false,
|
|
352
|
+
errors
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function normalizeButtonLabel(label) {
|
|
357
|
+
return normalizeFilterOptionLabel(label).toLowerCase();
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function buttonRank(candidate) {
|
|
361
|
+
const label = normalizeButtonLabel(candidate.label);
|
|
362
|
+
if (/确定|确认|完成|ok|confirm/.test(label)) return 0;
|
|
363
|
+
if (/重置|清空|取消|reset|cancel/.test(label)) return 2;
|
|
364
|
+
return 1;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async function readButtonCandidate(client, nodeId, index) {
|
|
368
|
+
const [attributes, outerHTML] = await Promise.all([
|
|
369
|
+
getAttributesMap(client, nodeId),
|
|
370
|
+
getOuterHTML(client, nodeId)
|
|
371
|
+
]);
|
|
372
|
+
return {
|
|
373
|
+
node_id: nodeId,
|
|
374
|
+
index,
|
|
375
|
+
label: normalizeButtonLabel(htmlToText(outerHTML)),
|
|
376
|
+
class_name: attributes.class || ""
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async function readConfirmButtonCandidates(client, frameNodeId) {
|
|
381
|
+
const nodeIds = await querySelectorAll(client, frameNodeId, RECOMMEND_FILTER_SELECTORS.confirmButton);
|
|
382
|
+
const candidates = [];
|
|
383
|
+
for (let index = 0; index < nodeIds.length; index += 1) {
|
|
384
|
+
candidates.push(await readButtonCandidate(client, nodeIds[index], index));
|
|
385
|
+
}
|
|
386
|
+
return candidates.sort((left, right) => {
|
|
387
|
+
const rankDiff = buttonRank(left) - buttonRank(right);
|
|
388
|
+
if (rankDiff !== 0) return rankDiff;
|
|
389
|
+
return right.index - left.index;
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export async function selectFirstSafeFilterOption(client, frameNodeId, {
|
|
394
|
+
groupOrder = RECOMMEND_FILTER_GROUP_ORDER
|
|
395
|
+
} = {}) {
|
|
396
|
+
const options = await listFilterOptions(client, frameNodeId, { groupOrder });
|
|
397
|
+
const selected = chooseFirstSafeFilterOption(options, groupOrder);
|
|
398
|
+
if (!selected) {
|
|
399
|
+
throw new Error("No safe non-active recommend filter option was found");
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const box = await clickNodeCenter(client, selected.node_id, {
|
|
403
|
+
...DETERMINISTIC_CLICK_OPTIONS,
|
|
404
|
+
scrollIntoView: true
|
|
405
|
+
});
|
|
406
|
+
await sleep(300);
|
|
407
|
+
|
|
408
|
+
return {
|
|
409
|
+
selected_option: {
|
|
410
|
+
group: selected.group,
|
|
411
|
+
label: selected.label,
|
|
412
|
+
node_id: selected.node_id,
|
|
413
|
+
was_active: selected.active
|
|
414
|
+
},
|
|
415
|
+
option_box: box,
|
|
416
|
+
discovered_options: options.map((option) => ({
|
|
417
|
+
group: option.group,
|
|
418
|
+
label: option.label,
|
|
419
|
+
active: option.active,
|
|
420
|
+
node_id: option.node_id
|
|
421
|
+
}))
|
|
422
|
+
};
|
|
345
423
|
}
|
|
346
424
|
|
|
347
425
|
export async function selectFilterOption(client, frameNodeId, {
|
|
348
426
|
group = "",
|
|
349
427
|
labels = [],
|
|
350
|
-
groupOrder = RECOMMEND_FILTER_GROUP_ORDER
|
|
428
|
+
groupOrder = RECOMMEND_FILTER_GROUP_ORDER,
|
|
429
|
+
allowUnlimited = false
|
|
351
430
|
} = {}) {
|
|
352
431
|
const options = await listFilterOptions(client, frameNodeId, { groupOrder });
|
|
432
|
+
const alreadyActive = labels.length
|
|
433
|
+
? options.find((option) => (
|
|
434
|
+
(!group || option.group === group)
|
|
435
|
+
&& labels.some((label) => (
|
|
436
|
+
normalizeFilterOptionLabel(option.label) === normalizeFilterOptionLabel(label)
|
|
437
|
+
))
|
|
438
|
+
&& option.active
|
|
439
|
+
&& isSelectableFilterOptionLabel(option.label, { allowUnlimited })
|
|
440
|
+
))
|
|
441
|
+
: null;
|
|
442
|
+
if (alreadyActive) {
|
|
443
|
+
return {
|
|
444
|
+
selected_option: {
|
|
445
|
+
group: alreadyActive.group,
|
|
446
|
+
label: alreadyActive.label,
|
|
447
|
+
node_id: alreadyActive.node_id,
|
|
448
|
+
was_active: true,
|
|
449
|
+
clicked: false,
|
|
450
|
+
requested_group: group || null,
|
|
451
|
+
requested_labels: labels
|
|
452
|
+
},
|
|
453
|
+
selected_options: [],
|
|
454
|
+
option_box: null,
|
|
455
|
+
unavailable: false,
|
|
456
|
+
unavailable_groups: [],
|
|
457
|
+
discovered_options: options.map((option) => ({
|
|
458
|
+
group: option.group,
|
|
459
|
+
label: option.label,
|
|
460
|
+
active: option.active,
|
|
461
|
+
node_id: option.node_id
|
|
462
|
+
}))
|
|
463
|
+
};
|
|
464
|
+
}
|
|
353
465
|
const selected = labels.length
|
|
354
|
-
? chooseFilterOptionByLabels(options, { group, labels })
|
|
466
|
+
? chooseFilterOptionByLabels(options, { group, labels, allowUnlimited })
|
|
355
467
|
: chooseFirstSafeFilterOption(options, groupOrder);
|
|
356
468
|
|
|
357
469
|
if (!selected) {
|
|
470
|
+
const requestedLabels = labels.map(normalizeFilterOptionLabel).filter(Boolean);
|
|
471
|
+
const groupOptions = options.filter((option) => !group || option.group === group);
|
|
472
|
+
const unavailableDefault = (
|
|
473
|
+
group === RECOMMEND_ACTIVITY_GROUP
|
|
474
|
+
&& allowUnlimited
|
|
475
|
+
&& requestedLabels.length === 1
|
|
476
|
+
&& requestedLabels[0] === normalizeFilterOptionLabel("不限")
|
|
477
|
+
&& groupOptions.length === 0
|
|
478
|
+
);
|
|
479
|
+
if (unavailableDefault) {
|
|
480
|
+
return {
|
|
481
|
+
selected_option: null,
|
|
482
|
+
selected_options: [],
|
|
483
|
+
option_box: null,
|
|
484
|
+
unavailable: true,
|
|
485
|
+
unavailable_groups: [{
|
|
486
|
+
group,
|
|
487
|
+
requested_labels: labels,
|
|
488
|
+
reason: "activity_control_unavailable_default"
|
|
489
|
+
}],
|
|
490
|
+
discovered_options: options.map((option) => ({
|
|
491
|
+
group: option.group,
|
|
492
|
+
label: option.label,
|
|
493
|
+
active: option.active,
|
|
494
|
+
node_id: option.node_id
|
|
495
|
+
}))
|
|
496
|
+
};
|
|
497
|
+
}
|
|
358
498
|
const target = labels.length
|
|
359
499
|
? `${group || "any group"} / ${labels.join(", ")}`
|
|
360
500
|
: "first safe non-active option";
|
|
361
501
|
throw new Error(`No matching recommend filter option was found for ${target}`);
|
|
362
502
|
}
|
|
363
|
-
|
|
364
|
-
const box = await clickNodeCenter(client, selected.node_id, {
|
|
365
|
-
...DETERMINISTIC_CLICK_OPTIONS,
|
|
366
|
-
scrollIntoView: true
|
|
367
|
-
});
|
|
368
|
-
await sleep(300);
|
|
369
|
-
|
|
370
|
-
return {
|
|
503
|
+
|
|
504
|
+
const box = await clickNodeCenter(client, selected.node_id, {
|
|
505
|
+
...DETERMINISTIC_CLICK_OPTIONS,
|
|
506
|
+
scrollIntoView: true
|
|
507
|
+
});
|
|
508
|
+
await sleep(300);
|
|
509
|
+
|
|
510
|
+
return {
|
|
371
511
|
selected_option: {
|
|
372
512
|
group: selected.group,
|
|
373
513
|
label: selected.label,
|
|
374
514
|
node_id: selected.node_id,
|
|
375
515
|
was_active: selected.active,
|
|
516
|
+
clicked: true,
|
|
376
517
|
requested_group: group || null,
|
|
377
518
|
requested_labels: labels
|
|
378
519
|
},
|
|
379
520
|
option_box: box,
|
|
521
|
+
unavailable: false,
|
|
522
|
+
unavailable_groups: [],
|
|
380
523
|
discovered_options: options.map((option) => ({
|
|
381
524
|
group: option.group,
|
|
382
525
|
label: option.label,
|
|
383
526
|
active: option.active,
|
|
384
527
|
node_id: option.node_id
|
|
385
|
-
}))
|
|
386
|
-
};
|
|
528
|
+
}))
|
|
529
|
+
};
|
|
387
530
|
}
|
|
388
531
|
|
|
389
532
|
export async function selectFilterOptions(client, frameNodeId, {
|
|
390
533
|
group = "",
|
|
391
534
|
labels = [],
|
|
392
|
-
groupOrder = RECOMMEND_FILTER_GROUP_ORDER
|
|
535
|
+
groupOrder = RECOMMEND_FILTER_GROUP_ORDER,
|
|
536
|
+
allowUnlimited = false
|
|
393
537
|
} = {}) {
|
|
394
538
|
if (!labels.length) {
|
|
395
|
-
return selectFilterOption(client, frameNodeId, {
|
|
539
|
+
return selectFilterOption(client, frameNodeId, {
|
|
540
|
+
group,
|
|
541
|
+
labels,
|
|
542
|
+
groupOrder,
|
|
543
|
+
allowUnlimited
|
|
544
|
+
});
|
|
396
545
|
}
|
|
397
546
|
|
|
398
547
|
const selectedOptions = [];
|
|
399
548
|
const missingLabels = [];
|
|
400
549
|
let discoveredOptions = [];
|
|
401
|
-
|
|
402
|
-
for (const label of labels) {
|
|
403
|
-
if (await getFilterPanelCount(client, frameNodeId) === 0) {
|
|
404
|
-
await openFilterPanel(client, frameNodeId);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
const options = await listFilterOptions(client, frameNodeId, { groupOrder });
|
|
408
|
-
discoveredOptions = options.map((option) => ({
|
|
550
|
+
|
|
551
|
+
for (const label of labels) {
|
|
552
|
+
if (await getFilterPanelCount(client, frameNodeId) === 0) {
|
|
553
|
+
await openFilterPanel(client, frameNodeId);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const options = await listFilterOptions(client, frameNodeId, { groupOrder });
|
|
557
|
+
discoveredOptions = options.map((option) => ({
|
|
409
558
|
group: option.group,
|
|
410
559
|
label: option.label,
|
|
411
560
|
active: option.active,
|
|
412
561
|
node_id: option.node_id
|
|
413
562
|
}));
|
|
414
|
-
const selected = chooseFilterOptionByLabels(options, {
|
|
563
|
+
const selected = chooseFilterOptionByLabels(options, {
|
|
564
|
+
group,
|
|
565
|
+
labels: [label],
|
|
566
|
+
allowUnlimited
|
|
567
|
+
});
|
|
415
568
|
const alreadyActive = options.find((option) => (
|
|
416
569
|
(!group || option.group === group)
|
|
417
570
|
&& normalizeFilterOptionLabel(option.label) === normalizeFilterOptionLabel(label)
|
|
418
571
|
&& option.active
|
|
572
|
+
&& isSelectableFilterOptionLabel(option.label, { allowUnlimited })
|
|
419
573
|
));
|
|
420
574
|
|
|
421
575
|
if (alreadyActive) {
|
|
422
576
|
selectedOptions.push({
|
|
423
577
|
group: alreadyActive.group,
|
|
424
|
-
label: alreadyActive.label,
|
|
425
|
-
node_id: alreadyActive.node_id,
|
|
426
|
-
was_active: true,
|
|
427
|
-
clicked: false,
|
|
428
|
-
requested_group: group || null
|
|
429
|
-
});
|
|
430
|
-
continue;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
if (!selected) {
|
|
434
|
-
missingLabels.push(label);
|
|
435
|
-
continue;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
const box = await clickNodeCenter(client, selected.node_id, {
|
|
439
|
-
...DETERMINISTIC_CLICK_OPTIONS,
|
|
440
|
-
scrollIntoView: true
|
|
441
|
-
});
|
|
442
|
-
selectedOptions.push({
|
|
443
|
-
group: selected.group,
|
|
444
|
-
label: selected.label,
|
|
445
|
-
node_id: selected.node_id,
|
|
446
|
-
was_active: false,
|
|
447
|
-
clicked: true,
|
|
448
|
-
requested_group: group || null,
|
|
449
|
-
option_box: box
|
|
578
|
+
label: alreadyActive.label,
|
|
579
|
+
node_id: alreadyActive.node_id,
|
|
580
|
+
was_active: true,
|
|
581
|
+
clicked: false,
|
|
582
|
+
requested_group: group || null
|
|
583
|
+
});
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (!selected) {
|
|
588
|
+
missingLabels.push(label);
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const box = await clickNodeCenter(client, selected.node_id, {
|
|
593
|
+
...DETERMINISTIC_CLICK_OPTIONS,
|
|
594
|
+
scrollIntoView: true
|
|
595
|
+
});
|
|
596
|
+
selectedOptions.push({
|
|
597
|
+
group: selected.group,
|
|
598
|
+
label: selected.label,
|
|
599
|
+
node_id: selected.node_id,
|
|
600
|
+
was_active: false,
|
|
601
|
+
clicked: true,
|
|
602
|
+
requested_group: group || null,
|
|
603
|
+
option_box: box
|
|
450
604
|
});
|
|
451
605
|
await sleep(450);
|
|
452
606
|
}
|
|
453
607
|
|
|
454
608
|
if (missingLabels.length) {
|
|
609
|
+
const groupOptions = discoveredOptions.filter((option) => !group || option.group === group);
|
|
610
|
+
const unavailableDefault = (
|
|
611
|
+
group === RECOMMEND_ACTIVITY_GROUP
|
|
612
|
+
&& allowUnlimited
|
|
613
|
+
&& missingLabels.length === 1
|
|
614
|
+
&& normalizeFilterOptionLabel(missingLabels[0]) === normalizeFilterOptionLabel("不限")
|
|
615
|
+
&& groupOptions.length === 0
|
|
616
|
+
);
|
|
617
|
+
if (unavailableDefault) {
|
|
618
|
+
return {
|
|
619
|
+
selected_option: null,
|
|
620
|
+
selected_options: [],
|
|
621
|
+
option_box: null,
|
|
622
|
+
unavailable: true,
|
|
623
|
+
unavailable_groups: [{
|
|
624
|
+
group,
|
|
625
|
+
requested_labels: labels,
|
|
626
|
+
reason: "activity_control_unavailable_default"
|
|
627
|
+
}],
|
|
628
|
+
discovered_options: discoveredOptions
|
|
629
|
+
};
|
|
630
|
+
}
|
|
455
631
|
throw new Error(`No matching recommend filter options were found for ${group || "any group"} / ${missingLabels.join(", ")}`);
|
|
456
632
|
}
|
|
457
633
|
|
|
458
634
|
return {
|
|
459
635
|
selected_option: selectedOptions[0] || null,
|
|
460
|
-
selected_options: selectedOptions.map((option) => ({
|
|
461
|
-
group: option.group,
|
|
462
|
-
label: option.label,
|
|
463
|
-
node_id: option.node_id,
|
|
464
|
-
was_active: option.was_active,
|
|
636
|
+
selected_options: selectedOptions.map((option) => ({
|
|
637
|
+
group: option.group,
|
|
638
|
+
label: option.label,
|
|
639
|
+
node_id: option.node_id,
|
|
640
|
+
was_active: option.was_active,
|
|
465
641
|
clicked: option.clicked,
|
|
466
642
|
requested_group: option.requested_group,
|
|
467
643
|
requested_labels: labels
|
|
468
644
|
})),
|
|
469
645
|
option_box: selectedOptions.find((option) => option.option_box)?.option_box || null,
|
|
646
|
+
unavailable: false,
|
|
647
|
+
unavailable_groups: [],
|
|
470
648
|
discovered_options: discoveredOptions
|
|
471
649
|
};
|
|
472
650
|
}
|
|
@@ -477,23 +655,26 @@ export async function selectFilterGroups(client, frameNodeId, {
|
|
|
477
655
|
} = {}) {
|
|
478
656
|
const selectedOptions = [];
|
|
479
657
|
const discoveredOptions = [];
|
|
658
|
+
const unavailableGroups = [];
|
|
480
659
|
const groups = filterGroups.filter((item) => item && (item.group || item.labels?.length));
|
|
481
660
|
if (!groups.length) {
|
|
482
661
|
return selectFilterOption(client, frameNodeId, { groupOrder });
|
|
483
662
|
}
|
|
484
663
|
|
|
485
|
-
for (const spec of groups) {
|
|
664
|
+
for (const spec of groups) {
|
|
486
665
|
const labels = Array.isArray(spec.labels) ? spec.labels : [];
|
|
487
666
|
const selection = spec.selectAllLabels === false
|
|
488
667
|
? await selectFilterOption(client, frameNodeId, {
|
|
489
668
|
group: spec.group || "",
|
|
490
669
|
labels,
|
|
491
|
-
groupOrder
|
|
670
|
+
groupOrder,
|
|
671
|
+
allowUnlimited: spec.allowUnlimited === true
|
|
492
672
|
})
|
|
493
673
|
: await selectFilterOptions(client, frameNodeId, {
|
|
494
674
|
group: spec.group || "",
|
|
495
675
|
labels,
|
|
496
|
-
groupOrder
|
|
676
|
+
groupOrder,
|
|
677
|
+
allowUnlimited: spec.allowUnlimited === true
|
|
497
678
|
});
|
|
498
679
|
if (selection.selected_option) selectedOptions.push(selection.selected_option);
|
|
499
680
|
for (const option of selection.selected_options || []) {
|
|
@@ -502,109 +683,299 @@ export async function selectFilterGroups(client, frameNodeId, {
|
|
|
502
683
|
for (const option of selection.discovered_options || []) {
|
|
503
684
|
discoveredOptions.push(option);
|
|
504
685
|
}
|
|
686
|
+
for (const unavailable of selection.unavailable_groups || []) {
|
|
687
|
+
unavailableGroups.push(unavailable);
|
|
688
|
+
}
|
|
505
689
|
}
|
|
506
690
|
|
|
507
691
|
const dedupedSelected = [];
|
|
508
692
|
const seenSelected = new Set();
|
|
509
693
|
for (const option of selectedOptions) {
|
|
510
|
-
const key = `${option.group || ""}:${normalizeFilterOptionLabel(option.label || "")}`;
|
|
511
|
-
if (seenSelected.has(key)) continue;
|
|
512
|
-
seenSelected.add(key);
|
|
513
|
-
dedupedSelected.push(option);
|
|
514
|
-
}
|
|
694
|
+
const key = `${option.group || ""}:${normalizeFilterOptionLabel(option.label || "")}`;
|
|
695
|
+
if (seenSelected.has(key)) continue;
|
|
696
|
+
seenSelected.add(key);
|
|
697
|
+
dedupedSelected.push(option);
|
|
698
|
+
}
|
|
515
699
|
|
|
516
700
|
return {
|
|
517
701
|
selected_option: dedupedSelected[0] || null,
|
|
518
702
|
selected_options: dedupedSelected,
|
|
519
703
|
option_box: dedupedSelected.find((option) => option.option_box)?.option_box || null,
|
|
704
|
+
unavailable: unavailableGroups.length > 0,
|
|
705
|
+
unavailable_groups: unavailableGroups,
|
|
520
706
|
discovered_options: discoveredOptions
|
|
521
707
|
};
|
|
522
708
|
}
|
|
523
709
|
|
|
524
710
|
export async function confirmFilterPanel(client, frameNodeId, {
|
|
525
|
-
timeoutMs = 8000
|
|
711
|
+
timeoutMs = 8000
|
|
712
|
+
} = {}) {
|
|
713
|
+
const candidates = await readConfirmButtonCandidates(client, frameNodeId);
|
|
714
|
+
if (!candidates.length && await getFilterPanelCount(client, frameNodeId) === 0) {
|
|
715
|
+
return {
|
|
716
|
+
confirmed: true,
|
|
717
|
+
confirm_node_id: null,
|
|
718
|
+
confirm_label: "auto-closed",
|
|
719
|
+
confirm_candidates: [],
|
|
720
|
+
confirm_attempts: [],
|
|
721
|
+
panel_count: 0
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
if (!candidates.length) {
|
|
725
|
+
throw new Error("Recommend filter confirm button was not found");
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
const attempts = [];
|
|
729
|
+
for (const candidate of candidates) {
|
|
730
|
+
const clickResult = await clickFirstAvailableNode(client, [candidate.node_id]);
|
|
731
|
+
attempts.push({
|
|
732
|
+
node_id: candidate.node_id,
|
|
733
|
+
label: candidate.label,
|
|
734
|
+
clicked: clickResult.clicked,
|
|
735
|
+
errors: clickResult.errors
|
|
736
|
+
});
|
|
737
|
+
if (!clickResult.clicked) continue;
|
|
738
|
+
|
|
739
|
+
const started = Date.now();
|
|
740
|
+
while (Date.now() - started <= timeoutMs) {
|
|
741
|
+
const panelCount = await getFilterPanelCount(client, frameNodeId);
|
|
742
|
+
if (panelCount === 0) {
|
|
743
|
+
return {
|
|
744
|
+
confirmed: true,
|
|
745
|
+
confirm_node_id: clickResult.node_id,
|
|
746
|
+
confirm_label: candidate.label,
|
|
747
|
+
confirm_box: clickResult.box,
|
|
748
|
+
confirm_candidates: candidates,
|
|
749
|
+
confirm_attempts: attempts,
|
|
750
|
+
panel_count: 0
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
await sleep(250);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
return {
|
|
758
|
+
confirmed: false,
|
|
759
|
+
confirm_node_id: attempts.at(-1)?.node_id || null,
|
|
760
|
+
confirm_label: attempts.at(-1)?.label || null,
|
|
761
|
+
confirm_candidates: candidates,
|
|
762
|
+
confirm_attempts: attempts,
|
|
763
|
+
panel_count: await getFilterPanelCount(client, frameNodeId)
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
function normalizeStickyVerificationSpecs(options = {}) {
|
|
768
|
+
const source = Array.isArray(options.filterGroups) && options.filterGroups.length
|
|
769
|
+
? options.filterGroups
|
|
770
|
+
: [options];
|
|
771
|
+
return source.filter((spec) => spec?.verifySticky === true).map((spec) => ({
|
|
772
|
+
group: String(spec.group || ""),
|
|
773
|
+
labels: Array.isArray(spec.labels) ? spec.labels : [],
|
|
774
|
+
selectAllLabels: spec.selectAllLabels !== false,
|
|
775
|
+
allowUnlimited: spec.allowUnlimited === true
|
|
776
|
+
})).filter((spec) => spec.group && spec.labels.length);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
function findUnavailableGroup(unavailableGroups = [], group = "") {
|
|
780
|
+
return unavailableGroups.find((item) => item?.group === group) || null;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
function normalizeRequestedFilterGroups(options = {}) {
|
|
784
|
+
const source = Array.isArray(options.filterGroups) && options.filterGroups.length
|
|
785
|
+
? options.filterGroups.filter(Boolean)
|
|
786
|
+
: (options.group || options.labels?.length ? [options] : []);
|
|
787
|
+
return source.map((spec) => ({
|
|
788
|
+
group: String(spec?.group || ""),
|
|
789
|
+
labels: Array.isArray(spec?.labels)
|
|
790
|
+
? spec.labels.map(normalizeFilterOptionLabel).filter(Boolean)
|
|
791
|
+
: [],
|
|
792
|
+
select_all_labels: spec?.selectAllLabels !== false,
|
|
793
|
+
allow_unlimited: spec?.allowUnlimited === true,
|
|
794
|
+
verify_sticky: spec?.verifySticky === true
|
|
795
|
+
}));
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
export async function verifyFilterGroupsSticky(client, frameNodeId, {
|
|
799
|
+
specs = [],
|
|
800
|
+
unavailableGroups = [],
|
|
801
|
+
settleMs = 300
|
|
526
802
|
} = {}) {
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
803
|
+
const normalizedSpecs = specs.filter((spec) => spec?.group && Array.isArray(spec.labels) && spec.labels.length);
|
|
804
|
+
const groups = [];
|
|
805
|
+
const availableSpecs = [];
|
|
806
|
+
for (const spec of normalizedSpecs) {
|
|
807
|
+
const unavailable = findUnavailableGroup(unavailableGroups, spec.group);
|
|
808
|
+
if (unavailable) {
|
|
809
|
+
groups.push({
|
|
810
|
+
group: spec.group,
|
|
811
|
+
requested_labels: spec.labels,
|
|
812
|
+
active_labels: [],
|
|
813
|
+
verified: true,
|
|
814
|
+
unavailable: true,
|
|
815
|
+
reason: unavailable.reason || "control_unavailable_default"
|
|
816
|
+
});
|
|
817
|
+
} else {
|
|
818
|
+
availableSpecs.push(spec);
|
|
819
|
+
}
|
|
540
820
|
}
|
|
541
821
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
822
|
+
let reopen = null;
|
|
823
|
+
let closeConfirmation = null;
|
|
824
|
+
if (availableSpecs.length) {
|
|
825
|
+
reopen = await openFilterPanel(client, frameNodeId);
|
|
826
|
+
for (const spec of availableSpecs) {
|
|
827
|
+
const options = await listFilterOptions(client, frameNodeId, {
|
|
828
|
+
groupOrder: [spec.group]
|
|
829
|
+
});
|
|
830
|
+
const requestedLabels = spec.labels.map(normalizeFilterOptionLabel).filter(Boolean);
|
|
831
|
+
const activeLabels = options
|
|
832
|
+
.filter((option) => option.group === spec.group && option.active)
|
|
833
|
+
.map((option) => normalizeFilterOptionLabel(option.label));
|
|
834
|
+
const verified = spec.selectAllLabels === false
|
|
835
|
+
? requestedLabels.length === 1
|
|
836
|
+
&& activeLabels.length === 1
|
|
837
|
+
&& activeLabels[0] === requestedLabels[0]
|
|
838
|
+
: requestedLabels.every((label) => activeLabels.includes(label));
|
|
839
|
+
groups.push({
|
|
840
|
+
group: spec.group,
|
|
841
|
+
requested_labels: requestedLabels,
|
|
842
|
+
active_labels: activeLabels,
|
|
843
|
+
verified,
|
|
844
|
+
unavailable: false,
|
|
845
|
+
single_select: spec.selectAllLabels === false,
|
|
846
|
+
discovered_options: options.map((option) => ({
|
|
847
|
+
label: option.label,
|
|
848
|
+
active: option.active,
|
|
849
|
+
node_id: option.node_id
|
|
850
|
+
}))
|
|
851
|
+
});
|
|
568
852
|
}
|
|
853
|
+
closeConfirmation = await confirmFilterPanel(client, frameNodeId);
|
|
854
|
+
if (!closeConfirmation.confirmed) {
|
|
855
|
+
throw new Error("Recommend filter sticky verification panel could not be closed");
|
|
856
|
+
}
|
|
857
|
+
if (settleMs > 0) await sleep(settleMs);
|
|
569
858
|
}
|
|
570
859
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
860
|
+
const verified = groups.length === normalizedSpecs.length && groups.every((item) => item.verified);
|
|
861
|
+
const result = {
|
|
862
|
+
verified,
|
|
863
|
+
groups,
|
|
864
|
+
reopened: Boolean(reopen),
|
|
865
|
+
reopen_panel_node_id: reopen?.panel_node_id || null,
|
|
866
|
+
close_confirmation: closeConfirmation
|
|
867
|
+
? {
|
|
868
|
+
confirmed: closeConfirmation.confirmed,
|
|
869
|
+
label: closeConfirmation.confirm_label,
|
|
870
|
+
node_id: closeConfirmation.confirm_node_id
|
|
871
|
+
}
|
|
872
|
+
: null
|
|
578
873
|
};
|
|
874
|
+
if (!verified) {
|
|
875
|
+
const failedGroups = groups.filter((item) => !item.verified).map((item) => item.group);
|
|
876
|
+
const error = new Error(`Recommend filter sticky verification failed for: ${failedGroups.join(", ")}`);
|
|
877
|
+
error.sticky_verification = result;
|
|
878
|
+
throw error;
|
|
879
|
+
}
|
|
880
|
+
return result;
|
|
579
881
|
}
|
|
580
882
|
|
|
581
883
|
export async function selectAndConfirmFirstSafeFilter(client, frameNodeId, options = {}) {
|
|
582
884
|
const beforeCounts = await getRecommendFilterCounts(client, frameNodeId);
|
|
583
|
-
const
|
|
885
|
+
const requestedGroups = normalizeRequestedFilterGroups(options);
|
|
886
|
+
let openResult;
|
|
887
|
+
try {
|
|
888
|
+
openResult = await openFilterPanel(client, frameNodeId);
|
|
889
|
+
} catch (error) {
|
|
890
|
+
const onlyGroup = requestedGroups.length === 1 ? requestedGroups[0] : null;
|
|
891
|
+
const requestedLabels = onlyGroup?.labels || [];
|
|
892
|
+
const unavailableActivityDefault = (
|
|
893
|
+
error?.code === "RECOMMEND_FILTER_TRIGGER_UNAVAILABLE"
|
|
894
|
+
&& normalizeText(onlyGroup?.group) === RECOMMEND_ACTIVITY_GROUP
|
|
895
|
+
&& onlyGroup?.allow_unlimited === true
|
|
896
|
+
&& requestedLabels.length === 1
|
|
897
|
+
&& requestedLabels[0] === normalizeFilterOptionLabel("不限")
|
|
898
|
+
);
|
|
899
|
+
if (!unavailableActivityDefault) throw error;
|
|
900
|
+
|
|
901
|
+
const unavailableGroups = [{
|
|
902
|
+
group: RECOMMEND_ACTIVITY_GROUP,
|
|
903
|
+
requested_labels: requestedLabels,
|
|
904
|
+
reason: "activity_control_unavailable_default",
|
|
905
|
+
scope: "filter_panel"
|
|
906
|
+
}];
|
|
907
|
+
const stickySpecs = normalizeStickyVerificationSpecs(options);
|
|
908
|
+
const stickyVerification = stickySpecs.length
|
|
909
|
+
? await verifyFilterGroupsSticky(client, frameNodeId, {
|
|
910
|
+
specs: stickySpecs,
|
|
911
|
+
unavailableGroups,
|
|
912
|
+
settleMs: 0
|
|
913
|
+
})
|
|
914
|
+
: null;
|
|
915
|
+
const afterCounts = await getRecommendFilterCounts(client, frameNodeId);
|
|
916
|
+
return {
|
|
917
|
+
opened_panel: false,
|
|
918
|
+
trigger: null,
|
|
919
|
+
initial_close_attempts: [],
|
|
920
|
+
open_attempts: [],
|
|
921
|
+
before_counts: beforeCounts,
|
|
922
|
+
after_open_counts: afterCounts,
|
|
923
|
+
requested_groups: requestedGroups,
|
|
924
|
+
selected_option: null,
|
|
925
|
+
selected_options: [],
|
|
926
|
+
option_box: null,
|
|
927
|
+
unavailable: true,
|
|
928
|
+
unavailable_default: true,
|
|
929
|
+
unavailable_groups: unavailableGroups,
|
|
930
|
+
discovered_options: [],
|
|
931
|
+
confirmed: true,
|
|
932
|
+
confirm_node_id: null,
|
|
933
|
+
confirm_label: "unavailable-default",
|
|
934
|
+
confirm_candidates: [],
|
|
935
|
+
confirm_attempts: [],
|
|
936
|
+
panel_count: Number(afterCounts.filter_panel || 0),
|
|
937
|
+
sticky_verification: stickyVerification,
|
|
938
|
+
after_confirm_counts: afterCounts
|
|
939
|
+
};
|
|
940
|
+
}
|
|
584
941
|
const afterOpenCounts = await getRecommendFilterCounts(client, frameNodeId);
|
|
585
942
|
const filterGroups = Array.isArray(options.filterGroups) ? options.filterGroups : [];
|
|
586
|
-
const selection = filterGroups.length
|
|
943
|
+
const selection = filterGroups.length
|
|
587
944
|
? await selectFilterGroups(client, frameNodeId, { filterGroups, groupOrder: options.groupOrder })
|
|
588
945
|
: options.selectAllLabels
|
|
589
946
|
? await selectFilterOptions(client, frameNodeId, options)
|
|
590
947
|
: await selectFilterOption(client, frameNodeId, options);
|
|
591
948
|
const confirm = await confirmFilterPanel(client, frameNodeId);
|
|
592
|
-
|
|
949
|
+
const stickySpecs = normalizeStickyVerificationSpecs(options);
|
|
950
|
+
const stickyVerification = confirm.confirmed && stickySpecs.length
|
|
951
|
+
? await verifyFilterGroupsSticky(client, frameNodeId, {
|
|
952
|
+
specs: stickySpecs,
|
|
953
|
+
unavailableGroups: selection.unavailable_groups || [],
|
|
954
|
+
settleMs: Number.isFinite(options.stickySettleMs) ? Math.max(0, options.stickySettleMs) : 300
|
|
955
|
+
})
|
|
956
|
+
: null;
|
|
957
|
+
const afterConfirmSettleMs = Number.isFinite(options.afterConfirmSettleMs)
|
|
958
|
+
? Math.max(0, options.afterConfirmSettleMs)
|
|
959
|
+
: 1200;
|
|
960
|
+
if (afterConfirmSettleMs > 0) await sleep(afterConfirmSettleMs);
|
|
593
961
|
const afterConfirmCounts = await getRecommendFilterCounts(client, frameNodeId);
|
|
594
962
|
|
|
595
963
|
return {
|
|
596
964
|
opened_panel: true,
|
|
965
|
+
requested_groups: requestedGroups,
|
|
597
966
|
trigger: {
|
|
598
|
-
node_id: openResult.trigger.nodeId,
|
|
599
|
-
selector: openResult.trigger.selector,
|
|
600
|
-
center: openResult.trigger_box.center,
|
|
601
|
-
rect: openResult.trigger_box.rect
|
|
967
|
+
node_id: openResult.trigger.nodeId,
|
|
968
|
+
selector: openResult.trigger.selector,
|
|
969
|
+
center: openResult.trigger_box.center,
|
|
970
|
+
rect: openResult.trigger_box.rect
|
|
602
971
|
},
|
|
603
972
|
initial_close_attempts: openResult.initial_close_attempts,
|
|
973
|
+
open_attempts: openResult.open_attempts || [],
|
|
604
974
|
before_counts: beforeCounts,
|
|
605
975
|
after_open_counts: afterOpenCounts,
|
|
606
976
|
...selection,
|
|
607
977
|
...confirm,
|
|
978
|
+
sticky_verification: stickyVerification,
|
|
608
979
|
after_confirm_counts: afterConfirmCounts
|
|
609
980
|
};
|
|
610
981
|
}
|