Files
YTScraper/feishu-copy.mjs
beyondx123 50682eeb30 Sanitize for open-source release
- 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
2026-07-05 14:22:31 +08:00

117 lines
5.2 KiB
JavaScript
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 源文档 URLwiki 或 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);
}