@duckmind/dm-windows-x64 0.62.3 → 0.63.0
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/dm.exe +0 -0
- package/extensions/.dm-extensions.json +43 -183
- package/extensions/dm-context/src/context.js +1 -2
- package/extensions/dm-context/src/index.js +1 -2
- package/extensions/{greedysearch-dm/bin/cdp.mjs → dm-cua/bin/browser-cdp.mjs} +3 -3
- package/extensions/dm-cua/bin/browser-cua.mjs +1 -1
- package/extensions/{greedysearch-dm/bin/launch.mjs → dm-cua/bin/browser-launch.mjs} +12 -11
- package/extensions/dm-cua/index.js +1 -1
- package/extensions/dm-cua/src/browser-cua-lib.mjs +17 -12
- package/extensions/{greedysearch-dm/src/search → dm-cua/src/browser-helpers}/constants.mjs +11 -11
- package/extensions/{greedysearch-dm/src/utils → dm-cua/src/browser-helpers}/node-runtime.mjs +1 -1
- package/extensions/{greedysearch-dm/src/search → dm-cua/src/browser-helpers}/port-pid.mjs +1 -1
- package/extensions/dm-ponytail/extensions/ponytail.mjs +186 -0
- package/extensions/dm-ponytail/hooks/claude-codex-hooks.json +41 -0
- package/extensions/dm-ponytail/hooks/copilot-hooks.json +21 -0
- package/extensions/dm-ponytail/hooks/ponytail-activate.js +59 -0
- package/extensions/dm-ponytail/hooks/ponytail-config.js +116 -0
- package/extensions/dm-ponytail/hooks/ponytail-instructions.js +81 -0
- package/extensions/dm-ponytail/hooks/ponytail-mode-tracker.js +96 -0
- package/extensions/dm-ponytail/hooks/ponytail-runtime.js +79 -0
- package/extensions/dm-ponytail/hooks/ponytail-statusline.ps1 +24 -0
- package/extensions/dm-ponytail/hooks/ponytail-statusline.sh +18 -0
- package/extensions/dm-ponytail/hooks/ponytail-subagent.js +50 -0
- package/extensions/dm-ponytail/hooks/qoder-hooks.json +26 -0
- package/extensions/dm-ponytail/package.json +19 -0
- package/extensions/dm-ponytail/pi-extension/index.js +186 -0
- package/extensions/dm-ponytail/pi-extension/package.json +8 -0
- package/extensions/dm-ponytail/skills/ponytail/SKILL.md +120 -0
- package/extensions/dm-ponytail/skills/ponytail-audit/SKILL.md +41 -0
- package/extensions/dm-ponytail/skills/ponytail-debt/SKILL.md +44 -0
- package/extensions/dm-ponytail/skills/ponytail-gain/SKILL.md +50 -0
- package/extensions/dm-ponytail/skills/ponytail-help/SKILL.md +71 -0
- package/extensions/dm-ponytail/skills/ponytail-review/SKILL.md +57 -0
- package/package.json +2 -6
- package/extensions/dm-caveman/extensions/caveman.js +0 -300
- package/extensions/dm-caveman/package.json +0 -20
- package/extensions/dm-goal/package.json +0 -23
- package/extensions/dm-goal/src/goal.js +0 -914
- package/extensions/greedysearch-dm/bin/cdp-greedy.mjs +0 -40
- package/extensions/greedysearch-dm/bin/cdp-headless.mjs +0 -5
- package/extensions/greedysearch-dm/bin/cdp-visible.mjs +0 -5
- package/extensions/greedysearch-dm/bin/gschrome.mjs +0 -30
- package/extensions/greedysearch-dm/bin/kill-visible.mjs +0 -7
- package/extensions/greedysearch-dm/bin/launch-visible.mjs +0 -13
- package/extensions/greedysearch-dm/bin/mcp.mjs +0 -400
- package/extensions/greedysearch-dm/bin/search.mjs +0 -635
- package/extensions/greedysearch-dm/bin/visible.mjs +0 -22
- package/extensions/greedysearch-dm/extractors/bing-copilot.mjs +0 -344
- package/extensions/greedysearch-dm/extractors/chatgpt.mjs +0 -354
- package/extensions/greedysearch-dm/extractors/common.mjs +0 -794
- package/extensions/greedysearch-dm/extractors/consensus.mjs +0 -527
- package/extensions/greedysearch-dm/extractors/consent.mjs +0 -449
- package/extensions/greedysearch-dm/extractors/gemini.mjs +0 -291
- package/extensions/greedysearch-dm/extractors/google-ai.mjs +0 -86
- package/extensions/greedysearch-dm/extractors/logically.mjs +0 -589
- package/extensions/greedysearch-dm/extractors/perplexity.mjs +0 -370
- package/extensions/greedysearch-dm/extractors/selectors.mjs +0 -32
- package/extensions/greedysearch-dm/extractors/semantic-scholar.mjs +0 -199
- package/extensions/greedysearch-dm/index.js +0 -123
- package/extensions/greedysearch-dm/package.json +0 -27
- package/extensions/greedysearch-dm/skills/greedy-search/skill.md +0 -18
- package/extensions/greedysearch-dm/src/fetcher.mjs +0 -577
- package/extensions/greedysearch-dm/src/formatters/results.js +0 -95
- package/extensions/greedysearch-dm/src/formatters/sources.js +0 -57
- package/extensions/greedysearch-dm/src/formatters/synthesis.js +0 -49
- package/extensions/greedysearch-dm/src/github.mjs +0 -228
- package/extensions/greedysearch-dm/src/reddit.mjs +0 -154
- package/extensions/greedysearch-dm/src/search/browser-lifecycle.mjs +0 -340
- package/extensions/greedysearch-dm/src/search/challenge-detect.mjs +0 -156
- package/extensions/greedysearch-dm/src/search/chrome.mjs +0 -510
- package/extensions/greedysearch-dm/src/search/defaults.mjs +0 -10
- package/extensions/greedysearch-dm/src/search/engines.mjs +0 -79
- package/extensions/greedysearch-dm/src/search/fetch-source.mjs +0 -470
- package/extensions/greedysearch-dm/src/search/file-sources.mjs +0 -31
- package/extensions/greedysearch-dm/src/search/output.mjs +0 -51
- package/extensions/greedysearch-dm/src/search/paths.mjs +0 -48
- package/extensions/greedysearch-dm/src/search/pdf.mjs +0 -64
- package/extensions/greedysearch-dm/src/search/progress.mjs +0 -109
- package/extensions/greedysearch-dm/src/search/query.mjs +0 -21
- package/extensions/greedysearch-dm/src/search/recovery.mjs +0 -49
- package/extensions/greedysearch-dm/src/search/research.mjs +0 -2255
- package/extensions/greedysearch-dm/src/search/scale-aware.mjs +0 -61
- package/extensions/greedysearch-dm/src/search/simple-research.mjs +0 -418
- package/extensions/greedysearch-dm/src/search/sources.mjs +0 -412
- package/extensions/greedysearch-dm/src/search/synthesis-runner.mjs +0 -127
- package/extensions/greedysearch-dm/src/search/synthesis.mjs +0 -202
- package/extensions/greedysearch-dm/src/tools/greedy-search-handler.js +0 -209
- package/extensions/greedysearch-dm/src/tools/shared.js +0 -226
- package/extensions/greedysearch-dm/src/types.js +0 -0
- package/extensions/greedysearch-dm/src/utils/content.mjs +0 -38
- package/extensions/greedysearch-dm/src/utils/helpers.js +0 -22
- /package/extensions/{greedysearch-dm/src/search → dm-cua/src/browser-helpers}/minimize.mjs +0 -0
- /package/extensions/{greedysearch-dm/src/utils → dm-cua/src/browser-helpers}/system-cmds.mjs +0 -0
|
@@ -1,449 +0,0 @@
|
|
|
1
|
-
import { randomInt } from "node:crypto";
|
|
2
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
-
import http from "node:http";
|
|
4
|
-
const CONSENT_JS = `
|
|
5
|
-
(function() {
|
|
6
|
-
// Google consent page (consent.google.com)
|
|
7
|
-
var g = document.querySelector('#L2AGLb, button[jsname="b3VHJd"], .tHlp8d');
|
|
8
|
-
if (g) { g.click(); return 'google'; }
|
|
9
|
-
|
|
10
|
-
// OneTrust (used by many sites including Stack Overflow)
|
|
11
|
-
var ot = document.querySelector('#onetrust-accept-btn-handler, .onetrust-accept-btn-handler');
|
|
12
|
-
if (ot) { ot.click(); return 'onetrust'; }
|
|
13
|
-
|
|
14
|
-
// Generic "accept all" / "agree" buttons
|
|
15
|
-
var btns = Array.from(document.querySelectorAll('button, a[role=button]'));
|
|
16
|
-
var accept = btns.find(b => /^(accept all|accept cookies|agree|i agree|got it|allow all|allow cookies)$/i.test(b.innerText?.trim()));
|
|
17
|
-
if (accept) { accept.click(); return 'generic:' + accept.innerText.trim(); }
|
|
18
|
-
|
|
19
|
-
return null;
|
|
20
|
-
})()
|
|
21
|
-
`;
|
|
22
|
-
const VERIFY_DETECT_JS = `
|
|
23
|
-
(function() {
|
|
24
|
-
var url = document.location.href;
|
|
25
|
-
|
|
26
|
-
// --- Google "sorry" page (hard CAPTCHA, can't auto-solve) ---
|
|
27
|
-
if (url.includes('/sorry/') || url.includes('sorry.google')) return 'sorry-page';
|
|
28
|
-
|
|
29
|
-
// --- Microsoft account verification page ---
|
|
30
|
-
if (url.includes('login.microsoftonline.com') || url.includes('login.live.com') || url.includes('account.microsoft.com')) {
|
|
31
|
-
var msBtns = Array.from(document.querySelectorAll('button, input[type=submit], a'));
|
|
32
|
-
var msVerify = msBtns.find(b => /verify|continue|next/i.test(b.innerText?.trim() || b.value || ''));
|
|
33
|
-
if (msVerify) { msVerify.setAttribute('data-gs-verify','1'); return JSON.stringify({t:'sel',s:'[data-gs-verify="1"]',txt:msVerify.innerText?.trim()||msVerify.value}); }
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// --- Copilot / modal verification ---
|
|
37
|
-
var modal = document.querySelector('[role="dialog"], .b_modal, [class*="verify"], [class*="challenge"]');
|
|
38
|
-
if (modal) {
|
|
39
|
-
var modalBtns = Array.from(modal.querySelectorAll('button, a[role="button"], input[type="submit"]'));
|
|
40
|
-
var actionBtn = modalBtns.find(b => /^(continue|verify|submit|next|i agree|accept|got it)$/i.test(b.innerText?.trim() || b.value || ''));
|
|
41
|
-
if (actionBtn) { actionBtn.setAttribute('data-gs-verify','1'); return JSON.stringify({t:'sel',s:'[data-gs-verify="1"]',txt:actionBtn.innerText?.trim()}); }
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// --- Turnstile / Cloudflare challenge iframe (return coordinates for humanClickXY) ---
|
|
45
|
-
var turnstileIframe = document.querySelector('iframe[src*="challenges.cloudflare.com"], iframe[src*="turnstile"], iframe[title*="challenge"]');
|
|
46
|
-
if (turnstileIframe) {
|
|
47
|
-
var r = turnstileIframe.getBoundingClientRect();
|
|
48
|
-
return JSON.stringify({t:'xy',x:r.left+30,y:r.top+r.height/2});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// --- Cloudflare Turnstile widget inside closed shadow DOM (Copilot, etc.) ---
|
|
52
|
-
// The iframe is not queryable from main document, but the host container
|
|
53
|
-
// (#cf-turnstile) and the hidden response input are. When only the
|
|
54
|
-
// hidden response input matches (no #cf-turnstile host and no visible
|
|
55
|
-
// iframe), the actual challenge widget is rendered inside a closed
|
|
56
|
-
// shadow DOM and cannot be auto-clicked. Return a sentinel so callers
|
|
57
|
-
// know to surface this as needs-human verification instead of wasting
|
|
58
|
-
// time on a doomed waitForSelector.
|
|
59
|
-
var cfTurnstileHost = document.querySelector('#cf-turnstile');
|
|
60
|
-
if (cfTurnstileHost) {
|
|
61
|
-
var r2 = cfTurnstileHost.getBoundingClientRect();
|
|
62
|
-
return JSON.stringify({t:'xy',x:r2.left+r2.width/2,y:r2.top+r2.height/2});
|
|
63
|
-
}
|
|
64
|
-
// Hidden cf-chl-widget-*_response input present but no visible host:
|
|
65
|
-
// the widget is in closed shadow DOM. Signal this so handleVerification
|
|
66
|
-
// can return 'needs-human' rather than 'clear'.
|
|
67
|
-
var cfResponseInput = document.querySelector('input[name="cf-turnstile-response"], [id^="cf-chl-widget-"][id$="_response"]');
|
|
68
|
-
if (cfResponseInput && cfResponseInput.value === '') {
|
|
69
|
-
return 'cf-closed-shadow-dom';
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// --- Cloudflare challenge page ---
|
|
73
|
-
var cfCheckbox = document.querySelector('#cf-stage input[type="checkbox"], .ctp-checkbox-container input');
|
|
74
|
-
if (cfCheckbox) { cfCheckbox.setAttribute('data-gs-verify','1'); return JSON.stringify({t:'sel',s:'[data-gs-verify="1"]',txt:'cloudflare-checkbox'}); }
|
|
75
|
-
var cfBtn = document.querySelector('#challenge-form button, .cf-challenge button');
|
|
76
|
-
if (cfBtn) { cfBtn.setAttribute('data-gs-verify','1'); return JSON.stringify({t:'sel',s:'[data-gs-verify="1"]',txt:cfBtn.innerText?.trim()}); }
|
|
77
|
-
|
|
78
|
-
// --- Microsoft "I am human" button ---
|
|
79
|
-
var msHumanBtn = document.querySelector('button[id*="i0"], button[id*="id__"]');
|
|
80
|
-
if (msHumanBtn && /verify|human|robot|continue/i.test(msHumanBtn.innerText?.trim())) {
|
|
81
|
-
msHumanBtn.setAttribute('data-gs-verify','1'); return JSON.stringify({t:'sel',s:'[data-gs-verify="1"]',txt:msHumanBtn.innerText.trim()});
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// --- Generic verify/continue/proceed buttons (catch-all) ---
|
|
85
|
-
// IMPORTANT: exclude sign-in / OAuth buttons (e.g. "Continue with Google",
|
|
86
|
-
// "Continue with email", "Login or sign up for free"). These appear on
|
|
87
|
-
// many sites (Perplexity, ChatGPT, etc.) when the user isn't logged in,
|
|
88
|
-
// and clicking them triggers a sign-in flow that takes us to a login
|
|
89
|
-
// wall — a much worse outcome than the original search failure we were
|
|
90
|
-
// trying to recover from. The exclusion list must cover both OAuth
|
|
91
|
-
// providers AND generic "sign in / log in / with email" patterns.
|
|
92
|
-
var btns = Array.from(document.querySelectorAll('button, input[type=submit], a[role=button]'));
|
|
93
|
-
var verify = btns.find(b => {
|
|
94
|
-
var t = (b.innerText?.trim() || b.value || '').toLowerCase();
|
|
95
|
-
var isVerifyLike = (t === 'continue' || t === 'proceed' || t === 'next' ||
|
|
96
|
-
t.startsWith('verify ') || t.startsWith('human ') || t === 'i am human' || t.includes('robot check')) &&
|
|
97
|
-
!t.includes('verified') && !document.querySelector('iframe[src*="recaptcha"]');
|
|
98
|
-
if (!isVerifyLike) return false;
|
|
99
|
-
// Exclude OAuth / sign-in buttons to prevent accidental login flows
|
|
100
|
-
// — covers "Continue with Google", "Continue with Apple", "Continue
|
|
101
|
-
// with email", "Login or sign up", "Log in", "Sign in", "Sign up",
|
|
102
|
-
// "Single sign-on", and the visible panel "Login or sign up for free"
|
|
103
|
-
// text. The previous list missed "email" and "sso" which let the
|
|
104
|
-
// auto-click land on the email/SSO sign-in buttons on Perplexity's
|
|
105
|
-
// anonymous-mode homepage, navigating us into a login flow.
|
|
106
|
-
var isSignIn = new RegExp("sign.?in|log.?in|sign.?up|with\\s+(google|apple|email|github|facebook|microsoft|sso)|sso|auth", "i").test(t);
|
|
107
|
-
return !isSignIn;
|
|
108
|
-
});
|
|
109
|
-
if (verify) { verify.setAttribute('data-gs-verify','1'); return JSON.stringify({t:'sel',s:'[data-gs-verify="1"]',txt:verify.innerText?.trim()||verify.value}); }
|
|
110
|
-
|
|
111
|
-
// --- Google reCAPTCHA checkbox ---
|
|
112
|
-
var recaptchaCheckbox = document.querySelector('.recaptcha-checkbox-unchecked, input[type=checkbox][id*="recaptcha"]');
|
|
113
|
-
if (recaptchaCheckbox) { recaptchaCheckbox.setAttribute('data-gs-verify','1'); return JSON.stringify({t:'sel',s:'[data-gs-verify="1"]',txt:'recaptcha'}); }
|
|
114
|
-
|
|
115
|
-
return null;
|
|
116
|
-
})()
|
|
117
|
-
`;
|
|
118
|
-
const VERIFY_RETRY_JS = `
|
|
119
|
-
(function() {
|
|
120
|
-
var url = document.location.href;
|
|
121
|
-
var isVerifyPage = url.includes('/sorry/') ||
|
|
122
|
-
url.includes('challenges.cloudflare.com') ||
|
|
123
|
-
url.includes('login.microsoftonline.com') ||
|
|
124
|
-
document.querySelector('#challenge-running, #challenge-stage, .cf-turnstile, [role="dialog"]');
|
|
125
|
-
if (!isVerifyPage) return 'cleared';
|
|
126
|
-
|
|
127
|
-
var btns = Array.from(document.querySelectorAll('button, input[type=submit], a[role=button]'));
|
|
128
|
-
var btn = btns.find(b => {
|
|
129
|
-
var t = (b.innerText?.trim() || b.value || '').toLowerCase();
|
|
130
|
-
var isVerifyLike = t.includes('verify') || t.includes('human') || t.includes('robot') || t.includes('continue') || t.includes('next') || t.includes('submit');
|
|
131
|
-
if (!isVerifyLike) return false;
|
|
132
|
-
var isSignIn = /sign.in|log.in|google|microsoft|apple|facebook|github|auth/i.test(t);
|
|
133
|
-
return !isSignIn;
|
|
134
|
-
});
|
|
135
|
-
if (btn) { btn.setAttribute('data-gs-verify','1'); return JSON.stringify({t:'sel',s:'[data-gs-verify="1"]',txt:btn.innerText?.trim()||btn.value}); }
|
|
136
|
-
|
|
137
|
-
var cf = document.querySelector('#cf-stage input[type="checkbox"], .cf-turnstile input');
|
|
138
|
-
if (cf) { cf.setAttribute('data-gs-verify','1'); return JSON.stringify({t:'sel',s:'[data-gs-verify="1"]',txt:'turnstile'}); }
|
|
139
|
-
|
|
140
|
-
// Cloudflare Turnstile widget inside closed shadow DOM (detected via host container)
|
|
141
|
-
var cfTurnstileHost = document.querySelector('#cf-turnstile, [id^="cf-chl-widget-"]');
|
|
142
|
-
if (cfTurnstileHost) { return 'still-verifying'; }
|
|
143
|
-
|
|
144
|
-
var modal = document.querySelector('[role="dialog"], .b_modal, [class*="verify"]');
|
|
145
|
-
if (modal) {
|
|
146
|
-
var modalBtn = modal.querySelector('button, a[role="button"]');
|
|
147
|
-
if (modalBtn) { modalBtn.setAttribute('data-gs-verify','1'); return JSON.stringify({t:'sel',s:'[data-gs-verify="1"]',txt:modalBtn.innerText?.trim()}); }
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return 'still-verifying';
|
|
151
|
-
})()
|
|
152
|
-
`;
|
|
153
|
-
export async function dismissConsent(tab, cdp) {
|
|
154
|
-
const result = await cdp(["eval", tab, CONSENT_JS]).catch(() => null);
|
|
155
|
-
if (result && result !== "null") {
|
|
156
|
-
await new Promise((r) => setTimeout(r, 1500));
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
function rng(min, max) {
|
|
160
|
-
return randomInt(min * 1000, max * 1000) / 1000;
|
|
161
|
-
}
|
|
162
|
-
async function browserLevelClick(x, y) {
|
|
163
|
-
if (!globalThis.WebSocket)
|
|
164
|
-
return;
|
|
165
|
-
const profileDir = process.env.CDP_PROFILE_DIR;
|
|
166
|
-
if (!profileDir)
|
|
167
|
-
return;
|
|
168
|
-
const portFile = `${profileDir.replaceAll("\\", "/")}/DevToolsActivePort`;
|
|
169
|
-
if (!existsSync(portFile))
|
|
170
|
-
return;
|
|
171
|
-
const port = readFileSync(portFile, "utf8").trim().split(`
|
|
172
|
-
`)[0];
|
|
173
|
-
const version = await new Promise((resolve, reject) => {
|
|
174
|
-
const req = http.get(`http://localhost:${port}/json/version`, (res) => {
|
|
175
|
-
let body = "";
|
|
176
|
-
res.on("data", (d) => body += d);
|
|
177
|
-
res.on("end", () => {
|
|
178
|
-
try {
|
|
179
|
-
resolve(JSON.parse(body));
|
|
180
|
-
} catch {
|
|
181
|
-
reject(new Error("bad JSON"));
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
req.on("error", reject);
|
|
186
|
-
req.setTimeout(1000, () => {
|
|
187
|
-
req.destroy();
|
|
188
|
-
reject(new Error("timeout"));
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
const ws = new globalThis.WebSocket(version.webSocketDebuggerUrl);
|
|
192
|
-
let msgId = 0;
|
|
193
|
-
await new Promise((resolve) => {
|
|
194
|
-
ws.onopen = async () => {
|
|
195
|
-
const send = (method, params) => new Promise((r) => {
|
|
196
|
-
const id = ++msgId;
|
|
197
|
-
const handler = (evt) => {
|
|
198
|
-
if (JSON.parse(evt.data).id === id) {
|
|
199
|
-
ws.removeEventListener("message", handler);
|
|
200
|
-
r();
|
|
201
|
-
}
|
|
202
|
-
};
|
|
203
|
-
ws.addEventListener("message", handler);
|
|
204
|
-
ws.send(JSON.stringify({ id, method, params }));
|
|
205
|
-
});
|
|
206
|
-
const cx = x + rng(-2, 2);
|
|
207
|
-
const cy = y + rng(-2, 2);
|
|
208
|
-
await send("Input.dispatchMouseEvent", {
|
|
209
|
-
type: "mouseMoved",
|
|
210
|
-
x: cx,
|
|
211
|
-
y: cy,
|
|
212
|
-
button: "none"
|
|
213
|
-
});
|
|
214
|
-
await new Promise((r) => setTimeout(r, rng(80, 160)));
|
|
215
|
-
await send("Input.dispatchMouseEvent", {
|
|
216
|
-
type: "mousePressed",
|
|
217
|
-
x: cx,
|
|
218
|
-
y: cy,
|
|
219
|
-
button: "left",
|
|
220
|
-
clickCount: 1
|
|
221
|
-
});
|
|
222
|
-
await new Promise((r) => setTimeout(r, rng(30, 80)));
|
|
223
|
-
await send("Input.dispatchMouseEvent", {
|
|
224
|
-
type: "mouseReleased",
|
|
225
|
-
x: cx + rng(-1, 1),
|
|
226
|
-
y: cy + rng(-1, 1),
|
|
227
|
-
button: "left",
|
|
228
|
-
clickCount: 1
|
|
229
|
-
});
|
|
230
|
-
setTimeout(() => {
|
|
231
|
-
ws.close();
|
|
232
|
-
resolve();
|
|
233
|
-
}, 200);
|
|
234
|
-
};
|
|
235
|
-
ws.onerror = () => resolve();
|
|
236
|
-
setTimeout(resolve, 3000);
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
export async function humanClickXY(tab, cdpFn, x, y) {
|
|
240
|
-
const cx = Number.parseFloat(x);
|
|
241
|
-
const cy = Number.parseFloat(y);
|
|
242
|
-
if (Number.isNaN(cx) || Number.isNaN(cy)) {
|
|
243
|
-
throw new Error(`humanClickXY: invalid coordinates (${x}, ${y})`);
|
|
244
|
-
}
|
|
245
|
-
const base = { button: "left", clickCount: 1, modifiers: 0 };
|
|
246
|
-
const jx = cx + rng(-3, 3);
|
|
247
|
-
const jy = cy + rng(-3, 3);
|
|
248
|
-
await cdpFn([
|
|
249
|
-
"evalraw",
|
|
250
|
-
tab,
|
|
251
|
-
"Input.dispatchMouseEvent",
|
|
252
|
-
JSON.stringify({ ...base, type: "mouseMoved", x: jx, y: jy })
|
|
253
|
-
]);
|
|
254
|
-
await new Promise((r) => setTimeout(r, rng(80, 180)));
|
|
255
|
-
const px = cx + rng(-2, 2);
|
|
256
|
-
const py = cy + rng(-2, 2);
|
|
257
|
-
await cdpFn([
|
|
258
|
-
"evalraw",
|
|
259
|
-
tab,
|
|
260
|
-
"Input.dispatchMouseEvent",
|
|
261
|
-
JSON.stringify({ ...base, type: "mousePressed", x: px, y: py })
|
|
262
|
-
]);
|
|
263
|
-
await new Promise((r) => setTimeout(r, rng(30, 90)));
|
|
264
|
-
const rx = px + rng(-1, 1);
|
|
265
|
-
const ry = py + rng(-1, 1);
|
|
266
|
-
await cdpFn([
|
|
267
|
-
"evalraw",
|
|
268
|
-
tab,
|
|
269
|
-
"Input.dispatchMouseEvent",
|
|
270
|
-
JSON.stringify({ ...base, type: "mouseReleased", x: rx, y: ry })
|
|
271
|
-
]);
|
|
272
|
-
await browserLevelClick(cx, cy).catch(() => {});
|
|
273
|
-
await new Promise((r) => setTimeout(r, rng(100, 300)));
|
|
274
|
-
return `human-clicked at (${cx.toFixed(0)}, ${cy.toFixed(0)})`;
|
|
275
|
-
}
|
|
276
|
-
export async function humanClickElement(tab, cdpFn, selector) {
|
|
277
|
-
const rect = await cdpFn([
|
|
278
|
-
"eval",
|
|
279
|
-
tab,
|
|
280
|
-
`(function() {
|
|
281
|
-
var el = document.querySelector('${selector.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}');
|
|
282
|
-
if (!el) return 'null';
|
|
283
|
-
var r = el.getBoundingClientRect();
|
|
284
|
-
return JSON.stringify({x: r.left + r.width / 2, y: r.top + r.height / 2, w: r.width, h: r.height});
|
|
285
|
-
})()`
|
|
286
|
-
]).catch(() => "null");
|
|
287
|
-
if (!rect || rect === "null") {
|
|
288
|
-
return null;
|
|
289
|
-
}
|
|
290
|
-
const parsed = JSON.parse(rect);
|
|
291
|
-
if (parsed.w === 0 || parsed.h === 0 || parsed.x === 0 && parsed.y === 0) {
|
|
292
|
-
return null;
|
|
293
|
-
}
|
|
294
|
-
const { x, y } = parsed;
|
|
295
|
-
return humanClickXY(tab, cdpFn, x, y);
|
|
296
|
-
}
|
|
297
|
-
function tryHumanClick(tab, cdp, detectResult) {
|
|
298
|
-
if (!detectResult || detectResult === "null" || detectResult === "cleared" || detectResult === "still-verifying" || detectResult === "cf-closed-shadow-dom")
|
|
299
|
-
return Promise.resolve("no-challenge");
|
|
300
|
-
try {
|
|
301
|
-
const info = JSON.parse(detectResult);
|
|
302
|
-
if (info.t === "sel" && info.s) {
|
|
303
|
-
process.stderr.write(`[greedysearch] Human-clicking "${info.txt}" via CDP...
|
|
304
|
-
`);
|
|
305
|
-
return humanClickElement(tab, cdp, info.s).then((r) => r !== null ? "clicked" : "cant-click");
|
|
306
|
-
}
|
|
307
|
-
if (info.t === "xy") {
|
|
308
|
-
if (!info.x && !info.y)
|
|
309
|
-
return Promise.resolve("cant-click");
|
|
310
|
-
process.stderr.write(`[greedysearch] Human-clicking at (${info.x.toFixed(0)}, ${info.y.toFixed(0)})...
|
|
311
|
-
`);
|
|
312
|
-
return humanClickXY(tab, cdp, info.x, info.y).then(() => "clicked");
|
|
313
|
-
}
|
|
314
|
-
} catch {}
|
|
315
|
-
return Promise.resolve("no-challenge");
|
|
316
|
-
}
|
|
317
|
-
export async function detectVerificationChallenge(tab, cdp) {
|
|
318
|
-
const result = await cdp(["eval", tab, VERIFY_DETECT_JS]).catch(() => null);
|
|
319
|
-
if (result === "cf-closed-shadow-dom") {
|
|
320
|
-
const cfIframe = await findCloudflareIframeViaPierce(tab, cdp).catch(() => null);
|
|
321
|
-
if (cfIframe)
|
|
322
|
-
return cfIframe;
|
|
323
|
-
return result;
|
|
324
|
-
}
|
|
325
|
-
if (result && result !== "null")
|
|
326
|
-
return result;
|
|
327
|
-
return null;
|
|
328
|
-
}
|
|
329
|
-
async function findCloudflareIframeViaPierce(tab, cdp) {
|
|
330
|
-
if (typeof cdp !== "function")
|
|
331
|
-
return null;
|
|
332
|
-
await cdp(["evalraw", tab, "DOM.enable", "{}"]).catch(() => {});
|
|
333
|
-
const doc = await cdp(["evalraw", tab, "DOM.getDocument", JSON.stringify({ depth: -1, pierce: true })]).catch(() => null);
|
|
334
|
-
if (!doc)
|
|
335
|
-
return null;
|
|
336
|
-
let docParsed;
|
|
337
|
-
try {
|
|
338
|
-
docParsed = JSON.parse(doc);
|
|
339
|
-
} catch {
|
|
340
|
-
return null;
|
|
341
|
-
}
|
|
342
|
-
if (docParsed.error || !docParsed.root)
|
|
343
|
-
return null;
|
|
344
|
-
const root = docParsed.root;
|
|
345
|
-
const found = await walkForCfIframe(root, tab, cdp);
|
|
346
|
-
return found;
|
|
347
|
-
}
|
|
348
|
-
async function walkForCfIframe(node, tab, cdp) {
|
|
349
|
-
if (!node)
|
|
350
|
-
return null;
|
|
351
|
-
const children = [];
|
|
352
|
-
if (node.shadowRoots && node.shadowRoots.length > 0) {
|
|
353
|
-
for (const s of node.shadowRoots) {
|
|
354
|
-
children.push(s);
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
if (node.children) {
|
|
358
|
-
for (const c of node.children)
|
|
359
|
-
children.push(c);
|
|
360
|
-
}
|
|
361
|
-
for (const child of children) {
|
|
362
|
-
if (child.nodeName === "IFRAME") {
|
|
363
|
-
const attrs = child.attributes || [];
|
|
364
|
-
const srcIdx = attrs.indexOf("src");
|
|
365
|
-
const src = srcIdx >= 0 ? attrs[srcIdx + 1] : "";
|
|
366
|
-
if (src && /challenges\.cloudflare\.com|turnstile/i.test(src) && child.backendNodeId) {
|
|
367
|
-
const boxRes = await cdp([
|
|
368
|
-
"evalraw",
|
|
369
|
-
tab,
|
|
370
|
-
"DOM.getBoxModel",
|
|
371
|
-
JSON.stringify({ backendNodeId: child.backendNodeId })
|
|
372
|
-
]).catch(() => null);
|
|
373
|
-
if (!boxRes)
|
|
374
|
-
continue;
|
|
375
|
-
let boxParsed;
|
|
376
|
-
try {
|
|
377
|
-
boxParsed = JSON.parse(boxRes);
|
|
378
|
-
} catch {
|
|
379
|
-
continue;
|
|
380
|
-
}
|
|
381
|
-
const content = boxParsed?.model?.content || boxParsed?.result?.model?.content;
|
|
382
|
-
if (!content || content.length < 8)
|
|
383
|
-
continue;
|
|
384
|
-
const x1 = content[0];
|
|
385
|
-
const y1 = content[1];
|
|
386
|
-
const x3 = content[4];
|
|
387
|
-
const y3 = content[5];
|
|
388
|
-
const width = x3 - x1;
|
|
389
|
-
const height = y3 - y1;
|
|
390
|
-
if (width < 50 || height < 20)
|
|
391
|
-
continue;
|
|
392
|
-
const checkboxX = x1 + width * 0.25;
|
|
393
|
-
const checkboxY = y1 + height * 0.5;
|
|
394
|
-
process.stderr.write(`[greedysearch] Found CF iframe via CDP pierce at (${x1.toFixed(0)}, ${y1.toFixed(0)}) ${width.toFixed(0)}x${height.toFixed(0)}, clicking checkbox at (${checkboxX.toFixed(0)}, ${checkboxY.toFixed(0)})
|
|
395
|
-
`);
|
|
396
|
-
return JSON.stringify({ t: "xy", x: checkboxX, y: checkboxY });
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
const deeper = await walkForCfIframe(child, tab, cdp);
|
|
400
|
-
if (deeper)
|
|
401
|
-
return deeper;
|
|
402
|
-
}
|
|
403
|
-
return null;
|
|
404
|
-
}
|
|
405
|
-
export async function handleVerification(tab, cdp, waitMs = 30000) {
|
|
406
|
-
const result = await detectVerificationChallenge(tab, cdp);
|
|
407
|
-
if (!result)
|
|
408
|
-
return "clear";
|
|
409
|
-
if (result === "sorry-page") {
|
|
410
|
-
process.stderr.write(`[greedysearch] Google CAPTCHA detected — please solve it in the browser window (waiting up to ${Math.floor(waitMs / 1000)}s)...
|
|
411
|
-
`);
|
|
412
|
-
const deadline = Date.now() + waitMs;
|
|
413
|
-
while (Date.now() < deadline) {
|
|
414
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
415
|
-
const url = await cdp(["eval", tab, "document.location.href"]).catch(() => "");
|
|
416
|
-
if (!url.includes("/sorry/"))
|
|
417
|
-
return "cleared-by-user";
|
|
418
|
-
}
|
|
419
|
-
return "needs-human";
|
|
420
|
-
}
|
|
421
|
-
const clickResult = await tryHumanClick(tab, cdp, result);
|
|
422
|
-
if (clickResult === "clicked") {
|
|
423
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
424
|
-
const deadline = Date.now() + waitMs;
|
|
425
|
-
while (Date.now() < deadline) {
|
|
426
|
-
const retryResult = await cdp(["eval", tab, VERIFY_RETRY_JS]).catch(() => null);
|
|
427
|
-
if (retryResult === "cleared" || !retryResult || retryResult === "null") {
|
|
428
|
-
process.stderr.write(`[greedysearch] Verification cleared.
|
|
429
|
-
`);
|
|
430
|
-
return "clicked";
|
|
431
|
-
}
|
|
432
|
-
if (retryResult !== "still-verifying") {
|
|
433
|
-
await tryHumanClick(tab, cdp, retryResult);
|
|
434
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
435
|
-
} else {
|
|
436
|
-
await new Promise((r) => setTimeout(r, 1500));
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
process.stderr.write(`[greedysearch] Verification may require manual intervention.
|
|
440
|
-
`);
|
|
441
|
-
return "needs-human";
|
|
442
|
-
}
|
|
443
|
-
if (clickResult === "cant-click") {
|
|
444
|
-
process.stderr.write(`[greedysearch] Verification challenge detected but cannot be auto-clicked — please solve it manually in the visible browser window.
|
|
445
|
-
`);
|
|
446
|
-
return "needs-human";
|
|
447
|
-
}
|
|
448
|
-
return "clear";
|
|
449
|
-
}
|