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
JavaScript
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 }));
}
;