{"version":3,"file":"execute.mjs","names":[],"sources":["../../../../../../src/astro/routes/api/import/wordpress-plugin/execute.ts"],"sourcesContent":["/**\n * WordPress Plugin execute import endpoint\n *\n * POST /_emdash/api/import/wordpress-plugin/execute\n *\n * Imports content from WordPress via EmDash Exporter plugin API.\n */\n\nimport type { APIRoute } from \"astro\";\nimport {\n\tContentRepository,\n\tSchemaRegistry,\n\ttype WxrCategory,\n\ttype WxrPost,\n\ttype WxrTag,\n\ttype WxrTerm,\n} from \"@premium-cms/emdash\";\nimport type { z } from \"zod\";\n\nimport { requirePerm } from \"#api/authorize.js\";\nimport { apiError, apiSuccess, handleError } from \"#api/error.js\";\nimport { handleTaxonomyCreate } from \"#api/handlers/taxonomies.js\";\nimport { isParseError, parseBody } from \"#api/parse.js\";\nimport { wpPluginExecuteBody } from \"#api/schemas.js\";\nimport { BylineRepository } from \"#db/repositories/byline.js\";\nimport { importCommentsFromPlugin } from \"#import/comments.js\";\nimport { getSource } from \"#import/index.js\";\nimport { importMenusFromPlugin } from \"#import/menus.js\";\nimport { importSiteSettings, parseSiteSettingsFromPlugin } from \"#import/settings.js\";\nimport {\n\tfetchPluginComments,\n\tfetchPluginCommentsPage,\n\tfetchPluginContentPage,\n\tfetchPluginMenus,\n\tfetchPluginOptions,\n\tfetchPluginTaxonomies,\n} from \"#import/sources/wordpress-plugin.js\";\nimport { resolveAndValidateExternalUrl, SsrfError } from \"#import/ssrf.js\";\nimport type { ImportConfig, ImportResult, NormalizedItem } from \"#import/types.js\";\nimport { resolveImportByline, sanitizeFieldSlug } from \"#import/utils.js\";\nimport {\n\tattachPostTaxonomies,\n\tloadTaxonomyPlanFromDb,\n\tpreImportWxrTaxonomies,\n\ttype TaxonomyImportPlan,\n} from \"#import/wxr-taxonomies.js\";\nimport type { FieldType } from \"#schema/types.js\";\nimport type { EmDashHandlers, EmDashManifest } from \"#types\";\nimport { slugify } from \"#utils/slugify.js\";\n\nimport { importMediaWithProgress } from \"../wordpress/media.js\";\n\nexport const prerender = false;\n\nexport interface WpPluginImportConfig extends ImportConfig {\n\t/** Author mappings (WP author login -> EmDash user ID) */\n\tauthorMappings?: Record<string, string | null>;\n\t/** Wizard toggles. Absent means enabled (older admins don't send them). */\n\timportMenus?: boolean;\n\timportSiteTitle?: boolean;\n\timportLogo?: boolean;\n\timportSeo?: boolean;\n}\n\nexport interface WpPluginImportResponse {\n\tsuccess: boolean;\n\tresult?: ImportResult;\n\terror?: { message: string };\n}\n\n// =============================================================================\n// Chunked mode (issue #475)\n//\n// A single-invocation import of a large site exceeds Cloudflare Worker\n// resource limits (CPU time, subrequests). In chunked mode the admin drives\n// the import as a loop of bounded requests: one WP content page per call,\n// then paginated comments, then a small finalize step (menus + site\n// identity). Cross-chunk state — the WP-ID -> EmDash-ID map, translation\n// groups, comment threading roots — is accumulated by the client and sent\n// back with each request, so the server stays stateless and an aborted\n// import simply re-runs (skipExisting rebuilds the map while skipping work).\n// =============================================================================\n\n/** Posts per content chunk. 50 posts ≈ a few hundred D1 ops per invocation. */\nconst CONTENT_CHUNK_SIZE = 50;\n\ninterface ChunkCursor {\n\tpostTypeIndex: number;\n\tpage: number;\n}\n\n/** Per-chunk response payload: partial result + state the client must carry. */\ninterface ChunkResponse {\n\tsuccess: boolean;\n\tresult: ImportResult;\n\tdone: boolean;\n\tcursor?: ChunkCursor;\n\tchunk?: {\n\t\tidMap?: Record<string, { id: string; collection: string }>;\n\t\ttranslationGroups?: Record<string, string>;\n\t\tcommentRoots?: Record<string, string>;\n\t};\n}\n\nfunction emptyImportResult(): ImportResult {\n\treturn { success: true, imported: 0, skipped: 0, errors: [], byCollection: {} };\n}\n\nfunction parseIdMap(idMap: Record<string, { id: string; collection: string }> | undefined): {\n\tcontentIdMap: Map<number, string>;\n\tcollectionByWpId: Map<number, string>;\n} {\n\tconst contentIdMap = new Map<number, string>();\n\tconst collectionByWpId = new Map<number, string>();\n\tfor (const [key, value] of Object.entries(idMap ?? {})) {\n\t\tconst wpId = Number(key);\n\t\tif (!Number.isFinite(wpId)) continue;\n\t\tcontentIdMap.set(wpId, value.id);\n\t\tcollectionByWpId.set(wpId, value.collection);\n\t}\n\treturn { contentIdMap, collectionByWpId };\n}\n\nfunction serializeIdMap(\n\tcontentIdMap: Map<number, string>,\n\tcollectionByWpId: Map<number, string>,\n): Record<string, { id: string; collection: string }> {\n\tconst out: Record<string, { id: string; collection: string }> = {};\n\tfor (const [wpId, id] of contentIdMap) {\n\t\tconst collection = collectionByWpId.get(wpId);\n\t\tif (collection) out[String(wpId)] = { id, collection };\n\t}\n\treturn out;\n}\n\nexport const POST: APIRoute = async ({ request, locals }) => {\n\tconst { emdash, user } = locals;\n\n\tconst denied = requirePerm(user, \"import:execute\");\n\tif (denied) return denied;\n\n\tif (!emdash?.handleContentCreate) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash not configured\", 500);\n\t}\n\n\ttry {\n\t\tconst emdashManifest = await emdash.getManifest();\n\n\t\tconst body = await parseBody(request, wpPluginExecuteBody);\n\t\tif (isParseError(body)) return body;\n\n\t\t// SSRF: reject internal/private network targets. Uses DNS resolution\n\t\t// to catch hostnames that resolve to private addresses.\n\t\ttry {\n\t\t\tawait resolveAndValidateExternalUrl(body.url);\n\t\t} catch (e) {\n\t\t\tconst msg = e instanceof SsrfError ? e.message : \"Invalid URL\";\n\t\t\treturn apiError(\"SSRF_BLOCKED\", msg, 400);\n\t\t}\n\n\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- Zod schema output narrowed to WpPluginImportConfig\n\t\tconst config = body.config as unknown as WpPluginImportConfig;\n\n\t\t// Get the WordPress plugin source\n\t\tconst source = getSource(\"wordpress-plugin\");\n\t\tif (!source) {\n\t\t\treturn apiError(\"NOT_CONFIGURED\", \"WordPress plugin source not available\", 500);\n\t\t}\n\n\t\t// Build the list of post types to fetch\n\t\tconst postTypes = Object.entries(config.postTypeMappings)\n\t\t\t.filter(([_, mapping]) => mapping.enabled)\n\t\t\t.map(([postType]) => postType);\n\n\t\tif (postTypes.length === 0) {\n\t\t\treturn apiError(\"VALIDATION_ERROR\", \"No post types selected for import\", 400);\n\t\t}\n\n\t\tconsole.log(\"[WP Plugin Import] Starting import for:\", body.url);\n\t\tconsole.log(\"[WP Plugin Import] Post types:\", postTypes);\n\n\t\t// Chunked mode: one bounded unit of work per invocation (issue #475).\n\t\tif (body.phase) {\n\t\t\tconst chunk = await runImportPhase(emdash, body, config, postTypes, emdashManifest);\n\t\t\treturn apiSuccess(chunk);\n\t\t}\n\n\t\t// Pre-create taxonomy defs (for custom CPT taxonomies) and terms so\n\t\t// per-post assignments can attach.\n\t\t// Non-fatal: a site whose /taxonomies call fails still gets content.\n\t\tlet taxonomyPlan: TaxonomyImportPlan | undefined;\n\t\tlet taxonomyDefsCreated: string[] = [];\n\t\ttry {\n\t\t\tconst built = await buildTaxonomyPlan(emdash, body.url, body.token, config);\n\t\t\ttaxonomyPlan = built.plan;\n\t\t\ttaxonomyDefsCreated = built.defsCreated;\n\t\t} catch (e) {\n\t\t\tconsole.warn(\"[WP Plugin Import] Taxonomy pre-import failed:\", e);\n\t\t}\n\n\t\t// Import content (including drafts since we have auth)\n\t\tconst { result, contentIdMap, collectionByWpId } = await importContent(\n\t\t\tsource.fetchContent(\n\t\t\t\t{ type: \"url\", url: body.url, token: body.token },\n\t\t\t\t{ postTypes, includeDrafts: true },\n\t\t\t),\n\t\t\tconfig,\n\t\t\temdash,\n\t\t\temdashManifest,\n\t\t\ttaxonomyPlan,\n\t\t);\n\n\t\t// Import navigation menus, resolving item references through the\n\t\t// WP-post-ID -> EmDash-ID map collected during the content pass.\n\t\tif (config.importMenus !== false) {\n\t\t\tawait importMenusInto(result, emdash, body.url, body.token, contentIdMap);\n\t\t}\n\n\t\t// Import comments into EmDash's native comments table, preserving\n\t\t// authors, dates, threading, and approval status.\n\t\t// Non-fatal: older plugin versions have no /comments endpoint.\n\t\ttry {\n\t\t\tconst comments = await fetchPluginComments(body.url, body.token);\n\t\t\tif (comments.length > 0) {\n\t\t\t\tconst commentsResult = await importCommentsFromPlugin(\n\t\t\t\t\tcomments,\n\t\t\t\t\temdash.db,\n\t\t\t\t\tcontentIdMap,\n\t\t\t\t\tcollectionByWpId,\n\t\t\t\t);\n\t\t\t\tresult.comments = {\n\t\t\t\t\timported: commentsResult.imported,\n\t\t\t\t\tskipped: commentsResult.skipped,\n\t\t\t\t};\n\t\t\t\tfor (const commentError of commentsResult.errors) {\n\t\t\t\t\tresult.errors.push({\n\t\t\t\t\t\ttitle: `Comment: ${commentError.comment}`,\n\t\t\t\t\t\terror: commentError.error,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tconsole.warn(\"[WP Plugin Import] Comment import failed:\", e);\n\t\t}\n\n\t\t// Apply site identity (title, tagline, logo, favicon) from the source\n\t\t// site's options, replacing the starter template's seed placeholders.\n\t\t// Non-fatal: content is already imported at this point.\n\t\ttry {\n\t\t\tresult.siteSettings = await applySiteSettings(emdash, body.url, body.token, config);\n\t\t} catch (e) {\n\t\t\tconsole.warn(\"[WP Plugin Import] Site settings import failed:\", e);\n\t\t}\n\n\t\tif (taxonomyDefsCreated.length > 0) {\n\t\t\tresult.taxonomiesCreated = taxonomyDefsCreated;\n\t\t}\n\n\t\tconsole.log(\"[WP Plugin Import] Import result:\", JSON.stringify(result, null, 2));\n\n\t\treturn apiSuccess({\n\t\t\tsuccess: true,\n\t\t\tresult,\n\t\t});\n\t} catch (error) {\n\t\treturn handleError(error, \"Failed to import from WordPress\", \"WP_PLUGIN_IMPORT_ERROR\");\n\t}\n};\n\n/**\n * Import navigation menus into `result`, resolving item references through\n * the WP-post-ID -> EmDash-ID map collected during the content pass.\n * Non-fatal: older plugin versions have no /menus endpoint.\n */\nasync function importMenusInto(\n\tresult: ImportResult,\n\temdash: EmDashHandlers,\n\turl: string,\n\ttoken: string,\n\tcontentIdMap: Map<number, string>,\n): Promise<void> {\n\ttry {\n\t\tconst menus = await fetchPluginMenus(url, token);\n\t\tif (menus.length > 0) {\n\t\t\tconst menuResult = await importMenusFromPlugin(menus, emdash.db, contentIdMap);\n\t\t\tresult.menus = {\n\t\t\t\tcreated: menuResult.menusCreated,\n\t\t\t\titems: menuResult.itemsCreated,\n\t\t\t};\n\t\t\tfor (const menuError of menuResult.errors) {\n\t\t\t\tresult.errors.push({ title: `Menu: ${menuError.menu}`, error: menuError.error });\n\t\t\t}\n\t\t}\n\t} catch (e) {\n\t\tconsole.warn(\"[WP Plugin Import] Menu import failed:\", e);\n\t}\n}\n\ntype ExecuteBody = z.infer<typeof wpPluginExecuteBody>;\n\n/** Dispatch one chunk of work for the requested phase. */\nasync function runImportPhase(\n\temdash: EmDashHandlers,\n\tbody: ExecuteBody,\n\tconfig: WpPluginImportConfig,\n\tpostTypes: string[],\n\tmanifest: EmDashManifest,\n): Promise<ChunkResponse> {\n\tswitch (body.phase) {\n\t\tcase \"content\":\n\t\t\treturn runContentChunk(emdash, body, config, postTypes, manifest);\n\t\tcase \"comments\":\n\t\t\treturn runCommentsChunk(emdash, body);\n\t\tcase \"finalize\":\n\t\t\treturn runFinalizePhase(emdash, body, config);\n\t\tcase undefined:\n\t\t\tthrow new Error(\"runImportPhase called without a phase\");\n\t\tdefault:\n\t\t\tbody.phase satisfies never;\n\t\t\tthrow new Error(\"Unknown import phase\");\n\t}\n}\n\n/**\n * Import one page of one post type. The first chunk additionally runs the\n * taxonomy setup (def + term creation); later chunks only reload the\n * lookup maps from the database.\n */\nasync function runContentChunk(\n\temdash: EmDashHandlers,\n\tbody: ExecuteBody,\n\tconfig: WpPluginImportConfig,\n\tpostTypes: string[],\n\tmanifest: EmDashManifest,\n): Promise<ChunkResponse> {\n\tconst cursor = body.cursor ?? { postTypeIndex: 0, page: 1 };\n\tconst postType = postTypes[cursor.postTypeIndex];\n\tif (!postType) {\n\t\treturn { success: true, result: emptyImportResult(), done: true };\n\t}\n\n\tconst isFirstChunk = cursor.postTypeIndex === 0 && cursor.page === 1;\n\tlet taxonomyPlan: TaxonomyImportPlan | undefined;\n\tlet taxonomyDefsCreated: string[] = [];\n\ttry {\n\t\tif (isFirstChunk) {\n\t\t\tconst built = await buildTaxonomyPlan(emdash, body.url, body.token, config);\n\t\t\ttaxonomyPlan = built.plan;\n\t\t\ttaxonomyDefsCreated = built.defsCreated;\n\t\t} else {\n\t\t\ttaxonomyPlan = await loadTaxonomyPlanFromDb(emdash.db);\n\t\t}\n\t} catch (e) {\n\t\tconsole.warn(\"[WP Plugin Import] Taxonomy pre-import failed:\", e);\n\t}\n\n\tconst page = await fetchPluginContentPage({\n\t\tsiteUrl: body.url,\n\t\ttoken: body.token,\n\t\tpostType,\n\t\tpage: cursor.page,\n\t\tperPage: CONTENT_CHUNK_SIZE,\n\t\tincludeDrafts: true,\n\t});\n\n\t// Seed translation-group state from earlier chunks so a translation in\n\t// this page links to its sibling imported three chunks ago.\n\tconst translationGroupMap = new Map(Object.entries(body.translationGroups ?? {}));\n\n\tconst { result, contentIdMap, collectionByWpId } = await importContent(\n\t\tpage.items,\n\t\tconfig,\n\t\temdash,\n\t\tmanifest,\n\t\ttaxonomyPlan,\n\t\ttranslationGroupMap,\n\t);\n\n\tif (taxonomyDefsCreated.length > 0) {\n\t\tresult.taxonomiesCreated = taxonomyDefsCreated;\n\t}\n\n\t// Advance: next page of this post type, first page of the next one,\n\t// or done. An empty last page (totalPages can shrink while paginating\n\t// a live site) still terminates because page >= totalPages.\n\tlet next: ChunkCursor | undefined;\n\tif (cursor.page < page.totalPages) {\n\t\tnext = { postTypeIndex: cursor.postTypeIndex, page: cursor.page + 1 };\n\t} else if (cursor.postTypeIndex + 1 < postTypes.length) {\n\t\tnext = { postTypeIndex: cursor.postTypeIndex + 1, page: 1 };\n\t}\n\n\treturn {\n\t\tsuccess: true,\n\t\tresult,\n\t\tdone: next === undefined,\n\t\tcursor: next,\n\t\tchunk: {\n\t\t\tidMap: serializeIdMap(contentIdMap, collectionByWpId),\n\t\t\ttranslationGroups: Object.fromEntries(translationGroupMap),\n\t\t},\n\t};\n}\n\n/**\n * Import one page of comments (500 per page, ordered by WP comment ID so\n * parents precede children across pages). Requires the accumulated idMap\n * from the content phase; threading roots accumulate in `commentRoots`.\n */\nasync function runCommentsChunk(emdash: EmDashHandlers, body: ExecuteBody): Promise<ChunkResponse> {\n\tconst page = body.cursor?.page ?? 1;\n\tconst result = emptyImportResult();\n\n\tconst { contentIdMap, collectionByWpId } = parseIdMap(body.idMap);\n\tconst rootIds = new Map<number, string>();\n\tfor (const [key, value] of Object.entries(body.commentRoots ?? {})) {\n\t\tconst wpId = Number(key);\n\t\tif (Number.isFinite(wpId)) rootIds.set(wpId, value);\n\t}\n\n\tconst { items, totalPages } = await fetchPluginCommentsPage(body.url, body.token, page);\n\n\tif (items.length > 0) {\n\t\tconst commentsResult = await importCommentsFromPlugin(\n\t\t\titems,\n\t\t\temdash.db,\n\t\t\tcontentIdMap,\n\t\t\tcollectionByWpId,\n\t\t\trootIds,\n\t\t);\n\t\tresult.comments = {\n\t\t\timported: commentsResult.imported,\n\t\t\tskipped: commentsResult.skipped,\n\t\t};\n\t\tfor (const commentError of commentsResult.errors) {\n\t\t\tresult.errors.push({ title: `Comment: ${commentError.comment}`, error: commentError.error });\n\t\t}\n\t\tresult.success = result.errors.length === 0;\n\t}\n\n\tconst done = page >= totalPages;\n\tconst commentRoots: Record<string, string> = {};\n\tfor (const [wpId, id] of rootIds) commentRoots[String(wpId)] = id;\n\n\treturn {\n\t\tsuccess: true,\n\t\tresult,\n\t\tdone,\n\t\tcursor: done ? undefined : { postTypeIndex: 0, page: page + 1 },\n\t\tchunk: { commentRoots },\n\t};\n}\n\n/** Menus + site identity — small, runs as a single closing chunk. */\nasync function runFinalizePhase(\n\temdash: EmDashHandlers,\n\tbody: ExecuteBody,\n\tconfig: WpPluginImportConfig,\n): Promise<ChunkResponse> {\n\tconst result = emptyImportResult();\n\tconst { contentIdMap } = parseIdMap(body.idMap);\n\n\tif (config.importMenus !== false) {\n\t\tawait importMenusInto(result, emdash, body.url, body.token, contentIdMap);\n\t}\n\n\ttry {\n\t\tresult.siteSettings = await applySiteSettings(emdash, body.url, body.token, config);\n\t} catch (e) {\n\t\tconsole.warn(\"[WP Plugin Import] Site settings import failed:\", e);\n\t}\n\n\tresult.success = result.errors.length === 0;\n\treturn { success: true, result, done: true };\n}\n\n/** Fields that should be auto-created if they don't exist */\nconst IMPORT_FIELDS: Array<{\n\tslug: string;\n\tlabel: string;\n\ttype: FieldType;\n\tcheck: (item: NormalizedItem) => boolean;\n}> = [\n\t{\n\t\tslug: \"title\",\n\t\tlabel: \"Title\",\n\t\ttype: \"string\",\n\t\tcheck: () => true,\n\t},\n\t{\n\t\tslug: \"content\",\n\t\tlabel: \"Content\",\n\t\ttype: \"portableText\",\n\t\tcheck: () => true,\n\t},\n\t{\n\t\tslug: \"excerpt\",\n\t\tlabel: \"Excerpt\",\n\t\ttype: \"text\",\n\t\tcheck: (item) => !!item.excerpt,\n\t},\n\t{\n\t\tslug: \"featured_image\",\n\t\tlabel: \"Featured Image\",\n\t\ttype: \"image\",\n\t\tcheck: (item) => !!item.featuredImage,\n\t},\n\t{\n\t\tslug: \"seo_title\",\n\t\tlabel: \"SEO Title\",\n\t\ttype: \"string\",\n\t\tcheck: (item) => !!extractSeo(item).title,\n\t},\n\t{\n\t\tslug: \"seo_description\",\n\t\tlabel: \"SEO Description\",\n\t\ttype: \"text\",\n\t\tcheck: (item) => !!extractSeo(item).description,\n\t},\n];\n\nconst SEO_FIELD_SLUGS = new Set([\"seo_title\", \"seo_description\"]);\n\n/**\n * Coerce a WordPress meta value to an EmDash field type. WP postmeta is\n * stringly typed and inconsistent across posts (the same key can hold\n * \"5\", 5, \"\", or false). Returns `undefined` when the value can't\n * reasonably represent the target type — the caller drops it.\n */\nexport function coerceToFieldType(value: unknown, fieldType: string): unknown {\n\tswitch (fieldType) {\n\t\tcase \"integer\": {\n\t\t\tconst n = typeof value === \"number\" ? value : Number(value);\n\t\t\treturn Number.isInteger(n) ? n : undefined;\n\t\t}\n\t\tcase \"number\": {\n\t\t\tconst n = typeof value === \"number\" ? value : Number(value);\n\t\t\treturn Number.isFinite(n) ? n : undefined;\n\t\t}\n\t\tcase \"boolean\": {\n\t\t\tif (typeof value === \"boolean\") return value;\n\t\t\tif (value === 1 || value === \"1\" || value === \"true\" || value === \"yes\") return true;\n\t\t\tif (value === 0 || value === \"0\" || value === \"false\" || value === \"no\") return false;\n\t\t\treturn undefined;\n\t\t}\n\t\tcase \"datetime\": {\n\t\t\tif (typeof value !== \"string\" && typeof value !== \"number\") return undefined;\n\t\t\tconst d = new Date(value);\n\t\t\treturn Number.isNaN(d.getTime()) ? undefined : d.toISOString();\n\t\t}\n\t\tcase \"json\":\n\t\t\t// Anything serializes; objects/arrays pass through as-is\n\t\t\treturn value;\n\t\tcase \"string\":\n\t\tcase \"text\":\n\t\tcase \"url\":\n\t\tcase \"select\":\n\t\tcase \"slug\":\n\t\tcase \"reference\":\n\t\t\tif (typeof value === \"string\") return value;\n\t\t\tif (typeof value === \"number\") return String(value);\n\t\t\treturn undefined;\n\t\tdefault:\n\t\t\t// Complex types (image, portableText, repeater, multiSelect, file):\n\t\t\t// raw meta can't be coerced reliably -- pass through only if it\n\t\t\t// already has a non-primitive shape.\n\t\t\treturn typeof value === \"object\" ? value : undefined;\n\t}\n}\n\n/**\n * Pull the per-post SEO title/description out of the Yoast / Rank Math\n * blobs the plugin source stashes in `item.meta`. Empty strings mean \"not\n * overridden for this post\" (the plugin exports the raw meta values) and\n * are treated as absent.\n */\nfunction pickSeoValue(blob: unknown, key: string): string | undefined {\n\tif (typeof blob !== \"object\" || blob === null) return undefined;\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- narrowed to non-null object above\n\tconst value = (blob as Record<string, unknown>)[key];\n\treturn typeof value === \"string\" && value.trim() !== \"\" ? value : undefined;\n}\n\nfunction extractSeo(item: NormalizedItem): { title?: string; description?: string } {\n\tconst yoast = item.meta?._yoast;\n\tconst rankmath = item.meta?._rankmath;\n\treturn {\n\t\ttitle: pickSeoValue(yoast, \"title\") ?? pickSeoValue(rankmath, \"title\"),\n\t\tdescription: pickSeoValue(yoast, \"description\") ?? pickSeoValue(rankmath, \"description\"),\n\t};\n}\n\n/**\n * Fetch the site's taxonomies from the plugin API and pre-create all terms,\n * reusing the WXR taxonomy machinery (same def-lookup, idempotency, and\n * collection-filter semantics).\n */\n/** WP built-in taxonomies that either map to seeded defs or are not content taxonomies. */\nconst BUILTIN_TAXONOMIES = new Set([\"category\", \"post_tag\", \"nav_menu\", \"post_format\"]);\n\n/** Mirrors NAME_PATTERN in the taxonomy handler -- names that fail stay in missingTaxonomies. */\nconst TAXONOMY_NAME_PATTERN = /^[a-z][a-z0-9_]*$/;\n\n/**\n * Create EmDash taxonomy defs for custom WP taxonomies (e.g. CPT taxonomies\n * like `company` or `plattform`) that don't exist yet, scoped to the\n * collections the enabled post-type mappings target. Returns the names of\n * the defs created. Runs before term pre-import so the terms and per-post\n * assignments flow through the existing machinery instead of being dropped\n * as `missingTaxonomies`.\n */\nexport async function ensureCustomTaxonomyDefs(\n\tdb: EmDashHandlers[\"db\"],\n\ttaxonomies: Awaited<ReturnType<typeof fetchPluginTaxonomies>>,\n\tconfig: WpPluginImportConfig,\n): Promise<string[]> {\n\tconst created: string[] = [];\n\tfor (const taxonomy of taxonomies) {\n\t\tif (BUILTIN_TAXONOMIES.has(taxonomy.name)) continue;\n\t\tif (taxonomy.terms.length === 0) continue;\n\t\tif (!TAXONOMY_NAME_PATTERN.test(taxonomy.name)) continue;\n\n\t\t// handleTaxonomyCreate's duplicate guard is locale-scoped; check\n\t\t// name-wide existence ourselves to stay idempotent on re-runs.\n\t\tconst existing = await db\n\t\t\t.selectFrom(\"_emdash_taxonomy_defs\")\n\t\t\t.select(\"id\")\n\t\t\t.where(\"name\", \"=\", taxonomy.name)\n\t\t\t.executeTakeFirst();\n\t\tif (existing) continue;\n\n\t\t// Scope the def to the collections the WP post types map onto.\n\t\t// Older plugin versions don't send post_types -> empty list = no\n\t\t// collection filter, which the term machinery treats as \"any\".\n\t\tconst collections = (taxonomy.post_types ?? [])\n\t\t\t.map((postType) => config.postTypeMappings[postType])\n\t\t\t.filter((mapping) => mapping?.enabled)\n\t\t\t.map((mapping) => mapping.collection);\n\n\t\tconst result = await handleTaxonomyCreate(db, {\n\t\t\tname: taxonomy.name,\n\t\t\tlabel: taxonomy.label,\n\t\t\tlabelSingular: taxonomy.label_singular,\n\t\t\thierarchical: taxonomy.hierarchical,\n\t\t\tcollections: [...new Set(collections)],\n\t\t});\n\t\tif (result.success) {\n\t\t\tcreated.push(taxonomy.name);\n\t\t} else {\n\t\t\tconsole.warn(\n\t\t\t\t`[WP Plugin Import] Could not create taxonomy '${taxonomy.name}':`,\n\t\t\t\tresult.error.message,\n\t\t\t);\n\t\t}\n\t}\n\treturn created;\n}\n\nasync function buildTaxonomyPlan(\n\temdash: EmDashHandlers,\n\turl: string,\n\ttoken: string,\n\tconfig: WpPluginImportConfig,\n): Promise<{ plan: TaxonomyImportPlan; defsCreated: string[] }> {\n\tconst taxonomies = await fetchPluginTaxonomies(url, token);\n\n\tconst defsCreated = await ensureCustomTaxonomyDefs(emdash.db, taxonomies, config);\n\n\tconst categories: WxrCategory[] = [];\n\tconst tags: WxrTag[] = [];\n\tconst terms: WxrTerm[] = [];\n\n\tfor (const taxonomy of taxonomies) {\n\t\tfor (const term of taxonomy.terms) {\n\t\t\tif (taxonomy.name === \"category\") {\n\t\t\t\tcategories.push({ nicename: term.slug, name: term.name, description: term.description });\n\t\t\t} else if (taxonomy.name === \"post_tag\") {\n\t\t\t\ttags.push({ slug: term.slug, name: term.name, description: term.description });\n\t\t\t} else if (taxonomy.name !== \"nav_menu\" && taxonomy.name !== \"post_format\") {\n\t\t\t\tterms.push({\n\t\t\t\t\tid: term.id,\n\t\t\t\t\ttaxonomy: taxonomy.name,\n\t\t\t\t\tslug: term.slug,\n\t\t\t\t\tname: term.name,\n\t\t\t\t\tdescription: term.description,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\tconst plan = await preImportWxrTaxonomies(emdash.db, [], categories, tags, terms, undefined);\n\treturn { plan, defsCreated };\n}\n\n/**\n * Fetch the source site's options and apply its identity (title, tagline,\n * logo, favicon) as EmDash site settings, overwriting seed placeholders.\n * Logo/favicon files are side-loaded into media storage first; the later\n * full media pass dedupes them by content hash.\n *\n * Returns the list of applied setting keys.\n */\nasync function applySiteSettings(\n\temdash: EmDashHandlers,\n\turl: string,\n\ttoken: string,\n\tconfig: WpPluginImportConfig,\n): Promise<string[]> {\n\tconst wantTitle = config.importSiteTitle !== false;\n\tconst wantLogo = config.importLogo !== false;\n\tif (!wantTitle && !wantLogo) return [];\n\n\tconst options = await fetchPluginOptions(url, token);\n\tconst parsed = parseSiteSettingsFromPlugin(options);\n\tif (!wantTitle) {\n\t\tdelete parsed.title;\n\t\tdelete parsed.tagline;\n\t}\n\tif (!wantLogo) {\n\t\tdelete parsed.logo;\n\t\tdelete parsed.favicon;\n\t}\n\n\tconst media: { logoMediaId?: string; faviconMediaId?: string } = {};\n\tif (emdash.storage && (parsed.logo?.url || parsed.favicon?.url)) {\n\t\tconst attachments = [];\n\t\tif (parsed.logo?.url) {\n\t\t\tattachments.push({ id: parsed.logo.id, url: parsed.logo.url });\n\t\t}\n\t\tif (parsed.favicon?.url && parsed.favicon.url !== parsed.logo?.url) {\n\t\t\tattachments.push({ id: parsed.favicon.id, url: parsed.favicon.url });\n\t\t}\n\t\tconst mediaResult = await importMediaWithProgress(\n\t\t\tattachments,\n\t\t\temdash.db,\n\t\t\temdash.storage,\n\t\t\t() => {},\n\t\t);\n\t\tfor (const item of mediaResult.imported) {\n\t\t\tif (item.originalUrl === parsed.logo?.url) {\n\t\t\t\tmedia.logoMediaId = item.mediaId;\n\t\t\t}\n\t\t\tif (item.originalUrl === parsed.favicon?.url) {\n\t\t\t\tmedia.faviconMediaId = item.mediaId;\n\t\t\t}\n\t\t}\n\t}\n\n\tconst settingsResult = await importSiteSettings(parsed, emdash.db, true, media);\n\tfor (const settingError of settingsResult.errors) {\n\t\tconsole.warn(\n\t\t\t`[WP Plugin Import] Site setting \"${settingError.setting}\" failed:`,\n\t\t\tsettingError.error,\n\t\t);\n\t}\n\treturn settingsResult.applied;\n}\n\n/**\n * Adapt a NormalizedItem's taxonomy assignments to the WxrPost shape the\n * shared attach helper consumes.\n */\nfunction toWxrAssignments(item: NormalizedItem): WxrPost {\n\treturn {\n\t\tcategories: item.categories ?? [],\n\t\ttags: item.tags ?? [],\n\t\tcustomTaxonomies: item.customTaxonomies\n\t\t\t? new Map(Object.entries(item.customTaxonomies))\n\t\t\t: undefined,\n\t\tmeta: new Map(),\n\t};\n}\n\n/** Exported for tests (field auto-creation regression coverage). */\nexport async function importContent(\n\titems: AsyncIterable<NormalizedItem> | Iterable<NormalizedItem>,\n\tconfig: WpPluginImportConfig,\n\temdash: EmDashHandlers,\n\tmanifest: EmDashManifest,\n\ttaxonomyPlan: TaxonomyImportPlan | undefined,\n\tseedTranslationGroups?: Map<string, string>,\n): Promise<{\n\tresult: ImportResult;\n\tcontentIdMap: Map<number, string>;\n\tcollectionByWpId: Map<number, string>;\n}> {\n\tconst result: ImportResult = {\n\t\tsuccess: true,\n\t\timported: 0,\n\t\tskipped: 0,\n\t\terrors: [],\n\t\tbyCollection: {},\n\t};\n\n\t// WP post ID -> EmDash content ID, used to resolve menu item references\n\tconst contentIdMap = new Map<number, string>();\n\n\t// WP post ID -> EmDash collection slug, used by the comment import\n\tconst collectionByWpId = new Map<number, string>();\n\n\t// Create content repository for checking existing items\n\tconst contentRepo = new ContentRepository(emdash.db);\n\tconst bylineRepo = new BylineRepository(emdash.db);\n\tconst bylineCache = new Map<string, string>();\n\tconst schemaRegistry = new SchemaRegistry(emdash.db);\n\n\t// Track which (collection, field) pairs have been ensured. Keyed per\n\t// field, not per collection: a field like seo_title may first be\n\t// needed by the 30th item, and gating on the first item would skip it.\n\tconst ensuredFields = new Set<string>();\n\n\t// Field slug -> type per collection, for mapping custom meta/ACF onto\n\t// real fields (and coercing values to the field's type)\n\tconst fieldTypesByCollection = new Map<string, Map<string, string>>();\n\n\t// Track source translationGroup -> EmDash item ID for translation linking.\n\t// Maps source-side translation group ID to the EmDash ID of the first item\n\t// imported for that group (the default-locale item). The chunked import\n\t// seeds this from earlier chunks so groups can span page boundaries.\n\tconst translationGroupMap = seedTranslationGroups ?? new Map<string, string>();\n\n\tfor await (const item of items) {\n\t\tconsole.log(\"[WP Plugin Import] Processing item:\", {\n\t\t\tsourceId: item.sourceId,\n\t\t\ttitle: item.title,\n\t\t\tpostType: item.postType,\n\t\t\tstatus: item.status,\n\t\t\tcontentBlocks: Array.isArray(item.content) ? item.content.length : 0,\n\t\t\tfeaturedImage: item.featuredImage,\n\t\t\tlocale: item.locale,\n\t\t\ttranslationGroup: item.translationGroup,\n\t\t});\n\n\t\tconst mapping = config.postTypeMappings[item.postType];\n\n\t\t// Skip if not mapped or disabled\n\t\tif (!mapping || !mapping.enabled) {\n\t\t\tresult.skipped++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst collection = mapping.collection;\n\n\t\t// Check if collection exists in manifest\n\t\tif (!manifest?.collections[collection]) {\n\t\t\tresult.errors.push({\n\t\t\t\ttitle: item.title || \"Untitled\",\n\t\t\t\terror: `Collection \"${collection}\" does not exist`,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\ttry {\n\t\t\t// Ensure required fields exist in the collection schema. Checked\n\t\t\t// per field and item (not once per collection): whether a field\n\t\t\t// is needed depends on the item — the first post may have no SEO\n\t\t\t// override while a later one does.\n\t\t\tfor (const field of IMPORT_FIELDS) {\n\t\t\t\tif (config.importSeo === false && SEO_FIELD_SLUGS.has(field.slug)) continue;\n\t\t\t\tconst ensureKey = `${collection}:${field.slug}`;\n\t\t\t\tif (ensuredFields.has(ensureKey) || !field.check(item)) continue;\n\t\t\t\tensuredFields.add(ensureKey);\n\t\t\t\tconst existingField = await schemaRegistry.getField(collection, field.slug);\n\t\t\t\tif (!existingField) {\n\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t`[WP Plugin Import] Creating missing field \"${field.slug}\" in collection \"${collection}\"`,\n\t\t\t\t\t);\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait schemaRegistry.createField(collection, {\n\t\t\t\t\t\t\tslug: field.slug,\n\t\t\t\t\t\t\tlabel: field.label,\n\t\t\t\t\t\t\ttype: field.type,\n\t\t\t\t\t\t\trequired: false,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tfieldTypesByCollection.get(collection)?.set(field.slug, field.type);\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t// Field might already exist from concurrent creation\n\t\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\t`[WP Plugin Import] Field \"${field.slug}\" creation skipped:`,\n\t\t\t\t\t\t\te instanceof Error ? e.message : e,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Load the collection's field types once, so custom meta / ACF\n\t\t\t// values can land in matching schema fields (created by the\n\t\t\t// prepare step or already present).\n\t\t\tlet fieldTypes = fieldTypesByCollection.get(collection);\n\t\t\tif (!fieldTypes) {\n\t\t\t\tfieldTypes = new Map<string, string>();\n\t\t\t\tconst collectionDef = await schemaRegistry.getCollection(collection);\n\t\t\t\tif (collectionDef) {\n\t\t\t\t\tfor (const field of await schemaRegistry.listFields(collectionDef.id)) {\n\t\t\t\t\t\tfieldTypes.set(field.slug, field.type);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfieldTypesByCollection.set(collection, fieldTypes);\n\t\t\t}\n\n\t\t\t// Generate slug from item slug or title\n\t\t\tconst slug = item.slug || slugify(item.title || `post-${item.sourceId}`);\n\n\t\t\t// Check if already exists (idempotency) — locale-aware lookup\n\t\t\tif (config.skipExisting) {\n\t\t\t\tconst existing = await contentRepo.findBySlug(collection, slug, item.locale);\n\t\t\t\tif (existing) {\n\t\t\t\t\t// Still track the translation group mapping for later items\n\t\t\t\t\tif (item.translationGroup) {\n\t\t\t\t\t\ttranslationGroupMap.set(item.translationGroup, existing.id);\n\t\t\t\t\t}\n\t\t\t\t\t// Menus and comments may reference this post even when we skip it\n\t\t\t\t\tconst wpId = Number(item.sourceId);\n\t\t\t\t\tif (Number.isFinite(wpId)) {\n\t\t\t\t\t\tcontentIdMap.set(wpId, existing.id);\n\t\t\t\t\t\tcollectionByWpId.set(wpId, collection);\n\t\t\t\t\t}\n\t\t\t\t\tresult.skipped++;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Map WordPress status to EmDash status\n\t\t\tconst status = mapStatus(item.status);\n\n\t\t\t// Build data object - add all applicable fields\n\t\t\tconst data: Record<string, unknown> = {};\n\n\t\t\t// Add standard fields\n\t\t\tdata.title = item.title || \"Untitled\";\n\t\t\tdata.content = item.content;\n\n\t\t\tif (item.excerpt) {\n\t\t\t\tdata.excerpt = item.excerpt;\n\t\t\t}\n\t\t\tif (item.featuredImage) {\n\t\t\t\tdata.featured_image = item.featuredImage;\n\t\t\t\tconsole.log(\"[WP Plugin Import] Adding featured_image:\", item.featuredImage);\n\t\t\t}\n\n\t\t\t// Per-post SEO overrides from Yoast / Rank Math\n\t\t\tif (config.importSeo !== false) {\n\t\t\t\tconst seo = extractSeo(item);\n\t\t\t\tif (seo.title) {\n\t\t\t\t\tdata.seo_title = seo.title;\n\t\t\t\t}\n\t\t\t\tif (seo.description) {\n\t\t\t\t\tdata.seo_description = seo.description;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Map ACF values and custom meta onto schema fields with the same\n\t\t\t// (sanitized) slug — the same sanitization the analysis applied\n\t\t\t// when suggesting the fields, so keys like `event-date` land in\n\t\t\t// the `event_date` field the prepare step created. Values without\n\t\t\t// a matching field are dropped -- the user controls the schema,\n\t\t\t// we don't invent fields per meta key. Values are coerced to the\n\t\t\t// field's type (the analysis inferred it from ONE sample; real\n\t\t\t// values vary) and dropped when incoercible, so one odd meta\n\t\t\t// value can't fail the whole item.\n\t\t\tconst assignMetaValue = (key: string, value: unknown) => {\n\t\t\t\tif (value === null || value === \"\") return;\n\t\t\t\tconst fieldSlug = sanitizeFieldSlug(key);\n\t\t\t\tconst fieldType = fieldTypes.get(fieldSlug);\n\t\t\t\tif (!fieldType || fieldSlug in data) return;\n\t\t\t\tconst coerced = coerceToFieldType(value, fieldType);\n\t\t\t\tif (coerced !== undefined) {\n\t\t\t\t\tdata[fieldSlug] = coerced;\n\t\t\t\t}\n\t\t\t};\n\t\t\tconst acf = item.meta?._acf;\n\t\t\tif (typeof acf === \"object\" && acf !== null) {\n\t\t\t\tfor (const [key, value] of Object.entries(acf)) {\n\t\t\t\t\tassignMetaValue(key, value);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (item.meta) {\n\t\t\t\tfor (const [key, value] of Object.entries(item.meta)) {\n\t\t\t\t\t// Underscore keys are WP-internal or handled above (_acf/_yoast/_rankmath)\n\t\t\t\t\tif (key.startsWith(\"_\")) continue;\n\t\t\t\t\tassignMetaValue(key, value);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Resolve author ID from mappings\n\t\t\tlet authorId: string | undefined;\n\t\t\tif (config.authorMappings && item.author) {\n\t\t\t\tconst mappedUserId = config.authorMappings[item.author];\n\t\t\t\tif (mappedUserId !== undefined && mappedUserId !== null) {\n\t\t\t\t\tauthorId = mappedUserId;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst bylineId = await resolveImportByline(\n\t\t\t\titem.author,\n\t\t\t\titem.author, // display name fallback is the login\n\t\t\t\tauthorId,\n\t\t\t\tbylineRepo,\n\t\t\t\tbylineCache,\n\t\t\t);\n\n\t\t\t// Resolve translation link: if this item has a translationGroup and\n\t\t\t// we've already imported another item in the same group, link them.\n\t\t\tlet translationOf: string | undefined;\n\t\t\tif (item.translationGroup) {\n\t\t\t\tconst existingGroupItem = translationGroupMap.get(item.translationGroup);\n\t\t\t\tif (existingGroupItem) {\n\t\t\t\t\ttranslationOf = existingGroupItem;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Preserve original dates from the source\n\t\t\tconst itemDateTime = item.date?.getTime();\n\t\t\tconst createdAt =\n\t\t\t\titemDateTime !== undefined && !Number.isNaN(itemDateTime)\n\t\t\t\t\t? item.date.toISOString()\n\t\t\t\t\t: undefined;\n\t\t\tconst publishedAt = status === \"published\" && createdAt ? createdAt : undefined;\n\n\t\t\t// Create the content item\n\t\t\tconst createResult = await emdash.handleContentCreate(collection, {\n\t\t\t\tdata,\n\t\t\t\tslug,\n\t\t\t\tstatus,\n\t\t\t\tauthorId,\n\t\t\t\tbylines: bylineId ? [{ bylineId }] : undefined,\n\t\t\t\tlocale: item.locale,\n\t\t\t\ttranslationOf,\n\t\t\t\tcreatedAt,\n\t\t\t\tpublishedAt,\n\t\t\t});\n\n\t\t\tif (createResult.success) {\n\t\t\t\tresult.imported++;\n\t\t\t\tresult.byCollection[collection] = (result.byCollection[collection] || 0) + 1;\n\n\t\t\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- create handler returns { item, _rev }\n\t\t\t\tconst createdData = createResult.data as { item?: { id?: string } } | undefined;\n\t\t\t\tconst createdId = createdData?.item?.id;\n\n\t\t\t\tif (createdId) {\n\t\t\t\t\tconst wpId = Number(item.sourceId);\n\t\t\t\t\tif (Number.isFinite(wpId)) {\n\t\t\t\t\t\tcontentIdMap.set(wpId, createdId);\n\t\t\t\t\t\tcollectionByWpId.set(wpId, collection);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Attach category/tag/custom-taxonomy assignments\n\t\t\t\t\tif (taxonomyPlan) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst attached = await attachPostTaxonomies(\n\t\t\t\t\t\t\t\temdash.db,\n\t\t\t\t\t\t\t\tcollection,\n\t\t\t\t\t\t\t\tcreatedId,\n\t\t\t\t\t\t\t\ttoWxrAssignments(item),\n\t\t\t\t\t\t\t\ttaxonomyPlan,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tif (attached > 0) {\n\t\t\t\t\t\t\t\tresult.taxonomyAssignments = (result.taxonomyAssignments ?? 0) + attached;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\tconsole.warn(`[WP Plugin Import] Taxonomy attach failed for \"${slug}\":`, e);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Track translation group: first item in a group establishes the mapping\n\t\t\t\tif (item.translationGroup && !translationGroupMap.has(item.translationGroup) && createdId) {\n\t\t\t\t\ttranslationGroupMap.set(item.translationGroup, createdId);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tresult.errors.push({\n\t\t\t\t\ttitle: item.title || \"Untitled\",\n\t\t\t\t\terror:\n\t\t\t\t\t\ttypeof createResult.error === \"object\" && createResult.error !== null\n\t\t\t\t\t\t\t? (createResult.error as { message?: string }).message || \"Unknown error\"\n\t\t\t\t\t\t\t: String(createResult.error),\n\t\t\t\t});\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error(`Import error for \"${item.title || \"Untitled\"}\":`, error);\n\t\t\tresult.errors.push({\n\t\t\t\ttitle: item.title || \"Untitled\",\n\t\t\t\terror: error instanceof Error && error.message ? error.message : \"Failed to import item\",\n\t\t\t});\n\t\t}\n\t}\n\n\tif (taxonomyPlan && taxonomyPlan.missingTaxonomies.length > 0) {\n\t\tresult.missingTaxonomies = taxonomyPlan.missingTaxonomies;\n\t}\n\n\tresult.success = result.errors.length === 0;\n\treturn { result, contentIdMap, collectionByWpId };\n}\n\nfunction mapStatus(wpStatus: string | undefined): string {\n\tswitch (wpStatus) {\n\t\tcase \"publish\":\n\t\t\treturn \"published\";\n\t\tcase \"draft\":\n\t\t\treturn \"draft\";\n\t\tcase \"pending\":\n\t\t\treturn \"draft\";\n\t\tcase \"private\":\n\t\t\treturn \"draft\";\n\t\tdefault:\n\t\t\treturn \"draft\";\n\t}\n}\n"],"mappings":"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"}