193 lines
6.1 KiB
JavaScript
193 lines
6.1 KiB
JavaScript
|
|
|
||
|
|
async function fetchDocxContent(documentId, accessToken) {
|
||
|
|
// 1. Get document info for title
|
||
|
|
const infoUrl = `https://open.feishu.cn/open-apis/docx/v1/documents/${documentId}`;
|
||
|
|
const infoRes = await fetch(infoUrl, {
|
||
|
|
headers: { 'Authorization': `Bearer ${accessToken}` }
|
||
|
|
});
|
||
|
|
const infoData = await infoRes.json();
|
||
|
|
let title = "Untitled Docx";
|
||
|
|
if (infoData.code === 0 && infoData.data && infoData.data.document) {
|
||
|
|
title = infoData.data.document.title;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 2. Fetch all blocks
|
||
|
|
// List blocks API: GET https://open.feishu.cn/open-apis/docx/v1/documents/{document_id}/blocks
|
||
|
|
// Use pagination if necessary, fetching all for now (basic implementation)
|
||
|
|
let blocks = [];
|
||
|
|
let pageToken = '';
|
||
|
|
let hasMore = true;
|
||
|
|
|
||
|
|
while (hasMore) {
|
||
|
|
const url = `https://open.feishu.cn/open-apis/docx/v1/documents/${documentId}/blocks?page_size=500${pageToken ? `&page_token=${pageToken}` : ''}`;
|
||
|
|
const response = await fetch(url, {
|
||
|
|
headers: { 'Authorization': `Bearer ${accessToken}` }
|
||
|
|
});
|
||
|
|
const data = await response.json();
|
||
|
|
|
||
|
|
if (data.code !== 0) {
|
||
|
|
throw new Error(`Failed to fetch docx blocks: ${data.msg}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (data.data && data.data.items) {
|
||
|
|
blocks = blocks.concat(data.data.items);
|
||
|
|
}
|
||
|
|
|
||
|
|
hasMore = data.data.has_more;
|
||
|
|
pageToken = data.data.page_token;
|
||
|
|
}
|
||
|
|
|
||
|
|
const markdown = convertBlocksToMarkdown(blocks);
|
||
|
|
return { title, content: markdown };
|
||
|
|
}
|
||
|
|
|
||
|
|
function convertBlocksToMarkdown(blocks) {
|
||
|
|
if (!blocks || blocks.length === 0) return "";
|
||
|
|
|
||
|
|
let md = [];
|
||
|
|
|
||
|
|
for (const block of blocks) {
|
||
|
|
const type = block.block_type;
|
||
|
|
|
||
|
|
switch (type) {
|
||
|
|
case 1: // page
|
||
|
|
break;
|
||
|
|
case 2: // text (paragraph)
|
||
|
|
md.push(parseText(block.text));
|
||
|
|
break;
|
||
|
|
case 3: // heading1
|
||
|
|
md.push(`# ${parseText(block.heading1)}`);
|
||
|
|
break;
|
||
|
|
case 4: // heading2
|
||
|
|
md.push(`## ${parseText(block.heading2)}`);
|
||
|
|
break;
|
||
|
|
case 5: // heading3
|
||
|
|
md.push(`### ${parseText(block.heading3)}`);
|
||
|
|
break;
|
||
|
|
case 6: // heading4
|
||
|
|
md.push(`#### ${parseText(block.heading4)}`);
|
||
|
|
break;
|
||
|
|
case 7: // heading5
|
||
|
|
md.push(`##### ${parseText(block.heading5)}`);
|
||
|
|
break;
|
||
|
|
case 8: // heading6
|
||
|
|
md.push(`###### ${parseText(block.heading6)}`);
|
||
|
|
break;
|
||
|
|
case 9: // heading7
|
||
|
|
md.push(`####### ${parseText(block.heading7)}`);
|
||
|
|
break;
|
||
|
|
case 10: // heading8
|
||
|
|
md.push(`######## ${parseText(block.heading8)}`);
|
||
|
|
break;
|
||
|
|
case 11: // heading9
|
||
|
|
md.push(`######### ${parseText(block.heading9)}`);
|
||
|
|
break;
|
||
|
|
case 12: // bullet
|
||
|
|
md.push(`- ${parseText(block.bullet)}`);
|
||
|
|
break;
|
||
|
|
case 13: // ordered
|
||
|
|
md.push(`1. ${parseText(block.ordered)}`);
|
||
|
|
break;
|
||
|
|
case 14: // code
|
||
|
|
md.push('```' + (block.code?.style?.language === 1 ? '' : '') + '\n' + parseText(block.code) + '\n```');
|
||
|
|
break;
|
||
|
|
case 15: // quote
|
||
|
|
md.push(`> ${parseText(block.quote)}`);
|
||
|
|
break;
|
||
|
|
case 27: // image
|
||
|
|
md.push(``);
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
// Ignore unknown blocks for now
|
||
|
|
console.error(`Skipped block type: ${type}`, JSON.stringify(block).substring(0, 200));
|
||
|
|
md.push(`[UNSUPPORTED BLOCK TYPE: ${type}]`);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return md.join('\n\n');
|
||
|
|
}
|
||
|
|
|
||
|
|
async function appendDocxContent(documentId, content, accessToken) {
|
||
|
|
// 1. Convert markdown content to Feishu blocks
|
||
|
|
const blocks = convertMarkdownToBlocks(content);
|
||
|
|
|
||
|
|
// 2. Append to the end of the document (root block)
|
||
|
|
// POST https://open.feishu.cn/open-apis/docx/v1/documents/{document_id}/blocks/{block_id}/children
|
||
|
|
// Use documentId as block_id to append to root
|
||
|
|
const url = `https://open.feishu.cn/open-apis/docx/v1/documents/${documentId}/blocks/${documentId}/children`;
|
||
|
|
|
||
|
|
const response = await fetch(url, {
|
||
|
|
method: 'POST',
|
||
|
|
headers: {
|
||
|
|
'Authorization': `Bearer ${accessToken}`,
|
||
|
|
'Content-Type': 'application/json; charset=utf-8'
|
||
|
|
},
|
||
|
|
body: JSON.stringify({
|
||
|
|
children: blocks,
|
||
|
|
index: -1 // Append to end
|
||
|
|
})
|
||
|
|
});
|
||
|
|
|
||
|
|
const data = await response.json();
|
||
|
|
if (data.code !== 0) {
|
||
|
|
throw new Error(`Failed to append to docx: ${data.msg}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
return { success: true, appended_blocks: data.data.children };
|
||
|
|
}
|
||
|
|
|
||
|
|
function convertMarkdownToBlocks(markdown) {
|
||
|
|
// Simple parser: split by newlines, treat # as headers, others as text
|
||
|
|
// For robustness, this should be a real parser. Here we implement a basic one.
|
||
|
|
const lines = markdown.split('\n');
|
||
|
|
const blocks = [];
|
||
|
|
|
||
|
|
for (const line of lines) {
|
||
|
|
const trimmed = line.trim();
|
||
|
|
if (!trimmed) continue;
|
||
|
|
|
||
|
|
if (trimmed.startsWith('# ')) {
|
||
|
|
blocks.push({ block_type: 3, heading1: { elements: [{ text_run: { content: trimmed.substring(2) } }] } });
|
||
|
|
} else if (trimmed.startsWith('## ')) {
|
||
|
|
blocks.push({ block_type: 4, heading2: { elements: [{ text_run: { content: trimmed.substring(3) } }] } });
|
||
|
|
} else if (trimmed.startsWith('### ')) {
|
||
|
|
blocks.push({ block_type: 5, heading3: { elements: [{ text_run: { content: trimmed.substring(4) } }] } });
|
||
|
|
} else if (trimmed.startsWith('- ')) {
|
||
|
|
blocks.push({ block_type: 12, bullet: { elements: [{ text_run: { content: trimmed.substring(2) } }] } });
|
||
|
|
} else {
|
||
|
|
// Default to text (paragraph)
|
||
|
|
blocks.push({ block_type: 2, text: { elements: [{ text_run: { content: line } }] } });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return blocks;
|
||
|
|
}
|
||
|
|
|
||
|
|
function parseText(blockData) {
|
||
|
|
if (!blockData || !blockData.elements) return "";
|
||
|
|
|
||
|
|
return blockData.elements.map(el => {
|
||
|
|
if (el.text_run) {
|
||
|
|
let text = el.text_run.content;
|
||
|
|
const style = el.text_run.text_element_style;
|
||
|
|
if (style) {
|
||
|
|
if (style.bold) text = `**${text}**`;
|
||
|
|
if (style.italic) text = `*${text}*`;
|
||
|
|
if (style.strikethrough) text = `~~${text}~~`;
|
||
|
|
if (style.inline_code) text = `\`${text}\``;
|
||
|
|
if (style.link) text = `[${text}](${style.link.url})`;
|
||
|
|
}
|
||
|
|
return text;
|
||
|
|
}
|
||
|
|
if (el.mention_doc) {
|
||
|
|
return `[Doc: ${el.mention_doc.token}]`;
|
||
|
|
}
|
||
|
|
return "";
|
||
|
|
}).join("");
|
||
|
|
}
|
||
|
|
|
||
|
|
module.exports = {
|
||
|
|
fetchDocxContent,
|
||
|
|
appendDocxContent
|
||
|
|
};
|