notion.mjs
82 lines 3.2 KB
Raw
sha256:700fafdd1afa490919f9515d660ca6e75456bcd5bb67513abcd8757a634c01f6 docs: record AIP-b SD-21 land (KN #308) Human 9 days ago
1 /**
2 * Notion import. Fetches pages as markdown via Notion API (retrieve page as markdown).
3 * Requires NOTION_API_KEY. Input: comma-separated page IDs or a single page ID.
4 *
5 * One note per page; frontmatter: source: notion, source_id: page_id, date, title (if available).
6 */
7
8 import path from 'path';
9 import { writeNote } from '../write.mjs';
10 import { normalizeSlug } from '../vault.mjs';
11
12 const NOTION_API_BASE = 'https://api.notion.com/v1';
13 const NOTION_VERSION = '2022-06-28'; // Markdown endpoint may require 2026-03-11 on newer Notion API
14
15 /**
16 * Fetch one Notion page as Markdown without writing to the vault.
17 *
18 * @param {string} pageId
19 * @param {{ apiKey: string, fetchImpl?: typeof fetch }} options
20 * @returns {Promise<string>}
21 */
22 export async function fetchNotionPageMarkdown(pageId, { apiKey, fetchImpl = globalThis.fetch }) {
23 if (typeof pageId !== 'string' || !pageId.trim()) throw new TypeError('Notion page id is required');
24 if (typeof apiKey !== 'string' || !apiKey.trim()) throw new TypeError('Notion API key is required');
25 if (typeof fetchImpl !== 'function') throw new TypeError('fetch implementation is required');
26 const url = `${NOTION_API_BASE}/pages/${encodeURIComponent(pageId.trim())}/markdown`;
27 const res = await fetchImpl(url, {
28 method: 'GET',
29 headers: {
30 Authorization: `Bearer ${apiKey}`,
31 'Notion-Version': NOTION_VERSION,
32 Accept: 'application/json',
33 },
34 });
35 if (!res.ok) throw new Error('Notion page fetch failed');
36 const data = await res.json();
37 return typeof data.markdown === 'string' ? data.markdown : '';
38 }
39
40 /**
41 * @param {string} input - Comma-separated Notion page IDs (e.g. "uuid1,uuid2") or single ID
42 * @param {{ vaultPath: string, outputBase: string, project?: string, tags: string[], dryRun: boolean }} ctx
43 * @returns {Promise<{ imported: { path: string, source_id?: string }[], count: number }>}
44 */
45 export async function importNotion(input, ctx) {
46 const { vaultPath, outputBase, project, tags, dryRun } = ctx;
47 const apiKey = process.env.NOTION_API_KEY;
48 if (!apiKey || typeof apiKey !== 'string') {
49 throw new Error('NOTION_API_KEY is required for Notion import. Create an integration at notion.so/my-integrations.');
50 }
51
52 const pageIds = input.split(',').map((id) => id.trim()).filter(Boolean);
53 if (!pageIds.length) {
54 throw new Error('Provide at least one Notion page ID (or comma-separated list).');
55 }
56
57 const imported = [];
58 const now = new Date().toISOString().slice(0, 10);
59
60 for (let i = 0; i < pageIds.length; i++) {
61 const pageId = pageIds[i];
62 const body = await fetchNotionPageMarkdown(pageId, { apiKey, fetchImpl: globalThis.fetch });
63 const safeId = pageId.replace(/[^a-zA-Z0-9-_]/g, '_').slice(0, 16);
64 const safeName = `notion-${safeId}-${i + 1}.md`;
65 const outputRel = path.join(outputBase, safeName).replace(/\\/g, '/');
66
67 const frontmatter = {
68 source: 'notion',
69 source_id: pageId,
70 date: now,
71 ...(project && { project: normalizeSlug(project) }),
72 ...(tags.length && { tags }),
73 };
74
75 if (!dryRun) {
76 writeNote(vaultPath, outputRel, { body, frontmatter });
77 }
78 imported.push({ path: outputRel, source_id: pageId });
79 }
80
81 return { imported, count: imported.length };
82 }
File History 1 commit
sha256:700fafdd1afa490919f9515d660ca6e75456bcd5bb67513abcd8757a634c01f6 docs: record AIP-b SD-21 land (KN #308) Human 9 days ago