@reconcrap/boss-recommend-mcp 2.1.21 → 2.1.22
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 +8 -8
- 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 +2419 -2415
- 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 +1326 -1326
- package/src/core/reporting/legacy-csv.js +334 -332
- package/src/core/run/index.js +32 -32
- 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 +650 -650
- 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 +504 -361
- package/src/domains/recommend/roots.js +80 -80
- package/src/domains/recommend/run-service.js +987 -854
- 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 +13 -0
- package/src/parser.js +376 -8
- package/src/recommend-mcp.js +929 -915
- package/src/recommend-scheduler.js +496 -496
- package/src/recruit-mcp.js +2121 -2121
|
@@ -1,266 +1,360 @@
|
|
|
1
|
-
import { sleep } from "../../core/browser/index.js";
|
|
2
|
-
import {
|
|
3
|
-
buildRecommendSelfHealConfig,
|
|
4
|
-
resolveRecommendSelfHealRoots
|
|
5
|
-
} from "../../core/self-heal/index.js";
|
|
6
|
-
import {
|
|
7
|
-
createRecoverySettleError,
|
|
8
|
-
waitForMiniFreshStartSettle
|
|
9
|
-
} from "../common/recovery-settle.js";
|
|
10
|
-
import {
|
|
11
|
-
clickRecommendEndRefreshButton,
|
|
12
|
-
waitForRecommendCardNodeIds
|
|
13
|
-
} from "./cards.js";
|
|
1
|
+
import { sleep } from "../../core/browser/index.js";
|
|
2
|
+
import {
|
|
3
|
+
buildRecommendSelfHealConfig,
|
|
4
|
+
resolveRecommendSelfHealRoots
|
|
5
|
+
} from "../../core/self-heal/index.js";
|
|
6
|
+
import {
|
|
7
|
+
createRecoverySettleError,
|
|
8
|
+
waitForMiniFreshStartSettle
|
|
9
|
+
} from "../common/recovery-settle.js";
|
|
10
|
+
import {
|
|
11
|
+
clickRecommendEndRefreshButton,
|
|
12
|
+
waitForRecommendCardNodeIds
|
|
13
|
+
} from "./cards.js";
|
|
14
14
|
import {
|
|
15
15
|
RECOMMEND_RECENT_NOT_VIEW_LABEL,
|
|
16
16
|
RECOMMEND_TARGET_URL
|
|
17
17
|
} from "./constants.js";
|
|
18
18
|
import { selectAndConfirmFirstSafeFilter } from "./filters.js";
|
|
19
|
+
import { ensureRecommendCurrentCityOnly } from "./location.js";
|
|
19
20
|
import {
|
|
20
21
|
selectRecommendJob,
|
|
21
22
|
verifyRecommendJobSelection
|
|
22
23
|
} from "./jobs.js";
|
|
23
24
|
import { selectRecommendPageScope } from "./scopes.js";
|
|
24
|
-
import {
|
|
25
|
-
getRecommendRoots,
|
|
26
|
-
waitForRecommendRoots
|
|
27
|
-
} from "./roots.js";
|
|
28
|
-
import { isStaleRecommendNodeError } from "./detail.js";
|
|
29
|
-
|
|
30
|
-
function normalizeLabels(labels = []) {
|
|
31
|
-
return labels.map((label) => String(label || "").trim()).filter(Boolean);
|
|
32
|
-
}
|
|
25
|
+
import {
|
|
26
|
+
getRecommendRoots,
|
|
27
|
+
waitForRecommendRoots
|
|
28
|
+
} from "./roots.js";
|
|
29
|
+
import { isStaleRecommendNodeError } from "./detail.js";
|
|
30
|
+
|
|
31
|
+
function normalizeLabels(labels = []) {
|
|
32
|
+
return labels.map((label) => String(label || "").trim()).filter(Boolean);
|
|
33
|
+
}
|
|
33
34
|
|
|
34
35
|
function normalizeFilterGroup(spec = {}) {
|
|
35
36
|
return {
|
|
36
37
|
group: String(spec.group || "").trim(),
|
|
37
38
|
labels: normalizeLabels(spec.labels || spec.filterLabels || []),
|
|
38
|
-
selectAllLabels: spec.selectAllLabels !== false
|
|
39
|
+
selectAllLabels: spec.selectAllLabels !== false,
|
|
40
|
+
allowUnlimited: spec.allowUnlimited === true,
|
|
41
|
+
verifySticky: spec.verifySticky === true
|
|
39
42
|
};
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
export function buildRecommendFilterGroups(filter = {}, {
|
|
43
46
|
forceRecentNotView = false
|
|
44
|
-
} = {}) {
|
|
45
|
-
const groups = [];
|
|
46
|
-
const sourceGroups = Array.isArray(filter.filterGroups)
|
|
47
|
-
? filter.filterGroups
|
|
48
|
-
: Array.isArray(filter.groups)
|
|
49
|
-
? filter.groups
|
|
50
|
-
: [];
|
|
51
|
-
|
|
52
|
-
for (const spec of sourceGroups) {
|
|
53
|
-
const group = normalizeFilterGroup(spec);
|
|
54
|
-
if (group.group || group.labels.length) groups.push(group);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const rootGroup = normalizeFilterGroup(filter);
|
|
58
|
-
if ((rootGroup.group || rootGroup.labels.length) && !groups.length) {
|
|
59
|
-
groups.push(rootGroup);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (forceRecentNotView) {
|
|
63
|
-
const recentGroup = groups.find((item) => item.group === "recentNotView");
|
|
64
|
-
if (recentGroup) {
|
|
65
|
-
if (!recentGroup.labels.some((label) => label.replace(/\s+/g, "") === RECOMMEND_RECENT_NOT_VIEW_LABEL)) {
|
|
66
|
-
recentGroup.labels.push(RECOMMEND_RECENT_NOT_VIEW_LABEL);
|
|
67
|
-
}
|
|
47
|
+
} = {}) {
|
|
48
|
+
const groups = [];
|
|
49
|
+
const sourceGroups = Array.isArray(filter.filterGroups)
|
|
50
|
+
? filter.filterGroups
|
|
51
|
+
: Array.isArray(filter.groups)
|
|
52
|
+
? filter.groups
|
|
53
|
+
: [];
|
|
54
|
+
|
|
55
|
+
for (const spec of sourceGroups) {
|
|
56
|
+
const group = normalizeFilterGroup(spec);
|
|
57
|
+
if (group.group || group.labels.length) groups.push(group);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const rootGroup = normalizeFilterGroup(filter);
|
|
61
|
+
if ((rootGroup.group || rootGroup.labels.length) && !groups.length) {
|
|
62
|
+
groups.push(rootGroup);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (forceRecentNotView) {
|
|
66
|
+
const recentGroup = groups.find((item) => item.group === "recentNotView");
|
|
67
|
+
if (recentGroup) {
|
|
68
|
+
if (!recentGroup.labels.some((label) => label.replace(/\s+/g, "") === RECOMMEND_RECENT_NOT_VIEW_LABEL)) {
|
|
69
|
+
recentGroup.labels.push(RECOMMEND_RECENT_NOT_VIEW_LABEL);
|
|
70
|
+
}
|
|
68
71
|
recentGroup.selectAllLabels = true;
|
|
69
72
|
} else {
|
|
70
73
|
groups.unshift({
|
|
71
74
|
group: "recentNotView",
|
|
72
75
|
labels: [RECOMMEND_RECENT_NOT_VIEW_LABEL],
|
|
73
|
-
selectAllLabels: true
|
|
76
|
+
selectAllLabels: true,
|
|
77
|
+
allowUnlimited: false,
|
|
78
|
+
verifySticky: false
|
|
74
79
|
});
|
|
75
80
|
}
|
|
76
81
|
}
|
|
77
82
|
|
|
78
83
|
return groups;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export function buildRecommendFilterSelectionOptions(filter = {}, {
|
|
82
|
-
forceRecentNotView = false
|
|
83
|
-
} = {}) {
|
|
84
|
-
const filterGroups = buildRecommendFilterGroups(filter, { forceRecentNotView });
|
|
85
|
-
if (filterGroups.length > 1 || forceRecentNotView || Array.isArray(filter.filterGroups) || Array.isArray(filter.groups)) {
|
|
86
|
-
return { filterGroups };
|
|
87
|
-
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function buildRecommendFilterSelectionOptions(filter = {}, {
|
|
87
|
+
forceRecentNotView = false
|
|
88
|
+
} = {}) {
|
|
89
|
+
const filterGroups = buildRecommendFilterGroups(filter, { forceRecentNotView });
|
|
90
|
+
if (filterGroups.length > 1 || forceRecentNotView || Array.isArray(filter.filterGroups) || Array.isArray(filter.groups)) {
|
|
91
|
+
return { filterGroups };
|
|
92
|
+
}
|
|
88
93
|
const [singleGroup] = filterGroups;
|
|
89
94
|
if (singleGroup) {
|
|
90
95
|
return {
|
|
91
96
|
group: singleGroup.group,
|
|
92
97
|
labels: singleGroup.labels,
|
|
93
|
-
selectAllLabels: singleGroup.selectAllLabels
|
|
98
|
+
selectAllLabels: singleGroup.selectAllLabels,
|
|
99
|
+
allowUnlimited: singleGroup.allowUnlimited,
|
|
100
|
+
verifySticky: singleGroup.verifySticky
|
|
94
101
|
};
|
|
95
102
|
}
|
|
96
103
|
return {
|
|
97
104
|
group: filter.group || "",
|
|
98
105
|
labels: normalizeLabels(filter.labels || filter.filterLabels || []),
|
|
99
|
-
selectAllLabels: filter.selectAllLabels !== false
|
|
106
|
+
selectAllLabels: filter.selectAllLabels !== false,
|
|
107
|
+
allowUnlimited: filter.allowUnlimited === true,
|
|
108
|
+
verifySticky: filter.verifySticky === true
|
|
100
109
|
};
|
|
101
110
|
}
|
|
102
111
|
|
|
103
|
-
function
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
export function isRetryableRecommendFilterReapplyError(error) {
|
|
108
|
-
const message = String(error?.message || error || "");
|
|
109
|
-
return /Recommend filter panel did not open|Recommend filter trigger was not found|Recommend filter confirm button was not found|No matching recommend filter option/i.test(message);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function compactFilterReapplyError(error) {
|
|
113
|
-
return error?.message || String(error || "Recommend filter reapply failed");
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export function isRetryableRecommendJobSelectionError(error) {
|
|
117
|
-
if (isStaleRecommendNodeError(error)) return true;
|
|
118
|
-
const message = String(error?.message || error || "");
|
|
119
|
-
return /Recommend job trigger was not found|Recommend job dropdown did not mount options|Recommend job dropdown did not expose visible options|Matched recommend job has no clickable center|Matched recommend job has no visible clickable option|Recommend job selection was not sticky|Recommend job dropdown remained open after sticky verification/i.test(message);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function compactJobSelectionAttempt({
|
|
123
|
-
ok = false,
|
|
124
|
-
attempt = 0,
|
|
125
|
-
iframeDocumentNodeId = 0,
|
|
126
|
-
error = null,
|
|
127
|
-
selection = null
|
|
112
|
+
export async function applyRecommendFilterEnvelopeStages(filter = {}, {
|
|
113
|
+
applyCurrentCityOnly,
|
|
114
|
+
applyFilterPanel
|
|
128
115
|
} = {}) {
|
|
116
|
+
if (filter.enabled === false) {
|
|
117
|
+
return {
|
|
118
|
+
applied: false,
|
|
119
|
+
skipped: true,
|
|
120
|
+
current_city_only: null,
|
|
121
|
+
filter: null
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
if (typeof applyCurrentCityOnly !== "function" || typeof applyFilterPanel !== "function") {
|
|
125
|
+
throw new Error("Recommend native filter stages require location and filter-panel callbacks");
|
|
126
|
+
}
|
|
127
|
+
const currentCityOnlyResult = await applyCurrentCityOnly();
|
|
128
|
+
const filterResult = await applyFilterPanel();
|
|
129
129
|
return {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
attempt,
|
|
135
|
-
iframe_document_node_id: iframeDocumentNodeId || 0,
|
|
136
|
-
selected: Boolean(selection?.selected),
|
|
137
|
-
selection_reason: selection?.reason || null,
|
|
138
|
-
sticky_verified: selection?.sticky_verification?.verified ?? null,
|
|
139
|
-
sticky_current_label: selection?.sticky_verification?.current_label_without_salary
|
|
140
|
-
|| selection?.sticky_verification?.current_label
|
|
141
|
-
|| null,
|
|
142
|
-
sticky_menu_closed: selection?.sticky_verification?.menu_close?.ok ?? null
|
|
130
|
+
applied: true,
|
|
131
|
+
skipped: false,
|
|
132
|
+
current_city_only: currentCityOnlyResult,
|
|
133
|
+
filter: filterResult
|
|
143
134
|
};
|
|
144
135
|
}
|
|
145
136
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
137
|
+
function refreshFailureReason(method = "") {
|
|
138
|
+
return method === "page_navigate" ? "page_navigate_failed" : "page_reload_failed";
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function isRetryableRecommendFilterReapplyError(error) {
|
|
142
|
+
const message = String(error?.message || error || "");
|
|
143
|
+
return /Recommend filter panel did not open|Recommend filter trigger was not found|Recommend filter confirm button was not found|No matching recommend filter option/i.test(message);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function compactFilterReapplyError(error) {
|
|
147
|
+
return error?.message || String(error || "Recommend filter reapply failed");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function isRetryableRecommendJobSelectionError(error) {
|
|
151
|
+
if (isStaleRecommendNodeError(error)) return true;
|
|
152
|
+
const message = String(error?.message || error || "");
|
|
153
|
+
return /Recommend job trigger was not found|Recommend job dropdown did not mount options|Recommend job dropdown did not expose visible options|Matched recommend job has no clickable center|Matched recommend job has no visible clickable option|Recommend job selection was not sticky|Recommend job dropdown remained open after sticky verification/i.test(message);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function compactJobSelectionAttempt({
|
|
157
|
+
ok = false,
|
|
158
|
+
attempt = 0,
|
|
159
|
+
iframeDocumentNodeId = 0,
|
|
160
|
+
error = null,
|
|
161
|
+
selection = null
|
|
162
|
+
} = {}) {
|
|
163
|
+
return {
|
|
164
|
+
ok: Boolean(ok),
|
|
165
|
+
method: "job_select",
|
|
166
|
+
reason: error ? "job_select_failed" : null,
|
|
167
|
+
error: error ? (error?.message || String(error)) : null,
|
|
168
|
+
attempt,
|
|
169
|
+
iframe_document_node_id: iframeDocumentNodeId || 0,
|
|
170
|
+
selected: Boolean(selection?.selected),
|
|
171
|
+
selection_reason: selection?.reason || null,
|
|
172
|
+
sticky_verified: selection?.sticky_verification?.verified ?? null,
|
|
173
|
+
sticky_current_label: selection?.sticky_verification?.current_label_without_salary
|
|
174
|
+
|| selection?.sticky_verification?.current_label
|
|
175
|
+
|| null,
|
|
176
|
+
sticky_menu_closed: selection?.sticky_verification?.menu_close?.ok ?? null
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function waitForRecommendRecoverySettle(client, {
|
|
181
|
+
reloadSettleMs = 8000,
|
|
182
|
+
timeoutMs = 90000
|
|
183
|
+
} = {}) {
|
|
184
|
+
return waitForMiniFreshStartSettle(client, {
|
|
185
|
+
domain: "recommend",
|
|
186
|
+
timeoutMs,
|
|
187
|
+
intervalMs: reloadSettleMs > 10000 ? 1200 : 800,
|
|
188
|
+
settleMs: Math.max(0, Math.min(reloadSettleMs || 0, 5000)),
|
|
189
|
+
selfHealConfig: buildRecommendSelfHealConfig(),
|
|
190
|
+
resolveSelfHealRoots: resolveRecommendSelfHealRoots
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function waitForFreshRecommendRoots(client, {
|
|
195
|
+
timeoutMs = 10000,
|
|
196
|
+
intervalMs = 500
|
|
197
|
+
} = {}) {
|
|
198
|
+
const rootState = await waitForRecommendRoots(client, {
|
|
199
|
+
timeoutMs,
|
|
200
|
+
intervalMs
|
|
201
|
+
});
|
|
202
|
+
return rootState?.iframe?.documentNodeId ? rootState : null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export async function selectRecommendJobWithRootRefresh(client, rootState, {
|
|
206
|
+
jobLabel = "",
|
|
207
|
+
settleMs = 6000,
|
|
208
|
+
dropdownTimeoutMs = 4000,
|
|
209
|
+
totalTimeoutMs = 30000,
|
|
210
|
+
retryDelayMs = 1000
|
|
211
|
+
} = {}) {
|
|
212
|
+
const started = Date.now();
|
|
213
|
+
const attempts = [];
|
|
214
|
+
let currentRootState = rootState || null;
|
|
215
|
+
let lastError = null;
|
|
216
|
+
let attempt = 0;
|
|
217
|
+
|
|
218
|
+
while (Date.now() - started <= totalTimeoutMs) {
|
|
219
|
+
attempt += 1;
|
|
220
|
+
if (!currentRootState?.iframe?.documentNodeId) {
|
|
221
|
+
currentRootState = await waitForFreshRecommendRoots(client, {
|
|
222
|
+
timeoutMs: Math.min(10000, Math.max(2000, totalTimeoutMs - (Date.now() - started))),
|
|
223
|
+
intervalMs: 500
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
const iframeDocumentNodeId = currentRootState?.iframe?.documentNodeId || 0;
|
|
227
|
+
try {
|
|
228
|
+
const selection = await selectRecommendJob(client, iframeDocumentNodeId, {
|
|
229
|
+
jobLabel,
|
|
230
|
+
settleMs,
|
|
231
|
+
dropdownTimeoutMs
|
|
232
|
+
});
|
|
233
|
+
if (selection.selected) {
|
|
234
|
+
const stickyRootState = await waitForFreshRecommendRoots(client, {
|
|
235
|
+
timeoutMs: Math.min(10000, Math.max(2000, totalTimeoutMs - (Date.now() - started))),
|
|
236
|
+
intervalMs: 500
|
|
237
|
+
}) || currentRootState;
|
|
238
|
+
const stickyFrameNodeId = stickyRootState?.iframe?.documentNodeId || iframeDocumentNodeId;
|
|
239
|
+
const stickyVerification = await verifyRecommendJobSelection(client, stickyFrameNodeId, {
|
|
240
|
+
jobLabel,
|
|
241
|
+
delayMs: 2000,
|
|
242
|
+
dropdownTimeoutMs,
|
|
243
|
+
closeSettleMs: 300
|
|
244
|
+
});
|
|
245
|
+
selection.sticky_verification = stickyVerification;
|
|
246
|
+
currentRootState = stickyRootState || currentRootState;
|
|
247
|
+
if (!stickyVerification.verified) {
|
|
248
|
+
const stickyError = new Error(`Recommend job selection was not sticky after 2s: requested=${jobLabel}; current=${stickyVerification.current_label_without_salary || stickyVerification.current_label || "unknown"}`);
|
|
249
|
+
stickyError.sticky_verification = stickyVerification;
|
|
250
|
+
throw stickyError;
|
|
251
|
+
}
|
|
252
|
+
if (stickyVerification.menu_close && stickyVerification.menu_close.ok === false) {
|
|
253
|
+
const closeError = new Error(`Recommend job dropdown remained open after sticky verification: ${stickyVerification.menu_close.reason || "unknown"}`);
|
|
254
|
+
closeError.sticky_verification = stickyVerification;
|
|
255
|
+
throw closeError;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
attempts.push(compactJobSelectionAttempt({
|
|
259
|
+
ok: true,
|
|
260
|
+
attempt,
|
|
261
|
+
iframeDocumentNodeId,
|
|
262
|
+
selection
|
|
263
|
+
}));
|
|
264
|
+
return {
|
|
265
|
+
job_selection: {
|
|
266
|
+
...selection,
|
|
267
|
+
refresh_attempts: attempts
|
|
268
|
+
},
|
|
269
|
+
root_state: currentRootState,
|
|
270
|
+
attempts
|
|
271
|
+
};
|
|
272
|
+
} catch (error) {
|
|
273
|
+
lastError = error;
|
|
274
|
+
attempts.push(compactJobSelectionAttempt({
|
|
275
|
+
ok: false,
|
|
276
|
+
attempt,
|
|
277
|
+
iframeDocumentNodeId,
|
|
278
|
+
error
|
|
279
|
+
}));
|
|
280
|
+
if (!isRetryableRecommendJobSelectionError(error) || Date.now() - started >= totalTimeoutMs) {
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
if (retryDelayMs > 0) await sleep(retryDelayMs);
|
|
284
|
+
currentRootState = await waitForFreshRecommendRoots(client, {
|
|
285
|
+
timeoutMs: Math.min(10000, Math.max(2000, totalTimeoutMs - (Date.now() - started))),
|
|
286
|
+
intervalMs: 500
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const wrapped = new Error(lastError?.message || "Recommend job selection failed after refresh reload");
|
|
292
|
+
wrapped.cause = lastError;
|
|
293
|
+
wrapped.job_selection_attempts = attempts;
|
|
294
|
+
throw wrapped;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async function selectAndConfirmRefreshFilter(client, rootState, filterOptions, {
|
|
298
|
+
maxAttempts = 3,
|
|
299
|
+
retryDelayMs = 1500
|
|
300
|
+
} = {}) {
|
|
301
|
+
const attempts = [];
|
|
302
|
+
let currentRootState = rootState;
|
|
303
|
+
let lastError = null;
|
|
304
|
+
|
|
305
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
306
|
+
try {
|
|
307
|
+
const filter = await selectAndConfirmFirstSafeFilter(
|
|
308
|
+
client,
|
|
309
|
+
currentRootState.iframe.documentNodeId,
|
|
310
|
+
filterOptions
|
|
311
|
+
);
|
|
312
|
+
attempts.push({
|
|
313
|
+
ok: true,
|
|
314
|
+
method: "filter_reapply",
|
|
315
|
+
attempt
|
|
316
|
+
});
|
|
317
|
+
return {
|
|
318
|
+
filter,
|
|
319
|
+
root_state: currentRootState,
|
|
320
|
+
attempts
|
|
321
|
+
};
|
|
322
|
+
} catch (error) {
|
|
323
|
+
lastError = error;
|
|
324
|
+
attempts.push({
|
|
325
|
+
ok: false,
|
|
326
|
+
method: "filter_reapply",
|
|
327
|
+
reason: "filter_reapply_failed",
|
|
328
|
+
error: compactFilterReapplyError(error),
|
|
329
|
+
attempt
|
|
330
|
+
});
|
|
331
|
+
if (attempt >= maxAttempts || !isRetryableRecommendFilterReapplyError(error)) {
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
if (retryDelayMs > 0) await sleep(retryDelayMs);
|
|
335
|
+
currentRootState = await getRecommendRoots(client);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const wrapped = new Error(compactFilterReapplyError(lastError));
|
|
340
|
+
wrapped.cause = lastError;
|
|
341
|
+
wrapped.filter_reapply_attempts = attempts;
|
|
342
|
+
throw wrapped;
|
|
158
343
|
}
|
|
159
344
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
intervalMs = 500
|
|
163
|
-
} = {}) {
|
|
164
|
-
const rootState = await waitForRecommendRoots(client, {
|
|
165
|
-
timeoutMs,
|
|
166
|
-
intervalMs
|
|
167
|
-
});
|
|
168
|
-
return rootState?.iframe?.documentNodeId ? rootState : null;
|
|
345
|
+
function compactCurrentCityOnlyReapplyError(error) {
|
|
346
|
+
return error?.message || String(error || "Recommend current-city-only reapply failed");
|
|
169
347
|
}
|
|
170
348
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
retryDelayMs = 1000
|
|
177
|
-
} = {}) {
|
|
178
|
-
const started = Date.now();
|
|
179
|
-
const attempts = [];
|
|
180
|
-
let currentRootState = rootState || null;
|
|
181
|
-
let lastError = null;
|
|
182
|
-
let attempt = 0;
|
|
183
|
-
|
|
184
|
-
while (Date.now() - started <= totalTimeoutMs) {
|
|
185
|
-
attempt += 1;
|
|
186
|
-
if (!currentRootState?.iframe?.documentNodeId) {
|
|
187
|
-
currentRootState = await waitForFreshRecommendRoots(client, {
|
|
188
|
-
timeoutMs: Math.min(10000, Math.max(2000, totalTimeoutMs - (Date.now() - started))),
|
|
189
|
-
intervalMs: 500
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
const iframeDocumentNodeId = currentRootState?.iframe?.documentNodeId || 0;
|
|
193
|
-
try {
|
|
194
|
-
const selection = await selectRecommendJob(client, iframeDocumentNodeId, {
|
|
195
|
-
jobLabel,
|
|
196
|
-
settleMs,
|
|
197
|
-
dropdownTimeoutMs
|
|
198
|
-
});
|
|
199
|
-
if (selection.selected) {
|
|
200
|
-
const stickyRootState = await waitForFreshRecommendRoots(client, {
|
|
201
|
-
timeoutMs: Math.min(10000, Math.max(2000, totalTimeoutMs - (Date.now() - started))),
|
|
202
|
-
intervalMs: 500
|
|
203
|
-
}) || currentRootState;
|
|
204
|
-
const stickyFrameNodeId = stickyRootState?.iframe?.documentNodeId || iframeDocumentNodeId;
|
|
205
|
-
const stickyVerification = await verifyRecommendJobSelection(client, stickyFrameNodeId, {
|
|
206
|
-
jobLabel,
|
|
207
|
-
delayMs: 2000,
|
|
208
|
-
dropdownTimeoutMs,
|
|
209
|
-
closeSettleMs: 300
|
|
210
|
-
});
|
|
211
|
-
selection.sticky_verification = stickyVerification;
|
|
212
|
-
currentRootState = stickyRootState || currentRootState;
|
|
213
|
-
if (!stickyVerification.verified) {
|
|
214
|
-
const stickyError = new Error(`Recommend job selection was not sticky after 2s: requested=${jobLabel}; current=${stickyVerification.current_label_without_salary || stickyVerification.current_label || "unknown"}`);
|
|
215
|
-
stickyError.sticky_verification = stickyVerification;
|
|
216
|
-
throw stickyError;
|
|
217
|
-
}
|
|
218
|
-
if (stickyVerification.menu_close && stickyVerification.menu_close.ok === false) {
|
|
219
|
-
const closeError = new Error(`Recommend job dropdown remained open after sticky verification: ${stickyVerification.menu_close.reason || "unknown"}`);
|
|
220
|
-
closeError.sticky_verification = stickyVerification;
|
|
221
|
-
throw closeError;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
attempts.push(compactJobSelectionAttempt({
|
|
225
|
-
ok: true,
|
|
226
|
-
attempt,
|
|
227
|
-
iframeDocumentNodeId,
|
|
228
|
-
selection
|
|
229
|
-
}));
|
|
230
|
-
return {
|
|
231
|
-
job_selection: {
|
|
232
|
-
...selection,
|
|
233
|
-
refresh_attempts: attempts
|
|
234
|
-
},
|
|
235
|
-
root_state: currentRootState,
|
|
236
|
-
attempts
|
|
237
|
-
};
|
|
238
|
-
} catch (error) {
|
|
239
|
-
lastError = error;
|
|
240
|
-
attempts.push(compactJobSelectionAttempt({
|
|
241
|
-
ok: false,
|
|
242
|
-
attempt,
|
|
243
|
-
iframeDocumentNodeId,
|
|
244
|
-
error
|
|
245
|
-
}));
|
|
246
|
-
if (!isRetryableRecommendJobSelectionError(error) || Date.now() - started >= totalTimeoutMs) {
|
|
247
|
-
break;
|
|
248
|
-
}
|
|
249
|
-
if (retryDelayMs > 0) await sleep(retryDelayMs);
|
|
250
|
-
currentRootState = await waitForFreshRecommendRoots(client, {
|
|
251
|
-
timeoutMs: Math.min(10000, Math.max(2000, totalTimeoutMs - (Date.now() - started))),
|
|
252
|
-
intervalMs: 500
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
const wrapped = new Error(lastError?.message || "Recommend job selection failed after refresh reload");
|
|
258
|
-
wrapped.cause = lastError;
|
|
259
|
-
wrapped.job_selection_attempts = attempts;
|
|
260
|
-
throw wrapped;
|
|
349
|
+
function isRetryableRecommendCurrentCityOnlyReapplyError(error) {
|
|
350
|
+
if (isStaleRecommendNodeError(error)) return true;
|
|
351
|
+
const message = compactCurrentCityOnlyReapplyError(error);
|
|
352
|
+
return /current-city|current city|location|popover|trigger|checkbox|stale|did not (?:open|mount|close)/i.test(message)
|
|
353
|
+
&& !/unavailable/i.test(message);
|
|
261
354
|
}
|
|
262
355
|
|
|
263
|
-
async function
|
|
356
|
+
async function ensureRefreshCurrentCityOnly(client, rootState, {
|
|
357
|
+
enabled = false,
|
|
264
358
|
maxAttempts = 3,
|
|
265
359
|
retryDelayMs = 1500
|
|
266
360
|
} = {}) {
|
|
@@ -270,18 +364,19 @@ async function selectAndConfirmRefreshFilter(client, rootState, filterOptions, {
|
|
|
270
364
|
|
|
271
365
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
272
366
|
try {
|
|
273
|
-
const
|
|
367
|
+
const result = await ensureRecommendCurrentCityOnly(
|
|
274
368
|
client,
|
|
275
369
|
currentRootState.iframe.documentNodeId,
|
|
276
|
-
|
|
370
|
+
{ enabled }
|
|
277
371
|
);
|
|
278
372
|
attempts.push({
|
|
279
373
|
ok: true,
|
|
280
|
-
method: "
|
|
281
|
-
attempt
|
|
374
|
+
method: "current_city_only_reapply",
|
|
375
|
+
attempt,
|
|
376
|
+
result
|
|
282
377
|
});
|
|
283
378
|
return {
|
|
284
|
-
|
|
379
|
+
current_city_only: result,
|
|
285
380
|
root_state: currentRootState,
|
|
286
381
|
attempts
|
|
287
382
|
};
|
|
@@ -289,12 +384,12 @@ async function selectAndConfirmRefreshFilter(client, rootState, filterOptions, {
|
|
|
289
384
|
lastError = error;
|
|
290
385
|
attempts.push({
|
|
291
386
|
ok: false,
|
|
292
|
-
method: "
|
|
293
|
-
reason: "
|
|
294
|
-
error:
|
|
387
|
+
method: "current_city_only_reapply",
|
|
388
|
+
reason: "current_city_only_reapply_failed",
|
|
389
|
+
error: compactCurrentCityOnlyReapplyError(error),
|
|
295
390
|
attempt
|
|
296
391
|
});
|
|
297
|
-
if (attempt >= maxAttempts || !
|
|
392
|
+
if (attempt >= maxAttempts || !isRetryableRecommendCurrentCityOnlyReapplyError(error)) {
|
|
298
393
|
break;
|
|
299
394
|
}
|
|
300
395
|
if (retryDelayMs > 0) await sleep(retryDelayMs);
|
|
@@ -302,9 +397,9 @@ async function selectAndConfirmRefreshFilter(client, rootState, filterOptions, {
|
|
|
302
397
|
}
|
|
303
398
|
}
|
|
304
399
|
|
|
305
|
-
const wrapped = new Error(
|
|
400
|
+
const wrapped = new Error(compactCurrentCityOnlyReapplyError(lastError));
|
|
306
401
|
wrapped.cause = lastError;
|
|
307
|
-
wrapped.
|
|
402
|
+
wrapped.current_city_only_attempts = attempts;
|
|
308
403
|
throw wrapped;
|
|
309
404
|
}
|
|
310
405
|
|
|
@@ -313,174 +408,214 @@ async function applyRefreshMethod(client, method, {
|
|
|
313
408
|
pageScope = "recommend",
|
|
314
409
|
fallbackPageScope = "recommend",
|
|
315
410
|
filter = {},
|
|
316
|
-
targetUrl = RECOMMEND_TARGET_URL,
|
|
317
|
-
forceRecentNotView = true,
|
|
318
|
-
cardTimeoutMs = 30000,
|
|
319
|
-
reloadSettleMs = 8000
|
|
320
|
-
} = {}) {
|
|
411
|
+
targetUrl = RECOMMEND_TARGET_URL,
|
|
412
|
+
forceRecentNotView = true,
|
|
413
|
+
cardTimeoutMs = 30000,
|
|
414
|
+
reloadSettleMs = 8000
|
|
415
|
+
} = {}) {
|
|
321
416
|
const started = Date.now();
|
|
322
417
|
let currentRootState = null;
|
|
323
418
|
let jobSelection = null;
|
|
324
419
|
let jobSelectionAttempts = [];
|
|
325
420
|
let pageScopeResult = null;
|
|
421
|
+
let currentCityOnlyResult = null;
|
|
422
|
+
let currentCityOnlyAttempts = [];
|
|
326
423
|
let filterResult = null;
|
|
327
424
|
let filterReapplyAttempts = [];
|
|
328
425
|
let recoverySettle = null;
|
|
329
426
|
try {
|
|
330
427
|
if (method === "page_navigate") {
|
|
331
|
-
await client.Page.navigate({ url: targetUrl || RECOMMEND_TARGET_URL });
|
|
332
|
-
} else {
|
|
333
|
-
await client.Page.reload({ ignoreCache: true });
|
|
334
|
-
}
|
|
335
|
-
recoverySettle = await waitForRecommendRecoverySettle(client, {
|
|
336
|
-
reloadSettleMs,
|
|
337
|
-
timeoutMs: Math.max(45000, reloadSettleMs * 6)
|
|
338
|
-
});
|
|
339
|
-
if (!recoverySettle.ok) {
|
|
340
|
-
throw createRecoverySettleError("recommend", recoverySettle);
|
|
341
|
-
}
|
|
342
|
-
currentRootState = await waitForRecommendRoots(client, {
|
|
343
|
-
timeoutMs: Math.max(45000, reloadSettleMs * 6),
|
|
344
|
-
intervalMs: 500
|
|
345
|
-
});
|
|
346
|
-
if (!currentRootState?.iframe?.documentNodeId) {
|
|
347
|
-
throw new Error("Recommend iframe was not ready after refresh reload");
|
|
348
|
-
}
|
|
349
|
-
if (jobLabel) {
|
|
350
|
-
const jobSelectionResult = await selectRecommendJobWithRootRefresh(client, currentRootState, {
|
|
351
|
-
jobLabel,
|
|
352
|
-
settleMs: reloadSettleMs > 10000 ? 12000 : 6000,
|
|
353
|
-
dropdownTimeoutMs: 4000,
|
|
354
|
-
totalTimeoutMs: reloadSettleMs > 10000 ? 45000 : 30000,
|
|
355
|
-
retryDelayMs: 1200
|
|
356
|
-
});
|
|
357
|
-
jobSelection = jobSelectionResult.job_selection;
|
|
358
|
-
jobSelectionAttempts = jobSelectionResult.attempts;
|
|
359
|
-
if (!jobSelection.selected) {
|
|
360
|
-
throw new Error(`Requested recommend job was not selected after refresh reload: ${jobSelection.reason}`);
|
|
361
|
-
}
|
|
362
|
-
currentRootState = jobSelectionResult.root_state || await getRecommendRoots(client);
|
|
363
|
-
}
|
|
364
|
-
pageScopeResult = await selectRecommendPageScope(
|
|
365
|
-
client,
|
|
366
|
-
currentRootState.iframe.documentNodeId,
|
|
367
|
-
{
|
|
368
|
-
pageScope,
|
|
369
|
-
fallbackScope: fallbackPageScope,
|
|
370
|
-
settleMs: reloadSettleMs > 10000 ? 3000 : 1200,
|
|
371
|
-
timeoutMs: Math.max(10000, Math.min(cardTimeoutMs, 60000))
|
|
372
|
-
}
|
|
428
|
+
await client.Page.navigate({ url: targetUrl || RECOMMEND_TARGET_URL });
|
|
429
|
+
} else {
|
|
430
|
+
await client.Page.reload({ ignoreCache: true });
|
|
431
|
+
}
|
|
432
|
+
recoverySettle = await waitForRecommendRecoverySettle(client, {
|
|
433
|
+
reloadSettleMs,
|
|
434
|
+
timeoutMs: Math.max(45000, reloadSettleMs * 6)
|
|
435
|
+
});
|
|
436
|
+
if (!recoverySettle.ok) {
|
|
437
|
+
throw createRecoverySettleError("recommend", recoverySettle);
|
|
438
|
+
}
|
|
439
|
+
currentRootState = await waitForRecommendRoots(client, {
|
|
440
|
+
timeoutMs: Math.max(45000, reloadSettleMs * 6),
|
|
441
|
+
intervalMs: 500
|
|
442
|
+
});
|
|
443
|
+
if (!currentRootState?.iframe?.documentNodeId) {
|
|
444
|
+
throw new Error("Recommend iframe was not ready after refresh reload");
|
|
445
|
+
}
|
|
446
|
+
if (jobLabel) {
|
|
447
|
+
const jobSelectionResult = await selectRecommendJobWithRootRefresh(client, currentRootState, {
|
|
448
|
+
jobLabel,
|
|
449
|
+
settleMs: reloadSettleMs > 10000 ? 12000 : 6000,
|
|
450
|
+
dropdownTimeoutMs: 4000,
|
|
451
|
+
totalTimeoutMs: reloadSettleMs > 10000 ? 45000 : 30000,
|
|
452
|
+
retryDelayMs: 1200
|
|
453
|
+
});
|
|
454
|
+
jobSelection = jobSelectionResult.job_selection;
|
|
455
|
+
jobSelectionAttempts = jobSelectionResult.attempts;
|
|
456
|
+
if (!jobSelection.selected) {
|
|
457
|
+
throw new Error(`Requested recommend job was not selected after refresh reload: ${jobSelection.reason}`);
|
|
458
|
+
}
|
|
459
|
+
currentRootState = jobSelectionResult.root_state || await getRecommendRoots(client);
|
|
460
|
+
}
|
|
461
|
+
pageScopeResult = await selectRecommendPageScope(
|
|
462
|
+
client,
|
|
463
|
+
currentRootState.iframe.documentNodeId,
|
|
464
|
+
{
|
|
465
|
+
pageScope,
|
|
466
|
+
fallbackScope: fallbackPageScope,
|
|
467
|
+
settleMs: reloadSettleMs > 10000 ? 3000 : 1200,
|
|
468
|
+
timeoutMs: Math.max(10000, Math.min(cardTimeoutMs, 60000))
|
|
469
|
+
}
|
|
373
470
|
);
|
|
374
471
|
if (!pageScopeResult.selected) {
|
|
375
472
|
throw new Error(`Recommend page scope was not selected after refresh reload: ${pageScopeResult.reason || pageScope}`);
|
|
376
473
|
}
|
|
377
474
|
currentRootState = await getRecommendRoots(client);
|
|
378
|
-
const
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
475
|
+
const retryDelayMs = Math.max(1200, Math.min(5000, Math.floor((reloadSettleMs || 8000) / 2)));
|
|
476
|
+
const filterStages = await applyRecommendFilterEnvelopeStages(filter, {
|
|
477
|
+
applyCurrentCityOnly: async () => {
|
|
478
|
+
const selection = await ensureRefreshCurrentCityOnly(client, currentRootState, {
|
|
479
|
+
enabled: filter.currentCityOnly === true || filter.current_city_only === true,
|
|
480
|
+
retryDelayMs
|
|
481
|
+
});
|
|
482
|
+
currentCityOnlyAttempts = selection.attempts;
|
|
483
|
+
currentRootState = await getRecommendRoots(client);
|
|
484
|
+
return selection.current_city_only;
|
|
485
|
+
},
|
|
486
|
+
applyFilterPanel: async () => {
|
|
487
|
+
const selection = await selectAndConfirmRefreshFilter(
|
|
488
|
+
client,
|
|
489
|
+
currentRootState,
|
|
490
|
+
buildRecommendFilterSelectionOptions(filter, { forceRecentNotView }),
|
|
491
|
+
{ retryDelayMs }
|
|
492
|
+
);
|
|
493
|
+
filterReapplyAttempts = selection.attempts;
|
|
494
|
+
currentRootState = await getRecommendRoots(client);
|
|
495
|
+
return selection.filter;
|
|
384
496
|
}
|
|
385
|
-
);
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
currentRootState = await getRecommendRoots(client);
|
|
497
|
+
});
|
|
498
|
+
currentCityOnlyResult = filterStages.current_city_only;
|
|
499
|
+
filterResult = filterStages.filter;
|
|
389
500
|
const cardNodeIds = await waitForRecommendCardNodeIds(client, currentRootState.iframe.documentNodeId, {
|
|
390
501
|
timeoutMs: cardTimeoutMs,
|
|
391
502
|
intervalMs: 500
|
|
392
503
|
});
|
|
393
504
|
if (!cardNodeIds.length) {
|
|
394
|
-
throw new Error("No recommend candidate cards were found after refresh reload");
|
|
395
|
-
}
|
|
396
|
-
return {
|
|
397
|
-
ok: true,
|
|
398
|
-
method,
|
|
505
|
+
throw new Error("No recommend candidate cards were found after refresh reload");
|
|
506
|
+
}
|
|
507
|
+
return {
|
|
508
|
+
ok: true,
|
|
509
|
+
method,
|
|
399
510
|
target_url: method === "page_navigate" ? (targetUrl || RECOMMEND_TARGET_URL) : null,
|
|
400
511
|
job_selection: jobSelection,
|
|
401
512
|
job_selection_attempts: jobSelectionAttempts,
|
|
402
513
|
recovery_settle: recoverySettle,
|
|
403
514
|
page_scope: pageScopeResult,
|
|
515
|
+
current_city_only: currentCityOnlyResult,
|
|
516
|
+
current_city_only_attempts: currentCityOnlyAttempts,
|
|
404
517
|
filter: filterResult,
|
|
405
518
|
filter_reapply_attempts: filterReapplyAttempts,
|
|
406
519
|
card_count: cardNodeIds.length,
|
|
407
520
|
root_state: currentRootState,
|
|
408
|
-
forced_recent_not_view: forceRecentNotView,
|
|
521
|
+
forced_recent_not_view: Boolean(forceRecentNotView && filter.enabled !== false),
|
|
409
522
|
elapsed_ms: Date.now() - started
|
|
410
523
|
};
|
|
411
524
|
} catch (error) {
|
|
412
525
|
return {
|
|
413
526
|
ok: false,
|
|
414
|
-
method,
|
|
415
|
-
reason: refreshFailureReason(method),
|
|
416
|
-
error: error?.message || String(error),
|
|
527
|
+
method,
|
|
528
|
+
reason: refreshFailureReason(method),
|
|
529
|
+
error: error?.message || String(error),
|
|
417
530
|
target_url: method === "page_navigate" ? (targetUrl || RECOMMEND_TARGET_URL) : null,
|
|
418
531
|
job_selection: jobSelection,
|
|
419
532
|
job_selection_attempts: error?.job_selection_attempts || jobSelectionAttempts,
|
|
420
533
|
recovery_settle: error?.recovery_settle || recoverySettle,
|
|
421
534
|
page_scope: pageScopeResult,
|
|
535
|
+
current_city_only: currentCityOnlyResult,
|
|
536
|
+
current_city_only_attempts: error?.current_city_only_attempts || currentCityOnlyAttempts,
|
|
422
537
|
filter: filterResult,
|
|
423
538
|
filter_reapply_attempts: error?.filter_reapply_attempts || filterReapplyAttempts,
|
|
424
539
|
card_count: 0,
|
|
425
540
|
root_state: currentRootState,
|
|
426
|
-
forced_recent_not_view: forceRecentNotView,
|
|
541
|
+
forced_recent_not_view: Boolean(forceRecentNotView && filter.enabled !== false),
|
|
427
542
|
elapsed_ms: Date.now() - started
|
|
428
543
|
};
|
|
429
544
|
}
|
|
430
545
|
}
|
|
431
546
|
|
|
432
|
-
export async function refreshRecommendListAtEnd(client, {
|
|
433
|
-
rootState = null,
|
|
434
|
-
jobLabel = "",
|
|
435
|
-
pageScope = "recommend",
|
|
436
|
-
fallbackPageScope = "recommend",
|
|
437
|
-
filter = {},
|
|
438
|
-
preferEndRefreshButton = true,
|
|
439
|
-
forceNavigate = false,
|
|
440
|
-
targetUrl = RECOMMEND_TARGET_URL,
|
|
441
|
-
forceRecentNotView = true,
|
|
442
|
-
cardTimeoutMs = 30000,
|
|
443
|
-
buttonSettleMs = 8000,
|
|
444
|
-
reloadSettleMs = 8000
|
|
445
|
-
} = {}) {
|
|
446
|
-
const attempts = [];
|
|
447
|
-
let currentRootState = rootState || null;
|
|
448
|
-
|
|
449
|
-
if (preferEndRefreshButton) {
|
|
450
|
-
currentRootState = currentRootState || await getRecommendRoots(client);
|
|
451
|
-
const buttonResult = await clickRecommendEndRefreshButton(
|
|
452
|
-
client,
|
|
547
|
+
export async function refreshRecommendListAtEnd(client, {
|
|
548
|
+
rootState = null,
|
|
549
|
+
jobLabel = "",
|
|
550
|
+
pageScope = "recommend",
|
|
551
|
+
fallbackPageScope = "recommend",
|
|
552
|
+
filter = {},
|
|
553
|
+
preferEndRefreshButton = true,
|
|
554
|
+
forceNavigate = false,
|
|
555
|
+
targetUrl = RECOMMEND_TARGET_URL,
|
|
556
|
+
forceRecentNotView = true,
|
|
557
|
+
cardTimeoutMs = 30000,
|
|
558
|
+
buttonSettleMs = 8000,
|
|
559
|
+
reloadSettleMs = 8000
|
|
560
|
+
} = {}) {
|
|
561
|
+
const attempts = [];
|
|
562
|
+
let currentRootState = rootState || null;
|
|
563
|
+
|
|
564
|
+
if (preferEndRefreshButton) {
|
|
565
|
+
currentRootState = currentRootState || await getRecommendRoots(client);
|
|
566
|
+
const buttonResult = await clickRecommendEndRefreshButton(
|
|
567
|
+
client,
|
|
453
568
|
currentRootState.iframe.documentNodeId,
|
|
454
569
|
{ settleMs: buttonSettleMs }
|
|
455
570
|
);
|
|
456
571
|
attempts.push(buttonResult);
|
|
457
572
|
if (buttonResult.ok) {
|
|
573
|
+
let pageScopeResult = null;
|
|
574
|
+
let currentCityOnlyResult = null;
|
|
575
|
+
let currentCityOnlyAttempts = [];
|
|
576
|
+
let filterResult = null;
|
|
577
|
+
let filterReapplyAttempts = [];
|
|
458
578
|
try {
|
|
459
579
|
currentRootState = await getRecommendRoots(client);
|
|
460
|
-
|
|
580
|
+
pageScopeResult = await selectRecommendPageScope(
|
|
461
581
|
client,
|
|
462
582
|
currentRootState.iframe.documentNodeId,
|
|
463
583
|
{
|
|
464
584
|
pageScope,
|
|
465
585
|
fallbackScope: fallbackPageScope,
|
|
466
|
-
settleMs: buttonSettleMs > 10000 ? 3000 : 1200,
|
|
467
|
-
timeoutMs: Math.max(10000, Math.min(cardTimeoutMs, 60000))
|
|
468
|
-
}
|
|
586
|
+
settleMs: buttonSettleMs > 10000 ? 3000 : 1200,
|
|
587
|
+
timeoutMs: Math.max(10000, Math.min(cardTimeoutMs, 60000))
|
|
588
|
+
}
|
|
469
589
|
);
|
|
470
590
|
if (!pageScopeResult.selected) {
|
|
471
591
|
throw new Error(`Recommend page scope was not selected after end refresh: ${pageScopeResult.reason || pageScope}`);
|
|
472
592
|
}
|
|
473
593
|
currentRootState = await getRecommendRoots(client);
|
|
474
|
-
const
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
594
|
+
const retryDelayMs = Math.max(1200, Math.min(5000, Math.floor((buttonSettleMs || 8000) / 2)));
|
|
595
|
+
const filterStages = await applyRecommendFilterEnvelopeStages(filter, {
|
|
596
|
+
applyCurrentCityOnly: async () => {
|
|
597
|
+
const selection = await ensureRefreshCurrentCityOnly(client, currentRootState, {
|
|
598
|
+
enabled: filter.currentCityOnly === true || filter.current_city_only === true,
|
|
599
|
+
retryDelayMs
|
|
600
|
+
});
|
|
601
|
+
currentCityOnlyAttempts = selection.attempts;
|
|
602
|
+
currentRootState = await getRecommendRoots(client);
|
|
603
|
+
return selection.current_city_only;
|
|
604
|
+
},
|
|
605
|
+
applyFilterPanel: async () => {
|
|
606
|
+
const selection = await selectAndConfirmRefreshFilter(
|
|
607
|
+
client,
|
|
608
|
+
currentRootState,
|
|
609
|
+
buildRecommendFilterSelectionOptions(filter, { forceRecentNotView }),
|
|
610
|
+
{ retryDelayMs }
|
|
611
|
+
);
|
|
612
|
+
filterReapplyAttempts = selection.attempts;
|
|
613
|
+
currentRootState = await getRecommendRoots(client);
|
|
614
|
+
return selection.filter;
|
|
480
615
|
}
|
|
481
|
-
);
|
|
482
|
-
|
|
483
|
-
|
|
616
|
+
});
|
|
617
|
+
currentCityOnlyResult = filterStages.current_city_only;
|
|
618
|
+
filterResult = filterStages.filter;
|
|
484
619
|
const cardNodeIds = await waitForRecommendCardNodeIds(client, currentRootState.iframe.documentNodeId, {
|
|
485
620
|
timeoutMs: cardTimeoutMs,
|
|
486
621
|
intervalMs: 500
|
|
@@ -490,65 +625,73 @@ export async function refreshRecommendListAtEnd(client, {
|
|
|
490
625
|
method: "end_refresh_button",
|
|
491
626
|
attempts,
|
|
492
627
|
page_scope: pageScopeResult,
|
|
628
|
+
current_city_only: currentCityOnlyResult,
|
|
629
|
+
current_city_only_attempts: currentCityOnlyAttempts,
|
|
493
630
|
filter: filterResult,
|
|
494
|
-
filter_reapply_attempts:
|
|
631
|
+
filter_reapply_attempts: filterReapplyAttempts,
|
|
495
632
|
card_count: cardNodeIds.length,
|
|
496
633
|
root_state: currentRootState,
|
|
497
|
-
forced_recent_not_view: forceRecentNotView
|
|
634
|
+
forced_recent_not_view: Boolean(forceRecentNotView && filter.enabled !== false)
|
|
498
635
|
};
|
|
499
636
|
} catch (error) {
|
|
500
637
|
attempts.push({
|
|
501
638
|
ok: false,
|
|
502
639
|
method: "end_refresh_button_after_click",
|
|
503
640
|
reason: "end_refresh_reapply_failed",
|
|
504
|
-
error: error?.message || String(error)
|
|
641
|
+
error: error?.message || String(error),
|
|
642
|
+
page_scope: pageScopeResult,
|
|
643
|
+
current_city_only: currentCityOnlyResult,
|
|
644
|
+
current_city_only_attempts: error?.current_city_only_attempts || currentCityOnlyAttempts,
|
|
645
|
+
filter: filterResult,
|
|
646
|
+
filter_reapply_attempts: error?.filter_reapply_attempts || filterReapplyAttempts,
|
|
647
|
+
forced_recent_not_view: Boolean(forceRecentNotView && filter.enabled !== false)
|
|
505
648
|
});
|
|
506
649
|
}
|
|
507
650
|
}
|
|
508
651
|
}
|
|
509
652
|
|
|
510
|
-
const fallbackMethods = [];
|
|
511
|
-
if (forceNavigate && typeof client?.Page?.navigate === "function") {
|
|
512
|
-
fallbackMethods.push("page_navigate");
|
|
513
|
-
}
|
|
514
|
-
if (typeof client?.Page?.reload === "function") {
|
|
515
|
-
fallbackMethods.push("page_reload");
|
|
516
|
-
}
|
|
517
|
-
if (!fallbackMethods.length) {
|
|
518
|
-
fallbackMethods.push("page_reload");
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
let lastRefreshResult = null;
|
|
522
|
-
for (const method of fallbackMethods) {
|
|
523
|
-
const refreshResult = await applyRefreshMethod(client, method, {
|
|
524
|
-
jobLabel,
|
|
525
|
-
pageScope,
|
|
526
|
-
fallbackPageScope,
|
|
527
|
-
filter,
|
|
528
|
-
targetUrl,
|
|
529
|
-
forceRecentNotView,
|
|
530
|
-
cardTimeoutMs,
|
|
531
|
-
reloadSettleMs
|
|
532
|
-
});
|
|
533
|
-
if (refreshResult.ok) {
|
|
534
|
-
return {
|
|
535
|
-
...refreshResult,
|
|
536
|
-
attempts
|
|
537
|
-
};
|
|
538
|
-
}
|
|
539
|
-
attempts.push(refreshResult);
|
|
540
|
-
lastRefreshResult = refreshResult;
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
return {
|
|
544
|
-
...(lastRefreshResult || {
|
|
545
|
-
ok: false,
|
|
653
|
+
const fallbackMethods = [];
|
|
654
|
+
if (forceNavigate && typeof client?.Page?.navigate === "function") {
|
|
655
|
+
fallbackMethods.push("page_navigate");
|
|
656
|
+
}
|
|
657
|
+
if (typeof client?.Page?.reload === "function") {
|
|
658
|
+
fallbackMethods.push("page_reload");
|
|
659
|
+
}
|
|
660
|
+
if (!fallbackMethods.length) {
|
|
661
|
+
fallbackMethods.push("page_reload");
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
let lastRefreshResult = null;
|
|
665
|
+
for (const method of fallbackMethods) {
|
|
666
|
+
const refreshResult = await applyRefreshMethod(client, method, {
|
|
667
|
+
jobLabel,
|
|
668
|
+
pageScope,
|
|
669
|
+
fallbackPageScope,
|
|
670
|
+
filter,
|
|
671
|
+
targetUrl,
|
|
672
|
+
forceRecentNotView,
|
|
673
|
+
cardTimeoutMs,
|
|
674
|
+
reloadSettleMs
|
|
675
|
+
});
|
|
676
|
+
if (refreshResult.ok) {
|
|
677
|
+
return {
|
|
678
|
+
...refreshResult,
|
|
679
|
+
attempts
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
attempts.push(refreshResult);
|
|
683
|
+
lastRefreshResult = refreshResult;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
return {
|
|
687
|
+
...(lastRefreshResult || {
|
|
688
|
+
ok: false,
|
|
546
689
|
method: fallbackMethods[fallbackMethods.length - 1] || "page_reload",
|
|
547
690
|
reason: "refresh_failed",
|
|
548
691
|
error: "Recommend refresh did not run",
|
|
549
692
|
card_count: 0,
|
|
550
693
|
root_state: currentRootState,
|
|
551
|
-
forced_recent_not_view: forceRecentNotView
|
|
694
|
+
forced_recent_not_view: Boolean(forceRecentNotView && filter.enabled !== false)
|
|
552
695
|
}),
|
|
553
696
|
attempts
|
|
554
697
|
};
|