mcp-scraper 0.3.17 → 0.3.19
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 +26 -58
- package/dist/bin/api-server.cjs +346 -60
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +2 -2
- package/dist/bin/mcp-scraper-cli.cjs +4 -4
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +5 -5
- package/dist/bin/mcp-scraper-cli.js.map +1 -1
- package/dist/bin/mcp-scraper-install.cjs +12 -12
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +3 -3
- package/dist/bin/mcp-scraper-install.js.map +1 -1
- package/dist/bin/mcp-stdio-server.cjs +2441 -785
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +1377 -9
- package/dist/bin/mcp-stdio-server.js.map +1 -1
- package/dist/bin/paa-harvest.js +2 -3
- package/dist/bin/paa-harvest.js.map +1 -1
- package/dist/{chunk-N33YEY6W.js → chunk-A46NKAAM.js} +210 -10
- package/dist/{chunk-N33YEY6W.js.map → chunk-A46NKAAM.js.map} +1 -1
- package/dist/chunk-AUKOY2IV.js +7 -0
- package/dist/chunk-AUKOY2IV.js.map +1 -0
- package/dist/{chunk-ACIUCZ27.js → chunk-DE7WP4UU.js} +11 -11
- package/dist/chunk-DE7WP4UU.js.map +1 -0
- package/dist/{chunk-4OPKIDON.js → chunk-IGOEAKC7.js} +4 -6
- package/dist/{chunk-4OPKIDON.js.map → chunk-IGOEAKC7.js.map} +1 -1
- package/dist/{chunk-ZAUMSBV3.js → chunk-T337IGVE.js} +8 -8
- package/dist/chunk-T337IGVE.js.map +1 -0
- package/dist/{chunk-M2S27J6Z.js → chunk-ZAP7MYVO.js} +26 -1
- package/dist/chunk-ZAP7MYVO.js.map +1 -0
- package/dist/index.js +2 -3
- package/dist/index.js.map +1 -1
- package/dist/{server-TKWY47JA.js → server-EYNMXWUJ.js} +379 -167
- package/dist/server-EYNMXWUJ.js.map +1 -0
- package/dist/{worker-2WVKKCC7.js → worker-YZ2ZJE4F.js} +9 -10
- package/dist/{worker-2WVKKCC7.js.map → worker-YZ2ZJE4F.js.map} +1 -1
- package/docs/mcp-tool-manifest.generated.json +28 -27
- package/package.json +3 -3
- package/dist/bin/browser-agent-stdio-server.cjs +0 -1523
- package/dist/bin/browser-agent-stdio-server.cjs.map +0 -1
- package/dist/bin/browser-agent-stdio-server.d.cts +0 -1
- package/dist/bin/browser-agent-stdio-server.d.ts +0 -1
- package/dist/bin/browser-agent-stdio-server.js +0 -43
- package/dist/bin/browser-agent-stdio-server.js.map +0 -1
- package/dist/bin/mcp-scraper-combined-stdio-server.cjs +0 -5718
- package/dist/bin/mcp-scraper-combined-stdio-server.cjs.map +0 -1
- package/dist/bin/mcp-scraper-combined-stdio-server.d.cts +0 -1
- package/dist/bin/mcp-scraper-combined-stdio-server.d.ts +0 -1
- package/dist/bin/mcp-scraper-combined-stdio-server.js +0 -70
- package/dist/bin/mcp-scraper-combined-stdio-server.js.map +0 -1
- package/dist/chunk-3YGKXXUG.js +0 -133
- package/dist/chunk-3YGKXXUG.js.map +0 -1
- package/dist/chunk-ACIUCZ27.js.map +0 -1
- package/dist/chunk-M2S27J6Z.js.map +0 -1
- package/dist/chunk-PVWWY5NV.js +0 -7
- package/dist/chunk-PVWWY5NV.js.map +0 -1
- package/dist/chunk-UFD7N36R.js +0 -1332
- package/dist/chunk-UFD7N36R.js.map +0 -1
- package/dist/chunk-WN7PBKMV.js +0 -28
- package/dist/chunk-WN7PBKMV.js.map +0 -1
- package/dist/chunk-ZAUMSBV3.js.map +0 -1
- package/dist/server-TKWY47JA.js.map +0 -1
|
@@ -1,3 +1,39 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CaptureSerpPageSnapshotsInputSchema,
|
|
3
|
+
CaptureSerpPageSnapshotsOutputSchema,
|
|
4
|
+
CaptureSerpSnapshotInputSchema,
|
|
5
|
+
CaptureSerpSnapshotOutputSchema,
|
|
6
|
+
HttpMcpToolExecutor,
|
|
7
|
+
auditImages,
|
|
8
|
+
buildLinkGraph,
|
|
9
|
+
buildLinkReport,
|
|
10
|
+
buildPaaExtractorMcpServer,
|
|
11
|
+
computeIssues,
|
|
12
|
+
configureReportSaving,
|
|
13
|
+
exportFanout,
|
|
14
|
+
formatCaptureSerpPageSnapshots,
|
|
15
|
+
formatCaptureSerpSnapshot,
|
|
16
|
+
harvestTimeoutBudget,
|
|
17
|
+
liveWebToolAnnotations,
|
|
18
|
+
outputBaseDir,
|
|
19
|
+
renderImageSection,
|
|
20
|
+
renderIssueReport,
|
|
21
|
+
renderLinkReport,
|
|
22
|
+
sanitizeAttempts,
|
|
23
|
+
sanitizeHarvestResult
|
|
24
|
+
} from "./chunk-A46NKAAM.js";
|
|
25
|
+
import {
|
|
26
|
+
csvRecords,
|
|
27
|
+
listWorkflowDefinitions,
|
|
28
|
+
rowsToCsv,
|
|
29
|
+
runWorkflow,
|
|
30
|
+
runWorkflowStep,
|
|
31
|
+
workflowDefinition,
|
|
32
|
+
workflowOutputBaseDir,
|
|
33
|
+
workflowStepCount,
|
|
34
|
+
workflowSupportsSteps
|
|
35
|
+
} from "./chunk-H2R232HK.js";
|
|
36
|
+
import "./chunk-AUKOY2IV.js";
|
|
1
37
|
import {
|
|
2
38
|
BALANCE_PACK_LABELS,
|
|
3
39
|
BALANCE_PRICE_IDS,
|
|
@@ -16,7 +52,34 @@ import {
|
|
|
16
52
|
harvestProblemResponse,
|
|
17
53
|
insufficientBalanceResponse,
|
|
18
54
|
serializeHarvestProblem
|
|
19
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-T337IGVE.js";
|
|
56
|
+
import {
|
|
57
|
+
BrowserDriver,
|
|
58
|
+
MapsSelectors,
|
|
59
|
+
buildYouTubeChannelVideosUrl,
|
|
60
|
+
deleteKernelProxyId,
|
|
61
|
+
harvest,
|
|
62
|
+
resolveKernelProxyId
|
|
63
|
+
} from "./chunk-IGOEAKC7.js";
|
|
64
|
+
import {
|
|
65
|
+
CaptchaError,
|
|
66
|
+
RECAPTCHA_INSTRUCTIONS,
|
|
67
|
+
browserServiceApiKey,
|
|
68
|
+
browserServiceProfileName,
|
|
69
|
+
browserServiceProfileSaveChanges,
|
|
70
|
+
browserServiceProxyId,
|
|
71
|
+
sanitizeVendorName
|
|
72
|
+
} from "./chunk-ZAP7MYVO.js";
|
|
73
|
+
import {
|
|
74
|
+
DEFAULT_MAPS_PROXY_MODE,
|
|
75
|
+
DEFAULT_PROXY_MODE,
|
|
76
|
+
MapsPlaceOptionsSchema,
|
|
77
|
+
MapsSearchOptionsSchema,
|
|
78
|
+
RawMapsAboutAttributeSchema,
|
|
79
|
+
RawMapsHoursRowSchema,
|
|
80
|
+
RawMapsOverviewSchema,
|
|
81
|
+
RawMapsReviewStatsSchema
|
|
82
|
+
} from "./chunk-LFATOGDF.js";
|
|
20
83
|
import {
|
|
21
84
|
SiteAuditJobRowSchema,
|
|
22
85
|
advanceWorkflowRunStep,
|
|
@@ -78,118 +141,6 @@ import {
|
|
|
78
141
|
setStripeCustomerId,
|
|
79
142
|
verifyPassword
|
|
80
143
|
} from "./chunk-5H22TOXQ.js";
|
|
81
|
-
import {
|
|
82
|
-
BrowserDriver,
|
|
83
|
-
MapsSelectors,
|
|
84
|
-
buildYouTubeChannelVideosUrl,
|
|
85
|
-
deleteKernelProxyId,
|
|
86
|
-
harvest,
|
|
87
|
-
resolveKernelProxyId
|
|
88
|
-
} from "./chunk-4OPKIDON.js";
|
|
89
|
-
import {
|
|
90
|
-
CaptureSerpPageSnapshotsInputSchema,
|
|
91
|
-
CaptureSerpPageSnapshotsOutputSchema,
|
|
92
|
-
CaptureSerpSnapshotInputSchema,
|
|
93
|
-
CaptureSerpSnapshotOutputSchema,
|
|
94
|
-
HttpMcpToolExecutor,
|
|
95
|
-
auditImages,
|
|
96
|
-
buildLinkGraph,
|
|
97
|
-
buildLinkReport,
|
|
98
|
-
buildPaaExtractorMcpServer,
|
|
99
|
-
computeIssues,
|
|
100
|
-
configureReportSaving,
|
|
101
|
-
formatCaptureSerpPageSnapshots,
|
|
102
|
-
formatCaptureSerpSnapshot,
|
|
103
|
-
harvestTimeoutBudget,
|
|
104
|
-
liveWebToolAnnotations,
|
|
105
|
-
outputBaseDir,
|
|
106
|
-
renderImageSection,
|
|
107
|
-
renderIssueReport,
|
|
108
|
-
renderLinkReport
|
|
109
|
-
} from "./chunk-N33YEY6W.js";
|
|
110
|
-
import {
|
|
111
|
-
CaptchaError,
|
|
112
|
-
RECAPTCHA_INSTRUCTIONS,
|
|
113
|
-
sanitizeVendorName
|
|
114
|
-
} from "./chunk-M2S27J6Z.js";
|
|
115
|
-
import {
|
|
116
|
-
exportFanout
|
|
117
|
-
} from "./chunk-3YGKXXUG.js";
|
|
118
|
-
import {
|
|
119
|
-
browserServiceApiKey,
|
|
120
|
-
browserServiceProfileName,
|
|
121
|
-
browserServiceProfileSaveChanges,
|
|
122
|
-
browserServiceProxyId
|
|
123
|
-
} from "./chunk-WN7PBKMV.js";
|
|
124
|
-
import {
|
|
125
|
-
csvRecords,
|
|
126
|
-
listWorkflowDefinitions,
|
|
127
|
-
rowsToCsv,
|
|
128
|
-
runWorkflow,
|
|
129
|
-
runWorkflowStep,
|
|
130
|
-
workflowDefinition,
|
|
131
|
-
workflowOutputBaseDir,
|
|
132
|
-
workflowStepCount,
|
|
133
|
-
workflowSupportsSteps
|
|
134
|
-
} from "./chunk-H2R232HK.js";
|
|
135
|
-
import {
|
|
136
|
-
DEFAULT_MAPS_PROXY_MODE,
|
|
137
|
-
DEFAULT_PROXY_MODE,
|
|
138
|
-
MapsPlaceOptionsSchema,
|
|
139
|
-
MapsSearchOptionsSchema,
|
|
140
|
-
RawMapsAboutAttributeSchema,
|
|
141
|
-
RawMapsHoursRowSchema,
|
|
142
|
-
RawMapsOverviewSchema,
|
|
143
|
-
RawMapsReviewStatsSchema
|
|
144
|
-
} from "./chunk-LFATOGDF.js";
|
|
145
|
-
import "./chunk-PVWWY5NV.js";
|
|
146
|
-
|
|
147
|
-
// src/api/outbound-sanitize.ts
|
|
148
|
-
var KEY_RENAMES = {
|
|
149
|
-
kernel: "browserRuntime",
|
|
150
|
-
kernel_session_id: "browser_session_id",
|
|
151
|
-
kernel_delete_started: "session_cleanup_started",
|
|
152
|
-
kernel_delete_succeeded: "session_cleanup_succeeded",
|
|
153
|
-
kernel_delete_error: "session_cleanup_error",
|
|
154
|
-
kernelSessionId: "browserSessionId",
|
|
155
|
-
kernelDeleteStarted: "sessionCleanupStarted",
|
|
156
|
-
kernelDeleteSucceeded: "sessionCleanupSucceeded",
|
|
157
|
-
kernelDeleteError: "sessionCleanupError",
|
|
158
|
-
kernelProxyId: "proxyId"
|
|
159
|
-
};
|
|
160
|
-
var SANITIZED_VALUE_KEYS = /error|message/i;
|
|
161
|
-
function sanitizeOutboundDiagnostics(value, parentKey = "") {
|
|
162
|
-
if (typeof value === "string") {
|
|
163
|
-
if (SANITIZED_VALUE_KEYS.test(parentKey) && /kernel/i.test(value)) {
|
|
164
|
-
return sanitizeVendorName(value);
|
|
165
|
-
}
|
|
166
|
-
return value;
|
|
167
|
-
}
|
|
168
|
-
if (Array.isArray(value)) return value.map((v) => sanitizeOutboundDiagnostics(v, parentKey));
|
|
169
|
-
if (value !== null && typeof value === "object") {
|
|
170
|
-
const out = {};
|
|
171
|
-
for (const [key, val] of Object.entries(value)) {
|
|
172
|
-
const renamed = KEY_RENAMES[key] ?? key;
|
|
173
|
-
out[renamed] = sanitizeOutboundDiagnostics(val, key);
|
|
174
|
-
}
|
|
175
|
-
return out;
|
|
176
|
-
}
|
|
177
|
-
return value;
|
|
178
|
-
}
|
|
179
|
-
function sanitizeAttempts(attempts) {
|
|
180
|
-
return attempts.map((a) => sanitizeOutboundDiagnostics(a));
|
|
181
|
-
}
|
|
182
|
-
function sanitizeHarvestResult(result) {
|
|
183
|
-
const diagnostics = result?.diagnostics;
|
|
184
|
-
if (!diagnostics?.debug) return result;
|
|
185
|
-
return {
|
|
186
|
-
...result,
|
|
187
|
-
diagnostics: {
|
|
188
|
-
...diagnostics,
|
|
189
|
-
debug: sanitizeOutboundDiagnostics(diagnostics.debug)
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
144
|
|
|
194
145
|
// src/blog/registry.ts
|
|
195
146
|
var posts = [
|
|
@@ -7586,11 +7537,6 @@ async function settleExtractJob(jobId, userId, refundMc, netChargeMc, reference)
|
|
|
7586
7537
|
|
|
7587
7538
|
// src/inngest/functions/site-extract.ts
|
|
7588
7539
|
var BATCH = 30;
|
|
7589
|
-
function chunk(items, size) {
|
|
7590
|
-
const out = [];
|
|
7591
|
-
for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
|
|
7592
|
-
return out;
|
|
7593
|
-
}
|
|
7594
7540
|
function buildArtifacts(job, pages, branding, imageAudit) {
|
|
7595
7541
|
const { edges, metrics } = buildLinkGraph(pages, job.startUrl);
|
|
7596
7542
|
const issues = computeIssues(pages, metrics);
|
|
@@ -7649,18 +7595,62 @@ var siteExtractFn = inngest.createFunction(
|
|
|
7649
7595
|
const maxPages = Number(job.options.maxPages ?? 1e4);
|
|
7650
7596
|
const concurrency = Number(job.options.concurrency ?? 1);
|
|
7651
7597
|
const urlsPerBrowser = Number(job.options.urlsPerBrowser ?? job.options.rotateProxyEvery ?? 10);
|
|
7652
|
-
const
|
|
7598
|
+
const seed = await step.run("discover", async () => {
|
|
7653
7599
|
const discovered = await discoverUrls(job.startUrl, maxPages, key);
|
|
7654
|
-
|
|
7655
|
-
|
|
7600
|
+
const out = discovered.length ? discovered : [job.startUrl];
|
|
7601
|
+
await setExtractJobTotal(jobId, out.length);
|
|
7602
|
+
return out;
|
|
7656
7603
|
});
|
|
7657
|
-
const
|
|
7658
|
-
|
|
7659
|
-
|
|
7660
|
-
|
|
7604
|
+
const siteHost = (() => {
|
|
7605
|
+
try {
|
|
7606
|
+
return new URL(job.startUrl).hostname.replace(/^www\./, "");
|
|
7607
|
+
} catch {
|
|
7608
|
+
return "";
|
|
7609
|
+
}
|
|
7610
|
+
})();
|
|
7611
|
+
const norm = (u) => {
|
|
7612
|
+
try {
|
|
7613
|
+
const x = new URL(u);
|
|
7614
|
+
x.hash = "";
|
|
7615
|
+
return (x.origin + x.pathname).replace(/\/+$/, "") + x.search;
|
|
7616
|
+
} catch {
|
|
7617
|
+
return u;
|
|
7618
|
+
}
|
|
7619
|
+
};
|
|
7620
|
+
const sameSite = (u) => {
|
|
7621
|
+
try {
|
|
7622
|
+
return new URL(u).hostname.replace(/^www\./, "") === siteHost;
|
|
7623
|
+
} catch {
|
|
7624
|
+
return false;
|
|
7625
|
+
}
|
|
7626
|
+
};
|
|
7627
|
+
const queue = [...seed];
|
|
7628
|
+
const seen = new Set(seed.map(norm));
|
|
7629
|
+
let crawled = 0;
|
|
7630
|
+
const MAX_BATCHES = Math.ceil(maxPages / BATCH) + 30;
|
|
7631
|
+
for (let i = 0; i < MAX_BATCHES && crawled < maxPages && crawled < queue.length; i++) {
|
|
7632
|
+
const batch = queue.slice(crawled, Math.min(crawled + BATCH, maxPages));
|
|
7633
|
+
if (!batch.length) break;
|
|
7634
|
+
const newLinks = await step.run(`crawl-batch-${i}`, async () => {
|
|
7635
|
+
const pages = await extractPagesRotating(batch, { kernelApiKey: key, concurrency, urlsPerBrowser });
|
|
7661
7636
|
await saveExtractPages(jobId, pages);
|
|
7662
|
-
|
|
7637
|
+
const found = [];
|
|
7638
|
+
for (const p of pages) for (const l of p.outlinks ?? []) {
|
|
7639
|
+
if (l.internal && l.href && sameSite(l.href)) found.push(l.href);
|
|
7640
|
+
}
|
|
7641
|
+
return [...new Set(found)];
|
|
7663
7642
|
});
|
|
7643
|
+
crawled += batch.length;
|
|
7644
|
+
let added = false;
|
|
7645
|
+
for (const href of newLinks) {
|
|
7646
|
+
const n = norm(href);
|
|
7647
|
+
if (!seen.has(n) && seen.size < maxPages) {
|
|
7648
|
+
seen.add(n);
|
|
7649
|
+
queue.push(href);
|
|
7650
|
+
added = true;
|
|
7651
|
+
}
|
|
7652
|
+
}
|
|
7653
|
+
if (added) await setExtractJobTotal(jobId, Math.min(seen.size, maxPages));
|
|
7664
7654
|
}
|
|
7665
7655
|
const branding = Array.isArray(job.options.formats) && job.options.formats.includes("branding") ? await step.run("branding", () => extractBranding(job.startUrl, key).catch(() => null)) : null;
|
|
7666
7656
|
const imageAudit = Array.isArray(job.options.formats) && job.options.formats.includes("images") ? await step.run("image-audit", async () => {
|
|
@@ -9343,10 +9333,10 @@ function buildTranscriptMarkdown(result) {
|
|
|
9343
9333
|
if (result.chunks?.length) {
|
|
9344
9334
|
lines.push("## Timestamped Segments");
|
|
9345
9335
|
lines.push("");
|
|
9346
|
-
for (const
|
|
9347
|
-
const start = fmtTs(
|
|
9348
|
-
const end = fmtTs(
|
|
9349
|
-
lines.push(`**[${start} \u2192 ${end}]** ${
|
|
9336
|
+
for (const chunk of result.chunks) {
|
|
9337
|
+
const start = fmtTs(chunk.timestamp[0]);
|
|
9338
|
+
const end = fmtTs(chunk.timestamp[1]);
|
|
9339
|
+
lines.push(`**[${start} \u2192 ${end}]** ${chunk.text.trim()}`);
|
|
9350
9340
|
lines.push("");
|
|
9351
9341
|
}
|
|
9352
9342
|
}
|
|
@@ -9420,7 +9410,11 @@ screenshotApp.post("/", async (c) => {
|
|
|
9420
9410
|
metadata: { url: targetUrl, device }
|
|
9421
9411
|
});
|
|
9422
9412
|
if (!gate.ok) return c.json(concurrencyLimitExceededResponse(gate), 429, { "Retry-After": String(gate.retryAfterSeconds) });
|
|
9413
|
+
let debited = false;
|
|
9423
9414
|
try {
|
|
9415
|
+
const { ok, balance_mc } = await debitMc(user.id, MC_COSTS.page_scrape, LedgerOperation.EXTRACT_URL, targetUrl);
|
|
9416
|
+
if (!ok) return c.json(insufficientBalanceResponse(balance_mc, MC_COSTS.page_scrape), 402);
|
|
9417
|
+
debited = true;
|
|
9424
9418
|
const buf = await captureScreenshot(targetUrl, browserServiceApiKey(), device);
|
|
9425
9419
|
return new Response(new Uint8Array(buf), {
|
|
9426
9420
|
status: 200,
|
|
@@ -9431,6 +9425,7 @@ screenshotApp.post("/", async (c) => {
|
|
|
9431
9425
|
}
|
|
9432
9426
|
});
|
|
9433
9427
|
} catch (err) {
|
|
9428
|
+
if (debited) await creditMc(user.id, MC_COSTS.page_scrape, LedgerOperation.EXTRACT_URL, "failed screenshot refund");
|
|
9434
9429
|
const msg = err instanceof Error ? err.message : String(err);
|
|
9435
9430
|
return c.json({ error: msg }, 502);
|
|
9436
9431
|
} finally {
|
|
@@ -9847,20 +9842,20 @@ var FacebookAdExtractor = class {
|
|
|
9847
9842
|
if (splitRe.lastIndex === m.index) splitRe.lastIndex++;
|
|
9848
9843
|
}
|
|
9849
9844
|
if (last < bodyText.length) adChunks.push(bodyText.slice(last));
|
|
9850
|
-
const ads = adChunks.filter((c) => /Library ID[:\s]+\d{10,20}/i.test(c)).slice(0, maxAds).map((
|
|
9851
|
-
const lidM =
|
|
9845
|
+
const ads = adChunks.filter((c) => /Library ID[:\s]+\d{10,20}/i.test(c)).slice(0, maxAds).map((chunk) => {
|
|
9846
|
+
const lidM = chunk.match(/Library ID[:\s]+([0-9]{10,20})/i);
|
|
9852
9847
|
const libraryId = lidM ? lidM[1] : null;
|
|
9853
|
-
const statusM =
|
|
9848
|
+
const statusM = chunk.match(/^(Active|Inactive)/i);
|
|
9854
9849
|
const status = statusM ? statusM[1] : null;
|
|
9855
|
-
const startM =
|
|
9850
|
+
const startM = chunk.match(/Started running on\s+([A-Za-z]+\.?\s+\d{1,2},?\s*\d{4})/i);
|
|
9856
9851
|
const started = startM ? startM[1].replace(/\s+/g, " ").trim() : null;
|
|
9857
9852
|
const visual = adVisuals[libraryId ?? ""] ?? { imageSrc: null, videoSrc: null, videoPoster: null };
|
|
9858
|
-
const hasVideoText = /0:00\s*\/\s*0:00|\d+:\d+\s*\/\s*\d+:\d+/.test(
|
|
9853
|
+
const hasVideoText = /0:00\s*\/\s*0:00|\d+:\d+\s*\/\s*\d+:\d+/.test(chunk);
|
|
9859
9854
|
const creativeType = visual.videoSrc || hasVideoText ? "video" : visual.imageSrc ? "image" : "unknown";
|
|
9860
9855
|
let domain = null;
|
|
9861
9856
|
const domainRe = /\b([A-Z0-9]{2,}(?:\.[A-Z]{2,})+)\b/g;
|
|
9862
9857
|
let dm;
|
|
9863
|
-
while ((dm = domainRe.exec(
|
|
9858
|
+
while ((dm = domainRe.exec(chunk)) !== null) {
|
|
9864
9859
|
const c = dm[1];
|
|
9865
9860
|
if (c.length >= 6 && c !== "LIBRARY") {
|
|
9866
9861
|
domain = c;
|
|
@@ -9869,9 +9864,9 @@ var FacebookAdExtractor = class {
|
|
|
9869
9864
|
}
|
|
9870
9865
|
let primaryText = null;
|
|
9871
9866
|
let headline = null;
|
|
9872
|
-
const sponsoredIdx =
|
|
9867
|
+
const sponsoredIdx = chunk.search(/\bSponsored\b/i);
|
|
9873
9868
|
if (sponsoredIdx >= 0) {
|
|
9874
|
-
let afterSponsored =
|
|
9869
|
+
let afterSponsored = chunk.slice(sponsoredIdx + "Sponsored".length).trim();
|
|
9875
9870
|
afterSponsored = afterSponsored.replace(/\s*\d*:?\d+\s*\/\s*\d*:?\d+.*$/, "");
|
|
9876
9871
|
if (domain) {
|
|
9877
9872
|
const di = afterSponsored.indexOf(domain);
|
|
@@ -9882,18 +9877,18 @@ var FacebookAdExtractor = class {
|
|
|
9882
9877
|
}
|
|
9883
9878
|
const ctaRe2 = new RegExp("\\b(" + CTA_LABELS.map((l) => l.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|") + ")\\b", "i");
|
|
9884
9879
|
if (domain) {
|
|
9885
|
-
const di =
|
|
9880
|
+
const di = chunk.indexOf(domain);
|
|
9886
9881
|
if (di >= 0) {
|
|
9887
|
-
let afterDomain =
|
|
9882
|
+
let afterDomain = chunk.slice(di + domain.length).trim();
|
|
9888
9883
|
const ctaMatch = afterDomain.search(ctaRe2);
|
|
9889
9884
|
if (ctaMatch >= 0) afterDomain = afterDomain.slice(0, ctaMatch).trim();
|
|
9890
9885
|
if (afterDomain.length > 3 && afterDomain.length < 120) headline = afterDomain;
|
|
9891
9886
|
}
|
|
9892
9887
|
}
|
|
9893
9888
|
if (!headline) {
|
|
9894
|
-
const ctaIdx =
|
|
9889
|
+
const ctaIdx = chunk.search(ctaRe2);
|
|
9895
9890
|
if (ctaIdx > 0) {
|
|
9896
|
-
const beforeCta =
|
|
9891
|
+
const beforeCta = chunk.slice(0, ctaIdx).trimEnd();
|
|
9897
9892
|
const sentenceEnd = Math.max(beforeCta.lastIndexOf(". "), beforeCta.lastIndexOf("! "), beforeCta.lastIndexOf("? "));
|
|
9898
9893
|
if (sentenceEnd >= 0) {
|
|
9899
9894
|
const candidate = beforeCta.slice(sentenceEnd + 2).trim();
|
|
@@ -9905,12 +9900,12 @@ var FacebookAdExtractor = class {
|
|
|
9905
9900
|
}
|
|
9906
9901
|
let cta = null;
|
|
9907
9902
|
for (const lbl of CTA_LABELS) {
|
|
9908
|
-
if (
|
|
9903
|
+
if (chunk.toLowerCase().includes(lbl.toLowerCase())) {
|
|
9909
9904
|
cta = lbl;
|
|
9910
9905
|
break;
|
|
9911
9906
|
}
|
|
9912
9907
|
}
|
|
9913
|
-
const multiM =
|
|
9908
|
+
const multiM = chunk.match(/(\d+)\s+ad[s]?\s+use[s]?\s+this/i);
|
|
9914
9909
|
const clusterCount = multiM ? parseInt(multiM[1]) : null;
|
|
9915
9910
|
return {
|
|
9916
9911
|
libraryId,
|
|
@@ -9933,8 +9928,8 @@ var FacebookAdExtractor = class {
|
|
|
9933
9928
|
const unknownCreativeCount = ads.filter((a) => a.creativeType === "unknown").length;
|
|
9934
9929
|
const advertiserPageId = new URL(page.url()).searchParams.get("view_all_page_id") ?? null;
|
|
9935
9930
|
const nameFreq = /* @__PURE__ */ new Map();
|
|
9936
|
-
for (const
|
|
9937
|
-
const detailsParts =
|
|
9931
|
+
for (const chunk of adChunks) {
|
|
9932
|
+
const detailsParts = chunk.split("See ad details ");
|
|
9938
9933
|
if (detailsParts.length < 2) continue;
|
|
9939
9934
|
const sponsoredIdx = detailsParts[1].indexOf(" Sponsored");
|
|
9940
9935
|
if (sponsoredIdx < 0) continue;
|
|
@@ -10674,11 +10669,11 @@ facebookAdApp.post("/search", createApiKeyAuth(), async (c) => {
|
|
|
10674
10669
|
}
|
|
10675
10670
|
if (last < bodyText.length) adChunks.push(bodyText.slice(last));
|
|
10676
10671
|
const advertiserMap = /* @__PURE__ */ new Map();
|
|
10677
|
-
for (const
|
|
10678
|
-
const lidM =
|
|
10672
|
+
for (const chunk of adChunks) {
|
|
10673
|
+
const lidM = chunk.match(/Library ID[:\s]+([0-9]{10,20})/i);
|
|
10679
10674
|
if (!lidM) continue;
|
|
10680
10675
|
const libraryId = lidM[1];
|
|
10681
|
-
const detailsParts =
|
|
10676
|
+
const detailsParts = chunk.split("See ad details ");
|
|
10682
10677
|
if (detailsParts.length < 2) continue;
|
|
10683
10678
|
const afterDetails = detailsParts[1];
|
|
10684
10679
|
const sponsoredIdx = afterDetails.indexOf(" Sponsored");
|
|
@@ -11230,8 +11225,8 @@ function runFfmpegMux(videoPath, audioPath, outPath) {
|
|
|
11230
11225
|
stdio: ["ignore", "ignore", "pipe"]
|
|
11231
11226
|
});
|
|
11232
11227
|
let stderr = "";
|
|
11233
|
-
child.stderr.on("data", (
|
|
11234
|
-
stderr += String(
|
|
11228
|
+
child.stderr.on("data", (chunk) => {
|
|
11229
|
+
stderr += String(chunk);
|
|
11235
11230
|
});
|
|
11236
11231
|
child.on("error", (err) => resolve({ ok: false, error: err.message }));
|
|
11237
11232
|
child.on("close", (code) => resolve({ ok: code === 0, error: code === 0 ? null : stderr.trim() || `ffmpeg exited ${code}` }));
|
|
@@ -14363,8 +14358,64 @@ async function migrateBrowserAgent() {
|
|
|
14363
14358
|
)
|
|
14364
14359
|
`);
|
|
14365
14360
|
await db.execute(`CREATE INDEX IF NOT EXISTS browser_agent_replays_session ON browser_agent_replays(session_id)`);
|
|
14361
|
+
await db.execute(`
|
|
14362
|
+
CREATE TABLE IF NOT EXISTS browser_auth_connections (
|
|
14363
|
+
connection_id TEXT PRIMARY KEY,
|
|
14364
|
+
hosted_url TEXT,
|
|
14365
|
+
domain TEXT,
|
|
14366
|
+
profile TEXT,
|
|
14367
|
+
account_email TEXT,
|
|
14368
|
+
note TEXT,
|
|
14369
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
14370
|
+
updated_at TEXT
|
|
14371
|
+
)
|
|
14372
|
+
`);
|
|
14373
|
+
try {
|
|
14374
|
+
await db.execute(`ALTER TABLE browser_auth_connections ADD COLUMN account_email TEXT`);
|
|
14375
|
+
} catch {
|
|
14376
|
+
}
|
|
14377
|
+
try {
|
|
14378
|
+
await db.execute(`ALTER TABLE browser_auth_connections ADD COLUMN note TEXT`);
|
|
14379
|
+
} catch {
|
|
14380
|
+
}
|
|
14381
|
+
try {
|
|
14382
|
+
await db.execute(`ALTER TABLE browser_auth_connections ADD COLUMN updated_at TEXT`);
|
|
14383
|
+
} catch {
|
|
14384
|
+
}
|
|
14385
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS browser_auth_connections_profile ON browser_auth_connections(profile)`);
|
|
14366
14386
|
_ready = true;
|
|
14367
14387
|
}
|
|
14388
|
+
async function saveAuthConnection(input) {
|
|
14389
|
+
if (!input.connectionId) return;
|
|
14390
|
+
const db = getDb();
|
|
14391
|
+
await db.execute({
|
|
14392
|
+
sql: `INSERT INTO browser_auth_connections (connection_id, hosted_url, domain, profile, account_email, note, updated_at)
|
|
14393
|
+
VALUES (?, ?, ?, ?, ?, ?, datetime('now'))
|
|
14394
|
+
ON CONFLICT(connection_id) DO UPDATE SET
|
|
14395
|
+
hosted_url = COALESCE(excluded.hosted_url, browser_auth_connections.hosted_url),
|
|
14396
|
+
domain = COALESCE(excluded.domain, browser_auth_connections.domain),
|
|
14397
|
+
profile = COALESCE(excluded.profile, browser_auth_connections.profile),
|
|
14398
|
+
account_email = COALESCE(excluded.account_email, browser_auth_connections.account_email),
|
|
14399
|
+
note = COALESCE(excluded.note, browser_auth_connections.note),
|
|
14400
|
+
updated_at = datetime('now')`,
|
|
14401
|
+
args: [input.connectionId, input.hostedUrl, input.domain, input.profile, input.accountEmail ?? null, input.note ?? null]
|
|
14402
|
+
});
|
|
14403
|
+
}
|
|
14404
|
+
async function getAuthConnectionRow(connectionId) {
|
|
14405
|
+
if (!connectionId) return null;
|
|
14406
|
+
const db = getDb();
|
|
14407
|
+
const res = await db.execute({ sql: `SELECT * FROM browser_auth_connections WHERE connection_id = ?`, args: [connectionId] });
|
|
14408
|
+
return res.rows[0] ?? null;
|
|
14409
|
+
}
|
|
14410
|
+
async function listAuthConnectionsByProfile(profile) {
|
|
14411
|
+
if (!profile) return [];
|
|
14412
|
+
const db = getDb();
|
|
14413
|
+
const res = await db.execute({
|
|
14414
|
+
sql: `SELECT * FROM browser_auth_connections WHERE profile = ? ORDER BY COALESCE(updated_at, created_at) DESC`,
|
|
14415
|
+
args: [profile]
|
|
14416
|
+
});
|
|
14417
|
+
return res.rows;
|
|
14418
|
+
}
|
|
14368
14419
|
async function createSessionRow(input) {
|
|
14369
14420
|
const db = getDb();
|
|
14370
14421
|
const id = `bas_${randomUUID2().replace(/-/g, "").slice(0, 20)}`;
|
|
@@ -14398,6 +14449,14 @@ async function listSessionRows(userId, includeClosed = false) {
|
|
|
14398
14449
|
});
|
|
14399
14450
|
return res.rows;
|
|
14400
14451
|
}
|
|
14452
|
+
async function listIdleOpenSessionRows(idleSeconds) {
|
|
14453
|
+
const db = getDb();
|
|
14454
|
+
const res = await db.execute({
|
|
14455
|
+
sql: `SELECT * FROM browser_agent_sessions WHERE status = 'open' AND COALESCE(last_action_at, created_at) <= datetime('now', ?) ORDER BY created_at LIMIT 200`,
|
|
14456
|
+
args: [`-${Math.max(1, Math.round(idleSeconds))} seconds`]
|
|
14457
|
+
});
|
|
14458
|
+
return res.rows;
|
|
14459
|
+
}
|
|
14401
14460
|
async function addActiveMs(id, deltaMs) {
|
|
14402
14461
|
const db = getDb();
|
|
14403
14462
|
await db.execute({
|
|
@@ -15197,6 +15256,17 @@ async function findProfileConnection(k, profileName, domain) {
|
|
|
15197
15256
|
}
|
|
15198
15257
|
return null;
|
|
15199
15258
|
}
|
|
15259
|
+
async function listProfileConnections(profileName) {
|
|
15260
|
+
if (!profileName) return [];
|
|
15261
|
+
const k = client();
|
|
15262
|
+
const out = [];
|
|
15263
|
+
for await (const connection of k.auth.connections.list({ profile_name: profileName })) {
|
|
15264
|
+
const data = connection;
|
|
15265
|
+
if (data.profile_name && data.profile_name !== profileName) continue;
|
|
15266
|
+
out.push(profileOnboardResult(connection));
|
|
15267
|
+
}
|
|
15268
|
+
return out;
|
|
15269
|
+
}
|
|
15200
15270
|
async function findOrCreateProfileConnection(k, opts) {
|
|
15201
15271
|
try {
|
|
15202
15272
|
return await k.auth.connections.create({
|
|
@@ -15575,29 +15645,61 @@ function browserSessionLockTtlSeconds(timeoutSeconds) {
|
|
|
15575
15645
|
}
|
|
15576
15646
|
return DEFAULT_BROWSER_SESSION_LOCK_TTL_SECONDS;
|
|
15577
15647
|
}
|
|
15578
|
-
async function charge(sessionId, userId,
|
|
15579
|
-
const
|
|
15580
|
-
|
|
15581
|
-
const
|
|
15582
|
-
|
|
15648
|
+
async function charge(sessionId, userId, _startedAtMs) {
|
|
15649
|
+
const row = await getSessionRow(sessionId);
|
|
15650
|
+
if (!row) return;
|
|
15651
|
+
const createdMs = parseAgentDate(row.created_at);
|
|
15652
|
+
if (createdMs == null) return;
|
|
15653
|
+
const uptimeMs = Math.max(0, Date.now() - createdMs);
|
|
15654
|
+
const owed = browserActiveCostMc(uptimeMs);
|
|
15655
|
+
await addActiveMs(sessionId, 0);
|
|
15656
|
+
const delta = owed - Number(row.billed_mc ?? 0);
|
|
15583
15657
|
if (delta > 0) {
|
|
15584
15658
|
const res = await debitMc(userId, delta, LedgerOperation.BROWSER_SESSION, sessionId);
|
|
15585
15659
|
if (res.ok) await setBilledMc(sessionId, owed);
|
|
15586
15660
|
}
|
|
15587
15661
|
}
|
|
15662
|
+
async function reapIdleBrowserSessions(idleSeconds = 120) {
|
|
15663
|
+
const rows = await listIdleOpenSessionRows(idleSeconds);
|
|
15664
|
+
let reaped = 0;
|
|
15665
|
+
for (const row of rows) {
|
|
15666
|
+
if (row.user_id != null) {
|
|
15667
|
+
try {
|
|
15668
|
+
await charge(row.id, row.user_id);
|
|
15669
|
+
} catch {
|
|
15670
|
+
}
|
|
15671
|
+
}
|
|
15672
|
+
try {
|
|
15673
|
+
await closeSession(row.runtime_session_id);
|
|
15674
|
+
} catch {
|
|
15675
|
+
}
|
|
15676
|
+
await markSessionClosed(row.id).catch(() => {
|
|
15677
|
+
});
|
|
15678
|
+
if (row.concurrency_lock_id) await releaseConcurrencyGate(row.concurrency_lock_id).catch(() => {
|
|
15679
|
+
});
|
|
15680
|
+
reaped++;
|
|
15681
|
+
}
|
|
15682
|
+
return { reaped };
|
|
15683
|
+
}
|
|
15588
15684
|
function publicSession(row) {
|
|
15589
15685
|
return {
|
|
15590
15686
|
session_id: row.id,
|
|
15591
15687
|
status: row.status,
|
|
15592
15688
|
label: row.label,
|
|
15593
|
-
|
|
15689
|
+
watch_url: `/console/${row.id}`,
|
|
15594
15690
|
created_at: row.created_at,
|
|
15595
15691
|
last_action_at: row.last_action_at,
|
|
15596
15692
|
closed_at: row.closed_at,
|
|
15597
|
-
|
|
15693
|
+
uptime_seconds: uptimeSeconds(row),
|
|
15598
15694
|
credits_used: Math.round((row.billed_mc ?? 0) / 10) / 100
|
|
15599
15695
|
};
|
|
15600
15696
|
}
|
|
15697
|
+
function uptimeSeconds(row) {
|
|
15698
|
+
const createdMs = parseAgentDate(row.created_at);
|
|
15699
|
+
const endMs = row.closed_at ? parseAgentDate(row.closed_at) : Date.now();
|
|
15700
|
+
if (createdMs == null || endMs == null) return 0;
|
|
15701
|
+
return Math.max(0, Math.round((endMs - createdMs) / 1e3));
|
|
15702
|
+
}
|
|
15601
15703
|
function failure(err) {
|
|
15602
15704
|
const msg = err instanceof Error ? err.message : String(err);
|
|
15603
15705
|
return { error: sanitizeVendorName(msg) };
|
|
@@ -15646,10 +15748,23 @@ function buildBrowserAgentRoutes() {
|
|
|
15646
15748
|
const profileName = typeof body.profile === "string" ? body.profile.trim() : "";
|
|
15647
15749
|
const domain = typeof body.domain === "string" ? body.domain.trim().replace(/^https?:\/\//, "").replace(/\/.*$/, "") : "chatgpt.com";
|
|
15648
15750
|
const loginUrl = typeof body.login_url === "string" ? body.login_url.trim() : void 0;
|
|
15751
|
+
const accountEmail = typeof body.account_email === "string" ? body.account_email.trim() || null : null;
|
|
15752
|
+
const note = typeof body.note === "string" ? body.note.trim() || null : null;
|
|
15649
15753
|
if (!profileName) return c.json({ error: "profile is required" }, 400);
|
|
15650
15754
|
if (!domain) return c.json({ error: "domain is required" }, 400);
|
|
15651
15755
|
try {
|
|
15652
15756
|
const result = await startProfileOnboarding({ profileName, domain, loginUrl });
|
|
15757
|
+
try {
|
|
15758
|
+
await saveAuthConnection({
|
|
15759
|
+
connectionId: result.connectionId,
|
|
15760
|
+
hostedUrl: result.hostedUrl,
|
|
15761
|
+
domain: result.domain,
|
|
15762
|
+
profile: result.profileName,
|
|
15763
|
+
accountEmail,
|
|
15764
|
+
note
|
|
15765
|
+
});
|
|
15766
|
+
} catch {
|
|
15767
|
+
}
|
|
15653
15768
|
return c.json({
|
|
15654
15769
|
connection_id: result.connectionId,
|
|
15655
15770
|
profile: result.profileName,
|
|
@@ -15658,8 +15773,6 @@ function buildBrowserAgentRoutes() {
|
|
|
15658
15773
|
flow_status: result.flowStatus,
|
|
15659
15774
|
flow_step: result.flowStep,
|
|
15660
15775
|
flow_expires_at: result.flowExpiresAt,
|
|
15661
|
-
hosted_url: result.hostedUrl,
|
|
15662
|
-
live_view_url: result.liveViewUrl,
|
|
15663
15776
|
browser_session_id: result.browserSessionId,
|
|
15664
15777
|
post_login_url: result.postLoginUrl
|
|
15665
15778
|
});
|
|
@@ -15674,6 +15787,17 @@ function buildBrowserAgentRoutes() {
|
|
|
15674
15787
|
const domain = typeof body.domain === "string" ? body.domain.trim().replace(/^https?:\/\//, "").replace(/\/.*$/, "") : void 0;
|
|
15675
15788
|
try {
|
|
15676
15789
|
const result = await getProfileOnboardingStatus({ connectionId, profileName, domain });
|
|
15790
|
+
if (result.hostedUrl) {
|
|
15791
|
+
try {
|
|
15792
|
+
await saveAuthConnection({
|
|
15793
|
+
connectionId: result.connectionId,
|
|
15794
|
+
hostedUrl: result.hostedUrl,
|
|
15795
|
+
domain: result.domain,
|
|
15796
|
+
profile: result.profileName
|
|
15797
|
+
});
|
|
15798
|
+
} catch {
|
|
15799
|
+
}
|
|
15800
|
+
}
|
|
15677
15801
|
return c.json({
|
|
15678
15802
|
connection_id: result.connectionId,
|
|
15679
15803
|
profile: result.profileName,
|
|
@@ -15682,8 +15806,6 @@ function buildBrowserAgentRoutes() {
|
|
|
15682
15806
|
flow_status: result.flowStatus,
|
|
15683
15807
|
flow_step: result.flowStep,
|
|
15684
15808
|
flow_expires_at: result.flowExpiresAt,
|
|
15685
|
-
hosted_url: result.hostedUrl,
|
|
15686
|
-
live_view_url: result.liveViewUrl,
|
|
15687
15809
|
browser_session_id: result.browserSessionId,
|
|
15688
15810
|
post_login_url: result.postLoginUrl
|
|
15689
15811
|
});
|
|
@@ -15691,6 +15813,37 @@ function buildBrowserAgentRoutes() {
|
|
|
15691
15813
|
return c.json(failure(err), 404);
|
|
15692
15814
|
}
|
|
15693
15815
|
});
|
|
15816
|
+
app2.post("/profiles/list", async (c) => {
|
|
15817
|
+
const body = await c.req.json().catch(() => ({}));
|
|
15818
|
+
const profileName = typeof body.profile === "string" ? body.profile.trim() : "";
|
|
15819
|
+
if (!profileName) return c.json({ error: "profile is required" }, 400);
|
|
15820
|
+
const domainFilter = typeof body.domain === "string" ? body.domain.trim().replace(/^https?:\/\//, "").replace(/\/.*$/, "") : void 0;
|
|
15821
|
+
const connectionIdFilter = typeof body.connection_id === "string" ? body.connection_id.trim() : void 0;
|
|
15822
|
+
try {
|
|
15823
|
+
const [live, stored] = await Promise.all([
|
|
15824
|
+
listProfileConnections(profileName),
|
|
15825
|
+
listAuthConnectionsByProfile(profileName).catch(() => [])
|
|
15826
|
+
]);
|
|
15827
|
+
const notesByDomain = new Map(stored.map((row) => [row.domain ?? "", row]));
|
|
15828
|
+
const notesByConnection = new Map(stored.map((row) => [row.connection_id, row]));
|
|
15829
|
+
let connections = live.map((conn) => {
|
|
15830
|
+
const record = notesByConnection.get(conn.connectionId) ?? notesByDomain.get(conn.domain);
|
|
15831
|
+
return {
|
|
15832
|
+
connection_id: conn.connectionId,
|
|
15833
|
+
domain: conn.domain,
|
|
15834
|
+
status: conn.status,
|
|
15835
|
+
account_email: record?.account_email ?? null,
|
|
15836
|
+
note: record?.note ?? null,
|
|
15837
|
+
last_connected_at: record?.updated_at ?? record?.created_at ?? null
|
|
15838
|
+
};
|
|
15839
|
+
});
|
|
15840
|
+
if (connectionIdFilter) connections = connections.filter((c2) => c2.connection_id === connectionIdFilter);
|
|
15841
|
+
if (domainFilter) connections = connections.filter((c2) => c2.domain === domainFilter);
|
|
15842
|
+
return c.json({ profile: profileName, connections });
|
|
15843
|
+
} catch (err) {
|
|
15844
|
+
return c.json(failure(err), 502);
|
|
15845
|
+
}
|
|
15846
|
+
});
|
|
15694
15847
|
app2.post("/sessions", async (c) => {
|
|
15695
15848
|
const user = c.get("user");
|
|
15696
15849
|
if (Number(user.balance_mc ?? 0) < BROWSER_OPEN_MIN_BALANCE_MC) {
|
|
@@ -15758,6 +15911,7 @@ function buildBrowserAgentRoutes() {
|
|
|
15758
15911
|
const user = c.get("user");
|
|
15759
15912
|
const row = await loadOpenSession(c.req.param("id"), user.id);
|
|
15760
15913
|
if (!row) return c.json({ error: "not found" }, 404);
|
|
15914
|
+
await charge(row.id, user.id);
|
|
15761
15915
|
try {
|
|
15762
15916
|
await closeSession(row.runtime_session_id);
|
|
15763
15917
|
} catch {
|
|
@@ -16047,6 +16201,50 @@ function buildBrowserAgentRoutes() {
|
|
|
16047
16201
|
}
|
|
16048
16202
|
|
|
16049
16203
|
// src/api/browser-agent-console.ts
|
|
16204
|
+
function renderAuthConsoleHtml(connectionId, hostedUrl) {
|
|
16205
|
+
const safeId = String(connectionId).replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
16206
|
+
const goUrl = `/console/auth/${encodeURIComponent(connectionId)}/go`;
|
|
16207
|
+
const body = hostedUrl ? `<iframe class="stage" allow="clipboard-read; clipboard-write" src="${goUrl}"></iframe>
|
|
16208
|
+
<div class="fallback">
|
|
16209
|
+
<span>Sign-in window not showing?</span>
|
|
16210
|
+
<a class="btn" href="${goUrl}" target="_blank" rel="noopener">Open sign-in in a new tab</a>
|
|
16211
|
+
</div>` : `<div class="empty">
|
|
16212
|
+
<div>This sign-in link is no longer active.</div>
|
|
16213
|
+
<div class="muted">Ask the assistant to reconnect the profile, then open the new link.</div>
|
|
16214
|
+
</div>`;
|
|
16215
|
+
return `<!DOCTYPE html>
|
|
16216
|
+
<html lang="en">
|
|
16217
|
+
<head>
|
|
16218
|
+
<meta charset="UTF-8" />
|
|
16219
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
16220
|
+
<title>MCP Scraper \u2014 Secure Sign-in</title>
|
|
16221
|
+
<style>
|
|
16222
|
+
:root { color-scheme: dark; }
|
|
16223
|
+
:where(*) { box-sizing: border-box; }
|
|
16224
|
+
body { margin: 0; height: 100vh; display: flex; flex-direction: column; font: 14px/1.5 ui-sans-serif, system-ui, -apple-system, sans-serif; background: #0b0e14; color: #d7dce5; }
|
|
16225
|
+
header { display: flex; align-items: center; gap: 10px; padding: 12px 18px; border-bottom: 1px solid #1c2230; background: #0f131c; }
|
|
16226
|
+
header h1 { font-size: 14px; margin: 0; font-weight: 600; color: #fff; }
|
|
16227
|
+
header .muted { color: #6b7689; font-size: 12px; }
|
|
16228
|
+
.spacer { flex: 1; }
|
|
16229
|
+
.stage { flex: 1; width: 100%; border: 0; display: block; background: #05070b; }
|
|
16230
|
+
.fallback { display: flex; align-items: center; gap: 12px; padding: 10px 18px; border-top: 1px solid #1c2230; background: #0f131c; font-size: 13px; color: #8893a7; }
|
|
16231
|
+
.btn { background: #2b6cff; color: #fff; border-radius: 7px; padding: 7px 12px; text-decoration: none; font-weight: 500; }
|
|
16232
|
+
.empty { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; color: #8893a7; }
|
|
16233
|
+
.muted { color: #6b7689; font-size: 12px; }
|
|
16234
|
+
</style>
|
|
16235
|
+
${ATTRIBUTION_PIXEL_SCRIPT}
|
|
16236
|
+
</head>
|
|
16237
|
+
<body>
|
|
16238
|
+
<header>
|
|
16239
|
+
<h1>MCP Scraper</h1>
|
|
16240
|
+
<span class="muted">Secure sign-in \xB7 ${safeId}</span>
|
|
16241
|
+
<span class="spacer"></span>
|
|
16242
|
+
<span class="muted">Complete the login below \u2014 your session is saved to your profile.</span>
|
|
16243
|
+
</header>
|
|
16244
|
+
${body}
|
|
16245
|
+
</body>
|
|
16246
|
+
</html>`;
|
|
16247
|
+
}
|
|
16050
16248
|
function renderConsoleHtml(initialSessionId) {
|
|
16051
16249
|
const initial = JSON.stringify(initialSessionId ?? "");
|
|
16052
16250
|
return `<!DOCTYPE html>
|
|
@@ -17378,14 +17576,15 @@ app.get("/cron/tick", async (c) => {
|
|
|
17378
17576
|
if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
|
|
17379
17577
|
return c.json({ error: "Unauthorized" }, 401);
|
|
17380
17578
|
}
|
|
17381
|
-
const { drainQueue } = await import("./worker-
|
|
17579
|
+
const { drainQueue } = await import("./worker-YZ2ZJE4F.js");
|
|
17382
17580
|
const budget = { maxJobs: 10, deadlineMs: Date.now() + 28e4 };
|
|
17383
17581
|
const workflowDispatchResult = await dispatchDueWorkflowSchedules(`${new URL(c.req.url).protocol}//${new URL(c.req.url).host}`);
|
|
17384
|
-
const [results, sweepResult] = await Promise.all([
|
|
17582
|
+
const [results, sweepResult, reapResult] = await Promise.all([
|
|
17385
17583
|
drainQueue(budget),
|
|
17386
|
-
runMonthlyRefreshSweep()
|
|
17584
|
+
runMonthlyRefreshSweep(),
|
|
17585
|
+
reapIdleBrowserSessions(120).catch(() => ({ reaped: 0 }))
|
|
17387
17586
|
]);
|
|
17388
|
-
return c.json({ drained: results.length, results, sweepResult, workflowDispatch: workflowDispatchResult });
|
|
17587
|
+
return c.json({ drained: results.length, results, sweepResult, reaped: reapResult, workflowDispatch: workflowDispatchResult });
|
|
17389
17588
|
});
|
|
17390
17589
|
app.on(["GET", "POST", "PUT"], "/api/inngest", serveInngest({ client: inngest, functions: [siteAuditFn, siteExtractFn] }));
|
|
17391
17590
|
app.route("/api/internal/site-architecture-auditor", siteAuditApp);
|
|
@@ -17400,6 +17599,19 @@ app.route("/serp-intelligence", serpIntelligenceApp);
|
|
|
17400
17599
|
app.route("/mcp", mcpApp);
|
|
17401
17600
|
app.route("/agent", buildBrowserAgentRoutes());
|
|
17402
17601
|
app.get("/console", (c) => c.html(renderConsoleHtml()));
|
|
17602
|
+
app.get("/console/auth/:id", async (c) => {
|
|
17603
|
+
const id = c.req.param("id");
|
|
17604
|
+
await migrateBrowserAgent();
|
|
17605
|
+
const row = await getAuthConnectionRow(id);
|
|
17606
|
+
return c.html(renderAuthConsoleHtml(id, row?.hosted_url ?? null));
|
|
17607
|
+
});
|
|
17608
|
+
app.get("/console/auth/:id/go", async (c) => {
|
|
17609
|
+
const id = c.req.param("id");
|
|
17610
|
+
await migrateBrowserAgent();
|
|
17611
|
+
const row = await getAuthConnectionRow(id);
|
|
17612
|
+
if (!row?.hosted_url) return c.text("This sign-in link is no longer active.", 404);
|
|
17613
|
+
return c.redirect(row.hosted_url, 302);
|
|
17614
|
+
});
|
|
17403
17615
|
app.get("/console/:id", (c) => c.html(renderConsoleHtml(c.req.param("id"))));
|
|
17404
17616
|
app.route("/stripe", stripeApp);
|
|
17405
17617
|
if (!process.env.INNGEST_EVENT_KEY) {
|
|
@@ -17508,4 +17720,4 @@ app.get("/blog/:slug/", (c) => {
|
|
|
17508
17720
|
export {
|
|
17509
17721
|
app
|
|
17510
17722
|
};
|
|
17511
|
-
//# sourceMappingURL=server-
|
|
17723
|
+
//# sourceMappingURL=server-EYNMXWUJ.js.map
|