mcp-scraper 0.88.2 → 0.89.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -15
- package/README.md +6 -5
- package/THIRD_PARTY_NOTICES.html +203 -0
- package/dist/analytics-repository-2BMT5JNE.js +1 -0
- package/dist/bin/api-server.js +2 -41
- package/dist/bin/mcp-scraper-cli.js +39 -756
- package/dist/bin/mcp-scraper-core.js +1 -60
- package/dist/bin/mcp-scraper-install.js +2 -25
- package/dist/bin/mcp-stdio-server.js +1 -19
- package/dist/bin/paa-harvest.js +1 -41
- package/dist/chunk-3GP5CYZX.js +1 -0
- package/dist/chunk-4AI7DOS7.js +59 -0
- package/dist/chunk-4FROKQJN.js +1 -0
- package/dist/chunk-7YGVI5J4.js +21710 -0
- package/dist/chunk-CCYWSJNG.js +16 -0
- package/dist/chunk-CFI6CXIV.js +182 -0
- package/dist/chunk-E2WRWV3A.js +1 -0
- package/dist/chunk-GMWKPIYX.js +1172 -0
- package/dist/chunk-HDPYG3XV.js +102 -0
- package/dist/chunk-HE45FFBU.js +1 -0
- package/dist/chunk-HUV2WTRW.js +1 -0
- package/dist/chunk-KJQXUZ4Y.js +4 -0
- package/dist/chunk-L4CGLFPU.js +4 -0
- package/dist/chunk-M22MM4N4.js +84 -0
- package/dist/chunk-MASR22K4.js +73 -0
- package/dist/chunk-MZN4U5BL.js +1 -0
- package/dist/chunk-PUHFVA7P.js +1280 -0
- package/dist/chunk-QPWPR5XG.js +10 -0
- package/dist/chunk-TMB56NCA.js +1 -0
- package/dist/chunk-TXENITMS.js +20 -0
- package/dist/chunk-W2BVJ7S2.js +13 -0
- package/dist/chunk-WO3N5FH2.js +5 -0
- package/dist/chunk-WSCGYRWA.js +2595 -0
- package/dist/chunk-X54CQLK2.js +1 -0
- package/dist/chunk-XLWNEVUZ.js +27 -0
- package/dist/chunk-XPZVJIZ2.js +100 -0
- package/dist/chunk-YQZGZBB4.js +1 -0
- package/dist/chunk-Z2QGQJS2.js +1 -0
- package/dist/db-F2MX63GI.js +1 -0
- package/dist/extract-bundle-SNUIHM3J.js +26 -0
- package/dist/gmail-service-BZ3H75XC.js +1 -0
- package/dist/index.cjs +21750 -6045
- package/dist/index.d.cts +14 -14
- package/dist/index.d.ts +14 -14
- package/dist/index.js +18 -315
- package/dist/lead-list-enrichment-repository-S2H3U7T7.js +1 -0
- package/dist/location-data-repository-OTWHWMV6.js +1 -0
- package/dist/server-RFR2A5UJ.js +7303 -0
- package/dist/site-extract-repository-SE776XDC.js +1 -0
- package/dist/worker-XUDSM3AL.js +1 -0
- package/package.json +17 -124
- package/dist/analytics-repository-GGJJCVVP.js +0 -194
- package/dist/chunk-4QMUF6XM.js +0 -1013
- package/dist/chunk-6HAV7LCE.js +0 -265
- package/dist/chunk-ABF2CGOZ.js +0 -113
- package/dist/chunk-C5Z4OFKW.js +0 -404
- package/dist/chunk-DNM65UCK.js +0 -299
- package/dist/chunk-EQGTEHLZ.js +0 -592
- package/dist/chunk-F5GQJWZU.js +0 -732
- package/dist/chunk-GGZEC22A.js +0 -215
- package/dist/chunk-GXBZXWXB.js +0 -184
- package/dist/chunk-IHXAXYIS.js +0 -843
- package/dist/chunk-K3Z5AQYE.js +0 -683
- package/dist/chunk-K45K75OF.js +0 -6
- package/dist/chunk-LFW2FRPJ.js +0 -224
- package/dist/chunk-MZDNZQWT.js +0 -2078
- package/dist/chunk-NVUKO5NN.js +0 -256
- package/dist/chunk-OM7HVEJ3.js +0 -26
- package/dist/chunk-OPQIGAFB.js +0 -286
- package/dist/chunk-OZJMVCDK.js +0 -16
- package/dist/chunk-P7FWOMU7.js +0 -505
- package/dist/chunk-PGJQDMC2.js +0 -383
- package/dist/chunk-PKZS6SHW.js +0 -33139
- package/dist/chunk-RJ7JVYKU.js +0 -68
- package/dist/chunk-S24LFPL7.js +0 -5262
- package/dist/chunk-T3MZISOF.js +0 -240
- package/dist/chunk-UZPTGUDV.js +0 -1915
- package/dist/chunk-X623GTBV.js +0 -8290
- package/dist/chunk-YXNDOQXN.js +0 -4018
- package/dist/db-Z34LPZNR.js +0 -284
- package/dist/extract-bundle-565SBZCR.js +0 -1003
- package/dist/gmail-service-E6ALS7JG.js +0 -25
- package/dist/lead-list-enrichment-repository-36RPVV6N.js +0 -67
- package/dist/location-data-repository-WPRG62GE.js +0 -34
- package/dist/server-SQZ3A7SY.js +0 -86606
- package/dist/site-extract-repository-VYFZASPU.js +0 -69
- package/dist/worker-LDCAULWL.js +0 -146
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
var s={message:"Discover public Facebook Reel URLs, retain partial inventories, and transcribe selected videos. Facebook extraction now dismisses optional login dialogs and retries failed public acquisition."};var _={reset:"\x1B[0m",cyan:"\x1B[36m",lime:"\x1B[32m",amber:"\x1B[33m",red:"\x1B[31m",muted:"\x1B[90m",bold:"\x1B[1m"};function r(t,e,n){return n?`${_[e]}${t}${_.reset}`:t}function o(t,e,n){let a=t.padEnd(9," ");return` ${r(a,"muted",n)} ${e.join(r(" . ","muted",n))}`}function p(t){let e=t.color??!0,n=t.apiKeyConfigured?"$MCP_SCRAPER_API_KEY":"sk_live_your_key",a=String.raw`
|
|
2
|
+
__ __ ____ ____
|
|
3
|
+
| \/ |/ ___| _ \
|
|
4
|
+
| |\/| | | | |_) |
|
|
5
|
+
| | | | |___| __/
|
|
6
|
+
|_| |_|\____|_|
|
|
7
|
+
|
|
8
|
+
____ ____ ____ _ ____ _____ ____
|
|
9
|
+
/ ___| / ___| _ \ / \ | _ \| ____| _ \
|
|
10
|
+
\___ \| | | |_) | / _ \ | |_) | _| | |_) |
|
|
11
|
+
___) | |___| _ < / ___ \| __/| |___| _ <
|
|
12
|
+
|____/ \____|_| \_\/_/ \_\_| |_____|_| \_\
|
|
13
|
+
`,i=[`MCP_SCRAPER_API_KEY=${n} npx -y -p mcp-scraper@latest \\`," mcp-scraper-cli agent install claude --apply"].join(`
|
|
14
|
+
`),c=["[mcp_servers.mcp-scraper]",'command = "npx"','args = ["-y", "-p", "mcp-scraper@latest", "mcp-scraper"]',`env = { MCP_SCRAPER_API_KEY = "${n}" }`].join(`
|
|
15
|
+
`);return[r(`mcp-scraper v${t.version}`,"bold",e),r("> mcp-scraper-install","muted",e),r(a,"amber",e),`${r("MCP Scraper Agent","cyan",e)} . v${t.version} . mcpscraper.dev`,"1/1 install surfaces ready",r(`Newest in v${t.version}: ${s.message}`,"lime",e),"",`${r("Tools","cyan",e)} ${r("(376 MCP tools)","muted",e)}`,o("search",["harvest_paa","search_serp","maps_search","maps_place_intel"],e),o("extract",["extract_url","map_site_urls","extract_site","audit_site","directory_workflow"],e),o("build",["create_editorial_reading_room","rank_tracker_workflow","portable HTML"],e),o("media",["youtube_harvest","youtube_transcribe","facebook_reels_inventory","facebook_ad_search","facebook_page_intel","facebook_ad_transcribe","facebook_video_transcribe","instagram_profile_content","instagram_media_download","reddit_thread"],e),o("browser",["serp_identity_create","serp_identity_list","browser_open","browser_profile_connect","browser_profile_list","browser_close","browser_screenshot","browser_read","browser_locate","browser_replay_mark","browser_replay_annotate"],e),o("connect",["list_service_connections","describe_service_connection_tool","import_service_connection_to_memory","export_connected_service_data","renew_connected_data_download","read_service_connection","call_service_connection_action"],e),o("commons",["commons_search_entities","commons_get_entity_linkset","commons_prepare_entity","commons_submit_entity","commons_prepare_publication","commons_claim_publication","commons_publish_editorial","commons_get_publication"],e),o("account",["credits_info","reports","MCP resources"],e),o("memory",["memory-put","memory-get","memory-search","list-vaults","record-fact","list-scheduled-actions"],e),`${r("Workflows","cyan",e)} ${r("(MCP + CLI + API)","muted",e)}`,o("route",["workflow_list","workflow_suggest","workflow_run","workflow_step","workflow_status","workflow_artifact_read"],e),o("seo",["directory","agent-packet","competitive audit","map/serp comparison","PAA/AIO briefs","scheduled runs"],e),"",r("Usage tips:","amber",e),"Run mcp-scraper-install for this visible card. Run mcp-scraper-cli for setup utilities and subcommands.","Run mcp-scraper in a human terminal to print this card; MCP clients get the same command as a silent stdio server.","Explicit card command: npx -y -p mcp-scraper@latest mcp-scraper-install","Hosted browser sessions use direct/no-proxy egress by default.","Customer auth setup: run browser_profile_connect, send the watch_url, let the user sign in, then call browser_profile_list until AUTHENTICATED.","Connected account ranges: call export_connected_service_data once. It handles Gmail, Calendar, Zoom, and Resend pagination; do not loop read_service_connection over individual records.","Connected account RAG: call import_service_connection_to_memory for one bounded approved read. It writes a redacted, untrusted snapshot to a stable Memory path and embeds it for search.","Stack logins / reconnect: run browser_profile_connect again with the same profile name and another domain to add accounts or refresh a login.","Start with workflow_suggest for broad jobs like market analysis, ICP research, CRO audits, brand briefs, content gaps, and AI visibility.","For MCP clients, use mcp-scraper so one install can mix SERP, Maps, browser, reports, and saved MCP resources.","If you hit the concurrency limit, add 2 browsers for $5/month with mcp-scraper-cli billing concurrency checkout.","",`${r("Ready.","lime",e)} Install the combined MCP server with one command:`,"",r("Setup doctor","amber",e),"npx -y -p mcp-scraper@latest mcp-scraper-cli doctor","",r("Hosted profile setup","amber",e),'In your MCP client, call browser_profile_connect with email="seo@example.com" and domain="chatgpt.com".',"Give the returned watch_url to the user. After they sign in, call browser_profile_list, then browser_open with the returned profile. Add more logins by calling browser_profile_connect again with the same profile and a new domain.","",r("Claude Code one-command setup","amber",e),i,"Then fully exit Claude Code and open a new Claude terminal. Check with: claude mcp list","",r("Codex config","amber",e),c,"",r("Claude Desktop Extension","amber",e),"Download: https://mcpscraper.dev/downloads/mcp-scraper.mcpb","",r("Safety note:","muted",e),"mcp-scraper prints this card only when stdin/stdout are an interactive TTY. In MCP clients it writes only JSON-RPC to stdout.","Use --stdio or MCP_SCRAPER_FORCE_STDIO=1 to force server mode from a terminal.",""].join(`
|
|
16
|
+
`)}export{p as a};
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import{a as j,b as T,c as P,d as C,f as S}from"./chunk-TMB56NCA.js";import{W as w}from"./chunk-MZN4U5BL.js";import{Ib as D,d as m,e as I,i as u,s,zb as N}from"./chunk-GMWKPIYX.js";import{createHash as W}from"crypto";import{gunzipSync as $,gzipSync as q}from"zlib";var y="site-extracts/",L=10080*60*1e3,H=900*1e3;function U(){return process.env.SITE_EXTRACT_ARTIFACT_READ_WRITE_TOKEN?.trim()||process.env.PRIVATE_ARTIFACT_READ_WRITE_TOKEN?.trim()||process.env.CONNECTED_DATA_READ_WRITE_TOKEN?.trim()||process.env.CONNECTED_DATA_BLOB_READ_WRITE_TOKEN?.trim()||null}function v(){return process.env.VERCEL==="1"||process.env.NODE_ENV==="production"}function p(){return{prefix:y,artifactTtlMs:L,downloadTtlMs:H,token:U()}}function h(t){return j(t,y)}async function V(t){let e=await P({policy:p(),ownerId:t.ownerId,artifactKey:`${t.jobId}.zip`,createdAt:t.createdAt,filename:`${t.jobId}-site-export.zip`,contentType:"application/zip",content:t.content});return{key:e.artifactId,url:e.downloadUrl??"",bytes:e.bytes,contentType:e.contentType,filename:e.filename,sha256:e.sha256,expiresAt:e.expiresAt,downloadUrlExpiresAt:e.downloadUrlExpiresAt,kind:"bundle"}}async function G(t){let e=await T({policy:p(),ownerId:t.ownerId,scopeSegments:[t.jobId,"images"],artifactKey:`${t.imageId}.bin`,createdAt:new Date,filename:t.filename,contentType:t.contentType,content:t.content});return{key:e.artifactId,url:e.downloadUrl??"",bytes:e.bytes,contentType:e.contentType,filename:e.filename,sha256:e.sha256,expiresAt:e.expiresAt,downloadUrlExpiresAt:e.downloadUrlExpiresAt,kind:"image",imageId:t.imageId,sourceUrl:t.sourceUrl,sourcePage:t.sourcePage}}async function O(t){return T({policy:p(),ownerId:t.ownerId,scopeSegments:[t.jobId,"content"],artifactKey:`${t.chunkKey}.json.gz`,createdAt:t.createdAt,filename:`${t.chunkKey}.json.gz`,contentType:"application/gzip",content:t.content})}async function Q(t){return C({policy:p(),artifactId:t.artifactId,ownerId:t.ownerId})}async function J(t){return S({policy:p(),artifactId:t,maxBytes:50*1024*1024})}async function Z(t){return h(t.artifactId)!==t.ownerId?null:J(t.artifactId)}async function k(t){return h(t.artifactId)!==t.ownerId?null:S({policy:p(),artifactId:t.artifactId,maxBytes:25*1024*1024})}async function tt(t){return h(t.artifactId)!==t.ownerId?null:S({policy:p(),artifactId:t.artifactId,maxBytes:10*1024*1024})}async function et(t={}){let e=t.now??new Date,n=U();if(!n)return{deleted:0,store:v()?"none":"local"};if(!t.force&&!(e.getUTCHours()===3&&e.getUTCMinutes()===21))return{deleted:0,store:"private-vercel-blob",skipped:!0};let r=e.getTime()-L,{list:i,del:a}=await import("@vercel/blob"),o,c=0;for(let d=0;d<20;d+=1){let l=await i({prefix:y,token:n,limit:1e3,cursor:o}),f=l.blobs.filter(A=>new Date(A.uploadedAt).getTime()<=r);if(f.length>0&&(await a(f.map(A=>A.pathname),{token:n}),c+=f.length),!l.hasMore||!l.cursor)break;o=l.cursor}return{deleted:c,store:"private-vercel-blob"}}var F=10,b=20*1024*1024;function E(t){return W("sha256").update(t).digest("hex")}function B(t){return!t.acquiredHtml||t.extractionStatus==="failed"?null:{pageId:t.pageId??E(t.archivedUrl??t.url),url:t.archivedUrl??t.url,html:t.acquiredHtml,mainHtml:t.mainHtml??"",markdown:t.bodyMarkdown,htmlSha256:t.htmlSha256??E(t.acquiredHtml),markdownSha256:t.markdownSha256??E(t.bodyMarkdown)}}function R(t){return Buffer.from(JSON.stringify({version:"site-extract-content.v1",pages:t}))}async function M(t){let e=t.pages.flatMap(o=>{let c=B(o);return c?[c]:[]}),n=new Map,r=[],i=0,a=async()=>{if(!r.length)return;let o=R(r);if(o.length>b)throw new Error(`site export content chunk exceeds ${b} bytes`);let c=E(r.map(l=>l.pageId).join("\0")).slice(0,16),d=await O({ownerId:t.ownerId,jobId:t.jobId,createdAt:t.createdAt,chunkKey:`content-${String(i).padStart(4,"0")}-${c}`,content:q(o,{level:6})});for(let l of r)n.set(l.url,{artifactId:d.artifactId,artifactSha256:d.sha256,pageId:l.pageId,uncompressedBytes:o.length});i++,r=[]};for(let o of e){let c=[...r,o];if(r.length>0&&(c.length>F||R(c).length>b)&&await a(),r.push(o),R(r).length>b)throw new Error(`page ${o.pageId} exceeds the durable content chunk limit`)}return await a(),n}function it(t){let e=new Map;return async n=>{let r=e.get(n.artifactId);if(!r){let a=await k({artifactId:n.artifactId,ownerId:t});if(!a)throw new Error("site export content chunk is missing or unauthorized");if(E(a)!==n.artifactSha256)throw new Error("site export content chunk checksum mismatch");let o=$(a);if(o.length>b)throw new Error("site export content chunk exceeds its read limit");if(r=JSON.parse(o.toString("utf8")),r.version!=="site-extract-content.v1"||!Array.isArray(r.pages))throw new Error("site export content chunk has an unsupported contract");for(e.set(n.artifactId,r);e.size>2;)e.delete(e.keys().next().value)}let i=r.pages.find(a=>a.pageId===n.pageId);if(!i)throw new Error("site export page is missing from its content chunk");if(E(i.html)!==i.htmlSha256||E(i.markdown)!==i.markdownSha256)throw new Error("site export page checksum mismatch");return i}}var g="xray_entitlement";function pt(t){let e=Math.max(1,Number(t.options.effectiveMaxPages??t.options.maxPages??1)),n=Math.max(e,Number(t.options.requestedMaxPages??e)),r=n>e;return{requestedMaxPages:n,effectiveMaxPages:e,creditLimited:r,creditTruncated:r&&t.totalUrls>=e}}function Et(t,e=!1){return t.successfulUrls===0?"failed":t.failedUrls>0||t.remainingUrls>0||e?"partial":"complete"}function _(t){let e=Number(t.total_urls??0),n=t.attempted_urls==null,r=Number(n?t.done_urls??0:t.attempted_urls),i=Number(n?t.done_urls??0:t.successful_urls??0),a=Number(t.failed_urls??Math.max(0,r-i));return{id:String(t.id),userId:t.user_id!=null?Number(t.user_id):null,idempotencyKey:t.idempotency_key!=null?String(t.idempotency_key):null,requestFingerprint:t.request_fingerprint!=null?String(t.request_fingerprint):null,status:t.status!=null?String(t.status):"pending",startUrl:String(t.start_url??""),options:t.options?JSON.parse(String(t.options)):{},totalUrls:e,doneUrls:r,attemptedUrls:r,successfulUrls:i,failedUrls:a,remainingUrls:Math.max(0,e-r),artifacts:t.artifacts?JSON.parse(String(t.artifacts)):null,error:t.error!=null?String(t.error):null,publicError:I(t.public_error_json),billedMc:t.billed_mc!=null?Number(t.billed_mc):null,createdAt:String(t.created_at??""),updatedAt:String(t.updated_at??"")}}async function mt(t,e,n,r){await s().execute({sql:`INSERT INTO site_extract_jobs (id, user_id, status, start_url, options, created_at, updated_at)
|
|
2
|
+
VALUES (?, ?, 'pending', ?, ?, datetime('now'), datetime('now'))`,args:[t,e,n,JSON.stringify(r)]})}async function K(t,e){let r=await s().execute({sql:"SELECT * FROM site_extract_jobs WHERE user_id = ? AND idempotency_key = ? LIMIT 1",args:[t,e]});return r.rows[0]?_(r.rows[0]):null}async function gt(t){let n=await s().execute({sql:`INSERT OR IGNORE INTO site_extract_jobs
|
|
3
|
+
(id, user_id, status, start_url, options, idempotency_key, request_fingerprint, created_at, updated_at)
|
|
4
|
+
VALUES (?, ?, 'pending', ?, ?, ?, ?, datetime('now'), datetime('now'))`,args:[t.jobId,t.userId,t.startUrl,JSON.stringify(t.options),t.idempotencyKey,t.requestFingerprint]}),r=await K(t.userId,t.idempotencyKey);if(!r)throw new Error("idempotent extract job was not persisted");return{job:r,created:n.rowsAffected===1,conflict:r.requestFingerprint!==t.requestFingerprint}}async function x(t){let n=await s().execute({sql:"SELECT * FROM site_extract_jobs WHERE id = ?",args:[t]});return n.rows[0]?_(n.rows[0]):null}async function _t(t){return(await s().execute({sql:"SELECT * FROM site_extract_jobs WHERE user_id = ? ORDER BY created_at DESC LIMIT 50",args:[t]})).rows.map(r=>_(r))}async function xt(t=25){return(await s().execute({sql:`SELECT * FROM site_extract_jobs
|
|
5
|
+
WHERE billed_mc IS NULL AND status IN ('complete', 'partial', 'failed')
|
|
6
|
+
AND (next_settlement_at IS NULL OR next_settlement_at <= datetime('now'))
|
|
7
|
+
ORDER BY updated_at ASC LIMIT ?`,args:[Math.max(1,Math.min(100,Math.round(t)))]})).rows.map(r=>_(r))}async function ft(t=25,e=2){let n=s(),r=Math.max(1,Math.min(60,Math.round(e)));return(await n.execute({sql:`SELECT j.* FROM site_extract_jobs j
|
|
8
|
+
LEFT JOIN billing_debits d
|
|
9
|
+
ON d.idempotency_key = json_extract(j.options, '$.debitKey')
|
|
10
|
+
AND d.user_id = j.user_id
|
|
11
|
+
AND d.status = 'applied'
|
|
12
|
+
WHERE j.status = 'pending'
|
|
13
|
+
AND j.billed_mc IS NULL
|
|
14
|
+
AND (
|
|
15
|
+
d.idempotency_key IS NOT NULL
|
|
16
|
+
OR (
|
|
17
|
+
json_extract(j.options, '$.billingClass') = ?
|
|
18
|
+
AND json_extract(j.options, '$.heldMc') = 0
|
|
19
|
+
AND json_extract(j.options, '$.debitKey') IS NULL
|
|
20
|
+
AND json_extract(j.options, '$.xraySetupReceipt.billingClass') = ?
|
|
21
|
+
AND json_extract(j.options, '$.xraySetupReceipt.consumesMcpScraperCredits') = 0
|
|
22
|
+
)
|
|
23
|
+
)
|
|
24
|
+
AND (j.next_dispatch_at IS NULL OR j.next_dispatch_at <= datetime('now'))
|
|
25
|
+
AND j.updated_at <= datetime('now', ?)
|
|
26
|
+
ORDER BY j.updated_at ASC LIMIT ?`,args:[g,g,`-${r} minutes`,Math.max(1,Math.min(100,Math.round(t)))]})).rows.map(a=>_(a))}async function bt(t){return await s().execute({sql:`UPDATE site_extract_jobs
|
|
27
|
+
SET dispatch_attempts = dispatch_attempts + 1,
|
|
28
|
+
updated_at = datetime('now'),
|
|
29
|
+
next_dispatch_at = datetime('now', '+5 minutes'),
|
|
30
|
+
dispatch_error = NULL,
|
|
31
|
+
status = CASE
|
|
32
|
+
WHEN dispatch_attempts + 1 >= 10 OR created_at <= datetime('now', '-6 hours') THEN 'failed'
|
|
33
|
+
ELSE status
|
|
34
|
+
END,
|
|
35
|
+
error = CASE
|
|
36
|
+
WHEN dispatch_attempts + 1 >= 10 OR created_at <= datetime('now', '-6 hours')
|
|
37
|
+
THEN 'Background crawl was acknowledged repeatedly but never started; the hold will be refunded.'
|
|
38
|
+
ELSE error
|
|
39
|
+
END
|
|
40
|
+
WHERE id = ? AND status = 'pending'`,args:[t]}),x(t)}async function wt(t,e){let n=s(),r=u(e).slice(0,1e3);return await n.execute({sql:`UPDATE site_extract_jobs SET
|
|
41
|
+
dispatch_attempts = dispatch_attempts + 1,
|
|
42
|
+
dispatch_error = ?,
|
|
43
|
+
next_dispatch_at = datetime('now', '+5 minutes'),
|
|
44
|
+
updated_at = datetime('now'),
|
|
45
|
+
status = CASE
|
|
46
|
+
WHEN dispatch_attempts + 1 >= 10 OR created_at <= datetime('now', '-6 hours') THEN 'failed'
|
|
47
|
+
ELSE status
|
|
48
|
+
END,
|
|
49
|
+
error = CASE
|
|
50
|
+
WHEN dispatch_attempts + 1 >= 10 OR created_at <= datetime('now', '-6 hours')
|
|
51
|
+
THEN 'Background crawl could not be dispatched after repeated attempts; the hold will be refunded.'
|
|
52
|
+
ELSE error
|
|
53
|
+
END
|
|
54
|
+
WHERE id = ? AND status = 'pending'`,args:[r,t]}),x(t)}async function St(t,e){await s().execute({sql:`UPDATE site_extract_jobs SET
|
|
55
|
+
settlement_attempts = settlement_attempts + 1,
|
|
56
|
+
settlement_error = ?,
|
|
57
|
+
next_settlement_at = datetime('now', '+15 minutes'),
|
|
58
|
+
updated_at = datetime('now')
|
|
59
|
+
WHERE id = ? AND billed_mc IS NULL`,args:[u(e).slice(0,1e3),t]})}async function At(t=25,e=60){let n=s(),r=Math.max(15,Math.min(1440,Math.round(e)));return(await n.execute({sql:`SELECT j.* FROM site_extract_jobs j
|
|
60
|
+
LEFT JOIN billing_debits d
|
|
61
|
+
ON d.idempotency_key = json_extract(j.options, '$.debitKey')
|
|
62
|
+
AND d.user_id = j.user_id
|
|
63
|
+
AND d.status = 'applied'
|
|
64
|
+
WHERE j.status = 'running'
|
|
65
|
+
AND j.billed_mc IS NULL
|
|
66
|
+
AND (
|
|
67
|
+
json_extract(j.options, '$.debitKey') IS NULL
|
|
68
|
+
OR d.idempotency_key IS NOT NULL
|
|
69
|
+
)
|
|
70
|
+
AND j.updated_at <= datetime('now', ?)
|
|
71
|
+
ORDER BY j.updated_at ASC LIMIT ?`,args:[`-${r} minutes`,Math.max(1,Math.min(100,Math.round(t)))]})).rows.map(a=>_(a))}async function Nt(t,e=60){let n=s(),r=Math.max(15,Math.min(1440,Math.round(e)));return(await n.execute({sql:`UPDATE site_extract_jobs
|
|
72
|
+
SET status = 'failed',
|
|
73
|
+
error = 'Background crawl stopped without a heartbeat; completed pages were preserved and settlement is pending.',
|
|
74
|
+
public_error_json = ?,
|
|
75
|
+
updated_at = datetime('now')
|
|
76
|
+
WHERE id = ?
|
|
77
|
+
AND status = 'running'
|
|
78
|
+
AND billed_mc IS NULL
|
|
79
|
+
AND updated_at <= datetime('now', ?)`,args:[m({error_code:"extraction_failed",error_type:"extraction",message:"The background crawl stopped without a heartbeat. Completed pages were preserved; retry after settlement completes.",retryable:!0,charge_status:"refund_pending"}),t,`-${r} minutes`]})).rowsAffected===1}async function Tt(t){return(await s().execute({sql:`UPDATE site_extract_jobs
|
|
80
|
+
SET status = 'running', updated_at = datetime('now')
|
|
81
|
+
WHERE id = ? AND status = 'failed'`,args:[t]})).rowsAffected!==1?null:x(t)}async function yt(t,e){await s().execute({sql:`UPDATE site_extract_jobs SET billed_mc = 0, settlement_error = ?, updated_at = datetime('now')
|
|
82
|
+
WHERE id = ? AND billed_mc IS NULL`,args:[u(e).slice(0,1e3),t]})}async function ht(t,e){await s().execute({sql:`UPDATE site_extract_jobs
|
|
83
|
+
SET status = 'running', total_urls = MAX(total_urls, ?), updated_at = datetime('now')
|
|
84
|
+
WHERE id = ? AND status IN ('pending', 'running')`,args:[e,t]})}async function Rt(t,e){if(e.length===0)return;let n=s(),r=await x(t),i=r?.userId!=null?await M({ownerId:String(r.userId),jobId:t,createdAt:r.createdAt,pages:e}):new Map;await n.batch([...e.map(a=>{let{discoveryLinks:o,acquiredHtml:c,mainHtml:d,...l}=a;return l.contentRef=i.get(a.archivedUrl??a.url)??a.contentRef,{sql:`INSERT INTO site_extract_pages (job_id, url, page)
|
|
85
|
+
SELECT ?, ?, ?
|
|
86
|
+
WHERE EXISTS (
|
|
87
|
+
SELECT 1 FROM site_extract_jobs
|
|
88
|
+
WHERE id = ? AND status IN ('pending', 'running')
|
|
89
|
+
)
|
|
90
|
+
ON CONFLICT(job_id, url) DO UPDATE SET page = excluded.page
|
|
91
|
+
WHERE NOT CASE
|
|
92
|
+
WHEN json_valid(site_extract_pages.page) = 0 THEN 0
|
|
93
|
+
WHEN json_extract(site_extract_pages.page, '$.extractionStatus') IS NOT NULL
|
|
94
|
+
THEN json_extract(site_extract_pages.page, '$.extractionStatus') = 'successful'
|
|
95
|
+
ELSE COALESCE(
|
|
96
|
+
json_extract(site_extract_pages.page, '$.status') >= 200
|
|
97
|
+
AND json_extract(site_extract_pages.page, '$.status') < 300
|
|
98
|
+
AND json_extract(site_extract_pages.page, '$.wordCount') > 0,
|
|
99
|
+
0
|
|
100
|
+
)
|
|
101
|
+
END
|
|
102
|
+
OR CASE
|
|
103
|
+
WHEN json_valid(excluded.page) = 0 THEN 0
|
|
104
|
+
WHEN json_extract(excluded.page, '$.extractionStatus') IS NOT NULL
|
|
105
|
+
THEN json_extract(excluded.page, '$.extractionStatus') = 'successful'
|
|
106
|
+
ELSE COALESCE(
|
|
107
|
+
json_extract(excluded.page, '$.status') >= 200
|
|
108
|
+
AND json_extract(excluded.page, '$.status') < 300
|
|
109
|
+
AND json_extract(excluded.page, '$.wordCount') > 0,
|
|
110
|
+
0
|
|
111
|
+
)
|
|
112
|
+
END`,args:[t,a.archivedUrl??a.url,JSON.stringify(l),t]}}),{sql:`UPDATE site_extract_jobs SET
|
|
113
|
+
done_urls = (SELECT COUNT(*) FROM site_extract_pages WHERE job_id = ?),
|
|
114
|
+
attempted_urls = (SELECT COUNT(*) FROM site_extract_pages WHERE job_id = ?),
|
|
115
|
+
successful_urls = (
|
|
116
|
+
SELECT COUNT(*) FROM site_extract_pages
|
|
117
|
+
WHERE job_id = ? AND CASE
|
|
118
|
+
WHEN json_extract(page, '$.extractionStatus') IS NOT NULL
|
|
119
|
+
THEN json_extract(page, '$.extractionStatus') = 'successful'
|
|
120
|
+
ELSE json_extract(page, '$.status') >= 200
|
|
121
|
+
AND json_extract(page, '$.status') < 300
|
|
122
|
+
AND json_extract(page, '$.wordCount') > 0
|
|
123
|
+
END
|
|
124
|
+
),
|
|
125
|
+
failed_urls = (
|
|
126
|
+
SELECT COUNT(*) FROM site_extract_pages
|
|
127
|
+
WHERE job_id = ? AND NOT CASE
|
|
128
|
+
WHEN json_extract(page, '$.extractionStatus') IS NOT NULL
|
|
129
|
+
THEN json_extract(page, '$.extractionStatus') = 'successful'
|
|
130
|
+
ELSE COALESCE(
|
|
131
|
+
json_extract(page, '$.status') >= 200
|
|
132
|
+
AND json_extract(page, '$.status') < 300
|
|
133
|
+
AND json_extract(page, '$.wordCount') > 0,
|
|
134
|
+
0
|
|
135
|
+
)
|
|
136
|
+
END
|
|
137
|
+
),
|
|
138
|
+
updated_at = datetime('now')
|
|
139
|
+
WHERE id = ? AND status IN ('pending', 'running')`,args:[t,t,t,t,t]}],"write")}async function It(t){return(await s().execute({sql:"SELECT page FROM site_extract_pages WHERE job_id = ? ORDER BY rowid",args:[t]})).rows.map(r=>JSON.parse(String(r.page)))}async function Dt(t){return(await s().execute({sql:"SELECT page FROM site_extract_pages WHERE job_id = ?",args:[t]})).rows.map(r=>JSON.parse(String(r.page)))}async function jt(t,e=181){let n=s(),r=Math.max(1,Math.min(5001,Math.round(e)));return(await n.execute({sql:`SELECT DISTINCT CAST(images.value AS TEXT) AS url
|
|
140
|
+
FROM site_extract_pages p,
|
|
141
|
+
json_each(CASE WHEN json_valid(p.page) THEN p.page ELSE '{}' END, '$.imageLinks') images
|
|
142
|
+
WHERE p.job_id = ?
|
|
143
|
+
AND images.type = 'text'
|
|
144
|
+
AND length(CAST(images.value AS TEXT)) BETWEEN 1 AND 4096
|
|
145
|
+
LIMIT ?`,args:[t,r]})).rows.map(a=>String(a.url))}async function Pt(t){let n=await s().execute({sql:`SELECT COUNT(*) AS n FROM site_extract_pages
|
|
146
|
+
WHERE job_id = ? AND CASE
|
|
147
|
+
WHEN json_extract(page, '$.extractionStatus') IS NOT NULL
|
|
148
|
+
THEN json_extract(page, '$.extractionStatus') = 'successful'
|
|
149
|
+
ELSE COALESCE(
|
|
150
|
+
json_extract(page, '$.status') >= 200
|
|
151
|
+
AND json_extract(page, '$.status') < 300
|
|
152
|
+
AND json_extract(page, '$.wordCount') > 0,
|
|
153
|
+
0
|
|
154
|
+
)
|
|
155
|
+
END`,args:[t]});return Number(n.rows[0]?.n??0)}async function Ct(t){let n=await s().execute({sql:"SELECT url FROM site_extract_pages WHERE job_id = ?",args:[t]});return new Set(n.rows.map(r=>String(r.url)))}async function z(t,e,n,r=null,i=!1,a=null){return(await s().execute({sql:`UPDATE site_extract_jobs
|
|
156
|
+
SET status = ?, artifacts = ?, error = ?, public_error_json = ?, updated_at = datetime('now')
|
|
157
|
+
WHERE id = ? AND (status IN ('pending', 'running') OR (? = 1 AND status = 'failed'))`,args:[n,JSON.stringify(e??[]),r?u(r).slice(0,2e3):null,m(a),t,i?1:0]})).rowsAffected===1}async function Lt(t,e){await z(t,e,"complete")}async function Ut(t,e,n){await s().execute({sql:`UPDATE site_extract_jobs
|
|
158
|
+
SET status = 'failed', error = ?, public_error_json = ?, updated_at = datetime('now')
|
|
159
|
+
WHERE id = ? AND status IN ('pending', 'running')`,args:[u(e).slice(0,2e3),m(n),t]})}async function Ot(t,e,n){await s().execute({sql:`UPDATE site_extract_jobs
|
|
160
|
+
SET status = 'failed', billed_mc = 0, error = ?, public_error_json = ?, updated_at = datetime('now')
|
|
161
|
+
WHERE id = ? AND status = 'pending' AND billed_mc IS NULL`,args:[u(e).slice(0,2e3),m(n),t]})}async function kt(t,e){let r=(await x(t))?.options.billingClass===g&&e?{...e,charge_status:"not_charged"}:e;await s().execute({sql:`UPDATE site_extract_jobs SET public_error_json = ?, updated_at = datetime('now')
|
|
162
|
+
WHERE id = ? AND status IN ('complete', 'partial', 'failed')`,args:[m(r),t]})}async function Mt(t,e,n,r,i){let a=s(),o=await x(t);if(!o||o.billedMc!=null)return;if(o.options.billingClass===g){if(!await X(t))throw new Error("xray_extract_zero_charge_finalization_rejected");return}let c=typeof o.options.debitKey=="string"?o.options.debitKey:null;if(c){let f=await D(e,c,Math.max(0,r),w.EXTRACT_SITE_REFUND,i,"extract_site");await a.execute({sql:"UPDATE site_extract_jobs SET billed_mc = ?, updated_at = datetime('now') WHERE id = ? AND billed_mc IS NULL",args:[f.final_amount_mc,t]});return}let d=Math.max(0,n),l=Math.max(0,r);await a.batch([{sql:"UPDATE site_extract_jobs SET billed_mc = -1 WHERE id = ? AND billed_mc IS NULL",args:[t]},{sql:`INSERT INTO credit_lots (user_id, amount_mc, remaining_mc, source, expires_at)
|
|
163
|
+
SELECT ?, ?, ?, ?, datetime(j.created_at, ?)
|
|
164
|
+
FROM site_extract_jobs j
|
|
165
|
+
WHERE j.id = ?
|
|
166
|
+
AND j.billed_mc = -1
|
|
167
|
+
AND ? > 0
|
|
168
|
+
AND changes() = 1
|
|
169
|
+
AND datetime(j.created_at, ?) > datetime('now')`,args:[e,d,d,w.EXTRACT_SITE_REFUND,N,t,d,N]},{sql:`UPDATE users SET balance_mc = balance_mc + ?
|
|
170
|
+
WHERE id = ? AND ? > 0 AND changes() = 1`,args:[d,e,d]},{sql:`INSERT INTO ledger (user_id, amount_mc, operation, description)
|
|
171
|
+
SELECT ?, ?, ?, ? WHERE ? > 0 AND changes() = 1`,args:[e,d,w.EXTRACT_SITE_REFUND,i,d]},{sql:`UPDATE site_extract_jobs SET billed_mc = ?, updated_at = datetime('now')
|
|
172
|
+
WHERE id = ? AND billed_mc = -1`,args:[l,t]}],"write")}async function X(t){return(await s().execute({sql:`UPDATE site_extract_jobs
|
|
173
|
+
SET billed_mc = 0, updated_at = datetime('now')
|
|
174
|
+
WHERE id = ?
|
|
175
|
+
AND billed_mc IS NULL
|
|
176
|
+
AND json_extract(options, '$.billingClass') = ?
|
|
177
|
+
AND json_extract(options, '$.heldMc') = 0
|
|
178
|
+
AND json_extract(options, '$.debitKey') IS NULL
|
|
179
|
+
AND json_extract(options, '$.xraySetupReceipt.billingClass') = ?
|
|
180
|
+
AND json_extract(options, '$.xraySetupReceipt.consumesMcpScraperCredits') = 0
|
|
181
|
+
AND json_extract(options, '$.xraySetupReceipt.heldMc') = 0
|
|
182
|
+
AND json_extract(options, '$.xraySetupReceipt.billedMc') = 0`,args:[t,g,g]})).rowsAffected===1}export{y as a,V as b,G as c,Q as d,Z as e,tt as f,et as g,it as h,g as i,pt as j,Et as k,mt as l,K as m,gt as n,x as o,_t as p,xt as q,ft as r,bt as s,wt as t,St as u,At as v,Nt as w,Tt as x,yt as y,ht as z,Rt as A,It as B,Dt as C,jt as D,Pt as E,Ct as F,z as G,Lt as H,Ut as I,Ot as J,kt as K,Mt as L,X as M};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as m}from"./chunk-YQZGZBB4.js";import{b as n,ba as p,ca as d,ea as b,g as o,h as i,j as l,m as u,o as c}from"./chunk-GMWKPIYX.js";function f(e){return e instanceof Error?e.message:String(e)}function y(e,t){return e instanceof DOMException&&(e.name==="TimeoutError"||e.name==="AbortError")?!0:/timeout|timed out|Timeout \d+ms exceeded|deadline/i.test(t)}function g(e){return/captcha|recaptcha|unusual traffic|google\.com\/sorry|blocked/i.test(e)}function S(e){return/ERR_TUNNEL_CONNECTION_FAILED|ERR_PROXY_CONNECTION_FAILED|ERR_SOCKS_CONNECTION_FAILED|tunnel connection failed|proxy connection failed|transport error: proxy/i.test(e)}function E(e){return/proxy unavailable|proxy_unavailable|connection_test_failed|did not return a proxy id|configured fallback/i.test(e)}function h(e){return/(?:spending|billing|organization|account).{0,80}(?:cap|limit|blocked|disabled)|(?:quota|capacity).{0,40}(?:reached|exceeded)|https?:\/\/\S*dashboard/i.test(e)}function v(e){return/browser (?:has been )?closed|context (?:has been )?closed|target page, context or browser has been closed|session closed|page has been closed/i.test(e)}function x(e){let t=f(e);return e instanceof u?{error_code:"request_aborted",error_type:"request_aborted",message:o("request_aborted"),retryable:!0,httpStatus:408,terminalStatus:"cancelled"}:e instanceof l||g(t)?{error_code:"captcha_exhausted",error_type:"captcha",message:o("captcha_exhausted"),retryable:!0,httpStatus:503,terminalStatus:"failed"}:m(e,t)?{error_code:"vendor_unavailable",error_type:"service_unavailable",message:o("vendor_unavailable"),retryable:!0,httpStatus:503,terminalStatus:"failed"}:e instanceof c?{error_code:"location_mismatch",error_type:"location_mismatch",message:o("location_mismatch"),retryable:!0,httpStatus:503,terminalStatus:"failed"}:S(t)?{error_code:"proxy_tunnel_failed",error_type:"connection",message:o("proxy_tunnel_failed"),retryable:!0,httpStatus:503,terminalStatus:"failed"}:E(t)?{error_code:"proxy_unavailable",error_type:"connection",message:o("proxy_unavailable"),retryable:!0,httpStatus:503,terminalStatus:"failed"}:v(t)?{error_code:"browser_session_interrupted",error_type:"extraction",message:o("browser_session_interrupted"),retryable:!0,httpStatus:503,terminalStatus:"failed"}:y(e,t)?{error_code:"harvest_timeout",error_type:"timeout",message:o("harvest_timeout"),retryable:!0,httpStatus:504,terminalStatus:"failed"}:h(t)?{error_code:"service_unavailable",error_type:"service_unavailable",message:n,retryable:!0,httpStatus:503,terminalStatus:"failed"}:{error_code:"extraction_failed",error_type:"extraction",message:n,retryable:!1,httpStatus:500,terminalStatus:"failed"}}function w(e){return JSON.stringify({error_code:e.error_code,error_type:e.error_type,message:e.message,retryable:e.retryable})}function _(e,t={}){let r=i({errorCode:e.error_code,errorType:e.error_type,retryable:e.retryable,retryAfterSeconds:t.retryAfterSeconds,chargeStatus:t.chargeStatus,details:t.details});return{error:r.message,...r}}function A(e,t={}){let{error:r,...a}=_(e,t);return a}function C(e,t={}){let r=x(e);return{body:_(r,t),status:r.httpStatus}}function D(e,t){return async r=>{if(r.type==="started"){await p({jobId:e,userId:t,attemptNumber:r.attemptNumber,maxAttempts:r.maxAttempts,query:r.query,location:r.location,maxQuestions:r.maxQuestions,startedAt:r.startedAt});return}if(r.type==="provider_session_started"){await d({jobId:e,attemptNumber:r.attemptNumber,browserProvider:r.provider,providerSessionId:r.providerSessionId,observedAt:r.observedAt});return}await b({jobId:e,attemptNumber:r.attemptNumber,outcome:r.outcome,\u006b\u0065\u0072\u006e\u0065\u006cSessionId:r.\u006b\u0065\u0072\u006e\u0065\u006cSessionId,questionCount:r.questionCount,durationMs:r.durationMs,error:r.error,willRetry:r.willRetry,\u006b\u0065\u0072\u006e\u0065\u006cDeleteStarted:r.cleanup.\u006b\u0065\u0072\u006e\u0065\u006cDeleteStarted,\u006b\u0065\u0072\u006e\u0065\u006cDeleteSucceeded:r.cleanup.\u006b\u0065\u0072\u006e\u0065\u006cDeleteSucceeded,\u006b\u0065\u0072\u006e\u0065\u006cDeleteError:r.cleanup.\u006b\u0065\u0072\u006e\u0065\u006cDeleteError,browserCloseSucceeded:r.cleanup.browserCloseSucceeded,browserCloseError:r.cleanup.browserCloseError,browserProvider:r.cleanup.provider??null,providerSessionId:r.cleanup.providerSessionId??null,providerLookupStatus:r.cleanup.providerSessionId?"pending":"not_applicable",providerErrorCode:r.cleanup.providerDisconnectObserved?"browser_session_interrupted":null,providerErrorMessage:r.cleanup.providerDisconnectMessage??null,providerReasonSource:r.cleanup.providerDisconnectObserved?"client_runtime":null,providerDisconnectObserved:r.cleanup.providerDisconnectObserved??!1,providerDisconnectMessage:r.cleanup.providerDisconnectMessage??null,debug:r.debug,completedAt:r.completedAt})}}async function I(e,t,r=3){for(let a=1;a<=r;a++){try{let s=await fetch(e,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t),signal:AbortSignal.timeout(1e4)});if(s.ok)return;console.warn(`[webhook] attempt ${a} \u2192 ${s.status} from ${e}`)}catch(s){console.warn(`[webhook] attempt ${a} failed:`,s instanceof Error?s.message:s)}a<r&&await new Promise(s=>setTimeout(s,1e3*a*2))}console.error(`[webhook] gave up after ${r} attempts for ${e}`)}export{x as a,w as b,_ as c,A as d,C as e,D as f,I as g};
|