131 lines
3.9 KiB
JavaScript
131 lines
3.9 KiB
JavaScript
|
|
|
||
|
|
async function fetchSheetContent(token, accessToken) {
|
||
|
|
// 1. Get metainfo to find sheetIds
|
||
|
|
const metaUrl = `https://open.feishu.cn/open-apis/sheets/v3/spreadsheets/${token}/sheets/query`;
|
||
|
|
const metaRes = await fetch(metaUrl, {
|
||
|
|
headers: { 'Authorization': `Bearer ${accessToken}` }
|
||
|
|
});
|
||
|
|
const metaData = await metaRes.json();
|
||
|
|
|
||
|
|
if (metaData.code !== 0) {
|
||
|
|
// Fallback or error
|
||
|
|
return { title: "Sheet", content: `Error fetching sheet meta: ${metaData.msg}` };
|
||
|
|
}
|
||
|
|
|
||
|
|
const sheets = metaData.data.sheets;
|
||
|
|
if (!sheets || sheets.length === 0) {
|
||
|
|
return { title: "Sheet", content: "Empty spreadsheet." };
|
||
|
|
}
|
||
|
|
|
||
|
|
let fullContent = [];
|
||
|
|
|
||
|
|
// Sort sheets by index just in case
|
||
|
|
sheets.sort((a, b) => a.index - b.index);
|
||
|
|
|
||
|
|
// 2. Fetch content for up to 3 sheets to balance context vs info
|
||
|
|
// Skip hidden sheets
|
||
|
|
const visibleSheets = sheets.filter(s => !s.hidden).slice(0, 3);
|
||
|
|
|
||
|
|
for (const sheet of visibleSheets) {
|
||
|
|
const sheetId = sheet.sheet_id;
|
||
|
|
const title = sheet.title;
|
||
|
|
|
||
|
|
// Determine Range based on grid properties
|
||
|
|
// Default safe limits: Max 20 columns (T), Max 100 rows
|
||
|
|
// This prevents massive JSON payloads
|
||
|
|
let maxRows = 100;
|
||
|
|
let maxCols = 20;
|
||
|
|
|
||
|
|
if (sheet.grid_properties) {
|
||
|
|
maxRows = Math.min(sheet.grid_properties.row_count, 100);
|
||
|
|
maxCols = Math.min(sheet.grid_properties.column_count, 20);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Avoid fetching empty grids (though unlikely for valid sheets)
|
||
|
|
if (maxRows === 0 || maxCols === 0) {
|
||
|
|
fullContent.push(`## Sheet: ${title} (Empty)`);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
const lastColName = indexToColName(maxCols); // 1-based index to A, B, ... T
|
||
|
|
const range = `${sheetId}!A1:${lastColName}${maxRows}`;
|
||
|
|
|
||
|
|
const valUrl = `https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/${token}/values/${range}`;
|
||
|
|
|
||
|
|
const valRes = await fetch(valUrl, {
|
||
|
|
headers: { 'Authorization': `Bearer ${accessToken}` }
|
||
|
|
});
|
||
|
|
const valData = await valRes.json();
|
||
|
|
|
||
|
|
fullContent.push(`## Sheet: ${title}`);
|
||
|
|
|
||
|
|
if (valData.code === 0 && valData.data && valData.data.valueRange) {
|
||
|
|
const rows = valData.data.valueRange.values;
|
||
|
|
fullContent.push(markdownTable(rows));
|
||
|
|
|
||
|
|
if (sheet.grid_properties && sheet.grid_properties.row_count > maxRows) {
|
||
|
|
fullContent.push(`*(Truncated: showing first ${maxRows} of ${sheet.grid_properties.row_count} rows)*`);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
fullContent.push(`(Could not fetch values: ${valData.msg})`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
title: "Feishu Sheet",
|
||
|
|
content: fullContent.join("\n\n")
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
function indexToColName(num) {
|
||
|
|
let ret = '';
|
||
|
|
while (num > 0) {
|
||
|
|
num--;
|
||
|
|
ret = String.fromCharCode(65 + (num % 26)) + ret;
|
||
|
|
num = Math.floor(num / 26);
|
||
|
|
}
|
||
|
|
return ret || 'A';
|
||
|
|
}
|
||
|
|
|
||
|
|
function markdownTable(rows) {
|
||
|
|
if (!rows || rows.length === 0) return "";
|
||
|
|
|
||
|
|
// Normalize row length
|
||
|
|
const maxLength = Math.max(...rows.map(r => r ? r.length : 0));
|
||
|
|
|
||
|
|
if (maxLength === 0) return "(Empty Table)";
|
||
|
|
|
||
|
|
// Ensure all rows are arrays and have strings
|
||
|
|
const cleanRows = rows.map(row => {
|
||
|
|
if (!Array.isArray(row)) return Array(maxLength).fill("");
|
||
|
|
return row.map(cell => {
|
||
|
|
if (cell === null || cell === undefined) return "";
|
||
|
|
if (typeof cell === 'object') return JSON.stringify(cell); // Handle rich text segments roughly
|
||
|
|
return String(cell).replace(/\n/g, "<br>"); // Keep single line
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
const header = cleanRows[0];
|
||
|
|
const body = cleanRows.slice(1);
|
||
|
|
|
||
|
|
// Handle case where header might be shorter than max length
|
||
|
|
const paddedHeader = [...header];
|
||
|
|
while(paddedHeader.length < maxLength) paddedHeader.push("");
|
||
|
|
|
||
|
|
let md = "| " + paddedHeader.join(" | ") + " |\n";
|
||
|
|
md += "| " + paddedHeader.map(() => "---").join(" | ") + " |\n";
|
||
|
|
|
||
|
|
for (const row of body) {
|
||
|
|
// Pad row if needed
|
||
|
|
const padded = [...row];
|
||
|
|
while(padded.length < maxLength) padded.push("");
|
||
|
|
md += "| " + padded.join(" | ") + " |\n";
|
||
|
|
}
|
||
|
|
|
||
|
|
return md;
|
||
|
|
}
|
||
|
|
|
||
|
|
module.exports = {
|
||
|
|
fetchSheetContent
|
||
|
|
};
|