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.
Files changed (159) hide show
  1. package/CHANGELOG.md +352 -1
  2. package/README.md +33 -566
  3. package/dist/assessment.d.ts +10 -3
  4. package/dist/assessment.js +21 -6
  5. package/dist/assessment.js.map +1 -1
  6. package/dist/body-export.d.ts +10 -0
  7. package/dist/body-export.js +28 -0
  8. package/dist/body-export.js.map +1 -0
  9. package/dist/browser-session.d.ts +32 -1
  10. package/dist/browser-session.js +373 -3
  11. package/dist/browser-session.js.map +1 -1
  12. package/dist/cli-fetch-options.d.ts +7 -0
  13. package/dist/cli-fetch-options.js +41 -0
  14. package/dist/cli-fetch-options.js.map +1 -0
  15. package/dist/cli.js +31 -12
  16. package/dist/cli.js.map +1 -1
  17. package/dist/comment-export.d.ts +10 -0
  18. package/dist/comment-export.js +39 -0
  19. package/dist/comment-export.js.map +1 -0
  20. package/dist/composition.d.ts +1 -22
  21. package/dist/composition.js +0 -16
  22. package/dist/composition.js.map +1 -1
  23. package/dist/extraction/budget.d.ts +3 -0
  24. package/dist/extraction/budget.js +88 -7
  25. package/dist/extraction/budget.js.map +1 -1
  26. package/dist/extraction/document.js +134 -9
  27. package/dist/extraction/document.js.map +1 -1
  28. package/dist/korea/coupang-filters.d.ts +5 -0
  29. package/dist/korea/coupang-filters.js +87 -0
  30. package/dist/korea/coupang-filters.js.map +1 -0
  31. package/dist/korea/coupang.d.ts +27 -6
  32. package/dist/korea/coupang.js +202 -82
  33. package/dist/korea/coupang.js.map +1 -1
  34. package/dist/korea/proxy-client.d.ts +1 -0
  35. package/dist/korea/proxy-client.js +6 -2
  36. package/dist/korea/proxy-client.js.map +1 -1
  37. package/dist/korea/smartvertical.d.ts +1 -0
  38. package/dist/korea/smartvertical.js +39 -25
  39. package/dist/korea/smartvertical.js.map +1 -1
  40. package/dist/lib.d.ts +4 -2
  41. package/dist/lib.js +2 -1
  42. package/dist/lib.js.map +1 -1
  43. package/dist/mcp-server.js +39 -15
  44. package/dist/mcp-server.js.map +1 -1
  45. package/dist/operator-browser-fixture.d.ts +7 -0
  46. package/dist/operator-browser-fixture.js +19 -0
  47. package/dist/operator-browser-fixture.js.map +1 -0
  48. package/dist/operator-canary-cli.d.ts +6 -0
  49. package/dist/operator-canary-cli.js +190 -0
  50. package/dist/operator-canary-cli.js.map +1 -0
  51. package/dist/operator-canary.d.ts +74 -0
  52. package/dist/operator-canary.js +135 -0
  53. package/dist/operator-canary.js.map +1 -0
  54. package/dist/operator-cdp.d.ts +15 -0
  55. package/dist/operator-cdp.js +195 -0
  56. package/dist/operator-cdp.js.map +1 -0
  57. package/dist/operator-frame.d.ts +3 -0
  58. package/dist/operator-frame.js +55 -0
  59. package/dist/operator-frame.js.map +1 -0
  60. package/dist/settings.d.ts +3 -0
  61. package/dist/settings.js +1 -0
  62. package/dist/settings.js.map +1 -1
  63. package/dist/shared.d.ts +47 -10
  64. package/dist/shared.js +15 -9
  65. package/dist/shared.js.map +1 -1
  66. package/dist/smartbrowser.d.ts +169 -0
  67. package/dist/smartbrowser.js +288 -0
  68. package/dist/smartbrowser.js.map +1 -0
  69. package/dist/smartcrawl.js +16 -21
  70. package/dist/smartcrawl.js.map +1 -1
  71. package/dist/smartfetch/academic-fallback.js +3 -5
  72. package/dist/smartfetch/academic-fallback.js.map +1 -1
  73. package/dist/smartfetch/archive-fallback.js +2 -5
  74. package/dist/smartfetch/archive-fallback.js.map +1 -1
  75. package/dist/smartfetch/assets.d.ts +1 -1
  76. package/dist/smartfetch/assets.js +28 -1
  77. package/dist/smartfetch/assets.js.map +1 -1
  78. package/dist/smartfetch/jina-reader.d.ts +1 -0
  79. package/dist/smartfetch/jina-reader.js +17 -3
  80. package/dist/smartfetch/jina-reader.js.map +1 -1
  81. package/dist/smartfetch/pipeline.js +15 -25
  82. package/dist/smartfetch/pipeline.js.map +1 -1
  83. package/dist/smartfetch/provider-policy.d.ts +3 -0
  84. package/dist/smartfetch/provider-policy.js +45 -2
  85. package/dist/smartfetch/provider-policy.js.map +1 -1
  86. package/dist/smartfetch/provider-types.d.ts +8 -0
  87. package/dist/smartfetch/providers/article.js +76 -10
  88. package/dist/smartfetch/providers/article.js.map +1 -1
  89. package/dist/smartfetch/providers/blind-post.d.ts +2 -0
  90. package/dist/smartfetch/providers/blind-post.js +167 -0
  91. package/dist/smartfetch/providers/blind-post.js.map +1 -0
  92. package/dist/smartfetch/providers/clien.d.ts +2 -0
  93. package/dist/smartfetch/providers/clien.js +157 -0
  94. package/dist/smartfetch/providers/clien.js.map +1 -0
  95. package/dist/smartfetch/providers/commerce.d.ts +15 -0
  96. package/dist/smartfetch/providers/commerce.js +123 -7
  97. package/dist/smartfetch/providers/commerce.js.map +1 -1
  98. package/dist/smartfetch/providers/dcinside.js +313 -20
  99. package/dist/smartfetch/providers/dcinside.js.map +1 -1
  100. package/dist/smartfetch/providers/hackernews-html.d.ts +21 -0
  101. package/dist/smartfetch/providers/hackernews-html.js +148 -0
  102. package/dist/smartfetch/providers/hackernews-html.js.map +1 -0
  103. package/dist/smartfetch/providers/hackernews.js +76 -14
  104. package/dist/smartfetch/providers/hackernews.js.map +1 -1
  105. package/dist/smartfetch/providers/index.js +7 -1
  106. package/dist/smartfetch/providers/index.js.map +1 -1
  107. package/dist/smartfetch/providers/linkedin.js +2 -1
  108. package/dist/smartfetch/providers/linkedin.js.map +1 -1
  109. package/dist/smartfetch/providers/naver-blog.d.ts +2 -0
  110. package/dist/smartfetch/providers/naver-blog.js +87 -45
  111. package/dist/smartfetch/providers/naver-blog.js.map +1 -1
  112. package/dist/smartfetch/providers/naver-cafe-native.d.ts +7 -0
  113. package/dist/smartfetch/providers/naver-cafe-native.js +188 -0
  114. package/dist/smartfetch/providers/naver-cafe-native.js.map +1 -0
  115. package/dist/smartfetch/providers/naver-cafe.js +165 -4
  116. package/dist/smartfetch/providers/naver-cafe.js.map +1 -1
  117. package/dist/smartfetch/providers/naver-map-search.js +1 -1
  118. package/dist/smartfetch/providers/naver-map-search.js.map +1 -1
  119. package/dist/smartfetch/providers/ppomppu.d.ts +2 -0
  120. package/dist/smartfetch/providers/ppomppu.js +289 -0
  121. package/dist/smartfetch/providers/ppomppu.js.map +1 -0
  122. package/dist/smartfetch/providers/reddit.js +24 -3
  123. package/dist/smartfetch/providers/reddit.js.map +1 -1
  124. package/dist/smartfetch/providers/threads.js +1 -1
  125. package/dist/smartfetch/providers/threads.js.map +1 -1
  126. package/dist/smartfetch/providers/wikipedia.js +138 -0
  127. package/dist/smartfetch/providers/wikipedia.js.map +1 -1
  128. package/dist/smartfetch.d.ts +11 -2
  129. package/dist/smartfetch.js +369 -78
  130. package/dist/smartfetch.js.map +1 -1
  131. package/dist/smartsearch-brave.d.ts +2 -0
  132. package/dist/smartsearch-brave.js +43 -0
  133. package/dist/smartsearch-brave.js.map +1 -0
  134. package/dist/smartsearch-browser.d.ts +5 -0
  135. package/dist/smartsearch-browser.js +116 -0
  136. package/dist/smartsearch-browser.js.map +1 -0
  137. package/dist/smartsearch-ddg.d.ts +2 -0
  138. package/dist/smartsearch-ddg.js +46 -0
  139. package/dist/smartsearch-ddg.js.map +1 -0
  140. package/dist/smartsearch-node.d.ts +6 -0
  141. package/dist/smartsearch-node.js +21 -0
  142. package/dist/smartsearch-node.js.map +1 -0
  143. package/dist/smartsearch-postgresql.d.ts +7 -0
  144. package/dist/smartsearch-postgresql.js +44 -0
  145. package/dist/smartsearch-postgresql.js.map +1 -0
  146. package/dist/smartsearch-python.d.ts +8 -0
  147. package/dist/smartsearch-python.js +87 -0
  148. package/dist/smartsearch-python.js.map +1 -0
  149. package/dist/smartsearch-quality.d.ts +6 -0
  150. package/dist/smartsearch-quality.js +47 -0
  151. package/dist/smartsearch-quality.js.map +1 -0
  152. package/dist/smartsearch-youtube.d.ts +7 -0
  153. package/dist/smartsearch-youtube.js +76 -0
  154. package/dist/smartsearch-youtube.js.map +1 -0
  155. package/dist/smartsearch.d.ts +3 -0
  156. package/dist/smartsearch.js +617 -132
  157. package/dist/smartsearch.js.map +1 -1
  158. package/package.json +8 -5
  159. 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,2 @@
1
+ import type { SmartfetchProvider } from "../provider-types.js";
2
+ export declare const clienProvider: SmartfetchProvider;
@@ -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 absolute = canonicalProductUrl(absolutizeUrl(rawHref, baseUrl), site);
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 url = canonicalProductUrl(absolutizeUrl(anchor.href, baseUrl), site);
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 (weakCommerceProduct(site, detailed.post)) {
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
  };