コンテンツインポート

このページ

EmDash のインポートシステムはプラガブルなソースアーキテクチャを使用します。各ソースは特定のプラットフォームからのプローブ、分析、コンテンツ取得の方法を知っています。

インポートソース

ソース IDプラットフォームプローブOAuthフルインポート
wxrWordPress エクスポートファイルNoNoYes
wordpress-comWordPress.comYesYesYes
wordpress-restセルフホスト WordPressYesNoプローブのみ

WXR ファイルアップロード

最も完全なインポート方法です。WordPress eXtended RSS (WXR) エクスポートファイルを管理ダッシュボードに直接アップロードします。

機能:

  • すべての投稿タイプ(カスタムを含む)
  • すべてのメタフィールド
  • 下書きと非公開投稿
  • 完全なタクソノミー階層
  • メディア添付ファイルメタデータ

WXR ファイルの取得方法:

  1. WordPress 管理画面で ツール → エクスポート に移動
  2. すべてのコンテンツ または特定の投稿タイプを選択
  3. エクスポートファイルをダウンロード をクリック
  4. .xml ファイルを EmDash にアップロード

WordPress.com OAuth

WordPress.com でホストされたサイトの場合、OAuth で接続して手動ファイルエクスポートなしでインポートできます。

  1. WordPress.com サイト URL を入力
  2. Connect with WordPress.com をクリック
  3. WordPress.com ポップアップで EmDash を認証
  4. インポートするコンテンツを選択

含まれるもの:

  • 公開済みと下書きコンテンツ
  • 非公開投稿(認証付き)
  • API 経由のメディアファイル
  • REST API に公開されたカスタムフィールド

WordPress REST API プローブ

URL を入力すると、EmDash はサイトをプローブして WordPress を検出し、利用可能なコンテンツを表示します。

Detected: WordPress 6.4
├── Posts: 127 (published)
├── Pages: 12 (published)
└── Media: 89 files

Note: Drafts and private content require authentication
or a full WXR export.

REST プローブは情報提供用です。完全なインポートには WXR ファイルのアップロードまたは OAuth 接続(WordPress.com の場合)を提案します。

インポートフロー

すべてのソースは同じフローに従います。

┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   接続      │────▶│   分析      │────▶│   準備      │────▶│   実行      │
│  (プローブ/  │     │  (スキーマ   │     │  (スキーマ   │     │  (コンテンツ  │
│   アップロード)│     │   チェック)   │     │   作成)      │     │   インポート) │
└─────────────┘     └─────────────┘     └─────────────┘     └─────────────┘

ステップ 1: 接続

URL を入力してプローブするか、ファイルを直接アップロードします。

URL プローブ は登録されたすべてのソースを並列実行します。最も高い確信度のマッチが次のアクションを決定します。

  • WordPress.com サイト → OAuth 接続を提案
  • セルフホスト WordPress → エクスポート手順を表示
  • 不明 → ファイルアップロードを提案

ステップ 2: 分析

ソースがコンテンツを解析し、スキーマの互換性をチェックします。

Post Types:
├── post (127) → posts [新規コレクション]
├── page (12)  → pages [既存、互換性あり]
├── product (45) → products [3フィールド追加]
└── revision (234) → [スキップ - 内部タイプ]

Required Schema Changes:
├── Create collection: posts
├── Add fields to pages: featured_image
└── Create collection: products

各投稿タイプのステータス:

ステータス意味
Ready互換性のあるフィールドを持つコレクションが存在
New collection自動的に作成されます
Add fieldsコレクションが存在し、不足フィールドが追加されます
Incompatibleフィールドタイプの競合(手動修正が必要)

ステップ 3: スキーマの準備

Create Schema & Import をクリックして:

  1. SchemaRegistry 経由で新しいコレクションを作成
  2. 正しいカラム型で不足フィールドを追加
  3. インデックス付きのコンテンツテーブルをセットアップ

ステップ 4: インポート実行

コンテンツは順次インポートされます:

  • Gutenberg/HTML を Portable Text に変換
  • WordPress ステータスを EmDash ステータスにマッピング
  • WordPress 著者を所有権(authorId)とプレゼンテーション署名にマッピング
  • タクソノミーの作成とリンク
  • 再利用可能ブロック(wp_block)をセクションとしてインポート
  • 進行状況をリアルタイムで表示

著者インポートの動作:

  • 著者マッピングが EmDash ユーザーを指す場合、所有権はそのユーザーに設定され、同じユーザーの紐付き署名が作成/再利用されます。
  • ユーザーマッピングがない場合、WordPress の著者アイデンティティからゲスト署名が作成/再利用されます。
  • インポートされたエントリは順序付き署名クレジットを取得し、最初のクレジットが primaryBylineId として設定されます。

ステップ 5: メディアインポート(任意)

コンテンツの後に、任意でメディアをインポートします。

  1. 分析 — タイプ別の添付ファイル数を表示

    Media found:
    ├── Images: 75 files
    ├── Video: 10 files
    └── Other: 4 files
  2. ダウンロード — WordPress URL から進行状況付きでストリーミング

    Importing media...
    ├── 45 of 89 (50%)
    ├── Current: vacation-photo.jpg
    └── Status: Uploading
  3. URL の書き換え — コンテンツが新しい URL で自動的に更新

