smart-web-mcp 0.44.0 → 1.0.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 +352 -1
- package/README.md +33 -566
- package/dist/assessment.d.ts +10 -3
- package/dist/assessment.js +21 -6
- package/dist/assessment.js.map +1 -1
- package/dist/body-export.d.ts +10 -0
- package/dist/body-export.js +28 -0
- package/dist/body-export.js.map +1 -0
- package/dist/browser-session.d.ts +32 -1
- package/dist/browser-session.js +373 -3
- package/dist/browser-session.js.map +1 -1
- package/dist/cli-fetch-options.d.ts +7 -0
- package/dist/cli-fetch-options.js +41 -0
- package/dist/cli-fetch-options.js.map +1 -0
- package/dist/cli.js +31 -12
- package/dist/cli.js.map +1 -1
- package/dist/comment-export.d.ts +10 -0
- package/dist/comment-export.js +39 -0
- package/dist/comment-export.js.map +1 -0
- package/dist/composition.d.ts +1 -22
- package/dist/composition.js +0 -16
- package/dist/composition.js.map +1 -1
- package/dist/extraction/budget.d.ts +3 -0
- package/dist/extraction/budget.js +88 -7
- package/dist/extraction/budget.js.map +1 -1
- package/dist/extraction/document.js +134 -9
- package/dist/extraction/document.js.map +1 -1
- package/dist/korea/coupang-filters.d.ts +5 -0
- package/dist/korea/coupang-filters.js +87 -0
- package/dist/korea/coupang-filters.js.map +1 -0
- package/dist/korea/coupang.d.ts +27 -6
- package/dist/korea/coupang.js +202 -82
- package/dist/korea/coupang.js.map +1 -1
- package/dist/korea/proxy-client.d.ts +1 -0
- package/dist/korea/proxy-client.js +6 -2
- package/dist/korea/proxy-client.js.map +1 -1
- package/dist/korea/smartvertical.d.ts +1 -0
- package/dist/korea/smartvertical.js +39 -25
- package/dist/korea/smartvertical.js.map +1 -1
- package/dist/lib.d.ts +4 -2
- package/dist/lib.js +2 -1
- package/dist/lib.js.map +1 -1
- package/dist/mcp-server.js +39 -15
- package/dist/mcp-server.js.map +1 -1
- package/dist/operator-browser-fixture.d.ts +7 -0
- package/dist/operator-browser-fixture.js +19 -0
- package/dist/operator-browser-fixture.js.map +1 -0
- package/dist/operator-canary-cli.d.ts +6 -0
- package/dist/operator-canary-cli.js +190 -0
- package/dist/operator-canary-cli.js.map +1 -0
- package/dist/operator-canary.d.ts +74 -0
- package/dist/operator-canary.js +135 -0
- package/dist/operator-canary.js.map +1 -0
- package/dist/operator-cdp.d.ts +15 -0
- package/dist/operator-cdp.js +195 -0
- package/dist/operator-cdp.js.map +1 -0
- package/dist/operator-frame.d.ts +3 -0
- package/dist/operator-frame.js +55 -0
- package/dist/operator-frame.js.map +1 -0
- package/dist/settings.d.ts +3 -0
- package/dist/settings.js +1 -0
- package/dist/settings.js.map +1 -1
- package/dist/shared.d.ts +47 -10
- package/dist/shared.js +15 -9
- package/dist/shared.js.map +1 -1
- package/dist/smartbrowser.d.ts +169 -0
- package/dist/smartbrowser.js +288 -0
- package/dist/smartbrowser.js.map +1 -0
- package/dist/smartcrawl.js +16 -21
- package/dist/smartcrawl.js.map +1 -1
- package/dist/smartfetch/academic-fallback.js +3 -5
- package/dist/smartfetch/academic-fallback.js.map +1 -1
- package/dist/smartfetch/archive-fallback.js +2 -5
- package/dist/smartfetch/archive-fallback.js.map +1 -1
- package/dist/smartfetch/assets.d.ts +1 -1
- package/dist/smartfetch/assets.js +28 -1
- package/dist/smartfetch/assets.js.map +1 -1
- package/dist/smartfetch/jina-reader.d.ts +1 -0
- package/dist/smartfetch/jina-reader.js +17 -3
- package/dist/smartfetch/jina-reader.js.map +1 -1
- package/dist/smartfetch/pipeline.js +15 -25
- package/dist/smartfetch/pipeline.js.map +1 -1
- package/dist/smartfetch/provider-policy.d.ts +3 -0
- package/dist/smartfetch/provider-policy.js +45 -2
- package/dist/smartfetch/provider-policy.js.map +1 -1
- package/dist/smartfetch/provider-types.d.ts +8 -0
- package/dist/smartfetch/providers/article.js +76 -10
- package/dist/smartfetch/providers/article.js.map +1 -1
- package/dist/smartfetch/providers/blind-post.d.ts +2 -0
- package/dist/smartfetch/providers/blind-post.js +167 -0
- package/dist/smartfetch/providers/blind-post.js.map +1 -0
- package/dist/smartfetch/providers/clien.d.ts +2 -0
- package/dist/smartfetch/providers/clien.js +157 -0
- package/dist/smartfetch/providers/clien.js.map +1 -0
- package/dist/smartfetch/providers/commerce.d.ts +15 -0
- package/dist/smartfetch/providers/commerce.js +123 -7
- package/dist/smartfetch/providers/commerce.js.map +1 -1
- package/dist/smartfetch/providers/dcinside.js +313 -20
- package/dist/smartfetch/providers/dcinside.js.map +1 -1
- package/dist/smartfetch/providers/hackernews-html.d.ts +21 -0
- package/dist/smartfetch/providers/hackernews-html.js +148 -0
- package/dist/smartfetch/providers/hackernews-html.js.map +1 -0
- package/dist/smartfetch/providers/hackernews.js +76 -14
- package/dist/smartfetch/providers/hackernews.js.map +1 -1
- package/dist/smartfetch/providers/index.js +7 -1
- package/dist/smartfetch/providers/index.js.map +1 -1
- package/dist/smartfetch/providers/linkedin.js +2 -1
- package/dist/smartfetch/providers/linkedin.js.map +1 -1
- package/dist/smartfetch/providers/naver-blog.d.ts +2 -0
- package/dist/smartfetch/providers/naver-blog.js +87 -45
- package/dist/smartfetch/providers/naver-blog.js.map +1 -1
- package/dist/smartfetch/providers/naver-cafe-native.d.ts +7 -0
- package/dist/smartfetch/providers/naver-cafe-native.js +188 -0
- package/dist/smartfetch/providers/naver-cafe-native.js.map +1 -0
- package/dist/smartfetch/providers/naver-cafe.js +165 -4
- package/dist/smartfetch/providers/naver-cafe.js.map +1 -1
- package/dist/smartfetch/providers/naver-map-search.js +1 -1
- package/dist/smartfetch/providers/naver-map-search.js.map +1 -1
- package/dist/smartfetch/providers/ppomppu.d.ts +2 -0
- package/dist/smartfetch/providers/ppomppu.js +289 -0
- package/dist/smartfetch/providers/ppomppu.js.map +1 -0
- package/dist/smartfetch/providers/reddit.js +24 -3
- package/dist/smartfetch/providers/reddit.js.map +1 -1
- package/dist/smartfetch/providers/threads.js +1 -1
- package/dist/smartfetch/providers/threads.js.map +1 -1
- package/dist/smartfetch/providers/wikipedia.js +138 -0
- package/dist/smartfetch/providers/wikipedia.js.map +1 -1
- package/dist/smartfetch.d.ts +11 -2
- package/dist/smartfetch.js +369 -78
- package/dist/smartfetch.js.map +1 -1
- package/dist/smartsearch-brave.d.ts +2 -0
- package/dist/smartsearch-brave.js +43 -0
- package/dist/smartsearch-brave.js.map +1 -0
- package/dist/smartsearch-browser.d.ts +5 -0
- package/dist/smartsearch-browser.js +116 -0
- package/dist/smartsearch-browser.js.map +1 -0
- package/dist/smartsearch-ddg.d.ts +2 -0
- package/dist/smartsearch-ddg.js +46 -0
- package/dist/smartsearch-ddg.js.map +1 -0
- package/dist/smartsearch-node.d.ts +6 -0
- package/dist/smartsearch-node.js +21 -0
- package/dist/smartsearch-node.js.map +1 -0
- package/dist/smartsearch-postgresql.d.ts +7 -0
- package/dist/smartsearch-postgresql.js +44 -0
- package/dist/smartsearch-postgresql.js.map +1 -0
- package/dist/smartsearch-python.d.ts +8 -0
- package/dist/smartsearch-python.js +87 -0
- package/dist/smartsearch-python.js.map +1 -0
- package/dist/smartsearch-quality.d.ts +6 -0
- package/dist/smartsearch-quality.js +47 -0
- package/dist/smartsearch-quality.js.map +1 -0
- package/dist/smartsearch-youtube.d.ts +7 -0
- package/dist/smartsearch-youtube.js +76 -0
- package/dist/smartsearch-youtube.js.map +1 -0
- package/dist/smartsearch.d.ts +3 -0
- package/dist/smartsearch.js +617 -132
- package/dist/smartsearch.js.map +1 -1
- package/package.json +8 -5
- package/python/requirements-undetected-chromedriver.txt +2 -2
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { parseHTML } from "linkedom";
|
|
2
|
+
import { Buffer } from "node:buffer";
|
|
3
|
+
import { dedupeUrls, extractAnchorHrefs, extractUrls, stripTags } from "../../shared.js";
|
|
4
|
+
import { BrowserSessionToolError, looksBotBlockedContent } from "../../browser-session.js";
|
|
5
|
+
function identity(value) {
|
|
6
|
+
try {
|
|
7
|
+
const url = new URL(value);
|
|
8
|
+
return url.protocol === "https:" && !url.username && !url.password
|
|
9
|
+
&& ["www.teamblind.com", "teamblind.com"].includes(url.hostname)
|
|
10
|
+
&& /^\/kr\/post\/[^/]+-[A-Za-z0-9]{8,12}\/?$/.test(url.pathname) ? url.pathname.replace(/\/$/, "") : null;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function parsePost(html, url) {
|
|
17
|
+
const { document } = parseHTML(html);
|
|
18
|
+
for (const node of document.querySelectorAll('script, style, noscript, template, textarea, input, [hidden], [aria-hidden="true"], [inert]'))
|
|
19
|
+
node.remove();
|
|
20
|
+
for (const node of document.querySelectorAll("[style]")) {
|
|
21
|
+
if (/(?:^|;)\s*(?:display\s*:\s*none|visibility\s*:\s*(?:hidden|collapse))\s*(?:!important\s*)?(?:;|$)/i.test(node.getAttribute("style") || ""))
|
|
22
|
+
node.remove();
|
|
23
|
+
}
|
|
24
|
+
const roots = Array.from(document.querySelectorAll(".contents")).filter(node => !node.parentElement?.closest(".contents"));
|
|
25
|
+
const root = roots.length === 1 ? roots[0] : null;
|
|
26
|
+
const titles = Array.from(root?.querySelectorAll(".article-view-head > h1") || []).filter(node => node.closest(".contents") === root);
|
|
27
|
+
const bodies = Array.from(root?.querySelectorAll(".article-view-contents") || []).filter(node => node.parentElement === root);
|
|
28
|
+
const commentScopes = Array.from(root?.querySelectorAll(".article-comments") || []).filter(node => node.parentElement === root);
|
|
29
|
+
const body = bodies.length === 1 ? bodies[0] : null;
|
|
30
|
+
const nativeBodies = Array.from(body?.querySelectorAll("#contentArea.contents-txt") || []).filter(node => node.parentElement === body);
|
|
31
|
+
const nativeBody = nativeBodies.length === 1 ? nativeBodies[0] : null;
|
|
32
|
+
const scope = commentScopes.length === 1 ? commentScopes[0] : null;
|
|
33
|
+
const more = Array.from(scope?.querySelectorAll("button.btn-reply") || []).filter(node => node.parentElement === scope || (node.parentElement?.classList.contains("wrap-reply") && node.parentElement.parentElement === scope))
|
|
34
|
+
.filter(node => /^(?:댓글|\+?\s*대댓글)\s*[1-9]\d*개\s*더보기$/.test(stripTags(node.innerHTML)));
|
|
35
|
+
const bodyHtml = nativeBody?.innerHTML || "";
|
|
36
|
+
const title = titles.length === 1 ? stripTags(titles[0].innerHTML) : "";
|
|
37
|
+
const text = stripTags(bodyHtml);
|
|
38
|
+
const countNodes = Array.from(scope?.querySelectorAll(":scope > .write_area > .comments_function > h3") || []);
|
|
39
|
+
const countText = countNodes.length === 1 ? stripTags(countNodes[0].innerHTML) : "";
|
|
40
|
+
const count = /^댓글\s*(\d{1,6})$/.exec(countText);
|
|
41
|
+
const declared = count ? Number(count[1]) : null;
|
|
42
|
+
const comments = [];
|
|
43
|
+
const ids = new Set();
|
|
44
|
+
let conflicting = false;
|
|
45
|
+
const links = [...extractAnchorHrefs(bodyHtml), ...extractUrls(bodyHtml)];
|
|
46
|
+
const assetHtml = [bodyHtml];
|
|
47
|
+
for (const row of scope?.querySelectorAll(".comment_area") || []) {
|
|
48
|
+
const parent = row.parentElement;
|
|
49
|
+
if (row.closest(".article-comments") !== scope || !(parent === scope || (parent?.classList.contains("wrap-reply") && parent.parentElement === scope)))
|
|
50
|
+
continue;
|
|
51
|
+
const id = row.id;
|
|
52
|
+
const contents = Array.from(row.querySelectorAll(".cmt-txt")).filter(node => node.closest(".comment_area") === row);
|
|
53
|
+
if (!id && parent === scope && row.querySelector(":scope > p.name > span.cp")
|
|
54
|
+
&& /(?:^|\s)광고(?:\s|$)/.test(stripTags(row.querySelector(":scope > p.name")?.innerHTML || "")))
|
|
55
|
+
continue;
|
|
56
|
+
if (!/^[1-9]\d*$/.test(id) || ids.has(id) || contents.length !== 1) {
|
|
57
|
+
conflicting = true;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const content = contents[0];
|
|
61
|
+
const commentText = stripTags(content.innerHTML);
|
|
62
|
+
if (!commentText && !content.querySelector("img, video")) {
|
|
63
|
+
conflicting = true;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
ids.add(id);
|
|
67
|
+
// A reply wrapper proves reply status, not which preceding row is its parent.
|
|
68
|
+
comments.push({ id, parent_id: null, is_reply: Boolean(row.closest(".wrap-reply")), text: commentText, url: `${url.split("#")[0]}#${id}` });
|
|
69
|
+
links.push(...extractAnchorHrefs(content.innerHTML), ...extractUrls(content.innerHTML));
|
|
70
|
+
assetHtml.push(content.innerHTML);
|
|
71
|
+
}
|
|
72
|
+
const ok = Boolean(title && nativeBody && (text || nativeBody.querySelector("img, video")));
|
|
73
|
+
return { title, text, comments, declared, ok, more: more.length, complete: Boolean(ok && scope && declared !== null && !conflicting && !more.length && declared === comments.length),
|
|
74
|
+
links: dedupeUrls(links), assetHtml: assetHtml.join("\n") };
|
|
75
|
+
}
|
|
76
|
+
async function readCommentMore(context, baseline, deadline) {
|
|
77
|
+
const selector = '.contents > .article-comments > button.btn-reply, .contents > .article-comments > .wrap-reply > button.btn-reply';
|
|
78
|
+
let finalHtml = "";
|
|
79
|
+
let bytes = 0;
|
|
80
|
+
const session = await context.runBrowserSession({ ...context.browserSessionOptions, url: context.url,
|
|
81
|
+
timeoutMs: Math.min(context.timeoutMs, deadline - Date.now()),
|
|
82
|
+
operatorTask: async (page, remaining) => {
|
|
83
|
+
let prior = baseline;
|
|
84
|
+
for (let step = 0; step < 8; step++) {
|
|
85
|
+
remaining();
|
|
86
|
+
if (identity(page.url()) !== identity(context.url))
|
|
87
|
+
throw new Error("Blind post identity changed");
|
|
88
|
+
const visible = await page.mainFrame().evaluate(() => document.body?.innerText || "");
|
|
89
|
+
if (looksBotBlockedContent(visible.slice(0, 4000)) || await page.locator('dialog[open]:visible, [role="dialog"]:visible, [aria-modal="true"]:visible, iframe[src*="recaptcha"]:visible, iframe[src*="hcaptcha"]:visible').count()) {
|
|
90
|
+
throw new BrowserSessionToolError({ category: "block", code: "operator_browser_user_required", message: "Comment reading requires user participation" });
|
|
91
|
+
}
|
|
92
|
+
const html = await page.content();
|
|
93
|
+
bytes += Buffer.byteLength(html);
|
|
94
|
+
if (bytes > 8 * 1024 * 1024)
|
|
95
|
+
throw new Error("Blind comment snapshots exceed 8 MiB");
|
|
96
|
+
const current = parsePost(html, context.url);
|
|
97
|
+
if (!current.ok || current.title !== baseline.title || current.text !== baseline.text || current.declared !== baseline.declared
|
|
98
|
+
|| prior.comments.some(row => !current.comments.some(other => JSON.stringify(other) === JSON.stringify(row))))
|
|
99
|
+
throw new Error("Blind post evidence changed");
|
|
100
|
+
finalHtml = html;
|
|
101
|
+
if (!current.more || step === 7)
|
|
102
|
+
break;
|
|
103
|
+
const controls = page.locator(selector);
|
|
104
|
+
if (await controls.count() !== current.more)
|
|
105
|
+
throw new Error("Blind comment controls changed");
|
|
106
|
+
const next = controls.first();
|
|
107
|
+
if (!await next.isVisible())
|
|
108
|
+
throw new Error("Blind comment control is unavailable");
|
|
109
|
+
await next.click({ trial: true, timeout: remaining() });
|
|
110
|
+
if (identity(page.url()) !== identity(context.url))
|
|
111
|
+
throw new Error("Blind post identity changed before continuation");
|
|
112
|
+
await next.click({ timeout: remaining() });
|
|
113
|
+
const changed = await page.waitForFunction(ids => {
|
|
114
|
+
const current = Array.from(document.querySelectorAll('.contents > .article-comments .comment_area[id]')).map(row => row.id);
|
|
115
|
+
return current.some(id => !ids.includes(id));
|
|
116
|
+
}, current.comments.map(row => String(row.id)), { polling: 25, timeout: remaining() });
|
|
117
|
+
await changed.dispose();
|
|
118
|
+
prior = current;
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
if (!session.ok)
|
|
123
|
+
throw new BrowserSessionToolError(session.error);
|
|
124
|
+
if (Date.now() >= deadline || identity(session.final_url) !== identity(context.url) || !finalHtml)
|
|
125
|
+
throw new Error("Blind comment continuation exceeded deadline or identity");
|
|
126
|
+
const recovered = parsePost(finalHtml, context.url);
|
|
127
|
+
if (!recovered.ok || recovered.title !== baseline.title || recovered.text !== baseline.text || recovered.declared !== baseline.declared
|
|
128
|
+
|| baseline.comments.some(row => !recovered.comments.some(other => JSON.stringify(other) === JSON.stringify(row)))
|
|
129
|
+
|| recovered.comments.length <= baseline.comments.length)
|
|
130
|
+
throw new Error("Blind comments did not advance");
|
|
131
|
+
return recovered;
|
|
132
|
+
}
|
|
133
|
+
export const blindPostProvider = {
|
|
134
|
+
id: "blind-post",
|
|
135
|
+
matches: (url, target) => target === "generic" && Boolean(identity(url)),
|
|
136
|
+
async normalize(context) {
|
|
137
|
+
const matched = identity(context.url) && identity(context.resolvedUrl) === identity(context.url);
|
|
138
|
+
let parsed = parsePost(matched ? context.active.content : "", context.url);
|
|
139
|
+
let method = context.active.method;
|
|
140
|
+
const errors = [...context.errors];
|
|
141
|
+
if (!matched)
|
|
142
|
+
errors.push({ category: "parse_error", code: "blind_post_identity_mismatch", message: "Retrieved page changed the requested Blind post identity" });
|
|
143
|
+
const deadline = context.deadlineAt ?? Date.now() + context.timeoutMs;
|
|
144
|
+
if (matched && parsed.ok && parsed.more && context.runBrowserSession && context.browserSessionOptions?.cdpUrl
|
|
145
|
+
&& !context.browserSessionOptions.profileDir && deadline - Date.now() >= 1000) {
|
|
146
|
+
try {
|
|
147
|
+
parsed = await readCommentMore(context, parsed, deadline);
|
|
148
|
+
method = "blind_browser_comments";
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
if (error instanceof BrowserSessionToolError)
|
|
152
|
+
errors.push(error.toolError);
|
|
153
|
+
errors.push({ category: "unavailable", code: "blind_comment_pages_incomplete", message: "Bounded native comment continuation failed; original evidence is preserved" });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (!parsed.ok)
|
|
157
|
+
errors.push({ category: "parse_error", code: "blind_post_parse_failed", message: "Could not verify the requested public Blind post body" });
|
|
158
|
+
else if (!parsed.complete)
|
|
159
|
+
errors.push({ category: "parse_error", code: "blind_post_comments_incomplete", message: `Collected ${parsed.comments.length} readable comments; declared total ${parsed.declared ?? "unknown"}. Hidden or unloaded comments are not claimed.` });
|
|
160
|
+
return {
|
|
161
|
+
post: parsed.ok ? { url: context.url, title: parsed.title, text: parsed.text, status: "ok", comment_coverage: { declared_count: parsed.declared, collected_count: parsed.comments.length, complete: parsed.complete } } : null,
|
|
162
|
+
thread: [], comments: parsed.ok ? parsed.comments : [], outbound_links: parsed.ok ? parsed.links : [],
|
|
163
|
+
assetHtml: parsed.ok ? parsed.assetHtml : "", partial: !parsed.complete, errors, method,
|
|
164
|
+
};
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
//# sourceMappingURL=blind-post.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blind-post.js","sourceRoot":"","sources":["../../../src/smartfetch/providers/blind-post.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { parseHTML } from "linkedom";
|
|
2
|
+
import { dedupeUrls, extractAnchorHrefs, extractUrls, stripTags } from "../../shared.js";
|
|
3
|
+
function identity(url) {
|
|
4
|
+
try {
|
|
5
|
+
const parsed = new URL(url);
|
|
6
|
+
const match = /^\/service\/board\/([a-zA-Z0-9_]+)\/([1-9]\d*)\/?$/.exec(parsed.pathname);
|
|
7
|
+
return ["www.clien.net", "clien.net"].includes(parsed.hostname) && match ? `${match[1]}/${match[2]}` : null;
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
const userContent = '.post_article, .comment_view, textarea, template, [contenteditable]:not([contenteditable="false"]), [role="textbox"]';
|
|
14
|
+
function consistentCount(texts) {
|
|
15
|
+
const values = texts.map(text => /^(?:\d+|\d{1,3}(?:,\d{3})+)$/.test(text) ? Number(text.replace(/,/g, "")) : null);
|
|
16
|
+
const first = values[0] ?? null;
|
|
17
|
+
return first !== null && Number.isSafeInteger(first) && values.every(value => value === first) ? first : null;
|
|
18
|
+
}
|
|
19
|
+
function parsePost(html, url) {
|
|
20
|
+
const { document } = parseHTML(html);
|
|
21
|
+
for (const node of document.querySelectorAll('script, style, noscript, template, textarea, input, [hidden], [aria-hidden="true"], .none, [contenteditable]:not([contenteditable="false"]), [role="textbox"]'))
|
|
22
|
+
node.remove();
|
|
23
|
+
for (const node of document.querySelectorAll("[style]")) {
|
|
24
|
+
if (/(?:^|;)\s*(?:display\s*:\s*none|visibility\s*:\s*(?:hidden|collapse))\s*(?:!important\s*)?(?:;|$)/i.test(node.getAttribute("style") || ""))
|
|
25
|
+
node.remove();
|
|
26
|
+
}
|
|
27
|
+
const roots = Array.from(document.querySelectorAll(".content_view")).filter(node => !node.closest(userContent) && !node.parentElement?.closest(".content_view"));
|
|
28
|
+
const root = roots.length === 1 ? roots[0] : null;
|
|
29
|
+
const own = (selector) => Array.from(root?.querySelectorAll(selector) || []).filter(node => !node.closest(userContent) && node.closest(".content_view") === root);
|
|
30
|
+
const titles = own('.post_title .post_subject > span:not(.post_category):not(.fa)');
|
|
31
|
+
const title = titles.length === 1 ? stripTags(titles[0].innerHTML) : "";
|
|
32
|
+
const bodies = Array.from(root?.querySelectorAll(".post_view .post_article") || []).filter(node => !node.parentElement?.closest(userContent) && node.closest(".content_view") === root);
|
|
33
|
+
const body = bodies.length === 1 ? bodies[0] : null;
|
|
34
|
+
for (const node of body?.querySelectorAll("script, style, noscript, template, textarea, input") || [])
|
|
35
|
+
node.remove();
|
|
36
|
+
const bodyHtml = body?.innerHTML || "";
|
|
37
|
+
const text = stripTags(bodyHtml);
|
|
38
|
+
const scopes = own(".post_comment").filter(node => node.parentElement === root);
|
|
39
|
+
const scope = scopes.length === 1 ? scopes[0] : null;
|
|
40
|
+
const lists = Array.from(scope?.querySelectorAll('[data-role="comment"]') || []).filter(node => node.parentElement === scope);
|
|
41
|
+
const list = lists.length === 1 ? lists[0] : null;
|
|
42
|
+
const headerCounts = own('.post_title .post_subject > .post_reply > span:not(.fa)');
|
|
43
|
+
const counts = [...headerCounts, ...Array.from(scope?.querySelectorAll('.comment_head > a > strong, .comment_more [data-role="comment-total-count"]') || []).filter(node => !node.closest(`${userContent}, .comment_row`) && node.closest(".post_comment") === scope)];
|
|
44
|
+
const declared = consistentCount(counts.map(node => node.textContent?.trim() || ""));
|
|
45
|
+
// Plain HTTP leaves this client-populated label empty even with every row
|
|
46
|
+
// already present. Ignore absent labels, never a contradictory numeric one.
|
|
47
|
+
const currentCounts = Array.from(scope?.querySelectorAll('.comment_more [data-role="comment-current-count"]') || []).filter(node => !node.closest(`${userContent}, .comment_row`) && node.closest(".post_comment") === scope).map(node => node.textContent?.trim() || "").filter(Boolean);
|
|
48
|
+
const current = consistentCount(currentCounts);
|
|
49
|
+
const comments = [];
|
|
50
|
+
const deletions = [];
|
|
51
|
+
const links = [...extractAnchorHrefs(bodyHtml), ...extractUrls(bodyHtml)];
|
|
52
|
+
const assetHtml = [bodyHtml];
|
|
53
|
+
const seen = new Map();
|
|
54
|
+
let conflicting = false;
|
|
55
|
+
let position = 0;
|
|
56
|
+
for (const row of list?.children || []) {
|
|
57
|
+
if (!row.classList.contains("comment_row"))
|
|
58
|
+
continue;
|
|
59
|
+
position++;
|
|
60
|
+
const id = row.getAttribute("data-comment-sn") || "";
|
|
61
|
+
const point = Array.from(row.querySelectorAll(".comment_point")).filter(node => node.parentElement === row);
|
|
62
|
+
if (!id && row.classList.contains("blocked") && !row.querySelector(".comment_view") && /^삭제\s*되었습니다\.$/.test(stripTags(row.innerHTML))) {
|
|
63
|
+
deletions.push({ position, text: stripTags(row.innerHTML) });
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const contents = Array.from(row.querySelectorAll(".comment_view")).filter(node => node.closest(".comment_row") === row && !node.parentElement?.closest(userContent));
|
|
67
|
+
if (!/^[1-9]\d*$/.test(id) || point.length !== 1 || point[0].id !== id || contents.length !== 1 || contents[0].getAttribute("data-comment-view") !== id) {
|
|
68
|
+
conflicting = true;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const content = contents[0];
|
|
72
|
+
for (const node of content.querySelectorAll("script, style, noscript, template, textarea, input"))
|
|
73
|
+
node.remove();
|
|
74
|
+
const commentText = stripTags(content.innerHTML);
|
|
75
|
+
const media = Array.from(row.querySelectorAll(".comment-img img[src], .comment_view img[src]")).filter(node => node.closest(".comment_row") === row).flatMap(node => {
|
|
76
|
+
try {
|
|
77
|
+
const src = new URL(node.getAttribute("src") || "", url);
|
|
78
|
+
return ["http:", "https:"].includes(src.protocol) && !src.username && !src.password ? [src.toString()] : [];
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
if (!commentText && !media.length) {
|
|
85
|
+
conflicting = true;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const info = Array.from(row.children).find(node => node.classList.contains("comment_info"));
|
|
89
|
+
const authorNode = info?.querySelector(".nickname");
|
|
90
|
+
const author = stripTags(authorNode?.innerHTML || "");
|
|
91
|
+
const createdAt = info?.querySelector(".timestamp")?.getAttribute("datetime") || null;
|
|
92
|
+
// The public flat DOM exposes reply status, not a verified parent ID.
|
|
93
|
+
// Never infer a parent from the nearest row or a mentioned nickname.
|
|
94
|
+
const reply = row.classList.contains("re");
|
|
95
|
+
const signature = JSON.stringify([commentText, author, createdAt, reply, media]);
|
|
96
|
+
if (seen.has(id)) {
|
|
97
|
+
if (seen.get(id) !== signature)
|
|
98
|
+
conflicting = true;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
seen.set(id, signature);
|
|
102
|
+
comments.push({ id, parent_id: null, is_reply: reply, position, author, text: commentText, created_at: createdAt, url: `${url.split("#")[0]}#${id}`, ...(media.length ? { media: dedupeUrls(media) } : {}) });
|
|
103
|
+
assetHtml.push(content.innerHTML, ...Array.from(row.querySelectorAll(".comment-img")).filter(node => node.parentElement === row).map(node => node.innerHTML));
|
|
104
|
+
links.push(...extractAnchorHrefs(content.innerHTML), ...extractUrls(content.innerHTML));
|
|
105
|
+
}
|
|
106
|
+
return { title, text, comments, deletions, links: dedupeUrls(links), assetHtml: assetHtml.join("\n"), declared,
|
|
107
|
+
ok: Boolean(title && (text || body?.querySelector("img, video"))),
|
|
108
|
+
complete: Boolean(list && !conflicting && declared !== null && declared === comments.length && (!currentCounts.length || current === declared)) };
|
|
109
|
+
}
|
|
110
|
+
export const clienProvider = {
|
|
111
|
+
id: "clien",
|
|
112
|
+
matches: (url, target) => target === "generic" && Boolean(identity(url)),
|
|
113
|
+
async normalize(context) {
|
|
114
|
+
const expected = identity(context.url);
|
|
115
|
+
const matched = expected && identity(context.resolvedUrl) === expected;
|
|
116
|
+
let parsed = parsePost(matched ? context.active.content : "", context.url);
|
|
117
|
+
let method = context.active.method;
|
|
118
|
+
const errors = [...context.errors];
|
|
119
|
+
if (!matched)
|
|
120
|
+
errors.push({ category: "parse_error", code: "clien_identity_mismatch", message: "Retrieved page does not match the requested Clien board/post identity" });
|
|
121
|
+
const deadline = context.deadlineAt ?? Date.now() + context.timeoutMs;
|
|
122
|
+
const remaining = Math.max(0, Math.min(context.timeoutMs, deadline - Date.now()));
|
|
123
|
+
const connected = context.browserSessionOptions?.cdpUrl && !context.browserSessionOptions.profileDir;
|
|
124
|
+
if (expected && parsed.ok && !parsed.complete && !parsed.comments.length && context.runBrowserSession && connected && remaining >= 1000) {
|
|
125
|
+
try {
|
|
126
|
+
const session = await context.runBrowserSession({ ...context.browserSessionOptions, url: context.url, timeoutMs: remaining, maxAnchors: 100, waitForSelector: '.content_view > .post_comment > [data-role="comment"] > .comment_row .comment_view' });
|
|
127
|
+
if (Date.now() >= deadline)
|
|
128
|
+
errors.push({ category: "timeout", code: "clien_comments_timeout", message: "Comment continuation exceeded the original retrieval deadline" });
|
|
129
|
+
else if (session.ok && identity(session.final_url) === expected) {
|
|
130
|
+
const recovered = parsePost(session.html, context.url);
|
|
131
|
+
if (recovered.ok && (recovered.comments.length > parsed.comments.length || recovered.complete)) {
|
|
132
|
+
parsed = recovered;
|
|
133
|
+
method = "clien_browser_comments";
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
else if (!session.ok)
|
|
137
|
+
errors.push(session.error);
|
|
138
|
+
else
|
|
139
|
+
errors.push({ category: "parse_error", code: "clien_identity_mismatch", message: "Comment continuation navigated to a different post" });
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
errors.push({ category: "unavailable", code: "clien_comments_browser_failed", message: "Bounded comment continuation failed" });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (!parsed.ok)
|
|
146
|
+
errors.push({ category: "parse_error", code: "clien_parse_failed", message: "Could not parse the requested native title/body" });
|
|
147
|
+
else if (!parsed.complete)
|
|
148
|
+
errors.push({ category: "parse_error", code: "clien_comments_incomplete", message: `Collected ${parsed.comments.length} readable comments; declared total ${parsed.declared ?? "unknown"}. Deleted positions are separate; unloaded comments are not claimed as retrieved.` });
|
|
149
|
+
return {
|
|
150
|
+
post: parsed.ok ? { url: context.url, title: parsed.title, text: parsed.text, status: "ok", comment_deletions: parsed.deletions, comment_coverage: { declared_count: parsed.declared, collected_count: parsed.comments.length, complete: parsed.complete } } : null,
|
|
151
|
+
thread: [], comments: parsed.ok ? parsed.comments : [], outbound_links: parsed.ok ? parsed.links : [],
|
|
152
|
+
assetHtml: parsed.ok ? parsed.assetHtml : "",
|
|
153
|
+
partial: !parsed.ok || !parsed.complete, errors, method,
|
|
154
|
+
};
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
//# sourceMappingURL=clien.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clien.js","sourceRoot":"","sources":["../../../src/smartfetch/providers/clien.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,2 +1,17 @@
|
|
|
1
|
+
import type { BrowserPageSnapshot } from "../../browser-session.js";
|
|
1
2
|
import type { SmartfetchProvider } from "../provider-types.js";
|
|
3
|
+
type CommerceItem = {
|
|
4
|
+
title: string;
|
|
5
|
+
url: string;
|
|
6
|
+
price?: string;
|
|
7
|
+
image?: string;
|
|
8
|
+
source?: string;
|
|
9
|
+
availability?: string;
|
|
10
|
+
rating?: string;
|
|
11
|
+
snippet?: string;
|
|
12
|
+
};
|
|
13
|
+
export declare function isCoupangStorefrontProductUrl(value: string): boolean;
|
|
14
|
+
export declare function coupangTitleQueryEvidence(title: string, query: string): boolean | null;
|
|
15
|
+
export declare function extractCoupangBrowserItems(snapshot: BrowserPageSnapshot, query: string): CommerceItem[];
|
|
2
16
|
export declare const commerceProvider: SmartfetchProvider;
|
|
17
|
+
export {};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { absolutizeUrl, dedupeUrls, extractMetaDescription, extractMetaName, extractMetaProperty, extractTitleFromHtml, stripTags, } from "../../shared.js";
|
|
2
2
|
import { runSmartsearch } from "../../smartsearch.js";
|
|
3
|
+
import { parseHTML } from "linkedom";
|
|
3
4
|
import { commerceSiteForUrl } from "../commerce-sites.js";
|
|
4
5
|
import { normalizeArticleLike } from "./article.js";
|
|
5
6
|
function normalizeSpace(value) {
|
|
@@ -55,11 +56,25 @@ function blockedCommercePage(html) {
|
|
|
55
56
|
const haystack = normalizeSpace(stripTags(html).slice(0, 4000)).toLowerCase();
|
|
56
57
|
if (!haystack)
|
|
57
58
|
return false;
|
|
58
|
-
return /(access denied|you don'?t have permission|이 페이지에 접근할 수 있는 권한이 없습니다|이페이지에접근할수있는권한이없습니다|powered and protected by privacy|akamai)/i.test(haystack);
|
|
59
|
+
return /(access denied|you don'?t have permission|사용\s*권한(?:이 없| 제한)|이 페이지에 접근할 수 있는 권한이 없습니다|이페이지에접근할수있는권한이없습니다|powered and protected by privacy|akamai)/i.test(haystack);
|
|
59
60
|
}
|
|
60
61
|
function allowedKoreanCoupangHost(host) {
|
|
61
62
|
return host === "www.coupang.com" || host === "coupang.com" || host === "m.coupang.com";
|
|
62
63
|
}
|
|
64
|
+
export function isCoupangStorefrontProductUrl(value) {
|
|
65
|
+
try {
|
|
66
|
+
const url = new URL(value);
|
|
67
|
+
return url.protocol === "https:" && !url.username && !url.password && !url.port
|
|
68
|
+
&& allowedKoreanCoupangHost(url.hostname) && /^\/vp\/products\/\d+$/.test(url.pathname)
|
|
69
|
+
&& ["itemId", "vendorItemId"].every((key) => {
|
|
70
|
+
const values = url.searchParams.getAll(key);
|
|
71
|
+
return values.length === 0 || (values.length === 1 && /^\d+$/.test(values[0] || ""));
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
63
78
|
function coupangRelayHost(host) {
|
|
64
79
|
return host === "link.coupang.com";
|
|
65
80
|
}
|
|
@@ -460,9 +475,16 @@ function canonicalProductUrl(url, site) {
|
|
|
460
475
|
}
|
|
461
476
|
if (site === "coupang") {
|
|
462
477
|
const match = parsed.pathname.match(/^(\/vp\/products\/\d+)/i);
|
|
478
|
+
const identity = new URLSearchParams();
|
|
479
|
+
for (const key of ["itemId", "vendorItemId"]) {
|
|
480
|
+
const value = parsed.searchParams.get(key);
|
|
481
|
+
if (value)
|
|
482
|
+
identity.set(key, value);
|
|
483
|
+
}
|
|
484
|
+
const suffix = identity.size ? `?${identity}` : "";
|
|
463
485
|
if (allowedKoreanCoupangHost(parsed.hostname.toLowerCase()))
|
|
464
|
-
return `https://www.coupang.com${match?.[1] || parsed.pathname}`;
|
|
465
|
-
return `${parsed.origin}${match?.[1] || parsed.pathname}`;
|
|
486
|
+
return `https://www.coupang.com${match?.[1] || parsed.pathname}${suffix}`;
|
|
487
|
+
return `${parsed.origin}${match?.[1] || parsed.pathname}${suffix}`;
|
|
466
488
|
}
|
|
467
489
|
if (site === "aliexpress") {
|
|
468
490
|
const match = parsed.pathname.match(/^(\/item\/\d+\.html)/i);
|
|
@@ -478,7 +500,10 @@ function anchorMap(html, baseUrl, site, hrefPattern) {
|
|
|
478
500
|
const map = new Map();
|
|
479
501
|
for (const match of html.matchAll(/<a\b[^>]*href=(?:"([^"]+)"|'([^']+)'|([^\s>]+))[^>]*>([\s\S]*?)<\/a>/gi)) {
|
|
480
502
|
const rawHref = String(match[1] || match[2] || match[3] || "");
|
|
481
|
-
const
|
|
503
|
+
const originalUrl = absolutizeUrl(rawHref, baseUrl);
|
|
504
|
+
if (site === "coupang" && !isCoupangStorefrontProductUrl(originalUrl))
|
|
505
|
+
continue;
|
|
506
|
+
const absolute = canonicalProductUrl(originalUrl, site);
|
|
482
507
|
if (!absolute || !hrefPattern.test(absolute))
|
|
483
508
|
continue;
|
|
484
509
|
const text = cleanTitle(stripTags(String(match[4] || "")));
|
|
@@ -517,7 +542,10 @@ function productFromStructuredAnchors(context, site) {
|
|
|
517
542
|
const map = new Map();
|
|
518
543
|
const baseUrl = context.resolvedUrl || context.url;
|
|
519
544
|
for (const anchor of anchors) {
|
|
520
|
-
const
|
|
545
|
+
const originalUrl = absolutizeUrl(anchor.href, baseUrl);
|
|
546
|
+
if (site === "coupang" && !isCoupangStorefrontProductUrl(originalUrl))
|
|
547
|
+
continue;
|
|
548
|
+
const url = canonicalProductUrl(originalUrl, site);
|
|
521
549
|
if (!pattern.test(url))
|
|
522
550
|
continue;
|
|
523
551
|
const existing = map.get(url) || { url, texts: [] };
|
|
@@ -546,6 +574,40 @@ function productFromStructuredAnchors(context, site) {
|
|
|
546
574
|
}
|
|
547
575
|
return dedupeByUrl(items);
|
|
548
576
|
}
|
|
577
|
+
// Shared pure parser: the vertical API recovery must not invoke commerce's
|
|
578
|
+
// search-reference fallback or replace a failed browser read with snippets.
|
|
579
|
+
export function coupangTitleQueryEvidence(title, query) {
|
|
580
|
+
const normalize = (value) => value.normalize("NFKC").toLowerCase()
|
|
581
|
+
.replace(/([\p{Script=Hangul}])([a-z0-9])/gu, "$1 $2")
|
|
582
|
+
.replace(/([a-z0-9])([\p{Script=Hangul}])/gu, "$1 $2");
|
|
583
|
+
const terms = normalize(query).split(/[^\p{L}\p{N}]+/u).filter(Boolean);
|
|
584
|
+
const normalizedTitle = normalize(title);
|
|
585
|
+
const matches = terms.length > 0 && terms.every((term, index) => {
|
|
586
|
+
if (!/^[a-z0-9]+$/.test(term))
|
|
587
|
+
return normalizedTitle.includes(term);
|
|
588
|
+
// Keep a separate model version next to its name. An unrelated "3 devices"
|
|
589
|
+
// elsewhere in a Master 3S card must not establish the requested Master 3.
|
|
590
|
+
const previous = terms[index - 1];
|
|
591
|
+
const evidence = /^[0-9]+$/.test(term) && previous && /\p{L}/u.test(previous)
|
|
592
|
+
? `${previous}[\\s-]*${term}` : term;
|
|
593
|
+
return new RegExp(`(?:^|[^a-z0-9])${evidence}(?=$|[^a-z0-9])`).test(normalizedTitle);
|
|
594
|
+
});
|
|
595
|
+
if (matches)
|
|
596
|
+
return true;
|
|
597
|
+
// A single broad category may be implicit in an API product's brand/title.
|
|
598
|
+
// This is unknown, not a lexical mismatch or independently verified relevance.
|
|
599
|
+
if (terms.length < 2 && !terms.some((term) => /[0-9]/.test(term)))
|
|
600
|
+
return null;
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
603
|
+
export function extractCoupangBrowserItems(snapshot, query) {
|
|
604
|
+
if (wrongMarketCoupangHost(snapshot.final_url) || blockedCommercePage(snapshot.html))
|
|
605
|
+
return [];
|
|
606
|
+
return productFromStructuredAnchors({
|
|
607
|
+
url: snapshot.url, resolvedUrl: snapshot.final_url,
|
|
608
|
+
active: { ok: true, method: snapshot.method, content: snapshot.html, anchors: snapshot.anchors, links: snapshot.links },
|
|
609
|
+
}, "coupang").filter((item) => coupangTitleQueryEvidence(item.title, query) === true);
|
|
610
|
+
}
|
|
549
611
|
function productFromAnchorMap(html, baseUrl, site) {
|
|
550
612
|
const pattern = productUrlPattern(site);
|
|
551
613
|
const map = anchorMap(html, baseUrl, site, pattern);
|
|
@@ -685,6 +747,8 @@ function weakCommerceProduct(site, post) {
|
|
|
685
747
|
const title = normalizeSpace(String(post.title || ""));
|
|
686
748
|
const description = normalizeSpace(String(post.description || ""));
|
|
687
749
|
const price = normalizeSpace(String(post.price || ""));
|
|
750
|
+
if (site === "coupang" && (!title || /^(?:쿠팡|coupang|로딩\s*중|loading)[.\s…]*$/i.test(title)))
|
|
751
|
+
return true;
|
|
688
752
|
if (site === "aliexpress") {
|
|
689
753
|
if (!title)
|
|
690
754
|
return true;
|
|
@@ -695,6 +759,28 @@ function weakCommerceProduct(site, post) {
|
|
|
695
759
|
}
|
|
696
760
|
return false;
|
|
697
761
|
}
|
|
762
|
+
function coupangVisibleProductMatches(identity, visible) {
|
|
763
|
+
const observed = visible.match(/쿠팡상품번호\s*:\s*(\d+)\s*[-–]\s*(\d+)/);
|
|
764
|
+
if (!identity || !observed)
|
|
765
|
+
return false;
|
|
766
|
+
return (!identity.productId || identity.productId === observed[1])
|
|
767
|
+
&& (!identity.itemId || identity.itemId === observed[2]);
|
|
768
|
+
}
|
|
769
|
+
function coupangRenderedRegularPrice(html, visible) {
|
|
770
|
+
const { document } = parseHTML(html);
|
|
771
|
+
const layouts = [...document.querySelectorAll(".price-container .price-layout-container.price-layout-normal")];
|
|
772
|
+
if (layouts.length !== 1)
|
|
773
|
+
return undefined;
|
|
774
|
+
const row = layouts[0]?.firstElementChild;
|
|
775
|
+
if (!row || /(?:카드|쿠폰|와우|회원|전용|즉시할인)/.test(row.textContent || "")
|
|
776
|
+
|| row.querySelector(".twc-line-through, s, del, strike"))
|
|
777
|
+
return undefined;
|
|
778
|
+
const prices = (row.textContent || "").match(/\b[\d,]+\s*원/g) || [];
|
|
779
|
+
if (prices.length !== 1)
|
|
780
|
+
return undefined;
|
|
781
|
+
const price = normalizeSpace(prices[0]);
|
|
782
|
+
return visible.includes(price) ? price : undefined;
|
|
783
|
+
}
|
|
698
784
|
function buildSearchSummary(query, host, items) {
|
|
699
785
|
const lines = items.slice(0, 8).map((item, index) => `${index + 1}. ${item.title}${item.price ? ` — ${item.price}` : ""}`);
|
|
700
786
|
return {
|
|
@@ -732,6 +818,15 @@ async function normalizeCommerce(context) {
|
|
|
732
818
|
const host = hostLabel(url);
|
|
733
819
|
const blocked = blockedCommercePage(html);
|
|
734
820
|
const wrongMarket = site === "coupang" && (wrongMarketCoupangHost(rawUrl) || wrongMarketCoupangHost(url));
|
|
821
|
+
const requestedIdentity = site === "coupang" ? coupangProductIdentity(rawUrl) : null;
|
|
822
|
+
if (requestedIdentity && !sameCoupangIdentity(requestedIdentity, url)) {
|
|
823
|
+
return {
|
|
824
|
+
post: { url: rawUrl, title: "Unverified Coupang product variant", text: "The storefront redirected to a different product or variant; the requested identity was not verified.", kind: "commerce_product", status: "blocked_or_unavailable" },
|
|
825
|
+
thread: [], comments: [], outbound_links: [], partial: true,
|
|
826
|
+
errors: [...context.errors, { category: "parse_error", code: "commerce_product_identity_mismatch", message: "Requested productId/itemId/vendorItemId did not survive navigation" }],
|
|
827
|
+
method: "commerce_product",
|
|
828
|
+
};
|
|
829
|
+
}
|
|
735
830
|
if (wrongMarket) {
|
|
736
831
|
context.errors.push({ category: "block", code: "commerce_wrong_market_host", message: "Localized Coupang host differs from the canonical www.coupang.com product surface; treat this as reference evidence, not a final verified buy link." });
|
|
737
832
|
}
|
|
@@ -772,7 +867,20 @@ async function normalizeCommerce(context) {
|
|
|
772
867
|
};
|
|
773
868
|
}
|
|
774
869
|
const detailed = productDetailsFromJsonLd(html, url, site) || productFallbackFromHtml(html, url, site);
|
|
775
|
-
if (
|
|
870
|
+
if (site === "coupang" && context.active.method === "local_browser_cdp" && context.active.primary_content?.text) {
|
|
871
|
+
const visible = context.active.primary_content.text;
|
|
872
|
+
// JSON-LD can advertise a membership/signup coupon price without its
|
|
873
|
+
// condition. Prefer the explicitly labelled regular price and preserve
|
|
874
|
+
// the rendered conditions/specifications instead of metadata-only prose.
|
|
875
|
+
const regularPrice = visible.match(/일반\s*판매가\s*([\d,]+\s*원)/)?.[1];
|
|
876
|
+
const identityMatches = coupangVisibleProductMatches(requestedIdentity, visible);
|
|
877
|
+
detailed.post.price = identityMatches
|
|
878
|
+
? regularPrice || coupangRenderedRegularPrice(html, visible)
|
|
879
|
+
: undefined;
|
|
880
|
+
detailed.post.text = visible.slice(0, 12000);
|
|
881
|
+
detailed.post.url = canonicalProductUrl(url, site);
|
|
882
|
+
}
|
|
883
|
+
if (!wrongMarket && weakCommerceProduct(site, detailed.post)) {
|
|
776
884
|
context.errors.push({ category: "parse_error", code: "commerce_product_unverified", message: "Storefront exposed too little stable product metadata to verify this product page confidently." });
|
|
777
885
|
return {
|
|
778
886
|
post: {
|
|
@@ -797,12 +905,20 @@ async function normalizeCommerce(context) {
|
|
|
797
905
|
text: `Warning: this Coupang URL resolved to a localized/non-Korean host and should be treated as reference evidence, not a final verified buy link.\n${detailed.post.text}`,
|
|
798
906
|
}
|
|
799
907
|
: detailed.post;
|
|
908
|
+
const priceUnverified = site === "coupang" && context.active.method === "local_browser_cdp"
|
|
909
|
+
&& !post.price && /\d[\d,]*\s*원/.test(context.active.primary_content?.text || "");
|
|
910
|
+
if (priceUnverified)
|
|
911
|
+
context.errors.push({
|
|
912
|
+
category: "parse_error",
|
|
913
|
+
code: "commerce_price_unverified",
|
|
914
|
+
message: "Visible storefront prices could not be tied to the requested variant and their conditions; no verified price is reported.",
|
|
915
|
+
});
|
|
800
916
|
return {
|
|
801
917
|
post,
|
|
802
918
|
thread: [],
|
|
803
919
|
comments: [],
|
|
804
920
|
outbound_links: detailed.links,
|
|
805
|
-
partial: wrongMarket,
|
|
921
|
+
partial: wrongMarket || priceUnverified,
|
|
806
922
|
errors: context.errors,
|
|
807
923
|
method: "commerce_product",
|
|
808
924
|
};
|