50682eeb30
- Replace internal tenant name yitanger.feishu.cn with your-tenant.feishu.cn - Replace real wiki token with placeholder WikiNodeTokenExample - Parameterize tenant via FEISHU_TENANT env var in feishu-to-md.mjs - Expand .gitignore to cover cookies, auth artifacts, IDE files
117 lines
5.2 KiB
JavaScript
Executable File
117 lines
5.2 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
||
/**
|
||
* 飞书文档复制工具
|
||
*
|
||
* 功能:
|
||
* 1. 读取源飞书文档(wiki/docx)的完整内容
|
||
* 2. 在你的飞书账号下创建新文档
|
||
* 3. 复制所有文本、格式、图片到新文档
|
||
*
|
||
* 用法:node feishu-copy.mjs <source_wiki_url> [目标文件夹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 <source_url> [目标文件夹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);
|
||
}
|