@duckmind/dm-windows-x64 0.60.4 → 0.60.8
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 +70 -123
- package/extensions/dm-9router-ext/src/index.js +410 -5
- package/extensions/dm-caveman/extensions/caveman.js +283 -12
- package/extensions/dm-cliproxy/index.js +182 -2
- package/extensions/dm-cliproxy/scripts/check-config-migration.js +66 -8
- package/extensions/dm-cliproxy/src/apply.js +228 -1
- package/extensions/dm-cliproxy/src/cache.js +42 -1
- package/extensions/dm-cliproxy/src/commands.js +50 -2
- package/extensions/dm-cliproxy/src/compat.js +81 -1
- package/extensions/dm-cliproxy/src/config.js +192 -2
- package/extensions/dm-cliproxy/src/conflicts.js +46 -1
- package/extensions/dm-cliproxy/src/fetch-models.js +190 -1
- package/extensions/dm-cliproxy/src/fetch-usage.js +41 -1
- package/extensions/dm-cliproxy/src/log.js +23 -1
- package/extensions/dm-cliproxy/src/status-quota.js +77 -1
- package/extensions/dm-cliproxy/src/ui-frame.js +50 -1
- package/extensions/dm-cliproxy/src/ui-hub/hub.js +199 -2
- package/extensions/dm-cliproxy/src/ui-hub/index.js +26 -2
- package/extensions/dm-cliproxy/src/ui-hub/shell.js +46 -1
- package/extensions/dm-cliproxy/src/ui-hub/view-diagnostics.js +69 -1
- package/extensions/dm-cliproxy/src/ui-hub/view-models.js +379 -1
- package/extensions/dm-cliproxy/src/ui-hub/view-usage.js +106 -1
- package/extensions/dm-cliproxy/src/ui-picker/catalog.js +41 -1
- package/extensions/dm-cliproxy/src/ui-picker/mutate.js +115 -1
- package/extensions/dm-cliproxy/src/ui-picker/prompt-confirm.js +35 -1
- package/extensions/dm-cliproxy/src/ui-picker/prompt-name.js +62 -1
- package/extensions/dm-cliproxy/src/ui-picker/providers.js +45 -1
- package/extensions/dm-cliproxy/src/ui-picker/render-text.js +34 -1
- package/extensions/dm-cliproxy/src/ui-picker/rows.js +57 -1
- package/extensions/dm-cliproxy/src/ui-setup.js +260 -2
- package/extensions/dm-cliproxy/src/ui-usage.js +151 -1
- package/extensions/dm-cliproxy/src/usage-shared-cache.js +97 -1
- package/extensions/dm-context/src/context.js +144 -1
- package/extensions/dm-context/src/index.js +339 -7
- package/extensions/dm-context/src/utils.js +9 -1
- package/extensions/dm-cua/bin/browser-cua.mjs +73 -8
- package/extensions/dm-cua/index.js +75 -6
- package/extensions/dm-cua/src/browser-cua-lib.mjs +490 -6
- package/extensions/dm-cua/src/browser-install.mjs +331 -2
- package/extensions/dm-fff/src/index.js +688 -12
- package/extensions/dm-fff/src/query.js +60 -1
- package/extensions/dm-goal/src/goal.js +894 -23
- package/extensions/dm-image2/index.js +103 -9
- package/extensions/dm-image2/src/image-lib.mjs +1275 -8
- package/extensions/dm-subagents/install.mjs +62 -8
- package/extensions/dm-subagents/src/agents/agent-management.js +1190 -36
- package/extensions/dm-subagents/src/agents/agent-memory.js +216 -6
- package/extensions/dm-subagents/src/agents/agent-scope.js +5 -1
- package/extensions/dm-subagents/src/agents/agent-selection.js +20 -1
- package/extensions/dm-subagents/src/agents/agent-serializer.js +120 -5
- package/extensions/dm-subagents/src/agents/agents.js +1139 -11
- package/extensions/dm-subagents/src/agents/chain-serializer.js +299 -11
- package/extensions/dm-subagents/src/agents/frontmatter.js +65 -6
- package/extensions/dm-subagents/src/agents/identity.js +29 -1
- package/extensions/dm-subagents/src/agents/proactive-skills.js +141 -1
- package/extensions/dm-subagents/src/agents/skills.js +614 -6
- package/extensions/dm-subagents/src/extension/config.js +35 -2
- package/extensions/dm-subagents/src/extension/control-notices.js +69 -4
- package/extensions/dm-subagents/src/extension/doctor.js +172 -15
- package/extensions/dm-subagents/src/extension/fanout-child.js +158 -231
- package/extensions/dm-subagents/src/extension/index.js +521 -359
- package/extensions/dm-subagents/src/extension/rpc.js +266 -7
- package/extensions/dm-subagents/src/extension/schemas.js +275 -1
- package/extensions/dm-subagents/src/extension/tool-description.js +111 -6
- package/extensions/dm-subagents/src/intercom/intercom-bridge.js +126 -4
- package/extensions/dm-subagents/src/intercom/native-supervisor-channel.js +452 -5
- package/extensions/dm-subagents/src/intercom/result-intercom.js +319 -3
- package/extensions/dm-subagents/src/profiles/profiles.js +458 -3
- package/extensions/dm-subagents/src/runs/background/async-execution.js +834 -40
- package/extensions/dm-subagents/src/runs/background/async-job-tracker.js +435 -14
- package/extensions/dm-subagents/src/runs/background/async-resume.js +334 -8
- package/extensions/dm-subagents/src/runs/background/async-status.js +313 -12
- package/extensions/dm-subagents/src/runs/background/chain-append.js +245 -2
- package/extensions/dm-subagents/src/runs/background/chain-root-attachment.js +136 -1
- package/extensions/dm-subagents/src/runs/background/completion-batcher.js +94 -1
- package/extensions/dm-subagents/src/runs/background/completion-dedupe.js +54 -1
- package/extensions/dm-subagents/src/runs/background/control-channel.js +190 -1
- package/extensions/dm-subagents/src/runs/background/fleet-view.js +483 -17
- package/extensions/dm-subagents/src/runs/background/notify.js +129 -3
- package/extensions/dm-subagents/src/runs/background/parallel-groups.js +34 -1
- package/extensions/dm-subagents/src/runs/background/result-watcher.js +236 -6
- package/extensions/dm-subagents/src/runs/background/run-id-resolver.js +76 -4
- package/extensions/dm-subagents/src/runs/background/run-status.js +427 -23
- package/extensions/dm-subagents/src/runs/background/scheduled-runs.js +487 -4
- package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.js +306 -9
- package/extensions/dm-subagents/src/runs/background/subagent-runner.js +2849 -73
- package/extensions/dm-subagents/src/runs/background/top-level-async.js +5 -1
- package/extensions/dm-subagents/src/runs/background/wait.js +206 -11
- package/extensions/dm-subagents/src/runs/foreground/chain-clarify.js +1013 -12
- package/extensions/dm-subagents/src/runs/foreground/chain-execution.js +980 -101
- package/extensions/dm-subagents/src/runs/foreground/execution.js +1165 -45
- package/extensions/dm-subagents/src/runs/foreground/subagent-executor.js +3157 -222
- package/extensions/dm-subagents/src/runs/shared/acceptance.js +835 -3
- package/extensions/dm-subagents/src/runs/shared/chain-outputs.js +104 -1
- package/extensions/dm-subagents/src/runs/shared/completion-guard.js +116 -3
- package/extensions/dm-subagents/src/runs/shared/dm-args.js +208 -1
- package/extensions/dm-subagents/src/runs/shared/dm-spawn.js +90 -1
- package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.js +282 -1
- package/extensions/dm-subagents/src/runs/shared/long-running-guard.js +148 -1
- package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.js +305 -1
- package/extensions/dm-subagents/src/runs/shared/model-fallback.js +194 -1
- package/extensions/dm-subagents/src/runs/shared/model-scope.js +65 -1
- package/extensions/dm-subagents/src/runs/shared/nested-events.js +851 -8
- package/extensions/dm-subagents/src/runs/shared/nested-path.js +41 -1
- package/extensions/dm-subagents/src/runs/shared/nested-render.js +105 -1
- package/extensions/dm-subagents/src/runs/shared/parallel-utils.js +81 -4
- package/extensions/dm-subagents/src/runs/shared/run-history.js +51 -4
- package/extensions/dm-subagents/src/runs/shared/single-output.js +149 -8
- package/extensions/dm-subagents/src/runs/shared/structured-output.js +58 -1
- package/extensions/dm-subagents/src/runs/shared/subagent-control.js +166 -5
- package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.js +329 -13
- package/extensions/dm-subagents/src/runs/shared/tool-budget.js +73 -1
- package/extensions/dm-subagents/src/runs/shared/turn-budget.js +47 -4
- package/extensions/dm-subagents/src/runs/shared/workflow-graph.js +196 -1
- package/extensions/dm-subagents/src/runs/shared/worktree.js +435 -3
- package/extensions/dm-subagents/src/shared/artifacts.js +92 -2
- package/extensions/dm-subagents/src/shared/atomic-json.js +55 -1
- package/extensions/dm-subagents/src/shared/child-transcript.js +167 -5
- package/extensions/dm-subagents/src/shared/file-coalescer.js +25 -1
- package/extensions/dm-subagents/src/shared/fork-context.js +147 -4
- package/extensions/dm-subagents/src/shared/formatters.js +98 -7
- package/extensions/dm-subagents/src/shared/jsonl-writer.js +56 -2
- package/extensions/dm-subagents/src/shared/model-info.js +62 -1
- package/extensions/dm-subagents/src/shared/post-exit-stdio-guard.js +68 -1
- package/extensions/dm-subagents/src/shared/session-identity.js +6 -1
- package/extensions/dm-subagents/src/shared/session-tokens.js +39 -2
- package/extensions/dm-subagents/src/shared/settings.js +198 -11
- package/extensions/dm-subagents/src/shared/status-format.js +53 -1
- package/extensions/dm-subagents/src/shared/types.js +184 -6
- package/extensions/dm-subagents/src/shared/utils.js +462 -2
- package/extensions/dm-subagents/src/slash/prompt-template-bridge.js +288 -1
- package/extensions/dm-subagents/src/slash/prompt-workflows.js +297 -7
- package/extensions/dm-subagents/src/slash/slash-bridge.js +118 -1
- package/extensions/dm-subagents/src/slash/slash-commands.js +1287 -31
- package/extensions/dm-subagents/src/slash/slash-live-state.js +240 -4
- package/extensions/dm-subagents/src/tui/render-helpers.js +64 -1
- package/extensions/dm-subagents/src/tui/render.js +1542 -4
- package/extensions/dm-usage/index.js +1294 -9
- package/extensions/greedysearch-dm/bin/cdp-greedy.mjs +40 -9
- package/extensions/greedysearch-dm/bin/cdp-headless.mjs +5 -2
- package/extensions/greedysearch-dm/bin/cdp-visible.mjs +5 -2
- package/extensions/greedysearch-dm/bin/cdp.mjs +896 -30
- package/extensions/greedysearch-dm/bin/gschrome.mjs +30 -2
- package/extensions/greedysearch-dm/bin/kill-visible.mjs +7 -2
- package/extensions/greedysearch-dm/bin/launch-visible.mjs +13 -2
- package/extensions/greedysearch-dm/bin/launch.mjs +282 -10
- package/extensions/greedysearch-dm/bin/mcp.mjs +386 -361
- package/extensions/greedysearch-dm/bin/search.mjs +620 -540
- package/extensions/greedysearch-dm/bin/visible.mjs +22 -2
- package/extensions/greedysearch-dm/extractors/bing-copilot.mjs +329 -579
- package/extensions/greedysearch-dm/extractors/chatgpt.mjs +301 -583
- package/extensions/greedysearch-dm/extractors/common.mjs +408 -32
- package/extensions/greedysearch-dm/extractors/consensus.mjs +376 -365
- package/extensions/greedysearch-dm/extractors/consent.mjs +303 -14
- package/extensions/greedysearch-dm/extractors/gemini.mjs +228 -592
- package/extensions/greedysearch-dm/extractors/google-ai.mjs +78 -499
- package/extensions/greedysearch-dm/extractors/logically.mjs +270 -347
- package/extensions/greedysearch-dm/extractors/perplexity.mjs +243 -581
- package/extensions/greedysearch-dm/extractors/selectors.mjs +32 -1
- package/extensions/greedysearch-dm/extractors/semantic-scholar.mjs +130 -317
- package/extensions/greedysearch-dm/index.js +123 -23
- package/extensions/greedysearch-dm/src/fetcher.mjs +576 -2
- package/extensions/greedysearch-dm/src/formatters/results.js +95 -10
- package/extensions/greedysearch-dm/src/formatters/sources.js +57 -1
- package/extensions/greedysearch-dm/src/formatters/synthesis.js +49 -1
- package/extensions/greedysearch-dm/src/github.mjs +222 -7
- package/extensions/greedysearch-dm/src/reddit.mjs +145 -14
- package/extensions/greedysearch-dm/src/search/browser-lifecycle.mjs +340 -9
- package/extensions/greedysearch-dm/src/search/challenge-detect.mjs +112 -4
- package/extensions/greedysearch-dm/src/search/chrome.mjs +486 -285
- package/extensions/greedysearch-dm/src/search/constants.mjs +109 -8
- package/extensions/greedysearch-dm/src/search/defaults.mjs +10 -1
- package/extensions/greedysearch-dm/src/search/engines.mjs +79 -9
- package/extensions/greedysearch-dm/src/search/fetch-source.mjs +441 -349
- package/extensions/greedysearch-dm/src/search/file-sources.mjs +29 -7
- package/extensions/greedysearch-dm/src/search/minimize.mjs +86 -1
- package/extensions/greedysearch-dm/src/search/output.mjs +51 -5
- package/extensions/greedysearch-dm/src/search/paths.mjs +48 -1
- package/extensions/greedysearch-dm/src/search/pdf.mjs +63 -2
- package/extensions/greedysearch-dm/src/search/port-pid.mjs +69 -1
- package/extensions/greedysearch-dm/src/search/progress.mjs +109 -2
- package/extensions/greedysearch-dm/src/search/query.mjs +21 -1
- package/extensions/greedysearch-dm/src/search/recovery.mjs +49 -1
- package/extensions/greedysearch-dm/src/search/research.mjs +2227 -458
- package/extensions/greedysearch-dm/src/search/scale-aware.mjs +61 -11
- package/extensions/greedysearch-dm/src/search/simple-research.mjs +396 -805
- package/extensions/greedysearch-dm/src/search/sources.mjs +412 -1
- package/extensions/greedysearch-dm/src/search/synthesis-runner.mjs +127 -12
- package/extensions/greedysearch-dm/src/search/synthesis.mjs +202 -12
- package/extensions/greedysearch-dm/src/tools/greedy-search-handler.js +209 -23
- package/extensions/greedysearch-dm/src/tools/shared.js +226 -10
- package/extensions/greedysearch-dm/src/utils/content.mjs +35 -4
- package/extensions/greedysearch-dm/src/utils/helpers.js +22 -1
- package/extensions/greedysearch-dm/src/utils/node-runtime.mjs +10 -1
- package/extensions/greedysearch-dm/src/utils/system-cmds.mjs +61 -1
- package/package.json +1 -1
|
@@ -1,359 +1,356 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
(function() {
|
|
45
|
-
// ── Runtime.enable / CDP detection masking ──────────────
|
|
46
|
-
try { delete window.__REBROWSER_RUNTIME_ENABLE; } catch(_) {}
|
|
47
|
-
try { delete window.__REBROWSER_DEVTOOLS; } catch(_) {}
|
|
48
|
-
try { delete window.__nightmare; } catch(_) {}
|
|
49
|
-
try { delete window.__phantom; } catch(_) {}
|
|
50
|
-
try { delete window.callPhantom; } catch(_) {}
|
|
51
|
-
try { delete window._phantom; } catch(_) {}
|
|
52
|
-
try { delete window.Buffer; } catch(_) {}
|
|
53
|
-
|
|
54
|
-
// Real Chrome without automation should not expose navigator.webdriver at all.
|
|
55
|
-
// A literal false or an own-property getter returning undefined is itself a
|
|
56
|
-
// common stealth tell; remove both instance and prototype properties when the
|
|
57
|
-
// descriptor is configurable (as it is with --disable-blink-features).
|
|
58
|
-
try { delete navigator.webdriver; } catch(_) {}
|
|
59
|
-
try { delete Navigator.prototype.webdriver; } catch(_) {}
|
|
60
|
-
Object.defineProperty(navigator, 'vendor', { get: () => 'Google Inc.', configurable: true });
|
|
61
|
-
Object.defineProperty(navigator, 'platform', { get: () => 'Win32', configurable: true });
|
|
62
|
-
Object.defineProperty(navigator, 'maxTouchPoints', { get: () => 0, configurable: true });
|
|
63
|
-
Object.defineProperty(navigator, 'pdfViewerEnabled', { get: () => true, configurable: true });
|
|
64
|
-
Object.defineProperty(navigator, 'productSub', { get: () => '20030107', configurable: true });
|
|
65
|
-
Object.defineProperty(navigator, 'product', { get: () => 'Gecko', configurable: true });
|
|
66
|
-
var __greedyMimeTypes = null;
|
|
67
|
-
function __makeMimeTypes() {
|
|
68
|
-
var pdf = { type: 'application/pdf', suffixes: 'pdf', description: 'Portable Document Format', enabledPlugin: null };
|
|
69
|
-
var textPdf = { type: 'text/pdf', suffixes: 'pdf', description: 'Portable Document Format', enabledPlugin: null };
|
|
70
|
-
try { Object.setPrototypeOf(pdf, MimeType.prototype); } catch(_) {}
|
|
71
|
-
try { Object.setPrototypeOf(textPdf, MimeType.prototype); } catch(_) {}
|
|
72
|
-
var m = [pdf, textPdf];
|
|
73
|
-
try { Object.setPrototypeOf(m, MimeTypeArray.prototype); } catch(_) {}
|
|
74
|
-
m.item = function item(i) { return this[i] || null; };
|
|
75
|
-
m.namedItem = function namedItem(name) { return Array.prototype.find.call(this, function(x) { return x && x.type === name; }) || null; };
|
|
76
|
-
return m;
|
|
77
|
-
}
|
|
78
|
-
Object.defineProperty(navigator, 'plugins', {
|
|
79
|
-
get: () => {
|
|
80
|
-
__greedyMimeTypes = __greedyMimeTypes || __makeMimeTypes();
|
|
81
|
-
var plugin0 = { name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format' };
|
|
82
|
-
var plugin1 = { name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '' };
|
|
83
|
-
var plugin2 = { name: 'Native Client', filename: 'internal-nacl-plugin', description: '' };
|
|
84
|
-
try { Object.setPrototypeOf(plugin0, Plugin.prototype); } catch(_) {}
|
|
85
|
-
try { Object.setPrototypeOf(plugin1, Plugin.prototype); } catch(_) {}
|
|
86
|
-
try { Object.setPrototypeOf(plugin2, Plugin.prototype); } catch(_) {}
|
|
87
|
-
var p = [plugin0, plugin1, plugin2];
|
|
88
|
-
p.item = function item(i) { return this[i] || null; };
|
|
89
|
-
p.namedItem = function namedItem(name) { return Array.prototype.find.call(this, function(x) { return x && x.name === name; }) || null; };
|
|
90
|
-
p.refresh = function refresh() {};
|
|
91
|
-
try { Object.setPrototypeOf(p, PluginArray.prototype); } catch(_) {}
|
|
1
|
+
import {
|
|
2
|
+
fetchSourceHttp,
|
|
3
|
+
extractContent,
|
|
4
|
+
detectBotBlock,
|
|
5
|
+
checkContentQuality,
|
|
6
|
+
defaultFetchHeaders,
|
|
7
|
+
isPrivateUrl
|
|
8
|
+
} from "../fetcher.mjs";
|
|
9
|
+
import { fetchGitHubContent, parseGitHubUrl } from "../github.mjs";
|
|
10
|
+
import { fetchRedditContent, parseRedditUrl } from "../reddit.mjs";
|
|
11
|
+
import { trimContentHeadTail } from "../utils/content.mjs";
|
|
12
|
+
import { cdp, closeTab, openNewTab } from "./chrome.mjs";
|
|
13
|
+
import { SOURCE_FETCH_CONCURRENCY } from "./constants.mjs";
|
|
14
|
+
import { extractPdfMarkdown } from "./pdf.mjs";
|
|
15
|
+
import { trimText } from "./sources.mjs";
|
|
16
|
+
async function fetchSourceViaChrome(tab, url, maxChars = 8000) {
|
|
17
|
+
const start = Date.now();
|
|
18
|
+
try {
|
|
19
|
+
const frames = await cdp(["evalraw", tab, "Page.getFrameTree", "{}"]).then((r) => JSON.parse(r)).catch(() => null);
|
|
20
|
+
const frameId = frames?.frameTree?.frame?.id || undefined;
|
|
21
|
+
const raw = await cdp([
|
|
22
|
+
"evalraw",
|
|
23
|
+
tab,
|
|
24
|
+
"Network.loadNetworkResource",
|
|
25
|
+
JSON.stringify({
|
|
26
|
+
frameId,
|
|
27
|
+
url,
|
|
28
|
+
options: { disableCache: true, includeCredentials: false }
|
|
29
|
+
})
|
|
30
|
+
], 20000);
|
|
31
|
+
const result = JSON.parse(raw);
|
|
32
|
+
const resource = result.resource;
|
|
33
|
+
if (!resource?.success || !resource.httpStatusCode) {
|
|
34
|
+
return {
|
|
35
|
+
url,
|
|
36
|
+
error: resource?.netErrorName || resource?.netError || "loadNetworkResource failed",
|
|
37
|
+
source: "chrome",
|
|
38
|
+
duration: Date.now() - start,
|
|
39
|
+
needsFallback: true
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
let body = "";
|
|
43
|
+
if (resource.stream) {
|
|
92
44
|
try {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
45
|
+
const ioRaw = await cdp([
|
|
46
|
+
"evalraw",
|
|
47
|
+
tab,
|
|
48
|
+
"IO.read",
|
|
49
|
+
JSON.stringify({ handle: resource.stream })
|
|
50
|
+
], 1e4);
|
|
51
|
+
const ioResult = JSON.parse(ioRaw);
|
|
52
|
+
body = ioResult.data || "";
|
|
53
|
+
await cdp([
|
|
54
|
+
"evalraw",
|
|
55
|
+
tab,
|
|
56
|
+
"IO.close",
|
|
57
|
+
JSON.stringify({ handle: resource.stream })
|
|
58
|
+
]).catch(() => {});
|
|
59
|
+
} catch {}
|
|
60
|
+
}
|
|
61
|
+
if (!body || body.length < 100) {
|
|
62
|
+
return {
|
|
63
|
+
url,
|
|
64
|
+
error: "Empty response body from Network.loadNetworkResource",
|
|
65
|
+
source: "chrome",
|
|
66
|
+
duration: Date.now() - start,
|
|
67
|
+
needsFallback: true
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const botCheck = detectBotBlock(resource.httpStatusCode, body, url, url);
|
|
71
|
+
if (botCheck.blocked) {
|
|
72
|
+
return {
|
|
73
|
+
url,
|
|
74
|
+
status: resource.httpStatusCode,
|
|
75
|
+
error: `Blocked: ${botCheck.reason}`,
|
|
76
|
+
source: "chrome",
|
|
77
|
+
duration: Date.now() - start,
|
|
78
|
+
needsBrowser: true
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const extracted = await extractContent(body, url);
|
|
82
|
+
const quality = checkContentQuality(extracted);
|
|
83
|
+
if (!quality.ok) {
|
|
84
|
+
return {
|
|
85
|
+
url,
|
|
86
|
+
status: resource.httpStatusCode,
|
|
87
|
+
error: `Low quality: ${quality.reason}`,
|
|
88
|
+
source: "chrome",
|
|
89
|
+
duration: Date.now() - start,
|
|
90
|
+
needsBrowser: true
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const content = trimContentHeadTail(extracted.markdown, maxChars);
|
|
94
|
+
return {
|
|
95
|
+
url,
|
|
96
|
+
finalUrl: url,
|
|
97
|
+
status: resource.httpStatusCode,
|
|
98
|
+
contentType: "text/markdown",
|
|
99
|
+
lastModified: "",
|
|
100
|
+
publishedTime: extracted.publishedTime || "",
|
|
101
|
+
byline: extracted.byline || "",
|
|
102
|
+
siteName: extracted.siteName || "",
|
|
103
|
+
lang: extracted.lang || "",
|
|
104
|
+
title: extracted.title || url,
|
|
105
|
+
snippet: extracted.excerpt,
|
|
106
|
+
content,
|
|
107
|
+
contentChars: content.length,
|
|
108
|
+
source: "chrome",
|
|
109
|
+
duration: Date.now() - start
|
|
110
|
+
};
|
|
111
|
+
} catch (error) {
|
|
112
|
+
return {
|
|
113
|
+
url,
|
|
114
|
+
error: error.message,
|
|
115
|
+
source: "chrome",
|
|
116
|
+
duration: Date.now() - start,
|
|
117
|
+
needsFallback: true
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function isLikelyPdfUrl(url) {
|
|
108
122
|
try {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
123
|
+
const parsed = new URL(url);
|
|
124
|
+
return parsed.pathname.toLowerCase().endsWith(".pdf");
|
|
125
|
+
} catch {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async function fetchPdfSourceHttp(url, maxChars = 8000) {
|
|
130
|
+
const privateCheck = isPrivateUrl(url);
|
|
131
|
+
if (privateCheck.blocked) {
|
|
132
|
+
return {
|
|
133
|
+
url,
|
|
134
|
+
finalUrl: url,
|
|
135
|
+
status: 403,
|
|
136
|
+
error: `Blocked: ${privateCheck.reason}`,
|
|
137
|
+
source: "pdf-http"
|
|
138
|
+
};
|
|
124
139
|
}
|
|
125
|
-
|
|
140
|
+
const controller = new AbortController;
|
|
141
|
+
const timeoutId = setTimeout(() => controller.abort(), 20000);
|
|
142
|
+
const start = Date.now();
|
|
126
143
|
try {
|
|
127
|
-
|
|
128
|
-
|
|
144
|
+
const response = await fetch(url, {
|
|
145
|
+
method: "GET",
|
|
146
|
+
redirect: "follow",
|
|
147
|
+
signal: controller.signal,
|
|
148
|
+
headers: defaultFetchHeaders({
|
|
149
|
+
accept: "application/pdf,application/octet-stream;q=0.9,*/*;q=0.5"
|
|
150
|
+
})
|
|
151
|
+
});
|
|
152
|
+
clearTimeout(timeoutId);
|
|
153
|
+
const contentType = response.headers.get("content-type") || "";
|
|
154
|
+
const finalUrl = response.url || url;
|
|
155
|
+
const contentLength = Number.parseInt(response.headers.get("content-length") || "0", 10);
|
|
156
|
+
if (response.status >= 400) {
|
|
157
|
+
return {
|
|
158
|
+
url,
|
|
159
|
+
finalUrl,
|
|
160
|
+
status: response.status,
|
|
161
|
+
error: `HTTP ${response.status}`,
|
|
162
|
+
source: "pdf-http",
|
|
163
|
+
duration: Date.now() - start
|
|
164
|
+
};
|
|
129
165
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
166
|
+
if (!contentType.toLowerCase().includes("application/pdf") && !isLikelyPdfUrl(finalUrl)) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
if (contentLength > 30 * 1024 * 1024) {
|
|
170
|
+
return {
|
|
171
|
+
url,
|
|
172
|
+
finalUrl,
|
|
173
|
+
status: response.status,
|
|
174
|
+
error: `PDF too large: ${contentLength} bytes`,
|
|
175
|
+
source: "pdf-http",
|
|
176
|
+
duration: Date.now() - start
|
|
177
|
+
};
|
|
134
178
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
179
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
180
|
+
const pdf = await extractPdfMarkdown(buffer, finalUrl);
|
|
181
|
+
if (!pdf || pdf.error) {
|
|
182
|
+
return {
|
|
183
|
+
url,
|
|
184
|
+
finalUrl,
|
|
185
|
+
status: response.status,
|
|
186
|
+
error: pdf?.error || "PDF text extraction failed",
|
|
187
|
+
source: "pdf-http",
|
|
188
|
+
duration: Date.now() - start
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
const content = trimContentHeadTail(pdf.content, maxChars);
|
|
192
|
+
return {
|
|
193
|
+
url,
|
|
194
|
+
finalUrl,
|
|
195
|
+
status: response.status,
|
|
196
|
+
contentType: "application/pdf",
|
|
197
|
+
lastModified: response.headers.get("last-modified") || "",
|
|
198
|
+
title: pdf.title,
|
|
199
|
+
snippet: trimText(content, 320),
|
|
200
|
+
content,
|
|
201
|
+
contentChars: content.length,
|
|
202
|
+
pages: pdf.pages,
|
|
203
|
+
source: "pdf-http",
|
|
204
|
+
duration: Date.now() - start
|
|
205
|
+
};
|
|
206
|
+
} catch (error) {
|
|
207
|
+
clearTimeout(timeoutId);
|
|
208
|
+
return {
|
|
209
|
+
url,
|
|
210
|
+
finalUrl: url,
|
|
211
|
+
error: error.message || String(error),
|
|
212
|
+
source: "pdf-http",
|
|
213
|
+
duration: Date.now() - start
|
|
146
214
|
};
|
|
147
215
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return origQuery.apply(this, arguments);
|
|
156
|
-
});
|
|
216
|
+
}
|
|
217
|
+
export async function fetchSourceContent(url, maxChars = 8000) {
|
|
218
|
+
const start = Date.now();
|
|
219
|
+
if (isLikelyPdfUrl(url)) {
|
|
220
|
+
const pdfResult = await fetchPdfSourceHttp(url, maxChars);
|
|
221
|
+
if (pdfResult?.content || pdfResult?.status === 403)
|
|
222
|
+
return pdfResult;
|
|
157
223
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
-
});
|
|
165
|
-
} catch(_) {}
|
|
166
|
-
// ── WebGL readPixels noise ──────────────────────────
|
|
167
|
-
// CreepJS and other fingerprinters draw content with WebGL and read back the
|
|
168
|
-
// rendered pixels. Adding subtle noise breaks rendering-based fingerprinting.
|
|
169
|
-
try {
|
|
170
|
-
var origReadPixels = WebGLRenderingContext.prototype.readPixels;
|
|
171
|
-
WebGLRenderingContext.prototype.readPixels = __markNative(function readPixels(x, y, width, height, format, type, pixels) {
|
|
172
|
-
var result = origReadPixels.call(this, x, y, width, height, format, type, pixels);
|
|
173
|
-
if (pixels && pixels.length > 0) {
|
|
174
|
-
pixels[0] ^= 1;
|
|
175
|
-
}
|
|
176
|
-
return result;
|
|
177
|
-
});
|
|
178
|
-
} catch(_) {}
|
|
179
|
-
Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 8, configurable: true });
|
|
180
|
-
Object.defineProperty(navigator, 'deviceMemory', { get: () => 8, configurable: true });
|
|
181
|
-
|
|
182
|
-
// ── Canvas fingerprint noise ─────────────────────────
|
|
183
|
-
// Headless rendering engines produce slightly different canvas output
|
|
184
|
-
// than headed Chrome. Subtle noise breaks hash-based fingerprinting.
|
|
185
|
-
try {
|
|
186
|
-
var __canvasNoise = ((Date.now() & 0xFF) | 1);
|
|
187
|
-
var origFill = CanvasRenderingContext2D.prototype.fillText;
|
|
188
|
-
CanvasRenderingContext2D.prototype.fillText = __markNative(function fillText() {
|
|
189
|
-
this.globalAlpha = 0.9995;
|
|
190
|
-
return origFill.apply(this, arguments);
|
|
191
|
-
});
|
|
192
|
-
} catch(_) {}
|
|
193
|
-
try {
|
|
194
|
-
var origStroke = CanvasRenderingContext2D.prototype.strokeText;
|
|
195
|
-
CanvasRenderingContext2D.prototype.strokeText = __markNative(function strokeText() {
|
|
196
|
-
this.globalAlpha = 0.9995;
|
|
197
|
-
return origStroke.apply(this, arguments);
|
|
198
|
-
});
|
|
199
|
-
} catch(_) {}
|
|
200
|
-
try {
|
|
201
|
-
var origToDataURL = HTMLCanvasElement.prototype.toDataURL;
|
|
202
|
-
HTMLCanvasElement.prototype.toDataURL = __markNative(function toDataURL() {
|
|
203
|
-
var ctx = this.getContext('2d');
|
|
204
|
-
if (ctx) {
|
|
205
|
-
// Spread noise across canvas to break hash-based fingerprinting.
|
|
206
|
-
// Uses a deterministic pattern so it's consistent per page load
|
|
207
|
-
// but varies between sessions.
|
|
208
|
-
var w = this.width, h = this.height;
|
|
209
|
-
if (w > 0 && h > 0) {
|
|
210
|
-
var imgData = ctx.getImageData(0, 0, Math.min(w, 4), Math.min(h, 4));
|
|
211
|
-
if (imgData && imgData.data) {
|
|
212
|
-
for (var __i = 0; __i < imgData.data.length; __i += 4) {
|
|
213
|
-
imgData.data[__i] ^= (__canvasNoise + __i) & 0xFF;
|
|
214
|
-
}
|
|
215
|
-
ctx.putImageData(imgData, 0, 0);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
return origToDataURL.apply(this, arguments);
|
|
220
|
-
});
|
|
221
|
-
} catch(_) {}
|
|
222
|
-
|
|
223
|
-
// ── AudioContext fingerprint noise ────────────────────
|
|
224
|
-
// Headless Chrome's AudioContext produces slightly different output.
|
|
225
|
-
// Subtle noise breaks audio-based fingerprinting.
|
|
226
|
-
try {
|
|
227
|
-
var __audioSeed = ((Date.now() & 0x1F) | 1);
|
|
228
|
-
var origGetChannelData = AudioBuffer.prototype.getChannelData;
|
|
229
|
-
AudioBuffer.prototype.getChannelData = __markNative(function getChannelData(channel) {
|
|
230
|
-
var data = origGetChannelData.call(this, channel);
|
|
231
|
-
for (var __i = 0; __i < data.length; __i += 64) {
|
|
232
|
-
data[__i] *= 0.99999;
|
|
233
|
-
}
|
|
234
|
-
return data;
|
|
235
|
-
});
|
|
236
|
-
} catch(_) {}
|
|
237
|
-
|
|
238
|
-
// ── window outer dimensions ──────────────────────────
|
|
239
|
-
// outerWidth/Height = 0 in headless — a well-known bot signal.
|
|
240
|
-
// Mirror innerWidth/Height (set by --window-size flag) so the ratio is sane.
|
|
241
|
-
try {
|
|
242
|
-
if (!window.outerWidth) Object.defineProperty(window, 'outerWidth', { get: () => window.innerWidth || 1920, configurable: true });
|
|
243
|
-
if (!window.outerHeight) Object.defineProperty(window, 'outerHeight', { get: () => window.innerHeight || 1080, configurable: true });
|
|
244
|
-
} catch(_) {}
|
|
245
|
-
|
|
246
|
-
// ── screen properties ─────────────────────────────────
|
|
247
|
-
// Headless Chrome often reports an 800x600 screen even when the viewport is
|
|
248
|
-
// 1920x1080. Keep screen metrics internally consistent with our launch flags.
|
|
249
|
-
try {
|
|
250
|
-
Object.defineProperty(screen, 'width', { get: () => 1920, configurable: true });
|
|
251
|
-
Object.defineProperty(screen, 'height', { get: () => 1080, configurable: true });
|
|
252
|
-
Object.defineProperty(screen, 'availWidth', { get: () => 1920, configurable: true });
|
|
253
|
-
Object.defineProperty(screen, 'availHeight', { get: () => 1040, configurable: true });
|
|
254
|
-
Object.defineProperty(screen, 'colorDepth', { get: () => 24, configurable: true });
|
|
255
|
-
Object.defineProperty(screen, 'pixelDepth', { get: () => 24, configurable: true });
|
|
256
|
-
} catch(_) {}
|
|
257
|
-
|
|
258
|
-
// ── navigator.userAgentData (UA Client Hints) ─────────
|
|
259
|
-
// Derive version from the UA string already set by --user-agent flag so the
|
|
260
|
-
// two APIs are always consistent. Removes any "HeadlessChrome" brand entry.
|
|
261
|
-
try {
|
|
262
|
-
var _uaMajor = (navigator.userAgent.match(new RegExp('Chrome/([0-9]+)')) || [])[1] || '136';
|
|
263
|
-
var _uaFull = (navigator.userAgent.match(new RegExp('Chrome/([0-9.]+)')) || [])[1] || (_uaMajor + '.0.0.0');
|
|
264
|
-
var _brands = [
|
|
265
|
-
{ brand: 'Not)A;Brand', version: '99' },
|
|
266
|
-
{ brand: 'Google Chrome', version: _uaMajor },
|
|
267
|
-
{ brand: 'Chromium', version: _uaMajor },
|
|
268
|
-
];
|
|
269
|
-
Object.defineProperty(navigator, 'userAgentData', {
|
|
270
|
-
get: function() {
|
|
224
|
+
if (parseGitHubUrl(url)) {
|
|
225
|
+
const parsed = parseGitHubUrl(url);
|
|
226
|
+
if (parsed && (parsed.type === "root" || parsed.type === "tree" || parsed.type === "blob" && !parsed.path?.includes("."))) {
|
|
227
|
+
const ghResult = await fetchGitHubContent(url);
|
|
228
|
+
if (ghResult.ok) {
|
|
229
|
+
const content = trimContentHeadTail(ghResult.content, maxChars);
|
|
271
230
|
return {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
});
|
|
285
|
-
},
|
|
286
|
-
toJSON: function() { return { brands: _brands, mobile: false, platform: 'Windows' }; },
|
|
231
|
+
url,
|
|
232
|
+
finalUrl: url,
|
|
233
|
+
status: 200,
|
|
234
|
+
contentType: "text/markdown",
|
|
235
|
+
lastModified: "",
|
|
236
|
+
title: ghResult.title,
|
|
237
|
+
snippet: content.slice(0, 320),
|
|
238
|
+
content,
|
|
239
|
+
contentChars: content.length,
|
|
240
|
+
source: "github-api",
|
|
241
|
+
...ghResult.tree && { tree: ghResult.tree },
|
|
242
|
+
duration: Date.now() - start
|
|
287
243
|
};
|
|
288
|
-
},
|
|
289
|
-
configurable: true,
|
|
290
|
-
});
|
|
291
|
-
} catch(_) {}
|
|
292
|
-
|
|
293
|
-
// ── CDP Runtime serialization guard ──────────────────
|
|
294
|
-
// Sites detect CDP by putting a getter on Error.prototype.stack
|
|
295
|
-
// and checking if console.log triggers it (only happens when
|
|
296
|
-
// Runtime domain is enabled). We monkey-patch console methods to
|
|
297
|
-
// strip custom getters from arguments before they reach CDP.
|
|
298
|
-
try {
|
|
299
|
-
var _origLog = console.log, _origError = console.error,
|
|
300
|
-
_origWarn = console.warn, _origDebug = console.debug,
|
|
301
|
-
_origInfo = console.info;
|
|
302
|
-
var _safeArg = function(a) {
|
|
303
|
-
if (a instanceof Error) {
|
|
304
|
-
try { return new Error(a.message); } catch(_) { return a; }
|
|
305
244
|
}
|
|
306
|
-
|
|
245
|
+
process.stderr.write(`[greedysearch] GitHub API fetch failed, trying HTTP: ${ghResult.error}
|
|
246
|
+
`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const redditInfo = parseRedditUrl(url);
|
|
250
|
+
if (redditInfo?.type === "post") {
|
|
251
|
+
process.stderr.write(`[greedysearch] Using Reddit JSON API for: ${url.slice(0, 60)}...
|
|
252
|
+
`);
|
|
253
|
+
const redditResult = await fetchRedditContent(url, maxChars);
|
|
254
|
+
if (redditResult.ok) {
|
|
255
|
+
const content = trimContentHeadTail(redditResult.markdown, maxChars);
|
|
256
|
+
return {
|
|
257
|
+
url,
|
|
258
|
+
finalUrl: redditResult.finalUrl,
|
|
259
|
+
status: redditResult.status,
|
|
260
|
+
contentType: "text/markdown",
|
|
261
|
+
lastModified: redditResult.lastModified || "",
|
|
262
|
+
publishedTime: redditResult.publishedTime || "",
|
|
263
|
+
byline: redditResult.byline || "",
|
|
264
|
+
siteName: redditResult.siteName || "",
|
|
265
|
+
lang: redditResult.lang || "",
|
|
266
|
+
title: redditResult.title,
|
|
267
|
+
snippet: redditResult.excerpt,
|
|
268
|
+
content,
|
|
269
|
+
contentChars: content.length,
|
|
270
|
+
source: "reddit-api",
|
|
271
|
+
duration: Date.now() - start
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
process.stderr.write(`[greedysearch] Reddit API fetch failed, falling back to HTTP: ${redditResult.error}
|
|
275
|
+
`);
|
|
276
|
+
}
|
|
277
|
+
const httpResult = await fetchSourceHttp(url, { timeoutMs: 1e4 });
|
|
278
|
+
if (httpResult.ok) {
|
|
279
|
+
const content = trimContentHeadTail(httpResult.markdown, maxChars);
|
|
280
|
+
return {
|
|
281
|
+
url,
|
|
282
|
+
finalUrl: httpResult.finalUrl,
|
|
283
|
+
status: httpResult.status,
|
|
284
|
+
contentType: "text/markdown",
|
|
285
|
+
lastModified: httpResult.lastModified || "",
|
|
286
|
+
publishedTime: httpResult.publishedTime || "",
|
|
287
|
+
byline: httpResult.byline || "",
|
|
288
|
+
siteName: httpResult.siteName || "",
|
|
289
|
+
lang: httpResult.lang || "",
|
|
290
|
+
title: httpResult.title,
|
|
291
|
+
snippet: httpResult.excerpt,
|
|
292
|
+
content,
|
|
293
|
+
contentChars: content.length,
|
|
294
|
+
source: "http",
|
|
295
|
+
duration: Date.now() - start
|
|
307
296
|
};
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
Function.prototype.toString = function toString() {
|
|
320
|
-
if (__greedyNativeFns.indexOf(this) !== -1) {
|
|
321
|
-
var name = this.name || '';
|
|
322
|
-
return 'function ' + name + '() { [native code] }';
|
|
297
|
+
}
|
|
298
|
+
if (httpResult.needsBrowser) {
|
|
299
|
+
try {
|
|
300
|
+
const chromeTab = await openNewTab();
|
|
301
|
+
try {
|
|
302
|
+
const chromeResult = await fetchSourceViaChrome(chromeTab, url, maxChars);
|
|
303
|
+
if (chromeResult.content && chromeResult.content.length > 100) {
|
|
304
|
+
return chromeResult;
|
|
305
|
+
}
|
|
306
|
+
} finally {
|
|
307
|
+
await closeTab(chromeTab);
|
|
323
308
|
}
|
|
324
|
-
|
|
309
|
+
} catch {}
|
|
310
|
+
}
|
|
311
|
+
process.stderr.write(`[greedysearch] HTTP failed for ${url.slice(0, 60)}, trying browser...
|
|
312
|
+
`);
|
|
313
|
+
return await fetchSourceContentBrowser(url, maxChars);
|
|
314
|
+
}
|
|
315
|
+
async function waitForPageReady(tab, timeoutMs = 4000, intervalMs = 200) {
|
|
316
|
+
const deadline = Date.now() + timeoutMs;
|
|
317
|
+
while (Date.now() < deadline) {
|
|
318
|
+
try {
|
|
319
|
+
const ready = await cdp([
|
|
320
|
+
"eval",
|
|
321
|
+
tab,
|
|
322
|
+
`document.readyState === "complete" && !!document.body && document.body.innerText.length > 500`
|
|
323
|
+
]);
|
|
324
|
+
if (ready.trim() === "true")
|
|
325
|
+
return;
|
|
326
|
+
} catch {}
|
|
327
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
async function fetchSourceContentBrowser(url, maxChars = 8000) {
|
|
331
|
+
const start = Date.now();
|
|
332
|
+
let tab;
|
|
333
|
+
try {
|
|
334
|
+
tab = await openNewTab();
|
|
335
|
+
} catch (e) {
|
|
336
|
+
return {
|
|
337
|
+
url,
|
|
338
|
+
title: "",
|
|
339
|
+
content: null,
|
|
340
|
+
snippet: "",
|
|
341
|
+
contentChars: 0,
|
|
342
|
+
error: `openNewTab failed: ${e.message}`,
|
|
343
|
+
source: "browser",
|
|
344
|
+
duration: Date.now() - start
|
|
325
345
|
};
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
[greedysearch] Falling back to default: ${B}
|
|
335
|
-
`)}}}catch{}return B}var H={perplexity:"perplexity.mjs",p:"perplexity.mjs",bing:"bing-copilot.mjs",b:"bing-copilot.mjs",google:"google-ai.mjs",g:"google-ai.mjs",gemini:"gemini.mjs",gem:"gemini.mjs",chatgpt:"chatgpt.mjs",gpt:"chatgpt.mjs","semantic-scholar":"semantic-scholar.mjs",semanticscholar:"semantic-scholar.mjs",s2:"semantic-scholar.mjs",logically:"logically.mjs",log:"logically.mjs"},dr=dt(),mr=ht(),hr=Math.max(1,Number.parseInt(process.env.GREEDY_FETCH_CONCURRENCY||"5",10)||5);process.env.CDP_PROFILE_DIR=v;function _t(e,t=9222){let r=Number.parseInt(String(e??""),10);return Number.isInteger(r)&&r>1024&&r<65535?r:t}var Et=vt().replaceAll("\\","/"),_r=_t(process.env.GREEDY_SEARCH_PORT),_=(process.env.GREEDY_SEARCH_PROFILE_DIR||process.env.CDP_PROFILE_DIR||`${Et}/greedysearch-chrome-profile`).replaceAll("\\","/"),Er=`${_}/DevToolsActivePort`,$r=process.env.GREEDY_SEARCH_PID_FILE||`${_}/browser.pid`,Dr=process.env.CDP_PAGES_CACHE||`${_}/cdp-pages.json`,Pr=process.env.GREEDY_SEARCH_MODE_FILE||`${_}/browser-mode`,Ar=process.env.GREEDY_SEARCH_METADATA_FILE||`${_}/browser-metadata.json`,Ir=process.env.GREEDY_SEARCH_LAUNCH_LOCK_FILE||`${_}/browser-launch.lock`,Sr=process.env.GREEDY_SEARCH_ACTIVITY_FILE||`${_}/browser-last-activity`,jr=(process.env.CDP_SOCKET_DIR||`${_}/cdp-sockets`).replaceAll("\\","/"),kr=`${_}/visible-recovery.jsonl`,U=$e(bt(),".dm"),D=$e(U,"greedyconfig"),J=["perplexity","google","chatgpt","gemini"],q="gemini";function $t(){try{if(R(D)){let e=Ee(D,"utf8"),t=JSON.parse(e);if(Array.isArray(t.engines)&&t.engines.length>0&&t.engines.every((r)=>typeof r==="string")){let r=t.engines.filter((i)=>M[i]),n=t.engines.filter((i)=>!M[i]);if(n.length>0)process.stderr.write(`[greedysearch] Warning: ignoring unknown engine(s) in ${D}: ${n.join(", ")}
|
|
336
|
-
[greedysearch] Available engines: ${Object.keys(M).join(", ")}
|
|
337
|
-
`);if(r.length>0)return r;process.stderr.write(`[greedysearch] Warning: no valid engines in ${D}, falling back to defaults: ${J.join(", ")}
|
|
338
|
-
`)}}}catch{}return J}function Dt(){try{if(!R(U))yt(U,{recursive:!0});if(!R(D))wt(D,JSON.stringify({engines:J,synthesizer:q},null,2)+`
|
|
339
|
-
`,"utf8")}catch{}}Dt();var we=["gemini","chatgpt"];function Pt(){try{if(R(D)){let e=Ee(D,"utf8"),t=JSON.parse(e);if(typeof t.synthesizer==="string"){let r=t.synthesizer.toLowerCase();if(we.includes(r))return r;process.stderr.write(`[greedysearch] Warning: unknown synthesizer "${t.synthesizer}" in ${D}
|
|
340
|
-
[greedysearch] Available synthesizers: ${we.join(", ")}
|
|
341
|
-
[greedysearch] Falling back to default: ${q}
|
|
342
|
-
`)}}}catch{}return q}var M={perplexity:"perplexity.mjs",p:"perplexity.mjs",bing:"bing-copilot.mjs",b:"bing-copilot.mjs",google:"google-ai.mjs",g:"google-ai.mjs",gemini:"gemini.mjs",gem:"gemini.mjs",chatgpt:"chatgpt.mjs",gpt:"chatgpt.mjs","semantic-scholar":"semantic-scholar.mjs",semanticscholar:"semantic-scholar.mjs",s2:"semantic-scholar.mjs",logically:"logically.mjs",log:"logically.mjs"},Nr=$t(),Rr=Pt(),Cr=Math.max(1,Number.parseInt(process.env.GREEDY_FETCH_CONCURRENCY||"5",10)||5);process.env.CDP_PROFILE_DIR=_;var Lr=Number.parseInt(process.env.GREEDY_SEARCH_IDLE_TIMEOUT_MINUTES||"5",10)||5;var Or=st(import.meta.url);var Tr=Number.parseInt(process.env.GREEDY_SEARCH_IDLE_TIMEOUT_MINUTES||"5",10)||5,Fr=Number.parseInt(process.env.GREEDY_SEARCH_VISIBLE_IDLE_TIMEOUT_MINUTES||"60",10)||60;var g=be;async function De(){let e=(await g(["list"])).split(`
|
|
343
|
-
`)[0];if(!e)throw Error("No Chrome tabs found");return e.slice(0,8)}async function C(e="about:blank"){let t=await De(),r=new URL(e).hostname;if(r==="copilot.microsoft.com"||r==="www.perplexity.ai"||r==="perplexity.ai"||r.endsWith(".perplexity.ai")){let a=await g(["evalraw",t,"Target.createTarget",JSON.stringify({url:"about:blank"})]),{targetId:o}=JSON.parse(a),s=o.slice(0,8);if(await g(["list"]).catch(()=>null),r==="copilot.microsoft.com")await he(s);else he(s).catch(()=>{});return await g(["list"]).catch(()=>null),o}let n=await g(["evalraw",t,"Target.createTarget",JSON.stringify({url:e})]),{targetId:i}=JSON.parse(n);return await g(["list"]).catch(()=>null),i}async function L(e){try{let t=await De();await g(["evalraw",t,"Target.closeTarget",JSON.stringify({targetId:e})])}catch{}}import{existsSync as O,mkdirSync as At,readFileSync as Ae,writeFileSync as It}from"node:fs";import{homedir as St}from"node:os";import{join as Ie}from"node:path";import{tmpdir as jt}from"node:os";function kt(e,t=9222){let r=Number.parseInt(String(e??""),10);return Number.isInteger(r)&&r>1024&&r<65535?r:t}var Nt=jt().replaceAll("\\","/"),Yr=kt(process.env.GREEDY_SEARCH_PORT),E=(process.env.GREEDY_SEARCH_PROFILE_DIR||process.env.CDP_PROFILE_DIR||`${Nt}/greedysearch-chrome-profile`).replaceAll("\\","/"),Br=`${E}/DevToolsActivePort`,Ur=process.env.GREEDY_SEARCH_PID_FILE||`${E}/browser.pid`,Jr=process.env.CDP_PAGES_CACHE||`${E}/cdp-pages.json`,qr=process.env.GREEDY_SEARCH_MODE_FILE||`${E}/browser-mode`,Vr=process.env.GREEDY_SEARCH_METADATA_FILE||`${E}/browser-metadata.json`,Zr=process.env.GREEDY_SEARCH_LAUNCH_LOCK_FILE||`${E}/browser-launch.lock`,Xr=process.env.GREEDY_SEARCH_ACTIVITY_FILE||`${E}/browser-last-activity`,Qr=(process.env.CDP_SOCKET_DIR||`${E}/cdp-sockets`).replaceAll("\\","/"),Kr=`${E}/visible-recovery.jsonl`,Z=Ie(St(),".dm"),P=Ie(Z,"greedyconfig"),X=["perplexity","google","chatgpt","gemini"],Q="gemini";function Rt(){try{if(O(P)){let e=Ae(P,"utf8"),t=JSON.parse(e);if(Array.isArray(t.engines)&&t.engines.length>0&&t.engines.every((r)=>typeof r==="string")){let r=t.engines.filter((i)=>V[i]),n=t.engines.filter((i)=>!V[i]);if(n.length>0)process.stderr.write(`[greedysearch] Warning: ignoring unknown engine(s) in ${P}: ${n.join(", ")}
|
|
344
|
-
[greedysearch] Available engines: ${Object.keys(V).join(", ")}
|
|
345
|
-
`);if(r.length>0)return r;process.stderr.write(`[greedysearch] Warning: no valid engines in ${P}, falling back to defaults: ${X.join(", ")}
|
|
346
|
-
`)}}}catch{}return X}function Ct(){try{if(!O(Z))At(Z,{recursive:!0});if(!O(P))It(P,JSON.stringify({engines:X,synthesizer:Q},null,2)+`
|
|
347
|
-
`,"utf8")}catch{}}Ct();var Pe=["gemini","chatgpt"];function Lt(){try{if(O(P)){let e=Ae(P,"utf8"),t=JSON.parse(e);if(typeof t.synthesizer==="string"){let r=t.synthesizer.toLowerCase();if(Pe.includes(r))return r;process.stderr.write(`[greedysearch] Warning: unknown synthesizer "${t.synthesizer}" in ${P}
|
|
348
|
-
[greedysearch] Available synthesizers: ${Pe.join(", ")}
|
|
349
|
-
[greedysearch] Falling back to default: ${Q}
|
|
350
|
-
`)}}}catch{}return Q}var V={perplexity:"perplexity.mjs",p:"perplexity.mjs",bing:"bing-copilot.mjs",b:"bing-copilot.mjs",google:"google-ai.mjs",g:"google-ai.mjs",gemini:"gemini.mjs",gem:"gemini.mjs",chatgpt:"chatgpt.mjs",gpt:"chatgpt.mjs","semantic-scholar":"semantic-scholar.mjs",semanticscholar:"semantic-scholar.mjs",s2:"semantic-scholar.mjs",logically:"logically.mjs",log:"logically.mjs"},zr=Rt();var en=Lt(),K=Math.max(1,Number.parseInt(process.env.GREEDY_FETCH_CONCURRENCY||"5",10)||5);process.env.CDP_PROFILE_DIR=E;function Ot(){if(typeof globalThis.DOMMatrix>"u")globalThis.DOMMatrix=class{constructor(t=void 0){}multiplySelf(){return this}preMultiplySelf(){return this}translateSelf(){return this}scaleSelf(){return this}rotateSelf(){return this}};if(typeof globalThis.ImageData>"u")globalThis.ImageData=class{constructor(t=void 0,r=0,n=0){this.data=t,this.width=r,this.height=n}};if(typeof globalThis.Path2D>"u")globalThis.Path2D=class{constructor(t=void 0){}}}async function Tt(){Ot();let e=await import("pdf-parse"),t=e.PDFParse??e.default;if(!t)throw Error("pdf-parse did not export PDFParse");return t}async function Se(e,t){try{let n=new(await Tt())({data:new Uint8Array(e)});await n.load();let i=await n.getText(),a=i.text?.trim();if(!a)return null;return{title:new URL(t).pathname.split("/").pop()||"Document.pdf",content:`## PDF Content (${i.total} pages)
|
|
351
|
-
|
|
352
|
-
${a}`,pages:i.total}}catch(r){return{error:r.message||String(r)}}}function z(e="",t=240){let r=String(e).replaceAll(/\s+/g," ").trim();if(r.length<=t)return r;let n=r.slice(0,t),i=n.lastIndexOf(" ");return i>0?`${n.slice(0,i)}...`:`${n}...`}async function Ft(e,t,r=8000){let n=Date.now();try{let a=(await g(["evalraw",e,"Page.getFrameTree","{}"]).then((h)=>JSON.parse(h)).catch(()=>null))?.frameTree?.frame?.id||void 0,o=await g(["evalraw",e,"Network.loadNetworkResource",JSON.stringify({frameId:a,url:t,options:{disableCache:!0,includeCredentials:!1}})],20000),l=JSON.parse(o).resource;if(!l?.success||!l.httpStatusCode)return{url:t,error:l?.netErrorName||l?.netError||"loadNetworkResource failed",source:"chrome",duration:Date.now()-n,needsFallback:!0};let c="";if(l.stream)try{let h=await g(["evalraw",e,"IO.read",JSON.stringify({handle:l.stream})],1e4);c=JSON.parse(h).data||"",await g(["evalraw",e,"IO.close",JSON.stringify({handle:l.stream})]).catch(()=>{})}catch{}if(!c||c.length<100)return{url:t,error:"Empty response body from Network.loadNetworkResource",source:"chrome",duration:Date.now()-n,needsFallback:!0};let u=F(l.httpStatusCode,c,t,t);if(u.blocked)return{url:t,status:l.httpStatusCode,error:`Blocked: ${u.reason}`,source:"chrome",duration:Date.now()-n,needsBrowser:!0};let f=await x(c,t),d=G(f);if(!d.ok)return{url:t,status:l.httpStatusCode,error:`Low quality: ${d.reason}`,source:"chrome",duration:Date.now()-n,needsBrowser:!0};let p=A(f.markdown,r);return{url:t,finalUrl:t,status:l.httpStatusCode,contentType:"text/markdown",lastModified:"",publishedTime:f.publishedTime||"",byline:f.byline||"",siteName:f.siteName||"",lang:f.lang||"",title:f.title||t,snippet:f.excerpt,content:p,contentChars:p.length,source:"chrome",duration:Date.now()-n}}catch(i){return{url:t,error:i.message,source:"chrome",duration:Date.now()-n,needsFallback:!0}}}function je(e){try{return new URL(e).pathname.toLowerCase().endsWith(".pdf")}catch{return!1}}async function xt(e,t=8000){let r=j(e);if(r.blocked)return{url:e,finalUrl:e,status:403,error:`Blocked: ${r.reason}`,source:"pdf-http"};let n=new AbortController,i=setTimeout(()=>n.abort(),20000),a=Date.now();try{let o=await fetch(e,{method:"GET",redirect:"follow",signal:n.signal,headers:oe({accept:"application/pdf,application/octet-stream;q=0.9,*/*;q=0.5"})});clearTimeout(i);let s=o.headers.get("content-type")||"",l=o.url||e,c=Number.parseInt(o.headers.get("content-length")||"0",10);if(o.status>=400)return{url:e,finalUrl:l,status:o.status,error:`HTTP ${o.status}`,source:"pdf-http",duration:Date.now()-a};if(!s.toLowerCase().includes("application/pdf")&&!je(l))return null;if(c>31457280)return{url:e,finalUrl:l,status:o.status,error:`PDF too large: ${c} bytes`,source:"pdf-http",duration:Date.now()-a};let u=Buffer.from(await o.arrayBuffer()),f=await Se(u,l);if(!f||f.error)return{url:e,finalUrl:l,status:o.status,error:f?.error||"PDF text extraction failed",source:"pdf-http",duration:Date.now()-a};let d=A(f.content,t);return{url:e,finalUrl:l,status:o.status,contentType:"application/pdf",lastModified:o.headers.get("last-modified")||"",title:f.title,snippet:z(d,320),content:d,contentChars:d.length,pages:f.pages,source:"pdf-http",duration:Date.now()-a}}catch(o){return clearTimeout(i),{url:e,finalUrl:e,error:o.message||String(o),source:"pdf-http",duration:Date.now()-a}}}async function Gt(e,t=8000){let r=Date.now();if(je(e)){let a=await xt(e,t);if(a?.content||a?.status===403)return a}if(k(e)){let a=k(e);if(a&&(a.type==="root"||a.type==="tree"||a.type==="blob"&&!a.path?.includes("."))){let o=await ue(e);if(o.ok){let s=A(o.content,t);return{url:e,finalUrl:e,status:200,contentType:"text/markdown",lastModified:"",title:o.title,snippet:s.slice(0,320),content:s,contentChars:s.length,source:"github-api",...o.tree&&{tree:o.tree},duration:Date.now()-r}}process.stderr.write(`[greedysearch] GitHub API fetch failed, trying HTTP: ${o.error}
|
|
353
|
-
`)}}if(pe(e)?.type==="post"){process.stderr.write(`[greedysearch] Using Reddit JSON API for: ${e.slice(0,60)}...
|
|
354
|
-
`);let a=await ge(e,t);if(a.ok){let o=A(a.markdown,t);return{url:e,finalUrl:a.finalUrl,status:a.status,contentType:"text/markdown",lastModified:a.lastModified||"",publishedTime:a.publishedTime||"",byline:a.byline||"",siteName:a.siteName||"",lang:a.lang||"",title:a.title,snippet:a.excerpt,content:o,contentChars:o.length,source:"reddit-api",duration:Date.now()-r}}process.stderr.write(`[greedysearch] Reddit API fetch failed, falling back to HTTP: ${a.error}
|
|
355
|
-
`)}let i=await se(e,{timeoutMs:1e4});if(i.ok){let a=A(i.markdown,t);return{url:e,finalUrl:i.finalUrl,status:i.status,contentType:"text/markdown",lastModified:i.lastModified||"",publishedTime:i.publishedTime||"",byline:i.byline||"",siteName:i.siteName||"",lang:i.lang||"",title:i.title,snippet:i.excerpt,content:a,contentChars:a.length,source:"http",duration:Date.now()-r}}if(i.needsBrowser)try{let a=await C();try{let o=await Ft(a,e,t);if(o.content&&o.content.length>100)return o}finally{await L(a)}}catch{}return process.stderr.write(`[greedysearch] HTTP failed for ${e.slice(0,60)}, trying browser...
|
|
356
|
-
`),await Ht(e,t)}async function ke(e,t=4000,r=200){let n=Date.now()+t;while(Date.now()<n){try{if((await g(["eval",e,'document.readyState === "complete" && !!document.body && document.body.innerText.length > 500'])).trim()==="true")return}catch{}await new Promise((i)=>setTimeout(i,r))}}async function Ht(e,t=8000){let r=Date.now(),n;try{n=await C()}catch(i){return{url:e,title:"",content:null,snippet:"",contentChars:0,error:`openNewTab failed: ${i.message}`,source:"browser",duration:Date.now()-r}}try{await g(["nav",n,e],30000),await ke(n);let i=await g(["eval",n,String.raw`
|
|
346
|
+
}
|
|
347
|
+
try {
|
|
348
|
+
await cdp(["nav", tab, url], 30000);
|
|
349
|
+
await waitForPageReady(tab);
|
|
350
|
+
const content = await cdp([
|
|
351
|
+
"eval",
|
|
352
|
+
tab,
|
|
353
|
+
String.raw`
|
|
357
354
|
(function(){
|
|
358
355
|
var el = document.querySelector('article, [role="main"], main, .post-content, .article-body, #content, .content');
|
|
359
356
|
var text = (el || document.body).innerText;
|
|
@@ -363,16 +360,111 @@ ${a}`,pages:i.total}}catch(r){return{error:r.message||String(r)}}}function z(e="
|
|
|
363
360
|
url: location.href
|
|
364
361
|
});
|
|
365
362
|
})()
|
|
366
|
-
`
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
363
|
+
`
|
|
364
|
+
]);
|
|
365
|
+
const parsed = JSON.parse(content);
|
|
366
|
+
const finalContent = trimContentHeadTail(parsed.content, maxChars);
|
|
367
|
+
return {
|
|
368
|
+
url,
|
|
369
|
+
finalUrl: parsed.url || url,
|
|
370
|
+
status: 200,
|
|
371
|
+
contentType: "text/plain",
|
|
372
|
+
lastModified: "",
|
|
373
|
+
title: parsed.title,
|
|
374
|
+
snippet: trimText(finalContent, 320),
|
|
375
|
+
content: finalContent,
|
|
376
|
+
contentChars: finalContent.length,
|
|
377
|
+
source: "browser",
|
|
378
|
+
duration: Date.now() - start
|
|
379
|
+
};
|
|
380
|
+
} catch (error) {
|
|
381
|
+
return {
|
|
382
|
+
url,
|
|
383
|
+
title: "",
|
|
384
|
+
content: null,
|
|
385
|
+
snippet: "",
|
|
386
|
+
contentChars: 0,
|
|
387
|
+
error: error.message,
|
|
388
|
+
source: "browser",
|
|
389
|
+
duration: Date.now() - start
|
|
390
|
+
};
|
|
391
|
+
} finally {
|
|
392
|
+
await closeTab(tab);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
export async function fetchMultipleSources(sources, maxSources = 5, maxChars = 8000, concurrency = SOURCE_FETCH_CONCURRENCY) {
|
|
396
|
+
const toFetch = sources.slice(0, maxSources);
|
|
397
|
+
if (toFetch.length === 0)
|
|
398
|
+
return [];
|
|
399
|
+
const workerCount = Math.min(toFetch.length, Math.max(1, Number.parseInt(String(concurrency), 10) || SOURCE_FETCH_CONCURRENCY));
|
|
400
|
+
process.stderr.write(`[greedysearch] Fetching content from ${toFetch.length} sources via HTTP (concurrency ${workerCount})...
|
|
401
|
+
`);
|
|
402
|
+
const fetched = new Array(toFetch.length);
|
|
403
|
+
let nextIndex = 0;
|
|
404
|
+
let completed = 0;
|
|
405
|
+
async function worker() {
|
|
406
|
+
while (true) {
|
|
407
|
+
const index = nextIndex++;
|
|
408
|
+
if (index >= toFetch.length)
|
|
409
|
+
return;
|
|
410
|
+
const s = toFetch[index];
|
|
411
|
+
const url = s.canonicalUrl || s.url;
|
|
412
|
+
process.stderr.write(`[greedysearch] [${index + 1}/${toFetch.length}] Fetching: ${url.slice(0, 60)}...
|
|
413
|
+
`);
|
|
414
|
+
const result = await fetchSourceContent(url, maxChars).catch((e) => ({
|
|
415
|
+
url,
|
|
416
|
+
title: "",
|
|
417
|
+
content: null,
|
|
418
|
+
snippet: "",
|
|
419
|
+
contentChars: 0,
|
|
420
|
+
error: e.message,
|
|
421
|
+
source: "error",
|
|
422
|
+
duration: 0
|
|
423
|
+
}));
|
|
424
|
+
fetched[index] = {
|
|
425
|
+
id: s.id,
|
|
426
|
+
...result
|
|
427
|
+
};
|
|
428
|
+
if (result.content && result.content.length > 100) {
|
|
429
|
+
process.stderr.write(`[greedysearch] ✓ ${result.source}: ${result.content.length} chars
|
|
430
|
+
`);
|
|
431
|
+
} else if (result.error) {
|
|
432
|
+
process.stderr.write(`[greedysearch] ✗ ${result.error.slice(0, 80)}
|
|
433
|
+
`);
|
|
434
|
+
}
|
|
435
|
+
completed += 1;
|
|
436
|
+
process.stderr.write(`PROGRESS:fetch:${completed}/${toFetch.length}
|
|
437
|
+
`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
await Promise.all(Array.from({ length: workerCount }, () => worker()));
|
|
441
|
+
const successful = fetched.filter((f) => f.content && f.content.length > 100);
|
|
442
|
+
const httpCount = fetched.filter((f) => f.source === "http").length;
|
|
443
|
+
const browserCount = fetched.filter((f) => f.source === "browser").length;
|
|
444
|
+
process.stderr.write(`[greedysearch] Fetched ${successful.length}/${fetched.length} sources ` + `(HTTP: ${httpCount}, Browser: ${browserCount})
|
|
445
|
+
`);
|
|
446
|
+
return fetched;
|
|
447
|
+
}
|
|
448
|
+
export async function fetchTopSource(url) {
|
|
449
|
+
const tab = await openNewTab();
|
|
450
|
+
try {
|
|
451
|
+
await cdp(["nav", tab, url], 30000);
|
|
452
|
+
await waitForPageReady(tab);
|
|
453
|
+
const content = await cdp([
|
|
454
|
+
"eval",
|
|
455
|
+
tab,
|
|
456
|
+
String.raw`
|
|
373
457
|
(function(){
|
|
374
458
|
var el = document.querySelector('article, [role="main"], main, .post-content, .article-body, #content, .content');
|
|
375
459
|
var text = (el || document.body).innerText;
|
|
376
460
|
return text.replace(/\s+/g, ' ').trim();
|
|
377
461
|
})()
|
|
378
|
-
`
|
|
462
|
+
`
|
|
463
|
+
]);
|
|
464
|
+
return { url, content };
|
|
465
|
+
} catch (e) {
|
|
466
|
+
return { url, content: null, error: e.message };
|
|
467
|
+
} finally {
|
|
468
|
+
await closeTab(tab);
|
|
469
|
+
}
|
|
470
|
+
}
|