@daf-sdk/runtime 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-4H6PEYL7.mjs +1221 -0
- package/dist/chunk-HGSJ2UZZ.mjs +625 -0
- package/dist/chunk-HT5X5ZSB.mjs +7231 -0
- package/dist/encryption-5J2VZDSY.mjs +11 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +9336 -0
- package/dist/index.mjs +318 -0
- package/dist/mongodbService-PJCZZWQ3.mjs +23 -0
- package/dist/notion-3VBQFXSQ.mjs +52 -0
- package/dist/protocol/index.d.mts +589 -0
- package/dist/protocol/index.d.ts +589 -0
- package/dist/protocol/index.js +735 -0
- package/dist/protocol/index.mjs +174 -0
- package/dist/runtime/index.d.mts +957 -0
- package/dist/runtime/index.d.ts +957 -0
- package/dist/runtime/index.js +8628 -0
- package/dist/runtime/index.mjs +146 -0
- package/package.json +64 -0
|
@@ -0,0 +1,1221 @@
|
|
|
1
|
+
// src/runtime/integrations/notion.ts
|
|
2
|
+
import { Client } from "@notionhq/client";
|
|
3
|
+
var NOTION_CLIENT_ID = process.env.NOTION_CLIENT_ID || "";
|
|
4
|
+
var NOTION_CLIENT_SECRET = process.env.NOTION_CLIENT_SECRET || "";
|
|
5
|
+
var NOTION_REDIRECT_URI = process.env.NOTION_REDIRECT_URI || "http://localhost:3000/api/integrations/notion/callback";
|
|
6
|
+
function getNotionAuthUrl(userId) {
|
|
7
|
+
const authUrl = new URL("https://api.notion.com/v1/oauth/authorize");
|
|
8
|
+
authUrl.searchParams.set("client_id", NOTION_CLIENT_ID);
|
|
9
|
+
authUrl.searchParams.set("response_type", "code");
|
|
10
|
+
authUrl.searchParams.set("owner", "user");
|
|
11
|
+
authUrl.searchParams.set("redirect_uri", NOTION_REDIRECT_URI);
|
|
12
|
+
authUrl.searchParams.set("state", userId);
|
|
13
|
+
return authUrl.toString();
|
|
14
|
+
}
|
|
15
|
+
async function exchangeNotionCode(code) {
|
|
16
|
+
const encoded = Buffer.from(`${NOTION_CLIENT_ID}:${NOTION_CLIENT_SECRET}`).toString("base64");
|
|
17
|
+
const response = await fetch("https://api.notion.com/v1/oauth/token", {
|
|
18
|
+
method: "POST",
|
|
19
|
+
headers: {
|
|
20
|
+
"Authorization": `Basic ${encoded}`,
|
|
21
|
+
"Content-Type": "application/json"
|
|
22
|
+
},
|
|
23
|
+
body: JSON.stringify({
|
|
24
|
+
grant_type: "authorization_code",
|
|
25
|
+
code,
|
|
26
|
+
redirect_uri: NOTION_REDIRECT_URI
|
|
27
|
+
})
|
|
28
|
+
});
|
|
29
|
+
if (!response.ok) {
|
|
30
|
+
const error = await response.text();
|
|
31
|
+
throw new Error(`Failed to exchange Notion code: ${error}`);
|
|
32
|
+
}
|
|
33
|
+
const data = await response.json();
|
|
34
|
+
return {
|
|
35
|
+
accessToken: data.access_token,
|
|
36
|
+
workspaceId: data.workspace_id,
|
|
37
|
+
workspaceName: data.workspace_name,
|
|
38
|
+
botId: data.bot_id
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
async function getNotionAccessToken(userId, adapter) {
|
|
42
|
+
const integration = await adapter.getNotionIntegration(userId);
|
|
43
|
+
if (!integration) {
|
|
44
|
+
throw new Error("Notion integration not found. Please connect your Notion account.");
|
|
45
|
+
}
|
|
46
|
+
return integration.accessToken;
|
|
47
|
+
}
|
|
48
|
+
function extractPageIdFromUrl(url) {
|
|
49
|
+
const urlWithoutQuery = url.split("?")[0];
|
|
50
|
+
const match = urlWithoutQuery.match(/([a-f0-9]{32}|[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})$/i);
|
|
51
|
+
if (match) {
|
|
52
|
+
const id = match[1].replace(/-/g, "");
|
|
53
|
+
return `${id.slice(0, 8)}-${id.slice(8, 12)}-${id.slice(12, 16)}-${id.slice(16, 20)}-${id.slice(20)}`;
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
function extractDatabaseIdFromUrl(url) {
|
|
58
|
+
return extractPageIdFromUrl(url);
|
|
59
|
+
}
|
|
60
|
+
async function getNotionPageTitle(pageId, accessToken) {
|
|
61
|
+
try {
|
|
62
|
+
const notion = new Client({ auth: accessToken });
|
|
63
|
+
const page = await notion.pages.retrieve({ page_id: pageId });
|
|
64
|
+
if (page.properties) {
|
|
65
|
+
for (const prop of Object.values(page.properties)) {
|
|
66
|
+
if (prop.type === "title" && prop.title) {
|
|
67
|
+
const titleArray = prop.title;
|
|
68
|
+
if (titleArray.length > 0 && titleArray[0].plain_text) {
|
|
69
|
+
return titleArray[0].plain_text;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error("Error fetching Notion page title:", error);
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async function readNotionPage(pageId, accessToken) {
|
|
81
|
+
const notion = new Client({ auth: accessToken });
|
|
82
|
+
async function getBlockContent(blockId, indentLevel = 0) {
|
|
83
|
+
const blocks = await notion.blocks.children.list({
|
|
84
|
+
block_id: blockId
|
|
85
|
+
});
|
|
86
|
+
let content = "";
|
|
87
|
+
const indent = " ".repeat(indentLevel);
|
|
88
|
+
for (const block of blocks.results) {
|
|
89
|
+
if ("type" in block) {
|
|
90
|
+
const blockType = block.type;
|
|
91
|
+
if (blockType === "paragraph" && "paragraph" in block) {
|
|
92
|
+
const paragraph = block.paragraph;
|
|
93
|
+
if (paragraph.rich_text && paragraph.rich_text.length > 0) {
|
|
94
|
+
content += indent + extractTextFromRichText(paragraph.rich_text) + "\n";
|
|
95
|
+
} else {
|
|
96
|
+
content += "\n";
|
|
97
|
+
}
|
|
98
|
+
} else if (blockType === "heading_1" && "heading_1" in block) {
|
|
99
|
+
const heading = block.heading_1;
|
|
100
|
+
if (heading.rich_text) {
|
|
101
|
+
content += indent + "# " + extractTextFromRichText(heading.rich_text) + "\n";
|
|
102
|
+
}
|
|
103
|
+
} else if (blockType === "heading_2" && "heading_2" in block) {
|
|
104
|
+
const heading = block.heading_2;
|
|
105
|
+
if (heading.rich_text) {
|
|
106
|
+
content += indent + "## " + extractTextFromRichText(heading.rich_text) + "\n";
|
|
107
|
+
}
|
|
108
|
+
} else if (blockType === "heading_3" && "heading_3" in block) {
|
|
109
|
+
const heading = block.heading_3;
|
|
110
|
+
if (heading.rich_text) {
|
|
111
|
+
content += indent + "### " + extractTextFromRichText(heading.rich_text) + "\n";
|
|
112
|
+
}
|
|
113
|
+
} else if (blockType === "bulleted_list_item" && "bulleted_list_item" in block) {
|
|
114
|
+
const item = block.bulleted_list_item;
|
|
115
|
+
if (item.rich_text) {
|
|
116
|
+
content += indent + "\u2022 " + extractTextFromRichText(item.rich_text) + "\n";
|
|
117
|
+
}
|
|
118
|
+
if (block.has_children && "id" in block) {
|
|
119
|
+
content += await getBlockContent(block.id, indentLevel + 1);
|
|
120
|
+
}
|
|
121
|
+
} else if (blockType === "numbered_list_item" && "numbered_list_item" in block) {
|
|
122
|
+
const item = block.numbered_list_item;
|
|
123
|
+
if (item.rich_text) {
|
|
124
|
+
content += indent + "1. " + extractTextFromRichText(item.rich_text) + "\n";
|
|
125
|
+
}
|
|
126
|
+
if (block.has_children && "id" in block) {
|
|
127
|
+
content += await getBlockContent(block.id, indentLevel + 1);
|
|
128
|
+
}
|
|
129
|
+
} else if (blockType === "to_do" && "to_do" in block) {
|
|
130
|
+
const todo = block.to_do;
|
|
131
|
+
const checkbox = todo.checked ? "[x]" : "[ ]";
|
|
132
|
+
if (todo.rich_text) {
|
|
133
|
+
content += indent + `- ${checkbox} ` + extractTextFromRichText(todo.rich_text) + "\n";
|
|
134
|
+
}
|
|
135
|
+
if (block.has_children && "id" in block) {
|
|
136
|
+
content += await getBlockContent(block.id, indentLevel + 1);
|
|
137
|
+
}
|
|
138
|
+
} else if (blockType === "toggle" && "toggle" in block) {
|
|
139
|
+
const toggle = block.toggle;
|
|
140
|
+
if (toggle.rich_text) {
|
|
141
|
+
content += indent + "\u25B8 " + extractTextFromRichText(toggle.rich_text) + "\n";
|
|
142
|
+
}
|
|
143
|
+
if (block.has_children && "id" in block) {
|
|
144
|
+
content += await getBlockContent(block.id, indentLevel + 1);
|
|
145
|
+
}
|
|
146
|
+
} else if (blockType === "code" && "code" in block) {
|
|
147
|
+
const code = block.code;
|
|
148
|
+
if (code.rich_text) {
|
|
149
|
+
content += indent + "```\n" + extractTextFromRichText(code.rich_text) + "\n```\n";
|
|
150
|
+
}
|
|
151
|
+
} else if (blockType === "quote" && "quote" in block) {
|
|
152
|
+
const quote = block.quote;
|
|
153
|
+
if (quote.rich_text) {
|
|
154
|
+
content += indent + "> " + extractTextFromRichText(quote.rich_text) + "\n";
|
|
155
|
+
}
|
|
156
|
+
} else if (blockType === "callout" && "callout" in block) {
|
|
157
|
+
const callout = block.callout;
|
|
158
|
+
if (callout.rich_text) {
|
|
159
|
+
const icon = callout.icon && "emoji" in callout.icon ? callout.icon.emoji : "\u{1F4CC}";
|
|
160
|
+
content += indent + `${icon} ` + extractTextFromRichText(callout.rich_text) + "\n";
|
|
161
|
+
}
|
|
162
|
+
} else if (blockType === "divider") {
|
|
163
|
+
content += indent + "---\n";
|
|
164
|
+
} else if (blockType === "child_page" && "child_page" in block && "id" in block) {
|
|
165
|
+
const childPage = block.child_page;
|
|
166
|
+
const title = childPage.title || "Untitled";
|
|
167
|
+
const url = `https://www.notion.so/${block.id.replace(/-/g, "")}`;
|
|
168
|
+
content += indent + `${title} (${url})
|
|
169
|
+
`;
|
|
170
|
+
} else if (blockType === "child_database" && "child_database" in block && "id" in block) {
|
|
171
|
+
const childDb = block.child_database;
|
|
172
|
+
const title = childDb.title || "Untitled Database";
|
|
173
|
+
const url = `https://www.notion.so/${block.id.replace(/-/g, "")}`;
|
|
174
|
+
content += indent + `[Database] ${title} (${url})
|
|
175
|
+
`;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return content;
|
|
180
|
+
}
|
|
181
|
+
return await getBlockContent(pageId);
|
|
182
|
+
}
|
|
183
|
+
function extractTextFromRichText(richText) {
|
|
184
|
+
return richText.map((text) => {
|
|
185
|
+
if (text.type === "mention" && text.mention) {
|
|
186
|
+
const plainText = text.plain_text;
|
|
187
|
+
if (text.mention.type === "page") {
|
|
188
|
+
const pageId = text.mention.page?.id;
|
|
189
|
+
if (pageId) {
|
|
190
|
+
const url = `https://www.notion.so/${pageId.replace(/-/g, "")}`;
|
|
191
|
+
return `${plainText} (${url})`;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return plainText;
|
|
195
|
+
}
|
|
196
|
+
return text.plain_text;
|
|
197
|
+
}).join("");
|
|
198
|
+
}
|
|
199
|
+
async function writeNotionPage(pageId, content, accessToken) {
|
|
200
|
+
const notion = new Client({ auth: accessToken });
|
|
201
|
+
const existingBlocks = await notion.blocks.children.list({
|
|
202
|
+
block_id: pageId
|
|
203
|
+
});
|
|
204
|
+
for (const block of existingBlocks.results) {
|
|
205
|
+
if ("id" in block) {
|
|
206
|
+
await notion.blocks.delete({
|
|
207
|
+
block_id: block.id
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const lines = content.split("\n").filter((line) => line.trim());
|
|
212
|
+
const blocks = [];
|
|
213
|
+
for (const line of lines) {
|
|
214
|
+
if (line.startsWith("# ")) {
|
|
215
|
+
blocks.push({
|
|
216
|
+
object: "block",
|
|
217
|
+
type: "heading_1",
|
|
218
|
+
heading_1: {
|
|
219
|
+
rich_text: [{ type: "text", text: { content: line.slice(2) } }]
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
} else if (line.startsWith("## ")) {
|
|
223
|
+
blocks.push({
|
|
224
|
+
object: "block",
|
|
225
|
+
type: "heading_2",
|
|
226
|
+
heading_2: {
|
|
227
|
+
rich_text: [{ type: "text", text: { content: line.slice(3) } }]
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
} else if (line.startsWith("### ")) {
|
|
231
|
+
blocks.push({
|
|
232
|
+
object: "block",
|
|
233
|
+
type: "heading_3",
|
|
234
|
+
heading_3: {
|
|
235
|
+
rich_text: [{ type: "text", text: { content: line.slice(4) } }]
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
} else if (line.startsWith("\u2022 ") || line.startsWith("- ")) {
|
|
239
|
+
blocks.push({
|
|
240
|
+
object: "block",
|
|
241
|
+
type: "bulleted_list_item",
|
|
242
|
+
bulleted_list_item: {
|
|
243
|
+
rich_text: [{ type: "text", text: { content: line.slice(2) } }]
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
} else if (line.match(/^\d+\.\s/)) {
|
|
247
|
+
blocks.push({
|
|
248
|
+
object: "block",
|
|
249
|
+
type: "numbered_list_item",
|
|
250
|
+
numbered_list_item: {
|
|
251
|
+
rich_text: [{ type: "text", text: { content: line.replace(/^\d+\.\s/, "") } }]
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
} else {
|
|
255
|
+
blocks.push({
|
|
256
|
+
object: "block",
|
|
257
|
+
type: "paragraph",
|
|
258
|
+
paragraph: {
|
|
259
|
+
rich_text: [{ type: "text", text: { content: line } }]
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const NOTION_BLOCK_BATCH_LIMIT = 100;
|
|
265
|
+
for (let i = 0; i < blocks.length; i += NOTION_BLOCK_BATCH_LIMIT) {
|
|
266
|
+
await notion.blocks.children.append({
|
|
267
|
+
block_id: pageId,
|
|
268
|
+
children: blocks.slice(i, i + NOTION_BLOCK_BATCH_LIMIT)
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
async function selectiveUpdateNotionPage(pageId, content, startBlockIndex, endBlockIndex, accessToken) {
|
|
273
|
+
const notion = new Client({ auth: accessToken });
|
|
274
|
+
const response = await notion.blocks.children.list({
|
|
275
|
+
block_id: pageId
|
|
276
|
+
});
|
|
277
|
+
const allBlocks = response.results;
|
|
278
|
+
if (startBlockIndex < 0 || endBlockIndex > allBlocks.length) {
|
|
279
|
+
throw new Error(`Invalid block range: startBlockIndex=${startBlockIndex}, endBlockIndex=${endBlockIndex}, total blocks=${allBlocks.length}`);
|
|
280
|
+
}
|
|
281
|
+
for (let i = startBlockIndex; i < endBlockIndex && i < allBlocks.length; i++) {
|
|
282
|
+
await notion.blocks.delete({
|
|
283
|
+
block_id: allBlocks[i].id
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
const lines = content.split("\n").filter((line) => line.trim());
|
|
287
|
+
const newBlocks = [];
|
|
288
|
+
for (const line of lines) {
|
|
289
|
+
if (line.startsWith("# ")) {
|
|
290
|
+
newBlocks.push({
|
|
291
|
+
object: "block",
|
|
292
|
+
type: "heading_1",
|
|
293
|
+
heading_1: {
|
|
294
|
+
rich_text: [{ type: "text", text: { content: line.slice(2) } }]
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
} else if (line.startsWith("## ")) {
|
|
298
|
+
newBlocks.push({
|
|
299
|
+
object: "block",
|
|
300
|
+
type: "heading_2",
|
|
301
|
+
heading_2: {
|
|
302
|
+
rich_text: [{ type: "text", text: { content: line.slice(3) } }]
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
} else if (line.startsWith("### ")) {
|
|
306
|
+
newBlocks.push({
|
|
307
|
+
object: "block",
|
|
308
|
+
type: "heading_3",
|
|
309
|
+
heading_3: {
|
|
310
|
+
rich_text: [{ type: "text", text: { content: line.slice(4) } }]
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
} else if (line.match(/^[•\-]\s/)) {
|
|
314
|
+
newBlocks.push({
|
|
315
|
+
object: "block",
|
|
316
|
+
type: "bulleted_list_item",
|
|
317
|
+
bulleted_list_item: {
|
|
318
|
+
rich_text: [{ type: "text", text: { content: line.slice(2) } }]
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
} else if (line.match(/^\d+\.\s/)) {
|
|
322
|
+
newBlocks.push({
|
|
323
|
+
object: "block",
|
|
324
|
+
type: "numbered_list_item",
|
|
325
|
+
numbered_list_item: {
|
|
326
|
+
rich_text: [{ type: "text", text: { content: line.replace(/^\d+\.\s/, "") } }]
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
} else {
|
|
330
|
+
newBlocks.push({
|
|
331
|
+
object: "block",
|
|
332
|
+
type: "paragraph",
|
|
333
|
+
paragraph: {
|
|
334
|
+
rich_text: [{ type: "text", text: { content: line } }]
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
if (newBlocks.length > 0) {
|
|
340
|
+
const blocksToMoveToEnd = allBlocks.slice(endBlockIndex);
|
|
341
|
+
const savedBlocks = [];
|
|
342
|
+
for (const block of blocksToMoveToEnd) {
|
|
343
|
+
savedBlocks.push(block);
|
|
344
|
+
await notion.blocks.delete({
|
|
345
|
+
block_id: block.id
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
await notion.blocks.children.append({
|
|
349
|
+
block_id: pageId,
|
|
350
|
+
children: newBlocks
|
|
351
|
+
});
|
|
352
|
+
if (savedBlocks.length > 0) {
|
|
353
|
+
const blocksToReinsert = savedBlocks.map((block) => {
|
|
354
|
+
const blockType = block.type;
|
|
355
|
+
const blockData = block[blockType];
|
|
356
|
+
return {
|
|
357
|
+
object: "block",
|
|
358
|
+
type: blockType,
|
|
359
|
+
[blockType]: blockData
|
|
360
|
+
};
|
|
361
|
+
});
|
|
362
|
+
await notion.blocks.children.append({
|
|
363
|
+
block_id: pageId,
|
|
364
|
+
children: blocksToReinsert
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
async function appendNotionPage(pageId, content, accessToken) {
|
|
370
|
+
const notion = new Client({ auth: accessToken });
|
|
371
|
+
const lines = content.split("\n").filter((line) => line.trim());
|
|
372
|
+
const blocks = [];
|
|
373
|
+
for (const line of lines) {
|
|
374
|
+
if (line.startsWith("# ")) {
|
|
375
|
+
blocks.push({
|
|
376
|
+
object: "block",
|
|
377
|
+
type: "heading_1",
|
|
378
|
+
heading_1: {
|
|
379
|
+
rich_text: [{ type: "text", text: { content: line.slice(2) } }]
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
} else if (line.startsWith("## ")) {
|
|
383
|
+
blocks.push({
|
|
384
|
+
object: "block",
|
|
385
|
+
type: "heading_2",
|
|
386
|
+
heading_2: {
|
|
387
|
+
rich_text: [{ type: "text", text: { content: line.slice(3) } }]
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
} else if (line.startsWith("### ")) {
|
|
391
|
+
blocks.push({
|
|
392
|
+
object: "block",
|
|
393
|
+
type: "heading_3",
|
|
394
|
+
heading_3: {
|
|
395
|
+
rich_text: [{ type: "text", text: { content: line.slice(4) } }]
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
} else {
|
|
399
|
+
blocks.push({
|
|
400
|
+
object: "block",
|
|
401
|
+
type: "paragraph",
|
|
402
|
+
paragraph: {
|
|
403
|
+
rich_text: [{ type: "text", text: { content: line } }]
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
const NOTION_BLOCK_BATCH_LIMIT = 100;
|
|
409
|
+
for (let i = 0; i < blocks.length; i += NOTION_BLOCK_BATCH_LIMIT) {
|
|
410
|
+
await notion.blocks.children.append({
|
|
411
|
+
block_id: pageId,
|
|
412
|
+
children: blocks.slice(i, i + NOTION_BLOCK_BATCH_LIMIT)
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
async function deleteNotionPage(pageId, accessToken) {
|
|
417
|
+
const notion = new Client({ auth: accessToken });
|
|
418
|
+
try {
|
|
419
|
+
await notion.pages.update({
|
|
420
|
+
page_id: pageId,
|
|
421
|
+
archived: true
|
|
422
|
+
});
|
|
423
|
+
} catch (error) {
|
|
424
|
+
if (error?.message?.includes("workspace level pages")) {
|
|
425
|
+
throw new Error("This Notion page cannot be removed via API because it's at the workspace level. Please move it to a parent page first, or remove it manually in Notion.");
|
|
426
|
+
}
|
|
427
|
+
throw error;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
async function renameNotionPage(pageId, newTitle, accessToken) {
|
|
431
|
+
const notion = new Client({ auth: accessToken });
|
|
432
|
+
await notion.pages.update({
|
|
433
|
+
page_id: pageId,
|
|
434
|
+
properties: {
|
|
435
|
+
title: {
|
|
436
|
+
title: [
|
|
437
|
+
{
|
|
438
|
+
text: {
|
|
439
|
+
content: newTitle
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
]
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
async function duplicateNotionPage(pageId, newTitle, accessToken) {
|
|
448
|
+
const notion = new Client({ auth: accessToken });
|
|
449
|
+
const originalPage = await notion.pages.retrieve({ page_id: pageId });
|
|
450
|
+
if (!("parent" in originalPage)) {
|
|
451
|
+
throw new Error("Cannot access parent information for this page");
|
|
452
|
+
}
|
|
453
|
+
const blocks = await notion.blocks.children.list({
|
|
454
|
+
block_id: pageId
|
|
455
|
+
});
|
|
456
|
+
const parent = originalPage.parent;
|
|
457
|
+
let validParent;
|
|
458
|
+
if ("page_id" in parent) {
|
|
459
|
+
validParent = { page_id: parent.page_id };
|
|
460
|
+
} else if ("database_id" in parent) {
|
|
461
|
+
validParent = { database_id: parent.database_id };
|
|
462
|
+
} else if ("workspace" in parent) {
|
|
463
|
+
validParent = { workspace: true };
|
|
464
|
+
} else {
|
|
465
|
+
throw new Error("Cannot duplicate page: parent type not supported");
|
|
466
|
+
}
|
|
467
|
+
const newPage = await notion.pages.create({
|
|
468
|
+
parent: validParent,
|
|
469
|
+
properties: {
|
|
470
|
+
title: {
|
|
471
|
+
title: [
|
|
472
|
+
{
|
|
473
|
+
text: {
|
|
474
|
+
content: newTitle
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
]
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
if (blocks.results.length > 0) {
|
|
482
|
+
const blockChildren = blocks.results.map((block) => {
|
|
483
|
+
const blockCopy = {
|
|
484
|
+
type: block.type
|
|
485
|
+
};
|
|
486
|
+
if (block.type && block[block.type]) {
|
|
487
|
+
blockCopy[block.type] = block[block.type];
|
|
488
|
+
}
|
|
489
|
+
return blockCopy;
|
|
490
|
+
});
|
|
491
|
+
try {
|
|
492
|
+
await notion.blocks.children.append({
|
|
493
|
+
block_id: newPage.id,
|
|
494
|
+
children: blockChildren
|
|
495
|
+
});
|
|
496
|
+
} catch (error) {
|
|
497
|
+
console.error("Error copying blocks to new page:", error);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return {
|
|
501
|
+
pageId: newPage.id,
|
|
502
|
+
url: "url" in newPage ? newPage.url : `https://www.notion.so/${newPage.id.replace(/-/g, "")}`
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
async function createNotionSubpage(parentPageId, pageTitle, accessToken, initialContent) {
|
|
506
|
+
const notion = new Client({ auth: accessToken });
|
|
507
|
+
const pageData = {
|
|
508
|
+
properties: {
|
|
509
|
+
title: {
|
|
510
|
+
title: [
|
|
511
|
+
{
|
|
512
|
+
text: {
|
|
513
|
+
content: pageTitle
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
]
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
if (parentPageId) {
|
|
521
|
+
pageData.parent = {
|
|
522
|
+
page_id: parentPageId
|
|
523
|
+
};
|
|
524
|
+
} else {
|
|
525
|
+
pageData.parent = {
|
|
526
|
+
workspace: true
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
const newPage = await notion.pages.create(pageData);
|
|
530
|
+
if (initialContent) {
|
|
531
|
+
const lines = initialContent.split("\n").filter((line) => line.trim());
|
|
532
|
+
const blocks = [];
|
|
533
|
+
const NOTION_TEXT_LIMIT = 2e3;
|
|
534
|
+
const chunkText = (text) => {
|
|
535
|
+
const chunks = [];
|
|
536
|
+
for (let i = 0; i < text.length; i += NOTION_TEXT_LIMIT) {
|
|
537
|
+
chunks.push(text.slice(i, i + NOTION_TEXT_LIMIT));
|
|
538
|
+
}
|
|
539
|
+
return chunks.length > 0 ? chunks : [""];
|
|
540
|
+
};
|
|
541
|
+
for (const line of lines) {
|
|
542
|
+
if (line.startsWith("# ")) {
|
|
543
|
+
blocks.push({
|
|
544
|
+
object: "block",
|
|
545
|
+
type: "heading_1",
|
|
546
|
+
heading_1: {
|
|
547
|
+
rich_text: [{ type: "text", text: { content: line.slice(2).slice(0, NOTION_TEXT_LIMIT) } }]
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
} else if (line.startsWith("## ")) {
|
|
551
|
+
blocks.push({
|
|
552
|
+
object: "block",
|
|
553
|
+
type: "heading_2",
|
|
554
|
+
heading_2: {
|
|
555
|
+
rich_text: [{ type: "text", text: { content: line.slice(3).slice(0, NOTION_TEXT_LIMIT) } }]
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
} else if (line.startsWith("### ")) {
|
|
559
|
+
blocks.push({
|
|
560
|
+
object: "block",
|
|
561
|
+
type: "heading_3",
|
|
562
|
+
heading_3: {
|
|
563
|
+
rich_text: [{ type: "text", text: { content: line.slice(4).slice(0, NOTION_TEXT_LIMIT) } }]
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
} else if (line.startsWith("\u2022 ") || line.startsWith("- ")) {
|
|
567
|
+
blocks.push({
|
|
568
|
+
object: "block",
|
|
569
|
+
type: "bulleted_list_item",
|
|
570
|
+
bulleted_list_item: {
|
|
571
|
+
rich_text: [{ type: "text", text: { content: line.slice(2).slice(0, NOTION_TEXT_LIMIT) } }]
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
} else {
|
|
575
|
+
for (const chunk of chunkText(line)) {
|
|
576
|
+
blocks.push({
|
|
577
|
+
object: "block",
|
|
578
|
+
type: "paragraph",
|
|
579
|
+
paragraph: {
|
|
580
|
+
rich_text: [{ type: "text", text: { content: chunk } }]
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
const NOTION_BLOCK_BATCH_LIMIT = 100;
|
|
587
|
+
for (let i = 0; i < blocks.length; i += NOTION_BLOCK_BATCH_LIMIT) {
|
|
588
|
+
await notion.blocks.children.append({
|
|
589
|
+
block_id: newPage.id,
|
|
590
|
+
children: blocks.slice(i, i + NOTION_BLOCK_BATCH_LIMIT)
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return {
|
|
595
|
+
pageId: newPage.id,
|
|
596
|
+
url: "url" in newPage ? newPage.url : `https://www.notion.so/${newPage.id.replace(/-/g, "")}`
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
async function listNotionPageChildren(pageId, accessToken) {
|
|
600
|
+
const notion = new Client({ auth: accessToken });
|
|
601
|
+
const blocks = await notion.blocks.children.list({
|
|
602
|
+
block_id: pageId
|
|
603
|
+
});
|
|
604
|
+
const children = [];
|
|
605
|
+
for (const block of blocks.results) {
|
|
606
|
+
if (!("type" in block)) continue;
|
|
607
|
+
if (block.type === "child_page" && "child_page" in block) {
|
|
608
|
+
const childPage = block.child_page;
|
|
609
|
+
children.push({
|
|
610
|
+
id: block.id,
|
|
611
|
+
title: childPage.title || "Untitled",
|
|
612
|
+
type: "page",
|
|
613
|
+
url: `https://www.notion.so/${block.id.replace(/-/g, "")}`
|
|
614
|
+
});
|
|
615
|
+
} else if (block.type === "child_database" && "child_database" in block) {
|
|
616
|
+
const childDb = block.child_database;
|
|
617
|
+
children.push({
|
|
618
|
+
id: block.id,
|
|
619
|
+
title: childDb.title || "Untitled Database",
|
|
620
|
+
type: "database",
|
|
621
|
+
url: `https://www.notion.so/${block.id.replace(/-/g, "")}`
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
return children;
|
|
626
|
+
}
|
|
627
|
+
async function getNotionDatabase(databaseId, accessToken) {
|
|
628
|
+
const notion = new Client({ auth: accessToken });
|
|
629
|
+
const database = await notion.databases.retrieve({ database_id: databaseId });
|
|
630
|
+
let title = "Untitled Database";
|
|
631
|
+
if (database.title && database.title.length > 0) {
|
|
632
|
+
title = database.title[0].plain_text || "Untitled Database";
|
|
633
|
+
}
|
|
634
|
+
return {
|
|
635
|
+
title,
|
|
636
|
+
properties: database.properties || {}
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
async function createNotionDatabasePage(databaseId, properties, accessToken, content) {
|
|
640
|
+
const notion = new Client({ auth: accessToken });
|
|
641
|
+
const database = await notion.databases.retrieve({ database_id: databaseId });
|
|
642
|
+
let dbProperties;
|
|
643
|
+
if (database.data_sources && database.data_sources.length > 0) {
|
|
644
|
+
const dataSourceId = database.data_sources[0].id;
|
|
645
|
+
console.log(`Fetching data source properties for data source ID: ${dataSourceId}`);
|
|
646
|
+
try {
|
|
647
|
+
const dataSource = await notion.dataSources.retrieve({ data_source_id: dataSourceId });
|
|
648
|
+
dbProperties = dataSource.properties;
|
|
649
|
+
} catch (error) {
|
|
650
|
+
console.error("Error fetching data source:", error);
|
|
651
|
+
throw new Error(`Failed to fetch data source properties: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
652
|
+
}
|
|
653
|
+
} else if (database.properties) {
|
|
654
|
+
dbProperties = database.properties;
|
|
655
|
+
}
|
|
656
|
+
if (!dbProperties || Object.keys(dbProperties).length === 0) {
|
|
657
|
+
console.error("Database retrieval result:", JSON.stringify(database, null, 2));
|
|
658
|
+
throw new Error(`Database has no properties defined. Database ID: ${databaseId}`);
|
|
659
|
+
}
|
|
660
|
+
const formattedProperties = {};
|
|
661
|
+
for (const [propName, propValue] of Object.entries(properties)) {
|
|
662
|
+
const propSchema = dbProperties[propName];
|
|
663
|
+
if (!propSchema) {
|
|
664
|
+
console.warn(`Property "${propName}" not found in database schema, skipping`);
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
const propType = propSchema.type;
|
|
668
|
+
switch (propType) {
|
|
669
|
+
case "title":
|
|
670
|
+
formattedProperties[propName] = {
|
|
671
|
+
title: [{ text: { content: String(propValue) } }]
|
|
672
|
+
};
|
|
673
|
+
break;
|
|
674
|
+
case "rich_text":
|
|
675
|
+
formattedProperties[propName] = {
|
|
676
|
+
rich_text: [{ text: { content: String(propValue) } }]
|
|
677
|
+
};
|
|
678
|
+
break;
|
|
679
|
+
case "number":
|
|
680
|
+
formattedProperties[propName] = {
|
|
681
|
+
number: Number(propValue)
|
|
682
|
+
};
|
|
683
|
+
break;
|
|
684
|
+
case "select":
|
|
685
|
+
formattedProperties[propName] = {
|
|
686
|
+
select: { name: String(propValue) }
|
|
687
|
+
};
|
|
688
|
+
break;
|
|
689
|
+
case "multi_select":
|
|
690
|
+
const values = Array.isArray(propValue) ? propValue : [propValue];
|
|
691
|
+
formattedProperties[propName] = {
|
|
692
|
+
multi_select: values.map((v) => ({ name: String(v) }))
|
|
693
|
+
};
|
|
694
|
+
break;
|
|
695
|
+
case "date":
|
|
696
|
+
formattedProperties[propName] = {
|
|
697
|
+
date: { start: String(propValue) }
|
|
698
|
+
};
|
|
699
|
+
break;
|
|
700
|
+
case "checkbox":
|
|
701
|
+
formattedProperties[propName] = {
|
|
702
|
+
checkbox: Boolean(propValue)
|
|
703
|
+
};
|
|
704
|
+
break;
|
|
705
|
+
case "url":
|
|
706
|
+
formattedProperties[propName] = {
|
|
707
|
+
url: String(propValue)
|
|
708
|
+
};
|
|
709
|
+
break;
|
|
710
|
+
case "email":
|
|
711
|
+
formattedProperties[propName] = {
|
|
712
|
+
email: String(propValue)
|
|
713
|
+
};
|
|
714
|
+
break;
|
|
715
|
+
case "phone_number":
|
|
716
|
+
formattedProperties[propName] = {
|
|
717
|
+
phone_number: String(propValue)
|
|
718
|
+
};
|
|
719
|
+
break;
|
|
720
|
+
default:
|
|
721
|
+
console.warn(`Property type "${propType}" not supported for property "${propName}", skipping`);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
if (Object.keys(formattedProperties).length === 0) {
|
|
725
|
+
const availableProps = Object.keys(dbProperties).join(", ");
|
|
726
|
+
const requestedProps = Object.keys(properties).join(", ");
|
|
727
|
+
throw new Error(
|
|
728
|
+
`No valid properties found. Requested properties: ${requestedProps}. Available properties in database: ${availableProps}`
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
const newPage = await notion.pages.create({
|
|
732
|
+
parent: { database_id: databaseId },
|
|
733
|
+
properties: formattedProperties
|
|
734
|
+
});
|
|
735
|
+
if (content) {
|
|
736
|
+
const lines = content.split("\n").filter((line) => line.trim());
|
|
737
|
+
const blocks = [];
|
|
738
|
+
for (const line of lines) {
|
|
739
|
+
if (line.startsWith("# ")) {
|
|
740
|
+
blocks.push({
|
|
741
|
+
object: "block",
|
|
742
|
+
type: "heading_1",
|
|
743
|
+
heading_1: {
|
|
744
|
+
rich_text: [{ type: "text", text: { content: line.slice(2) } }]
|
|
745
|
+
}
|
|
746
|
+
});
|
|
747
|
+
} else if (line.startsWith("## ")) {
|
|
748
|
+
blocks.push({
|
|
749
|
+
object: "block",
|
|
750
|
+
type: "heading_2",
|
|
751
|
+
heading_2: {
|
|
752
|
+
rich_text: [{ type: "text", text: { content: line.slice(3) } }]
|
|
753
|
+
}
|
|
754
|
+
});
|
|
755
|
+
} else if (line.startsWith("### ")) {
|
|
756
|
+
blocks.push({
|
|
757
|
+
object: "block",
|
|
758
|
+
type: "heading_3",
|
|
759
|
+
heading_3: {
|
|
760
|
+
rich_text: [{ type: "text", text: { content: line.slice(4) } }]
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
} else if (line.startsWith("\u2022 ") || line.startsWith("- ")) {
|
|
764
|
+
blocks.push({
|
|
765
|
+
object: "block",
|
|
766
|
+
type: "bulleted_list_item",
|
|
767
|
+
bulleted_list_item: {
|
|
768
|
+
rich_text: [{ type: "text", text: { content: line.slice(2) } }]
|
|
769
|
+
}
|
|
770
|
+
});
|
|
771
|
+
} else {
|
|
772
|
+
blocks.push({
|
|
773
|
+
object: "block",
|
|
774
|
+
type: "paragraph",
|
|
775
|
+
paragraph: {
|
|
776
|
+
rich_text: [{ type: "text", text: { content: line } }]
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
const NOTION_BLOCK_BATCH_LIMIT = 100;
|
|
782
|
+
for (let i = 0; i < blocks.length; i += NOTION_BLOCK_BATCH_LIMIT) {
|
|
783
|
+
await notion.blocks.children.append({
|
|
784
|
+
block_id: newPage.id,
|
|
785
|
+
children: blocks.slice(i, i + NOTION_BLOCK_BATCH_LIMIT)
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
return {
|
|
790
|
+
pageId: newPage.id,
|
|
791
|
+
url: "url" in newPage ? newPage.url : `https://www.notion.so/${newPage.id.replace(/-/g, "")}`
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
async function queryNotionDatabase(databaseId, accessToken, filter, sorts) {
|
|
795
|
+
const notion = new Client({ auth: accessToken });
|
|
796
|
+
const response = await notion.databases.query({
|
|
797
|
+
database_id: databaseId,
|
|
798
|
+
filter,
|
|
799
|
+
sorts
|
|
800
|
+
});
|
|
801
|
+
return response.results;
|
|
802
|
+
}
|
|
803
|
+
async function createNotionDatabase(parentPageId, title, properties, accessToken) {
|
|
804
|
+
const notion = new Client({ auth: accessToken });
|
|
805
|
+
const parent = parentPageId ? {
|
|
806
|
+
type: "page_id",
|
|
807
|
+
page_id: parentPageId
|
|
808
|
+
} : {
|
|
809
|
+
type: "workspace",
|
|
810
|
+
workspace: true
|
|
811
|
+
};
|
|
812
|
+
const database = await notion.databases.create({
|
|
813
|
+
parent,
|
|
814
|
+
title: [
|
|
815
|
+
{
|
|
816
|
+
type: "text",
|
|
817
|
+
text: {
|
|
818
|
+
content: title
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
],
|
|
822
|
+
properties
|
|
823
|
+
});
|
|
824
|
+
return {
|
|
825
|
+
databaseId: database.id,
|
|
826
|
+
url: "url" in database ? database.url : `https://www.notion.so/${database.id.replace(/-/g, "")}`
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
async function readNotionDatabase(databaseId, accessToken) {
|
|
830
|
+
const notion = new Client({ auth: accessToken });
|
|
831
|
+
const database = await notion.databases.retrieve({ database_id: databaseId });
|
|
832
|
+
let title = "Untitled Database";
|
|
833
|
+
if (database.title && database.title.length > 0) {
|
|
834
|
+
title = database.title[0].plain_text || "Untitled Database";
|
|
835
|
+
}
|
|
836
|
+
let dbProperties = {};
|
|
837
|
+
if (database.data_sources && database.data_sources.length > 0) {
|
|
838
|
+
const dataSourceId = database.data_sources[0].id;
|
|
839
|
+
try {
|
|
840
|
+
const dataSource = await notion.dataSources.retrieve({ data_source_id: dataSourceId });
|
|
841
|
+
dbProperties = dataSource.properties || {};
|
|
842
|
+
} catch (error) {
|
|
843
|
+
console.error("Error fetching data source:", error);
|
|
844
|
+
}
|
|
845
|
+
} else if (database.properties) {
|
|
846
|
+
dbProperties = database.properties;
|
|
847
|
+
}
|
|
848
|
+
return {
|
|
849
|
+
title,
|
|
850
|
+
properties: dbProperties,
|
|
851
|
+
url: database.url || `https://www.notion.so/${databaseId.replace(/-/g, "")}`
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
async function updateNotionDatabase(databaseId, accessToken, title, properties) {
|
|
855
|
+
const notion = new Client({ auth: accessToken });
|
|
856
|
+
if (title) {
|
|
857
|
+
await notion.databases.update({
|
|
858
|
+
database_id: databaseId,
|
|
859
|
+
title: [
|
|
860
|
+
{
|
|
861
|
+
type: "text",
|
|
862
|
+
text: {
|
|
863
|
+
content: title
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
]
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
if (properties && Object.keys(properties).length > 0) {
|
|
870
|
+
const database = await notion.databases.retrieve({ database_id: databaseId });
|
|
871
|
+
if (database.data_sources && database.data_sources.length > 0) {
|
|
872
|
+
const dataSourceId = database.data_sources[0].id;
|
|
873
|
+
await notion.dataSources.update({
|
|
874
|
+
data_source_id: dataSourceId,
|
|
875
|
+
properties
|
|
876
|
+
});
|
|
877
|
+
} else {
|
|
878
|
+
await notion.databases.update({
|
|
879
|
+
database_id: databaseId,
|
|
880
|
+
properties
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
async function deleteNotionDatabase(databaseId, accessToken) {
|
|
886
|
+
const notion = new Client({ auth: accessToken });
|
|
887
|
+
try {
|
|
888
|
+
const result = await notion.databases.update({
|
|
889
|
+
database_id: databaseId,
|
|
890
|
+
in_trash: true
|
|
891
|
+
// Databases use in_trash instead of archived
|
|
892
|
+
});
|
|
893
|
+
console.log("[deleteNotionDatabase] Database moved to trash successfully:", {
|
|
894
|
+
id: result.id,
|
|
895
|
+
in_trash: result.in_trash
|
|
896
|
+
});
|
|
897
|
+
} catch (error) {
|
|
898
|
+
if (error.message && error.message.includes("workspace level")) {
|
|
899
|
+
throw new Error("Cannot archive workspace-level databases via API. Only databases inside pages can be archived. Please archive this database manually in Notion or move it inside a page first.");
|
|
900
|
+
}
|
|
901
|
+
throw new Error(`Failed to move database to trash: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
async function duplicateNotionDatabase(databaseId, accessToken, newDatabaseName) {
|
|
905
|
+
const notion = new Client({ auth: accessToken });
|
|
906
|
+
const originalDatabase = await notion.databases.retrieve({ database_id: databaseId });
|
|
907
|
+
let originalTitle = "Untitled";
|
|
908
|
+
if (originalDatabase.title && Array.isArray(originalDatabase.title) && originalDatabase.title.length > 0) {
|
|
909
|
+
originalTitle = originalDatabase.title[0].plain_text || "Untitled";
|
|
910
|
+
}
|
|
911
|
+
const newTitle = newDatabaseName || `${originalTitle} (Copy)`;
|
|
912
|
+
let rawProperties = {};
|
|
913
|
+
if (originalDatabase.data_sources && originalDatabase.data_sources.length > 0) {
|
|
914
|
+
const dataSourceId = originalDatabase.data_sources[0].id;
|
|
915
|
+
const dataSource = await notion.dataSources.retrieve({ data_source_id: dataSourceId });
|
|
916
|
+
rawProperties = dataSource.properties;
|
|
917
|
+
} else if (originalDatabase.properties) {
|
|
918
|
+
rawProperties = originalDatabase.properties;
|
|
919
|
+
}
|
|
920
|
+
console.log("[duplicateDatabase] Raw properties retrieved:", JSON.stringify(rawProperties, null, 2));
|
|
921
|
+
const properties = {};
|
|
922
|
+
for (const [propName, propSchema] of Object.entries(rawProperties)) {
|
|
923
|
+
const schema = propSchema;
|
|
924
|
+
const propType = schema.type;
|
|
925
|
+
if (propType === "title") {
|
|
926
|
+
properties[propName] = { title: {} };
|
|
927
|
+
} else if (propType === "rich_text") {
|
|
928
|
+
properties[propName] = { rich_text: {} };
|
|
929
|
+
} else if (propType === "number") {
|
|
930
|
+
properties[propName] = { number: schema.number || {} };
|
|
931
|
+
} else if (propType === "select") {
|
|
932
|
+
properties[propName] = { select: schema.select || {} };
|
|
933
|
+
} else if (propType === "multi_select") {
|
|
934
|
+
properties[propName] = { multi_select: schema.multi_select || {} };
|
|
935
|
+
} else if (propType === "date") {
|
|
936
|
+
properties[propName] = { date: {} };
|
|
937
|
+
} else if (propType === "checkbox") {
|
|
938
|
+
properties[propName] = { checkbox: {} };
|
|
939
|
+
} else if (propType === "url") {
|
|
940
|
+
properties[propName] = { url: {} };
|
|
941
|
+
} else if (propType === "email") {
|
|
942
|
+
properties[propName] = { email: {} };
|
|
943
|
+
} else if (propType === "phone_number") {
|
|
944
|
+
properties[propName] = { phone_number: {} };
|
|
945
|
+
} else if (propType === "people") {
|
|
946
|
+
properties[propName] = { people: {} };
|
|
947
|
+
} else if (propType === "files") {
|
|
948
|
+
properties[propName] = { files: {} };
|
|
949
|
+
} else if (propType === "relation") {
|
|
950
|
+
properties[propName] = { relation: schema.relation || {} };
|
|
951
|
+
} else if (propType === "rollup") {
|
|
952
|
+
properties[propName] = { rollup: schema.rollup || {} };
|
|
953
|
+
} else if (propType === "formula") {
|
|
954
|
+
properties[propName] = { formula: schema.formula || {} };
|
|
955
|
+
} else if (propType === "status") {
|
|
956
|
+
properties[propName] = { status: schema.status || {} };
|
|
957
|
+
} else if (propType === "created_time") {
|
|
958
|
+
properties[propName] = { created_time: {} };
|
|
959
|
+
} else if (propType === "created_by") {
|
|
960
|
+
properties[propName] = { created_by: {} };
|
|
961
|
+
} else if (propType === "last_edited_time") {
|
|
962
|
+
properties[propName] = { last_edited_time: {} };
|
|
963
|
+
} else if (propType === "last_edited_by") {
|
|
964
|
+
properties[propName] = { last_edited_by: {} };
|
|
965
|
+
} else {
|
|
966
|
+
console.warn(`[duplicateDatabase] Unknown property type "${propType}" for property "${propName}", copying as-is`);
|
|
967
|
+
properties[propName] = { [propType]: schema[propType] || {} };
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
console.log("[duplicateDatabase] Cleaned properties for create:", JSON.stringify(properties, null, 2));
|
|
971
|
+
const parent = originalDatabase.parent;
|
|
972
|
+
console.log("[duplicateDatabase] Creating database with parent:", JSON.stringify(parent, null, 2));
|
|
973
|
+
console.log("[duplicateDatabase] Number of properties:", Object.keys(properties).length);
|
|
974
|
+
try {
|
|
975
|
+
const titleProperty = Object.entries(properties).find(([_, schema]) => schema.title);
|
|
976
|
+
const titlePropName = titleProperty ? titleProperty[0] : "Name";
|
|
977
|
+
const minimalProperties = titleProperty ? { [titlePropName]: titleProperty[1] } : { "Name": { "title": {} } };
|
|
978
|
+
console.log("[duplicateDatabase] Step 1: Creating with title property only");
|
|
979
|
+
const newDatabase = await notion.databases.create({
|
|
980
|
+
parent,
|
|
981
|
+
title: [{ type: "text", text: { content: newTitle } }],
|
|
982
|
+
properties: minimalProperties
|
|
983
|
+
});
|
|
984
|
+
console.log("[duplicateDatabase] Database created:", newDatabase.id);
|
|
985
|
+
const remainingProperties = Object.fromEntries(
|
|
986
|
+
Object.entries(properties).filter(([name, _]) => name !== titlePropName)
|
|
987
|
+
);
|
|
988
|
+
if (Object.keys(remainingProperties).length > 0) {
|
|
989
|
+
console.log("[duplicateDatabase] Step 2: Adding", Object.keys(remainingProperties).length, "remaining properties");
|
|
990
|
+
const createdDb = await notion.databases.retrieve({ database_id: newDatabase.id });
|
|
991
|
+
if (createdDb.data_sources && createdDb.data_sources.length > 0) {
|
|
992
|
+
const dataSourceId = createdDb.data_sources[0].id;
|
|
993
|
+
console.log("[duplicateDatabase] Using data source API to add properties to data source:", dataSourceId);
|
|
994
|
+
await notion.dataSources.update({
|
|
995
|
+
data_source_id: dataSourceId,
|
|
996
|
+
properties: remainingProperties
|
|
997
|
+
});
|
|
998
|
+
console.log("[duplicateDatabase] All properties added successfully via data source API");
|
|
999
|
+
} else {
|
|
1000
|
+
console.log("[duplicateDatabase] No data sources found, using legacy database update API");
|
|
1001
|
+
await notion.databases.update({
|
|
1002
|
+
database_id: newDatabase.id,
|
|
1003
|
+
properties: remainingProperties
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
const verifyDatabase = await notion.databases.retrieve({ database_id: newDatabase.id });
|
|
1008
|
+
let finalPropertyCount = 0;
|
|
1009
|
+
if (verifyDatabase.data_sources && verifyDatabase.data_sources.length > 0) {
|
|
1010
|
+
const dataSourceId = verifyDatabase.data_sources[0].id;
|
|
1011
|
+
const verifyDataSource = await notion.dataSources.retrieve({ data_source_id: dataSourceId });
|
|
1012
|
+
finalPropertyCount = Object.keys(verifyDataSource.properties || {}).length;
|
|
1013
|
+
console.log("[duplicateDatabase] Final verification - properties:", finalPropertyCount, "/", Object.keys(properties).length);
|
|
1014
|
+
} else if (verifyDatabase.properties) {
|
|
1015
|
+
finalPropertyCount = Object.keys(verifyDatabase.properties).length;
|
|
1016
|
+
console.log("[duplicateDatabase] Final verification - properties:", finalPropertyCount, "/", Object.keys(properties).length);
|
|
1017
|
+
}
|
|
1018
|
+
return {
|
|
1019
|
+
databaseId: newDatabase.id,
|
|
1020
|
+
title: newTitle,
|
|
1021
|
+
url: "url" in newDatabase ? newDatabase.url : `https://www.notion.so/${newDatabase.id.replace(/-/g, "")}`
|
|
1022
|
+
};
|
|
1023
|
+
} catch (error) {
|
|
1024
|
+
console.error("[duplicateDatabase] Error:", error);
|
|
1025
|
+
throw error;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
async function updateNotionDatabasePage(pageId, accessToken, properties, content) {
|
|
1029
|
+
const notion = new Client({ auth: accessToken });
|
|
1030
|
+
if (properties && Object.keys(properties).length > 0) {
|
|
1031
|
+
const page = await notion.pages.retrieve({ page_id: pageId });
|
|
1032
|
+
if (!("parent" in page) || !("database_id" in page.parent)) {
|
|
1033
|
+
throw new Error("This page is not a database page");
|
|
1034
|
+
}
|
|
1035
|
+
const databaseId = page.parent.database_id;
|
|
1036
|
+
const database = await notion.databases.retrieve({ database_id: databaseId });
|
|
1037
|
+
let dbProperties;
|
|
1038
|
+
if (database.data_sources && database.data_sources.length > 0) {
|
|
1039
|
+
const dataSourceId = database.data_sources[0].id;
|
|
1040
|
+
console.log(`Fetching data source properties for data source ID: ${dataSourceId}`);
|
|
1041
|
+
try {
|
|
1042
|
+
const dataSource = await notion.dataSources.retrieve({ data_source_id: dataSourceId });
|
|
1043
|
+
dbProperties = dataSource.properties;
|
|
1044
|
+
} catch (error) {
|
|
1045
|
+
console.error("Error fetching data source:", error);
|
|
1046
|
+
throw new Error(`Failed to fetch data source properties: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1047
|
+
}
|
|
1048
|
+
} else if (database.properties) {
|
|
1049
|
+
dbProperties = database.properties;
|
|
1050
|
+
}
|
|
1051
|
+
if (!dbProperties || Object.keys(dbProperties).length === 0) {
|
|
1052
|
+
throw new Error("Database has no properties defined");
|
|
1053
|
+
}
|
|
1054
|
+
const formattedProperties = {};
|
|
1055
|
+
for (const [propName, propValue] of Object.entries(properties)) {
|
|
1056
|
+
const propSchema = dbProperties[propName];
|
|
1057
|
+
if (!propSchema) {
|
|
1058
|
+
console.warn(`Property "${propName}" not found in database schema, skipping`);
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
const propType = propSchema.type;
|
|
1062
|
+
switch (propType) {
|
|
1063
|
+
case "title":
|
|
1064
|
+
formattedProperties[propName] = {
|
|
1065
|
+
title: [{ text: { content: String(propValue) } }]
|
|
1066
|
+
};
|
|
1067
|
+
break;
|
|
1068
|
+
case "rich_text":
|
|
1069
|
+
formattedProperties[propName] = {
|
|
1070
|
+
rich_text: [{ text: { content: String(propValue) } }]
|
|
1071
|
+
};
|
|
1072
|
+
break;
|
|
1073
|
+
case "number":
|
|
1074
|
+
formattedProperties[propName] = {
|
|
1075
|
+
number: Number(propValue)
|
|
1076
|
+
};
|
|
1077
|
+
break;
|
|
1078
|
+
case "select":
|
|
1079
|
+
formattedProperties[propName] = {
|
|
1080
|
+
select: propValue === null ? null : { name: String(propValue) }
|
|
1081
|
+
};
|
|
1082
|
+
break;
|
|
1083
|
+
case "multi_select":
|
|
1084
|
+
const values = Array.isArray(propValue) ? propValue : [propValue];
|
|
1085
|
+
formattedProperties[propName] = {
|
|
1086
|
+
multi_select: values.map((v) => ({ name: String(v) }))
|
|
1087
|
+
};
|
|
1088
|
+
break;
|
|
1089
|
+
case "date":
|
|
1090
|
+
formattedProperties[propName] = {
|
|
1091
|
+
date: propValue === null ? null : { start: String(propValue) }
|
|
1092
|
+
};
|
|
1093
|
+
break;
|
|
1094
|
+
case "checkbox":
|
|
1095
|
+
formattedProperties[propName] = {
|
|
1096
|
+
checkbox: Boolean(propValue)
|
|
1097
|
+
};
|
|
1098
|
+
break;
|
|
1099
|
+
case "url":
|
|
1100
|
+
formattedProperties[propName] = {
|
|
1101
|
+
url: propValue === null ? null : String(propValue)
|
|
1102
|
+
};
|
|
1103
|
+
break;
|
|
1104
|
+
case "email":
|
|
1105
|
+
formattedProperties[propName] = {
|
|
1106
|
+
email: propValue === null ? null : String(propValue)
|
|
1107
|
+
};
|
|
1108
|
+
break;
|
|
1109
|
+
case "phone_number":
|
|
1110
|
+
formattedProperties[propName] = {
|
|
1111
|
+
phone_number: propValue === null ? null : String(propValue)
|
|
1112
|
+
};
|
|
1113
|
+
break;
|
|
1114
|
+
default:
|
|
1115
|
+
console.warn(`Property type "${propType}" not supported for property "${propName}", skipping`);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
if (Object.keys(formattedProperties).length === 0) {
|
|
1119
|
+
const availableProps = Object.keys(dbProperties).join(", ");
|
|
1120
|
+
const requestedProps = Object.keys(properties).join(", ");
|
|
1121
|
+
throw new Error(
|
|
1122
|
+
`No valid properties found to update. Requested properties: ${requestedProps}. Available properties in database: ${availableProps}`
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1125
|
+
await notion.pages.update({
|
|
1126
|
+
page_id: pageId,
|
|
1127
|
+
properties: formattedProperties
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
if (content !== void 0) {
|
|
1131
|
+
const existingBlocks = await notion.blocks.children.list({
|
|
1132
|
+
block_id: pageId
|
|
1133
|
+
});
|
|
1134
|
+
for (const block of existingBlocks.results) {
|
|
1135
|
+
if ("id" in block) {
|
|
1136
|
+
await notion.blocks.delete({
|
|
1137
|
+
block_id: block.id
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
const lines = content.split("\n").filter((line) => line.trim());
|
|
1142
|
+
const blocks = [];
|
|
1143
|
+
for (const line of lines) {
|
|
1144
|
+
if (line.startsWith("# ")) {
|
|
1145
|
+
blocks.push({
|
|
1146
|
+
object: "block",
|
|
1147
|
+
type: "heading_1",
|
|
1148
|
+
heading_1: {
|
|
1149
|
+
rich_text: [{ type: "text", text: { content: line.slice(2) } }]
|
|
1150
|
+
}
|
|
1151
|
+
});
|
|
1152
|
+
} else if (line.startsWith("## ")) {
|
|
1153
|
+
blocks.push({
|
|
1154
|
+
object: "block",
|
|
1155
|
+
type: "heading_2",
|
|
1156
|
+
heading_2: {
|
|
1157
|
+
rich_text: [{ type: "text", text: { content: line.slice(3) } }]
|
|
1158
|
+
}
|
|
1159
|
+
});
|
|
1160
|
+
} else if (line.startsWith("### ")) {
|
|
1161
|
+
blocks.push({
|
|
1162
|
+
object: "block",
|
|
1163
|
+
type: "heading_3",
|
|
1164
|
+
heading_3: {
|
|
1165
|
+
rich_text: [{ type: "text", text: { content: line.slice(4) } }]
|
|
1166
|
+
}
|
|
1167
|
+
});
|
|
1168
|
+
} else if (line.startsWith("\u2022 ") || line.startsWith("- ")) {
|
|
1169
|
+
blocks.push({
|
|
1170
|
+
object: "block",
|
|
1171
|
+
type: "bulleted_list_item",
|
|
1172
|
+
bulleted_list_item: {
|
|
1173
|
+
rich_text: [{ type: "text", text: { content: line.slice(2) } }]
|
|
1174
|
+
}
|
|
1175
|
+
});
|
|
1176
|
+
} else {
|
|
1177
|
+
blocks.push({
|
|
1178
|
+
object: "block",
|
|
1179
|
+
type: "paragraph",
|
|
1180
|
+
paragraph: {
|
|
1181
|
+
rich_text: [{ type: "text", text: { content: line } }]
|
|
1182
|
+
}
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
const NOTION_BLOCK_BATCH_LIMIT = 100;
|
|
1187
|
+
for (let i = 0; i < blocks.length; i += NOTION_BLOCK_BATCH_LIMIT) {
|
|
1188
|
+
await notion.blocks.children.append({
|
|
1189
|
+
block_id: pageId,
|
|
1190
|
+
children: blocks.slice(i, i + NOTION_BLOCK_BATCH_LIMIT)
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
export {
|
|
1197
|
+
getNotionAuthUrl,
|
|
1198
|
+
exchangeNotionCode,
|
|
1199
|
+
getNotionAccessToken,
|
|
1200
|
+
extractPageIdFromUrl,
|
|
1201
|
+
extractDatabaseIdFromUrl,
|
|
1202
|
+
getNotionPageTitle,
|
|
1203
|
+
readNotionPage,
|
|
1204
|
+
writeNotionPage,
|
|
1205
|
+
selectiveUpdateNotionPage,
|
|
1206
|
+
appendNotionPage,
|
|
1207
|
+
deleteNotionPage,
|
|
1208
|
+
renameNotionPage,
|
|
1209
|
+
duplicateNotionPage,
|
|
1210
|
+
createNotionSubpage,
|
|
1211
|
+
listNotionPageChildren,
|
|
1212
|
+
getNotionDatabase,
|
|
1213
|
+
createNotionDatabasePage,
|
|
1214
|
+
queryNotionDatabase,
|
|
1215
|
+
createNotionDatabase,
|
|
1216
|
+
readNotionDatabase,
|
|
1217
|
+
updateNotionDatabase,
|
|
1218
|
+
deleteNotionDatabase,
|
|
1219
|
+
duplicateNotionDatabase,
|
|
1220
|
+
updateNotionDatabasePage
|
|
1221
|
+
};
|