@aglyn/plugins-ai 1.0.0-beta.288 → 1.0.0-beta.290
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/package.json +17 -17
- package/src/lib/activity/ai-activity-actions.d.ts +3 -2
- package/src/lib/activity/ai-activity-actions.js +3 -0
- package/src/lib/activity/ai-activity-actions.js.map +1 -1
- package/src/lib/components/ai-job-requests.d.ts +2 -0
- package/src/lib/components/ai-job-requests.js +2 -0
- package/src/lib/components/ai-job-requests.js.map +1 -1
- package/src/lib/components/ai-site-build-page.component.js +4 -0
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/ai-site-import-job.component.d.ts +6 -1
- package/src/lib/components/ai-site-import-job.component.js +124 -17
- package/src/lib/components/ai-site-import-job.component.js.map +1 -1
- package/src/lib/components/ai-site-import-start.component.d.ts +6 -0
- package/src/lib/components/ai-site-import-start.component.js +18 -4
- package/src/lib/components/ai-site-import-start.component.js.map +1 -1
- package/src/lib/components/ai-site-start-card.component.js +2 -1
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/jobs/ai-image-reads.d.ts +20 -2
- package/src/lib/jobs/ai-image-reads.js +47 -0
- package/src/lib/jobs/ai-image-reads.js.map +1 -1
- package/src/lib/jobs/ai-job-import-step.d.ts +53 -3
- package/src/lib/jobs/ai-job-import-step.js +386 -64
- package/src/lib/jobs/ai-job-import-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.d.ts +33 -0
- package/src/lib/jobs/ai-job-layout-language.js +75 -1
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.js +8 -3
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.d.ts +29 -0
- package/src/lib/jobs/ai-job-page-language.js +178 -2
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.js +3 -0
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-site-import-document.d.ts +117 -0
- package/src/lib/jobs/ai-site-import-document.js +710 -0
- package/src/lib/jobs/ai-site-import-document.js.map +1 -0
- package/src/lib/jobs/ai-site-import-look.d.ts +112 -0
- package/src/lib/jobs/ai-site-import-look.js +402 -0
- package/src/lib/jobs/ai-site-import-look.js.map +1 -0
- package/src/lib/jobs/ai-site-import-publish.d.ts +41 -0
- package/src/lib/jobs/ai-site-import-publish.js +92 -0
- package/src/lib/jobs/ai-site-import-publish.js.map +1 -0
- package/src/lib/jobs/ai-theme-brand-inputs.d.ts +27 -2
- package/src/lib/jobs/ai-theme-brand-inputs.js +37 -8
- package/src/lib/jobs/ai-theme-brand-inputs.js.map +1 -1
- package/src/lib/layout-language/ai-layout-frame.d.ts +17 -0
- package/src/lib/layout-language/ai-layout-frame.js +73 -2
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
- package/src/lib/layout-language/ai-layout-language.d.ts +43 -0
- package/src/lib/layout-language/ai-layout-language.js +199 -0
- package/src/lib/layout-language/ai-layout-language.js.map +1 -1
- package/src/lib/model/ai-credit-estimate.d.ts +15 -0
- package/src/lib/model/ai-credit-estimate.js +14 -1
- package/src/lib/model/ai-credit-estimate.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +13 -1
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-site-import-view.d.ts +47 -3
- package/src/lib/model/ai-site-import-view.js +122 -20
- package/src/lib/model/ai-site-import-view.js.map +1 -1
- package/src/lib/model/ai-site-import.d.ts +232 -7
- package/src/lib/model/ai-site-import.js +365 -35
- package/src/lib/model/ai-site-import.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +18 -0
- package/src/lib/runtime/ai-doctrine-validators.js +16 -5
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-palette.generated.js +31 -0
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/server/ai-image-reads-route.js +24 -1
- package/src/lib/server/ai-image-reads-route.js.map +1 -1
- package/src/lib/server/ai-jobs-resume.js +7 -1
- package/src/lib/server/ai-jobs-resume.js.map +1 -1
- package/src/lib/site-import/fixtures/aglyn-com-recordings.d.ts +57 -0
- package/src/lib/site-import/fixtures/aglyn-com-recordings.js +172 -0
- package/src/lib/site-import/fixtures/aglyn-com-recordings.js.map +1 -0
- package/src/lib/site-import/site-import-crawl.d.ts +32 -7
- package/src/lib/site-import/site-import-crawl.js +133 -44
- package/src/lib/site-import/site-import-crawl.js.map +1 -1
- package/src/lib/site-import/site-import-edge-server.d.ts +31 -0
- package/src/lib/site-import/site-import-edge-server.js +31 -0
- package/src/lib/site-import/site-import-edge-server.js.map +1 -0
- package/src/lib/site-import/site-import-edge.d.ts +44 -0
- package/src/lib/site-import/site-import-edge.js +108 -0
- package/src/lib/site-import/site-import-edge.js.map +1 -0
- package/src/lib/site-import/site-import-html.d.ts +73 -3
- package/src/lib/site-import/site-import-html.js +332 -45
- package/src/lib/site-import/site-import-html.js.map +1 -1
- package/src/lib/site-import/site-import-images.d.ts +22 -0
- package/src/lib/site-import/site-import-images.js +78 -2
- package/src/lib/site-import/site-import-images.js.map +1 -1
- package/src/lib/site-import/site-import-page.d.ts +44 -2
- package/src/lib/site-import/site-import-page.js +116 -16
- package/src/lib/site-import/site-import-page.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/site-import/site-import-html.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*==========================================\n * WHAT A CRAWLED PAGE SAYS (AGL-3974, parent AGL-3973).\n *\n * \"Bring my site\" reads a person's existing pages for their words, their\n * structure and their pictures. No server HTML parser is a dependency of any\n * lib, and a page's text in order is a small question, so this is a\n * tokenizer: tags and text runs, with the few elements that matter tracked\n * and everything else read through.\n *\n * What never survives, before a single tag is walked:\n * - scripts, styles, iframes, embeds, objects, SVG, canvas, media players,\n * templates and comments, content and all;\n * - any form with a card, bank or payment field, whole;\n * - and a page with a password field is not read at all: it is refused and\n * reported (`loginForm`), because a sign-in page is exactly what a\n * phishing clone wants and nothing a site import needs.\n *\n * The output is DATA. Text from a page reaches a model only fenced as\n * untrusted (`fenceUntrustedText`), never as instructions.\n *=========================================*/\n\n/** One run of a page's content, in the order the page draws it. */\nexport type SiteImportBlock =\n | { kind: 'heading'; level: 1 | 2 | 3 | 4 | 5 | 6; text: string; region: SiteImportRegion }\n | { kind: 'paragraph' | 'list-item' | 'quote'; text: string; region: SiteImportRegion }\n | { kind: 'image'; url: string; alt: string; region: SiteImportRegion }\n\n/** Where on the page a block sat: the header and footer carry business details. */\nexport type SiteImportRegion = 'header' | 'main' | 'footer'\n\nexport interface SiteImportImage {\n /** Absolute https (or http) address. */\n url: string\n alt: string\n /** On the site's own origin; otherwise a CDN or a third party. The importer decides. */\n sameSite: boolean\n}\n\nexport interface SiteImportNavLink {\n text: string\n url: string\n}\n\n/** A form that was kept: what it asks, never what was typed. */\nexport interface SiteImportForm {\n fields: { type: string; name: string }[]\n}\n\nexport interface SiteImportExtract {\n title: string\n description: string\n lang: string | null\n canonical: string | null\n blocks: SiteImportBlock[]\n images: SiteImportImage[]\n navLinks: SiteImportNavLink[]\n /** Same-site page addresses, fragment-free, in first-seen order. */\n links: string[]\n forms: { kept: SiteImportForm[]; removed: number }\n /** True when the page has a password field; nothing else is extracted. */\n loginForm: boolean\n}\n\n/** Ceilings that keep one odd page from becoming the whole inventory. */\nexport const SITE_IMPORT_PAGE_LIMITS = {\n blocks: 400,\n blockChars: 2_000,\n images: 100,\n links: 300,\n navLinks: 60,\n forms: 10,\n fieldsPerForm: 30,\n} as const\n\n/** Elements removed with everything inside them. */\nconst REMOVED_WITH_CONTENT = [\n 'script',\n 'style',\n 'noscript',\n 'template',\n 'iframe',\n 'frame',\n 'frameset',\n 'object',\n 'applet',\n 'svg',\n 'math',\n 'canvas',\n 'video',\n 'audio',\n]\n\n/** Form controls, removed with their content once the forms are summarized. */\nconst FORM_CONTROLS = ['textarea', 'select', 'button']\n\n/** Void elements removed wherever they appear. */\nconst REMOVED_VOID = /<(?:embed|param|source|track|link|base)\\b(?:[^>\"']|\"[^\"]*\"|'[^']*')*>/gi\n\nconst PASSWORD_INPUT = /<input\\b(?:[^>\"']|\"[^\"]*\"|'[^']*')*\\btype\\s*=\\s*[\"']?password\\b/i\n\n/**\n * A field that takes money or a bank detail: an autocomplete hint for a\n * card, or a name or id that says card, CVC, IBAN, routing or payment.\n */\nconst PAYMENT_FIELD =\n /<(?:input|select)\\b(?:[^>\"']|\"[^\"]*\"|'[^']*')*?(?:\\bautocomplete\\s*=\\s*[\"']?\\s*cc-|\\b(?:name|id|data-stripe|data-braintree-name)\\s*=\\s*[\"']?[^\"'\\s>]*(?:card|cc[-_]?(?:num|number|exp|csc|cvv|cvc)|cvv|cvc|csc|iban|routing|account[-_]?number|payment|billing[-_]?card))/i\n\nconst BLOCK_TAGS: Record<string, 'paragraph' | 'list-item' | 'quote'> = {\n p: 'paragraph',\n li: 'list-item',\n dt: 'paragraph',\n dd: 'paragraph',\n td: 'paragraph',\n th: 'paragraph',\n address: 'paragraph',\n pre: 'paragraph',\n figcaption: 'paragraph',\n caption: 'paragraph',\n label: 'paragraph',\n blockquote: 'quote',\n}\n\n/** Tags that end a run of loose text without being a block themselves. */\nconst BREAK_TAGS = new Set([\n 'div',\n 'section',\n 'article',\n 'main',\n 'aside',\n 'header',\n 'footer',\n 'nav',\n 'ul',\n 'ol',\n 'dl',\n 'table',\n 'tr',\n 'tbody',\n 'thead',\n 'figure',\n 'form',\n 'hr',\n 'br',\n 'body',\n])\n\nconst NAMED_ENTITIES: Record<string, string> = {\n amp: '&',\n lt: '<',\n gt: '>',\n quot: '\"',\n apos: \"'\",\n nbsp: ' ',\n rsquo: '’',\n lsquo: '‘',\n rdquo: '”',\n ldquo: '“',\n mdash: '—',\n ndash: '–',\n hellip: '…',\n copy: '©',\n reg: '®',\n trade: '™',\n middot: '·',\n bull: '•',\n eacute: 'é',\n egrave: 'è',\n aacute: 'á',\n ntilde: 'ñ',\n uuml: 'ü',\n ouml: 'ö',\n auml: 'ä',\n deg: '°',\n times: '×',\n}\n\n/** Character references to characters; an unknown name stays as written. */\nexport function decodeHtmlEntities(text: string): string {\n return text.replace(/&(#x[0-9a-f]+|#\\d+|[a-z][a-z0-9]*);/gi, (whole, body: string) => {\n if (body[0] === '#') {\n const code =\n body[1] === 'x' || body[1] === 'X' ? parseInt(body.slice(2), 16) : parseInt(body.slice(1), 10)\n if (!Number.isFinite(code) || code <= 0 || code > 0x10ffff) return ''\n if (code < 32 && code !== 9 && code !== 10 && code !== 13) return ''\n return String.fromCodePoint(code)\n }\n const named = NAMED_ENTITIES[body.toLowerCase()]\n return named ?? whole\n })\n}\n\nconst ATTRIBUTE = /([^\\s=/>\"']+)(?:\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s>\"']+)))?/g\n\n/** A tag's attributes, names lowercased, values decoded. */\nexport function parseAttributes(source: string): Record<string, string> {\n const attributes: Record<string, string> = {}\n for (const match of source.matchAll(ATTRIBUTE)) {\n const name = match[1].toLowerCase()\n if (name in attributes) continue\n attributes[name] = decodeHtmlEntities(match[2] ?? match[3] ?? match[4] ?? '')\n }\n return attributes\n}\n\nconst TAG = /<(\\/?)([a-zA-Z][\\w:-]*)((?:[^>\"']|\"[^\"]*\"|'[^']*')*)>/g\n\n/** Every `<meta>` tag's attributes. */\nfunction metaTags(html: string): Record<string, string>[] {\n const out: Record<string, string>[] = []\n for (const match of html.matchAll(/<meta\\b((?:[^>\"']|\"[^\"]*\"|'[^']*')*)>/gi)) {\n out.push(parseAttributes(match[1]))\n }\n return out\n}\n\n/** The `content` of every `<meta name=\"…\">` with this name. */\nexport function readMetaValues(html: string, name: string): string[] {\n const wanted = name.toLowerCase()\n return metaTags(html)\n .filter((attributes) => (attributes['name'] ?? '').toLowerCase() === wanted)\n .map((attributes) => (attributes['content'] ?? '').trim())\n .filter(Boolean)\n}\n\nfunction collapse(text: string): string {\n return text.replace(/\\s+/g, ' ').trim()\n}\n\nfunction clip(text: string, max: number): string {\n return text.length > max ? `${text.slice(0, max - 1).trimEnd()}…` : text\n}\n\nfunction removeElements(html: string, tags: readonly string[]): string {\n let out = html\n for (const tag of tags) {\n const paired = new RegExp(`<${tag}\\\\b(?:[^>\"']|\"[^\"]*\"|'[^']*')*>[\\\\s\\\\S]*?<\\\\/${tag}\\\\s*>`, 'gi')\n out = out.replace(paired, ' ')\n // One left open runs to the end of the document, as a browser reads it.\n const unclosed = new RegExp(`<${tag}\\\\b(?:[^>\"']|\"[^\"]*\"|'[^']*')*>[\\\\s\\\\S]*$`, 'i')\n out = out.replace(unclosed, ' ')\n }\n return out\n}\n\n/** Forms with a payment field go whole; the rest are summarized, then go. */\nfunction takeForms(html: string): { html: string; kept: SiteImportForm[]; removed: number } {\n const kept: SiteImportForm[] = []\n let removed = 0\n const out = html.replace(/<form\\b(?:[^>\"']|\"[^\"]*\"|'[^']*')*>[\\s\\S]*?(?:<\\/form\\s*>|$)/gi, (form) => {\n if (PAYMENT_FIELD.test(form)) {\n removed += 1\n return ' '\n }\n if (kept.length < SITE_IMPORT_PAGE_LIMITS.forms) {\n const fields: SiteImportForm['fields'] = []\n for (const match of form.matchAll(/<(input|textarea|select)\\b((?:[^>\"']|\"[^\"]*\"|'[^']*')*)>/gi)) {\n const attributes = parseAttributes(match[2])\n const type = match[1].toLowerCase() === 'input' ? (attributes['type'] || 'text').toLowerCase() : match[1].toLowerCase()\n if (type === 'hidden' || type === 'submit' || type === 'button' || type === 'image') continue\n fields.push({ type, name: clip(attributes['name'] || attributes['id'] || '', 80) })\n if (fields.length >= SITE_IMPORT_PAGE_LIMITS.fieldsPerForm) break\n }\n kept.push({ fields })\n }\n return ' '\n })\n return { html: out, kept, removed }\n}\n\n/** `raw` against the page, as an absolute http(s) address without a fragment. */\nexport function resolveSiteUrl(raw: string | undefined, base: string): URL | null {\n const value = String(raw ?? '').trim()\n if (!value || /^(?:javascript|data|mailto|tel|sms|blob|about):/i.test(value)) return null\n try {\n const url = new URL(value, base)\n if (url.protocol !== 'https:' && url.protocol !== 'http:') return null\n url.hash = ''\n return url\n } catch {\n return null\n }\n}\n\n/** The first address a `srcset` offers. */\nfunction firstSrcset(srcset: string | undefined): string | undefined {\n const first = String(srcset ?? '').split(',')[0]?.trim().split(/\\s+/)[0]\n return first || undefined\n}\n\n/**\n * The words, structure, pictures and links of one page.\n *\n * `isSameSite(url)` says whether an address is on the site being imported;\n * only those become `links`, and images are marked by it.\n */\nexport function extractSiteImportPage(\n html: string,\n pageUrl: string,\n isSameSite: (url: URL) => boolean,\n): SiteImportExtract {\n const empty: SiteImportExtract = {\n title: '',\n description: '',\n lang: null,\n canonical: null,\n blocks: [],\n images: [],\n navLinks: [],\n links: [],\n forms: { kept: [], removed: 0 },\n loginForm: false,\n }\n let source = String(html ?? '')\n .replace(/<!--[\\s\\S]*?(?:-->|$)/g, ' ')\n .replace(/<!\\[CDATA\\[[\\s\\S]*?\\]\\]>/g, ' ')\n if (PASSWORD_INPUT.test(source)) return { ...empty, loginForm: true }\n\n source = removeElements(source, REMOVED_WITH_CONTENT).replace(REMOVED_VOID, ' ')\n // Forms next, so a field list is read before the controls go.\n const forms = takeForms(source)\n source = removeElements(forms.html, FORM_CONTROLS)\n\n const metas = metaTags(source)\n const metaContent = (key: string, attribute: 'name' | 'property') =>\n collapse(metas.find((m) => (m[attribute] ?? '').toLowerCase() === key)?.['content'] ?? '')\n const titleMatch = /<title\\b[^>]*>([\\s\\S]*?)<\\/title\\s*>/i.exec(source)\n const title = clip(\n collapse(decodeHtmlEntities(titleMatch?.[1] ?? '')) || metaContent('og:title', 'property'),\n 300,\n )\n const description = clip(\n metaContent('description', 'name') || metaContent('og:description', 'property'),\n 1_000,\n )\n const htmlTag = /<html\\b((?:[^>\"']|\"[^\"]*\"|'[^']*')*)>/i.exec(source)\n const lang = htmlTag ? parseAttributes(htmlTag[1])['lang']?.trim() || null : null\n let canonical: string | null = null\n for (const match of String(html ?? '').matchAll(/<link\\b((?:[^>\"']|\"[^\"]*\"|'[^']*')*)>/gi)) {\n const attributes = parseAttributes(match[1])\n if ((attributes['rel'] ?? '').toLowerCase().split(/\\s+/).includes('canonical')) {\n canonical = resolveSiteUrl(attributes['href'], pageUrl)?.toString() ?? null\n break\n }\n }\n\n const bodyStart = source.search(/<body\\b/i)\n const body = bodyStart >= 0 ? source.slice(bodyStart) : source.replace(/<head\\b[\\s\\S]*?<\\/head\\s*>/i, ' ')\n const hasNav = /<nav\\b/i.test(body)\n\n const blocks: SiteImportBlock[] = []\n const images: SiteImportImage[] = []\n const imageSeen = new Set<string>()\n const navLinks: SiteImportNavLink[] = []\n const links: string[] = []\n const linkSeen = new Set<string>()\n\n let navDepth = 0\n let headerDepth = 0\n let footerDepth = 0\n let current: { kind: 'heading' | 'paragraph' | 'list-item' | 'quote'; level?: number; tag: string } | null = null\n let buffer = ''\n let anchor: { url: URL; text: string } | null = null\n\n const region = (): SiteImportRegion =>\n footerDepth > 0 ? 'footer' : headerDepth > 0 ? 'header' : 'main'\n // A site without a <nav> keeps its menu in the header; its links are the nav.\n const inNav = () => navDepth > 0 || (!hasNav && headerDepth > 0)\n\n const flush = () => {\n const text = clip(collapse(decodeHtmlEntities(buffer)), SITE_IMPORT_PAGE_LIMITS.blockChars)\n buffer = ''\n const kind = current?.kind ?? 'paragraph'\n const level = current?.level\n current = null\n if (!text || navDepth > 0 || blocks.length >= SITE_IMPORT_PAGE_LIMITS.blocks) return\n const last = blocks[blocks.length - 1]\n if (last && last.kind !== 'image' && last.text === text) return\n if (kind === 'heading') {\n blocks.push({ kind, level: (level ?? 2) as 1 | 2 | 3 | 4 | 5 | 6, text, region: region() })\n } else {\n blocks.push({ kind, text, region: region() })\n }\n }\n\n for (const match of body.matchAll(new RegExp(`${TAG.source}|([^<]+)|<`, 'g'))) {\n const text = match[4]\n if (text !== undefined || match[2] === undefined) {\n const run = text ?? '<'\n buffer += run\n if (anchor) anchor.text += run\n continue\n }\n const closing = match[1] === '/'\n const tag = match[2].toLowerCase()\n const attributes = closing ? {} : parseAttributes(match[3] ?? '')\n\n if (tag === 'a') {\n if (closing) {\n if (anchor) {\n const label = clip(collapse(decodeHtmlEntities(anchor.text)), 120)\n if (inNav() && label && navLinks.length < SITE_IMPORT_PAGE_LIMITS.navLinks) {\n navLinks.push({ text: label, url: anchor.url.toString() })\n }\n anchor = null\n }\n continue\n }\n const url = resolveSiteUrl(attributes['href'], pageUrl)\n anchor = url ? { url, text: '' } : null\n if (url && isSameSite(url) && links.length < SITE_IMPORT_PAGE_LIMITS.links) {\n const key = url.toString()\n if (!linkSeen.has(key)) {\n linkSeen.add(key)\n links.push(key)\n }\n }\n continue\n }\n\n if (tag === 'img' && !closing) {\n const url = resolveSiteUrl(\n attributes['src'] || attributes['data-src'] || firstSrcset(attributes['srcset'] || attributes['data-srcset']),\n pageUrl,\n )\n if (url && images.length < SITE_IMPORT_PAGE_LIMITS.images) {\n const key = url.toString()\n const alt = clip(collapse(attributes['alt'] ?? ''), 300)\n if (!imageSeen.has(key)) {\n imageSeen.add(key)\n images.push({ url: key, alt, sameSite: isSameSite(url) })\n if (navDepth === 0) {\n flush()\n if (blocks.length < SITE_IMPORT_PAGE_LIMITS.blocks) {\n blocks.push({ kind: 'image', url: key, alt, region: region() })\n }\n }\n }\n }\n continue\n }\n\n if (tag === 'nav' || tag === 'header' || tag === 'footer') {\n flush()\n const step = closing ? -1 : 1\n if (tag === 'nav') navDepth = Math.max(0, navDepth + step)\n if (tag === 'header') headerDepth = Math.max(0, headerDepth + step)\n if (tag === 'footer') footerDepth = Math.max(0, footerDepth + step)\n continue\n }\n\n const heading = /^h([1-6])$/.exec(tag)\n if (heading || BLOCK_TAGS[tag]) {\n if (closing) {\n if (current?.tag === tag) flush()\n continue\n }\n flush()\n current = heading\n ? { kind: 'heading', level: Number(heading[1]), tag }\n : { kind: BLOCK_TAGS[tag], tag }\n continue\n }\n\n if (BREAK_TAGS.has(tag)) {\n if (tag === 'br' && current) {\n buffer += ' '\n continue\n }\n flush()\n }\n }\n flush()\n\n return {\n title,\n description,\n lang,\n canonical,\n blocks,\n images,\n navLinks,\n links,\n forms: { kept: forms.kept, removed: forms.removed },\n loginForm: false,\n }\n}\n"],"names":["SITE_IMPORT_PAGE_LIMITS","blocks","blockChars","images","links","navLinks","forms","fieldsPerForm","REMOVED_WITH_CONTENT","FORM_CONTROLS","REMOVED_VOID","PASSWORD_INPUT","PAYMENT_FIELD","BLOCK_TAGS","p","li","dt","dd","td","th","address","pre","figcaption","caption","label","blockquote","BREAK_TAGS","Set","NAMED_ENTITIES","amp","lt","gt","quot","apos","nbsp","rsquo","lsquo","rdquo","ldquo","mdash","ndash","hellip","copy","reg","trade","middot","bull","eacute","egrave","aacute","ntilde","uuml","ouml","auml","deg","times","decodeHtmlEntities","text","replace","whole","body","code","parseInt","slice","Number","isFinite","String","fromCodePoint","named","toLowerCase","ATTRIBUTE","parseAttributes","source","attributes","match","matchAll","name","TAG","metaTags","html","out","push","readMetaValues","wanted","filter","map","trim","Boolean","collapse","clip","max","length","trimEnd","removeElements","tags","tag","paired","RegExp","unclosed","takeForms","kept","removed","form","test","fields","type","resolveSiteUrl","raw","base","value","url","URL","protocol","hash","firstSrcset","srcset","first","split","undefined","extractSiteImportPage","pageUrl","isSameSite","empty","title","description","lang","canonical","loginForm","metas","metaContent","key","attribute","find","m","titleMatch","exec","htmlTag","includes","toString","bodyStart","search","hasNav","imageSeen","linkSeen","navDepth","headerDepth","footerDepth","current","buffer","anchor","region","inNav","flush","kind","level","last","run","closing","has","add","alt","sameSite","step","Math","heading"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;2CAmB2C,GAE3C,iEAAiE;AA0CjE,uEAAuE,GACvE,OAAO,MAAMA,0BAA0B;IACrCC,QAAQ;IACRC,YAAY;IACZC,QAAQ;IACRC,OAAO;IACPC,UAAU;IACVC,OAAO;IACPC,eAAe;AACjB,EAAU;AAEV,kDAAkD,GAClD,MAAMC,uBAAuB;IAC3B;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD;AAED,6EAA6E,GAC7E,MAAMC,gBAAgB;IAAC;IAAY;IAAU;CAAS;AAEtD,gDAAgD,GAChD,MAAMC,eAAe;AAErB,MAAMC,iBAAiB;AAEvB;;;CAGC,GACD,MAAMC,gBACJ;AAEF,MAAMC,aAAkE;IACtEC,GAAG;IACHC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,IAAI;IACJC,SAAS;IACTC,KAAK;IACLC,YAAY;IACZC,SAAS;IACTC,OAAO;IACPC,YAAY;AACd;AAEA,wEAAwE,GACxE,MAAMC,aAAa,IAAIC,IAAI;IACzB;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD;AAED,MAAMC,iBAAyC;IAC7CC,KAAK;IACLC,IAAI;IACJC,IAAI;IACJC,MAAM;IACNC,MAAM;IACNC,MAAM;IACNC,OAAO;IACPC,OAAO;IACPC,OAAO;IACPC,OAAO;IACPC,OAAO;IACPC,OAAO;IACPC,QAAQ;IACRC,MAAM;IACNC,KAAK;IACLC,OAAO;IACPC,QAAQ;IACRC,MAAM;IACNC,QAAQ;IACRC,QAAQ;IACRC,QAAQ;IACRC,QAAQ;IACRC,MAAM;IACNC,MAAM;IACNC,MAAM;IACNC,KAAK;IACLC,OAAO;AACT;AAEA,0EAA0E,GAC1E,OAAO,SAASC,mBAAmBC,IAAY;IAC7C,OAAOA,KAAKC,OAAO,CAAC,yCAAyC,CAACC,OAAOC;QACnE,IAAIA,IAAI,CAAC,EAAE,KAAK,KAAK;YACnB,MAAMC,OACJD,IAAI,CAAC,EAAE,KAAK,OAAOA,IAAI,CAAC,EAAE,KAAK,MAAME,SAASF,KAAKG,KAAK,CAAC,IAAI,MAAMD,SAASF,KAAKG,KAAK,CAAC,IAAI;YAC7F,IAAI,CAACC,OAAOC,QAAQ,CAACJ,SAASA,QAAQ,KAAKA,OAAO,UAAU,OAAO;YACnE,IAAIA,OAAO,MAAMA,SAAS,KAAKA,SAAS,MAAMA,SAAS,IAAI,OAAO;YAClE,OAAOK,OAAOC,aAAa,CAACN;QAC9B;QACA,MAAMO,QAAQxC,cAAc,CAACgC,KAAKS,WAAW,GAAG;QAChD,OAAOD,gBAAAA,QAAST;IAClB;AACF;AAEA,MAAMW,YAAY;AAElB,0DAA0D,GAC1D,OAAO,SAASC,gBAAgBC,MAAc;IAC5C,MAAMC,aAAqC,CAAC;IAC5C,KAAK,MAAMC,SAASF,OAAOG,QAAQ,CAACL,WAAY;YAGRI,MAAAA,OAAAA;QAFtC,MAAME,OAAOF,KAAK,CAAC,EAAE,CAACL,WAAW;QACjC,IAAIO,QAAQH,YAAY;QACxBA,UAAU,CAACG,KAAK,GAAGpB,oBAAmBkB,QAAAA,SAAAA,UAAAA,KAAK,CAAC,EAAE,YAARA,UAAYA,KAAK,CAAC,EAAE,YAApBA,QAAwBA,KAAK,CAAC,EAAE,YAAhCA,OAAoC;IAC5E;IACA,OAAOD;AACT;AAEA,MAAMI,MAAM;AAEZ,qCAAqC,GACrC,SAASC,SAASC,IAAY;IAC5B,MAAMC,MAAgC,EAAE;IACxC,KAAK,MAAMN,SAASK,KAAKJ,QAAQ,CAAC,2CAA4C;QAC5EK,IAAIC,IAAI,CAACV,gBAAgBG,KAAK,CAAC,EAAE;IACnC;IACA,OAAOM;AACT;AAEA,6DAA6D,GAC7D,OAAO,SAASE,eAAeH,IAAY,EAAEH,IAAY;IACvD,MAAMO,SAASP,KAAKP,WAAW;IAC/B,OAAOS,SAASC,MACbK,MAAM,CAAC,CAACX;YAAgBA;eAAD,EAACA,mBAAAA,UAAU,CAAC,OAAO,YAAlBA,mBAAsB,IAAIJ,WAAW,OAAOc;OACpEE,GAAG,CAAC,CAACZ;YAAgBA;eAAD,EAACA,sBAAAA,UAAU,CAAC,UAAU,YAArBA,sBAAyB,IAAIa,IAAI;OACtDF,MAAM,CAACG;AACZ;AAEA,SAASC,SAAS/B,IAAY;IAC5B,OAAOA,KAAKC,OAAO,CAAC,QAAQ,KAAK4B,IAAI;AACvC;AAEA,SAASG,KAAKhC,IAAY,EAAEiC,GAAW;IACrC,OAAOjC,KAAKkC,MAAM,GAAGD,MAAM,GAAGjC,KAAKM,KAAK,CAAC,GAAG2B,MAAM,GAAGE,OAAO,GAAG,CAAC,CAAC,GAAGnC;AACtE;AAEA,SAASoC,eAAed,IAAY,EAAEe,IAAuB;IAC3D,IAAId,MAAMD;IACV,KAAK,MAAMgB,OAAOD,KAAM;QACtB,MAAME,SAAS,IAAIC,OAAO,CAAC,CAAC,EAAEF,IAAI,6CAA6C,EAAEA,IAAI,KAAK,CAAC,EAAE;QAC7Ff,MAAMA,IAAItB,OAAO,CAACsC,QAAQ;QAC1B,wEAAwE;QACxE,MAAME,WAAW,IAAID,OAAO,CAAC,CAAC,EAAEF,IAAI,yCAAyC,CAAC,EAAE;QAChFf,MAAMA,IAAItB,OAAO,CAACwC,UAAU;IAC9B;IACA,OAAOlB;AACT;AAEA,2EAA2E,GAC3E,SAASmB,UAAUpB,IAAY;IAC7B,MAAMqB,OAAyB,EAAE;IACjC,IAAIC,UAAU;IACd,MAAMrB,MAAMD,KAAKrB,OAAO,CAAC,kEAAkE,CAAC4C;QAC1F,IAAI1F,cAAc2F,IAAI,CAACD,OAAO;YAC5BD,WAAW;YACX,OAAO;QACT;QACA,IAAID,KAAKT,MAAM,GAAG3F,wBAAwBM,KAAK,EAAE;YAC/C,MAAMkG,SAAmC,EAAE;YAC3C,KAAK,MAAM9B,SAAS4B,KAAK3B,QAAQ,CAAC,8DAA+D;gBAC/F,MAAMF,aAAaF,gBAAgBG,KAAK,CAAC,EAAE;gBAC3C,MAAM+B,OAAO/B,KAAK,CAAC,EAAE,CAACL,WAAW,OAAO,UAAU,AAACI,CAAAA,UAAU,CAAC,OAAO,IAAI,MAAK,EAAGJ,WAAW,KAAKK,KAAK,CAAC,EAAE,CAACL,WAAW;gBACrH,IAAIoC,SAAS,YAAYA,SAAS,YAAYA,SAAS,YAAYA,SAAS,SAAS;gBACrFD,OAAOvB,IAAI,CAAC;oBAAEwB;oBAAM7B,MAAMa,KAAKhB,UAAU,CAAC,OAAO,IAAIA,UAAU,CAAC,KAAK,IAAI,IAAI;gBAAI;gBACjF,IAAI+B,OAAOb,MAAM,IAAI3F,wBAAwBO,aAAa,EAAE;YAC9D;YACA6F,KAAKnB,IAAI,CAAC;gBAAEuB;YAAO;QACrB;QACA,OAAO;IACT;IACA,OAAO;QAAEzB,MAAMC;QAAKoB;QAAMC;IAAQ;AACpC;AAEA,+EAA+E,GAC/E,OAAO,SAASK,eAAeC,GAAuB,EAAEC,IAAY;IAClE,MAAMC,QAAQ3C,OAAOyC,cAAAA,MAAO,IAAIrB,IAAI;IACpC,IAAI,CAACuB,SAAS,mDAAmDN,IAAI,CAACM,QAAQ,OAAO;IACrF,IAAI;QACF,MAAMC,MAAM,IAAIC,IAAIF,OAAOD;QAC3B,IAAIE,IAAIE,QAAQ,KAAK,YAAYF,IAAIE,QAAQ,KAAK,SAAS,OAAO;QAClEF,IAAIG,IAAI,GAAG;QACX,OAAOH;IACT,EAAE,eAAM;QACN,OAAO;IACT;AACF;AAEA,yCAAyC,GACzC,SAASI,YAAYC,MAA0B;QAC/BjD;IAAd,MAAMkD,SAAQlD,iBAAAA,OAAOiD,iBAAAA,SAAU,IAAIE,KAAK,CAAC,IAAI,CAAC,EAAE,qBAAlCnD,eAAoCoB,IAAI,GAAG+B,KAAK,CAAC,MAAM,CAAC,EAAE;IACxE,OAAOD,SAASE;AAClB;AAEA;;;;;CAKC,GACD,OAAO,SAASC,sBACdxC,IAAY,EACZyC,OAAe,EACfC,UAAiC;;QAqCVlD;IAnCvB,MAAMmD,QAA2B;QAC/BC,OAAO;QACPC,aAAa;QACbC,MAAM;QACNC,WAAW;QACX7H,QAAQ,EAAE;QACVE,QAAQ,EAAE;QACVE,UAAU,EAAE;QACZD,OAAO,EAAE;QACTE,OAAO;YAAE8F,MAAM,EAAE;YAAEC,SAAS;QAAE;QAC9B0B,WAAW;IACb;IACA,IAAIvD,SAASN,OAAOa,eAAAA,OAAQ,IACzBrB,OAAO,CAAC,0BAA0B,KAClCA,OAAO,CAAC,6BAA6B;IACxC,IAAI/C,eAAe4F,IAAI,CAAC/B,SAAS,OAAO,aAAKkD;QAAOK,WAAW;;IAE/DvD,SAASqB,eAAerB,QAAQhE,sBAAsBkD,OAAO,CAAChD,cAAc;IAC5E,8DAA8D;IAC9D,MAAMJ,QAAQ6F,UAAU3B;IACxBA,SAASqB,eAAevF,MAAMyE,IAAI,EAAEtE;IAEpC,MAAMuH,QAAQlD,SAASN;IACvB,MAAMyD,cAAc,CAACC,KAAaC;;YACvBH;eAATxC,kBAASwC,cAAAA,MAAMI,IAAI,CAAC,CAACC;gBAAOA;mBAAD,EAACA,eAAAA,CAAC,CAACF,UAAU,YAAZE,eAAgB,IAAIhE,WAAW,OAAO6D;+BAAzDF,WAA+D,CAAC,UAAU,mBAAI;;IACzF,MAAMM,aAAa,wCAAwCC,IAAI,CAAC/D;IAChE,MAAMmD,QAAQlC,KACZD,SAAShC,2BAAmB8E,8BAAAA,UAAY,CAAC,EAAE,mBAAI,QAAQL,YAAY,YAAY,aAC/E;IAEF,MAAML,cAAcnC,KAClBwC,YAAY,eAAe,WAAWA,YAAY,kBAAkB,aACpE;IAEF,MAAMO,UAAU,yCAAyCD,IAAI,CAAC/D;IAC9D,MAAMqD,OAAOW,UAAUjE,EAAAA,wBAAAA,gBAAgBiE,OAAO,CAAC,EAAE,CAAC,CAAC,OAAO,qBAAnCjE,sBAAqCe,IAAI,OAAM,OAAO;IAC7E,IAAIwC,YAA2B;IAC/B,KAAK,MAAMpD,SAASR,OAAOa,eAAAA,OAAQ,IAAIJ,QAAQ,CAAC,2CAA4C;YAErFF;QADL,MAAMA,aAAaF,gBAAgBG,KAAK,CAAC,EAAE;QAC3C,IAAI,EAACD,kBAAAA,UAAU,CAAC,MAAM,YAAjBA,kBAAqB,IAAIJ,WAAW,GAAGgD,KAAK,CAAC,OAAOoB,QAAQ,CAAC,cAAc;;gBAClE/B;YAAZoB,sBAAYpB,kBAAAA,eAAejC,UAAU,CAAC,OAAO,EAAE+C,6BAAnCd,gBAA6CgC,QAAQ,sBAAM;YACvE;QACF;IACF;IAEA,MAAMC,YAAYnE,OAAOoE,MAAM,CAAC;IAChC,MAAMhF,OAAO+E,aAAa,IAAInE,OAAOT,KAAK,CAAC4E,aAAanE,OAAOd,OAAO,CAAC,+BAA+B;IACtG,MAAMmF,SAAS,UAAUtC,IAAI,CAAC3C;IAE9B,MAAM3D,SAA4B,EAAE;IACpC,MAAME,SAA4B,EAAE;IACpC,MAAM2I,YAAY,IAAInH;IACtB,MAAMtB,WAAgC,EAAE;IACxC,MAAMD,QAAkB,EAAE;IAC1B,MAAM2I,WAAW,IAAIpH;IAErB,IAAIqH,WAAW;IACf,IAAIC,cAAc;IAClB,IAAIC,cAAc;IAClB,IAAIC,UAAyG;IAC7G,IAAIC,SAAS;IACb,IAAIC,SAA4C;IAEhD,MAAMC,SAAS,IACbJ,cAAc,IAAI,WAAWD,cAAc,IAAI,WAAW;IAC5D,8EAA8E;IAC9E,MAAMM,QAAQ,IAAMP,WAAW,KAAM,CAACH,UAAUI,cAAc;IAE9D,MAAMO,QAAQ;;QACZ,MAAM/F,OAAOgC,KAAKD,SAAShC,mBAAmB4F,UAAUpJ,wBAAwBE,UAAU;QAC1FkJ,SAAS;QACT,MAAMK,eAAON,2BAAAA,QAASM,IAAI,mBAAI;QAC9B,MAAMC,QAAQP,2BAAAA,QAASO,KAAK;QAC5BP,UAAU;QACV,IAAI,CAAC1F,QAAQuF,WAAW,KAAK/I,OAAO0F,MAAM,IAAI3F,wBAAwBC,MAAM,EAAE;QAC9E,MAAM0J,OAAO1J,MAAM,CAACA,OAAO0F,MAAM,GAAG,EAAE;QACtC,IAAIgE,QAAQA,KAAKF,IAAI,KAAK,WAAWE,KAAKlG,IAAI,KAAKA,MAAM;QACzD,IAAIgG,SAAS,WAAW;YACtBxJ,OAAOgF,IAAI,CAAC;gBAAEwE;gBAAMC,KAAK,EAAGA,gBAAAA,QAAS;gBAA6BjG;gBAAM6F,QAAQA;YAAS;QAC3F,OAAO;YACLrJ,OAAOgF,IAAI,CAAC;gBAAEwE;gBAAMhG;gBAAM6F,QAAQA;YAAS;QAC7C;IACF;IAEA,KAAK,MAAM5E,SAASd,KAAKe,QAAQ,CAAC,IAAIsB,OAAO,GAAGpB,IAAIL,MAAM,CAAC,UAAU,CAAC,EAAE,MAAO;YAU3BE;QATlD,MAAMjB,OAAOiB,KAAK,CAAC,EAAE;QACrB,IAAIjB,SAAS6D,aAAa5C,KAAK,CAAC,EAAE,KAAK4C,WAAW;YAChD,MAAMsC,MAAMnG,eAAAA,OAAQ;YACpB2F,UAAUQ;YACV,IAAIP,QAAQA,OAAO5F,IAAI,IAAImG;YAC3B;QACF;QACA,MAAMC,UAAUnF,KAAK,CAAC,EAAE,KAAK;QAC7B,MAAMqB,MAAMrB,KAAK,CAAC,EAAE,CAACL,WAAW;QAChC,MAAMI,aAAaoF,UAAU,CAAC,IAAItF,iBAAgBG,UAAAA,KAAK,CAAC,EAAE,YAARA,UAAY;QAE9D,IAAIqB,QAAQ,KAAK;YACf,IAAI8D,SAAS;gBACX,IAAIR,QAAQ;oBACV,MAAM7H,QAAQiE,KAAKD,SAAShC,mBAAmB6F,OAAO5F,IAAI,IAAI;oBAC9D,IAAI8F,WAAW/H,SAASnB,SAASsF,MAAM,GAAG3F,wBAAwBK,QAAQ,EAAE;wBAC1EA,SAAS4E,IAAI,CAAC;4BAAExB,MAAMjC;4BAAOsF,KAAKuC,OAAOvC,GAAG,CAAC4B,QAAQ;wBAAG;oBAC1D;oBACAW,SAAS;gBACX;gBACA;YACF;YACA,MAAMvC,MAAMJ,eAAejC,UAAU,CAAC,OAAO,EAAE+C;YAC/C6B,SAASvC,MAAM;gBAAEA;gBAAKrD,MAAM;YAAG,IAAI;YACnC,IAAIqD,OAAOW,WAAWX,QAAQ1G,MAAMuF,MAAM,GAAG3F,wBAAwBI,KAAK,EAAE;gBAC1E,MAAM8H,MAAMpB,IAAI4B,QAAQ;gBACxB,IAAI,CAACK,SAASe,GAAG,CAAC5B,MAAM;oBACtBa,SAASgB,GAAG,CAAC7B;oBACb9H,MAAM6E,IAAI,CAACiD;gBACb;YACF;YACA;QACF;QAEA,IAAInC,QAAQ,SAAS,CAAC8D,SAAS;YAC7B,MAAM/C,MAAMJ,eACVjC,UAAU,CAAC,MAAM,IAAIA,UAAU,CAAC,WAAW,IAAIyC,YAAYzC,UAAU,CAAC,SAAS,IAAIA,UAAU,CAAC,cAAc,GAC5G+C;YAEF,IAAIV,OAAO3G,OAAOwF,MAAM,GAAG3F,wBAAwBG,MAAM,EAAE;oBAE/BsE;gBAD1B,MAAMyD,MAAMpB,IAAI4B,QAAQ;gBACxB,MAAMsB,MAAMvE,KAAKD,UAASf,kBAAAA,UAAU,CAAC,MAAM,YAAjBA,kBAAqB,KAAK;gBACpD,IAAI,CAACqE,UAAUgB,GAAG,CAAC5B,MAAM;oBACvBY,UAAUiB,GAAG,CAAC7B;oBACd/H,OAAO8E,IAAI,CAAC;wBAAE6B,KAAKoB;wBAAK8B;wBAAKC,UAAUxC,WAAWX;oBAAK;oBACvD,IAAIkC,aAAa,GAAG;wBAClBQ;wBACA,IAAIvJ,OAAO0F,MAAM,GAAG3F,wBAAwBC,MAAM,EAAE;4BAClDA,OAAOgF,IAAI,CAAC;gCAAEwE,MAAM;gCAAS3C,KAAKoB;gCAAK8B;gCAAKV,QAAQA;4BAAS;wBAC/D;oBACF;gBACF;YACF;YACA;QACF;QAEA,IAAIvD,QAAQ,SAASA,QAAQ,YAAYA,QAAQ,UAAU;YACzDyD;YACA,MAAMU,OAAOL,UAAU,CAAC,IAAI;YAC5B,IAAI9D,QAAQ,OAAOiD,WAAWmB,KAAKzE,GAAG,CAAC,GAAGsD,WAAWkB;YACrD,IAAInE,QAAQ,UAAUkD,cAAckB,KAAKzE,GAAG,CAAC,GAAGuD,cAAciB;YAC9D,IAAInE,QAAQ,UAAUmD,cAAciB,KAAKzE,GAAG,CAAC,GAAGwD,cAAcgB;YAC9D;QACF;QAEA,MAAME,UAAU,aAAa7B,IAAI,CAACxC;QAClC,IAAIqE,WAAWvJ,UAAU,CAACkF,IAAI,EAAE;YAC9B,IAAI8D,SAAS;gBACX,IAAIV,CAAAA,2BAAAA,QAASpD,GAAG,MAAKA,KAAKyD;gBAC1B;YACF;YACAA;YACAL,UAAUiB,UACN;gBAAEX,MAAM;gBAAWC,OAAO1F,OAAOoG,OAAO,CAAC,EAAE;gBAAGrE;YAAI,IAClD;gBAAE0D,MAAM5I,UAAU,CAACkF,IAAI;gBAAEA;YAAI;YACjC;QACF;QAEA,IAAIrE,WAAWoI,GAAG,CAAC/D,MAAM;YACvB,IAAIA,QAAQ,QAAQoD,SAAS;gBAC3BC,UAAU;gBACV;YACF;YACAI;QACF;IACF;IACAA;IAEA,OAAO;QACL7B;QACAC;QACAC;QACAC;QACA7H;QACAE;QACAE;QACAD;QACAE,OAAO;YAAE8F,MAAM9F,MAAM8F,IAAI;YAAEC,SAAS/F,MAAM+F,OAAO;QAAC;QAClD0B,WAAW;IACb;AACF"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/site-import/site-import-html.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*==========================================\n * WHAT A CRAWLED PAGE SAYS (AGL-3974, parent AGL-3973).\n *\n * \"Bring my site\" reads a person's existing pages for their words, their\n * structure and their pictures. No server HTML parser is a dependency of any\n * lib, and a page's text in order is a small question, so this is a\n * tokenizer: tags and text runs, with the few elements that matter tracked\n * and everything else read through.\n *\n * What never survives, before a single tag is walked:\n * - scripts, styles, iframes, embeds, objects, SVG, canvas, media players,\n * templates and comments, content and all;\n * - any form with a card, bank or payment field, whole;\n * - and a page with a password field is not read at all: it is refused and\n * reported (`loginForm`), because a sign-in page is exactly what a\n * phishing clone wants and nothing a site import needs.\n *\n * The output is DATA. Text from a page reaches a model only fenced as\n * untrusted (`fenceUntrustedText`), never as instructions.\n *\n * What a page is made of is kept as the page shows it (AGL-4083): words that\n * sit in tags side by side stay apart (\"Sign in\" and \"Start free\", never\n * \"Sign inStart free\"); a link is a block of its own, `mailto:` and `tel:`\n * ones too, so an address or a phone number survives; a menu entry is named\n * by its first line, not by its mega-menu description; a `<picture>`'s\n * source stands in for an `<img>` with no address; and the head's share card,\n * icons, site name and Organization structured data come along for the\n * header, the footer and the site's own facts.\n *=========================================*/\n\n/** One run of a page's content, in the order the page draws it. */\nexport type SiteImportBlock =\n | { kind: 'heading'; level: 1 | 2 | 3 | 4 | 5 | 6; text: string; region: SiteImportRegion }\n | { kind: 'paragraph' | 'list-item' | 'quote'; text: string; region: SiteImportRegion }\n | { kind: 'image'; url: string; alt: string; region: SiteImportRegion }\n /**\n * A link the page shows outside its menus (AGL-4083): `href` is an absolute\n * http(s) address, a `mailto:` or a `tel:`. `inline` when its words are\n * already in the block before it (a link inside a sentence); a link on its\n * own (a button, an email address in a card) is only this block.\n */\n | { kind: 'link'; text: string; href: string; region: SiteImportRegion; inline?: true }\n\n/** Where on the page a block sat: the header and footer carry business details. */\nexport type SiteImportRegion = 'header' | 'main' | 'footer'\n\nexport interface SiteImportImage {\n /** Absolute https (or http) address. */\n url: string\n alt: string\n /** On the site's own origin; otherwise a CDN or a third party. The importer decides. */\n sameSite: boolean\n /** Where on the page it sat, its menus included (AGL-4083): a header's picture is often the logo. */\n region: SiteImportRegion\n}\n\n/** A `<link rel=\"icon\">` or `apple-touch-icon` of the page's head (AGL-4083). */\nexport interface SiteImportIcon {\n url: string\n rel: 'icon' | 'apple-touch-icon'\n /** The larger side its `sizes` names, or `null` for none or `any`. */\n size: number | null\n /** Its declared type, as written. */\n type: string | null\n}\n\n/**\n * The link to the home page in a page's header (AGL-4083): what its brand is\n * drawn with. `logo` is the first picture inside it; `text` its words, which\n * are the site's name when the header prints one.\n */\nexport interface SiteImportBrandMark {\n logo: { url: string; alt: string } | null\n text: string\n region: SiteImportRegion\n}\n\n/**\n * The page's Organization (or LocalBusiness) structured data (AGL-4083), as\n * written: the site's own statement of its name, address, phone, email and\n * profiles. Every field is the empty string, or an empty list, when absent.\n */\nexport interface SiteImportOrganization {\n name: string\n description: string\n url: string\n logo: string\n telephone: string\n email: string\n /** One line: street, town, region and postal code, country. */\n address: string\n sameAs: string[]\n}\n\nexport interface SiteImportNavLink {\n text: string\n url: string\n}\n\n/** A form that was kept: what it asks, never what was typed. */\nexport interface SiteImportForm {\n fields: { type: string; name: string }[]\n}\n\nexport interface SiteImportExtract {\n title: string\n description: string\n lang: string | null\n canonical: string | null\n /** The share card, `og:image` (or `twitter:image`), as an absolute address (AGL-4083). */\n ogImage: string | null\n /** The site's own name, `og:site_name` (or `application-name`); empty when absent. */\n siteName: string\n icons: SiteImportIcon[]\n brand: SiteImportBrandMark | null\n organization: SiteImportOrganization | null\n blocks: SiteImportBlock[]\n images: SiteImportImage[]\n navLinks: SiteImportNavLink[]\n /** Same-site page addresses, fragment-free, in first-seen order. */\n links: string[]\n forms: { kept: SiteImportForm[]; removed: number }\n /** True when the page has a password field; nothing else is extracted. */\n loginForm: boolean\n}\n\n/** Ceilings that keep one odd page from becoming the whole inventory. */\nexport const SITE_IMPORT_PAGE_LIMITS = {\n /** A pricing page's plan table runs past 400 (AGL-4083); a wave's read keeps fewer, under its own budget. */\n blocks: 2_000,\n /** A legal page's paragraph runs past 2,000 characters. */\n blockChars: 10_000,\n images: 100,\n links: 300,\n /** A mega-menu's entries and the footer's columns together. */\n navLinks: 150,\n forms: 10,\n fieldsPerForm: 30,\n icons: 12,\n} as const\n\n/** Elements removed with everything inside them. */\nconst REMOVED_WITH_CONTENT = [\n 'script',\n 'style',\n 'noscript',\n 'template',\n 'iframe',\n 'frame',\n 'frameset',\n 'object',\n 'applet',\n 'svg',\n 'math',\n 'canvas',\n 'video',\n 'audio',\n]\n\n/** Form controls, removed with their content once the forms are summarized. */\nconst FORM_CONTROLS = ['textarea', 'select', 'button']\n\n/** Void elements removed wherever they appear. */\nconst REMOVED_VOID = /<(?:embed|param|source|track|link|base)\\b(?:[^>\"']|\"[^\"]*\"|'[^']*')*>/gi\n\nconst PASSWORD_INPUT = /<input\\b(?:[^>\"']|\"[^\"]*\"|'[^']*')*\\btype\\s*=\\s*[\"']?password\\b/i\n\n/**\n * A field that takes money or a bank detail: an autocomplete hint for a\n * card, or a name or id that says card, CVC, IBAN, routing or payment.\n */\nconst PAYMENT_FIELD =\n /<(?:input|select)\\b(?:[^>\"']|\"[^\"]*\"|'[^']*')*?(?:\\bautocomplete\\s*=\\s*[\"']?\\s*cc-|\\b(?:name|id|data-stripe|data-braintree-name)\\s*=\\s*[\"']?[^\"'\\s>]*(?:card|cc[-_]?(?:num|number|exp|csc|cvv|cvc)|cvv|cvc|csc|iban|routing|account[-_]?number|payment|billing[-_]?card))/i\n\nconst BLOCK_TAGS: Record<string, 'paragraph' | 'list-item' | 'quote'> = {\n p: 'paragraph',\n li: 'list-item',\n dt: 'paragraph',\n dd: 'paragraph',\n td: 'paragraph',\n th: 'paragraph',\n address: 'paragraph',\n pre: 'paragraph',\n figcaption: 'paragraph',\n caption: 'paragraph',\n label: 'paragraph',\n blockquote: 'quote',\n}\n\n/** Tags that end a run of loose text without being a block themselves. */\nconst BREAK_TAGS = new Set([\n 'div',\n 'section',\n 'article',\n 'main',\n 'aside',\n 'header',\n 'footer',\n 'nav',\n 'ul',\n 'ol',\n 'dl',\n 'table',\n 'tr',\n 'tbody',\n 'thead',\n 'figure',\n 'form',\n 'hr',\n 'br',\n 'body',\n])\n\n/**\n * Tags a browser lays out apart from the words beside them (AGL-4083): two\n * links, or a link and a picture, side by side are two runs of words, never\n * one (\"Sign in\" and \"Start free\", not \"Sign inStart free\"). Every other tag\n * inside a block styles words where they are (a `<span>` or a `<strong>`\n * mid-sentence) and adds no space; block-level tags already end their block.\n */\nconst RUN_BOUNDARY = new Set(['a', 'img', 'button', 'input', 'select', 'textarea', 'picture', 'video', 'audio', 'iframe', 'object', 'embed', 'canvas', 'svg', 'meter', 'progress', 'output'])\n\n/** Where one run of words ends and the next begins, resolved when a block is written. */\nconst RUN_BREAK = '\\u0001'\n\n/**\n * The runs of a block joined as a reader sees them: a space between two\n * runs, except before closing punctuation or after an opening one, where\n * the page wrote none (\"<span>Austin</span>, Texas\" is \"Austin, Texas\").\n */\nconst RUN_BREAKS = new RegExp(`[\\\\s${RUN_BREAK}]*${RUN_BREAK}[\\\\s${RUN_BREAK}]*`, 'g')\n\nfunction joinRuns(text: string): string {\n if (!text.includes(RUN_BREAK)) return text\n return text.replace(RUN_BREAKS, (match: string, offset: number, whole: string) => {\n if (/\\s/.test(match)) return ' '\n const before = whole[offset - 1] ?? ''\n const after = whole[offset + match.length] ?? ''\n if (!before || !after) return ''\n if (/[.,;:!?%)\\]}\\u2019\\u201d\\u00bb]/.test(after) || /[([{\\u2018\\u201c\\u00ab]/.test(before)) return ''\n // A straight quote opens after a space or a bracket, and closes before one.\n if (/[\"']/.test(before) && /^$|[\\s([{]/.test(whole[offset - 2] ?? '')) return ''\n if (/[\"']/.test(after) && /^$|[\\s.,;:!?)\\]}]/.test(whole[offset + match.length + 1] ?? '')) return ''\n return ' '\n })\n}\n\nconst NAMED_ENTITIES: Record<string, string> = {\n amp: '&',\n lt: '<',\n gt: '>',\n quot: '\"',\n apos: \"'\",\n nbsp: ' ',\n rsquo: '’',\n lsquo: '‘',\n rdquo: '”',\n ldquo: '“',\n mdash: '—',\n ndash: '–',\n hellip: '…',\n copy: '©',\n reg: '®',\n trade: '™',\n middot: '·',\n bull: '•',\n eacute: 'é',\n egrave: 'è',\n aacute: 'á',\n ntilde: 'ñ',\n uuml: 'ü',\n ouml: 'ö',\n auml: 'ä',\n deg: '°',\n times: '×',\n}\n\n/** Character references to characters; an unknown name stays as written. */\nexport function decodeHtmlEntities(text: string): string {\n return text.replace(/&(#x[0-9a-f]+|#\\d+|[a-z][a-z0-9]*);/gi, (whole, body: string) => {\n if (body[0] === '#') {\n const code =\n body[1] === 'x' || body[1] === 'X' ? parseInt(body.slice(2), 16) : parseInt(body.slice(1), 10)\n if (!Number.isFinite(code) || code <= 0 || code > 0x10ffff) return ''\n if (code < 32 && code !== 9 && code !== 10 && code !== 13) return ''\n return String.fromCodePoint(code)\n }\n const named = NAMED_ENTITIES[body.toLowerCase()]\n return named ?? whole\n })\n}\n\nconst ATTRIBUTE = /([^\\s=/>\"']+)(?:\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s>\"']+)))?/g\n\n/** A tag's attributes, names lowercased, values decoded. */\nexport function parseAttributes(source: string): Record<string, string> {\n const attributes: Record<string, string> = {}\n for (const match of source.matchAll(ATTRIBUTE)) {\n const name = match[1].toLowerCase()\n if (name in attributes) continue\n attributes[name] = decodeHtmlEntities(match[2] ?? match[3] ?? match[4] ?? '')\n }\n return attributes\n}\n\nconst TAG = /<(\\/?)([a-zA-Z][\\w:-]*)((?:[^>\"']|\"[^\"]*\"|'[^']*')*)>/g\n\n/** Every `<meta>` tag's attributes. */\nfunction metaTags(html: string): Record<string, string>[] {\n const out: Record<string, string>[] = []\n for (const match of html.matchAll(/<meta\\b((?:[^>\"']|\"[^\"]*\"|'[^']*')*)>/gi)) {\n out.push(parseAttributes(match[1]))\n }\n return out\n}\n\n/** The `content` of every `<meta name=\"…\">` with this name. */\nexport function readMetaValues(html: string, name: string): string[] {\n const wanted = name.toLowerCase()\n return metaTags(html)\n .filter((attributes) => (attributes['name'] ?? '').toLowerCase() === wanted)\n .map((attributes) => (attributes['content'] ?? '').trim())\n .filter(Boolean)\n}\n\nfunction collapse(text: string): string {\n return joinRuns(text).replace(/\\s+/g, ' ').trim()\n}\n\nfunction clip(text: string, max: number): string {\n return text.length > max ? `${text.slice(0, max - 1).trimEnd()}…` : text\n}\n\nfunction removeElements(html: string, tags: readonly string[]): string {\n let out = html\n for (const tag of tags) {\n const paired = new RegExp(`<${tag}\\\\b(?:[^>\"']|\"[^\"]*\"|'[^']*')*>[\\\\s\\\\S]*?<\\\\/${tag}\\\\s*>`, 'gi')\n out = out.replace(paired, ' ')\n // One left open runs to the end of the document, as a browser reads it.\n const unclosed = new RegExp(`<${tag}\\\\b(?:[^>\"']|\"[^\"]*\"|'[^']*')*>[\\\\s\\\\S]*$`, 'i')\n out = out.replace(unclosed, ' ')\n }\n return out\n}\n\n/** Forms with a payment field go whole; the rest are summarized, then go. */\nfunction takeForms(html: string): { html: string; kept: SiteImportForm[]; removed: number } {\n const kept: SiteImportForm[] = []\n let removed = 0\n const out = html.replace(/<form\\b(?:[^>\"']|\"[^\"]*\"|'[^']*')*>[\\s\\S]*?(?:<\\/form\\s*>|$)/gi, (form) => {\n if (PAYMENT_FIELD.test(form)) {\n removed += 1\n return ' '\n }\n if (kept.length < SITE_IMPORT_PAGE_LIMITS.forms) {\n const fields: SiteImportForm['fields'] = []\n for (const match of form.matchAll(/<(input|textarea|select)\\b((?:[^>\"']|\"[^\"]*\"|'[^']*')*)>/gi)) {\n const attributes = parseAttributes(match[2])\n const type = match[1].toLowerCase() === 'input' ? (attributes['type'] || 'text').toLowerCase() : match[1].toLowerCase()\n if (type === 'hidden' || type === 'submit' || type === 'button' || type === 'image') continue\n // A field no visitor sees is a trap for bots or a widget's shadow, not a question (AGL-4083).\n if ((attributes['aria-hidden'] ?? '').toLowerCase() === 'true' || attributes['tabindex'] === '-1') continue\n fields.push({ type, name: clip(attributes['name'] || attributes['id'] || '', 80) })\n if (fields.length >= SITE_IMPORT_PAGE_LIMITS.fieldsPerForm) break\n }\n kept.push({ fields })\n }\n return ' '\n })\n return { html: out, kept, removed }\n}\n\n/** `raw` against the page, as an absolute http(s) address without a fragment. */\nexport function resolveSiteUrl(raw: string | undefined, base: string): URL | null {\n const value = String(raw ?? '').trim()\n if (!value || /^(?:javascript|data|mailto|tel|sms|blob|about):/i.test(value)) return null\n try {\n const url = new URL(value, base)\n if (url.protocol !== 'https:' && url.protocol !== 'http:') return null\n url.hash = ''\n return url\n } catch {\n return null\n }\n}\n\n/** The first address a `srcset` offers. */\nfunction firstSrcset(srcset: string | undefined): string | undefined {\n const first = String(srcset ?? '').split(',')[0]?.trim().split(/\\s+/)[0]\n return first || undefined\n}\n\n/**\n * Where a link a page shows goes (AGL-4083): an absolute http(s) address\n * without its fragment, a `mailto:` with an address in it, or a `tel:` with\n * a number in it; `null` for anything else (a script, a jump on the page).\n */\nexport function resolveSiteLink(raw: string | undefined, base: string): string | null {\n const value = String(raw ?? '').trim()\n const mail = /^mailto:([^?\\s<>\"']+@[^?\\s<>\"']+)/i.exec(value)\n if (mail) return `mailto:${decodeURIComponent(mail[1]).toLowerCase()}`\n const phone = /^tel:([+\\d][\\d\\s().-]{2,30})$/i.exec(value)\n if (phone) return `tel:${phone[1].replace(/[\\s().-]/g, '')}`\n if (value.startsWith('#')) return null\n return resolveSiteUrl(value, base)?.toString() ?? null\n}\n\n/**\n * A `<picture>` whose `<img>` has no address of its own (a placeholder, or\n * none at all) is read from its first `<source>` (AGL-4083); a `<picture>`\n * whose `<img>` has one keeps it. The sources go with the void elements.\n */\nfunction readPictures(html: string): string {\n return html.replace(/<picture\\b(?:[^>\"']|\"[^\"]*\"|'[^']*')*>([\\s\\S]*?)<\\/picture\\s*>/gi, (whole, inner: string) => {\n const img = /<img\\b((?:[^>\"']|\"[^\"]*\"|'[^']*')*)>/i.exec(inner)\n const attributes = img ? parseAttributes(img[1]) : {}\n const own = attributes['src'] || attributes['data-src'] || firstSrcset(attributes['srcset'] || attributes['data-srcset'])\n if (own && !/^data:/i.test(own.trim())) return whole\n let source: string | undefined\n for (const match of inner.matchAll(/<source\\b((?:[^>\"']|\"[^\"]*\"|'[^']*')*)>/gi)) {\n const sourceAttributes = parseAttributes(match[1])\n source = firstSrcset(sourceAttributes['srcset'] || sourceAttributes['data-srcset'])\n if (source) break\n }\n if (!source) return whole\n const quote = (value: string) => value.replace(/&/g, '&').replace(/\"/g, '"').replace(/</g, '<')\n return ` <img src=\"${quote(source)}\" alt=\"${quote(attributes['alt'] ?? '')}\"> `\n })\n}\n\n/** The head's icons, the largest first within each kind. */\nfunction readIcons(html: string, pageUrl: string): SiteImportIcon[] {\n const icons: SiteImportIcon[] = []\n for (const match of html.matchAll(/<link\\b((?:[^>\"']|\"[^\"]*\"|'[^']*')*)>/gi)) {\n const attributes = parseAttributes(match[1])\n const rels = (attributes['rel'] ?? '').toLowerCase().split(/\\s+/)\n const rel = rels.includes('apple-touch-icon') || rels.includes('apple-touch-icon-precomposed')\n ? 'apple-touch-icon'\n : rels.includes('icon')\n ? 'icon'\n : null\n if (!rel) continue\n const url = resolveSiteUrl(attributes['href'], pageUrl)\n if (!url) continue\n const sizes: string[] = [...((attributes['sizes'] ?? '').match(/(\\d+)\\s*x\\s*(\\d+)/gi) ?? [])]\n const size = sizes.reduce<number>((most, entry) => Math.max(most, ...entry.split(/x/i).map((side) => Number(side) || 0)), 0)\n icons.push({ url: url.toString(), rel, size: size || null, type: attributes['type']?.trim() || null })\n if (icons.length >= SITE_IMPORT_PAGE_LIMITS.icons) break\n }\n return icons.sort((a, b) => (a.rel === b.rel ? (b.size ?? 0) - (a.size ?? 0) : a.rel === 'apple-touch-icon' ? -1 : 1))\n}\n\nconst ORGANIZATION_TYPE = /(?:^|\\b)(?:Organization|Corporation|LocalBusiness|NGO|EducationalOrganization|MedicalOrganization|SportsOrganization|Store|Restaurant|ProfessionalService|HomeAndConstructionBusiness|HealthAndBeautyBusiness|FoodEstablishment|AutomotiveBusiness|LegalService|FinancialService|Dentist|Physician|LodgingBusiness|EntertainmentBusiness)$/\n\n/**\n * The first Organization (or kind of local business) the page's structured\n * data describes, read before scripts are thrown away. Malformed data, or\n * none, is `null`; nothing in it is ever run.\n */\nfunction readOrganization(html: string): SiteImportOrganization | null {\n const nodes: Record<string, unknown>[] = []\n const visit = (value: unknown, depth: number) => {\n if (depth > 4 || nodes.length > 50 || !value || typeof value !== 'object') return\n if (Array.isArray(value)) {\n for (const entry of value) visit(entry, depth + 1)\n return\n }\n const node = value as Record<string, unknown>\n nodes.push(node)\n if (node['@graph']) visit(node['@graph'], depth + 1)\n }\n for (const match of html.matchAll(/<script\\b[^>]*\\btype\\s*=\\s*[\"']?application\\/ld\\+json[\"']?[^>]*>([\\s\\S]*?)<\\/script\\s*>/gi)) {\n try {\n visit(JSON.parse(match[1]), 0)\n } catch {\n // Structured data that does not parse says nothing.\n }\n }\n const typeOf = (node: Record<string, unknown>) => ([] as unknown[]).concat(node['@type'] ?? []).map(String)\n const node = nodes.find((entry) => typeOf(entry).some((type) => ORGANIZATION_TYPE.test(type)))\n if (!node) return null\n const text = (value: unknown, max = 300): string => (typeof value === 'string' ? clip(collapse(value), max) : '')\n const urlOf = (value: unknown): string => {\n const raw = typeof value === 'string' ? value : value && typeof value === 'object' ? (value as Record<string, unknown>)['url'] : ''\n return typeof raw === 'string' && /^https?:\\/\\//i.test(raw.trim()) ? raw.trim() : ''\n }\n const contact = ([] as unknown[]).concat(node['contactPoint'] ?? []).find((entry) => entry && typeof entry === 'object') as\n | Record<string, unknown>\n | undefined\n const address = (() => {\n const value = node['address']\n if (typeof value === 'string') return text(value)\n if (!value || typeof value !== 'object') return ''\n const parts = value as Record<string, unknown>\n const region = [text(parts['addressRegion']), text(parts['postalCode'])].filter(Boolean).join(' ')\n const country = parts['addressCountry']\n return [text(parts['streetAddress']), text(parts['addressLocality']), region, typeof country === 'string' ? text(country) : text((country as Record<string, unknown> | null)?.['name'])]\n .filter(Boolean)\n .join(', ')\n })()\n return {\n name: text(node['name']),\n description: text(node['description'], 1_000),\n url: urlOf(node['url']),\n logo: urlOf(node['logo']),\n telephone: text(node['telephone']) || text(contact?.['telephone']),\n email: (text(node['email']) || text(contact?.['email'])).replace(/^mailto:/i, ''),\n address,\n sameAs: ([] as unknown[])\n .concat(node['sameAs'] ?? [])\n .map((entry) => urlOf(entry))\n .filter(Boolean)\n .slice(0, 20),\n }\n}\n\n/**\n * The words, structure, pictures and links of one page.\n *\n * `isSameSite(url)` says whether an address is on the site being imported;\n * only those become `links`, and images are marked by it.\n */\nexport function extractSiteImportPage(\n html: string,\n pageUrl: string,\n isSameSite: (url: URL) => boolean,\n): SiteImportExtract {\n const empty: SiteImportExtract = {\n title: '',\n description: '',\n lang: null,\n canonical: null,\n ogImage: null,\n siteName: '',\n icons: [],\n brand: null,\n organization: null,\n blocks: [],\n images: [],\n navLinks: [],\n links: [],\n forms: { kept: [], removed: 0 },\n loginForm: false,\n }\n let source = String(html ?? '')\n .replace(/<!--[\\s\\S]*?(?:-->|$)/g, ' ')\n .replace(/<!\\[CDATA\\[[\\s\\S]*?\\]\\]>/g, ' ')\n if (PASSWORD_INPUT.test(source)) return { ...empty, loginForm: true }\n\n // Read before scripts go: the structured data is a script's body.\n const organization = readOrganization(source)\n const icons = readIcons(source, pageUrl)\n source = removeElements(readPictures(source), REMOVED_WITH_CONTENT).replace(REMOVED_VOID, ' ')\n // Forms next, so a field list is read before the controls go.\n const forms = takeForms(source)\n source = removeElements(forms.html, FORM_CONTROLS)\n\n const metas = metaTags(source)\n const metaContent = (key: string, attribute: 'name' | 'property') =>\n collapse(metas.find((m) => (m[attribute] ?? '').toLowerCase() === key)?.['content'] ?? '')\n const titleMatch = /<title\\b[^>]*>([\\s\\S]*?)<\\/title\\s*>/i.exec(source)\n const title = clip(\n collapse(decodeHtmlEntities(titleMatch?.[1] ?? '')) || metaContent('og:title', 'property'),\n 300,\n )\n const description = clip(\n metaContent('description', 'name') || metaContent('og:description', 'property'),\n 1_000,\n )\n const ogImage =\n resolveSiteUrl(metaContent('og:image', 'property') || metaContent('og:image:url', 'property') || metaContent('twitter:image', 'name'), pageUrl)?.toString() ??\n null\n const siteName = clip(metaContent('og:site_name', 'property') || metaContent('application-name', 'name'), 120)\n const htmlTag = /<html\\b((?:[^>\"']|\"[^\"]*\"|'[^']*')*)>/i.exec(source)\n const lang = htmlTag ? parseAttributes(htmlTag[1])['lang']?.trim() || null : null\n let canonical: string | null = null\n for (const match of String(html ?? '').matchAll(/<link\\b((?:[^>\"']|\"[^\"]*\"|'[^']*')*)>/gi)) {\n const attributes = parseAttributes(match[1])\n if ((attributes['rel'] ?? '').toLowerCase().split(/\\s+/).includes('canonical')) {\n canonical = resolveSiteUrl(attributes['href'], pageUrl)?.toString() ?? null\n break\n }\n }\n\n const bodyStart = source.search(/<body\\b/i)\n const body = bodyStart >= 0 ? source.slice(bodyStart) : source.replace(/<head\\b[\\s\\S]*?<\\/head\\s*>/i, ' ')\n const hasNav = /<nav\\b/i.test(body)\n\n const blocks: SiteImportBlock[] = []\n const images: SiteImportImage[] = []\n const imageSeen = new Set<string>()\n const navLinks: SiteImportNavLink[] = []\n const links: string[] = []\n const linkSeen = new Set<string>()\n let brand: SiteImportBrandMark | null = null\n\n let navDepth = 0\n let headerDepth = 0\n let footerDepth = 0\n let current: { kind: 'heading' | 'paragraph' | 'list-item' | 'quote'; level?: number; tag: string } | null = null\n let buffer = ''\n /** The link being read: its menu address (http(s) only), where it goes, its words and its first picture. */\n let anchor: {\n url: URL | null\n href: string | null\n text: string\n /** Its words split where a block-level tag inside it began, for a menu entry's first line. */\n lines: string[]\n image: { url: string; alt: string } | null\n /** Where in `buffer` its words begin. */\n start: number\n /** Its words ran across a block's end: some of them are in a block already. */\n crossed: boolean\n region: SiteImportRegion\n } | null = null\n /** The links read into the block being built, written after it. */\n let pending: Array<{ text: string; href: string; start: number; end: number; crossed: boolean; region: SiteImportRegion }> = []\n\n const region = (): SiteImportRegion =>\n footerDepth > 0 ? 'footer' : headerDepth > 0 ? 'header' : 'main'\n // A site without a <nav> keeps its menu in the header; its links are the nav.\n const inNav = () => navDepth > 0 || (!hasNav && headerDepth > 0)\n const room = () => blocks.length < SITE_IMPORT_PAGE_LIMITS.blocks\n\n const flush = () => {\n const raw = buffer\n const written = pending\n buffer = ''\n pending = []\n if (anchor && collapse(decodeHtmlEntities(raw.slice(anchor.start)))) anchor.crossed = true\n if (anchor) anchor.start = 0\n const kind = current?.kind ?? 'paragraph'\n const level = current?.level\n const loose = current === null\n current = null\n if (navDepth > 0) return\n const text = clip(collapse(decodeHtmlEntities(raw)), SITE_IMPORT_PAGE_LIMITS.blockChars)\n // Loose words that are only links (a row of buttons, an address in a\n // card) are those links, each a block of its own; words around a link\n // keep their block, and the link follows it.\n let rest = raw\n for (const link of [...written].sort((a, b) => b.start - a.start)) rest = `${rest.slice(0, link.start)}${rest.slice(link.end)}`\n const onlyLinks = loose && written.length > 0 && !collapse(decodeHtmlEntities(rest))\n if (text && !onlyLinks && room()) {\n const last = blocks[blocks.length - 1]\n if (!(last && last.kind !== 'image' && last.kind !== 'link' && last.text === text)) {\n if (kind === 'heading') {\n blocks.push({ kind, level: (level ?? 2) as 1 | 2 | 3 | 4 | 5 | 6, text, region: region() })\n } else {\n blocks.push({ kind, text, region: region() })\n }\n }\n }\n for (const link of written) {\n if (!room()) break\n const last = blocks[blocks.length - 1]\n if (last?.kind === 'link' && last.href === link.href && last.text === link.text) continue\n const inline = !onlyLinks || link.crossed\n blocks.push({ kind: 'link', text: link.text, href: link.href, region: link.region, ...(inline ? { inline: true as const } : {}) })\n }\n }\n\n /** A boundary between two runs of words: in the block, and in the link being read. */\n const boundary = (block: boolean) => {\n buffer += RUN_BREAK\n if (!anchor) return\n anchor.text += RUN_BREAK\n if (block && anchor.lines[anchor.lines.length - 1] !== '') anchor.lines.push('')\n }\n\n for (const match of body.matchAll(new RegExp(`${TAG.source}|([^<]+)|<`, 'g'))) {\n const text = match[4]\n if (text !== undefined || match[2] === undefined) {\n const run = text ?? '<'\n buffer += run\n if (anchor) {\n anchor.text += run\n anchor.lines[anchor.lines.length - 1] += run\n }\n continue\n }\n const closing = match[1] === '/'\n const tag = match[2].toLowerCase()\n const attributes = closing ? {} : parseAttributes(match[3] ?? '')\n\n if (tag === 'a') {\n if (closing) {\n if (anchor) {\n const label = clip(collapse(decodeHtmlEntities(anchor.text)), 300)\n const firstLine = clip(collapse(decodeHtmlEntities(anchor.lines.find((line) => collapse(decodeHtmlEntities(line))) ?? '')), 120)\n if (inNav() && anchor.url && firstLine && navLinks.length < SITE_IMPORT_PAGE_LIMITS.navLinks) {\n navLinks.push({ text: firstLine, url: anchor.url.toString() })\n }\n if (!brand && anchor.region !== 'footer' && (headerDepth > 0 || navDepth > 0) && anchor.url && isSameSite(anchor.url) && anchor.url.pathname === '/') {\n brand = { logo: anchor.image, text: label, region: anchor.region }\n }\n if (!inNav() && anchor.href && label) {\n pending.push({ text: label, href: anchor.href, start: anchor.start, end: buffer.length, crossed: anchor.crossed, region: anchor.region })\n }\n anchor = null\n }\n boundary(false)\n continue\n }\n boundary(false)\n const url = resolveSiteUrl(attributes['href'], pageUrl)\n anchor = {\n url,\n href: resolveSiteLink(attributes['href'], pageUrl),\n text: '',\n lines: [''],\n image: null,\n start: buffer.length,\n crossed: false,\n region: region(),\n }\n if (url && isSameSite(url) && links.length < SITE_IMPORT_PAGE_LIMITS.links) {\n const key = url.toString()\n if (!linkSeen.has(key)) {\n linkSeen.add(key)\n links.push(key)\n }\n }\n continue\n }\n\n if (tag === 'img' && !closing) {\n const url = resolveSiteUrl(\n attributes['src'] || attributes['data-src'] || firstSrcset(attributes['srcset'] || attributes['data-srcset']),\n pageUrl,\n )\n boundary(false)\n if (url && images.length < SITE_IMPORT_PAGE_LIMITS.images) {\n const key = url.toString()\n const alt = clip(collapse(attributes['alt'] ?? ''), 300)\n if (anchor && !anchor.image) anchor.image = { url: key, alt }\n if (!imageSeen.has(key)) {\n imageSeen.add(key)\n images.push({ url: key, alt, sameSite: isSameSite(url), region: region() })\n if (navDepth === 0) {\n flush()\n if (room()) blocks.push({ kind: 'image', url: key, alt, region: region() })\n }\n }\n }\n continue\n }\n\n if (tag === 'nav' || tag === 'header' || tag === 'footer') {\n flush()\n boundary(true)\n const step = closing ? -1 : 1\n if (tag === 'nav') navDepth = Math.max(0, navDepth + step)\n if (tag === 'header') headerDepth = Math.max(0, headerDepth + step)\n if (tag === 'footer') footerDepth = Math.max(0, footerDepth + step)\n continue\n }\n\n const heading = /^h([1-6])$/.exec(tag)\n if (heading || BLOCK_TAGS[tag]) {\n if (closing) {\n if (current?.tag === tag) flush()\n boundary(true)\n continue\n }\n flush()\n boundary(true)\n current = heading\n ? { kind: 'heading', level: Number(heading[1]), tag }\n : { kind: BLOCK_TAGS[tag], tag }\n continue\n }\n\n if (BREAK_TAGS.has(tag)) {\n if (tag === 'br' && current) {\n buffer += ' '\n if (anchor) anchor.text += ' '\n continue\n }\n flush()\n boundary(true)\n continue\n }\n\n if (RUN_BOUNDARY.has(tag)) boundary(false)\n }\n flush()\n\n return {\n title,\n description,\n lang,\n canonical,\n ogImage,\n siteName,\n icons,\n brand,\n organization,\n blocks,\n images,\n navLinks,\n links,\n forms: { kept: forms.kept, removed: forms.removed },\n loginForm: false,\n }\n}\n"],"names":["SITE_IMPORT_PAGE_LIMITS","blocks","blockChars","images","links","navLinks","forms","fieldsPerForm","icons","REMOVED_WITH_CONTENT","FORM_CONTROLS","REMOVED_VOID","PASSWORD_INPUT","PAYMENT_FIELD","BLOCK_TAGS","p","li","dt","dd","td","th","address","pre","figcaption","caption","label","blockquote","BREAK_TAGS","Set","RUN_BOUNDARY","RUN_BREAK","RUN_BREAKS","RegExp","joinRuns","text","includes","replace","match","offset","whole","test","before","after","length","NAMED_ENTITIES","amp","lt","gt","quot","apos","nbsp","rsquo","lsquo","rdquo","ldquo","mdash","ndash","hellip","copy","reg","trade","middot","bull","eacute","egrave","aacute","ntilde","uuml","ouml","auml","deg","times","decodeHtmlEntities","body","code","parseInt","slice","Number","isFinite","String","fromCodePoint","named","toLowerCase","ATTRIBUTE","parseAttributes","source","attributes","matchAll","name","TAG","metaTags","html","out","push","readMetaValues","wanted","filter","map","trim","Boolean","collapse","clip","max","trimEnd","removeElements","tags","tag","paired","unclosed","takeForms","kept","removed","form","fields","type","resolveSiteUrl","raw","base","value","url","URL","protocol","hash","firstSrcset","srcset","first","split","undefined","resolveSiteLink","mail","exec","decodeURIComponent","phone","startsWith","toString","readPictures","inner","img","own","sourceAttributes","quote","readIcons","pageUrl","rels","rel","sizes","size","reduce","most","entry","Math","side","sort","a","b","ORGANIZATION_TYPE","readOrganization","node","nodes","visit","depth","Array","isArray","JSON","parse","typeOf","concat","find","some","urlOf","contact","parts","region","join","country","description","logo","telephone","email","sameAs","extractSiteImportPage","isSameSite","empty","title","lang","canonical","ogImage","siteName","brand","organization","loginForm","metas","metaContent","key","attribute","m","titleMatch","htmlTag","bodyStart","search","hasNav","imageSeen","linkSeen","navDepth","headerDepth","footerDepth","current","buffer","anchor","pending","inNav","room","flush","written","start","crossed","kind","level","loose","rest","link","end","onlyLinks","last","href","inline","boundary","block","lines","run","closing","firstLine","line","pathname","image","has","add","alt","sameSite","step","heading"],"mappings":"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"}
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import type { PluginMediaIngest } from '@aglyn/aglyn/plugin-manager/plugin-media-ingest';
|
|
18
18
|
import { type PublicFetchOptions, type PublicFetchResult } from '../jobs/ai-theme-brand-inputs';
|
|
19
19
|
import type { AiSiteImportPage } from '../model/ai-site-import';
|
|
20
|
+
import { type SiteImportPage } from './site-import-crawl';
|
|
20
21
|
/** The largest picture brought in. */
|
|
21
22
|
export declare const SITE_IMPORT_IMAGE_MAX_BYTES: number;
|
|
22
23
|
/** A picture smaller than this is a spacer or a tracking pixel, not a photo. */
|
|
@@ -52,3 +53,24 @@ export interface SiteImportPhotosDeps {
|
|
|
52
53
|
* library already holds by key. Never throws for anything a picture does.
|
|
53
54
|
*/
|
|
54
55
|
export declare function bringInSiteImportPhotos(input: SiteImportPhotosInput, deps: SiteImportPhotosDeps): Promise<NonNullable<AiSiteImportPage['photos']>>;
|
|
56
|
+
/**
|
|
57
|
+
* A picture that may be the site's logo, where it was found. `type` is what
|
|
58
|
+
* its address or its declared type says: `svg`, a raster, or `null` when
|
|
59
|
+
* neither says (a media library address with no extension); the ingest
|
|
60
|
+
* reads the answer's own type either way.
|
|
61
|
+
*/
|
|
62
|
+
export interface SiteImportLogoCandidate {
|
|
63
|
+
url: string;
|
|
64
|
+
alt: string;
|
|
65
|
+
source: 'header' | 'footer' | 'structured-data' | 'apple-touch-icon' | 'icon';
|
|
66
|
+
type: 'svg' | 'raster' | null;
|
|
67
|
+
}
|
|
68
|
+
/** A logo's address as it is fetched: https, no fragment or tracking parameter. An SVG is a logo, unlike a photo. */
|
|
69
|
+
export declare function siteImportLogoUrl(raw: string): string | null;
|
|
70
|
+
/**
|
|
71
|
+
* The pictures that may be the site's logo (AGL-4083), the likeliest first:
|
|
72
|
+
* the picture in the header's link home, any header picture that says it is
|
|
73
|
+
* the logo or the site's name, the footer's, the structured data's logo,
|
|
74
|
+
* then the largest touch icon and the largest icon. Each address once.
|
|
75
|
+
*/
|
|
76
|
+
export declare function siteImportLogoCandidates(page: Pick<SiteImportPage, 'brand' | 'images' | 'icons' | 'siteName' | 'organization' | 'title'>): SiteImportLogoCandidate[];
|
|
@@ -136,7 +136,9 @@ const EXTENSION = {
|
|
|
136
136
|
height: held.height
|
|
137
137
|
} : {}, held.alt || image.alt ? {
|
|
138
138
|
alt: held.alt || image.alt
|
|
139
|
-
} : {}
|
|
139
|
+
} : {}, {
|
|
140
|
+
from: image.url
|
|
141
|
+
}));
|
|
140
142
|
continue;
|
|
141
143
|
}
|
|
142
144
|
const fetched = await fetchBytes(url, _extends({
|
|
@@ -184,7 +186,10 @@ const EXTENSION = {
|
|
|
184
186
|
height: stored.height
|
|
185
187
|
} : {}, image.alt ? {
|
|
186
188
|
alt: image.alt.slice(0, 250)
|
|
187
|
-
} : {}
|
|
189
|
+
} : {}, {
|
|
190
|
+
// Where it sat on the old page, so a page copied as it is places it there (AGL-4084).
|
|
191
|
+
from: image.url
|
|
192
|
+
}));
|
|
188
193
|
} catch (error) {
|
|
189
194
|
console.warn('[site-import] a photo could not be brought in', {
|
|
190
195
|
hostId: input.hostId,
|
|
@@ -200,5 +205,76 @@ const EXTENSION = {
|
|
|
200
205
|
assets
|
|
201
206
|
};
|
|
202
207
|
}
|
|
208
|
+
/** A logo's address as it is fetched: https, no fragment or tracking parameter. An SVG is a logo, unlike a photo. */ export function siteImportLogoUrl(raw) {
|
|
209
|
+
let url;
|
|
210
|
+
try {
|
|
211
|
+
url = new URL(String(raw != null ? raw : '').trim());
|
|
212
|
+
} catch (unused) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
if (url.protocol === 'http:') url.protocol = 'https:';
|
|
216
|
+
if (url.protocol !== 'https:' || url.username || url.password) return null;
|
|
217
|
+
if (url.port && url.port !== '443') return null;
|
|
218
|
+
url.hash = '';
|
|
219
|
+
for (const key of [
|
|
220
|
+
...url.searchParams.keys()
|
|
221
|
+
]){
|
|
222
|
+
if (TRACKING_PARAM.test(key)) url.searchParams.delete(key);
|
|
223
|
+
}
|
|
224
|
+
return url.toString();
|
|
225
|
+
}
|
|
226
|
+
function logoTypeOf(url, declared) {
|
|
227
|
+
const type = (declared != null ? declared : '').toLowerCase();
|
|
228
|
+
if (type.includes('svg')) return 'svg';
|
|
229
|
+
if (/^image\/(?:png|jpe?g|webp|gif|x-icon|vnd\.microsoft\.icon)/.test(type)) return 'raster';
|
|
230
|
+
let path;
|
|
231
|
+
try {
|
|
232
|
+
path = new URL(url).pathname.toLowerCase();
|
|
233
|
+
} catch (unused) {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
if (path.endsWith('.svg')) return 'svg';
|
|
237
|
+
if (/\.(?:png|jpe?g|webp|gif|ico)$/.test(path)) return 'raster';
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* The pictures that may be the site's logo (AGL-4083), the likeliest first:
|
|
242
|
+
* the picture in the header's link home, any header picture that says it is
|
|
243
|
+
* the logo or the site's name, the footer's, the structured data's logo,
|
|
244
|
+
* then the largest touch icon and the largest icon. Each address once.
|
|
245
|
+
*/ export function siteImportLogoCandidates(page) {
|
|
246
|
+
var _page_organization, _page_brand, _page_organization1;
|
|
247
|
+
const name = (page.siteName || ((_page_organization = page.organization) == null ? void 0 : _page_organization.name) || '').trim().toLowerCase();
|
|
248
|
+
const says = (image)=>{
|
|
249
|
+
const alt = image.alt.trim().toLowerCase();
|
|
250
|
+
return /\blogo\b/i.test(image.alt) || /logo/i.test(image.url) || !!name && (alt === name || alt === `${name} logo`);
|
|
251
|
+
};
|
|
252
|
+
const out = [];
|
|
253
|
+
const add = (raw, alt, source, declared)=>{
|
|
254
|
+
const url = siteImportLogoUrl(raw);
|
|
255
|
+
if (!url || out.some((entry)=>entry.url === url)) return;
|
|
256
|
+
out.push({
|
|
257
|
+
url,
|
|
258
|
+
alt: alt.slice(0, 250),
|
|
259
|
+
source,
|
|
260
|
+
type: logoTypeOf(url, declared)
|
|
261
|
+
});
|
|
262
|
+
};
|
|
263
|
+
if (((_page_brand = page.brand) == null ? void 0 : _page_brand.logo) && page.brand.region !== 'footer') add(page.brand.logo.url, page.brand.logo.alt, 'header');
|
|
264
|
+
for (const image of page.images)if (image.region === 'header' && says(image)) add(image.url, image.alt, 'header');
|
|
265
|
+
for (const image of page.images)if (image.region === 'footer' && says(image)) add(image.url, image.alt, 'footer');
|
|
266
|
+
if ((_page_organization1 = page.organization) == null ? void 0 : _page_organization1.logo) add(page.organization.logo, page.organization.name, 'structured-data');
|
|
267
|
+
const touch = page.icons.filter((icon)=>icon.rel === 'apple-touch-icon').sort((a, b)=>{
|
|
268
|
+
var _b_size, _a_size;
|
|
269
|
+
return ((_b_size = b.size) != null ? _b_size : 0) - ((_a_size = a.size) != null ? _a_size : 0);
|
|
270
|
+
})[0];
|
|
271
|
+
if (touch) add(touch.url, '', 'apple-touch-icon', touch.type);
|
|
272
|
+
const icon = page.icons.filter((entry)=>entry.rel === 'icon').sort((a, b)=>{
|
|
273
|
+
var _b_size, _a_size;
|
|
274
|
+
return ((_b_size = b.size) != null ? _b_size : 0) - ((_a_size = a.size) != null ? _a_size : 0);
|
|
275
|
+
})[0];
|
|
276
|
+
if (icon) add(icon.url, '', 'icon', icon.type);
|
|
277
|
+
return out;
|
|
278
|
+
}
|
|
203
279
|
|
|
204
280
|
//# sourceMappingURL=site-import-images.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/site-import/site-import-images.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*==========================================\n * AN IMPORTED PAGE'S PHOTOS, INTO THE SITE'S OWN MEDIA LIBRARY (AGL-3976).\n *\n * Every picture goes through the media library's server door\n * (`pluginMediaIngest().ingest`), which checks the member, the site's\n * lockdown, the bytes' structure, the takedown list and the storage band\n * exactly as an upload does. This module only fetches and names:\n *\n * - **Never a second copy.** A picture's SOURCE KEY is `import:` and a hash\n * of its address with any fragment and tracking parameter removed. The\n * library is asked for that key first (`findImportedPhoto`); a logo every\n * page shows is stored once and placed everywhere. An ingest that cannot\n * answer the question stores nothing, rather than risk the second copy.\n * - **The safe fetch.** Bytes are read through `fetchPublicTextDetailed`,\n * the SSRF-guarded fetch the crawler uses (public https only, the socket\n * pinned, every redirect checked again), as AglynBot, capped in size and\n * time, and only as JPEG, PNG, WebP or GIF: the raster types the library\n * takes from a server.\n *=========================================*/\n\nimport type { PluginMediaIngest } from '@aglyn/aglyn/plugin-manager/plugin-media-ingest'\nimport { createHash } from 'node:crypto'\nimport {\n fetchPublicTextDetailed,\n type PublicFetchOptions,\n type PublicFetchResult,\n} from '../jobs/ai-theme-brand-inputs'\nimport type { AiSiteImportPage, AiSiteImportPhoto } from '../model/ai-site-import'\nimport { AGLYN_BOT_USER_AGENT } from './site-import-crawl'\n\n/** The largest picture brought in. */\nexport const SITE_IMPORT_IMAGE_MAX_BYTES = 8 * 1024 * 1024\n\n/** A picture smaller than this is a spacer or a tracking pixel, not a photo. */\nexport const SITE_IMPORT_IMAGE_MIN_BYTES = 1_024\n\n/** One picture's own clock. */\nexport const SITE_IMPORT_IMAGE_TIMEOUT_MS = 8_000\n\nconst RASTER = /^image\\/(?:jpeg|jpg|pjpeg|png|webp|gif)\\b/i\n\nconst NOT_A_PHOTO = /\\.(?:svg|ico|bmp|tiff?|avif|heic)$/i\n\nconst TRACKING_PARAM = /^(?:utm_[a-z]+|fbclid|gclid|msclkid|_ga|ref)$/i\n\nconst EXTENSION: Record<string, string> = {\n 'image/jpeg': 'jpg',\n 'image/jpg': 'jpg',\n 'image/pjpeg': 'jpg',\n 'image/png': 'png',\n 'image/webp': 'webp',\n 'image/gif': 'gif',\n}\n\n/** A picture's address as it is fetched and keyed: https, no fragment or tracking; `null` for one that is not a photo. */\nexport function siteImportImageUrl(raw: string): string | null {\n let url: URL\n try {\n url = new URL(String(raw ?? '').trim())\n } catch {\n return null\n }\n if (url.protocol === 'http:') url.protocol = 'https:'\n if (url.protocol !== 'https:' || url.username || url.password) return null\n if (url.port && url.port !== '443') return null\n if (NOT_A_PHOTO.test(url.pathname)) return null\n url.hash = ''\n for (const key of [...url.searchParams.keys()]) {\n if (TRACKING_PARAM.test(key)) url.searchParams.delete(key)\n }\n return url.toString()\n}\n\n/** The key a site's library holds an imported picture by. */\nexport function siteImportImageKey(url: string): string {\n return `import:${createHash('sha256').update(url).digest('hex').slice(0, 32)}`\n}\n\n/** A file name for the library, from the picture's address and type. */\nexport function siteImportImageFileName(url: string, contentType: string): string {\n let base = 'photo'\n try {\n const last = decodeURIComponent(new URL(url).pathname.split('/').filter(Boolean).pop() ?? '')\n base = last.replace(/\\.[a-z0-9]+$/i, '').replace(/[^a-zA-Z0-9 _-]+/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '') || 'photo'\n } catch {\n // An address that does not decode keeps the plain name.\n }\n const type = contentType.split(';')[0].trim().toLowerCase()\n return `${base.slice(0, 80)}.${EXTENSION[type] ?? 'jpg'}`\n}\n\nexport interface SiteImportPhotosInput {\n hostId: string\n /** The member the photos are stored as: the job's creator. */\n uid: string\n domain: string\n images: ReadonlyArray<{ url: string; alt: string }>\n signal?: AbortSignal\n /** When the pass must stop asking for more. */\n deadlineAt: number\n}\n\nexport interface SiteImportPhotosDeps {\n ingest: PluginMediaIngest | null\n fetch?: (url: string, options: PublicFetchOptions) => Promise<PublicFetchResult>\n now?: () => number\n}\n\n/**\n * Brings one page's pictures into the site's library, reusing any the\n * library already holds by key. Never throws for anything a picture does.\n */\nexport async function bringInSiteImportPhotos(\n input: SiteImportPhotosInput,\n deps: SiteImportPhotosDeps,\n): Promise<NonNullable<AiSiteImportPage['photos']>> {\n const now = deps.now ?? Date.now\n const fetchBytes = deps.fetch ?? fetchPublicTextDetailed\n const assets: AiSiteImportPhoto[] = []\n let brought = 0\n let reused = 0\n let skipped = 0\n const ingest = deps.ingest\n // No library door, or one that cannot say what it already holds: nothing\n // is stored, because a picture stored without that check could be a copy.\n if (!ingest?.findImportedPhoto) {\n return { brought, reused, skipped: input.images.length, assets }\n }\n const seen = new Set<string>()\n for (const image of input.images) {\n const url = siteImportImageUrl(image.url)\n if (!url || seen.has(url)) {\n if (!url) skipped += 1\n continue\n }\n seen.add(url)\n if (input.signal?.aborted || now() >= input.deadlineAt) {\n skipped += 1\n continue\n }\n const sourceKey = siteImportImageKey(url)\n try {\n const held = await ingest.findImportedPhoto({ hostId: input.hostId, sourceKey })\n if (held) {\n reused += 1\n assets.push({\n mediaId: held.mediaId,\n src: held.src,\n ...(held.width ? { width: held.width } : {}),\n ...(held.height ? { height: held.height } : {}),\n ...(held.alt || image.alt ? { alt: held.alt || image.alt } : {}),\n })\n continue\n }\n const fetched = await fetchBytes(url, {\n maxBytes: SITE_IMPORT_IMAGE_MAX_BYTES,\n accept: RASTER,\n acceptHeader: 'image/webp,image/png,image/jpeg,image/gif;q=0.9',\n userAgent: AGLYN_BOT_USER_AGENT,\n timeoutMs: Math.max(1, Math.min(SITE_IMPORT_IMAGE_TIMEOUT_MS, input.deadlineAt - now())),\n refuseOver: true,\n binary: true,\n ...(input.signal ? { signal: input.signal } : {}),\n })\n if (!fetched.ok || !fetched.data || fetched.data.byteLength < SITE_IMPORT_IMAGE_MIN_BYTES) {\n skipped += 1\n continue\n }\n const contentType = fetched.contentType.split(';')[0].trim().toLowerCase().replace('image/jpg', 'image/jpeg').replace('image/pjpeg', 'image/jpeg')\n const stored = await ingest.ingest({\n hostId: input.hostId,\n uid: input.uid,\n fileName: siteImportImageFileName(url, contentType),\n contentType,\n bytes: fetched.data,\n ...(image.alt ? { alt: image.alt.slice(0, 250) } : {}),\n importedFrom: { key: sourceKey, url, domain: input.domain },\n })\n if (!stored.ok) {\n skipped += 1\n continue\n }\n brought += 1\n assets.push({\n mediaId: stored.mediaId,\n src: stored.src,\n ...(stored.width ? { width: stored.width } : {}),\n ...(stored.height ? { height: stored.height } : {}),\n ...(image.alt ? { alt: image.alt.slice(0, 250) } : {}),\n })\n } catch (error) {\n console.warn('[site-import] a photo could not be brought in', { hostId: input.hostId, error })\n skipped += 1\n }\n }\n return { brought, reused, skipped, assets }\n}\n"],"names":["createHash","fetchPublicTextDetailed","AGLYN_BOT_USER_AGENT","SITE_IMPORT_IMAGE_MAX_BYTES","SITE_IMPORT_IMAGE_MIN_BYTES","SITE_IMPORT_IMAGE_TIMEOUT_MS","RASTER","NOT_A_PHOTO","TRACKING_PARAM","EXTENSION","siteImportImageUrl","raw","url","URL","String","trim","protocol","username","password","port","test","pathname","hash","key","searchParams","keys","delete","toString","siteImportImageKey","update","digest","slice","siteImportImageFileName","contentType","base","last","decodeURIComponent","split","filter","Boolean","pop","replace","type","toLowerCase","bringInSiteImportPhotos","input","deps","now","Date","fetchBytes","fetch","assets","brought","reused","skipped","ingest","findImportedPhoto","images","length","seen","Set","image","has","add","signal","aborted","deadlineAt","sourceKey","held","hostId","push","mediaId","src","width","height","alt","fetched","maxBytes","accept","acceptHeader","userAgent","timeoutMs","Math","max","min","refuseOver","binary","ok","data","byteLength","stored","uid","fileName","bytes","importedFrom","domain","error","console","warn"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/site-import/site-import-images.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*==========================================\n * AN IMPORTED PAGE'S PHOTOS, INTO THE SITE'S OWN MEDIA LIBRARY (AGL-3976).\n *\n * Every picture goes through the media library's server door\n * (`pluginMediaIngest().ingest`), which checks the member, the site's\n * lockdown, the bytes' structure, the takedown list and the storage band\n * exactly as an upload does. This module only fetches and names:\n *\n * - **Never a second copy.** A picture's SOURCE KEY is `import:` and a hash\n * of its address with any fragment and tracking parameter removed. The\n * library is asked for that key first (`findImportedPhoto`); a logo every\n * page shows is stored once and placed everywhere. An ingest that cannot\n * answer the question stores nothing, rather than risk the second copy.\n * - **The safe fetch.** Bytes are read through `fetchPublicTextDetailed`,\n * the SSRF-guarded fetch the crawler uses (public https only, the socket\n * pinned, every redirect checked again), as AglynBot, capped in size and\n * time, and only as JPEG, PNG, WebP or GIF: the raster types the library\n * takes from a server.\n *=========================================*/\n\nimport type { PluginMediaIngest } from '@aglyn/aglyn/plugin-manager/plugin-media-ingest'\nimport { createHash } from 'node:crypto'\nimport {\n fetchPublicTextDetailed,\n type PublicFetchOptions,\n type PublicFetchResult,\n} from '../jobs/ai-theme-brand-inputs'\nimport type { AiSiteImportPage, AiSiteImportPhoto } from '../model/ai-site-import'\nimport { AGLYN_BOT_USER_AGENT, type SiteImportPage } from './site-import-crawl'\n\n/** The largest picture brought in. */\nexport const SITE_IMPORT_IMAGE_MAX_BYTES = 8 * 1024 * 1024\n\n/** A picture smaller than this is a spacer or a tracking pixel, not a photo. */\nexport const SITE_IMPORT_IMAGE_MIN_BYTES = 1_024\n\n/** One picture's own clock. */\nexport const SITE_IMPORT_IMAGE_TIMEOUT_MS = 8_000\n\nconst RASTER = /^image\\/(?:jpeg|jpg|pjpeg|png|webp|gif)\\b/i\n\nconst NOT_A_PHOTO = /\\.(?:svg|ico|bmp|tiff?|avif|heic)$/i\n\nconst TRACKING_PARAM = /^(?:utm_[a-z]+|fbclid|gclid|msclkid|_ga|ref)$/i\n\nconst EXTENSION: Record<string, string> = {\n 'image/jpeg': 'jpg',\n 'image/jpg': 'jpg',\n 'image/pjpeg': 'jpg',\n 'image/png': 'png',\n 'image/webp': 'webp',\n 'image/gif': 'gif',\n}\n\n/** A picture's address as it is fetched and keyed: https, no fragment or tracking; `null` for one that is not a photo. */\nexport function siteImportImageUrl(raw: string): string | null {\n let url: URL\n try {\n url = new URL(String(raw ?? '').trim())\n } catch {\n return null\n }\n if (url.protocol === 'http:') url.protocol = 'https:'\n if (url.protocol !== 'https:' || url.username || url.password) return null\n if (url.port && url.port !== '443') return null\n if (NOT_A_PHOTO.test(url.pathname)) return null\n url.hash = ''\n for (const key of [...url.searchParams.keys()]) {\n if (TRACKING_PARAM.test(key)) url.searchParams.delete(key)\n }\n return url.toString()\n}\n\n/** The key a site's library holds an imported picture by. */\nexport function siteImportImageKey(url: string): string {\n return `import:${createHash('sha256').update(url).digest('hex').slice(0, 32)}`\n}\n\n/** A file name for the library, from the picture's address and type. */\nexport function siteImportImageFileName(url: string, contentType: string): string {\n let base = 'photo'\n try {\n const last = decodeURIComponent(new URL(url).pathname.split('/').filter(Boolean).pop() ?? '')\n base = last.replace(/\\.[a-z0-9]+$/i, '').replace(/[^a-zA-Z0-9 _-]+/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '') || 'photo'\n } catch {\n // An address that does not decode keeps the plain name.\n }\n const type = contentType.split(';')[0].trim().toLowerCase()\n return `${base.slice(0, 80)}.${EXTENSION[type] ?? 'jpg'}`\n}\n\nexport interface SiteImportPhotosInput {\n hostId: string\n /** The member the photos are stored as: the job's creator. */\n uid: string\n domain: string\n images: ReadonlyArray<{ url: string; alt: string }>\n signal?: AbortSignal\n /** When the pass must stop asking for more. */\n deadlineAt: number\n}\n\nexport interface SiteImportPhotosDeps {\n ingest: PluginMediaIngest | null\n fetch?: (url: string, options: PublicFetchOptions) => Promise<PublicFetchResult>\n now?: () => number\n}\n\n/**\n * Brings one page's pictures into the site's library, reusing any the\n * library already holds by key. Never throws for anything a picture does.\n */\nexport async function bringInSiteImportPhotos(\n input: SiteImportPhotosInput,\n deps: SiteImportPhotosDeps,\n): Promise<NonNullable<AiSiteImportPage['photos']>> {\n const now = deps.now ?? Date.now\n const fetchBytes = deps.fetch ?? fetchPublicTextDetailed\n const assets: AiSiteImportPhoto[] = []\n let brought = 0\n let reused = 0\n let skipped = 0\n const ingest = deps.ingest\n // No library door, or one that cannot say what it already holds: nothing\n // is stored, because a picture stored without that check could be a copy.\n if (!ingest?.findImportedPhoto) {\n return { brought, reused, skipped: input.images.length, assets }\n }\n const seen = new Set<string>()\n for (const image of input.images) {\n const url = siteImportImageUrl(image.url)\n if (!url || seen.has(url)) {\n if (!url) skipped += 1\n continue\n }\n seen.add(url)\n if (input.signal?.aborted || now() >= input.deadlineAt) {\n skipped += 1\n continue\n }\n const sourceKey = siteImportImageKey(url)\n try {\n const held = await ingest.findImportedPhoto({ hostId: input.hostId, sourceKey })\n if (held) {\n reused += 1\n assets.push({\n mediaId: held.mediaId,\n src: held.src,\n ...(held.width ? { width: held.width } : {}),\n ...(held.height ? { height: held.height } : {}),\n ...(held.alt || image.alt ? { alt: held.alt || image.alt } : {}),\n from: image.url,\n })\n continue\n }\n const fetched = await fetchBytes(url, {\n maxBytes: SITE_IMPORT_IMAGE_MAX_BYTES,\n accept: RASTER,\n acceptHeader: 'image/webp,image/png,image/jpeg,image/gif;q=0.9',\n userAgent: AGLYN_BOT_USER_AGENT,\n timeoutMs: Math.max(1, Math.min(SITE_IMPORT_IMAGE_TIMEOUT_MS, input.deadlineAt - now())),\n refuseOver: true,\n binary: true,\n ...(input.signal ? { signal: input.signal } : {}),\n })\n if (!fetched.ok || !fetched.data || fetched.data.byteLength < SITE_IMPORT_IMAGE_MIN_BYTES) {\n skipped += 1\n continue\n }\n const contentType = fetched.contentType.split(';')[0].trim().toLowerCase().replace('image/jpg', 'image/jpeg').replace('image/pjpeg', 'image/jpeg')\n const stored = await ingest.ingest({\n hostId: input.hostId,\n uid: input.uid,\n fileName: siteImportImageFileName(url, contentType),\n contentType,\n bytes: fetched.data,\n ...(image.alt ? { alt: image.alt.slice(0, 250) } : {}),\n importedFrom: { key: sourceKey, url, domain: input.domain },\n })\n if (!stored.ok) {\n skipped += 1\n continue\n }\n brought += 1\n assets.push({\n mediaId: stored.mediaId,\n src: stored.src,\n ...(stored.width ? { width: stored.width } : {}),\n ...(stored.height ? { height: stored.height } : {}),\n ...(image.alt ? { alt: image.alt.slice(0, 250) } : {}),\n // Where it sat on the old page, so a page copied as it is places it there (AGL-4084).\n from: image.url,\n })\n } catch (error) {\n console.warn('[site-import] a photo could not be brought in', { hostId: input.hostId, error })\n skipped += 1\n }\n }\n return { brought, reused, skipped, assets }\n}\n\n/*------------------------------------------\n * The site's logo (AGL-4083)\n *-----------------------------------------*/\n\n/**\n * A picture that may be the site's logo, where it was found. `type` is what\n * its address or its declared type says: `svg`, a raster, or `null` when\n * neither says (a media library address with no extension); the ingest\n * reads the answer's own type either way.\n */\nexport interface SiteImportLogoCandidate {\n url: string\n alt: string\n source: 'header' | 'footer' | 'structured-data' | 'apple-touch-icon' | 'icon'\n type: 'svg' | 'raster' | null\n}\n\n/** A logo's address as it is fetched: https, no fragment or tracking parameter. An SVG is a logo, unlike a photo. */\nexport function siteImportLogoUrl(raw: string): string | null {\n let url: URL\n try {\n url = new URL(String(raw ?? '').trim())\n } catch {\n return null\n }\n if (url.protocol === 'http:') url.protocol = 'https:'\n if (url.protocol !== 'https:' || url.username || url.password) return null\n if (url.port && url.port !== '443') return null\n url.hash = ''\n for (const key of [...url.searchParams.keys()]) {\n if (TRACKING_PARAM.test(key)) url.searchParams.delete(key)\n }\n return url.toString()\n}\n\nfunction logoTypeOf(url: string, declared?: string | null): SiteImportLogoCandidate['type'] {\n const type = (declared ?? '').toLowerCase()\n if (type.includes('svg')) return 'svg'\n if (/^image\\/(?:png|jpe?g|webp|gif|x-icon|vnd\\.microsoft\\.icon)/.test(type)) return 'raster'\n let path: string\n try {\n path = new URL(url).pathname.toLowerCase()\n } catch {\n return null\n }\n if (path.endsWith('.svg')) return 'svg'\n if (/\\.(?:png|jpe?g|webp|gif|ico)$/.test(path)) return 'raster'\n return null\n}\n\n/**\n * The pictures that may be the site's logo (AGL-4083), the likeliest first:\n * the picture in the header's link home, any header picture that says it is\n * the logo or the site's name, the footer's, the structured data's logo,\n * then the largest touch icon and the largest icon. Each address once.\n */\nexport function siteImportLogoCandidates(\n page: Pick<SiteImportPage, 'brand' | 'images' | 'icons' | 'siteName' | 'organization' | 'title'>,\n): SiteImportLogoCandidate[] {\n const name = (page.siteName || page.organization?.name || '').trim().toLowerCase()\n const says = (image: { url: string; alt: string }) => {\n const alt = image.alt.trim().toLowerCase()\n return /\\blogo\\b/i.test(image.alt) || /logo/i.test(image.url) || (!!name && (alt === name || alt === `${name} logo`))\n }\n const out: SiteImportLogoCandidate[] = []\n const add = (raw: string, alt: string, source: SiteImportLogoCandidate['source'], declared?: string | null) => {\n const url = siteImportLogoUrl(raw)\n if (!url || out.some((entry) => entry.url === url)) return\n out.push({ url, alt: alt.slice(0, 250), source, type: logoTypeOf(url, declared) })\n }\n if (page.brand?.logo && page.brand.region !== 'footer') add(page.brand.logo.url, page.brand.logo.alt, 'header')\n for (const image of page.images) if (image.region === 'header' && says(image)) add(image.url, image.alt, 'header')\n for (const image of page.images) if (image.region === 'footer' && says(image)) add(image.url, image.alt, 'footer')\n if (page.organization?.logo) add(page.organization.logo, page.organization.name, 'structured-data')\n const touch = page.icons.filter((icon) => icon.rel === 'apple-touch-icon').sort((a, b) => (b.size ?? 0) - (a.size ?? 0))[0]\n if (touch) add(touch.url, '', 'apple-touch-icon', touch.type)\n const icon = page.icons.filter((entry) => entry.rel === 'icon').sort((a, b) => (b.size ?? 0) - (a.size ?? 0))[0]\n if (icon) add(icon.url, '', 'icon', icon.type)\n return out\n}\n"],"names":["createHash","fetchPublicTextDetailed","AGLYN_BOT_USER_AGENT","SITE_IMPORT_IMAGE_MAX_BYTES","SITE_IMPORT_IMAGE_MIN_BYTES","SITE_IMPORT_IMAGE_TIMEOUT_MS","RASTER","NOT_A_PHOTO","TRACKING_PARAM","EXTENSION","siteImportImageUrl","raw","url","URL","String","trim","protocol","username","password","port","test","pathname","hash","key","searchParams","keys","delete","toString","siteImportImageKey","update","digest","slice","siteImportImageFileName","contentType","base","last","decodeURIComponent","split","filter","Boolean","pop","replace","type","toLowerCase","bringInSiteImportPhotos","input","deps","now","Date","fetchBytes","fetch","assets","brought","reused","skipped","ingest","findImportedPhoto","images","length","seen","Set","image","has","add","signal","aborted","deadlineAt","sourceKey","held","hostId","push","mediaId","src","width","height","alt","from","fetched","maxBytes","accept","acceptHeader","userAgent","timeoutMs","Math","max","min","refuseOver","binary","ok","data","byteLength","stored","uid","fileName","bytes","importedFrom","domain","error","console","warn","siteImportLogoUrl","logoTypeOf","declared","includes","path","endsWith","siteImportLogoCandidates","page","name","siteName","organization","says","out","source","some","entry","brand","logo","region","touch","icons","icon","rel","sort","a","b","size"],"mappings":"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"}
|
|
@@ -16,9 +16,11 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { type AiSiteImportLeftOutReason, type AiSiteImportListedPage, type AiSiteImportPageRead } from '../model/ai-site-import';
|
|
18
18
|
import type { SiteImportPage, SiteImportSkipReason } from './site-import-crawl';
|
|
19
|
+
import type { SiteImportBlock, SiteImportOrganization } from './site-import-html';
|
|
20
|
+
import { type SiteImportLogoCandidate } from './site-import-images';
|
|
19
21
|
/** A line of untrusted text safe to show outside a fence: neutralized, one line, cut short. */
|
|
20
22
|
export declare function siteImportLabel(text: string, max?: number): string;
|
|
21
|
-
/** How many words a page says, its title and headings included. */
|
|
23
|
+
/** How many words a page says, its title and headings included; a link inside a sentence is counted once. */
|
|
22
24
|
export declare function siteImportPageWords(page: Pick<SiteImportPage, 'title' | 'blocks'>): number;
|
|
23
25
|
/**
|
|
24
26
|
* A page's name on the new site: "Home" for the home page, else its browser
|
|
@@ -42,7 +44,47 @@ export declare function siteImportPageImages(page: Pick<SiteImportPage, 'blocks'
|
|
|
42
44
|
export declare function siteImportPageSections(page: Pick<SiteImportPage, 'title' | 'blocks'>): string[];
|
|
43
45
|
/** The page's text in order, as a prompt shows it inside its fence. */
|
|
44
46
|
export declare function siteImportPageText(page: Pick<SiteImportPage, 'title' | 'description' | 'blocks'>): string;
|
|
45
|
-
/** The page as a wave reads it. */
|
|
47
|
+
/** The page as a wave reads it: its main region's blocks whole, within the read's budget. */
|
|
46
48
|
export declare function siteImportPageRead(page: SiteImportPage, at: string): AiSiteImportPageRead;
|
|
49
|
+
/**
|
|
50
|
+
* How the source's header draws its brand: its logo alone (aglyn.com), its
|
|
51
|
+
* logo with its name beside it, or its name alone. `text` is the name as the
|
|
52
|
+
* header prints it, or the site's own name when it prints nothing at all.
|
|
53
|
+
*/
|
|
54
|
+
export interface SiteImportHeaderBrand {
|
|
55
|
+
kind: 'logo-only' | 'logo-and-name' | 'name-only';
|
|
56
|
+
logo: SiteImportLogoCandidate | null;
|
|
57
|
+
/** Whether the header prints the name as words. */
|
|
58
|
+
name: boolean;
|
|
59
|
+
text: string;
|
|
60
|
+
}
|
|
61
|
+
/** The site's own name: its head's, its structured data's, else its home page's title before a separator. */
|
|
62
|
+
export declare function siteImportSiteName(home: Pick<SiteImportPage, 'siteName' | 'organization' | 'title'>): string;
|
|
63
|
+
/**
|
|
64
|
+
* The home page's header brand (AGL-4083), as the source shows it: the link
|
|
65
|
+
* home in its header, the picture inside it, and whether words sit beside
|
|
66
|
+
* the picture, inside the link or next to it. A header with no picture link
|
|
67
|
+
* home is the name alone.
|
|
68
|
+
*/
|
|
69
|
+
export declare function siteImportHeaderBrand(home: Pick<SiteImportPage, 'brand' | 'images' | 'icons' | 'siteName' | 'organization' | 'title' | 'blocks'>): SiteImportHeaderBrand;
|
|
70
|
+
/** The facts a site's header and footer carry, as the source writes them. */
|
|
71
|
+
export interface SiteImportSiteFacts {
|
|
72
|
+
header: SiteImportBlock[];
|
|
73
|
+
footer: SiteImportBlock[];
|
|
74
|
+
organization: SiteImportOrganization | null;
|
|
75
|
+
/** Email addresses, from its structured data and its `mailto:` links. */
|
|
76
|
+
emails: string[];
|
|
77
|
+
/** Phone numbers as written, from its structured data and its `tel:` links. */
|
|
78
|
+
phones: string[];
|
|
79
|
+
/** Its profiles on other sites: its structured data's `sameAs` and its links to them. */
|
|
80
|
+
social: string[];
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The home page's own facts (AGL-4083), word for word: its header and footer
|
|
84
|
+
* blocks, its Organization structured data, and the email addresses, phone
|
|
85
|
+
* numbers and profiles they hold. Nothing here is invented or reworded; a
|
|
86
|
+
* fact the page does not state is not here.
|
|
87
|
+
*/
|
|
88
|
+
export declare function siteImportSiteFacts(home: Pick<SiteImportPage, 'blocks' | 'organization'>): SiteImportSiteFacts;
|
|
47
89
|
/** Why the crawler could not read a page, as an import leaves it out; `status` is the answer an `http-error` got. */
|
|
48
90
|
export declare function siteImportLeftOutReason(reason: SiteImportSkipReason, status?: number): AiSiteImportLeftOutReason;
|