UNPKG

n8n-nodes-notion-advanced

Version:

Advanced n8n Notion nodes: Full-featured workflow node + AI Agent Tool for intelligent Notion automation with 25+ block types (BETA)

466 lines (465 loc) 13.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.notionApiRequest = notionApiRequest; exports.validateCredentials = validateCredentials; exports.createRichText = createRichText; exports.parseRichTextInput = parseRichTextInput; exports.createParagraphBlock = createParagraphBlock; exports.createHeadingBlock = createHeadingBlock; exports.createListItemBlock = createListItemBlock; exports.createToDoBlock = createToDoBlock; exports.createCodeBlock = createCodeBlock; exports.createQuoteBlock = createQuoteBlock; exports.createCalloutBlock = createCalloutBlock; exports.createDividerBlock = createDividerBlock; exports.createEquationBlock = createEquationBlock; exports.createImageBlock = createImageBlock; exports.createBookmarkBlock = createBookmarkBlock; exports.createEmbedBlock = createEmbedBlock; exports.createToggleBlock = createToggleBlock; exports.createTableBlock = createTableBlock; exports.convertBlockInput = convertBlockInput; exports.resolvePageId = resolvePageId; exports.validateBlock = validateBlock; exports.paginatedRequest = paginatedRequest; exports.createPageInput = createPageInput; exports.getBlocksWithIds = getBlocksWithIds; exports.createExecutionData = createExecutionData; const n8n_workflow_1 = require("n8n-workflow"); /** * Makes an authenticated request to the Notion API */ async function notionApiRequest(method, endpoint, body = {}, qs = {}) { const credentials = (await this.getCredentials('notionApi')); const options = { method, headers: { 'Authorization': `Bearer ${credentials.apiKey}`, 'Notion-Version': '2022-06-28', 'Content-Type': 'application/json', }, url: `https://api.notion.com/v1${endpoint}`, body, qs, json: true, }; try { return await this.helpers.httpRequest(options); } catch (error) { throw new n8n_workflow_1.NodeApiError(this.getNode(), { message: error.message, description: 'Failed to make Notion API request', httpCode: error.status || error.statusCode || 500, }); } } /** * Validates Notion API credentials by making a test request */ async function validateCredentials() { try { await notionApiRequest.call(this, 'GET', '/users/me'); return true; } catch (error) { return false; } } /** * Creates a rich text object from a string with optional formatting */ function createRichText(text, annotations = {}, link) { return { type: 'text', text: { content: text, link: link ? { url: link } : null, }, annotations: { bold: annotations.bold || false, italic: annotations.italic || false, strikethrough: annotations.strikethrough || false, underline: annotations.underline || false, code: annotations.code || false, color: annotations.color || 'default', }, plain_text: text, href: link || null, }; } /** * Parses rich text input from various formats */ function parseRichTextInput(input) { if (typeof input === 'string') { return [createRichText(input)]; } if (Array.isArray(input)) { return input.map((item) => { if (typeof item === 'string') { return createRichText(item); } if (typeof item === 'object' && item !== null) { return createRichText(item.text || item.content || '', item.annotations || {}, item.link || item.url); } return createRichText(''); }); } if (typeof input === 'object' && input !== null) { if (input.type === 'text' || input.type === 'mention' || input.type === 'equation') { return [input]; } return [createRichText(input.text || input.content || '', input.annotations || {}, input.link || input.url)]; } return [createRichText('')]; } /** * Creates a paragraph block */ function createParagraphBlock(text, color, children) { return { type: 'paragraph', paragraph: { rich_text: typeof text === 'string' ? [createRichText(text)] : text, color, children, }, }; } /** * Creates a heading block */ function createHeadingBlock(level, text, color, isToggleable) { const richText = typeof text === 'string' ? [createRichText(text)] : text; const headingData = { rich_text: richText, color, is_toggleable: isToggleable, }; switch (level) { case 1: return { type: 'heading_1', heading_1: headingData, }; case 2: return { type: 'heading_2', heading_2: headingData, }; case 3: return { type: 'heading_3', heading_3: headingData, }; default: throw new Error('Invalid heading level. Must be 1, 2, or 3.'); } } /** * Creates a list item block */ function createListItemBlock(type, text, color, children) { const richText = typeof text === 'string' ? [createRichText(text)] : text; if (type === 'bulleted_list_item') { return { type: 'bulleted_list_item', bulleted_list_item: { rich_text: richText, color, children, }, }; } else { return { type: 'numbered_list_item', numbered_list_item: { rich_text: richText, color, children, }, }; } } /** * Creates a to-do block */ function createToDoBlock(text, checked = false, color, children) { return { type: 'to_do', to_do: { rich_text: typeof text === 'string' ? [createRichText(text)] : text, checked, color, children, }, }; } /** * Creates a code block */ function createCodeBlock(code, language, caption) { return { type: 'code', code: { rich_text: [createRichText(code)], language, caption, }, }; } /** * Creates a quote block */ function createQuoteBlock(text, color, children) { return { type: 'quote', quote: { rich_text: typeof text === 'string' ? [createRichText(text)] : text, color, children, }, }; } /** * Creates a callout block */ function createCalloutBlock(text, icon, color, children) { const iconObject = icon ? { type: 'emoji', emoji: icon } : undefined; return { type: 'callout', callout: { rich_text: typeof text === 'string' ? [createRichText(text)] : text, icon: iconObject, color, children, }, }; } /** * Creates a divider block */ function createDividerBlock() { return { type: 'divider', divider: {}, }; } /** * Creates an equation block */ function createEquationBlock(expression) { return { type: 'equation', equation: { expression, }, }; } /** * Creates an image block */ function createImageBlock(url, caption) { return { type: 'image', image: { type: 'external', external: { url }, caption, }, }; } /** * Creates a bookmark block */ function createBookmarkBlock(url, caption) { return { type: 'bookmark', bookmark: { url, caption, }, }; } /** * Creates an embed block */ function createEmbedBlock(url, caption) { return { type: 'embed', embed: { url, caption, }, }; } /** * Creates a toggle block */ function createToggleBlock(text, color, children) { return { type: 'toggle', toggle: { rich_text: typeof text === 'string' ? [createRichText(text)] : text, color, children, }, }; } /** * Creates a table block with rows */ function createTableBlock(tableWidth, rows, hasColumnHeader = false, hasRowHeader = false) { const tableRows = rows.map(cells => ({ type: 'table_row', table_row: { cells }, })); return { type: 'table', table: { table_width: tableWidth, has_column_header: hasColumnHeader, has_row_header: hasRowHeader, children: tableRows, }, }; } /** * Converts BlockInput to actual Block objects */ function convertBlockInput(blockInput) { const { type, content = '', properties = {}, children } = blockInput; const richText = parseRichTextInput(blockInput.richText || content); const childBlocks = children ? children.map(convertBlockInput) : undefined; switch (type) { case 'paragraph': return createParagraphBlock(richText, properties.color, childBlocks); case 'heading_1': case 'heading_2': case 'heading_3': const level = parseInt(type.split('_')[1]); return createHeadingBlock(level, richText, properties.color, properties.isToggleable); case 'bulleted_list_item': case 'numbered_list_item': return createListItemBlock(type, richText, properties.color, childBlocks); case 'to_do': return createToDoBlock(richText, properties.checked, properties.color, childBlocks); case 'code': return createCodeBlock(content, properties.language, properties.caption); case 'quote': return createQuoteBlock(richText, properties.color, childBlocks); case 'callout': return createCalloutBlock(richText, properties.icon, properties.color, childBlocks); case 'divider': return createDividerBlock(); case 'equation': return createEquationBlock(properties.expression || content); case 'image': return createImageBlock(properties.url || content, properties.caption); case 'bookmark': return createBookmarkBlock(properties.url || content, properties.caption); case 'embed': return createEmbedBlock(properties.url || content, properties.caption); default: throw new Error(`Unsupported block type: ${type}`); } } /** * Resolves a page ID from various input formats (URL, ID, title search) */ async function resolvePageId(pageInput) { // If it looks like a UUID, return it directly const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; if (uuidRegex.test(pageInput)) { return pageInput; } // If it looks like a Notion URL, extract the ID const urlRegex = /https:\/\/www\.notion\.so\/[^\/]*-([a-f0-9]{32})/; const urlMatch = pageInput.match(urlRegex); if (urlMatch) { const rawId = urlMatch[1]; // Convert 32-character ID to UUID format return `${rawId.slice(0, 8)}-${rawId.slice(8, 12)}-${rawId.slice(12, 16)}-${rawId.slice(16, 20)}-${rawId.slice(20)}`; } // Otherwise, search for page by title const searchResponse = await notionApiRequest.call(this, 'POST', '/search', { query: pageInput, filter: { property: 'object', value: 'page' }, }); if (searchResponse.results && searchResponse.results.length > 0) { const page = searchResponse.results[0]; return page.id; } throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Could not find page with identifier: ${pageInput}`); } /** * Validates a block structure before sending to Notion API */ function validateBlock(block) { if (!block.type) { throw new Error('Block must have a type property'); } // Add specific validation for each block type as needed switch (block.type) { case 'paragraph': if (!('paragraph' in block) || !block.paragraph || !Array.isArray(block.paragraph.rich_text)) { throw new Error('Paragraph block must have rich_text array'); } break; case 'code': if (!('code' in block) || !block.code || !Array.isArray(block.code.rich_text)) { throw new Error('Code block must have rich_text array'); } break; // Add more validation cases as needed } } /** * Paginated request helper for Notion API */ async function paginatedRequest(method, endpoint, body = {}) { const results = []; let hasMore = true; let nextCursor; while (hasMore) { const requestBody = { ...body }; if (nextCursor) { requestBody.start_cursor = nextCursor; } const response = await notionApiRequest.call(this, method, endpoint, requestBody); if (response.results) { results.push(...response.results); } hasMore = response.has_more || false; nextCursor = response.next_cursor || undefined; } return results; } /** * Creates page input from parameters for validation */ function createPageInput(title, parent, properties, children, icon, cover) { return { title, parent, properties, children, icon, cover, }; } /** * Gets blocks with full metadata including IDs */ async function getBlocksWithIds(blockId) { const response = await notionApiRequest.call(this, 'GET', `/blocks/${blockId}/children`); return response.results; } /** * Creates execution data from results */ function createExecutionData(data) { return data.map(item => ({ json: item })); }