|
|
#!/usr/bin/env node |
|
|
|
|
|
import { Client } from '@notionhq/client'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export async function extractNotionMetadata(pageId, notionToken) { |
|
|
const notion = new Client({ |
|
|
auth: notionToken, |
|
|
}); |
|
|
|
|
|
const metadata = {}; |
|
|
|
|
|
try { |
|
|
|
|
|
const page = await notion.pages.retrieve({ page_id: pageId }); |
|
|
|
|
|
|
|
|
if (page.properties.title && page.properties.title.title && page.properties.title.title.length > 0) { |
|
|
metadata.title = page.properties.title.title[0].plain_text; |
|
|
} |
|
|
|
|
|
|
|
|
if (page.created_time) { |
|
|
metadata.published = new Date(page.created_time).toLocaleDateString('en-US', { |
|
|
year: 'numeric', |
|
|
month: 'short', |
|
|
day: '2-digit' |
|
|
}); |
|
|
metadata.created_time = page.created_time; |
|
|
} |
|
|
|
|
|
|
|
|
if (page.last_edited_time) { |
|
|
metadata.last_edited_time = page.last_edited_time; |
|
|
} |
|
|
|
|
|
|
|
|
if (page.created_by && page.created_by.id) { |
|
|
metadata.created_by = page.created_by.id; |
|
|
} |
|
|
|
|
|
|
|
|
if (page.last_edited_by && page.last_edited_by.id) { |
|
|
metadata.last_edited_by = page.last_edited_by.id; |
|
|
} |
|
|
|
|
|
|
|
|
metadata.notion_url = page.url; |
|
|
|
|
|
|
|
|
metadata.notion_id = page.id; |
|
|
|
|
|
|
|
|
if (page.parent) { |
|
|
metadata.parent = { |
|
|
type: page.parent.type, |
|
|
id: page.parent[page.parent.type]?.id || page.parent[page.parent.type] |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
if (page.cover) { |
|
|
metadata.cover = { |
|
|
type: page.cover.type, |
|
|
url: page.cover[page.cover.type]?.url || page.cover[page.cover.type] |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
if (page.icon) { |
|
|
metadata.icon = { |
|
|
type: page.icon.type, |
|
|
emoji: page.icon.emoji, |
|
|
url: page.icon.external?.url || page.icon.file?.url |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
const customProperties = {}; |
|
|
for (const [key, value] of Object.entries(page.properties)) { |
|
|
if (key !== 'title') { |
|
|
const extractedValue = extractPropertyValue(value); |
|
|
|
|
|
|
|
|
if (key.toLowerCase().includes('author') || |
|
|
key.toLowerCase().includes('writer') || |
|
|
key.toLowerCase().includes('creator') || |
|
|
value.type === 'people') { |
|
|
metadata.authors = extractedValue; |
|
|
} else { |
|
|
customProperties[key] = extractedValue; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (!metadata.authors && page.created_by) { |
|
|
try { |
|
|
const user = await notion.users.retrieve({ user_id: page.created_by.id }); |
|
|
metadata.authors = [{ |
|
|
name: user.name || user.id, |
|
|
id: user.id |
|
|
}]; |
|
|
} catch (error) { |
|
|
console.log(' ⚠️ Could not fetch author from created_by:', error.message); |
|
|
|
|
|
metadata.authors = [{ |
|
|
name: page.created_by.name || page.created_by.id, |
|
|
id: page.created_by.id |
|
|
}]; |
|
|
} |
|
|
} |
|
|
|
|
|
if (Object.keys(customProperties).length > 0) { |
|
|
metadata.properties = customProperties; |
|
|
} |
|
|
|
|
|
|
|
|
try { |
|
|
const blocks = await notion.blocks.children.list({ block_id: pageId }); |
|
|
const firstParagraph = blocks.results.find(block => |
|
|
block.type === 'paragraph' && |
|
|
block.paragraph.rich_text && |
|
|
block.paragraph.rich_text.length > 0 |
|
|
); |
|
|
|
|
|
if (firstParagraph) { |
|
|
const description = firstParagraph.paragraph.rich_text |
|
|
.map(text => text.plain_text) |
|
|
.join('') |
|
|
.trim(); |
|
|
|
|
|
if (description && description.length > 0) { |
|
|
metadata.description = description.substring(0, 200) + (description.length > 200 ? '...' : ''); |
|
|
} |
|
|
} |
|
|
} catch (error) { |
|
|
console.log(' ⚠️ Could not extract description from page content'); |
|
|
} |
|
|
|
|
|
|
|
|
const tags = []; |
|
|
for (const [key, value] of Object.entries(page.properties)) { |
|
|
if (value.type === 'multi_select' && value.multi_select) { |
|
|
value.multi_select.forEach(option => { |
|
|
tags.push(option.name); |
|
|
}); |
|
|
} else if (value.type === 'select' && value.select) { |
|
|
tags.push(value.select.name); |
|
|
} |
|
|
} |
|
|
|
|
|
if (tags.length > 0) { |
|
|
metadata.tags = tags; |
|
|
} |
|
|
|
|
|
} catch (error) { |
|
|
console.error('Error extracting Notion metadata:', error.message); |
|
|
|
|
|
metadata.title = "Notion Article"; |
|
|
metadata.published = new Date().toLocaleDateString('en-US', { |
|
|
year: 'numeric', |
|
|
month: 'short', |
|
|
day: '2-digit' |
|
|
}); |
|
|
} |
|
|
|
|
|
return metadata; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function extractPropertyValue(property) { |
|
|
switch (property.type) { |
|
|
case 'rich_text': |
|
|
return property.rich_text.map(text => text.plain_text).join(''); |
|
|
case 'title': |
|
|
return property.title.map(text => text.plain_text).join(''); |
|
|
case 'number': |
|
|
return property.number; |
|
|
case 'select': |
|
|
return property.select?.name || null; |
|
|
case 'multi_select': |
|
|
return property.multi_select.map(option => option.name); |
|
|
case 'date': |
|
|
return property.date?.start || null; |
|
|
case 'checkbox': |
|
|
return property.checkbox; |
|
|
case 'url': |
|
|
return property.url; |
|
|
case 'email': |
|
|
return property.email; |
|
|
case 'phone_number': |
|
|
return property.phone_number; |
|
|
case 'created_time': |
|
|
return property.created_time; |
|
|
case 'created_by': |
|
|
return property.created_by?.id || null; |
|
|
case 'last_edited_time': |
|
|
return property.last_edited_time; |
|
|
case 'last_edited_by': |
|
|
return property.last_edited_by?.id || null; |
|
|
case 'people': |
|
|
return property.people.map(person => ({ |
|
|
name: person.name || person.id, |
|
|
id: person.id |
|
|
})); |
|
|
default: |
|
|
return null; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function generateNotionFrontmatter(metadata) { |
|
|
let frontmatter = '---\n'; |
|
|
|
|
|
|
|
|
if (metadata.title) { |
|
|
frontmatter += `title: "${metadata.title}"\n`; |
|
|
} |
|
|
|
|
|
|
|
|
if (metadata.description) { |
|
|
frontmatter += `description: "${metadata.description}"\n`; |
|
|
} |
|
|
|
|
|
|
|
|
if (metadata.published) { |
|
|
frontmatter += `published: "${metadata.published}"\n`; |
|
|
} |
|
|
|
|
|
|
|
|
if (metadata.authors && metadata.authors.length > 0) { |
|
|
frontmatter += 'authors:\n'; |
|
|
metadata.authors.forEach(author => { |
|
|
if (typeof author === 'string') { |
|
|
frontmatter += ` - name: "${author}"\n`; |
|
|
} else if (author.name) { |
|
|
frontmatter += ` - name: "${author.name}"\n`; |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
if (metadata.tags && metadata.tags.length > 0) { |
|
|
frontmatter += 'tags:\n'; |
|
|
metadata.tags.forEach(tag => { |
|
|
frontmatter += ` - "${tag}"\n`; |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (metadata.cover && metadata.cover.url) { |
|
|
frontmatter += `cover: "${metadata.cover.url}"\n`; |
|
|
} |
|
|
|
|
|
|
|
|
if (metadata.icon) { |
|
|
if (metadata.icon.emoji) { |
|
|
frontmatter += `icon: "${metadata.icon.emoji}"\n`; |
|
|
} else if (metadata.icon.url) { |
|
|
frontmatter += `icon: "${metadata.icon.url}"\n`; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontmatter += 'tableOfContentsAutoCollapse: true\n'; |
|
|
frontmatter += '---\n\n'; |
|
|
|
|
|
return frontmatter; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export async function extractAndGenerateNotionFrontmatter(pageId, notionToken) { |
|
|
const metadata = await extractNotionMetadata(pageId, notionToken); |
|
|
return generateNotionFrontmatter(metadata); |
|
|
} |
|
|
|