メディアインポートは重複排除にコンテンツハッシュ(xxHash64)を使用します。複数の投稿で使用された同じ画像は 1 回だけ保存されます。

ソースインターフェース

インポートソースは標準インターフェースを実装します:

interface ImportSource {
	/** 一意の識別子 */
	id: string;

	/** 表示名 */
	name: string;

	/** URL のプローブ(任意) */
	probe?(url: string): Promise<SourceProbeResult | null>;

	/** このソースからコンテンツを分析 */
	analyze(input: SourceInput, context: ImportContext): Promise<ImportAnalysis>;

	/** コンテンツアイテムをストリーミング */
	fetchContent(input: SourceInput, options: FetchOptions): AsyncGenerator<NormalizedItem>;
}

入力タイプ

ソースは異なる入力タイプを受け付けます:

// ファイルアップロード(WXR)
{ type: "file", file: File }

// トークン付き URL(REST API)
{ type: "url", url: string, token?: string }

// OAuth 接続(WordPress.com)
{ type: "oauth", url: string, accessToken: string }

正規化された出力

すべてのソースは同じ正規化されたフォーマットを生成します:

interface NormalizedItem {
	sourceId: string | number;
	postType: string;
	status: "publish" | "draft" | "pending" | "private" | "future";
	slug: string;
	title: string;
	content: PortableTextBlock[];
	excerpt?: string;
	date: Date;
	author?: string;
	authors?: string[];
	categories?: string[];
	tags?: string[];
	meta?: Record<string, unknown>;
	featuredImage?: string;
}

API エンドポイント

インポートシステムは以下のエンドポイントを公開します:

URL のプローブ

POST /_emdash/api/import/probe
Content-Type: application/json

{ "url": "https://example.com" }

検出されたプラットフォームと推奨アクションを返します。

WXR の分析

POST /_emdash/api/import/wordpress/analyze
Content-Type: multipart/form-data

file: [WordPress export .xml]

スキーマ互換性付きの投稿タイプ分析を返します。

スキーマの準備

POST /_emdash/api/import/wordpress/prepare
Content-Type: application/json

{
  "postTypes": [
    { "name": "post", "collection": "posts", "enabled": true }
  ]
}

コレクションとフィールドを作成します。

インポートの実行

POST /_emdash/api/import/wordpress/execute
Content-Type: multipart/form-data

file: [WordPress export .xml]
config: { "postTypeMappings": { "post": { "collection": "posts" } } }

指定されたコレクションにコンテンツをインポートします。

メディアのインポート

POST /_emdash/api/import/wordpress/media
Content-Type: application/json

{
  "attachments": [{ "id": 123, "url": "https://..." }],
  "stream": true
}

ダウンロード/アップロード中に NDJSON 進行状況更新をストリーミングします。

URL の書き換え

POST /_emdash/api/import/wordpress/rewrite-urls
Content-Type: application/json

{
  "urlMap": { "https://old.com/image.jpg": "/_emdash/media/abc123" }
}

新しいメディア URL で Portable Text コンテンツを更新します。

エラー処理

回復可能なエラー

  • ネットワークタイムアウト — バックオフ付きでリトライ
  • 単一アイテムのパース失敗 — ログ記録、スキップ、インポート継続
  • メディアダウンロード失敗 — 手動処理用にマーク

致命的なエラー

  • 無効なファイル形式 — エラーメッセージでインポート停止
  • データベース接続切断 — インポート一時停止、再開可能
  • ストレージクォータ超過 — インポート停止、使用量を表示

エラーレポート

インポート後:

Import Complete

✓ 125 posts imported
✓ 12 pages imported
✓ 85 media references recorded

⚠ 2 items had warnings:
  - Post "Special Characters ñ" - title encoding fixed
  - Page "About" - duplicate slug renamed to "about-1"

✗ 1 item failed:
  - Post ID 456 - content parsing error (saved as draft)

失敗したアイテムはレビュー用に元のコンテンツを _importError に含めて下書きとして保存されます。

カスタムソースの構築

他のプラットフォーム用のソースを作成:

import type { ImportSource } from "emdash/import";

export const mySource: ImportSource = {
	id: "my-platform",
	name: "My Platform",
	description: "Import from My Platform",
	icon: "globe",
	canProbe: true,

	async probe(url) {
		// URL がプラットフォームに一致するか確認
		const response = await fetch(`${url}/api/info`);
		if (!response.ok) return null;

		return {
			sourceId: "my-platform",
			confidence: "definite",
			detected: { platform: "my-platform" },
			// ...
		};
	},

	async analyze(input, context) {
		// コンテンツの解析と分析
		// ImportAnalysis を返す
	},

	async *fetchContent(input, options) {
		// 各コンテンツピースに対して NormalizedItem を yield
		for (const item of items) {
			yield {
				sourceId: item.id,
				postType: "post",
				title: item.title,
				content: convertToPortableText(item.body),
				// ...
			};
		}
	},
};

EmDash 設定にソースを登録:

import { mySource } from "./src/import/custom-source";

export default defineConfig({
	integrations: [
		emdash({
			import: {
				sources: [mySource],
			},
		}),
	],
});

次のステップ