#!/usr/bin/env node /** * 飞书文档复制工具 * * 功能: * 1. 读取源飞书文档(wiki/docx)的完整内容 * 2. 在你的飞书账号下创建新文档 * 3. 复制所有文本、格式、图片到新文档 * * 用法:node feishu-copy.mjs [目标文件夹token] * * 示例: * node feishu-copy.mjs https://xxx.feishu.cn/wiki/ABC123 * node feishu-copy.mjs https://xxx.feishu.cn/wiki/ABC123 fldcnXXXXXXX */ import { execSync } from 'child_process'; import { unlinkSync } from 'fs'; import { basename } from 'path'; const argv = process.argv.slice(2); if (!argv[0] || argv[0] === '--help' || argv[0] === '-h') { console.log(` 用法:node feishu-copy.mjs [目标文件夹token] 参数: source_url 源文档 URL(wiki 或 docx) 目标文件夹token 可选,新文档放在指定文件夹下 示例: node feishu-copy.mjs https://your-tenant.feishu.cn/wiki/WikiNodeTokenExample node feishu-copy.mjs https://your-tenant.feishu.cn/wiki/ABC123 fldcnXXXXXXX `); process.exit(0); } const sourceUrl = argv[0].trim(); const targetFolder = argv[1]?.trim(); console.log('╔════════════════════════════════════════════╗'); console.log('║ 飞书文档复制工具 ║'); console.log('╚════════════════════════════════════════════╝'); console.log(`📎 源文档: ${sourceUrl}\n`); // ── 1. 解析源文档 token ─────────────────────────────────────────────────────── console.log('🔍 正在解析源文档...'); let obj_token, title; if (sourceUrl.includes('/wiki/')) { // wiki → 先解析成 docx token const nodeInfo = larkJson(`lark-cli wiki +node-get --node-token "${sourceUrl}"`); if (!nodeInfo.ok) die('获取 wiki 节点失败', nodeInfo); obj_token = nodeInfo.data.obj_token; title = nodeInfo.data.title; } else if (sourceUrl.includes('/docx/') || sourceUrl.match(/[A-Za-z0-9]{20,}/)) { // docx URL 或直接是 token obj_token = sourceUrl.match(/[A-Za-z0-9]{20,}/)?.[0] || sourceUrl; // 先 fetch 一次拿标题 const docInfo = larkJson(`lark-cli docs +fetch --doc ${obj_token} --api-version v2`); if (!docInfo.ok) die('获取文档失败', docInfo); title = docInfo.data.document.title || '未命名文档'; } else { die('不支持的 URL 格式', { url: sourceUrl }); } console.log(`✅ 标题:${title}`); console.log(`✅ obj_token:${obj_token}\n`); // ── 2. 导出源文档为 Markdown ───────────────────────────────────────────────── console.log('📄 正在导出源文档为 Markdown...'); const exportResult = larkJson( `lark-cli drive +export --token ${obj_token} --doc-type docx --file-extension markdown --output-dir . --overwrite` ); if (!exportResult.ok) die('导出失败', exportResult); const mdPath = exportResult.data.saved_path; const mdFile = basename(mdPath); // 相对文件名,lark-cli 只接受相对路径 console.log(`✅ 已导出(${mdFile})\n`); // ── 3. 导入为新文档 ────────────────────────────────────────────────────────── console.log('📝 正在导入为新文档...'); const importCmd = targetFolder ? `lark-cli drive +import --file "@${mdFile}" --type markdown --folder-token ${targetFolder}` : `lark-cli drive +import --file "@${mdFile}" --type markdown`; const cwd = process.cwd(); const importResult = larkJson(importCmd, { cwd }); if (!importResult.ok) die('导入失败', importResult); const newDocToken = importResult.data?.token || importResult.data?.document_id; if (!newDocToken) die('导入返回异常', importResult); // 删除临时导出文件 try { unlinkSync(mdPath); } catch {} console.log(`✅ 新文档已创建并写入内容`); console.log(`✅ 新文档 token:${newDocToken}\n`); console.log('╔════════════════════════════════════════════╗'); console.log(`║ ✅ 复制完成!`); console.log(`║ 📄 新文档: https://feishu.cn/docx/${newDocToken}`); console.log(`║ 💡 图片为飞书链接,需登录后可见`); console.log('╚════════════════════════════════════════════╝'); // ── helpers ────────────────────────────────────────────────────────────────── function larkJson(cmd, opts = {}) { try { return JSON.parse(execSync(cmd, { encoding: 'utf-8', ...opts })); } catch (e) { try { return JSON.parse(e.stderr || e.stdout || '{}'); } catch { return {}; } } } function die(msg, info) { console.error(`❌ ${msg}:`, info?.error?.message || JSON.stringify(info)); process.exit(1); }