UNPKG

cw-ai-backlog

Version:

AI-powered tool to generate backlog items from meeting documents and output to Google Sheets

233 lines (199 loc) 6.72 kB
const fs = require('fs-extra'); const path = require('path'); const pdfParse = require('pdf-parse'); const mammoth = require('mammoth'); const axios = require('axios'); const { google } = require('googleapis'); /** * 解析檔案內容 * @param {string} input 檔案路徑或 Google Docs 連結 * @param {Object} config 配置物件 * @returns {string} 解析後的文字內容 */ async function parseFile(input, config) { // 判斷是 Google Docs 連結還是本地檔案 if (input.includes('docs.google.com')) { return await parseGoogleDocs(input, config); } else { return await parseLocalFile(input); } } /** * 解析 Google Docs * @param {string} url Google Docs URL * @param {Object} config 配置物件 * @returns {string} 文字內容 */ async function parseGoogleDocs(url, config) { try { // 提取文件 ID const docId = extractGoogleDocId(url); if (config.googleCredentials) { // 使用 Google Docs API return await parseWithGoogleAPI(docId, config.googleCredentials); } else { // 嘗試使用公開存取 return await parseGoogleDocsPublic(docId); } } catch (error) { if (error.message.includes('Google Docs API has not been used')) { throw new Error(`Google Docs API 未啟用。請按照以下步驟操作: 1. 前往 Google Cloud Console: https://console.developers.google.com/apis/api/docs.googleapis.com/overview?project=${error.message.match(/project (\d+)/)?.[1] || 'YOUR_PROJECT_ID'} 2. 點擊「啟用」按鈕來啟用 Google Docs API 3. 確保您的 service account 有權限存取該文件: - 在 Google Docs 中點擊「分享」 - 新增您的 service account email - 設定權限為「檢視者」或「編輯者」 4. 重新執行指令`); } throw new Error(`無法解析 Google Docs: ${error.message} 💡 解決方案: 1. 如果是私人文件,請設定 googleCredentials 或將文件設為公開 2. 如果是公開文件,請確認文件設為「知道連結的使用者可以檢視」 3. 執行 ai-backlog --setup 進行完整設定`); } } /** * 從 Google Docs URL 提取文件 ID * @param {string} url Google Docs URL * @returns {string} 文件 ID */ function extractGoogleDocId(url) { const match = url.match(/\/document\/d\/([a-zA-Z0-9-_]+)/); if (!match) { throw new Error('無效的 Google Docs URL'); } return match[1]; } /** * 使用 Google Docs API 解析文件 * @param {string} docId 文件 ID * @param {string} credentialsPath 憑證檔案路徑 * @returns {string} 文字內容 */ async function parseWithGoogleAPI(docId, credentialsPath) { try { const credentials = require(path.resolve(credentialsPath)); const auth = new google.auth.GoogleAuth({ credentials, scopes: [ 'https://www.googleapis.com/auth/documents.readonly', 'https://www.googleapis.com/auth/drive.readonly' ] }); const docs = google.docs({ version: 'v1', auth }); const doc = await docs.documents.get({ documentId: docId }); return extractTextFromGoogleDoc(doc.data); } catch (error) { if (error.message.includes('Google Docs API has not been used')) { throw new Error(`❌ Google Docs API 未啟用 🔧 解決步驟: 1. 前往 Google Cloud Console 啟用 Google Docs API: https://console.developers.google.com/apis/api/docs.googleapis.com/overview?project=${error.message.match(/project (\d+)/)?.[1] || 'YOUR_PROJECT_ID'} 2. 點擊「啟用」按鈕 3. 等待幾分鐘讓 API 生效,然後重試`); } if (error.message.includes('insufficient authentication scopes')) { throw new Error(`❌ Service Account 權限不足 🔧 解決步驟: 1. 確保您的 Service Account 有以下權限: - Google Docs API 存取權限 - Google Drive API 存取權限 2. 在 Google Docs 中分享文件給您的 Service Account: ${credentials.client_email || 'your-service-account@project.iam.gserviceaccount.com'} 3. 設定權限為「檢視者」或「編輯者」`); } if (error.code === 404) { throw new Error(`❌ 找不到文件或無權限存取 🔧 可能原因: 1. 文件 ID 不正確 2. Service Account 沒有存取文件的權限 3. 文件已被刪除或移動 💡 解決方法: 1. 確認 Google Docs URL 正確 2. 在文件中點擊「分享」→ 新增:${credentials.client_email || 'your-service-account@project.iam.gserviceaccount.com'} 3. 設定權限為「檢視者」或「編輯者」`); } throw error; } } /** * 嘗試解析公開的 Google Docs * @param {string} docId 文件 ID * @returns {string} 文字內容 */ async function parseGoogleDocsPublic(docId) { try { // 嘗試使用公開的純文字匯出 URL const exportUrl = `https://docs.google.com/document/d/${docId}/export?format=txt`; const response = await axios.get(exportUrl); return response.data; } catch (error) { throw new Error('無法存取 Google Docs,請確認文件為公開或提供 API 憑證'); } } /** * 從 Google Docs API 回應中提取文字 * @param {Object} docData Google Docs API 回應 * @returns {string} 文字內容 */ function extractTextFromGoogleDoc(docData) { let text = ''; if (docData.body && docData.body.content) { for (const element of docData.body.content) { if (element.paragraph) { for (const paragraphElement of element.paragraph.elements) { if (paragraphElement.textRun) { text += paragraphElement.textRun.content; } } } } } return text; } /** * 解析本地檔案 * @param {string} filePath 檔案路徑 * @returns {string} 文字內容 */ async function parseLocalFile(filePath) { if (!await fs.pathExists(filePath)) { throw new Error(`檔案不存在: ${filePath}`); } const ext = path.extname(filePath).toLowerCase(); switch (ext) { case '.pdf': return await parsePDF(filePath); case '.docx': return await parseDocx(filePath); case '.txt': case '.md': return await fs.readFile(filePath, 'utf-8'); default: throw new Error(`不支援的檔案格式: ${ext}`); } } /** * 解析 PDF 檔案 * @param {string} filePath PDF 檔案路徑 * @returns {string} 文字內容 */ async function parsePDF(filePath) { const dataBuffer = await fs.readFile(filePath); const data = await pdfParse(dataBuffer); return data.text; } /** * 解析 Word 檔案 * @param {string} filePath Word 檔案路徑 * @returns {string} 文字內容 */ async function parseDocx(filePath) { const result = await mammoth.extractRawText({ path: filePath }); return result.value; } module.exports = { parseFile };