@ceki/n8n-nodes-ceki 0.2.29 → 0.2.31
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 +3 -3
- package/dist/credentials/CekiApi.credentials.js +1 -0
- package/dist/credentials/CekiApi.credentials.js.map +2 -2
- package/dist/index.js +967 -956
- package/dist/index.js.map +4 -4
- package/dist/nodes/BrowserCeki/BrowserCeki.node.js +928 -305
- package/dist/nodes/BrowserCeki/BrowserCeki.node.js.map +4 -4
- package/dist/nodes/BrowserCeki/ceki-dark.svg +9 -0
- package/dist/nodes/BrowserCeki/ceki-light.svg +9 -0
- package/dist/nodes/BrowserCeki/ceki.png +0 -0
- package/package.json +9 -8
- package/dist/nodes/CekiContract/CekiContract.node.js +0 -415
- package/dist/nodes/CekiContract/CekiContract.node.js.map +0 -7
- package/dist/nodes/CekiContract/ceki.png +0 -0
- package/dist/nodes/recipes/CekiCaptchaScrape/CekiCaptchaScrape.node.js +0 -595
- package/dist/nodes/recipes/CekiCaptchaScrape/CekiCaptchaScrape.node.js.map +0 -7
- package/dist/nodes/recipes/CekiCaptchaScrape/ceki.png +0 -0
- package/dist/nodes/recipes/CekiScreenshotGeo/CekiScreenshotGeo.node.js +0 -514
- package/dist/nodes/recipes/CekiScreenshotGeo/CekiScreenshotGeo.node.js.map +0 -7
- package/dist/nodes/recipes/CekiScreenshotGeo/ceki.png +0 -0
- package/dist/nodes/recipes/ceki.png +0 -0
package/dist/index.js
CHANGED
|
@@ -25,6 +25,9 @@ __export(index_exports, {
|
|
|
25
25
|
});
|
|
26
26
|
module.exports = __toCommonJS(index_exports);
|
|
27
27
|
|
|
28
|
+
// nodes/BrowserCeki/BrowserCeki.node.ts
|
|
29
|
+
var import_n8n_workflow = require("n8n-workflow");
|
|
30
|
+
|
|
28
31
|
// lib/ceki-client.ts
|
|
29
32
|
function jsonParse(raw) {
|
|
30
33
|
try {
|
|
@@ -148,18 +151,18 @@ var CekiClient = class {
|
|
|
148
151
|
return;
|
|
149
152
|
}
|
|
150
153
|
await new Promise((r) => {
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
+
const onAbort = () => r();
|
|
155
|
+
const abortTimer = AbortSignal.timeout(5e3);
|
|
156
|
+
abortTimer.addEventListener("abort", onAbort, { once: true });
|
|
154
157
|
ws.onclose = () => {
|
|
155
158
|
this._connected = false;
|
|
156
|
-
|
|
159
|
+
abortTimer.removeEventListener("abort", onAbort);
|
|
157
160
|
r();
|
|
158
161
|
};
|
|
159
162
|
try {
|
|
160
163
|
ws.close();
|
|
161
164
|
} catch {
|
|
162
|
-
|
|
165
|
+
abortTimer.removeEventListener("abort", onAbort);
|
|
163
166
|
r();
|
|
164
167
|
}
|
|
165
168
|
});
|
|
@@ -371,9 +374,9 @@ var CekiBrowser = class {
|
|
|
371
374
|
async navigate(url, timeoutMs) {
|
|
372
375
|
await this.send("Page.navigate", { url }, timeoutMs);
|
|
373
376
|
}
|
|
374
|
-
async click(
|
|
375
|
-
await this.send("Input.dispatchMouseEvent", { type: "mousePressed", x
|
|
376
|
-
await this.send("Input.dispatchMouseEvent", { type: "mouseReleased", x
|
|
377
|
+
async click(x, y) {
|
|
378
|
+
await this.send("Input.dispatchMouseEvent", { type: "mousePressed", x, y, button: "left", clickCount: 1 });
|
|
379
|
+
await this.send("Input.dispatchMouseEvent", { type: "mouseReleased", x, y, button: "left", clickCount: 1 });
|
|
377
380
|
}
|
|
378
381
|
async type(text) {
|
|
379
382
|
await this.send("Ceki.typeText", { text });
|
|
@@ -436,7 +439,181 @@ var CekiBrowser = class {
|
|
|
436
439
|
}
|
|
437
440
|
};
|
|
438
441
|
|
|
442
|
+
// lib/contract-client.ts
|
|
443
|
+
function cleanArgs(o) {
|
|
444
|
+
const out = { ...o };
|
|
445
|
+
for (const k of Object.keys(out)) {
|
|
446
|
+
if (out[k] === void 0 || out[k] === null) delete out[k];
|
|
447
|
+
}
|
|
448
|
+
return out;
|
|
449
|
+
}
|
|
450
|
+
function parseBenefitable(value) {
|
|
451
|
+
if (!value) return null;
|
|
452
|
+
const m = /^(agent|user):(\d+)$/.exec(value);
|
|
453
|
+
if (!m) return null;
|
|
454
|
+
return { type: m[1], value: Number(m[2]) };
|
|
455
|
+
}
|
|
456
|
+
function parseParticipant(value, roleId) {
|
|
457
|
+
const b = parseBenefitable(value);
|
|
458
|
+
if (!b) return null;
|
|
459
|
+
return { participable_id: b.value, type: b.type, role_id: roleId };
|
|
460
|
+
}
|
|
461
|
+
function deriveLabel(desc) {
|
|
462
|
+
if (!desc) return "";
|
|
463
|
+
const line = desc.split("\n")[0].trim();
|
|
464
|
+
return line.length > 60 ? line.slice(0, 57) + "..." : line;
|
|
465
|
+
}
|
|
466
|
+
var ROLE_REVIEWER = 5;
|
|
467
|
+
var ROLE_QA = 6;
|
|
468
|
+
var ContractClient = class {
|
|
469
|
+
constructor(token, endpoint, apiBase) {
|
|
470
|
+
this._endpoint = (endpoint ?? "https://api.ceki.me/mcp").replace(/\/+$/, "");
|
|
471
|
+
this._apiBase = (apiBase ?? "https://api.ceki.me").replace(/\/+$/, "");
|
|
472
|
+
this._token = token;
|
|
473
|
+
}
|
|
474
|
+
_headers() {
|
|
475
|
+
return {
|
|
476
|
+
"Content-Type": "application/json",
|
|
477
|
+
Accept: "application/json",
|
|
478
|
+
Authorization: `Bearer ${this._token}`
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
async _rpc(method, params) {
|
|
482
|
+
const body = JSON.stringify({ jsonrpc: "2.0", id: Date.now(), method, params });
|
|
483
|
+
const resp = await fetch(this._endpoint, {
|
|
484
|
+
method: "POST",
|
|
485
|
+
headers: this._headers(),
|
|
486
|
+
body
|
|
487
|
+
});
|
|
488
|
+
if (!resp.ok) {
|
|
489
|
+
const text = await resp.text().catch(() => "");
|
|
490
|
+
throw new Error(`HTTP ${resp.status}: ${text.slice(0, 400)}`);
|
|
491
|
+
}
|
|
492
|
+
return resp.json();
|
|
493
|
+
}
|
|
494
|
+
async _call(tool, args) {
|
|
495
|
+
const body = await this._rpc("tools/call", { name: tool, arguments: args ?? {} });
|
|
496
|
+
if (body.error) throw new Error(`${tool} \u2192 ${JSON.stringify(body.error).slice(0, 400)}`);
|
|
497
|
+
const result = body.result ?? {};
|
|
498
|
+
const content = result.content;
|
|
499
|
+
if (Array.isArray(content)) {
|
|
500
|
+
const texts = content.filter((c) => c.type === "text").map((c) => String(c.text ?? ""));
|
|
501
|
+
const joined = texts.join("\n");
|
|
502
|
+
try {
|
|
503
|
+
return JSON.parse(joined);
|
|
504
|
+
} catch {
|
|
505
|
+
return joined;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
if (result.structuredContent !== void 0) return result.structuredContent;
|
|
509
|
+
return result;
|
|
510
|
+
}
|
|
511
|
+
// ── domain methods ─────────────────────────────────────────
|
|
512
|
+
async listContracts() {
|
|
513
|
+
return this._call("list-contracts");
|
|
514
|
+
}
|
|
515
|
+
async members(contractId) {
|
|
516
|
+
return this._call("contract-members", { contract_id: contractId });
|
|
517
|
+
}
|
|
518
|
+
async tasks(contractId) {
|
|
519
|
+
return this._call("contract-tasks", { contract_id: contractId });
|
|
520
|
+
}
|
|
521
|
+
async myEvents() {
|
|
522
|
+
return this._call("get-my-events");
|
|
523
|
+
}
|
|
524
|
+
async task(eventId) {
|
|
525
|
+
return this._call("get-event", { event_id: eventId });
|
|
526
|
+
}
|
|
527
|
+
async create(contractId, opts) {
|
|
528
|
+
const args = cleanArgs({
|
|
529
|
+
contract_id: contractId,
|
|
530
|
+
label: opts.label,
|
|
531
|
+
type_id: opts.type,
|
|
532
|
+
status_id: opts.status,
|
|
533
|
+
description: opts.description,
|
|
534
|
+
benefitable: opts.benefitable ? parseBenefitable(opts.benefitable) : void 0
|
|
535
|
+
});
|
|
536
|
+
const users = [];
|
|
537
|
+
const rev = parseParticipant(opts.reviewer, ROLE_REVIEWER);
|
|
538
|
+
if (rev) users.push(rev);
|
|
539
|
+
const qa = parseParticipant(opts.qa, ROLE_QA);
|
|
540
|
+
if (qa) users.push(qa);
|
|
541
|
+
if (users.length) args.users = users;
|
|
542
|
+
return this._call("create-contract-event", args);
|
|
543
|
+
}
|
|
544
|
+
async propose(eventId, opts) {
|
|
545
|
+
return this._call("propose-correction", cleanArgs({
|
|
546
|
+
event_id: eventId,
|
|
547
|
+
status_id: opts.status,
|
|
548
|
+
label: opts.label,
|
|
549
|
+
description: opts.description,
|
|
550
|
+
benefitable: opts.benefitable ? parseBenefitable(opts.benefitable) : void 0
|
|
551
|
+
}));
|
|
552
|
+
}
|
|
553
|
+
async comment(eventId, opts) {
|
|
554
|
+
return this._call("comment", cleanArgs({
|
|
555
|
+
event_id: eventId,
|
|
556
|
+
label: opts?.label,
|
|
557
|
+
description: opts?.description
|
|
558
|
+
}));
|
|
559
|
+
}
|
|
560
|
+
async progress(eventId, opts) {
|
|
561
|
+
let statusResult = null;
|
|
562
|
+
if (opts.status != null) {
|
|
563
|
+
statusResult = await this.propose(eventId, { status: opts.status });
|
|
564
|
+
}
|
|
565
|
+
const commentResult = await this.comment(eventId, { label: deriveLabel(opts.desc), description: opts.desc });
|
|
566
|
+
return { status_correction: statusResult, comment: commentResult };
|
|
567
|
+
}
|
|
568
|
+
async callHuman(eventId, kind, desc) {
|
|
569
|
+
if (!["input", "review", "stuck"].includes(kind)) throw new Error(`kind must be input|review|stuck, got ${kind}`);
|
|
570
|
+
return this._call("call-human", { event_id: eventId, kind, desc });
|
|
571
|
+
}
|
|
572
|
+
/** GET /agent/polling. Returns [] on 429. */
|
|
573
|
+
async poll() {
|
|
574
|
+
const resp = await fetch(`${this._apiBase}/agent/polling`, {
|
|
575
|
+
headers: { Accept: "application/json", Authorization: `Bearer ${this._token}` }
|
|
576
|
+
});
|
|
577
|
+
if (resp.status === 429) return [];
|
|
578
|
+
if (!resp.ok) {
|
|
579
|
+
const text = await resp.text().catch(() => "");
|
|
580
|
+
throw new Error(`poll HTTP ${resp.status}: ${text.slice(0, 300)}`);
|
|
581
|
+
}
|
|
582
|
+
const body = await resp.json();
|
|
583
|
+
if (Array.isArray(body)) return body;
|
|
584
|
+
if (body && typeof body === "object") {
|
|
585
|
+
for (const k of ["notifications", "data", "items"]) {
|
|
586
|
+
if (Array.isArray(body[k])) return body[k];
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
return [];
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
|
|
439
593
|
// nodes/BrowserCeki/BrowserCeki.node.ts
|
|
594
|
+
async function waitForSelector(node, browser, selector, timeoutMs, intervalMs = 500) {
|
|
595
|
+
const expr = `!!document.querySelector(${JSON.stringify(selector)})`;
|
|
596
|
+
const deadline = Date.now() + timeoutMs;
|
|
597
|
+
let lastErr = null;
|
|
598
|
+
while (Date.now() < deadline) {
|
|
599
|
+
try {
|
|
600
|
+
const res = await browser.send("Runtime.evaluate", { expression: expr, returnByValue: true });
|
|
601
|
+
if (res?.result?.value === true) return true;
|
|
602
|
+
} catch (e) {
|
|
603
|
+
lastErr = e;
|
|
604
|
+
}
|
|
605
|
+
await sleep(intervalMs);
|
|
606
|
+
}
|
|
607
|
+
throw new import_n8n_workflow.NodeOperationError(
|
|
608
|
+
node,
|
|
609
|
+
`waitForSelector("${selector}") timed out after ${timeoutMs}ms${lastErr ? ": " + lastErr.message : ""}`
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
async function extractHtml(browser, selector) {
|
|
613
|
+
const expr = selector.trim() === "" || selector === "body" ? "document.body ? document.body.outerHTML : ''" : "(function(){ var el = document.querySelector(" + JSON.stringify(selector) + "); return el ? el.outerHTML : ''; })()";
|
|
614
|
+
const res = await browser.send("Runtime.evaluate", { expression: expr, returnByValue: true });
|
|
615
|
+
return res?.result?.value ?? "";
|
|
616
|
+
}
|
|
440
617
|
var sleep = (ms) => new Promise((resolve) => {
|
|
441
618
|
AbortSignal.timeout(ms).addEventListener("abort", () => resolve(), { once: true });
|
|
442
619
|
});
|
|
@@ -445,14 +622,18 @@ var BrowserCeki = class {
|
|
|
445
622
|
this.description = {
|
|
446
623
|
displayName: "Browser Ceki",
|
|
447
624
|
name: "browserCeki",
|
|
448
|
-
icon:
|
|
625
|
+
icon: {
|
|
626
|
+
light: "file:ceki-light.svg",
|
|
627
|
+
dark: "file:ceki-dark.svg"
|
|
628
|
+
},
|
|
449
629
|
group: ["transform"],
|
|
450
630
|
version: 1,
|
|
631
|
+
usableAsTool: true,
|
|
451
632
|
subtitle: '={{ "Ceki: " + $parameter.operation }}',
|
|
452
633
|
description: "Rent a real human browser and control it: rent, navigate, click, type, screenshot, solve captchas, and more",
|
|
453
634
|
defaults: { name: "Browser Ceki" },
|
|
454
|
-
inputs: [
|
|
455
|
-
outputs: [
|
|
635
|
+
inputs: [import_n8n_workflow.NodeConnectionTypes.Main],
|
|
636
|
+
outputs: [import_n8n_workflow.NodeConnectionTypes.Main],
|
|
456
637
|
credentials: [{ name: "cekiApi", required: true }],
|
|
457
638
|
properties: [
|
|
458
639
|
{
|
|
@@ -460,22 +641,38 @@ var BrowserCeki = class {
|
|
|
460
641
|
name: "operation",
|
|
461
642
|
type: "options",
|
|
462
643
|
default: "search",
|
|
644
|
+
noDataExpression: true,
|
|
463
645
|
options: [
|
|
464
|
-
|
|
465
|
-
{ name: "
|
|
466
|
-
{ name: "Navigate", value: "navigate" },
|
|
646
|
+
// Browser operations
|
|
647
|
+
{ name: "Captcha-Protected Scrape", value: "captchaScrape" },
|
|
467
648
|
{ name: "Click", value: "click" },
|
|
468
|
-
{ name: "Type", value: "type" },
|
|
469
|
-
{ name: "Scroll", value: "scroll" },
|
|
470
|
-
{ name: "Screenshot", value: "screenshot" },
|
|
471
|
-
{ name: "Snapshot", value: "snapshot" },
|
|
472
|
-
{ name: "Wait", value: "wait" },
|
|
473
|
-
{ name: "Wait for Selector", value: "waitForSelector" },
|
|
474
|
-
{ name: "Upload", value: "upload" },
|
|
475
649
|
{ name: "Close", value: "close" },
|
|
650
|
+
// Contract operations
|
|
651
|
+
{ name: "Contract: Assign Executor", value: "contract_assign" },
|
|
652
|
+
{ name: "Contract: Call Human", value: "contract_callHuman" },
|
|
653
|
+
{ name: "Contract: Comment", value: "contract_comment" },
|
|
654
|
+
{ name: "Contract: Create Task", value: "contract_create" },
|
|
655
|
+
{ name: "Contract: Get Task", value: "contract_get" },
|
|
656
|
+
{ name: "Contract: List My Contracts", value: "contract_list" },
|
|
657
|
+
{ name: "Contract: List Tasks in Contract", value: "contract_tasks" },
|
|
658
|
+
{ name: "Contract: My Assigned Events", value: "contract_myEvents" },
|
|
659
|
+
{ name: "Contract: Poll Notifications", value: "contract_poll" },
|
|
660
|
+
{ name: "Contract: Progress Report", value: "contract_progress" },
|
|
661
|
+
{ name: "Contract: Update Status", value: "contract_setStatus" },
|
|
476
662
|
{ name: "Full: Rent \u2192 Navigate \u2192 Screenshot", value: "full" },
|
|
477
663
|
{ name: "My Sessions", value: "my_sessions" },
|
|
478
|
-
{ name: "
|
|
664
|
+
{ name: "Navigate", value: "navigate" },
|
|
665
|
+
{ name: "Rent", value: "rent" },
|
|
666
|
+
{ name: "Screenshot", value: "screenshot" },
|
|
667
|
+
{ name: "Screenshot in Geo", value: "screenshotGeo" },
|
|
668
|
+
{ name: "Scroll", value: "scroll" },
|
|
669
|
+
{ name: "Search", value: "search" },
|
|
670
|
+
{ name: "Snapshot", value: "snapshot" },
|
|
671
|
+
{ name: "Stop Session", value: "stop_session" },
|
|
672
|
+
{ name: "Type", value: "type" },
|
|
673
|
+
{ name: "Upload", value: "upload" },
|
|
674
|
+
{ name: "Wait", value: "wait" },
|
|
675
|
+
{ name: "Wait for Selector", value: "waitForSelector" }
|
|
479
676
|
]
|
|
480
677
|
},
|
|
481
678
|
// === Rent: rental parameters ===
|
|
@@ -493,31 +690,31 @@ var BrowserCeki = class {
|
|
|
493
690
|
type: "string",
|
|
494
691
|
default: "",
|
|
495
692
|
placeholder: "RU, EE, US\u2026",
|
|
496
|
-
displayOptions: { show: { operation: ["search"] } }
|
|
693
|
+
displayOptions: { show: { operation: ["search", "captchaScrape", "screenshotGeo"] } }
|
|
497
694
|
},
|
|
498
695
|
{
|
|
499
|
-
displayName: "Max $/
|
|
696
|
+
displayName: "Max $/Min",
|
|
500
697
|
name: "maxPrice",
|
|
501
698
|
type: "number",
|
|
502
699
|
typeOptions: { numberPrecision: 4 },
|
|
503
700
|
default: 0.02,
|
|
504
|
-
displayOptions: { show: { operation: ["search"] } }
|
|
701
|
+
displayOptions: { show: { operation: ["search", "captchaScrape", "screenshotGeo"] } }
|
|
505
702
|
},
|
|
506
703
|
{
|
|
507
|
-
displayName: "Min
|
|
704
|
+
displayName: "Min Rating",
|
|
508
705
|
name: "minRating",
|
|
509
706
|
type: "number",
|
|
510
707
|
default: 0,
|
|
511
|
-
displayOptions: { show: { operation: ["search"] } }
|
|
708
|
+
displayOptions: { show: { operation: ["search", "captchaScrape", "screenshotGeo"] } }
|
|
512
709
|
},
|
|
513
710
|
{
|
|
514
|
-
displayName: "Profile
|
|
711
|
+
displayName: "Profile Mode",
|
|
515
712
|
name: "mode",
|
|
516
713
|
type: "options",
|
|
517
714
|
default: "incognito",
|
|
518
715
|
options: [
|
|
519
|
-
{ name: "
|
|
520
|
-
{ name: "
|
|
716
|
+
{ name: "Incognito", value: "incognito" },
|
|
717
|
+
{ name: "Main", value: "main" }
|
|
521
718
|
],
|
|
522
719
|
displayOptions: { show: { operation: ["rent", "full"] } }
|
|
523
720
|
},
|
|
@@ -530,80 +727,278 @@ var BrowserCeki = class {
|
|
|
530
727
|
displayOptions: { show: { operation: ["navigate", "full"] } }
|
|
531
728
|
},
|
|
532
729
|
{
|
|
533
|
-
displayName: "Demo
|
|
730
|
+
displayName: "Demo Mode (No Browser Needed)",
|
|
534
731
|
name: "demoMode",
|
|
535
732
|
type: "boolean",
|
|
536
733
|
default: true,
|
|
537
|
-
description: "
|
|
734
|
+
description: "Whether to skip actual browser rent and generate demo output",
|
|
538
735
|
displayOptions: { show: { operation: ["full"] } }
|
|
539
736
|
},
|
|
540
|
-
// ===
|
|
541
|
-
{
|
|
542
|
-
displayName: "Session ID",
|
|
543
|
-
name: "sessionId",
|
|
544
|
-
type: "string",
|
|
545
|
-
default: "",
|
|
546
|
-
description: "From the Rent operation",
|
|
547
|
-
required: true,
|
|
548
|
-
displayOptions: {
|
|
549
|
-
show: {
|
|
550
|
-
operation: ["navigate", "click", "type", "scroll", "screenshot", "snapshot", "wait", "waitForSelector", "upload", "close", "stop_session"]
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
},
|
|
737
|
+
// === Captcha-protected Scrape params ===
|
|
554
738
|
{
|
|
555
|
-
displayName: "URL",
|
|
739
|
+
displayName: "Captcha URL",
|
|
556
740
|
name: "url",
|
|
557
741
|
type: "string",
|
|
558
|
-
default: "",
|
|
742
|
+
default: "https://example.com",
|
|
559
743
|
required: true,
|
|
560
|
-
|
|
744
|
+
description: "Page protected by anti-bot / captcha",
|
|
745
|
+
displayOptions: { show: { operation: ["captchaScrape"] } }
|
|
561
746
|
},
|
|
562
747
|
{
|
|
563
|
-
displayName: "
|
|
564
|
-
name: "
|
|
565
|
-
type: "
|
|
566
|
-
default:
|
|
567
|
-
|
|
748
|
+
displayName: "Geo",
|
|
749
|
+
name: "geo",
|
|
750
|
+
type: "string",
|
|
751
|
+
default: "RU",
|
|
752
|
+
placeholder: "RU, EE, US\u2026",
|
|
753
|
+
displayOptions: { show: { operation: ["captchaScrape"] } }
|
|
568
754
|
},
|
|
569
755
|
{
|
|
570
|
-
displayName: "
|
|
571
|
-
name: "
|
|
756
|
+
displayName: "Max $/Min",
|
|
757
|
+
name: "maxPrice",
|
|
572
758
|
type: "number",
|
|
573
|
-
|
|
574
|
-
|
|
759
|
+
typeOptions: { numberPrecision: 4 },
|
|
760
|
+
default: 0.02,
|
|
761
|
+
displayOptions: { show: { operation: ["captchaScrape"] } }
|
|
575
762
|
},
|
|
576
763
|
{
|
|
577
|
-
displayName: "
|
|
578
|
-
name: "
|
|
764
|
+
displayName: "Wait for Selector",
|
|
765
|
+
name: "waitSelector",
|
|
579
766
|
type: "string",
|
|
580
767
|
default: "",
|
|
581
|
-
|
|
768
|
+
placeholder: "CSS selector (optional)",
|
|
769
|
+
description: "Wait until this selector appears in the DOM",
|
|
770
|
+
displayOptions: { show: { operation: ["captchaScrape"] } }
|
|
582
771
|
},
|
|
583
772
|
{
|
|
584
|
-
displayName: "
|
|
585
|
-
name: "
|
|
773
|
+
displayName: "Wait Timeout (Ms)",
|
|
774
|
+
name: "waitTimeout",
|
|
586
775
|
type: "number",
|
|
587
|
-
default:
|
|
588
|
-
displayOptions: { show: { operation: ["
|
|
776
|
+
default: 3e4,
|
|
777
|
+
displayOptions: { show: { operation: ["captchaScrape"] } }
|
|
589
778
|
},
|
|
590
779
|
{
|
|
591
|
-
displayName: "
|
|
592
|
-
name: "
|
|
593
|
-
type: "
|
|
594
|
-
default:
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
780
|
+
displayName: "Extract HTML",
|
|
781
|
+
name: "extractHtml",
|
|
782
|
+
type: "boolean",
|
|
783
|
+
default: true,
|
|
784
|
+
displayOptions: { show: { operation: ["captchaScrape"] } }
|
|
785
|
+
},
|
|
786
|
+
{
|
|
787
|
+
displayName: "HTML Selector",
|
|
788
|
+
name: "htmlSelector",
|
|
789
|
+
type: "string",
|
|
790
|
+
default: "body",
|
|
791
|
+
placeholder: 'CSS selector or "body"',
|
|
792
|
+
description: "OuterHTML of this selector is returned as html",
|
|
793
|
+
displayOptions: { show: { operation: ["captchaScrape"] } }
|
|
600
794
|
},
|
|
601
795
|
{
|
|
602
|
-
displayName: "Full
|
|
796
|
+
displayName: "Full Page Screenshot",
|
|
603
797
|
name: "fullPage",
|
|
604
798
|
type: "boolean",
|
|
605
799
|
default: false,
|
|
606
|
-
displayOptions: { show: { operation: ["
|
|
800
|
+
displayOptions: { show: { operation: ["captchaScrape"] } }
|
|
801
|
+
},
|
|
802
|
+
// === Screenshot in Geo params ===
|
|
803
|
+
{
|
|
804
|
+
displayName: "Geo URL",
|
|
805
|
+
name: "url",
|
|
806
|
+
type: "string",
|
|
807
|
+
default: "https://ifconfig.me",
|
|
808
|
+
required: true,
|
|
809
|
+
displayOptions: { show: { operation: ["screenshotGeo"] } }
|
|
810
|
+
},
|
|
811
|
+
{
|
|
812
|
+
displayName: "Geo",
|
|
813
|
+
name: "geo",
|
|
814
|
+
type: "string",
|
|
815
|
+
default: "RU",
|
|
816
|
+
placeholder: "RU, EE, US\u2026",
|
|
817
|
+
displayOptions: { show: { operation: ["screenshotGeo"] } }
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
displayName: "Full Page Screenshot",
|
|
821
|
+
name: "fullPage",
|
|
822
|
+
type: "boolean",
|
|
823
|
+
default: false,
|
|
824
|
+
displayOptions: { show: { operation: ["screenshotGeo"] } }
|
|
825
|
+
},
|
|
826
|
+
{
|
|
827
|
+
displayName: "Max $/Min",
|
|
828
|
+
name: "maxPrice",
|
|
829
|
+
type: "number",
|
|
830
|
+
default: 0.02,
|
|
831
|
+
displayOptions: { show: { operation: ["screenshotGeo"] } }
|
|
832
|
+
},
|
|
833
|
+
// === Contract params ===
|
|
834
|
+
{
|
|
835
|
+
displayName: "Contract ID",
|
|
836
|
+
name: "contractId",
|
|
837
|
+
type: "number",
|
|
838
|
+
default: 0,
|
|
839
|
+
description: "Ceki contract ID",
|
|
840
|
+
displayOptions: { show: { operation: ["contract_tasks", "contract_create"] } }
|
|
841
|
+
},
|
|
842
|
+
{
|
|
843
|
+
displayName: "Event ID",
|
|
844
|
+
name: "eventId",
|
|
845
|
+
type: "number",
|
|
846
|
+
default: 0,
|
|
847
|
+
description: "Task / event ID (KalEvent)",
|
|
848
|
+
displayOptions: {
|
|
849
|
+
show: { operation: ["contract_get", "contract_assign", "contract_setStatus", "contract_comment", "contract_progress", "contract_callHuman"] }
|
|
850
|
+
}
|
|
851
|
+
},
|
|
852
|
+
{
|
|
853
|
+
displayName: "Label",
|
|
854
|
+
name: "label",
|
|
855
|
+
type: "string",
|
|
856
|
+
default: "",
|
|
857
|
+
required: true,
|
|
858
|
+
displayOptions: { show: { operation: ["contract_create"] } }
|
|
859
|
+
},
|
|
860
|
+
{
|
|
861
|
+
displayName: "Description",
|
|
862
|
+
name: "description",
|
|
863
|
+
type: "string",
|
|
864
|
+
typeOptions: { rows: 4 },
|
|
865
|
+
default: "",
|
|
866
|
+
displayOptions: { show: { operation: ["contract_create", "contract_comment"] } }
|
|
867
|
+
},
|
|
868
|
+
{
|
|
869
|
+
displayName: "Executor Type",
|
|
870
|
+
name: "benefitableType",
|
|
871
|
+
type: "options",
|
|
872
|
+
default: "agent",
|
|
873
|
+
options: [
|
|
874
|
+
{ name: "Agent", value: "agent" },
|
|
875
|
+
{ name: "User (Human)", value: "user" }
|
|
876
|
+
],
|
|
877
|
+
displayOptions: { show: { operation: ["contract_create", "contract_assign"] } }
|
|
878
|
+
},
|
|
879
|
+
{
|
|
880
|
+
displayName: "Executor ID",
|
|
881
|
+
name: "benefitableValue",
|
|
882
|
+
type: "number",
|
|
883
|
+
default: 0,
|
|
884
|
+
description: "Agent ID or user ID of the executor",
|
|
885
|
+
displayOptions: { show: { operation: ["contract_create", "contract_assign"] } }
|
|
886
|
+
},
|
|
887
|
+
{
|
|
888
|
+
displayName: "Status",
|
|
889
|
+
name: "status",
|
|
890
|
+
type: "options",
|
|
891
|
+
options: [
|
|
892
|
+
{ name: "100 \xB7 Backlog", value: 100 },
|
|
893
|
+
{ name: "200 \xB7 Hand (Assigned)", value: 200 },
|
|
894
|
+
{ name: "222 \xB7 Hand Done", value: 222 },
|
|
895
|
+
{ name: "300 \xB7 QA", value: 300 },
|
|
896
|
+
{ name: "350 \xB7 QA Done", value: 350 },
|
|
897
|
+
{ name: "499 \xB7 Reviewer", value: 499 }
|
|
898
|
+
],
|
|
899
|
+
default: 200,
|
|
900
|
+
displayOptions: { show: { operation: ["contract_create", "contract_setStatus", "contract_progress"] } }
|
|
901
|
+
},
|
|
902
|
+
{
|
|
903
|
+
displayName: "Progress Description",
|
|
904
|
+
name: "progressDesc",
|
|
905
|
+
type: "string",
|
|
906
|
+
typeOptions: { rows: 4 },
|
|
907
|
+
default: "",
|
|
908
|
+
required: true,
|
|
909
|
+
description: "Body of the progress comment (does not overwrite the task spec)",
|
|
910
|
+
displayOptions: { show: { operation: ["contract_progress"] } }
|
|
911
|
+
},
|
|
912
|
+
{
|
|
913
|
+
displayName: "Call Kind",
|
|
914
|
+
name: "callKind",
|
|
915
|
+
type: "options",
|
|
916
|
+
default: "review",
|
|
917
|
+
options: [
|
|
918
|
+
{ name: "Input (Need Clarification)", value: "input" },
|
|
919
|
+
{ name: "Review (Done, Take a Look)", value: "review" },
|
|
920
|
+
{ name: "Stuck (Blocked)", value: "stuck" }
|
|
921
|
+
],
|
|
922
|
+
description: "Type of escalation to a human (the call-human action)",
|
|
923
|
+
displayOptions: { show: { operation: ["contract_callHuman"] } }
|
|
924
|
+
},
|
|
925
|
+
{
|
|
926
|
+
displayName: "Message",
|
|
927
|
+
name: "callDesc",
|
|
928
|
+
type: "string",
|
|
929
|
+
typeOptions: { rows: 4 },
|
|
930
|
+
default: "",
|
|
931
|
+
required: true,
|
|
932
|
+
description: "What to tell the human \u2014 context, question, or what was done",
|
|
933
|
+
displayOptions: { show: { operation: ["contract_callHuman"] } }
|
|
934
|
+
},
|
|
935
|
+
// === Operations: session_id ===
|
|
936
|
+
{
|
|
937
|
+
displayName: "Session ID",
|
|
938
|
+
name: "sessionId",
|
|
939
|
+
type: "string",
|
|
940
|
+
default: "",
|
|
941
|
+
description: "From the Rent operation",
|
|
942
|
+
required: true,
|
|
943
|
+
displayOptions: {
|
|
944
|
+
show: {
|
|
945
|
+
operation: ["navigate", "click", "type", "scroll", "screenshot", "snapshot", "wait", "waitForSelector", "upload", "close", "stop_session"]
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
},
|
|
949
|
+
{
|
|
950
|
+
displayName: "URL",
|
|
951
|
+
name: "url",
|
|
952
|
+
type: "string",
|
|
953
|
+
default: "",
|
|
954
|
+
required: true,
|
|
955
|
+
displayOptions: { show: { operation: ["navigate"] } }
|
|
956
|
+
},
|
|
957
|
+
{
|
|
958
|
+
displayName: "X",
|
|
959
|
+
name: "x",
|
|
960
|
+
type: "number",
|
|
961
|
+
default: 0,
|
|
962
|
+
displayOptions: { show: { operation: ["click"] } }
|
|
963
|
+
},
|
|
964
|
+
{
|
|
965
|
+
displayName: "Y",
|
|
966
|
+
name: "y",
|
|
967
|
+
type: "number",
|
|
968
|
+
default: 0,
|
|
969
|
+
displayOptions: { show: { operation: ["click"] } }
|
|
970
|
+
},
|
|
971
|
+
{
|
|
972
|
+
displayName: "Text",
|
|
973
|
+
name: "text",
|
|
974
|
+
type: "string",
|
|
975
|
+
default: "",
|
|
976
|
+
displayOptions: { show: { operation: ["type"] } }
|
|
977
|
+
},
|
|
978
|
+
{
|
|
979
|
+
displayName: "Delta Y",
|
|
980
|
+
name: "deltaY",
|
|
981
|
+
type: "number",
|
|
982
|
+
default: -300,
|
|
983
|
+
displayOptions: { show: { operation: ["scroll"] } }
|
|
984
|
+
},
|
|
985
|
+
{
|
|
986
|
+
displayName: "Format",
|
|
987
|
+
name: "format",
|
|
988
|
+
type: "options",
|
|
989
|
+
default: "png",
|
|
990
|
+
options: [
|
|
991
|
+
{ name: "Base64", value: "base64" },
|
|
992
|
+
{ name: "PNG (Binary)", value: "png" }
|
|
993
|
+
],
|
|
994
|
+
displayOptions: { show: { operation: ["screenshot"] } }
|
|
995
|
+
},
|
|
996
|
+
{
|
|
997
|
+
displayName: "Full Page",
|
|
998
|
+
name: "fullPage",
|
|
999
|
+
type: "boolean",
|
|
1000
|
+
default: false,
|
|
1001
|
+
displayOptions: { show: { operation: ["screenshot"] } }
|
|
607
1002
|
},
|
|
608
1003
|
{
|
|
609
1004
|
displayName: "Milliseconds",
|
|
@@ -624,7 +1019,7 @@ var BrowserCeki = class {
|
|
|
624
1019
|
displayOptions: { show: { operation: ["waitForSelector"] } }
|
|
625
1020
|
},
|
|
626
1021
|
{
|
|
627
|
-
displayName: "Timeout (
|
|
1022
|
+
displayName: "Timeout (Ms)",
|
|
628
1023
|
name: "waitTimeout",
|
|
629
1024
|
type: "number",
|
|
630
1025
|
default: 3e4,
|
|
@@ -654,6 +1049,40 @@ var BrowserCeki = class {
|
|
|
654
1049
|
const out = [];
|
|
655
1050
|
const creds = await this.getCredentials("cekiApi");
|
|
656
1051
|
const token = creds.token;
|
|
1052
|
+
const node = this.getNode();
|
|
1053
|
+
const touchedSessions = /* @__PURE__ */ new Set();
|
|
1054
|
+
const _stopSession = async (sid) => {
|
|
1055
|
+
await new Promise((resolve) => {
|
|
1056
|
+
const stopWs = new WebSocket("wss://browser.ceki.me/ws/agent", ["bearer." + token]);
|
|
1057
|
+
const _t = AbortSignal.timeout(1e4);
|
|
1058
|
+
const _done = () => {
|
|
1059
|
+
try {
|
|
1060
|
+
stopWs.close();
|
|
1061
|
+
} catch {
|
|
1062
|
+
}
|
|
1063
|
+
resolve();
|
|
1064
|
+
};
|
|
1065
|
+
_t.addEventListener("abort", _done, { once: true });
|
|
1066
|
+
stopWs.onopen = () => {
|
|
1067
|
+
try {
|
|
1068
|
+
stopWs.send(JSON.stringify({ type: "stop", session_id: sid, reason: "n8n error" }));
|
|
1069
|
+
} catch {
|
|
1070
|
+
}
|
|
1071
|
+
};
|
|
1072
|
+
stopWs.onmessage = (ev) => {
|
|
1073
|
+
try {
|
|
1074
|
+
const m = JSON.parse(ev.data);
|
|
1075
|
+
if (m.type === "session_ended") {
|
|
1076
|
+
_t.removeEventListener("abort", _done);
|
|
1077
|
+
_done();
|
|
1078
|
+
}
|
|
1079
|
+
} catch {
|
|
1080
|
+
}
|
|
1081
|
+
};
|
|
1082
|
+
stopWs.onerror = _done;
|
|
1083
|
+
stopWs.onclose = _done;
|
|
1084
|
+
});
|
|
1085
|
+
};
|
|
657
1086
|
const resolveSid = async (i, client) => {
|
|
658
1087
|
const scheduleId = this.getNodeParameter("scheduleId", i);
|
|
659
1088
|
if (scheduleId) return scheduleId;
|
|
@@ -663,943 +1092,527 @@ var BrowserCeki = class {
|
|
|
663
1092
|
geo: geo || void 0,
|
|
664
1093
|
max_price_per_min: maxPrice
|
|
665
1094
|
});
|
|
666
|
-
if (!list.length) throw new
|
|
1095
|
+
if (!list.length) throw new import_n8n_workflow.NodeOperationError(node, "No browsers found by filters");
|
|
667
1096
|
return list[0].schedule_id;
|
|
668
1097
|
};
|
|
669
1098
|
for (let i = 0; i < items.length; i++) {
|
|
670
|
-
const op = this.getNodeParameter("operation", i);
|
|
671
|
-
const client = new CekiClient(token);
|
|
672
|
-
let browser;
|
|
673
|
-
let needFullClose = false;
|
|
674
1099
|
try {
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
1100
|
+
const op = this.getNodeParameter("operation", i);
|
|
1101
|
+
const client = new CekiClient(token);
|
|
1102
|
+
let browser;
|
|
1103
|
+
let needFullClose = false;
|
|
1104
|
+
try {
|
|
1105
|
+
await client.connect();
|
|
1106
|
+
if (op === "rent") {
|
|
1107
|
+
try {
|
|
1108
|
+
const sid2 = await resolveSid(i, client);
|
|
1109
|
+
const mode = this.getNodeParameter("mode", i);
|
|
1110
|
+
browser = await client.rent(sid2, { mode });
|
|
1111
|
+
out.push({
|
|
1112
|
+
json: { session_id: browser.sessionId, schedule_id: sid2, mode },
|
|
1113
|
+
pairedItem: { item: i }
|
|
1114
|
+
});
|
|
1115
|
+
} catch (e) {
|
|
1116
|
+
throw new import_n8n_workflow.NodeApiError(node, e, { itemIndex: i, message: "Rent failed" });
|
|
1117
|
+
} finally {
|
|
1118
|
+
await client.disconnect();
|
|
1119
|
+
}
|
|
1120
|
+
continue;
|
|
688
1121
|
}
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
})
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
1122
|
+
if (op === "search") {
|
|
1123
|
+
try {
|
|
1124
|
+
const geo = this.getNodeParameter("geo", i);
|
|
1125
|
+
const maxPrice = this.getNodeParameter("maxPrice", i);
|
|
1126
|
+
const list = await client.search({
|
|
1127
|
+
geo: geo || void 0,
|
|
1128
|
+
max_price_per_min: maxPrice
|
|
1129
|
+
});
|
|
1130
|
+
out.push({ json: { browsers: list, count: list.length }, pairedItem: { item: i } });
|
|
1131
|
+
} catch (e) {
|
|
1132
|
+
throw new import_n8n_workflow.NodeApiError(node, e, { itemIndex: i, message: "Search failed" });
|
|
1133
|
+
} finally {
|
|
1134
|
+
await client.disconnect();
|
|
1135
|
+
}
|
|
1136
|
+
continue;
|
|
704
1137
|
}
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
1138
|
+
if (op === "my_sessions") {
|
|
1139
|
+
try {
|
|
1140
|
+
const resp = await fetch("https://api.ceki.me/api/agent/sessions", {
|
|
1141
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
1142
|
+
});
|
|
1143
|
+
if (!resp.ok) throw new import_n8n_workflow.NodeApiError(node, void 0, { itemIndex: i, message: `HTTP ${resp.status}`, httpCode: `${resp.status}` });
|
|
1144
|
+
const body = await resp.json();
|
|
1145
|
+
const sessions = body.data ?? [];
|
|
1146
|
+
out.push({ json: { sessions, count: sessions.length }, pairedItem: { item: i } });
|
|
1147
|
+
} catch (e) {
|
|
1148
|
+
throw new import_n8n_workflow.NodeApiError(node, e, { itemIndex: i, message: "My sessions failed" });
|
|
1149
|
+
}
|
|
1150
|
+
continue;
|
|
718
1151
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
const t = AbortSignal.timeout(15e3);
|
|
727
|
-
t.addEventListener("abort", () => {
|
|
728
|
-
try {
|
|
729
|
-
ws.close();
|
|
730
|
-
} catch {
|
|
731
|
-
}
|
|
732
|
-
reject(new Error("Stop session timed out"));
|
|
733
|
-
}, { once: true });
|
|
734
|
-
ws.onopen = () => {
|
|
735
|
-
ws.send(JSON.stringify({ type: "stop", session_id: sessionId2, reason: "n8n stop_session" }));
|
|
736
|
-
};
|
|
737
|
-
ws.onmessage = (ev) => {
|
|
738
|
-
const msg = JSON.parse(ev.data);
|
|
739
|
-
if (msg.type === "session_ended" || msg.type === "pong") {
|
|
1152
|
+
if (op === "stop_session") {
|
|
1153
|
+
const sessionId2 = this.getNodeParameter("sessionId", i);
|
|
1154
|
+
try {
|
|
1155
|
+
await new Promise((resolve, reject) => {
|
|
1156
|
+
const ws = new WebSocket("wss://browser.ceki.me/ws/agent", [`bearer.${token}`]);
|
|
1157
|
+
const t = AbortSignal.timeout(15e3);
|
|
1158
|
+
t.addEventListener("abort", () => {
|
|
740
1159
|
try {
|
|
741
1160
|
ws.close();
|
|
742
1161
|
} catch {
|
|
743
1162
|
}
|
|
744
|
-
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
sid2 = await resolveSid(i, client);
|
|
1163
|
+
reject(new import_n8n_workflow.NodeApiError(node, void 0, { itemIndex: i, message: "Stop session timed out" }));
|
|
1164
|
+
}, { once: true });
|
|
1165
|
+
ws.onopen = () => {
|
|
1166
|
+
ws.send(JSON.stringify({ type: "stop", session_id: sessionId2, reason: "n8n stop_session" }));
|
|
1167
|
+
};
|
|
1168
|
+
ws.onmessage = (ev) => {
|
|
1169
|
+
const msg = JSON.parse(ev.data);
|
|
1170
|
+
if (msg.type === "session_ended" || msg.type === "pong") {
|
|
1171
|
+
try {
|
|
1172
|
+
ws.close();
|
|
1173
|
+
} catch {
|
|
1174
|
+
}
|
|
1175
|
+
resolve();
|
|
1176
|
+
}
|
|
1177
|
+
};
|
|
1178
|
+
ws.onerror = () => {
|
|
1179
|
+
reject(new import_n8n_workflow.NodeApiError(node, void 0, { itemIndex: i, message: "WebSocket connection failed" }));
|
|
1180
|
+
};
|
|
1181
|
+
});
|
|
1182
|
+
out.push({ json: { stopped: true, session_id: sessionId2 }, pairedItem: { item: i } });
|
|
765
1183
|
} catch (e) {
|
|
766
|
-
throw new
|
|
1184
|
+
throw new import_n8n_workflow.NodeApiError(node, e, { itemIndex: i, message: "Stop session failed" });
|
|
767
1185
|
}
|
|
1186
|
+
continue;
|
|
768
1187
|
}
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
let session_id;
|
|
773
|
-
let binary;
|
|
774
|
-
try {
|
|
1188
|
+
if (op === "full") {
|
|
1189
|
+
const demoMode = this.getNodeParameter("demoMode", i);
|
|
1190
|
+
let sid2;
|
|
775
1191
|
if (demoMode) {
|
|
776
|
-
|
|
777
|
-
const demoPng = Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAlgAAAGQCAIAAAD9V4nPAAAnuElEQVR4nO3ceVxU9f748feZhU0RxRUTXEEWFXG3Rc3MJb3mUlrueivzVvebVre+38ql7d5bqZmZlZV4UUvLUtPUUtOKUAREBRVkEVQQxQ2UdWbO74+p+XGZAWlBqs/r+ddhOJ/lHHrw6sCIFhDQRQAAUJWhrjcAAEBdIoQAAKURQgCA0gghAEBphBAAoDRCCABQGiEEACiNEAIAlEYIAQBKI4QAAKURQgCA0gghAEBphBAAoDRCCABQGiEEACiNEAIAlEYIAQBKI4QAAKURQgCA0gghAEBphBAAoDRCCABQGiEEACiNEAIAlEYIAQBKI4QAAKURQgCA0gghAEBphBAAoDRCCABQGiEEACjNVKuzD1odeyXtiGiaybN+2rpl5+P31upydq3uGOs/eJyl5Jq1pOjoey+VXDjr8rSBkdG7p93i+LC+f/tGId1PfbXe8Yp987ouBpPp9K7PcvZsqvWtAwBuuNoNoc1SfmD+X0XEu3XHiKeX3IAQNu7St8Wtw/Y/P8VWVtok4tZOj7wY98KDNRl49VT61VPpFV9xbN7o7hnx9JvW0uK8mK9qZdMAgLpTuyF0KMxO1a1WERkYGX0udldB5vG8mK/CZi0wenhZS4qSl8/rueDD+JdmFp/P7f7sO9dyMo+v/LdvWI9Wg+69nJp40+2jRddT175RkJYcPOMZ94ZNNJM5NWrhlbSkihNmb1srIm3+MvXE2jdtZaUikn/w+2a9BmpGk8nDy3mgnZuPb4/n3zu85Omrp9IrPSM6WEuLU1cvDp7+9MXD+1xuIG//Tt/QHic3RzYMjmjYsWv2to+ytka5N2xS8QJLL+ffmFsNAPhZblAIfTv1Oh75qogYzObc6O0XDv3Q+e//PBu9LefbLS37jQiaPCc/MbpRSPeS/C/FoHm36SgijUK65yd+HzT5ie8fG+7u26zt6Ada9B2Svf2jKyeOeDTxi3h6ScxT4ypOaF+ovn/7wpPHHeseffcFEQmaPMd5oIgYTOYuj796PPLVSs+CzgqzUr1a+Lucx2B2O73z0/RP3um3bNv+Zyelfby010urs7ZGBU15ouIFHln6f7/9bQUA/Gq1G0KDydxz/gcGs1uD9p0uJu0/H79Xt9kuHt4nIr5hPZOXzxORszFfBU58PPmd+c173VGYlVqYedy7dUeTZ71GId1Pff2pb6denR596dSO9UlvPdtv+VdeLfztMxvdPTWDQbfZHBP+tKSLt/80Dr/ZeaCIhPz1/3K/23oxKfa6F6IZjTaLxfU8ul6QnqzbbDZLeUH6UV23Gd09nC/wF99DAECtukG/I6wfENjrhUgR0a1WXbeJiIhW8cyLyQcCJ/y9YVD4peMHrWWljUJ7GMxuZVcuJC17vlFI99bDJ/ndOkwzGuNf/putvFTTDA2DI+wxqzChiEhRbpZ3m45XThwREdG0Tn97MWnZcy4HGszm+gEdROTM7s+veyE+HTpfzT7h3TbYeR6bpfzHg/KyijupdIEAgN+nG/TPJ8oLLxflnar4ysXk2OZ97hSR5n3uvJh8wFZWWnb5QrPed1xOSbx8/GDrEVMuHo0zedXvueDDy6mHjiz9vyYRt11OSWzWa6CINIm4pe3ov7pc6NSOdR3ue9RgdhORFjcPtR+4HGgrL499bqpHs5at7hhb/ebN9RoETXz85OaVNdlAVRd4/XsEAKgLN+JHo7qui8jR916s+KnUqEVhsxa0GnSPtbQ4eflcEclPjL7pjrHlhZevnDjcKKRb2rq3LEVXz8d/2/uV1ZpmyNjw3rkD34TOnOs/+F7dak1+d4HLFc/+sMPLr3Wff31cVnCprODisfdfFpGUyNdcDtR125Elz/R+eXVhVkrFd9D89+bFYDJlbvrwYnJc0dnT191ANRcIAPgd0gICutT1HgAAqDP8ZRkAgNIIIQBAaYQQAKA0QggAUBohBAAojRACAJRGCAEASiOEAAClEUIAgNIIIQBAaYQQAKA0QggAUBohBAAojRACAJRGCAEASiOEAAClEUIAgNIIIQBAaYQQAKA0QggAUBohBAAojRACAJRGCAEASiOEAAClEUIAgNIIIQBAaYQQAKA0QggAUBohBAAojRACAJRGCAEASiOEAAClEUIAgNIIIQBAaYQQAKA0QggAUBohBAAojRACAJRGCAEASiOEAAClEUIAgNIIIQBAaYQQAKC0Wg3hypXLP/305Q0b3l+1atn48Q9169YlJKRbevrJTz9d9cknpfv3i8gvvZzde++9lJZ2UtM0o9HYpUuHL7/8+meN+vTTl1esWKppmtFo7NSp/aefrvq5o/r163P11BlZ5Omnn7pwoXDkyDsNBkPFl2JiYtLTs0SkbdsAg8GQnX1m6tR7R49+8LqjFiyYn5iYLCMmCdf3AAAPyElEQVT3f+DAwZ07v922bZeI3HZbnyeffFi3nU40Xb165dy5Tw4ePBiP7wDwc1MHhFA87LbbQ0S6du3YteufFi58d+3az444+QcPHszNPZeami4izZo1iYlJEBGTyZiTU/rrrwz86QoCAr7//vvffOVeImI0Gnx8GtTWcgAQd7/fI5mYxZEjaUVFRX369GjSpLGIZGZmP/XUI4sXz69mQu2+l+Cdd17+4IM14h7Q6MmXl2/fvnvMmIfsL3333cHMzFMiYjabd+3aLyJ9+nR7660PIiIi3n33lYiIro88cn1iYlJqapr9Fy+/vDgp6Yj9OCkpafbsGfv2JYjIV1/tFZEBA/rExe1v0KC+yWTy8HAvKCgUkccee3DWrKfsyz3++IMi0qCB99tvf/TCC88dOJB4332jHn10prgH/Pe//3z44T/X1h0DgD+EWguhplmGDbvdYrHk5+cnJma4uzfw8/Np1KjhnXf+yc/PR9O0s2fP7tuX4HJCl0+EmzZ9euzYsT17Ymy21/Ly/n/s3XlYU9f+B/BvQhISloAsYVEQkEURQVktiB2XqlZbq+217VufttV6az989T219vq01k5r62Wq1qXa1qcWByqj4gIqyCqLIIggEFaBsCRk+f3hO0eHSSAO1Pm9nn9w8n3OPfckhPNhOZyT3KNHj/v2HQgIGDppUo2mnT6deetbSssriouVpjxNT09PTq0fPHhQe7rOzs78+PHe+/btv+22oICA4e7u7pGRkUql0sPDY+TIQBERXl5eUVExIuLo6CgizZqeys7ONQwVEe3adTwtTbspIi2tFhEx5M+f//f48eOEQhYA1F/nhXDRok9///3wypU/CgiY/PLLi0TELyRkSk5Orqj0PDwmy5YtHjly+px5HzUOV3shiUSiUqk2bvzH2bMXZkyYOG3aLBMTE01XZpc4ObV6ltMq/2xHtNtaW5vv2rXX1ta2Z8+e8+cvs7GxUSgU+/cfMTE28etlL6bt5ua2Y8f/REQlMo/M/K2ZmeXvvycfO3a8cUumUHe2TwBwczQ2m6VnV4lCoaisVFdXV1dUKCsrKysrq66vN1lfX2dpaSkibHhYLJZd3ctXk9lsnnYlEon8/PiPH3761Km/Nm36JiPjT2dn53379nfu3HnFiu91e2ZmntX9WCajjUQiSUhI1Oo4IiLo9/0EADdMo4tD165dPX/+YlpaRufOnboSEYnI2bMXvbw8e/XqXlhY+OOPO/r27T158qiGhgalUq26du3atWtX/fz6i0hBQcHly5ddXbkmJib3W6+2trbW1lYREYvFEhNbc3MLEWlublapVA0NDY6Ojvt+P5iVlTU7a9Z8G5v2IpKZmUlEXl4eIpKRkVlaWhYSMsXFxcXUtJmpqamHh4etrZ2RkVGXLl3u2Z6ISOXlVxoaGkRkyZJFmzZtNzMza2hoyM09EBAQoC1n+fLQo0eP7927f8uWLZ6eHkQ0YMCAsLCf0tLSfH199+7df/nyFXNz82++Wd+5c+dbLgcA0Ag0R8R4RE+dmmAiY+gbG3Xr1iUl5aj2o5GRkY2NjYODQ3Z29syZ0/LzC+7ZZkZG5pQpE4cOHaJSqRITkxYsSFBVVUVExOubmNUq5fj4CT4+3hER4Q0NDUlJqUuWLPrii4QR6voeHh5hYWG9e/e6fPny1q3fh4VN++yzz42MjD5b99nmzVu3bv3e27uXiLBYLBMTk6NHj3355ZezZ88SEXd3d19fXz4/tKio6OjRY8OG+R0/frK6unrlyuX/+9/eF198MTl5v7Oz8/bte955562oqCiNxiI+ft6GDT8FBAR4e/M8PT26des2fHhAXV1dWlraLbf0pkIhRESjocLCQh42bPhvv+3p3bv3lStX0tJ03+Tq1r9//3feeaewsKhXr16pqWmXLl3Ky8srKioODQ01MTHZtu37xMRES0tLLy/PoUOH6o6h4XoBoAVojiAxHu8wGnXLls0/+eQjfWO7dOn0zz93d8nYGB4Z6ddfd/zzn1+o1dd5Y8IMe/bFf/MaBoPh4eFeWVl5C5cdHh5uYmLKMNzevb+amJhIpdJvv/0vEURF3b1hww+TJ0/64Ydt8fGJs2d/lpCQEBcXN3PmTO0SdXVqR0en0NApInL48JH169dv377d39+/qurql1+upN/XEQBoeYxuWSAo/t6nT5+JiopydnY+ePBQWlp6u38X7gMA6hYhBFjx8bELFizm8XhE9MEH78+dG3NnVwEAf1t4RwgAIH/7NQIAALcDIQQAkEIIIQCEEDI+QggAIYQAEEIIACCEEABC2BwYAAAQEEIACCGEABBCAAAJIQQAkEIIASCEAABCCCEEgBACQEL3WAUAAAKEEABCCAEghBAAQggBIYQAEEIIACGEABBCAAAJIYQAEEIIACGEAABCCCEEgBBCAAghhAAAAYQQAEIIASCEEABACCEEgBBCAAghBIAQQgAIIQQAIYQAEEIIACGEAABCCCEEgBBCAAghBIAQQgAIIQQAIYQAEEIIACGEABBCAAAJIYQAEEIIACGEAABCCCEEgBBCAAghBIAQQgAIIQQAIYQAEEIIACGEABBCAAAJIYQAEEIIACGEAABCCCEEgBBCAAghBIAQQgAIIQQAIYQAEEIIACGEABBCAAAJIYQAEEIIACGEAABCCCEEgBBCAAghBIAQQgAIIQQAIYQAEEIIACGEABBCAAAJIYQAEEIIACGEAABCCCEEgBBCAAghBIAQQgAIIQQAIYQAEEIIACGEABBCAAAJIYQAEEIIACGEAABCCCEEgBBCAAghBIAQQgAIIQQAIYQAEEIIACGEABBCAAAJIYSIEHHBggXdunWLiYk5f/78naxl27aYb7/9loho+vTp77//PpPJvPWl2rVr19DQoE8tk8l0d3d//PHHXVxc9KkNAODe1CofER48ePDChQsODg4LFy5kMpmXL1/esGGDnZ3dmjVrbq2Wy5cvE5GLi4s+NenU4Ovr26NHjxMnsg4ePKBPbQAA96zWO0d44cIFNze3119/3c/Pj8ViiUhVVdXjjz9eU1NzC7Xs2LFDJpMJBILy8nJ9yiouLq6srGQymT/99FN2djYAADzaWohIJBIJBAJPT0+VStXS9Rw9elStVs+ZM8fCwkJfQQsWLJgxY4aentovvvhi586d58+f15mZmZk5atQoJycnIpJKpXFxcRqNZtu2bZ07dx40aJBIJNq3b19AQMDly5f3798/bNgwgUDwzTffFBcXBwcHp6SkHDhwYP/+/U5OTmZmZl999VVNTU1wcPCiRYuOHDkSFhZ25MiRFStWhIeHZ2dnL1y4sLq6ms/n9+7de9WqVVwut6GhIT4+Pjo6+rXXXhs9evTZs2d//PHHV1555Y6+OQCAv52ndl+/fi0iREQajcbGxmbHjh3Dhg2rqKj44IMPrK2te/XqtXv3bn1Keuihhw4dOpScnNyrVy99jpiYmOjoaGMjYy6XS0RqtVomkxUWFkokEg8PDyaTmZ6evmfPHoFAIJPJJk+e/Oabb1706Lx9+/b//Oc/iYmJpqamubm5Fy9efPPNN9euXbtq1ar58+f37dv3wIEDNBpNqVQyGAwiyszMnDx5cnFxsZWVVbdu3XJzc4nowIEDOTk5n376KRGpVKq8vLyvvvoqLy/v1KlTq1evjo+PLy4ufvbZZ8vLy+/0+wMA+FtonTlCgUCwb98+lUp19uzZTz75pKamJi4ubsCAAUePHvXy8rp06VJJSYk+Jen2b7e3txcIBLrDWCwWhUIpKSkRCARyuVx3RG1tbXV1tbW1NRFxuVwigUKhMBiMoqKiX3755ZVXXhGLxUS0cePGkSNHOjk5tWvXTluf7nP0/2tqarKzs4lo0qRJGzZsMDY2Xrt2bUBAQHFxMRG5uLgcPnxYLpcvXrz4mWee+fbbb2NjY59//vnBgwc3e4sAAOQ+Yjx9glZUVJSUlBw6dOjSpUv9+vXbt2/fDz/8sH///nfffTcqKqpVq1bTp09vrsl+v5WUlJCSkkJEFRUVUqlUJBIZGRkRUUlJCZfL7dq1K4vFcnFx6dq1K5PJ7NKlS2RkZEBAQFhYGBFJpdLNmze//vrra9euzcnJISLd5f+oOTY2NjU1NSYmJjg4+NixY9bW1hqN5tChQ2PGjPH3928wclxM0w1VqVTJyclvv/22bqG2trZFRUVarbdv3/7UU0+1adOGz+f37duXiGJjY6dOnerp6Xnul1NiYuL3338/efJkR0fHxYsXS6XSbdu2LViwYP78+Z6enn/X7xAAQJ9a70+EYrH4448/NjU1Xbt27fvvv9+7d28Oh+Po6FhUVDRq1Kh169bNmDGD7jN9+PDhDz74gIjuueee9957j8/nazSaUaNGhYWFZWZmzps3LzQ09KWXXiIiFovl5+dnbGzcr1+/rKysRx99VKVSzZgxo7y83MTE5JNPPqHT6UQkFAr9/f3Nzc1XrlzZ0NBw/PjxHj16+Pv7u7u7d+/e3d3d3cPD49y5c2KxuLKy8urVq05OTn369MnPzzczM7ty5YqTk9PAgQOzsrKISLdHmZmZ06dPnzZtmi7DlpaW48ePz8jI+Pbbb0ePHl1cXMxisQICAi5cuMDn8zMyMhob3oxGI51O1+01NjbGZDKnTp168ODB2bNnr1mzpn///hkZGT/99NPo0aPPnz/f3I2j0WhEwwMIAO5VGIIiIiIajebi4pKbm7ty5crQ0NCffvpp9uzZb7/9dlhYmFgsXrlyJYVCeeyxx0JCQmbMmJGRkXHp0iXlj+G/MAAAHONJREFU2RIrK6u//OUvCoVCIpE8/vjjfD6/oqIiLCzMwcGBx+MRUZ8+fVasWBEYGDh+/Phr1649/vjjp0+fjo+P79mzZ3h4eFJSkpmZ2e7duxmN/xD1b7/9xmazg4KCRowY8cILL0yfPj0lJaW2tnbz5s3du3fXarVpaWm1tbVcLvfw4cOPPPLIBx98sGvXLi6XK5FIUlJS0tLSOnToUFZWNnTo0H//+99ExOFw4uLiampqJkyYUFZWRkT5+flisVgsFj/xxBMCgaChoeHy5csSicTc3DwsLIzFYhERjUYrKChISkqaM2eOp6cnnU4nIpVK1bt3706dOq1bt27dunWEH90D4N6GjggR0aRJk4hIqVSKRCIzM7OysjI3NzeJREJEubm5EyZMGDRo0Jtvvvn+++8vX768Xbt2Tz75ZHh4+IIFC2g0mkgk6tmzJ5vNzs/Pd3Fxqa2tbdu2rVar7d+/v6en57Rp0/h8PhHJ5XI7O7sFCxY4Ojq+9NJL5ubm9vb2QqHwkUceOX/+fEFBQW5u7vr162NjY3Uvs99fOp3OZDJ79uzZqVOn4uJiCwuLxx9/nMlkHjp0qLi4WFcGl8utqanp0KGDQqFISUnR1WFkZGRnZ3fgwIH58+enpKQIpjI++eQTIpLL5ZWVlVu2bCktLS0rK0tOTlYsXRIeHi4SiYiopqbmo48+2rlzZ0ZGBofD6dKli5+fX0lJyTfffLN48eIuXbpIpVI+n9+tWzerxq48AACPAouI8IKK6Nq1a9euXXUqeOeddxYvXvzYY4+dP39eX2k7O7vk5OTOnTsfOXJk+/btCxYsIKINGzZ88skn48eP7y9on5aWNn/+fN3QefPmHTp0KDU1dc6cOc2VT6fTN2/e/NJLL+krGwAAPu05Qh5PKBaLjx071rFjx/bt2xORQqGQy+UCgaC6urq6unrz5s1E5O7u3rdv3+rq6ps3b2q12o4dO546dUokEu3bt8/KyuqTTz4hIk9PzwcffLC+vh7vhQCAFqd9R4iUBn+LKBQKCoWSlJSk0Whqa2vPnDlTVlZ26NChkJAQlUp18+bN5OTk3NxcLpd769GlS5dSqVQ6nd69e/cXX3zR19f3ypUrmzZtKioq6tOnT3x8PIPBICYGAAC8IzxtsFmssLCwgIAAHo8nkUguXLiQnZ1tZ2e3cuXKqVOfpv+PYzQajUb70/8vFyIqKiqKjIycMmVKbW1tRUVFbm6uWq32xv8LBQB4tGkUwtmzZxMTE/FPCQHgd/7OS5QAAEA3Vqs/ESEAAIDeEEIAABmEEABABiEEAJBBCAEAZBBCAAAZhBAAQKaNTQfcbwIAQAhCCAAghiEEABBDCAEAxBBCAIBbhhACAOT/xDcAAMB/hj8RAgDI/H+J3gYgOqnyVQAAAABJRU5ErkJggg==", "base64");
|
|
778
|
-
binary = await this.helpers.prepareBinaryData(demoPng, "screenshot.png", "image/png");
|
|
1192
|
+
sid2 = 99999;
|
|
779
1193
|
} else {
|
|
780
1194
|
try {
|
|
781
|
-
|
|
782
|
-
session_id = browser.sessionId;
|
|
783
|
-
await browser.navigate(url);
|
|
784
|
-
await sleep(ms);
|
|
785
|
-
const shot = await browser.screenshot({ format: "png", fullPage: true });
|
|
786
|
-
const data = shot.toString("base64");
|
|
787
|
-
binary = await this.helpers.prepareBinaryData(Buffer.from(data, "base64"), "screenshot.png", "image/png");
|
|
788
|
-
await browser.close();
|
|
1195
|
+
sid2 = await resolveSid(i, client);
|
|
789
1196
|
} catch (e) {
|
|
790
|
-
throw new
|
|
1197
|
+
throw new import_n8n_workflow.NodeApiError(node, e, { itemIndex: i, message: "Full: resolve schedule failed" });
|
|
791
1198
|
}
|
|
792
1199
|
}
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
url,
|
|
799
|
-
waited: ms
|
|
800
|
-
},
|
|
801
|
-
binary: { data: binary }
|
|
802
|
-
});
|
|
803
|
-
} finally {
|
|
804
|
-
await client.close().catch(() => {
|
|
805
|
-
});
|
|
806
|
-
}
|
|
807
|
-
continue;
|
|
808
|
-
}
|
|
809
|
-
const sessionId = this.getNodeParameter("sessionId", i);
|
|
810
|
-
try {
|
|
811
|
-
browser = await client.resume(sessionId);
|
|
812
|
-
} catch (e) {
|
|
813
|
-
throw new Error(`Resume session "${sessionId}" failed: ${e.message}. Session may have expired or still be in grace.`);
|
|
814
|
-
}
|
|
815
|
-
const sid = browser.sessionId;
|
|
816
|
-
switch (op) {
|
|
817
|
-
case "navigate": {
|
|
1200
|
+
const mode = this.getNodeParameter("mode", i);
|
|
1201
|
+
const url = this.getNodeParameter("url", i);
|
|
1202
|
+
const ms = this.getNodeParameter("ms", i);
|
|
1203
|
+
let session_id;
|
|
1204
|
+
let binary;
|
|
818
1205
|
try {
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
1206
|
+
if (demoMode) {
|
|
1207
|
+
session_id = "demo-" + Date.now();
|
|
1208
|
+
const demoPng = Buffer.from("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", "base64");
|
|
1209
|
+
binary = await this.helpers.prepareBinaryData(demoPng, "screenshot.png", "image/png");
|
|
1210
|
+
} else {
|
|
1211
|
+
try {
|
|
1212
|
+
browser = await client.rent(sid2, { mode });
|
|
1213
|
+
session_id = browser.sessionId;
|
|
1214
|
+
await browser.navigate(url);
|
|
1215
|
+
await sleep(ms);
|
|
1216
|
+
const shot = await browser.screenshot({ format: "png", fullPage: true });
|
|
1217
|
+
const data = shot.toString("base64");
|
|
1218
|
+
binary = await this.helpers.prepareBinaryData(Buffer.from(data, "base64"), "screenshot.png", "image/png");
|
|
1219
|
+
await browser.close();
|
|
1220
|
+
} catch (e) {
|
|
1221
|
+
throw new import_n8n_workflow.NodeApiError(node, e, { itemIndex: i, message: "Full op failed" });
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
out.push({
|
|
1225
|
+
json: {
|
|
1226
|
+
session_id,
|
|
1227
|
+
schedule_id: sid2,
|
|
1228
|
+
mode,
|
|
1229
|
+
url,
|
|
1230
|
+
waited: ms
|
|
1231
|
+
},
|
|
1232
|
+
binary: { data: binary },
|
|
1233
|
+
pairedItem: { item: i }
|
|
1234
|
+
});
|
|
1235
|
+
} finally {
|
|
1236
|
+
await client.close().catch(() => {
|
|
1237
|
+
});
|
|
824
1238
|
}
|
|
825
|
-
|
|
1239
|
+
continue;
|
|
826
1240
|
}
|
|
827
|
-
|
|
1241
|
+
if (op === "captchaScrape") {
|
|
1242
|
+
const url = this.getNodeParameter("url", i);
|
|
1243
|
+
const geo = this.getNodeParameter("geo", i);
|
|
1244
|
+
const maxPrice = this.getNodeParameter("maxPrice", i);
|
|
1245
|
+
const waitSelector = this.getNodeParameter("waitSelector", i) || "";
|
|
1246
|
+
const waitTimeout = this.getNodeParameter("waitTimeout", i);
|
|
1247
|
+
const extractHtmlFlag = this.getNodeParameter("extractHtml", i);
|
|
1248
|
+
const htmlSelector = this.getNodeParameter("htmlSelector", i) || "body";
|
|
1249
|
+
const fullPage = this.getNodeParameter("fullPage", i);
|
|
1250
|
+
let scheduleId = 0;
|
|
828
1251
|
try {
|
|
829
|
-
const
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
const
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
1252
|
+
const list = await client.search({ geo: geo || void 0, max_price_per_min: maxPrice });
|
|
1253
|
+
if (!list.length) throw new import_n8n_workflow.NodeOperationError(node, "No browsers in geo " + (geo || "*"), { itemIndex: i });
|
|
1254
|
+
scheduleId = list[0].schedule_id;
|
|
1255
|
+
browser = await client.rent(scheduleId);
|
|
1256
|
+
touchedSessions.add(browser.sessionId);
|
|
1257
|
+
await browser.navigate(url);
|
|
1258
|
+
if (waitSelector) {
|
|
1259
|
+
await waitForSelector(node, browser, waitSelector, waitTimeout);
|
|
1260
|
+
}
|
|
1261
|
+
const shot = await browser.screenshot({ format: "base64", fullPage });
|
|
1262
|
+
const data = shot.data ?? (shot instanceof Buffer ? shot.toString("base64") : "");
|
|
1263
|
+
const binary = await this.helpers.prepareBinaryData(
|
|
1264
|
+
Buffer.from(data, "base64"),
|
|
1265
|
+
"captcha-scrape.png",
|
|
1266
|
+
"image/png"
|
|
1267
|
+
);
|
|
1268
|
+
const json = {
|
|
1269
|
+
url,
|
|
1270
|
+
geo,
|
|
1271
|
+
schedule_id: scheduleId
|
|
1272
|
+
};
|
|
1273
|
+
if (extractHtmlFlag) {
|
|
1274
|
+
json.html = await extractHtml(browser, htmlSelector);
|
|
1275
|
+
}
|
|
1276
|
+
out.push({ json, binary: { data: binary }, pairedItem: { item: i } });
|
|
853
1277
|
} catch (e) {
|
|
854
|
-
throw new
|
|
1278
|
+
throw new import_n8n_workflow.NodeApiError(node, e, { itemIndex: i, message: "Captcha-protected scrape failed" });
|
|
1279
|
+
} finally {
|
|
1280
|
+
if (browser) {
|
|
1281
|
+
try {
|
|
1282
|
+
await browser.close();
|
|
1283
|
+
} catch {
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
try {
|
|
1287
|
+
await client.close();
|
|
1288
|
+
} catch {
|
|
1289
|
+
}
|
|
855
1290
|
}
|
|
856
|
-
|
|
1291
|
+
continue;
|
|
857
1292
|
}
|
|
858
|
-
|
|
1293
|
+
if (op === "screenshotGeo") {
|
|
1294
|
+
const url = this.getNodeParameter("url", i);
|
|
1295
|
+
const geo = this.getNodeParameter("geo", i);
|
|
1296
|
+
const fullPage = this.getNodeParameter("fullPage", i);
|
|
1297
|
+
const maxPrice = this.getNodeParameter("maxPrice", i);
|
|
859
1298
|
try {
|
|
860
|
-
const
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
1299
|
+
const list = await client.search({ geo, max_price_per_min: maxPrice });
|
|
1300
|
+
if (!list.length) throw new import_n8n_workflow.NodeOperationError(node, "No browsers in geo " + geo, { itemIndex: i });
|
|
1301
|
+
browser = await client.rent(list[0].schedule_id);
|
|
1302
|
+
touchedSessions.add(browser.sessionId);
|
|
1303
|
+
await browser.navigate(url);
|
|
1304
|
+
const shot = await browser.screenshot({ format: "base64", fullPage });
|
|
864
1305
|
const binary = await this.helpers.prepareBinaryData(
|
|
865
|
-
Buffer.from(data, "base64"),
|
|
1306
|
+
Buffer.from(shot.data, "base64"),
|
|
866
1307
|
"screenshot.png",
|
|
867
1308
|
"image/png"
|
|
868
1309
|
);
|
|
869
|
-
out.push({
|
|
1310
|
+
out.push({
|
|
1311
|
+
json: { url, geo, schedule_id: list[0].schedule_id },
|
|
1312
|
+
binary: { data: binary },
|
|
1313
|
+
pairedItem: { item: i }
|
|
1314
|
+
});
|
|
870
1315
|
} catch (e) {
|
|
871
|
-
throw new
|
|
1316
|
+
throw new import_n8n_workflow.NodeApiError(node, e, { itemIndex: i, message: "Screenshot in geo " + geo + " failed" });
|
|
1317
|
+
} finally {
|
|
1318
|
+
if (browser) {
|
|
1319
|
+
try {
|
|
1320
|
+
await browser.close();
|
|
1321
|
+
} catch {
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
try {
|
|
1325
|
+
await client.close();
|
|
1326
|
+
} catch {
|
|
1327
|
+
}
|
|
872
1328
|
}
|
|
873
|
-
|
|
1329
|
+
continue;
|
|
874
1330
|
}
|
|
875
|
-
|
|
1331
|
+
if (op.startsWith("contract_")) {
|
|
1332
|
+
const contractClient = new ContractClient(token);
|
|
1333
|
+
const pop = op.replace("contract_", "");
|
|
1334
|
+
let result;
|
|
876
1335
|
try {
|
|
877
|
-
|
|
1336
|
+
switch (pop) {
|
|
1337
|
+
case "list":
|
|
1338
|
+
result = await contractClient.listContracts();
|
|
1339
|
+
break;
|
|
1340
|
+
case "tasks": {
|
|
1341
|
+
const contractId = this.getNodeParameter("contractId", i);
|
|
1342
|
+
result = await contractClient.tasks(contractId);
|
|
1343
|
+
break;
|
|
1344
|
+
}
|
|
1345
|
+
case "get": {
|
|
1346
|
+
const eventId = this.getNodeParameter("eventId", i);
|
|
1347
|
+
result = await contractClient.task(eventId);
|
|
1348
|
+
break;
|
|
1349
|
+
}
|
|
1350
|
+
case "myEvents":
|
|
1351
|
+
result = await contractClient.myEvents();
|
|
1352
|
+
break;
|
|
1353
|
+
case "create": {
|
|
1354
|
+
const contractId = this.getNodeParameter("contractId", i);
|
|
1355
|
+
const label = this.getNodeParameter("label", i);
|
|
1356
|
+
const description = this.getNodeParameter("description", i) || "";
|
|
1357
|
+
const status = this.getNodeParameter("status", i);
|
|
1358
|
+
const bType = this.getNodeParameter("benefitableType", i);
|
|
1359
|
+
const bValue = this.getNodeParameter("benefitableValue", i);
|
|
1360
|
+
result = await contractClient.create(contractId, {
|
|
1361
|
+
label,
|
|
1362
|
+
description: description || void 0,
|
|
1363
|
+
status,
|
|
1364
|
+
benefitable: bValue ? bType + ":" + bValue : void 0
|
|
1365
|
+
});
|
|
1366
|
+
break;
|
|
1367
|
+
}
|
|
1368
|
+
case "assign": {
|
|
1369
|
+
const eventId = this.getNodeParameter("eventId", i);
|
|
1370
|
+
const bType = this.getNodeParameter("benefitableType", i);
|
|
1371
|
+
const bValue = this.getNodeParameter("benefitableValue", i);
|
|
1372
|
+
result = await contractClient.assign(eventId, bType + ":" + bValue);
|
|
1373
|
+
break;
|
|
1374
|
+
}
|
|
1375
|
+
case "setStatus": {
|
|
1376
|
+
const eventId = this.getNodeParameter("eventId", i);
|
|
1377
|
+
const status = this.getNodeParameter("status", i);
|
|
1378
|
+
result = await contractClient.setStatus(eventId, status);
|
|
1379
|
+
break;
|
|
1380
|
+
}
|
|
1381
|
+
case "comment": {
|
|
1382
|
+
const eventId = this.getNodeParameter("eventId", i);
|
|
1383
|
+
const text = this.getNodeParameter("description", i) || "";
|
|
1384
|
+
result = await contractClient.comment(eventId, text);
|
|
1385
|
+
break;
|
|
1386
|
+
}
|
|
1387
|
+
case "progress": {
|
|
1388
|
+
const eventId = this.getNodeParameter("eventId", i);
|
|
1389
|
+
const status = this.getNodeParameter("status", i);
|
|
1390
|
+
const desc = this.getNodeParameter("progressDesc", i);
|
|
1391
|
+
result = await contractClient.progress(eventId, status, desc);
|
|
1392
|
+
break;
|
|
1393
|
+
}
|
|
1394
|
+
case "callHuman": {
|
|
1395
|
+
const eventId = this.getNodeParameter("eventId", i);
|
|
1396
|
+
const kind = this.getNodeParameter("callKind", i);
|
|
1397
|
+
const msg = this.getNodeParameter("callDesc", i);
|
|
1398
|
+
result = await contractClient.callHuman(eventId, kind, msg);
|
|
1399
|
+
break;
|
|
1400
|
+
}
|
|
1401
|
+
case "poll":
|
|
1402
|
+
result = await contractClient.poll();
|
|
1403
|
+
break;
|
|
1404
|
+
default:
|
|
1405
|
+
throw new import_n8n_workflow.NodeOperationError(node, "Unknown contract operation: " + pop, { itemIndex: i });
|
|
1406
|
+
}
|
|
878
1407
|
out.push({
|
|
879
|
-
json:
|
|
1408
|
+
json: result,
|
|
1409
|
+
pairedItem: { item: i }
|
|
880
1410
|
});
|
|
881
1411
|
} catch (e) {
|
|
882
|
-
throw new
|
|
1412
|
+
throw new import_n8n_workflow.NodeApiError(node, e, { itemIndex: i, message: "Contract " + pop + " failed" });
|
|
883
1413
|
}
|
|
884
|
-
|
|
1414
|
+
continue;
|
|
885
1415
|
}
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
await
|
|
889
|
-
|
|
890
|
-
|
|
1416
|
+
const sessionId = this.getNodeParameter("sessionId", i);
|
|
1417
|
+
try {
|
|
1418
|
+
browser = await client.resume(sessionId);
|
|
1419
|
+
} catch (e) {
|
|
1420
|
+
throw new import_n8n_workflow.NodeApiError(node, e, { itemIndex: i, message: `Resume session "${sessionId}" failed. Session may have expired or still be in grace.` });
|
|
891
1421
|
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
const expr = `!!document.querySelector(${JSON.stringify(selector)})`;
|
|
896
|
-
const deadline = Date.now() + timeout;
|
|
897
|
-
let ok = false;
|
|
898
|
-
let lastErr = null;
|
|
899
|
-
while (Date.now() < deadline) {
|
|
1422
|
+
const sid = browser.sessionId;
|
|
1423
|
+
switch (op) {
|
|
1424
|
+
case "navigate": {
|
|
900
1425
|
try {
|
|
901
|
-
const
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
break;
|
|
905
|
-
}
|
|
1426
|
+
const url = this.getNodeParameter("url", i);
|
|
1427
|
+
await browser.navigate(url);
|
|
1428
|
+
out.push({ json: { session_id: sid, url }, pairedItem: { item: i } });
|
|
906
1429
|
} catch (e) {
|
|
907
|
-
|
|
1430
|
+
throw new import_n8n_workflow.NodeApiError(node, e, { itemIndex: i, message: "Navigate failed" });
|
|
908
1431
|
}
|
|
909
|
-
|
|
1432
|
+
break;
|
|
910
1433
|
}
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
1434
|
+
case "click": {
|
|
1435
|
+
try {
|
|
1436
|
+
const x = this.getNodeParameter("x", i);
|
|
1437
|
+
const y = this.getNodeParameter("y", i);
|
|
1438
|
+
await browser.click(x, y);
|
|
1439
|
+
out.push({ json: { session_id: sid, clicked: [x, y] }, pairedItem: { item: i } });
|
|
1440
|
+
} catch (e) {
|
|
1441
|
+
throw new import_n8n_workflow.NodeApiError(node, e, { itemIndex: i, message: "Click failed" });
|
|
1442
|
+
}
|
|
1443
|
+
break;
|
|
915
1444
|
}
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
const stream = await this.helpers.getBinaryStream(bin.id);
|
|
926
|
-
const chunks = [];
|
|
927
|
-
for await (const c of stream) chunks.push(c);
|
|
928
|
-
const buf = Buffer.concat(chunks);
|
|
929
|
-
const res = await browser.upload(selector, buf);
|
|
930
|
-
out.push({ json: { session_id: sid, uploaded: res } });
|
|
931
|
-
} catch (e) {
|
|
932
|
-
throw new Error(`Upload failed: ${e.message}`);
|
|
1445
|
+
case "type": {
|
|
1446
|
+
try {
|
|
1447
|
+
const text = this.getNodeParameter("text", i);
|
|
1448
|
+
await browser.type(text);
|
|
1449
|
+
out.push({ json: { session_id: sid, typed: text }, pairedItem: { item: i } });
|
|
1450
|
+
} catch (e) {
|
|
1451
|
+
throw new import_n8n_workflow.NodeApiError(node, e, { itemIndex: i, message: "Type failed" });
|
|
1452
|
+
}
|
|
1453
|
+
break;
|
|
933
1454
|
}
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
})
|
|
940
|
-
|
|
1455
|
+
case "scroll": {
|
|
1456
|
+
try {
|
|
1457
|
+
const deltaY = this.getNodeParameter("deltaY", i);
|
|
1458
|
+
await browser.scroll(deltaY);
|
|
1459
|
+
out.push({ json: { session_id: sid, scrolled: deltaY }, pairedItem: { item: i } });
|
|
1460
|
+
} catch (e) {
|
|
1461
|
+
throw new import_n8n_workflow.NodeApiError(node, e, { itemIndex: i, message: "Scroll failed" });
|
|
1462
|
+
}
|
|
1463
|
+
break;
|
|
1464
|
+
}
|
|
1465
|
+
case "screenshot": {
|
|
1466
|
+
try {
|
|
1467
|
+
const format = this.getNodeParameter("format", i);
|
|
1468
|
+
const fullPage = this.getNodeParameter("fullPage", i);
|
|
1469
|
+
const shot = await browser.screenshot({ format, fullPage });
|
|
1470
|
+
const data = format === "base64" ? shot.data : shot.toString("base64");
|
|
1471
|
+
const binary = await this.helpers.prepareBinaryData(
|
|
1472
|
+
Buffer.from(data, "base64"),
|
|
1473
|
+
"screenshot.png",
|
|
1474
|
+
"image/png"
|
|
1475
|
+
);
|
|
1476
|
+
out.push({ json: { session_id: sid }, binary: { data: binary }, pairedItem: { item: i } });
|
|
1477
|
+
} catch (e) {
|
|
1478
|
+
throw new import_n8n_workflow.NodeApiError(node, e, { itemIndex: i, message: "Screenshot failed" });
|
|
1479
|
+
}
|
|
1480
|
+
break;
|
|
1481
|
+
}
|
|
1482
|
+
case "snapshot": {
|
|
1483
|
+
try {
|
|
1484
|
+
const snap = await browser.snapshot();
|
|
1485
|
+
out.push({
|
|
1486
|
+
json: { session_id: sid, screenshot: snap.screenshot },
|
|
1487
|
+
pairedItem: { item: i }
|
|
1488
|
+
});
|
|
1489
|
+
} catch (e) {
|
|
1490
|
+
throw new import_n8n_workflow.NodeApiError(node, e, { itemIndex: i, message: "Snapshot failed" });
|
|
1491
|
+
}
|
|
1492
|
+
break;
|
|
1493
|
+
}
|
|
1494
|
+
case "wait": {
|
|
1495
|
+
const ms = this.getNodeParameter("ms", i);
|
|
1496
|
+
await sleep(ms);
|
|
1497
|
+
out.push({ json: { session_id: sid, waited: ms }, pairedItem: { item: i } });
|
|
1498
|
+
break;
|
|
941
1499
|
}
|
|
942
|
-
|
|
943
|
-
const
|
|
944
|
-
const
|
|
945
|
-
const
|
|
1500
|
+
case "waitForSelector": {
|
|
1501
|
+
const selector = this.getNodeParameter("waitSelector", i);
|
|
1502
|
+
const timeout = this.getNodeParameter("waitTimeout", i);
|
|
1503
|
+
const expr = `!!document.querySelector(${JSON.stringify(selector)})`;
|
|
1504
|
+
const deadline = Date.now() + timeout;
|
|
1505
|
+
let ok = false;
|
|
1506
|
+
let lastErr = null;
|
|
1507
|
+
while (Date.now() < deadline) {
|
|
946
1508
|
try {
|
|
947
|
-
|
|
948
|
-
|
|
1509
|
+
const res = await browser.send("Runtime.evaluate", { expression: expr, returnByValue: true });
|
|
1510
|
+
if (res?.result?.value === true) {
|
|
1511
|
+
ok = true;
|
|
1512
|
+
break;
|
|
1513
|
+
}
|
|
1514
|
+
} catch (e) {
|
|
1515
|
+
lastErr = e;
|
|
949
1516
|
}
|
|
950
|
-
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
1517
|
+
await sleep(500);
|
|
1518
|
+
}
|
|
1519
|
+
if (!ok) {
|
|
1520
|
+
throw new import_n8n_workflow.NodeOperationError(
|
|
1521
|
+
node,
|
|
1522
|
+
`waitForSelector("${selector}") timed out after ${timeout}ms${lastErr ? `: ${lastErr.message}` : ""}`,
|
|
1523
|
+
{ itemIndex: i }
|
|
1524
|
+
);
|
|
1525
|
+
}
|
|
1526
|
+
out.push({ json: { session_id: sid, selector, found: true }, pairedItem: { item: i } });
|
|
1527
|
+
break;
|
|
1528
|
+
}
|
|
1529
|
+
case "upload": {
|
|
1530
|
+
try {
|
|
1531
|
+
const selector = this.getNodeParameter("selector", i);
|
|
1532
|
+
const bpn = this.getNodeParameter("binaryPropertyName", i);
|
|
1533
|
+
const bin = items[i].binary?.[bpn];
|
|
1534
|
+
if (!bin) throw new import_n8n_workflow.NodeOperationError(node, `Binary property "${bpn}" not found on input`, { itemIndex: i });
|
|
1535
|
+
const stream = await this.helpers.getBinaryStream(bin.id);
|
|
1536
|
+
const chunks = [];
|
|
1537
|
+
for await (const c of stream) chunks.push(c);
|
|
1538
|
+
const buf = Buffer.concat(chunks);
|
|
1539
|
+
const res = await browser.upload(selector, buf);
|
|
1540
|
+
out.push({ json: { session_id: sid, uploaded: res }, pairedItem: { item: i } });
|
|
1541
|
+
} catch (e) {
|
|
1542
|
+
throw new import_n8n_workflow.NodeApiError(node, e, { itemIndex: i, message: "Upload failed" });
|
|
1543
|
+
}
|
|
1544
|
+
break;
|
|
1545
|
+
}
|
|
1546
|
+
case "close": {
|
|
1547
|
+
try {
|
|
1548
|
+
await browser.close().catch(() => {
|
|
1549
|
+
});
|
|
1550
|
+
} catch {
|
|
1551
|
+
}
|
|
1552
|
+
await new Promise((resolve) => {
|
|
1553
|
+
const stopWs = new WebSocket("wss://browser.ceki.me/ws/agent", [`bearer.${token}`]);
|
|
1554
|
+
const abortTimer = AbortSignal.timeout(1e4);
|
|
1555
|
+
const onTimer = () => {
|
|
1556
|
+
try {
|
|
1557
|
+
stopWs.close();
|
|
1558
|
+
} catch {
|
|
1559
|
+
}
|
|
1560
|
+
resolve();
|
|
1561
|
+
};
|
|
1562
|
+
abortTimer.addEventListener("abort", onTimer, { once: true });
|
|
1563
|
+
stopWs.onopen = () => {
|
|
1564
|
+
stopWs.send(JSON.stringify({ type: "stop", session_id: sessionId, reason: "n8n close" }));
|
|
1565
|
+
};
|
|
1566
|
+
stopWs.onmessage = (ev) => {
|
|
1567
|
+
try {
|
|
1568
|
+
const msg = JSON.parse(ev.data);
|
|
1569
|
+
if (msg.type === "session_ended") {
|
|
1570
|
+
abortTimer.removeEventListener("abort", onTimer);
|
|
1571
|
+
try {
|
|
1572
|
+
stopWs.close();
|
|
1573
|
+
} catch {
|
|
1574
|
+
}
|
|
1575
|
+
resolve();
|
|
964
1576
|
}
|
|
965
|
-
|
|
1577
|
+
} catch {
|
|
966
1578
|
}
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
};
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
break;
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
needFullClose = op === "close";
|
|
984
|
-
} finally {
|
|
985
|
-
try {
|
|
986
|
-
if (needFullClose) {
|
|
987
|
-
await client.close();
|
|
988
|
-
} else {
|
|
989
|
-
await client.disconnect();
|
|
990
|
-
}
|
|
991
|
-
} catch {
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
return [out];
|
|
996
|
-
}
|
|
997
|
-
};
|
|
998
|
-
|
|
999
|
-
// lib/contract-client.ts
|
|
1000
|
-
function cleanArgs(o) {
|
|
1001
|
-
const out = { ...o };
|
|
1002
|
-
for (const k of Object.keys(out)) {
|
|
1003
|
-
if (out[k] === void 0 || out[k] === null) delete out[k];
|
|
1004
|
-
}
|
|
1005
|
-
return out;
|
|
1006
|
-
}
|
|
1007
|
-
function parseBenefitable(value) {
|
|
1008
|
-
if (!value) return null;
|
|
1009
|
-
const m = /^(agent|user):(\d+)$/.exec(value);
|
|
1010
|
-
if (!m) return null;
|
|
1011
|
-
return { type: m[1], value: Number(m[2]) };
|
|
1012
|
-
}
|
|
1013
|
-
function parseParticipant(value, roleId) {
|
|
1014
|
-
const b = parseBenefitable(value);
|
|
1015
|
-
if (!b) return null;
|
|
1016
|
-
return { participable_id: b.value, type: b.type, role_id: roleId };
|
|
1017
|
-
}
|
|
1018
|
-
function deriveLabel(desc) {
|
|
1019
|
-
if (!desc) return "";
|
|
1020
|
-
const line = desc.split("\n")[0].trim();
|
|
1021
|
-
return line.length > 60 ? line.slice(0, 57) + "..." : line;
|
|
1022
|
-
}
|
|
1023
|
-
var ROLE_REVIEWER = 5;
|
|
1024
|
-
var ROLE_QA = 6;
|
|
1025
|
-
var ContractClient = class {
|
|
1026
|
-
constructor(token, endpoint, apiBase) {
|
|
1027
|
-
this._endpoint = (endpoint ?? "https://api.ceki.me/mcp").replace(/\/+$/, "");
|
|
1028
|
-
this._apiBase = (apiBase ?? "https://api.ceki.me").replace(/\/+$/, "");
|
|
1029
|
-
this._token = token;
|
|
1030
|
-
}
|
|
1031
|
-
_headers() {
|
|
1032
|
-
return {
|
|
1033
|
-
"Content-Type": "application/json",
|
|
1034
|
-
Accept: "application/json",
|
|
1035
|
-
Authorization: `Bearer ${this._token}`
|
|
1036
|
-
};
|
|
1037
|
-
}
|
|
1038
|
-
async _rpc(method, params) {
|
|
1039
|
-
const body = JSON.stringify({ jsonrpc: "2.0", id: Date.now(), method, params });
|
|
1040
|
-
const resp = await fetch(this._endpoint, {
|
|
1041
|
-
method: "POST",
|
|
1042
|
-
headers: this._headers(),
|
|
1043
|
-
body
|
|
1044
|
-
});
|
|
1045
|
-
if (!resp.ok) {
|
|
1046
|
-
const text = await resp.text().catch(() => "");
|
|
1047
|
-
throw new Error(`HTTP ${resp.status}: ${text.slice(0, 400)}`);
|
|
1048
|
-
}
|
|
1049
|
-
return resp.json();
|
|
1050
|
-
}
|
|
1051
|
-
async _call(tool, args) {
|
|
1052
|
-
const body = await this._rpc("tools/call", { name: tool, arguments: args ?? {} });
|
|
1053
|
-
if (body.error) throw new Error(`${tool} \u2192 ${JSON.stringify(body.error).slice(0, 400)}`);
|
|
1054
|
-
const result = body.result ?? {};
|
|
1055
|
-
const content = result.content;
|
|
1056
|
-
if (Array.isArray(content)) {
|
|
1057
|
-
const texts = content.filter((c) => c.type === "text").map((c) => String(c.text ?? ""));
|
|
1058
|
-
const joined = texts.join("\n");
|
|
1059
|
-
try {
|
|
1060
|
-
return JSON.parse(joined);
|
|
1061
|
-
} catch {
|
|
1062
|
-
return joined;
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
if (result.structuredContent !== void 0) return result.structuredContent;
|
|
1066
|
-
return result;
|
|
1067
|
-
}
|
|
1068
|
-
// ── domain methods ─────────────────────────────────────────
|
|
1069
|
-
async listContracts() {
|
|
1070
|
-
return this._call("list-contracts");
|
|
1071
|
-
}
|
|
1072
|
-
async members(contractId) {
|
|
1073
|
-
return this._call("contract-members", { contract_id: contractId });
|
|
1074
|
-
}
|
|
1075
|
-
async tasks(contractId) {
|
|
1076
|
-
return this._call("contract-tasks", { contract_id: contractId });
|
|
1077
|
-
}
|
|
1078
|
-
async myEvents() {
|
|
1079
|
-
return this._call("get-my-events");
|
|
1080
|
-
}
|
|
1081
|
-
async task(eventId) {
|
|
1082
|
-
return this._call("get-event", { event_id: eventId });
|
|
1083
|
-
}
|
|
1084
|
-
async create(contractId, opts) {
|
|
1085
|
-
const args = cleanArgs({
|
|
1086
|
-
contract_id: contractId,
|
|
1087
|
-
label: opts.label,
|
|
1088
|
-
type_id: opts.type,
|
|
1089
|
-
status_id: opts.status,
|
|
1090
|
-
description: opts.description,
|
|
1091
|
-
benefitable: opts.benefitable ? parseBenefitable(opts.benefitable) : void 0
|
|
1092
|
-
});
|
|
1093
|
-
const users = [];
|
|
1094
|
-
const rev = parseParticipant(opts.reviewer, ROLE_REVIEWER);
|
|
1095
|
-
if (rev) users.push(rev);
|
|
1096
|
-
const qa = parseParticipant(opts.qa, ROLE_QA);
|
|
1097
|
-
if (qa) users.push(qa);
|
|
1098
|
-
if (users.length) args.users = users;
|
|
1099
|
-
return this._call("create-contract-event", args);
|
|
1100
|
-
}
|
|
1101
|
-
async propose(eventId, opts) {
|
|
1102
|
-
return this._call("propose-correction", cleanArgs({
|
|
1103
|
-
event_id: eventId,
|
|
1104
|
-
status_id: opts.status,
|
|
1105
|
-
label: opts.label,
|
|
1106
|
-
description: opts.description,
|
|
1107
|
-
benefitable: opts.benefitable ? parseBenefitable(opts.benefitable) : void 0
|
|
1108
|
-
}));
|
|
1109
|
-
}
|
|
1110
|
-
async comment(eventId, opts) {
|
|
1111
|
-
return this._call("comment", cleanArgs({
|
|
1112
|
-
event_id: eventId,
|
|
1113
|
-
label: opts?.label,
|
|
1114
|
-
description: opts?.description
|
|
1115
|
-
}));
|
|
1116
|
-
}
|
|
1117
|
-
async progress(eventId, opts) {
|
|
1118
|
-
let statusResult = null;
|
|
1119
|
-
if (opts.status != null) {
|
|
1120
|
-
statusResult = await this.propose(eventId, { status: opts.status });
|
|
1121
|
-
}
|
|
1122
|
-
const commentResult = await this.comment(eventId, { label: deriveLabel(opts.desc), description: opts.desc });
|
|
1123
|
-
return { status_correction: statusResult, comment: commentResult };
|
|
1124
|
-
}
|
|
1125
|
-
async callHuman(eventId, kind, desc) {
|
|
1126
|
-
if (!["input", "review", "stuck"].includes(kind)) throw new Error(`kind must be input|review|stuck, got ${kind}`);
|
|
1127
|
-
return this._call("call-human", { event_id: eventId, kind, desc });
|
|
1128
|
-
}
|
|
1129
|
-
/** GET /agent/polling. Returns [] on 429. */
|
|
1130
|
-
async poll() {
|
|
1131
|
-
const resp = await fetch(`${this._apiBase}/agent/polling`, {
|
|
1132
|
-
headers: { Accept: "application/json", Authorization: `Bearer ${this._token}` }
|
|
1133
|
-
});
|
|
1134
|
-
if (resp.status === 429) return [];
|
|
1135
|
-
if (!resp.ok) {
|
|
1136
|
-
const text = await resp.text().catch(() => "");
|
|
1137
|
-
throw new Error(`poll HTTP ${resp.status}: ${text.slice(0, 300)}`);
|
|
1138
|
-
}
|
|
1139
|
-
const body = await resp.json();
|
|
1140
|
-
if (Array.isArray(body)) return body;
|
|
1141
|
-
if (body && typeof body === "object") {
|
|
1142
|
-
for (const k of ["notifications", "data", "items"]) {
|
|
1143
|
-
if (Array.isArray(body[k])) return body[k];
|
|
1144
|
-
}
|
|
1145
|
-
}
|
|
1146
|
-
return [];
|
|
1147
|
-
}
|
|
1148
|
-
};
|
|
1149
|
-
|
|
1150
|
-
// nodes/CekiContract/CekiContract.node.ts
|
|
1151
|
-
var STATUS_OPTIONS = [
|
|
1152
|
-
{ name: "100 \xB7 Backlog", value: 100 },
|
|
1153
|
-
{ name: "200 \xB7 Hand (assigned)", value: 200 },
|
|
1154
|
-
{ name: "222 \xB7 Hand done", value: 222 },
|
|
1155
|
-
{ name: "300 \xB7 QA", value: 300 },
|
|
1156
|
-
{ name: "350 \xB7 QA done", value: 350 },
|
|
1157
|
-
{ name: "499 \xB7 Reviewer", value: 499 }
|
|
1158
|
-
];
|
|
1159
|
-
var CekiContract = class {
|
|
1160
|
-
constructor() {
|
|
1161
|
-
this.description = {
|
|
1162
|
-
displayName: "Ceki Contract",
|
|
1163
|
-
name: "cekiContract",
|
|
1164
|
-
icon: "file:ceki.png",
|
|
1165
|
-
group: ["transform"],
|
|
1166
|
-
version: 1,
|
|
1167
|
-
subtitle: '={{ "Contract: " + $operation }}',
|
|
1168
|
-
description: "Work with Ceki contract tasks: list, create, assign, update status, comment, report progress, escalate to a human, and poll",
|
|
1169
|
-
defaults: { name: "Ceki Contract" },
|
|
1170
|
-
inputs: ["main"],
|
|
1171
|
-
outputs: ["main"],
|
|
1172
|
-
credentials: [{ name: "cekiApi", required: true }],
|
|
1173
|
-
properties: [
|
|
1174
|
-
{
|
|
1175
|
-
displayName: "Operation",
|
|
1176
|
-
name: "operation",
|
|
1177
|
-
type: "options",
|
|
1178
|
-
default: "myEvents",
|
|
1179
|
-
options: [
|
|
1180
|
-
{ name: "List My Contracts", value: "listContracts" },
|
|
1181
|
-
{ name: "List Tasks in Contract", value: "listTasks" },
|
|
1182
|
-
{ name: "Get Task", value: "getTask" },
|
|
1183
|
-
{ name: "My Assigned Events", value: "myEvents" },
|
|
1184
|
-
{ name: "Create Task", value: "createTask" },
|
|
1185
|
-
{ name: "Assign Executor", value: "assign" },
|
|
1186
|
-
{ name: "Update Status", value: "setStatus" },
|
|
1187
|
-
{ name: "Comment", value: "comment" },
|
|
1188
|
-
{ name: "Progress Report", value: "progress" },
|
|
1189
|
-
{ name: "Call Human", value: "callHuman" },
|
|
1190
|
-
{ name: "Poll Notifications", value: "poll" }
|
|
1191
|
-
]
|
|
1192
|
-
},
|
|
1193
|
-
// --- contractId / eventId ---
|
|
1194
|
-
{
|
|
1195
|
-
displayName: "Contract ID",
|
|
1196
|
-
name: "contractId",
|
|
1197
|
-
type: "number",
|
|
1198
|
-
default: 0,
|
|
1199
|
-
description: "ceki contract id",
|
|
1200
|
-
displayOptions: { show: { operation: ["listTasks", "createTask"] } }
|
|
1201
|
-
},
|
|
1202
|
-
{
|
|
1203
|
-
displayName: "Event ID",
|
|
1204
|
-
name: "eventId",
|
|
1205
|
-
type: "number",
|
|
1206
|
-
default: 0,
|
|
1207
|
-
description: "Task / event id (KalEvent)",
|
|
1208
|
-
displayOptions: {
|
|
1209
|
-
show: { operation: ["getTask", "assign", "setStatus", "comment", "progress", "callHuman"] }
|
|
1579
|
+
};
|
|
1580
|
+
stopWs.onerror = () => {
|
|
1581
|
+
abortTimer.removeEventListener("abort", onTimer);
|
|
1582
|
+
resolve();
|
|
1583
|
+
};
|
|
1584
|
+
stopWs.onclose = () => {
|
|
1585
|
+
abortTimer.removeEventListener("abort", onTimer);
|
|
1586
|
+
resolve();
|
|
1587
|
+
};
|
|
1588
|
+
});
|
|
1589
|
+
out.push({ json: { closed: true, session_id: sessionId }, pairedItem: { item: i } });
|
|
1590
|
+
break;
|
|
1591
|
+
}
|
|
1210
1592
|
}
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
{
|
|
1214
|
-
displayName: "Label",
|
|
1215
|
-
name: "label",
|
|
1216
|
-
type: "string",
|
|
1217
|
-
default: "",
|
|
1218
|
-
required: true,
|
|
1219
|
-
displayOptions: { show: { operation: ["createTask"] } }
|
|
1220
|
-
},
|
|
1221
|
-
{
|
|
1222
|
-
displayName: "Description",
|
|
1223
|
-
name: "description",
|
|
1224
|
-
type: "string",
|
|
1225
|
-
typeOptions: { rows: 4 },
|
|
1226
|
-
default: "",
|
|
1227
|
-
displayOptions: { show: { operation: ["createTask", "comment"] } }
|
|
1228
|
-
},
|
|
1229
|
-
{
|
|
1230
|
-
displayName: "Executor (benefitable)",
|
|
1231
|
-
name: "benefitableType",
|
|
1232
|
-
type: "options",
|
|
1233
|
-
default: "agent",
|
|
1234
|
-
options: [
|
|
1235
|
-
{ name: "Agent", value: "agent" },
|
|
1236
|
-
{ name: "User (human)", value: "user" }
|
|
1237
|
-
],
|
|
1238
|
-
displayOptions: { show: { operation: ["createTask", "assign"] } }
|
|
1239
|
-
},
|
|
1240
|
-
{
|
|
1241
|
-
displayName: "Executor ID",
|
|
1242
|
-
name: "benefitableValue",
|
|
1243
|
-
type: "number",
|
|
1244
|
-
default: 0,
|
|
1245
|
-
description: "Agent ID or user ID of the executor",
|
|
1246
|
-
displayOptions: { show: { operation: ["createTask", "assign"] } }
|
|
1247
|
-
},
|
|
1248
|
-
// --- status ---
|
|
1249
|
-
{
|
|
1250
|
-
displayName: "Status",
|
|
1251
|
-
name: "status",
|
|
1252
|
-
type: "options",
|
|
1253
|
-
options: STATUS_OPTIONS,
|
|
1254
|
-
default: 200,
|
|
1255
|
-
displayOptions: { show: { operation: ["createTask", "setStatus", "progress"] } }
|
|
1256
|
-
},
|
|
1257
|
-
// --- progress desc ---
|
|
1258
|
-
{
|
|
1259
|
-
displayName: "Progress Description",
|
|
1260
|
-
name: "progressDesc",
|
|
1261
|
-
type: "string",
|
|
1262
|
-
typeOptions: { rows: 4 },
|
|
1263
|
-
default: "",
|
|
1264
|
-
required: true,
|
|
1265
|
-
description: "Body of the progress comment (does not overwrite the task spec)",
|
|
1266
|
-
displayOptions: { show: { operation: ["progress"] } }
|
|
1267
|
-
},
|
|
1268
|
-
// --- call human (escalate) ---
|
|
1269
|
-
{
|
|
1270
|
-
displayName: "Call Kind",
|
|
1271
|
-
name: "callKind",
|
|
1272
|
-
type: "options",
|
|
1273
|
-
default: "review",
|
|
1274
|
-
options: [
|
|
1275
|
-
{ name: "Input (need clarification)", value: "input" },
|
|
1276
|
-
{ name: "Review (done, take a look)", value: "review" },
|
|
1277
|
-
{ name: "Stuck (blocked)", value: "stuck" }
|
|
1278
|
-
],
|
|
1279
|
-
description: "Type of escalation to a human (the call-human action)",
|
|
1280
|
-
displayOptions: { show: { operation: ["callHuman"] } }
|
|
1281
|
-
},
|
|
1282
|
-
{
|
|
1283
|
-
displayName: "Message",
|
|
1284
|
-
name: "callDesc",
|
|
1285
|
-
type: "string",
|
|
1286
|
-
typeOptions: { rows: 4 },
|
|
1287
|
-
default: "",
|
|
1288
|
-
required: true,
|
|
1289
|
-
description: "What to tell the human \u2014 context, question, or what was done",
|
|
1290
|
-
displayOptions: { show: { operation: ["callHuman"] } }
|
|
1291
|
-
}
|
|
1292
|
-
]
|
|
1293
|
-
};
|
|
1294
|
-
}
|
|
1295
|
-
async execute() {
|
|
1296
|
-
const items = this.getInputData();
|
|
1297
|
-
const out = [];
|
|
1298
|
-
const creds = await this.getCredentials("cekiApi");
|
|
1299
|
-
const token = creds.token;
|
|
1300
|
-
const client = new ContractClient(token);
|
|
1301
|
-
for (let i = 0; i < items.length; i++) {
|
|
1302
|
-
const op = this.getNodeParameter("operation", i);
|
|
1303
|
-
let result;
|
|
1304
|
-
switch (op) {
|
|
1305
|
-
case "listContracts":
|
|
1306
|
-
result = await client.listContracts();
|
|
1307
|
-
break;
|
|
1308
|
-
case "listTasks": {
|
|
1309
|
-
const contractId = this.getNodeParameter("contractId", i);
|
|
1310
|
-
result = await client.tasks(contractId);
|
|
1311
|
-
break;
|
|
1312
|
-
}
|
|
1313
|
-
case "getTask": {
|
|
1314
|
-
const eventId = this.getNodeParameter("eventId", i);
|
|
1315
|
-
result = await client.task(eventId);
|
|
1316
|
-
break;
|
|
1317
|
-
}
|
|
1318
|
-
case "myEvents":
|
|
1319
|
-
result = await client.myEvents();
|
|
1320
|
-
break;
|
|
1321
|
-
case "createTask": {
|
|
1322
|
-
const contractId = this.getNodeParameter("contractId", i);
|
|
1323
|
-
const label = this.getNodeParameter("label", i);
|
|
1324
|
-
const description = this.getNodeParameter("description", i) || "";
|
|
1325
|
-
const status = this.getNodeParameter("status", i);
|
|
1326
|
-
const bType = this.getNodeParameter("benefitableType", i);
|
|
1327
|
-
const bValue = this.getNodeParameter("benefitableValue", i);
|
|
1328
|
-
result = await client.create(contractId, {
|
|
1329
|
-
label,
|
|
1330
|
-
description: description || void 0,
|
|
1331
|
-
status,
|
|
1332
|
-
benefitable: bValue ? `${bType}:${bValue}` : void 0
|
|
1333
|
-
});
|
|
1334
|
-
break;
|
|
1335
|
-
}
|
|
1336
|
-
case "assign": {
|
|
1337
|
-
const eventId = this.getNodeParameter("eventId", i);
|
|
1338
|
-
const bType = this.getNodeParameter("benefitableType", i);
|
|
1339
|
-
const bValue = this.getNodeParameter("benefitableValue", i);
|
|
1340
|
-
if (!bValue) throw new Error("Executor ID is required for Assign");
|
|
1341
|
-
result = await client.propose(eventId, { benefitable: `${bType}:${bValue}` });
|
|
1342
|
-
break;
|
|
1343
|
-
}
|
|
1344
|
-
case "setStatus": {
|
|
1345
|
-
const eventId = this.getNodeParameter("eventId", i);
|
|
1346
|
-
const status = this.getNodeParameter("status", i);
|
|
1347
|
-
result = await client.propose(eventId, { status });
|
|
1348
|
-
break;
|
|
1349
|
-
}
|
|
1350
|
-
case "comment": {
|
|
1351
|
-
const eventId = this.getNodeParameter("eventId", i);
|
|
1352
|
-
const description = this.getNodeParameter("description", i) || "";
|
|
1353
|
-
if (!description) throw new Error("Comment text is required");
|
|
1354
|
-
result = await client.comment(eventId, { description });
|
|
1355
|
-
break;
|
|
1356
|
-
}
|
|
1357
|
-
case "progress": {
|
|
1358
|
-
const eventId = this.getNodeParameter("eventId", i);
|
|
1359
|
-
const status = this.getNodeParameter("status", i);
|
|
1360
|
-
const desc = this.getNodeParameter("progressDesc", i);
|
|
1361
|
-
result = await client.progress(eventId, { status, desc });
|
|
1362
|
-
break;
|
|
1363
|
-
}
|
|
1364
|
-
case "callHuman": {
|
|
1365
|
-
const eventId = this.getNodeParameter("eventId", i);
|
|
1366
|
-
const kind = this.getNodeParameter("callKind", i);
|
|
1367
|
-
const desc = this.getNodeParameter("callDesc", i);
|
|
1368
|
-
if (!desc) throw new Error("Message is required for Call Human");
|
|
1369
|
-
result = await client.callHuman(eventId, kind, desc);
|
|
1370
|
-
break;
|
|
1371
|
-
}
|
|
1372
|
-
case "poll":
|
|
1373
|
-
result = await client.poll();
|
|
1374
|
-
break;
|
|
1375
|
-
default:
|
|
1376
|
-
throw new Error(`Unknown operation: ${op}`);
|
|
1377
|
-
}
|
|
1378
|
-
out.push({ json: { op, result } });
|
|
1379
|
-
}
|
|
1380
|
-
return [out];
|
|
1381
|
-
}
|
|
1382
|
-
};
|
|
1383
|
-
|
|
1384
|
-
// nodes/recipes/CekiCaptchaScrape/CekiCaptchaScrape.node.ts
|
|
1385
|
-
var sleep2 = (ms) => new Promise((resolve) => {
|
|
1386
|
-
AbortSignal.timeout(ms).addEventListener("abort", () => resolve(), { once: true });
|
|
1387
|
-
});
|
|
1388
|
-
async function waitForSelector(browser, selector, timeoutMs, intervalMs = 500) {
|
|
1389
|
-
const expr = `!!document.querySelector(${JSON.stringify(selector)})`;
|
|
1390
|
-
const deadline = Date.now() + timeoutMs;
|
|
1391
|
-
let lastErr = null;
|
|
1392
|
-
while (Date.now() < deadline) {
|
|
1393
|
-
try {
|
|
1394
|
-
const res = await browser.send("Runtime.evaluate", { expression: expr, returnByValue: true });
|
|
1395
|
-
if (res?.result?.value === true) return true;
|
|
1396
|
-
} catch (e) {
|
|
1397
|
-
lastErr = e;
|
|
1398
|
-
}
|
|
1399
|
-
await sleep2(intervalMs);
|
|
1400
|
-
}
|
|
1401
|
-
throw new Error(
|
|
1402
|
-
`waitForSelector("${selector}") timed out after ${timeoutMs}ms${lastErr ? `: ${lastErr.message}` : ""}`
|
|
1403
|
-
);
|
|
1404
|
-
}
|
|
1405
|
-
async function extractHtml(browser, selector) {
|
|
1406
|
-
const expr = selector.trim() === "" || selector === "body" ? `document.body ? document.body.outerHTML : ''` : `(function(){ var el = document.querySelector(${JSON.stringify(selector)}); return el ? el.outerHTML : ''; })()`;
|
|
1407
|
-
const res = await browser.send("Runtime.evaluate", { expression: expr, returnByValue: true });
|
|
1408
|
-
return res?.result?.value ?? "";
|
|
1409
|
-
}
|
|
1410
|
-
var CekiCaptchaScrape = class {
|
|
1411
|
-
constructor() {
|
|
1412
|
-
this.description = {
|
|
1413
|
-
displayName: "Browser Ceki: Captcha-protected Scrape",
|
|
1414
|
-
name: "cekiCaptchaScrape",
|
|
1415
|
-
description: "Rent a human browser, wait, screenshot/HTML, release",
|
|
1416
|
-
icon: "file:ceki.png",
|
|
1417
|
-
group: ["transform"],
|
|
1418
|
-
version: 1,
|
|
1419
|
-
subtitle: "=rent in {{ $geo }} \u2192 snapshot",
|
|
1420
|
-
defaults: { name: "Browser Ceki: Captcha-protected Scrape" },
|
|
1421
|
-
inputs: ["main"],
|
|
1422
|
-
outputs: ["main"],
|
|
1423
|
-
credentials: [{ name: "cekiApi", required: true }],
|
|
1424
|
-
properties: [
|
|
1425
|
-
{
|
|
1426
|
-
displayName: "URL",
|
|
1427
|
-
name: "url",
|
|
1428
|
-
type: "string",
|
|
1429
|
-
default: "https://example.com",
|
|
1430
|
-
required: true,
|
|
1431
|
-
description: "Page protected by anti-bot / captcha"
|
|
1432
|
-
},
|
|
1433
|
-
{
|
|
1434
|
-
displayName: "Geo",
|
|
1435
|
-
name: "geo",
|
|
1436
|
-
type: "string",
|
|
1437
|
-
default: "RU",
|
|
1438
|
-
placeholder: "RU, EE, US\u2026"
|
|
1439
|
-
},
|
|
1440
|
-
{ displayName: "Max $/min", name: "maxPrice", type: "number", typeOptions: { numberPrecision: 4 }, default: 0.02 },
|
|
1441
|
-
{
|
|
1442
|
-
displayName: "Wait for Selector",
|
|
1443
|
-
name: "waitSelector",
|
|
1444
|
-
type: "string",
|
|
1445
|
-
default: "",
|
|
1446
|
-
placeholder: "CSS selector (optional)",
|
|
1447
|
-
description: "Wait until this selector appears in the DOM"
|
|
1448
|
-
},
|
|
1449
|
-
{
|
|
1450
|
-
displayName: "Wait Timeout (ms)",
|
|
1451
|
-
name: "waitTimeout",
|
|
1452
|
-
type: "number",
|
|
1453
|
-
default: 3e4
|
|
1454
|
-
},
|
|
1455
|
-
{
|
|
1456
|
-
displayName: "Extract HTML",
|
|
1457
|
-
name: "extractHtml",
|
|
1458
|
-
type: "boolean",
|
|
1459
|
-
default: true
|
|
1460
|
-
},
|
|
1461
|
-
{
|
|
1462
|
-
displayName: "HTML Selector",
|
|
1463
|
-
name: "htmlSelector",
|
|
1464
|
-
type: "string",
|
|
1465
|
-
default: "body",
|
|
1466
|
-
placeholder: 'CSS selector or "body"',
|
|
1467
|
-
description: "outerHTML of this selector is returned as `html`",
|
|
1468
|
-
displayOptions: { show: { extractHtml: [true] } }
|
|
1469
|
-
},
|
|
1470
|
-
{
|
|
1471
|
-
displayName: "Full Page Screenshot",
|
|
1472
|
-
name: "fullPage",
|
|
1473
|
-
type: "boolean",
|
|
1474
|
-
default: false
|
|
1475
|
-
}
|
|
1476
|
-
]
|
|
1477
|
-
};
|
|
1478
|
-
}
|
|
1479
|
-
async execute() {
|
|
1480
|
-
const items = this.getInputData();
|
|
1481
|
-
const out = [];
|
|
1482
|
-
const creds = await this.getCredentials("cekiApi");
|
|
1483
|
-
for (let i = 0; i < items.length; i++) {
|
|
1484
|
-
const url = this.getNodeParameter("url", i);
|
|
1485
|
-
const geo = this.getNodeParameter("geo", i);
|
|
1486
|
-
const maxPrice = this.getNodeParameter("maxPrice", i);
|
|
1487
|
-
const waitSelector = this.getNodeParameter("waitSelector", i) || "";
|
|
1488
|
-
const waitTimeout = this.getNodeParameter("waitTimeout", i);
|
|
1489
|
-
const extractHtmlFlag = this.getNodeParameter("extractHtml", i);
|
|
1490
|
-
const htmlSelector = this.getNodeParameter("htmlSelector", i) || "body";
|
|
1491
|
-
const fullPage = this.getNodeParameter("fullPage", i);
|
|
1492
|
-
const client = new CekiClient(creds.token);
|
|
1493
|
-
await client.connect();
|
|
1494
|
-
let browser;
|
|
1495
|
-
let scheduleId = 0;
|
|
1496
|
-
try {
|
|
1497
|
-
const list = await client.search({ geo: geo || void 0, max_price_per_min: maxPrice });
|
|
1498
|
-
if (!list.length) throw new Error(`No browsers in geo ${geo || "*"}`);
|
|
1499
|
-
scheduleId = list[0].schedule_id;
|
|
1500
|
-
browser = await client.rent(scheduleId);
|
|
1501
|
-
await browser.navigate(url);
|
|
1502
|
-
if (waitSelector) {
|
|
1503
|
-
await waitForSelector(browser, waitSelector, waitTimeout);
|
|
1504
|
-
}
|
|
1505
|
-
const shot = await browser.screenshot({ format: "base64", fullPage });
|
|
1506
|
-
const data = shot.data ?? (shot instanceof Buffer ? shot.toString("base64") : "");
|
|
1507
|
-
const binary = await this.helpers.prepareBinaryData(
|
|
1508
|
-
Buffer.from(data, "base64"),
|
|
1509
|
-
"captcha-scrape.png",
|
|
1510
|
-
"image/png"
|
|
1511
|
-
);
|
|
1512
|
-
const json = {
|
|
1513
|
-
url,
|
|
1514
|
-
geo,
|
|
1515
|
-
schedule_id: scheduleId
|
|
1516
|
-
};
|
|
1517
|
-
if (extractHtmlFlag) {
|
|
1518
|
-
json.html = await extractHtml(browser, htmlSelector);
|
|
1519
|
-
}
|
|
1520
|
-
out.push({ json, binary: { data: binary } });
|
|
1521
|
-
} finally {
|
|
1522
|
-
if (browser) {
|
|
1593
|
+
needFullClose = op === "close";
|
|
1594
|
+
} finally {
|
|
1523
1595
|
try {
|
|
1524
|
-
|
|
1596
|
+
if (needFullClose) {
|
|
1597
|
+
await client.close();
|
|
1598
|
+
} else {
|
|
1599
|
+
await client.disconnect();
|
|
1600
|
+
}
|
|
1525
1601
|
} catch {
|
|
1526
1602
|
}
|
|
1527
1603
|
}
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1604
|
+
} catch (error) {
|
|
1605
|
+
if (this.continueOnFail()) {
|
|
1606
|
+
out.push({ json: { error: error.message }, pairedItem: { item: i } });
|
|
1607
|
+
continue;
|
|
1531
1608
|
}
|
|
1609
|
+
throw new import_n8n_workflow.NodeApiError(this.getNode(), error, { itemIndex: i });
|
|
1532
1610
|
}
|
|
1533
1611
|
}
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
};
|
|
1537
|
-
|
|
1538
|
-
// nodes/recipes/CekiScreenshotGeo/CekiScreenshotGeo.node.ts
|
|
1539
|
-
var CekiScreenshotGeo = class {
|
|
1540
|
-
constructor() {
|
|
1541
|
-
this.description = {
|
|
1542
|
-
displayName: "Browser Ceki: Screenshot in Geo",
|
|
1543
|
-
name: "cekiScreenshotGeo",
|
|
1544
|
-
description: "Rent a browser in a given geo, screenshot a page, release",
|
|
1545
|
-
icon: "file:ceki.png",
|
|
1546
|
-
group: ["transform"],
|
|
1547
|
-
version: 1,
|
|
1548
|
-
subtitle: "=rent in {{ $geo }} \u2192 screenshot \u2192 release",
|
|
1549
|
-
defaults: { name: "Browser Ceki: Screenshot in Geo" },
|
|
1550
|
-
inputs: ["main"],
|
|
1551
|
-
outputs: ["main"],
|
|
1552
|
-
credentials: [{ name: "cekiApi", required: true }],
|
|
1553
|
-
properties: [
|
|
1554
|
-
{
|
|
1555
|
-
displayName: "URL",
|
|
1556
|
-
name: "url",
|
|
1557
|
-
type: "string",
|
|
1558
|
-
default: "https://ifconfig.me",
|
|
1559
|
-
required: true
|
|
1560
|
-
},
|
|
1561
|
-
{ displayName: "Geo", name: "geo", type: "string", default: "RU", placeholder: "RU, EE, US\u2026" },
|
|
1562
|
-
{ displayName: "Full page", name: "fullPage", type: "boolean", default: false },
|
|
1563
|
-
{ displayName: "Max $/min", name: "maxPrice", type: "number", default: 0.02 }
|
|
1564
|
-
]
|
|
1565
|
-
};
|
|
1566
|
-
}
|
|
1567
|
-
async execute() {
|
|
1568
|
-
const items = this.getInputData();
|
|
1569
|
-
const out = [];
|
|
1570
|
-
const creds = await this.getCredentials("cekiApi");
|
|
1571
|
-
for (let i = 0; i < items.length; i++) {
|
|
1572
|
-
const url = this.getNodeParameter("url", i);
|
|
1573
|
-
const geo = this.getNodeParameter("geo", i);
|
|
1574
|
-
const fullPage = this.getNodeParameter("fullPage", i);
|
|
1575
|
-
const maxPrice = this.getNodeParameter("maxPrice", i);
|
|
1576
|
-
const client = new CekiClient(creds.token);
|
|
1577
|
-
await client.connect();
|
|
1578
|
-
let browser;
|
|
1579
|
-
try {
|
|
1580
|
-
const list = await client.search({ geo, max_price_per_min: maxPrice });
|
|
1581
|
-
if (!list.length) throw new Error(`No browsers in geo ${geo}`);
|
|
1582
|
-
browser = await client.rent(list[0].schedule_id);
|
|
1583
|
-
await browser.navigate(url);
|
|
1584
|
-
const shot = await browser.screenshot({ format: "base64", fullPage });
|
|
1585
|
-
const binary = await this.helpers.prepareBinaryData(
|
|
1586
|
-
Buffer.from(shot.data, "base64"),
|
|
1587
|
-
"screenshot.png",
|
|
1588
|
-
"image/png"
|
|
1589
|
-
);
|
|
1590
|
-
out.push({
|
|
1591
|
-
json: { url, geo, schedule_id: list[0].schedule_id },
|
|
1592
|
-
binary: { data: binary }
|
|
1593
|
-
});
|
|
1594
|
-
} finally {
|
|
1595
|
-
if (browser) {
|
|
1596
|
-
try {
|
|
1597
|
-
await browser.close();
|
|
1598
|
-
} catch {
|
|
1599
|
-
}
|
|
1600
|
-
}
|
|
1612
|
+
if (touchedSessions.size) {
|
|
1613
|
+
for (const sid of touchedSessions) {
|
|
1601
1614
|
try {
|
|
1602
|
-
await
|
|
1615
|
+
await _stopSession(sid);
|
|
1603
1616
|
} catch {
|
|
1604
1617
|
}
|
|
1605
1618
|
}
|
|
@@ -1613,6 +1626,7 @@ var CekiApi = class {
|
|
|
1613
1626
|
constructor() {
|
|
1614
1627
|
this.name = "cekiApi";
|
|
1615
1628
|
this.displayName = "Ceki API";
|
|
1629
|
+
this.icon = { light: "file:ceki-light.svg", dark: "file:ceki-dark.svg" };
|
|
1616
1630
|
this.documentationUrl = "https://browser.ceki.me/docs#api-key";
|
|
1617
1631
|
this.properties = [
|
|
1618
1632
|
{
|
|
@@ -1645,10 +1659,7 @@ var CekiApi = class {
|
|
|
1645
1659
|
|
|
1646
1660
|
// index.ts
|
|
1647
1661
|
var nodes = [
|
|
1648
|
-
BrowserCeki
|
|
1649
|
-
CekiContract,
|
|
1650
|
-
CekiCaptchaScrape,
|
|
1651
|
-
CekiScreenshotGeo
|
|
1662
|
+
BrowserCeki
|
|
1652
1663
|
];
|
|
1653
1664
|
var credentials = [
|
|
1654
1665
|
CekiApi
|