mcp-scraper 0.65.1 → 0.66.1
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 +31 -1
- package/README.md +11 -6
- package/dist/{analytics-repository-IKE53S3E.js → analytics-repository-RUXBFSFX.js} +1 -2
- package/dist/bin/api-server.cjs +1844 -71
- package/dist/bin/api-server.js +4 -5
- package/dist/bin/mcp-scraper-cli.cjs +1 -2
- package/dist/bin/mcp-scraper-cli.js +6 -7
- package/dist/bin/mcp-scraper-install.cjs +7 -3
- package/dist/bin/mcp-scraper-install.js +2 -3
- package/dist/bin/mcp-stdio-server.cjs +5150 -3467
- package/dist/bin/mcp-stdio-server.js +11 -11
- package/dist/bin/paa-harvest.cjs +8 -3
- package/dist/bin/paa-harvest.js +4 -5
- package/dist/{chunk-5PIO7QBG.js → chunk-3PIXKLGZ.js} +3 -4
- package/dist/{chunk-VIWT35S6.js → chunk-4OT7BAIE.js} +1 -2
- package/dist/{chunk-ORKXE7VX.js → chunk-4P3PHFPQ.js} +11 -6
- package/dist/{chunk-AMLFKPLL.js → chunk-75VW7CWE.js} +4 -7
- package/dist/{chunk-FG6LJGJ7.js → chunk-77LQV73B.js} +0 -1
- package/dist/{chunk-DKIG2TEE.js → chunk-AQKJOSIB.js} +2 -3
- package/dist/{chunk-2XUXVKT4.js → chunk-AYJHI2FK.js} +0 -1
- package/dist/{chunk-5BEPOFKG.js → chunk-CNLLTD5B.js} +0 -1
- package/dist/{chunk-EKJ65E3Z.js → chunk-DNM65UCK.js} +0 -1
- package/dist/{chunk-PDAKCYHZ.js → chunk-E4LMY5OJ.js} +6258 -3747
- package/dist/{chunk-MQGBCWZD.js → chunk-GGZEC22A.js} +64 -1
- package/dist/{chunk-4VG3NUUY.js → chunk-GTGSD4YS.js} +2 -2
- package/dist/{chunk-MQQS7ONH.js → chunk-KBTUPRTF.js} +8 -296
- package/dist/{chunk-XPEN6OG5.js → chunk-NS754CRS.js} +15 -3
- package/dist/{chunk-UN4LZVCB.js → chunk-OZJMVCDK.js} +1 -2
- package/dist/{chunk-342OUCSG.js → chunk-PGJQDMC2.js} +1 -2
- package/dist/{chunk-G3P3ZDB4.js → chunk-RJ7JVYKU.js} +0 -1
- package/dist/{chunk-N54JS5WP.js → chunk-TNJIPV74.js} +1 -2
- package/dist/{chunk-4LINSJNI.js → chunk-TNLGHNLX.js} +1 -2
- package/dist/chunk-VRJ7CO6L.js +6 -0
- package/dist/chunk-VXLU74YZ.js +293 -0
- package/dist/{chunk-DCWXVAQT.js → chunk-WID3MFRS.js} +6 -2
- package/dist/{db-BRI465XQ.js → db-PO7N7ZP3.js} +1 -2
- package/dist/{extract-bundle-UWKJT4MU.js → extract-bundle-SIBFUA6I.js} +8 -10
- package/dist/index.cjs +8 -3
- package/dist/index.js +4 -5
- package/dist/{lead-list-enrichment-repository-ODWGRVCQ.js → lead-list-enrichment-repository-OTAUQONJ.js} +2 -3
- package/dist/{location-data-repository-67MMEVZQ.js → location-data-repository-RM2KJ3H5.js} +3 -4
- package/dist/{server-RSHSZBUZ.js → server-ELEJNRRE.js} +875 -1629
- package/dist/{site-extract-repository-J3RH3MOS.js → site-extract-repository-ASYEJO7Z.js} +4 -5
- package/dist/{worker-YKRIK5LS.js → worker-MAVYYWM7.js} +8 -9
- package/package.json +3 -1
- package/dist/analytics-repository-IKE53S3E.js.map +0 -1
- package/dist/bin/api-server.cjs.map +0 -1
- package/dist/bin/api-server.js.map +0 -1
- package/dist/bin/mcp-scraper-cli.cjs.map +0 -1
- package/dist/bin/mcp-scraper-cli.js.map +0 -1
- package/dist/bin/mcp-scraper-install.cjs.map +0 -1
- package/dist/bin/mcp-scraper-install.js.map +0 -1
- package/dist/bin/mcp-stdio-server.cjs.map +0 -1
- package/dist/bin/mcp-stdio-server.js.map +0 -1
- package/dist/bin/paa-harvest.cjs.map +0 -1
- package/dist/bin/paa-harvest.js.map +0 -1
- package/dist/chunk-2XUXVKT4.js.map +0 -1
- package/dist/chunk-342OUCSG.js.map +0 -1
- package/dist/chunk-4LINSJNI.js.map +0 -1
- package/dist/chunk-4VG3NUUY.js.map +0 -1
- package/dist/chunk-5BEPOFKG.js.map +0 -1
- package/dist/chunk-5PIO7QBG.js.map +0 -1
- package/dist/chunk-AMLFKPLL.js.map +0 -1
- package/dist/chunk-CGKWNXJR.js +0 -7
- package/dist/chunk-CGKWNXJR.js.map +0 -1
- package/dist/chunk-DCWXVAQT.js.map +0 -1
- package/dist/chunk-DKIG2TEE.js.map +0 -1
- package/dist/chunk-EKJ65E3Z.js.map +0 -1
- package/dist/chunk-FG6LJGJ7.js.map +0 -1
- package/dist/chunk-G3P3ZDB4.js.map +0 -1
- package/dist/chunk-MQGBCWZD.js.map +0 -1
- package/dist/chunk-MQQS7ONH.js.map +0 -1
- package/dist/chunk-N54JS5WP.js.map +0 -1
- package/dist/chunk-ORKXE7VX.js.map +0 -1
- package/dist/chunk-PDAKCYHZ.js.map +0 -1
- package/dist/chunk-UN4LZVCB.js.map +0 -1
- package/dist/chunk-UVFB24RZ.js +0 -67
- package/dist/chunk-UVFB24RZ.js.map +0 -1
- package/dist/chunk-VIWT35S6.js.map +0 -1
- package/dist/chunk-XPEN6OG5.js.map +0 -1
- package/dist/db-BRI465XQ.js.map +0 -1
- package/dist/extract-bundle-UWKJT4MU.js.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lead-list-enrichment-repository-ODWGRVCQ.js.map +0 -1
- package/dist/location-data-repository-67MMEVZQ.js.map +0 -1
- package/dist/server-RSHSZBUZ.js.map +0 -1
- package/dist/site-extract-repository-J3RH3MOS.js.map +0 -1
- package/dist/worker-YKRIK5LS.js.map +0 -1
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isPrivateIpAddress,
|
|
3
|
+
loadHtmlDocument
|
|
4
|
+
} from "./chunk-GGZEC22A.js";
|
|
5
|
+
|
|
6
|
+
// src/lib/contact-evidence.ts
|
|
7
|
+
var MAX_EMAILS = 20;
|
|
8
|
+
var MAX_SOCIAL_PROFILES = 20;
|
|
9
|
+
var MAX_CONTACT_FORMS = 10;
|
|
10
|
+
var MAX_CANDIDATE_PAGE_URLS = 20;
|
|
11
|
+
var MAX_CONTEXT_CHARS = 280;
|
|
12
|
+
var EMAIL_PATTERN = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi;
|
|
13
|
+
var EMAIL_NOISE = [
|
|
14
|
+
".png",
|
|
15
|
+
".jpg",
|
|
16
|
+
".jpeg",
|
|
17
|
+
".gif",
|
|
18
|
+
".svg",
|
|
19
|
+
".webp",
|
|
20
|
+
".ico",
|
|
21
|
+
"@2x",
|
|
22
|
+
"sentry",
|
|
23
|
+
"wix.com",
|
|
24
|
+
"wixpress",
|
|
25
|
+
"example.",
|
|
26
|
+
"godaddy",
|
|
27
|
+
"squarespace",
|
|
28
|
+
"schema.org",
|
|
29
|
+
"u003e",
|
|
30
|
+
"core-js"
|
|
31
|
+
];
|
|
32
|
+
var DEVELOPER_CONTEXT = /\b(?:website|site|web)\s+(?:by|built|design(?:ed)?|develop(?:ed)?|powered)\b|\b(?:developer|webmaster)\b/i;
|
|
33
|
+
var CONTACT_PATH = /(?:^|\/)(?:about|bio|contact|contact-us|meet|our-team|people|profile|staff|team)(?:\/|$)/i;
|
|
34
|
+
var SOCIAL_HOSTS = {
|
|
35
|
+
"facebook.com": "facebook",
|
|
36
|
+
"fb.com": "facebook",
|
|
37
|
+
"instagram.com": "instagram",
|
|
38
|
+
"linkedin.com": "linkedin",
|
|
39
|
+
"twitter.com": "twitter",
|
|
40
|
+
"x.com": "x",
|
|
41
|
+
"youtube.com": "youtube",
|
|
42
|
+
"youtu.be": "youtube",
|
|
43
|
+
"tiktok.com": "tiktok",
|
|
44
|
+
"pinterest.com": "pinterest"
|
|
45
|
+
};
|
|
46
|
+
var SOCIAL_NOISE = /(?:\/shar(?:e|er)|\/intent\/|\/dialog\/|\/plugins\/|\/share\?|\/developers?\/|platform\.)/i;
|
|
47
|
+
function compact(value) {
|
|
48
|
+
return (value ?? "").replace(/\s+/g, " ").trim();
|
|
49
|
+
}
|
|
50
|
+
function safeHttpUrl(value, base) {
|
|
51
|
+
try {
|
|
52
|
+
const parsed = new URL(value, base);
|
|
53
|
+
if (!["http:", "https:"].includes(parsed.protocol) || parsed.username || parsed.password) return null;
|
|
54
|
+
return parsed;
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function normalizeEmail(value) {
|
|
60
|
+
let decoded = value;
|
|
61
|
+
try {
|
|
62
|
+
decoded = decodeURIComponent(value);
|
|
63
|
+
} catch {
|
|
64
|
+
}
|
|
65
|
+
const email = decoded.split("?")[0].trim().toLowerCase().replace(/[),.;:]+$/, "");
|
|
66
|
+
if (email.length > 254 || email.split("@").length !== 2) return null;
|
|
67
|
+
if (!/^[a-z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-z0-9.-]+\.[a-z]{2,}$/i.test(email)) return null;
|
|
68
|
+
if (EMAIL_NOISE.some((part) => email.includes(part))) return null;
|
|
69
|
+
return email;
|
|
70
|
+
}
|
|
71
|
+
function nearbyContext(node) {
|
|
72
|
+
const parent = node.closest("p,li,address,section,article,div").first();
|
|
73
|
+
return compact(parent.length ? parent.text() : node.text()).slice(0, MAX_CONTEXT_CHARS);
|
|
74
|
+
}
|
|
75
|
+
function addEmail(target, raw, sourceUrl, context) {
|
|
76
|
+
const value = normalizeEmail(raw);
|
|
77
|
+
if (!value || DEVELOPER_CONTEXT.test(context)) return;
|
|
78
|
+
if (!target.has(value)) {
|
|
79
|
+
target.set(value, {
|
|
80
|
+
value,
|
|
81
|
+
sourceUrl,
|
|
82
|
+
context: compact(context).slice(0, MAX_CONTEXT_CHARS),
|
|
83
|
+
sourceType: "first_party_page"
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function normalizedSocialUrl(parsed) {
|
|
88
|
+
parsed.hash = "";
|
|
89
|
+
for (const key of [...parsed.searchParams.keys()]) {
|
|
90
|
+
if (/^(?:utm_|fbclid|gclid)/i.test(key)) parsed.searchParams.delete(key);
|
|
91
|
+
}
|
|
92
|
+
return parsed.href;
|
|
93
|
+
}
|
|
94
|
+
function socialNetwork(hostname) {
|
|
95
|
+
const host = hostname.toLowerCase().replace(/^www\./, "");
|
|
96
|
+
const match = Object.entries(SOCIAL_HOSTS).find(([domain]) => host === domain || host.endsWith(`.${domain}`));
|
|
97
|
+
return match?.[1] ?? null;
|
|
98
|
+
}
|
|
99
|
+
function extractContactEvidence(html, pageUrl) {
|
|
100
|
+
const source = safeHttpUrl(pageUrl);
|
|
101
|
+
if (!source) throw new Error("pageUrl must be a public HTTP(S) URL");
|
|
102
|
+
const { $ } = loadHtmlDocument(html);
|
|
103
|
+
const emails = /* @__PURE__ */ new Map();
|
|
104
|
+
const socialProfiles = /* @__PURE__ */ new Map();
|
|
105
|
+
const contactForms = /* @__PURE__ */ new Map();
|
|
106
|
+
const candidatePageUrls = /* @__PURE__ */ new Map();
|
|
107
|
+
$('a[href^="mailto:" i]').each((_index, element) => {
|
|
108
|
+
const node = $(element);
|
|
109
|
+
addEmail(emails, (node.attr("href") ?? "").slice(7), source.href, nearbyContext(node));
|
|
110
|
+
});
|
|
111
|
+
$("body").find("*").addBack().contents().filter((_index, node) => node.type === "text").each((_index, node) => {
|
|
112
|
+
const parent = $(node.parent ?? node);
|
|
113
|
+
const text = String(node.data ?? "");
|
|
114
|
+
for (const match of text.match(EMAIL_PATTERN) ?? []) addEmail(emails, match, source.href, nearbyContext(parent));
|
|
115
|
+
});
|
|
116
|
+
$("a[href]").each((_index, element) => {
|
|
117
|
+
const node = $(element);
|
|
118
|
+
const parsed = safeHttpUrl(node.attr("href") ?? "", source.href);
|
|
119
|
+
if (!parsed) return;
|
|
120
|
+
const context = compact(`${node.text()} ${nearbyContext(node)}`).slice(0, MAX_CONTEXT_CHARS);
|
|
121
|
+
const network = socialNetwork(parsed.hostname);
|
|
122
|
+
if (network && !SOCIAL_NOISE.test(`${parsed.pathname}${parsed.search}`)) {
|
|
123
|
+
const url2 = normalizedSocialUrl(parsed);
|
|
124
|
+
if (!socialProfiles.has(url2)) socialProfiles.set(url2, { url: url2, network, sourceUrl: source.href, context });
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (parsed.origin !== source.origin || !CONTACT_PATH.test(parsed.pathname)) return;
|
|
128
|
+
parsed.hash = "";
|
|
129
|
+
const url = parsed.href;
|
|
130
|
+
if (!candidatePageUrls.has(url)) candidatePageUrls.set(url, { url, sourceUrl: source.href, context });
|
|
131
|
+
});
|
|
132
|
+
$("form").each((_index, element) => {
|
|
133
|
+
const node = $(element);
|
|
134
|
+
const action = safeHttpUrl(node.attr("action") || source.href, source.href);
|
|
135
|
+
if (!action || action.origin !== source.origin) return;
|
|
136
|
+
action.hash = "";
|
|
137
|
+
const url = action.href;
|
|
138
|
+
if (!contactForms.has(url)) contactForms.set(url, { url, sourceUrl: source.href, context: nearbyContext(node) });
|
|
139
|
+
});
|
|
140
|
+
return {
|
|
141
|
+
emails: [...emails.values()].slice(0, MAX_EMAILS),
|
|
142
|
+
socialProfiles: [...socialProfiles.values()].slice(0, MAX_SOCIAL_PROFILES),
|
|
143
|
+
contactForms: [...contactForms.values()].slice(0, MAX_CONTACT_FORMS),
|
|
144
|
+
candidatePageUrls: [...candidatePageUrls.values()].slice(0, MAX_CANDIDATE_PAGE_URLS)
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// src/leads/contact-enrichment.ts
|
|
149
|
+
var GENERIC_EMAIL_PREFIXES = /* @__PURE__ */ new Set([
|
|
150
|
+
"admin",
|
|
151
|
+
"contact",
|
|
152
|
+
"customerservice",
|
|
153
|
+
"hello",
|
|
154
|
+
"help",
|
|
155
|
+
"info",
|
|
156
|
+
"inquiries",
|
|
157
|
+
"mail",
|
|
158
|
+
"office",
|
|
159
|
+
"reception",
|
|
160
|
+
"sales",
|
|
161
|
+
"service",
|
|
162
|
+
"support",
|
|
163
|
+
"team"
|
|
164
|
+
]);
|
|
165
|
+
var CONTACT_PATH2 = /(?:^|\/)(?:about|bio|contact|contact-us|meet|our-team|people|profile|staff|team)(?:\/|$)/i;
|
|
166
|
+
var TEAM_CONTEXT = /\b(?:our team|meet the team|staff|agents?|brokers?|realtors?|people)\b/i;
|
|
167
|
+
var OFFICE_CONTEXT = /\b(?:brokerage|company|corporate|front desk|main office|office|organization|reception)\b/i;
|
|
168
|
+
function normalizeWords(value) {
|
|
169
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim().split(/\s+/).filter(Boolean);
|
|
170
|
+
}
|
|
171
|
+
function safeHttpUrl2(value, base) {
|
|
172
|
+
try {
|
|
173
|
+
const parsed = new URL(value, base);
|
|
174
|
+
if (!["http:", "https:"].includes(parsed.protocol) || parsed.username || parsed.password) return null;
|
|
175
|
+
return parsed;
|
|
176
|
+
} catch {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function classifyEmailScope(evidence, entity) {
|
|
181
|
+
const local = evidence.value.split("@")[0].toLowerCase().replace(/[^a-z0-9]+/g, " ");
|
|
182
|
+
const nameWords = normalizeWords(entity.name).filter((word) => word.length > 1);
|
|
183
|
+
const pagePath = safeHttpUrl2(evidence.sourceUrl)?.pathname.toLowerCase() ?? "";
|
|
184
|
+
const combinedContext = `${evidence.context} ${pagePath}`.toLowerCase();
|
|
185
|
+
const generic = GENERIC_EMAIL_PREFIXES.has(local.replace(/\s+/g, ""));
|
|
186
|
+
const nameHits = nameWords.filter((word) => new RegExp(`(?:^|[^a-z0-9])${word}(?:$|[^a-z0-9])`, "i").test(`${local} ${combinedContext}`)).length;
|
|
187
|
+
const hasExactPersonContext = entity.entityType === "person" && nameWords.length >= 2 && nameHits >= Math.min(2, nameWords.length) && (local.includes(nameWords[0]) || pagePath.includes(nameWords.join("-")) || pagePath.includes(nameWords.join("/")));
|
|
188
|
+
if (!generic && hasExactPersonContext) return { scope: "person", reasons: ["named_person_page_context"] };
|
|
189
|
+
if (generic || OFFICE_CONTEXT.test(combinedContext)) return { scope: "office", reasons: generic ? ["generic_mailbox_prefix"] : ["organization_page_context"] };
|
|
190
|
+
if (TEAM_CONTEXT.test(combinedContext)) return { scope: "team", reasons: ["team_page_context"] };
|
|
191
|
+
if (entity.entityType === "business") {
|
|
192
|
+
const organizationWords = normalizeWords(entity.organization || entity.name).filter((word) => word.length > 2);
|
|
193
|
+
if (organizationWords.some((word) => combinedContext.includes(word))) return { scope: "team", reasons: ["business_context"] };
|
|
194
|
+
}
|
|
195
|
+
return { scope: "unknown", reasons: ["public_occurrence_without_identity_evidence"] };
|
|
196
|
+
}
|
|
197
|
+
function selectPrimaryEmail(evidence, entity) {
|
|
198
|
+
const scoped = evidence.map((item) => ({ ...item, ...classifyEmailScope(item, entity) }));
|
|
199
|
+
const allowed = entity.entityType === "person" ? ["person", "team"] : ["person", "team", "office"];
|
|
200
|
+
const sourcePriority = ["provided", "first_party_page", "structured_data", "serp_snippet"];
|
|
201
|
+
const priority = ["person", "team", "office"];
|
|
202
|
+
return scoped.filter((item) => allowed.includes(item.scope)).sort((a, b) => {
|
|
203
|
+
const sourceDelta = sourcePriority.indexOf(a.sourceType) - sourcePriority.indexOf(b.sourceType);
|
|
204
|
+
return sourceDelta || priority.indexOf(a.scope) - priority.indexOf(b.scope);
|
|
205
|
+
})[0] ?? null;
|
|
206
|
+
}
|
|
207
|
+
function contactAttemptUrls(seedUrl, discoveredLinks, maxPages) {
|
|
208
|
+
if (!Number.isInteger(maxPages) || maxPages < 1 || maxPages > 4) throw new Error("maxPages must be an integer from 1 to 4");
|
|
209
|
+
const seed = safeHttpUrl2(seedUrl);
|
|
210
|
+
if (!seed) return [];
|
|
211
|
+
seed.hash = "";
|
|
212
|
+
const urls = /* @__PURE__ */ new Set([seed.href]);
|
|
213
|
+
const discovered = discoveredLinks.map((item) => typeof item === "string" ? item : item.url).map((item) => safeHttpUrl2(item, seed.href)).filter((item) => Boolean(item && item.origin === seed.origin && CONTACT_PATH2.test(item.pathname)));
|
|
214
|
+
for (const item of discovered) {
|
|
215
|
+
item.hash = "";
|
|
216
|
+
urls.add(item.href);
|
|
217
|
+
}
|
|
218
|
+
for (const pathname of ["/contact", "/contact-us"]) urls.add(new URL(pathname, seed.origin).href);
|
|
219
|
+
return [...urls].slice(0, maxPages);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// src/leads/serp-email-evidence.ts
|
|
223
|
+
var EMAIL_CANDIDATE = /[A-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi;
|
|
224
|
+
var DEVELOPER_CONTEXT2 = /\b(?:website|site|web)\s+(?:by|built|design(?:ed)?|develop(?:ed)?|powered)\b|\b(?:developer|webmaster)\b/i;
|
|
225
|
+
var MAX_CONTEXT_CHARS2 = 280;
|
|
226
|
+
var MAX_EMAILS2 = 10;
|
|
227
|
+
function normalizeHostname(hostname) {
|
|
228
|
+
return hostname.toLowerCase().replace(/^www\./, "").replace(/\.$/, "");
|
|
229
|
+
}
|
|
230
|
+
function isLocalHostname(hostname) {
|
|
231
|
+
const host = normalizeHostname(hostname);
|
|
232
|
+
return host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local") || !host.includes(".") && !host.includes(":");
|
|
233
|
+
}
|
|
234
|
+
function parsePublicLiteralUrl(websiteUrl) {
|
|
235
|
+
try {
|
|
236
|
+
const parsed = new URL(websiteUrl.trim());
|
|
237
|
+
if (!["http:", "https:"].includes(parsed.protocol) || parsed.username || parsed.password) return null;
|
|
238
|
+
if (isLocalHostname(parsed.hostname) || isPrivateIpAddress(parsed.hostname)) return null;
|
|
239
|
+
return parsed;
|
|
240
|
+
} catch {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
function compactContext(title, snippet) {
|
|
245
|
+
return `${title} ${snippet ?? ""}`.replace(/\s+/g, " ").trim().slice(0, MAX_CONTEXT_CHARS2);
|
|
246
|
+
}
|
|
247
|
+
function buildSerpEmailQuery(websiteUrl) {
|
|
248
|
+
const parsed = parsePublicLiteralUrl(websiteUrl);
|
|
249
|
+
if (!parsed) return null;
|
|
250
|
+
parsed.hostname = normalizeHostname(parsed.hostname);
|
|
251
|
+
return `email for ${parsed.protocol}//${parsed.host}/`;
|
|
252
|
+
}
|
|
253
|
+
function extractSerpEmailEvidence({
|
|
254
|
+
websiteUrl,
|
|
255
|
+
results
|
|
256
|
+
}) {
|
|
257
|
+
const parsedWebsite = parsePublicLiteralUrl(websiteUrl);
|
|
258
|
+
if (!parsedWebsite) return [];
|
|
259
|
+
const websiteDomain = normalizeHostname(parsedWebsite.hostname);
|
|
260
|
+
const accepted = /* @__PURE__ */ new Map();
|
|
261
|
+
const orderedResults = results.map((result, index) => ({ result, index })).sort((left, right) => {
|
|
262
|
+
const leftPosition = Number.isFinite(left.result.position) ? left.result.position : Number.MAX_SAFE_INTEGER;
|
|
263
|
+
const rightPosition = Number.isFinite(right.result.position) ? right.result.position : Number.MAX_SAFE_INTEGER;
|
|
264
|
+
return leftPosition - rightPosition || left.index - right.index;
|
|
265
|
+
});
|
|
266
|
+
for (const { result } of orderedResults) {
|
|
267
|
+
const searchableText = `${result.title} ${result.snippet ?? ""}`;
|
|
268
|
+
const context = compactContext(result.title, result.snippet);
|
|
269
|
+
for (const rawCandidate of searchableText.match(EMAIL_CANDIDATE) ?? []) {
|
|
270
|
+
const value = normalizeEmail(rawCandidate);
|
|
271
|
+
if (!value || DEVELOPER_CONTEXT2.test(searchableText) || normalizeHostname(value.slice(value.lastIndexOf("@") + 1)) !== websiteDomain) continue;
|
|
272
|
+
if (accepted.has(value)) continue;
|
|
273
|
+
accepted.set(value, {
|
|
274
|
+
value,
|
|
275
|
+
sourceUrl: result.url,
|
|
276
|
+
sourceTitle: result.title,
|
|
277
|
+
sourcePosition: result.position,
|
|
278
|
+
context,
|
|
279
|
+
sourceType: "serp_snippet"
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return [...accepted.values()].slice(0, MAX_EMAILS2);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export {
|
|
287
|
+
extractContactEvidence,
|
|
288
|
+
classifyEmailScope,
|
|
289
|
+
selectPrimaryEmail,
|
|
290
|
+
contactAttemptUrls,
|
|
291
|
+
buildSerpEmailQuery,
|
|
292
|
+
extractSerpEmailEvidence
|
|
293
|
+
};
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
// release-message.json
|
|
2
|
+
var release_message_default = {
|
|
3
|
+
message: "Long PAA research now continues as a durable MCP Task with preserved progress, while transient browser interruptions return specific retry guidance instead of a generic outage."
|
|
4
|
+
};
|
|
5
|
+
|
|
1
6
|
// src/install-terminal.ts
|
|
2
7
|
var CODES = {
|
|
3
8
|
reset: "\x1B[0m",
|
|
@@ -48,7 +53,7 @@ function renderInstallTerminal(options) {
|
|
|
48
53
|
colorize(ascii, "amber", color),
|
|
49
54
|
`${colorize("MCP Scraper Agent", "cyan", color)} . v${options.version} . mcpscraper.dev`,
|
|
50
55
|
"1/1 install surfaces ready",
|
|
51
|
-
colorize(
|
|
56
|
+
colorize(`Newest in v${options.version}: ${release_message_default.message}`, "lime", color),
|
|
52
57
|
"",
|
|
53
58
|
`${colorize("Tools", "cyan", color)} ${colorize("(259 MCP tools)", "muted", color)}`,
|
|
54
59
|
toolRow("search", ["harvest_paa", "search_serp", "maps_search", "maps_place_intel"], color),
|
|
@@ -106,4 +111,3 @@ function renderInstallTerminal(options) {
|
|
|
106
111
|
export {
|
|
107
112
|
renderInstallTerminal
|
|
108
113
|
};
|
|
109
|
-
//# sourceMappingURL=chunk-DCWXVAQT.js.map
|
|
@@ -126,7 +126,7 @@ import {
|
|
|
126
126
|
updateKpoJobState,
|
|
127
127
|
upsertPageSnapshot,
|
|
128
128
|
verifyPassword
|
|
129
|
-
} from "./chunk-
|
|
129
|
+
} from "./chunk-GTGSD4YS.js";
|
|
130
130
|
export {
|
|
131
131
|
CORE_SCHEMA_VERSION,
|
|
132
132
|
CREDIT_LOTS_EPOCH,
|
|
@@ -256,4 +256,3 @@ export {
|
|
|
256
256
|
upsertPageSnapshot,
|
|
257
257
|
verifyPassword
|
|
258
258
|
};
|
|
259
|
-
//# sourceMappingURL=db-BRI465XQ.js.map
|
|
@@ -4,27 +4,26 @@ import {
|
|
|
4
4
|
downloadAsset,
|
|
5
5
|
embedCommonsTexts,
|
|
6
6
|
publicizeExtractionFailure
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-75VW7CWE.js";
|
|
8
|
+
import "./chunk-OZJMVCDK.js";
|
|
9
9
|
import {
|
|
10
10
|
computeIssues,
|
|
11
11
|
renderImageSection,
|
|
12
12
|
renderIssueReport,
|
|
13
13
|
renderLinkReport
|
|
14
|
-
} from "./chunk-
|
|
15
|
-
import "./chunk-
|
|
16
|
-
import "./chunk-UVFB24RZ.js";
|
|
14
|
+
} from "./chunk-PGJQDMC2.js";
|
|
15
|
+
import "./chunk-GGZEC22A.js";
|
|
17
16
|
import {
|
|
18
17
|
createSiteExtractBundleArtifactStream,
|
|
19
18
|
createSiteExtractContentReader,
|
|
20
19
|
createSiteExtractImageArtifact,
|
|
21
20
|
extractJobLimitInfo
|
|
22
|
-
} from "./chunk-
|
|
23
|
-
import "./chunk-
|
|
24
|
-
import "./chunk-
|
|
21
|
+
} from "./chunk-3PIXKLGZ.js";
|
|
22
|
+
import "./chunk-DNM65UCK.js";
|
|
23
|
+
import "./chunk-AYJHI2FK.js";
|
|
25
24
|
import {
|
|
26
25
|
getDb
|
|
27
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-GTGSD4YS.js";
|
|
28
27
|
|
|
29
28
|
// src/api/extract-bundle.ts
|
|
30
29
|
import { createWriteStream, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "fs";
|
|
@@ -1002,4 +1001,3 @@ export {
|
|
|
1002
1001
|
SITE_EXTRACT_PAGE_CHUNK,
|
|
1003
1002
|
assembleExtractArtifacts
|
|
1004
1003
|
};
|
|
1005
|
-
//# sourceMappingURL=extract-bundle-UWKJT4MU.js.map
|
package/dist/index.cjs
CHANGED
|
@@ -4073,6 +4073,9 @@ function classifyAttemptError(err) {
|
|
|
4073
4073
|
if (err instanceof RequestAbortedError) return "request_aborted";
|
|
4074
4074
|
if (err instanceof DOMException && (err.name === "TimeoutError" || err.name === "AbortError")) return "timeout";
|
|
4075
4075
|
const message = err instanceof Error ? err.message : String(err);
|
|
4076
|
+
if (/browser (?:has been )?closed|context (?:has been )?closed|target page, context or browser has been closed|session closed|page has been closed/i.test(message)) {
|
|
4077
|
+
return "browser_session_interrupted";
|
|
4078
|
+
}
|
|
4076
4079
|
if (looksLikeProxyTunnelFailure(message)) return "proxy_tunnel_failed";
|
|
4077
4080
|
if (looksLikeProxyUnavailable(message)) return "proxy_unavailable";
|
|
4078
4081
|
return /timeout|timed out|Timeout \d+ms exceeded|deadline/i.test(message) ? "timeout" : "error";
|
|
@@ -4120,6 +4123,9 @@ function looksLikeProxyUnavailable(message) {
|
|
|
4120
4123
|
function retryableLocationProxyError(outcome) {
|
|
4121
4124
|
return outcome === "captcha" || outcome === "proxy_tunnel_failed" || outcome === "proxy_unavailable";
|
|
4122
4125
|
}
|
|
4126
|
+
function retryableFreshBrowserError(outcome) {
|
|
4127
|
+
return outcome === "browser_session_interrupted";
|
|
4128
|
+
}
|
|
4123
4129
|
function locationMismatchMessage(result) {
|
|
4124
4130
|
const evidence = result.diagnostics.debug?.locationEvidence;
|
|
4125
4131
|
const expected = evidence?.expected?.canonicalLocation ?? result.location ?? "requested location";
|
|
@@ -4303,7 +4309,7 @@ async function harvest(rawOptions) {
|
|
|
4303
4309
|
proxyFailures++;
|
|
4304
4310
|
if (proxyFailures >= 2) effectiveProxyMode = "none";
|
|
4305
4311
|
}
|
|
4306
|
-
const willRetry2 = i < maxAttempts - 1 && (outcome2 === "captcha" || proxyMode === "location" && retryableLocationProxyError(outcome2));
|
|
4312
|
+
const willRetry2 = i < maxAttempts - 1 && (outcome2 === "captcha" || retryableFreshBrowserError(outcome2) || proxyMode === "location" && retryableLocationProxyError(outcome2));
|
|
4307
4313
|
if (outcome2 === "captcha") {
|
|
4308
4314
|
console.warn(JSON.stringify({
|
|
4309
4315
|
event: "harvest_attempt_captcha",
|
|
@@ -4421,7 +4427,7 @@ async function harvest(rawOptions) {
|
|
|
4421
4427
|
proxyFailures++;
|
|
4422
4428
|
if (proxyFailures >= 2) effectiveProxyMode = "none";
|
|
4423
4429
|
}
|
|
4424
|
-
const willRetry = i < maxAttempts - 1 && (outcome === "captcha" || proxyMode === "location" && retryableLocationProxyError(outcome));
|
|
4430
|
+
const willRetry = i < maxAttempts - 1 && (outcome === "captcha" || retryableFreshBrowserError(outcome) || proxyMode === "location" && retryableLocationProxyError(outcome));
|
|
4425
4431
|
if (outcome === "captcha") {
|
|
4426
4432
|
console.warn(JSON.stringify({
|
|
4427
4433
|
event: "harvest_attempt_captcha",
|
|
@@ -4803,4 +4809,3 @@ var VideoGenerator = class {
|
|
|
4803
4809
|
buildClipPrompts,
|
|
4804
4810
|
harvest
|
|
4805
4811
|
});
|
|
4806
|
-
//# sourceMappingURL=index.cjs.map
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
harvest
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-4P3PHFPQ.js";
|
|
4
4
|
import {
|
|
5
5
|
recordVendorUsage
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-TNLGHNLX.js";
|
|
7
|
+
import "./chunk-77LQV73B.js";
|
|
8
|
+
import "./chunk-GTGSD4YS.js";
|
|
9
9
|
|
|
10
10
|
// src/video/VideoGenerator.ts
|
|
11
11
|
import { execSync as execSync2 } from "child_process";
|
|
@@ -333,4 +333,3 @@ export {
|
|
|
333
333
|
buildClipPrompts,
|
|
334
334
|
harvest
|
|
335
335
|
};
|
|
336
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -30,8 +30,8 @@ import {
|
|
|
30
30
|
saveLeadListEnrichmentRowResult,
|
|
31
31
|
setInitializingLeadListEnrichmentConcurrencyLock,
|
|
32
32
|
stableCanonicalJson
|
|
33
|
-
} from "./chunk-
|
|
34
|
-
import "./chunk-
|
|
33
|
+
} from "./chunk-TNJIPV74.js";
|
|
34
|
+
import "./chunk-GTGSD4YS.js";
|
|
35
35
|
export {
|
|
36
36
|
LEAD_LIST_MAX_DISPATCH_ATTEMPTS,
|
|
37
37
|
LEAD_LIST_RUNNING_STALE_MINUTES,
|
|
@@ -65,4 +65,3 @@ export {
|
|
|
65
65
|
setInitializingLeadListEnrichmentConcurrencyLock,
|
|
66
66
|
stableCanonicalJson
|
|
67
67
|
};
|
|
68
|
-
//# sourceMappingURL=lead-list-enrichment-repository-ODWGRVCQ.js.map
|
|
@@ -13,9 +13,9 @@ import {
|
|
|
13
13
|
parseHostedCensusPlacesCsv,
|
|
14
14
|
parseHostedZipGroupsCsv,
|
|
15
15
|
queryHostedLocationMarkets
|
|
16
|
-
} from "./chunk-
|
|
17
|
-
import "./chunk-
|
|
18
|
-
import "./chunk-
|
|
16
|
+
} from "./chunk-AQKJOSIB.js";
|
|
17
|
+
import "./chunk-RJ7JVYKU.js";
|
|
18
|
+
import "./chunk-GTGSD4YS.js";
|
|
19
19
|
export {
|
|
20
20
|
HOSTED_CENSUS_DATASET_PREFIX,
|
|
21
21
|
HOSTED_ZIP_DATASET_KIND,
|
|
@@ -32,4 +32,3 @@ export {
|
|
|
32
32
|
parseHostedZipGroupsCsv,
|
|
33
33
|
queryHostedLocationMarkets
|
|
34
34
|
};
|
|
35
|
-
//# sourceMappingURL=location-data-repository-67MMEVZQ.js.map
|