@reconcrap/boss-recommend-mcp 2.0.25 → 2.0.26
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/package.json +1 -1
- package/src/core/infinite-list/index.js +408 -8
- package/src/domains/chat/constants.js +15 -0
- package/src/domains/chat/run-service.js +13 -2
- package/src/domains/recommend/constants.js +13 -0
- package/src/domains/recommend/run-service.js +14 -3
- package/src/domains/recruit/constants.js +14 -0
- package/src/domains/recruit/run-service.js +14 -3
package/package.json
CHANGED
|
@@ -30,6 +30,7 @@ export const DEFAULT_LOAD_MORE_HINT_KEYWORDS = Object.freeze([
|
|
|
30
30
|
|
|
31
31
|
export const DEFAULT_BOTTOM_MARKER_SELECTORS = Object.freeze([
|
|
32
32
|
".finished-wrap",
|
|
33
|
+
".loadmore",
|
|
33
34
|
".load-tips",
|
|
34
35
|
"div[role=\"tfoot\"] .load-tips",
|
|
35
36
|
".no-data-refresh",
|
|
@@ -38,6 +39,7 @@ export const DEFAULT_BOTTOM_MARKER_SELECTORS = Object.freeze([
|
|
|
38
39
|
".no-data",
|
|
39
40
|
".tip-nodata",
|
|
40
41
|
"[class*=\"finished\"]",
|
|
42
|
+
"[class*=\"loadmore\"]",
|
|
41
43
|
"[class*=\"load-tips\"]",
|
|
42
44
|
"[class*=\"no-more\"]",
|
|
43
45
|
"[class*=\"no_more\"]"
|
|
@@ -96,6 +98,345 @@ function isUsableBox(box) {
|
|
|
96
98
|
return Number(box?.rect?.width || 0) > 2 && Number(box?.rect?.height || 0) > 2;
|
|
97
99
|
}
|
|
98
100
|
|
|
101
|
+
function isUsableRect(rect) {
|
|
102
|
+
return Number(rect?.width || 0) > 2 && Number(rect?.height || 0) > 2;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function pointFromRect(rect, {
|
|
106
|
+
xRatio = 0.5,
|
|
107
|
+
yRatio = 0.75,
|
|
108
|
+
inset = 8
|
|
109
|
+
} = {}) {
|
|
110
|
+
if (!isUsableRect(rect)) return null;
|
|
111
|
+
const safeInsetX = Math.min(Math.max(0, Number(inset) || 0), Math.max(0, rect.width / 2 - 1));
|
|
112
|
+
const safeInsetY = Math.min(Math.max(0, Number(inset) || 0), Math.max(0, rect.height / 2 - 1));
|
|
113
|
+
const minX = rect.x + safeInsetX;
|
|
114
|
+
const maxX = rect.x + rect.width - safeInsetX;
|
|
115
|
+
const minY = rect.y + safeInsetY;
|
|
116
|
+
const maxY = rect.y + rect.height - safeInsetY;
|
|
117
|
+
return {
|
|
118
|
+
x: Math.min(maxX, Math.max(minX, rect.x + rect.width * (Number(xRatio) || 0.5))),
|
|
119
|
+
y: Math.min(maxY, Math.max(minY, rect.y + rect.height * (Number(yRatio) || 0.75)))
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function unionRects(rects = []) {
|
|
124
|
+
const usable = rects.filter(isUsableRect);
|
|
125
|
+
if (!usable.length) return null;
|
|
126
|
+
const left = Math.min(...usable.map((rect) => rect.x));
|
|
127
|
+
const top = Math.min(...usable.map((rect) => rect.y));
|
|
128
|
+
const right = Math.max(...usable.map((rect) => rect.x + rect.width));
|
|
129
|
+
const bottom = Math.max(...usable.map((rect) => rect.y + rect.height));
|
|
130
|
+
return {
|
|
131
|
+
x: left,
|
|
132
|
+
y: top,
|
|
133
|
+
width: right - left,
|
|
134
|
+
height: bottom - top
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function pointInsideRect(point, rect, {
|
|
139
|
+
padding = 0
|
|
140
|
+
} = {}) {
|
|
141
|
+
if (!point || !isUsableRect(rect)) return false;
|
|
142
|
+
const pad = Math.max(0, Number(padding) || 0);
|
|
143
|
+
return Number(point.x) >= rect.x + pad
|
|
144
|
+
&& Number(point.x) <= rect.x + rect.width - pad
|
|
145
|
+
&& Number(point.y) >= rect.y + pad
|
|
146
|
+
&& Number(point.y) <= rect.y + rect.height - pad;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function rectsIntersect(a, b, {
|
|
150
|
+
padding = 0
|
|
151
|
+
} = {}) {
|
|
152
|
+
if (!isUsableRect(a) || !isUsableRect(b)) return false;
|
|
153
|
+
const pad = Math.max(0, Number(padding) || 0);
|
|
154
|
+
return a.x + a.width >= b.x + pad
|
|
155
|
+
&& b.x + b.width >= a.x + pad
|
|
156
|
+
&& a.y + a.height >= b.y + pad
|
|
157
|
+
&& b.y + b.height >= a.y + pad;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function intersectRects(a, b) {
|
|
161
|
+
if (!isUsableRect(a) || !isUsableRect(b)) return null;
|
|
162
|
+
const left = Math.max(a.x, b.x);
|
|
163
|
+
const top = Math.max(a.y, b.y);
|
|
164
|
+
const right = Math.min(a.x + a.width, b.x + b.width);
|
|
165
|
+
const bottom = Math.min(a.y + a.height, b.y + b.height);
|
|
166
|
+
if (right - left <= 2 || bottom - top <= 2) return null;
|
|
167
|
+
return {
|
|
168
|
+
x: left,
|
|
169
|
+
y: top,
|
|
170
|
+
width: right - left,
|
|
171
|
+
height: bottom - top
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function normalizePoint(point) {
|
|
176
|
+
if (!point) return null;
|
|
177
|
+
const x = Number(point.x);
|
|
178
|
+
const y = Number(point.y);
|
|
179
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) return null;
|
|
180
|
+
return { x, y };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function resolveViewportPoint(viewportPoint, viewport) {
|
|
184
|
+
if (!viewportPoint) return null;
|
|
185
|
+
if (viewport && ("xRatio" in viewportPoint || "yRatio" in viewportPoint)) {
|
|
186
|
+
const xRatio = Number(viewportPoint.xRatio ?? 0);
|
|
187
|
+
const yRatio = Number(viewportPoint.yRatio ?? 0);
|
|
188
|
+
if (Number.isFinite(xRatio) && Number.isFinite(yRatio)) {
|
|
189
|
+
return {
|
|
190
|
+
x: viewport.x + viewport.width * xRatio,
|
|
191
|
+
y: viewport.y + viewport.height * yRatio
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return normalizePoint(viewportPoint);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function getViewportRect(client) {
|
|
199
|
+
try {
|
|
200
|
+
const metrics = await client.Page.getLayoutMetrics();
|
|
201
|
+
const viewport = metrics.visualViewport || metrics.layoutViewport || metrics.cssVisualViewport || {};
|
|
202
|
+
const width = Number(viewport.clientWidth || viewport.width || metrics.layoutViewport?.clientWidth || 0);
|
|
203
|
+
const height = Number(viewport.clientHeight || viewport.height || metrics.layoutViewport?.clientHeight || 0);
|
|
204
|
+
const x = Number(viewport.pageX || viewport.x || 0);
|
|
205
|
+
const y = Number(viewport.pageY || viewport.y || 0);
|
|
206
|
+
if (width > 0 && height > 0) {
|
|
207
|
+
return { x, y, width, height };
|
|
208
|
+
}
|
|
209
|
+
} catch {
|
|
210
|
+
// Page.getLayoutMetrics is optional for fallback only.
|
|
211
|
+
}
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async function collectUsableNodeBoxes(client, nodeIds = [], {
|
|
216
|
+
maxNodes = 80
|
|
217
|
+
} = {}) {
|
|
218
|
+
const boxes = [];
|
|
219
|
+
const errors = [];
|
|
220
|
+
for (const nodeId of nodeIds.slice(0, Math.max(1, Number(maxNodes) || 80))) {
|
|
221
|
+
try {
|
|
222
|
+
const box = await getNodeBox(client, nodeId);
|
|
223
|
+
if (isUsableBox(box)) {
|
|
224
|
+
boxes.push({
|
|
225
|
+
node_id: nodeId,
|
|
226
|
+
box,
|
|
227
|
+
rect: box.rect
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
} catch (error) {
|
|
231
|
+
errors.push({
|
|
232
|
+
node_id: nodeId,
|
|
233
|
+
error: error?.message || String(error)
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return { boxes, errors };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function querySelectorBoxes(client, rootNodeId, selectors = [], {
|
|
241
|
+
maxNodes = 80
|
|
242
|
+
} = {}) {
|
|
243
|
+
const attempts = [];
|
|
244
|
+
if (!rootNodeId) return { boxes: [], attempts };
|
|
245
|
+
for (const selector of selectors.filter(Boolean)) {
|
|
246
|
+
let nodeIds = [];
|
|
247
|
+
try {
|
|
248
|
+
nodeIds = await querySelectorAll(client, rootNodeId, selector);
|
|
249
|
+
} catch (error) {
|
|
250
|
+
attempts.push({
|
|
251
|
+
selector,
|
|
252
|
+
error: error?.message || String(error),
|
|
253
|
+
node_count: 0,
|
|
254
|
+
box_count: 0
|
|
255
|
+
});
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
const measured = await collectUsableNodeBoxes(client, nodeIds, { maxNodes });
|
|
259
|
+
attempts.push({
|
|
260
|
+
selector,
|
|
261
|
+
node_count: nodeIds.length,
|
|
262
|
+
box_count: measured.boxes.length,
|
|
263
|
+
errors: measured.errors
|
|
264
|
+
});
|
|
265
|
+
if (measured.boxes.length) {
|
|
266
|
+
return {
|
|
267
|
+
boxes: measured.boxes,
|
|
268
|
+
selector,
|
|
269
|
+
attempts
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return { boxes: [], attempts };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export async function resolveInfiniteListFallbackPoint(client, {
|
|
277
|
+
rootNodeId = 0,
|
|
278
|
+
containerSelectors = [],
|
|
279
|
+
itemNodeIds = [],
|
|
280
|
+
itemSelectors = [],
|
|
281
|
+
allowedSources = ["container", "item_union", "viewport_ratio"],
|
|
282
|
+
containerXRatio = 0.5,
|
|
283
|
+
containerYRatio = 0.5,
|
|
284
|
+
itemXRatio = 0.5,
|
|
285
|
+
itemYRatio = 0.5,
|
|
286
|
+
viewportPoint = null,
|
|
287
|
+
validateViewportPoint = true,
|
|
288
|
+
maxProbeNodes = 80
|
|
289
|
+
} = {}) {
|
|
290
|
+
const attempts = [];
|
|
291
|
+
const allowed = new Set(Array.isArray(allowedSources) && allowedSources.length
|
|
292
|
+
? allowedSources.map((source) => String(source || ""))
|
|
293
|
+
: ["container", "item_union", "viewport_ratio"]);
|
|
294
|
+
|
|
295
|
+
const containerResult = await querySelectorBoxes(client, rootNodeId, containerSelectors, {
|
|
296
|
+
maxNodes: maxProbeNodes
|
|
297
|
+
});
|
|
298
|
+
attempts.push({
|
|
299
|
+
source: "container",
|
|
300
|
+
selector: containerResult.selector || null,
|
|
301
|
+
attempts: containerResult.attempts
|
|
302
|
+
});
|
|
303
|
+
const containerBox = containerResult.boxes
|
|
304
|
+
.slice()
|
|
305
|
+
.sort((a, b) => (b.rect.width * b.rect.height) - (a.rect.width * a.rect.height))[0];
|
|
306
|
+
const viewport = await getViewportRect(client);
|
|
307
|
+
const inputViewportRect = viewport
|
|
308
|
+
? { x: 0, y: 0, width: viewport.width, height: viewport.height }
|
|
309
|
+
: null;
|
|
310
|
+
const visibleContainerRect = inputViewportRect && containerBox?.rect
|
|
311
|
+
? intersectRects(containerBox.rect, inputViewportRect) || containerBox.rect
|
|
312
|
+
: containerBox?.rect;
|
|
313
|
+
const containerPoint = pointFromRect(visibleContainerRect, {
|
|
314
|
+
xRatio: containerXRatio,
|
|
315
|
+
yRatio: containerYRatio
|
|
316
|
+
});
|
|
317
|
+
if (containerPoint && allowed.has("container")) {
|
|
318
|
+
return {
|
|
319
|
+
ok: true,
|
|
320
|
+
source: "container",
|
|
321
|
+
point: containerPoint,
|
|
322
|
+
selector: containerResult.selector || null,
|
|
323
|
+
node_id: containerBox.node_id,
|
|
324
|
+
assist_node_id: itemNodeIds.slice(-1)[0] || null,
|
|
325
|
+
rect: visibleContainerRect,
|
|
326
|
+
full_rect: containerBox.rect,
|
|
327
|
+
attempts
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
let itemBoxes = [];
|
|
332
|
+
const itemProbeNodeIds = itemNodeIds.length > maxProbeNodes
|
|
333
|
+
? itemNodeIds.slice(-maxProbeNodes)
|
|
334
|
+
: itemNodeIds;
|
|
335
|
+
const measuredItems = await collectUsableNodeBoxes(client, itemProbeNodeIds, { maxNodes: maxProbeNodes });
|
|
336
|
+
itemBoxes = measuredItems.boxes;
|
|
337
|
+
attempts.push({
|
|
338
|
+
source: "visible_items",
|
|
339
|
+
node_count: itemNodeIds.length,
|
|
340
|
+
box_count: measuredItems.boxes.length,
|
|
341
|
+
errors: measuredItems.errors
|
|
342
|
+
});
|
|
343
|
+
if (!itemBoxes.length) {
|
|
344
|
+
const queriedItems = await querySelectorBoxes(client, rootNodeId, itemSelectors, {
|
|
345
|
+
maxNodes: maxProbeNodes
|
|
346
|
+
});
|
|
347
|
+
itemBoxes = queriedItems.boxes;
|
|
348
|
+
attempts.push({
|
|
349
|
+
source: "item_selector",
|
|
350
|
+
selector: queriedItems.selector || null,
|
|
351
|
+
attempts: queriedItems.attempts
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
const itemValidationRects = [
|
|
355
|
+
inputViewportRect,
|
|
356
|
+
visibleContainerRect || containerBox?.rect || null
|
|
357
|
+
].filter(isUsableRect);
|
|
358
|
+
const visibleItemBoxes = itemValidationRects.length
|
|
359
|
+
? itemBoxes.filter((item) => itemValidationRects.every((rect) => rectsIntersect(item.rect, rect, { padding: 1 })))
|
|
360
|
+
: itemBoxes;
|
|
361
|
+
attempts.push({
|
|
362
|
+
source: "visible_item_filter",
|
|
363
|
+
input_box_count: itemBoxes.length,
|
|
364
|
+
output_box_count: visibleItemBoxes.length,
|
|
365
|
+
validation_rect_count: itemValidationRects.length
|
|
366
|
+
});
|
|
367
|
+
const unionSourceBoxes = visibleItemBoxes.length ? visibleItemBoxes : itemBoxes;
|
|
368
|
+
const rawItemUnion = unionRects(unionSourceBoxes.map((item) => item.rect));
|
|
369
|
+
const itemUnion = itemValidationRects.reduce(
|
|
370
|
+
(rect, limit) => intersectRects(rect, limit) || rect,
|
|
371
|
+
rawItemUnion
|
|
372
|
+
);
|
|
373
|
+
const itemPoint = pointFromRect(itemUnion, {
|
|
374
|
+
xRatio: itemXRatio,
|
|
375
|
+
yRatio: itemYRatio
|
|
376
|
+
});
|
|
377
|
+
if (itemPoint && allowed.has("item_union")) {
|
|
378
|
+
const assistItem = unionSourceBoxes
|
|
379
|
+
.slice()
|
|
380
|
+
.sort((a, b) => ((b.rect.y + b.rect.height) - (a.rect.y + a.rect.height)))[0];
|
|
381
|
+
return {
|
|
382
|
+
ok: true,
|
|
383
|
+
source: "item_union",
|
|
384
|
+
point: itemPoint,
|
|
385
|
+
rect: itemUnion,
|
|
386
|
+
full_rect: rawItemUnion,
|
|
387
|
+
item_box_count: unionSourceBoxes.length,
|
|
388
|
+
visible_item_box_count: visibleItemBoxes.length,
|
|
389
|
+
assist_node_id: assistItem?.node_id || itemNodeIds.slice(-1)[0] || null,
|
|
390
|
+
attempts
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const viewportRatioPoint = resolveViewportPoint(viewportPoint, viewport);
|
|
395
|
+
const normalizedViewportPoint = normalizePoint(viewportRatioPoint);
|
|
396
|
+
if (normalizedViewportPoint && allowed.has("viewport_ratio")) {
|
|
397
|
+
if (!validateViewportPoint) {
|
|
398
|
+
return {
|
|
399
|
+
ok: true,
|
|
400
|
+
source: "viewport_ratio",
|
|
401
|
+
point: normalizedViewportPoint,
|
|
402
|
+
viewport,
|
|
403
|
+
validated: false,
|
|
404
|
+
attempts
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
const validationRects = [
|
|
408
|
+
...containerResult.boxes.map((item) => item.rect),
|
|
409
|
+
...itemBoxes.map((item) => item.rect)
|
|
410
|
+
].filter(isUsableRect);
|
|
411
|
+
const validatedRect = validationRects.find((rect) => pointInsideRect(normalizedViewportPoint, rect, { padding: 4 }));
|
|
412
|
+
attempts.push({
|
|
413
|
+
source: "viewport_ratio",
|
|
414
|
+
point: normalizedViewportPoint,
|
|
415
|
+
viewport,
|
|
416
|
+
validation_rect_count: validationRects.length,
|
|
417
|
+
validated: Boolean(validatedRect)
|
|
418
|
+
});
|
|
419
|
+
if (validatedRect) {
|
|
420
|
+
return {
|
|
421
|
+
ok: true,
|
|
422
|
+
source: "viewport_ratio",
|
|
423
|
+
point: normalizedViewportPoint,
|
|
424
|
+
viewport,
|
|
425
|
+
rect: validatedRect,
|
|
426
|
+
validated: true,
|
|
427
|
+
assist_node_id: itemNodeIds.slice(-1)[0] || null,
|
|
428
|
+
attempts
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return {
|
|
434
|
+
ok: false,
|
|
435
|
+
reason: "fallback_point_unavailable",
|
|
436
|
+
attempts
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
99
440
|
function shortHash(value) {
|
|
100
441
|
return crypto.createHash("sha256").update(String(value || "")).digest("hex").slice(0, 16);
|
|
101
442
|
}
|
|
@@ -561,6 +902,29 @@ export async function scrollInfiniteListByVisibleItems(client, items = [], {
|
|
|
561
902
|
}
|
|
562
903
|
|
|
563
904
|
const errors = [];
|
|
905
|
+
const wheelDelta = Math.max(1, Number(wheelDeltaY) || 850);
|
|
906
|
+
async function synthesizeGesture(x, y) {
|
|
907
|
+
if (typeof client?.Input?.synthesizeScrollGesture !== "function") return null;
|
|
908
|
+
try {
|
|
909
|
+
const gestureDistance = -Math.min(1200, wheelDelta);
|
|
910
|
+
await client.Input.synthesizeScrollGesture({
|
|
911
|
+
x,
|
|
912
|
+
y,
|
|
913
|
+
yDistance: gestureDistance,
|
|
914
|
+
speed: 800,
|
|
915
|
+
repeatCount: 1
|
|
916
|
+
});
|
|
917
|
+
return {
|
|
918
|
+
ok: true,
|
|
919
|
+
y_distance: gestureDistance
|
|
920
|
+
};
|
|
921
|
+
} catch (error) {
|
|
922
|
+
return {
|
|
923
|
+
ok: false,
|
|
924
|
+
error: error?.message || String(error)
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
}
|
|
564
928
|
for (const anchor of candidates.slice().reverse()) {
|
|
565
929
|
try {
|
|
566
930
|
await scrollNodeIntoView(client, anchor.node_id);
|
|
@@ -574,15 +938,17 @@ export async function scrollInfiniteListByVisibleItems(client, items = [], {
|
|
|
574
938
|
x,
|
|
575
939
|
y,
|
|
576
940
|
deltaX: 0,
|
|
577
|
-
deltaY:
|
|
941
|
+
deltaY: wheelDelta
|
|
578
942
|
});
|
|
943
|
+
const gesture = await synthesizeGesture(x, y);
|
|
579
944
|
if (settleMs > 0) await sleep(settleMs);
|
|
580
945
|
return {
|
|
581
946
|
ok: true,
|
|
582
947
|
anchor_key: anchor.key,
|
|
583
948
|
anchor_node_id: anchor.node_id,
|
|
584
949
|
point: { x, y },
|
|
585
|
-
wheel_delta_y:
|
|
950
|
+
wheel_delta_y: wheelDelta,
|
|
951
|
+
gesture,
|
|
586
952
|
settle_ms: settleMs,
|
|
587
953
|
skipped_stale_anchor_count: errors.length
|
|
588
954
|
};
|
|
@@ -595,23 +961,56 @@ export async function scrollInfiniteListByVisibleItems(client, items = [], {
|
|
|
595
961
|
}
|
|
596
962
|
}
|
|
597
963
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
964
|
+
const resolvedFallback = typeof fallbackPoint === "function"
|
|
965
|
+
? await fallbackPoint({ client, items, errors })
|
|
966
|
+
: (fallbackPoint ? { ok: true, source: "static", point: fallbackPoint } : null);
|
|
967
|
+
const resolvedPoint = normalizePoint(resolvedFallback?.point || resolvedFallback);
|
|
968
|
+
if (resolvedPoint) {
|
|
969
|
+
const x = resolvedPoint.x;
|
|
970
|
+
const y = resolvedPoint.y;
|
|
971
|
+
let assist = null;
|
|
972
|
+
if (resolvedFallback?.assist_node_id) {
|
|
973
|
+
try {
|
|
974
|
+
await scrollNodeIntoView(client, resolvedFallback.assist_node_id);
|
|
975
|
+
await sleep(150);
|
|
976
|
+
assist = {
|
|
977
|
+
ok: true,
|
|
978
|
+
node_id: resolvedFallback.assist_node_id
|
|
979
|
+
};
|
|
980
|
+
} catch (error) {
|
|
981
|
+
assist = {
|
|
982
|
+
ok: false,
|
|
983
|
+
node_id: resolvedFallback.assist_node_id,
|
|
984
|
+
error: error?.message || String(error)
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
}
|
|
601
988
|
await client.Input.dispatchMouseEvent({ type: "mouseMoved", x, y, button: "none" });
|
|
602
989
|
await client.Input.dispatchMouseEvent({
|
|
603
990
|
type: "mouseWheel",
|
|
604
991
|
x,
|
|
605
992
|
y,
|
|
606
993
|
deltaX: 0,
|
|
607
|
-
deltaY:
|
|
994
|
+
deltaY: wheelDelta
|
|
608
995
|
});
|
|
996
|
+
const gesture = await synthesizeGesture(x, y);
|
|
609
997
|
if (settleMs > 0) await sleep(settleMs);
|
|
610
998
|
return {
|
|
611
999
|
ok: true,
|
|
612
1000
|
mode: "fallback_point",
|
|
1001
|
+
fallback: {
|
|
1002
|
+
source: resolvedFallback?.source || "static",
|
|
1003
|
+
selector: resolvedFallback?.selector || null,
|
|
1004
|
+
node_id: resolvedFallback?.node_id || null,
|
|
1005
|
+
assist_node_id: resolvedFallback?.assist_node_id || null,
|
|
1006
|
+
rect: resolvedFallback?.rect || null,
|
|
1007
|
+
validated: resolvedFallback?.validated ?? null,
|
|
1008
|
+
reason: resolvedFallback?.reason || null
|
|
1009
|
+
},
|
|
1010
|
+
assist,
|
|
613
1011
|
point: { x, y },
|
|
614
|
-
wheel_delta_y:
|
|
1012
|
+
wheel_delta_y: wheelDelta,
|
|
1013
|
+
gesture,
|
|
615
1014
|
settle_ms: settleMs,
|
|
616
1015
|
skipped_stale_anchor_count: errors.length,
|
|
617
1016
|
stale_anchor_errors: errors
|
|
@@ -621,7 +1020,8 @@ export async function scrollInfiniteListByVisibleItems(client, items = [], {
|
|
|
621
1020
|
return {
|
|
622
1021
|
ok: false,
|
|
623
1022
|
reason: "scroll_anchor_unavailable",
|
|
624
|
-
errors
|
|
1023
|
+
errors,
|
|
1024
|
+
fallback: resolvedFallback || null
|
|
625
1025
|
};
|
|
626
1026
|
}
|
|
627
1027
|
|
|
@@ -8,6 +8,21 @@ export const CHAT_CARD_SELECTORS = Object.freeze([
|
|
|
8
8
|
"div[role=\"listitem\"]"
|
|
9
9
|
]);
|
|
10
10
|
|
|
11
|
+
export const CHAT_LIST_CONTAINER_SELECTORS = Object.freeze([
|
|
12
|
+
".chat-list",
|
|
13
|
+
".chat-list-content",
|
|
14
|
+
".chat-left",
|
|
15
|
+
".chat-left-main",
|
|
16
|
+
".chat-message-list-left",
|
|
17
|
+
".chat-conversation-list",
|
|
18
|
+
".geek-list",
|
|
19
|
+
".geek-list-wrap",
|
|
20
|
+
".chat-list-wrap",
|
|
21
|
+
".user-list",
|
|
22
|
+
".conversation-list",
|
|
23
|
+
"div[role=\"list\"]"
|
|
24
|
+
]);
|
|
25
|
+
|
|
11
26
|
export const CHAT_BOTTOM_MARKER_SELECTORS = Object.freeze([
|
|
12
27
|
"div[role=\"tfoot\"] .load-tips",
|
|
13
28
|
"p.load-tips",
|
|
@@ -22,7 +22,8 @@ import {
|
|
|
22
22
|
detectInfiniteListBottomMarker,
|
|
23
23
|
getNextInfiniteListCandidate,
|
|
24
24
|
markInfiniteListCandidateProcessed,
|
|
25
|
-
resetInfiniteListForRefreshRound
|
|
25
|
+
resetInfiniteListForRefreshRound,
|
|
26
|
+
resolveInfiniteListFallbackPoint
|
|
26
27
|
} from "../../core/infinite-list/index.js";
|
|
27
28
|
import { createViewportRunGuard } from "../../core/self-heal/index.js";
|
|
28
29
|
import { createRunLifecycleManager } from "../../core/run/index.js";
|
|
@@ -38,6 +39,8 @@ import {
|
|
|
38
39
|
} from "../../core/screening/index.js";
|
|
39
40
|
import {
|
|
40
41
|
CHAT_BOTTOM_MARKER_SELECTORS,
|
|
42
|
+
CHAT_CARD_SELECTORS,
|
|
43
|
+
CHAT_LIST_CONTAINER_SELECTORS,
|
|
41
44
|
CHAT_TARGET_URL
|
|
42
45
|
} from "./constants.js";
|
|
43
46
|
import {
|
|
@@ -682,6 +685,14 @@ export async function runChatWorkflow({
|
|
|
682
685
|
const results = [];
|
|
683
686
|
let cardNodeIds = [];
|
|
684
687
|
let listEndReason = "";
|
|
688
|
+
const listFallbackResolver = listFallbackPoint || (async ({ items = [] } = {}) => resolveInfiniteListFallbackPoint(client, {
|
|
689
|
+
rootNodeId: rootState?.rootNodes?.top,
|
|
690
|
+
containerSelectors: CHAT_LIST_CONTAINER_SELECTORS,
|
|
691
|
+
itemNodeIds: items.map((item) => item.node_id).filter(Boolean),
|
|
692
|
+
itemSelectors: CHAT_CARD_SELECTORS,
|
|
693
|
+
viewportPoint: { xRatio: 0.16, yRatio: 0.4 },
|
|
694
|
+
validateViewportPoint: true
|
|
695
|
+
}));
|
|
685
696
|
let requestedCount = 0;
|
|
686
697
|
let requestSatisfiedCount = 0;
|
|
687
698
|
let requestSkippedCount = 0;
|
|
@@ -921,7 +932,7 @@ export async function runChatWorkflow({
|
|
|
921
932
|
stableSignatureLimit: listStableSignatureLimit,
|
|
922
933
|
wheelDeltaY: listWheelDeltaY,
|
|
923
934
|
settleMs: listSettleMs,
|
|
924
|
-
fallbackPoint:
|
|
935
|
+
fallbackPoint: listFallbackResolver,
|
|
925
936
|
findNodeIds: async () => {
|
|
926
937
|
const currentRootState = await ensureChatViewport(await getChatRoots(client), "candidate_find_nodes");
|
|
927
938
|
rootState = currentRootState;
|
|
@@ -58,6 +58,19 @@ export const RECOMMEND_CARD_SELECTOR = [
|
|
|
58
58
|
"a[data-geekid]"
|
|
59
59
|
].join(", ");
|
|
60
60
|
|
|
61
|
+
export const RECOMMEND_LIST_CONTAINER_SELECTORS = Object.freeze([
|
|
62
|
+
".recommend-list",
|
|
63
|
+
".recommend-list-wrap",
|
|
64
|
+
".candidate-list",
|
|
65
|
+
".candidate-card-list",
|
|
66
|
+
".candidate-card-wrap-list",
|
|
67
|
+
".geek-list",
|
|
68
|
+
".geek-list-wrap",
|
|
69
|
+
".card-list",
|
|
70
|
+
".list-wrap",
|
|
71
|
+
".content-list"
|
|
72
|
+
]);
|
|
73
|
+
|
|
61
74
|
export const RECOMMEND_END_REFRESH_SELECTOR = [
|
|
62
75
|
".btn",
|
|
63
76
|
"button",
|
|
@@ -24,7 +24,8 @@ import {
|
|
|
24
24
|
detectInfiniteListBottomMarker,
|
|
25
25
|
getNextInfiniteListCandidate,
|
|
26
26
|
markInfiniteListCandidateProcessed,
|
|
27
|
-
resetInfiniteListForRefreshRound
|
|
27
|
+
resetInfiniteListForRefreshRound,
|
|
28
|
+
resolveInfiniteListFallbackPoint
|
|
28
29
|
} from "../../core/infinite-list/index.js";
|
|
29
30
|
import { createViewportRunGuard } from "../../core/self-heal/index.js";
|
|
30
31
|
import {
|
|
@@ -57,7 +58,9 @@ import {
|
|
|
57
58
|
} from "./scopes.js";
|
|
58
59
|
import {
|
|
59
60
|
RECOMMEND_BOTTOM_MARKER_SELECTORS,
|
|
60
|
-
|
|
61
|
+
RECOMMEND_CARD_SELECTOR,
|
|
62
|
+
RECOMMEND_END_REFRESH_SELECTOR,
|
|
63
|
+
RECOMMEND_LIST_CONTAINER_SELECTORS
|
|
61
64
|
} from "./constants.js";
|
|
62
65
|
import {
|
|
63
66
|
clickRecommendActionControl,
|
|
@@ -451,6 +454,14 @@ export async function runRecommendWorkflow({
|
|
|
451
454
|
let filterResult = null;
|
|
452
455
|
let cardNodeIds = [];
|
|
453
456
|
let listEndReason = "";
|
|
457
|
+
const listFallbackResolver = listFallbackPoint || (async ({ items = [] } = {}) => resolveInfiniteListFallbackPoint(client, {
|
|
458
|
+
rootNodeId: rootState?.iframe?.documentNodeId,
|
|
459
|
+
containerSelectors: RECOMMEND_LIST_CONTAINER_SELECTORS,
|
|
460
|
+
itemNodeIds: items.map((item) => item.node_id).filter(Boolean),
|
|
461
|
+
itemSelectors: [RECOMMEND_CARD_SELECTOR],
|
|
462
|
+
viewportPoint: { xRatio: 0.28, yRatio: 0.5 },
|
|
463
|
+
validateViewportPoint: true
|
|
464
|
+
}));
|
|
454
465
|
|
|
455
466
|
runControl.setPhase("recommend:cleanup");
|
|
456
467
|
await closeRecommendDetail(client, { attemptsLimit: 2 });
|
|
@@ -567,7 +578,7 @@ export async function runRecommendWorkflow({
|
|
|
567
578
|
stableSignatureLimit: listStableSignatureLimit,
|
|
568
579
|
wheelDeltaY: listWheelDeltaY,
|
|
569
580
|
settleMs: listSettleMs,
|
|
570
|
-
fallbackPoint:
|
|
581
|
+
fallbackPoint: listFallbackResolver,
|
|
571
582
|
findNodeIds: async () => {
|
|
572
583
|
let currentRootState = await getRecommendRoots(client);
|
|
573
584
|
currentRootState = await ensureRecommendViewport(currentRootState, "candidate_find_nodes");
|
|
@@ -16,6 +16,18 @@ export const RECRUIT_CARD_SELECTOR = [
|
|
|
16
16
|
"a[data-geekid]"
|
|
17
17
|
].join(", ");
|
|
18
18
|
|
|
19
|
+
export const RECRUIT_LIST_CONTAINER_SELECTORS = Object.freeze([
|
|
20
|
+
".search-list",
|
|
21
|
+
".search-result-list",
|
|
22
|
+
".candidate-list",
|
|
23
|
+
".geek-list",
|
|
24
|
+
".geek-list-wrap",
|
|
25
|
+
".card-list",
|
|
26
|
+
".list-wrap",
|
|
27
|
+
".search-content",
|
|
28
|
+
".search-container"
|
|
29
|
+
]);
|
|
30
|
+
|
|
19
31
|
export const RECRUIT_NO_DATA_SELECTORS = Object.freeze([
|
|
20
32
|
"i.tip-nodata",
|
|
21
33
|
".tip-nodata",
|
|
@@ -26,12 +38,14 @@ export const RECRUIT_NO_DATA_SELECTORS = Object.freeze([
|
|
|
26
38
|
|
|
27
39
|
export const RECRUIT_BOTTOM_MARKER_SELECTORS = Object.freeze([
|
|
28
40
|
".finished-wrap",
|
|
41
|
+
".loadmore",
|
|
29
42
|
".load-tips",
|
|
30
43
|
".tip-nodata",
|
|
31
44
|
".empty-tip",
|
|
32
45
|
".empty-text",
|
|
33
46
|
".no-data",
|
|
34
47
|
"[class*=\"finished\"]",
|
|
48
|
+
"[class*=\"loadmore\"]",
|
|
35
49
|
"[class*=\"load-tips\"]",
|
|
36
50
|
"[class*=\"empty\"]"
|
|
37
51
|
]);
|
|
@@ -22,7 +22,8 @@ import {
|
|
|
22
22
|
detectInfiniteListBottomMarker,
|
|
23
23
|
getNextInfiniteListCandidate,
|
|
24
24
|
markInfiniteListCandidateProcessed,
|
|
25
|
-
resetInfiniteListForRefreshRound
|
|
25
|
+
resetInfiniteListForRefreshRound,
|
|
26
|
+
resolveInfiniteListFallbackPoint
|
|
26
27
|
} from "../../core/infinite-list/index.js";
|
|
27
28
|
import { createViewportRunGuard } from "../../core/self-heal/index.js";
|
|
28
29
|
import {
|
|
@@ -52,7 +53,9 @@ import { refreshRecruitSearchAtEnd } from "./refresh.js";
|
|
|
52
53
|
import { getRecruitRoots } from "./roots.js";
|
|
53
54
|
import {
|
|
54
55
|
RECRUIT_BOTTOM_MARKER_SELECTORS,
|
|
55
|
-
RECRUIT_BOTTOM_REFRESH_SELECTORS
|
|
56
|
+
RECRUIT_BOTTOM_REFRESH_SELECTORS,
|
|
57
|
+
RECRUIT_CARD_SELECTOR,
|
|
58
|
+
RECRUIT_LIST_CONTAINER_SELECTORS
|
|
56
59
|
} from "./constants.js";
|
|
57
60
|
|
|
58
61
|
function compactScreening(screening) {
|
|
@@ -194,6 +197,14 @@ export async function runRecruitWorkflow({
|
|
|
194
197
|
let refreshRounds = 0;
|
|
195
198
|
let cardNodeIds = [];
|
|
196
199
|
let listEndReason = "";
|
|
200
|
+
const listFallbackResolver = listFallbackPoint || (async ({ items = [] } = {}) => resolveInfiniteListFallbackPoint(client, {
|
|
201
|
+
rootNodeId: rootState?.iframe?.documentNodeId,
|
|
202
|
+
containerSelectors: RECRUIT_LIST_CONTAINER_SELECTORS,
|
|
203
|
+
itemNodeIds: items.map((item) => item.node_id).filter(Boolean),
|
|
204
|
+
itemSelectors: [RECRUIT_CARD_SELECTOR],
|
|
205
|
+
viewportPoint: { xRatio: 0.28, yRatio: 0.5 },
|
|
206
|
+
validateViewportPoint: true
|
|
207
|
+
}));
|
|
197
208
|
|
|
198
209
|
runControl.setPhase("recruit:cleanup");
|
|
199
210
|
await closeRecruitDetail(client, { attemptsLimit: 2 });
|
|
@@ -283,7 +294,7 @@ export async function runRecruitWorkflow({
|
|
|
283
294
|
stableSignatureLimit: listStableSignatureLimit,
|
|
284
295
|
wheelDeltaY: listWheelDeltaY,
|
|
285
296
|
settleMs: listSettleMs,
|
|
286
|
-
fallbackPoint:
|
|
297
|
+
fallbackPoint: listFallbackResolver,
|
|
287
298
|
findNodeIds: async () => {
|
|
288
299
|
let currentRootState = await getRecruitRoots(client);
|
|
289
300
|
currentRootState = await ensureRecruitViewport(currentRootState, "candidate_find_nodes");
|