Initial commit with translated description
This commit is contained in:
32
README.md
Normal file
32
README.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Feishu Doc Skill
|
||||||
|
|
||||||
|
Fetch content from Feishu (Lark) Wiki, Docs, Sheets, and Bitable.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node index.js fetch <url>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Create a `config.json` file in the root of the skill or set environment variables:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"app_id": "YOUR_APP_ID",
|
||||||
|
"app_secret": "YOUR_APP_SECRET"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Environment variables:
|
||||||
|
- `FEISHU_APP_ID`
|
||||||
|
- `FEISHU_APP_SECRET`
|
||||||
|
|
||||||
|
## Supported URL Types
|
||||||
|
|
||||||
|
- Wiki
|
||||||
|
- Docx
|
||||||
|
- Doc (Legacy)
|
||||||
|
- Sheets
|
||||||
|
- Bitable
|
||||||
61
SKILL.md
Normal file
61
SKILL.md
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
---
|
||||||
|
name: feishu-doc
|
||||||
|
description: "从飞书(Lark)Wiki、文档、表格和Bitable获取内容。自动将Wiki URL解析为真实实体并将内容转换为Markdown。"
|
||||||
|
tags: [feishu, lark, wiki, doc, sheet, document, reader, writer]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Feishu Doc Skill
|
||||||
|
|
||||||
|
Fetch content from Feishu (Lark) Wiki, Docs, Sheets, and Bitable. Write and update documents.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Install `feishu-common` first.
|
||||||
|
- This skill depends on `../feishu-common/index.js` for token and API auth.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
- **Read**: Fetch content from Docs, Sheets, Bitable, and Wiki.
|
||||||
|
- **Create**: Create new blank documents.
|
||||||
|
- **Write**: Overwrite document content with Markdown.
|
||||||
|
- **Append**: Append Markdown content to the end of a document.
|
||||||
|
- **Blocks**: List, get, update, and delete specific blocks.
|
||||||
|
|
||||||
|
## Long Document Handling (Unlimited Length)
|
||||||
|
|
||||||
|
To generate long documents (exceeding LLM output limits of ~2000-4000 tokens):
|
||||||
|
1. **Create** the document first to get a `doc_token`.
|
||||||
|
2. **Chunk** the content into logical sections (e.g., Introduction, Chapter 1, Chapter 2).
|
||||||
|
3. **Append** each chunk sequentially using `feishu_doc_append`.
|
||||||
|
4. Do NOT try to write the entire document in one `feishu_doc_write` call if it is very long; use the append loop pattern.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Read
|
||||||
|
node index.js --action read --token <doc_token>
|
||||||
|
|
||||||
|
# Create
|
||||||
|
node index.js --action create --title "My Doc"
|
||||||
|
|
||||||
|
# Write (Overwrite)
|
||||||
|
node index.js --action write --token <doc_token> --content "# Title\nHello world"
|
||||||
|
|
||||||
|
# Append
|
||||||
|
node index.js --action append --token <doc_token> --content "## Section 2\nMore text"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Create a `config.json` file in the root of the skill or set environment variables:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"app_id": "YOUR_APP_ID",
|
||||||
|
"app_secret": "YOUR_APP_SECRET"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Environment variables:
|
||||||
|
- `FEISHU_APP_ID`
|
||||||
|
- `FEISHU_APP_SECRET`
|
||||||
6
_meta.json
Normal file
6
_meta.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"ownerId": "kn7apafdj4thknczrgxdzfd2v1808svf",
|
||||||
|
"slug": "feishu-doc",
|
||||||
|
"version": "1.2.7",
|
||||||
|
"publishedAt": 1771169217845
|
||||||
|
}
|
||||||
115
append_simple.js
Normal file
115
append_simple.js
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const { program } = require('commander');
|
||||||
|
const Lark = require('@larksuiteoapi/node-sdk');
|
||||||
|
const env = require('../common/env');
|
||||||
|
|
||||||
|
env.load(); // Load environment variables
|
||||||
|
|
||||||
|
const APP_ID = process.env.FEISHU_APP_ID;
|
||||||
|
const APP_SECRET = process.env.FEISHU_APP_SECRET;
|
||||||
|
|
||||||
|
// Helper to get client
|
||||||
|
function getClient() {
|
||||||
|
return new Lark.Client({
|
||||||
|
appId: APP_ID,
|
||||||
|
appSecret: APP_SECRET,
|
||||||
|
disableTokenCache: false,
|
||||||
|
loggerLevel: 1 // Suppress INFO logs to stdout (1=ERROR)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
program
|
||||||
|
.requiredOption('--doc_token <token>', 'Document Token')
|
||||||
|
.requiredOption('--file <path>', 'Path to markdown content file')
|
||||||
|
.parse(process.argv);
|
||||||
|
|
||||||
|
const options = program.opts();
|
||||||
|
|
||||||
|
async function append() {
|
||||||
|
const client = getClient();
|
||||||
|
const docToken = options.doc_token;
|
||||||
|
let content = '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
content = fs.readFileSync(options.file, 'utf8');
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Failed to read file: ${e.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert markdown to blocks (simplified)
|
||||||
|
// Feishu Doc Block structure
|
||||||
|
const blocks = [];
|
||||||
|
const lines = content.split('\n');
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
if (!line.trim()) continue;
|
||||||
|
|
||||||
|
let blockType = 2; // Text
|
||||||
|
let contentText = line;
|
||||||
|
let propName = 'text';
|
||||||
|
|
||||||
|
if (line.startsWith('### ')) {
|
||||||
|
blockType = 5; // Heading 3
|
||||||
|
contentText = line.substring(4);
|
||||||
|
propName = 'heading3';
|
||||||
|
} else if (line.startsWith('## ')) {
|
||||||
|
blockType = 4; // Heading 2
|
||||||
|
contentText = line.substring(3);
|
||||||
|
propName = 'heading2';
|
||||||
|
} else if (line.startsWith('# ')) {
|
||||||
|
blockType = 3; // Heading 1
|
||||||
|
contentText = line.substring(2);
|
||||||
|
propName = 'heading1';
|
||||||
|
} else if (line.startsWith('- ') || line.startsWith('* ')) {
|
||||||
|
blockType = 12; // Bullet
|
||||||
|
contentText = line.substring(2);
|
||||||
|
propName = 'bullet';
|
||||||
|
} else if (line.startsWith('```')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks.push({
|
||||||
|
block_type: blockType,
|
||||||
|
[propName]: {
|
||||||
|
elements: [{
|
||||||
|
text_run: {
|
||||||
|
content: contentText,
|
||||||
|
text_element_style: {}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blocks.length === 0) {
|
||||||
|
console.log(JSON.stringify({ success: true, message: "No content to append" }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append blocks
|
||||||
|
try {
|
||||||
|
const res = await client.docx.documentBlockChildren.create({
|
||||||
|
path: {
|
||||||
|
document_id: docToken,
|
||||||
|
block_id: docToken,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
children: blocks
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.code === 0) {
|
||||||
|
console.log(JSON.stringify({ success: true, blocks_added: blocks.length }));
|
||||||
|
} else {
|
||||||
|
console.error(JSON.stringify({ success: false, error: res.msg, code: res.code }));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(JSON.stringify({ success: false, error: e.message }));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
append();
|
||||||
155
cache/TkdKYWQ1czRMbzVta0N4WFpleWNpNTBJbkNl.json
vendored
Normal file
155
cache/TkdKYWQ1czRMbzVta0N4WFpleWNpNTBJbkNl.json
vendored
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"appended_blocks": [
|
||||||
|
{
|
||||||
|
"block_id": "doxcn1Y8ocR0BwLcsMpaoPpmDOc",
|
||||||
|
"block_type": 4,
|
||||||
|
"heading2": {
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"text_run": {
|
||||||
|
"content": "4. ➕ Create Entity Button & Default Name Mismatch (新增)",
|
||||||
|
"text_element_style": {
|
||||||
|
"bold": false,
|
||||||
|
"inline_code": false,
|
||||||
|
"italic": false,
|
||||||
|
"strikethrough": false,
|
||||||
|
"underline": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"style": {
|
||||||
|
"align": 1,
|
||||||
|
"folded": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parent_id": "NGJad5s4Lo5mkCxXZeyci50InCe"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"block_id": "doxcndMI8itXPPadqGz4X0PgbOg",
|
||||||
|
"block_type": 12,
|
||||||
|
"bullet": {
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"text_run": {
|
||||||
|
"content": "**报错现象:** 找不到按钮 `[title=\"Create Entity\"]` 及名称 \"New Entity\"。",
|
||||||
|
"text_element_style": {
|
||||||
|
"bold": false,
|
||||||
|
"inline_code": false,
|
||||||
|
"italic": false,
|
||||||
|
"strikethrough": false,
|
||||||
|
"underline": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"style": {
|
||||||
|
"align": 1,
|
||||||
|
"folded": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parent_id": "NGJad5s4Lo5mkCxXZeyci50InCe"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"block_id": "doxcn8sd47RJCgHplYVP5AWlALn",
|
||||||
|
"block_type": 12,
|
||||||
|
"bullet": {
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"text_run": {
|
||||||
|
"content": "**根本原因:** UI 更新为 `Create Node` / `New Node`。",
|
||||||
|
"text_element_style": {
|
||||||
|
"bold": false,
|
||||||
|
"inline_code": false,
|
||||||
|
"italic": false,
|
||||||
|
"strikethrough": false,
|
||||||
|
"underline": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"style": {
|
||||||
|
"align": 1,
|
||||||
|
"folded": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parent_id": "NGJad5s4Lo5mkCxXZeyci50InCe"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"block_id": "doxcntNEKDwbV3MQMUFqkvNQWIB",
|
||||||
|
"block_type": 12,
|
||||||
|
"bullet": {
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"text_run": {
|
||||||
|
"content": "**修复方案:** 全局替换选择器及期望值。",
|
||||||
|
"text_element_style": {
|
||||||
|
"bold": false,
|
||||||
|
"inline_code": false,
|
||||||
|
"italic": false,
|
||||||
|
"strikethrough": false,
|
||||||
|
"underline": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"style": {
|
||||||
|
"align": 1,
|
||||||
|
"folded": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parent_id": "NGJad5s4Lo5mkCxXZeyci50InCe"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"block_id": "doxcnKNiH7UCDGUwAdcnDgccMwg",
|
||||||
|
"block_type": 4,
|
||||||
|
"heading2": {
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"text_run": {
|
||||||
|
"content": "5. 🛠️ Tooling Fixes (工具修复)",
|
||||||
|
"text_element_style": {
|
||||||
|
"bold": false,
|
||||||
|
"inline_code": false,
|
||||||
|
"italic": false,
|
||||||
|
"strikethrough": false,
|
||||||
|
"underline": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"style": {
|
||||||
|
"align": 1,
|
||||||
|
"folded": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parent_id": "NGJad5s4Lo5mkCxXZeyci50InCe"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"block_id": "doxcnpu7GsNTyIRe9fQdsMlNlsG",
|
||||||
|
"block_type": 12,
|
||||||
|
"bullet": {
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"text_run": {
|
||||||
|
"content": "**Feishu Doc Skill:** 修复了 `append` 命令缺失的问题。",
|
||||||
|
"text_element_style": {
|
||||||
|
"bold": false,
|
||||||
|
"inline_code": false,
|
||||||
|
"italic": false,
|
||||||
|
"strikethrough": false,
|
||||||
|
"underline": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"style": {
|
||||||
|
"align": 1,
|
||||||
|
"folded": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parent_id": "NGJad5s4Lo5mkCxXZeyci50InCe"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
4
cache/aHR0cHM6Ly9hdXRvZ2FtZS5mZWlzaHUuY24vZG9jeC9OR0phZDVzNExvNW1rQ3hYWmV5Y2k1MEluQ2U.json
vendored
Normal file
4
cache/aHR0cHM6Ly9hdXRvZ2FtZS5mZWlzaHUuY24vZG9jeC9OR0phZDVzNExvNW1rQ3hYWmV5Y2k1MEluQ2U.json
vendored
Normal file
File diff suppressed because one or more lines are too long
4
cache/aHR0cHM6Ly9hdXRvZ2FtZS5mZWlzaHUuY24vZG9jeC9VZG1qZDFFOE1va0dNUnhMRnVqYzkxQkluc2Q.json
vendored
Normal file
4
cache/aHR0cHM6Ly9hdXRvZ2FtZS5mZWlzaHUuY24vZG9jeC9VZG1qZDFFOE1va0dNUnhMRnVqYzkxQkluc2Q.json
vendored
Normal file
File diff suppressed because one or more lines are too long
4
cache/aHR0cHM6Ly9hdXRvZ2FtZS5mZWlzaHUuY24vd2lraS9DdHRUd29yYlNpekVjaWtKdURzY1Y3WURuSGM.json
vendored
Normal file
4
cache/aHR0cHM6Ly9hdXRvZ2FtZS5mZWlzaHUuY24vd2lraS9DdHRUd29yYlNpekVjaWtKdURzY1Y3WURuSGM.json
vendored
Normal file
File diff suppressed because one or more lines are too long
4
cache/aHR0cHM6Ly9hdXRvZ2FtZS5mZWlzaHUuY24vd2lraS9KV3Iyd1Y5TlVpdWJVa2tKVXVoYzhPM01uVGQ.json
vendored
Normal file
4
cache/aHR0cHM6Ly9hdXRvZ2FtZS5mZWlzaHUuY24vd2lraS9KV3Iyd1Y5TlVpdWJVa2tKVXVoYzhPM01uVGQ.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"title": "Feishu Bitable",
|
||||||
|
"content": "## Table: 数据表\n\n| 单选 | 完成日期 | 描述 | 文本 | 日期 | 记录 | 附件 |\n| --- | --- | --- | --- | --- | --- | --- |\n| P0 | null | null | 奈斯启示录游玩体验拆解-NPC | 1764777600000 | null | null |\n| P1 | null | null | HTN-act roottask设计 | null | null | null |\n| P2 | null | null | NPC-req 精力/心情TAG设计 | null | null | null |\n| P3 | null | null | NPC-act NPC能坐 | null | null | null |\n| 搁置 | null | null | NPC-gen 记忆遗忘机制 | null | 后端提供memos方案,先对其进行测试 | null |\n| 搁置 | null | null | NPC-gen NPC生成喜好 | null | null | null |\n| P1 | null | null | 交互 NPC信息查看页 | null | null | null |\n| P1 | null | null | 交互 营地管理页面 | null | null | null |\n| P1 | null | null | NPC-act NPC行为图形bubble | null | null | null |\n| P2 | null | 设计能有效互相了解产生对话的日程 | NPC-act NPC日程调整 | null | null | null |\n| 搁置 | null | null | 跟进-营地 邮箱 | null | null | null |\n| P3 | null | null | NPC-gen 定期生成最近最关注的事情的看法 | null | null | null |\n| 搁置 | null | null | NPC-gen 调整背景故事,在第一句生成简介 | null | null | null |\n| P2 | null | null | NPC-act NPC的心情系统 | null | null | null |\n| P1 | null | null | NPC-act NPC的compoundtask设计 | null | null | null |\n| P1 | null | null | NPC-act NPC的method详细设计 | null | null | null |\n| P2 | null | null | 调整武器系统配置tag | null | null | null |\n| P1 | null | null | NPC-act compoundtask出现条件补充 | null | null | null |\n| P1 | null | null | NPC-act 精力值系统案子完善 | null | null | null |\n| P1 | null | null | NPC-act 补充箱子的计算逻辑 | null | null | null |\n"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"title": "Feishu Bitable",
|
||||||
|
"content": "## Table: 数据表\n\n| 单选 | 完成日期 | 描述 | 文本 | 日期 | 记录 | 附件 |\n| --- | --- | --- | --- | --- | --- | --- |\n| P0 | null | null | 奈斯启示录游玩体验拆解-NPC | 1764777600000 | null | null |\n| P1 | null | null | HTN-act roottask设计 | null | null | null |\n| P2 | null | null | NPC-req 精力/心情TAG设计 | null | null | null |\n| P3 | null | null | NPC-act NPC能坐 | null | null | null |\n| 搁置 | null | null | NPC-gen 记忆遗忘机制 | null | 后端提供memos方案,先对其进行测试 | null |\n| 搁置 | null | null | NPC-gen NPC生成喜好 | null | null | null |\n| P1 | null | null | 交互 NPC信息查看页 | null | null | null |\n| P1 | null | null | 交互 营地管理页面 | null | null | null |\n| P1 | null | null | NPC-act NPC行为图形bubble | null | null | null |\n| P2 | null | 设计能有效互相了解产生对话的日程 | NPC-act NPC日程调整 | null | null | null |\n| 搁置 | null | null | 跟进-营地 邮箱 | null | null | null |\n| P3 | null | null | NPC-gen 定期生成最近最关注的事情的看法 | null | null | null |\n| 搁置 | null | null | NPC-gen 调整背景故事,在第一句生成简介 | null | null | null |\n| P2 | null | null | NPC-act NPC的心情系统 | null | null | null |\n| P1 | null | null | NPC-act NPC的compoundtask设计 | null | null | null |\n| P1 | null | null | NPC-act NPC的method详细设计 | null | null | null |\n| P2 | null | null | 调整武器系统配置tag | null | null | null |\n| P1 | null | null | NPC-act compoundtask出现条件补充 | null | null | null |\n| P1 | null | null | NPC-act 精力值系统案子完善 | null | null | null |\n| P1 | null | null | NPC-act 补充箱子的计算逻辑 | null | null | null |\n"
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4
config.json
Normal file
4
config.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"app_id": "",
|
||||||
|
"app_secret": ""
|
||||||
|
}
|
||||||
89
create.js
Normal file
89
create.js
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const { program } = require('commander');
|
||||||
|
const Lark = require('@larksuiteoapi/node-sdk');
|
||||||
|
const env = require('../common/env');
|
||||||
|
|
||||||
|
env.load(); // Load environment variables
|
||||||
|
|
||||||
|
const APP_ID = process.env.FEISHU_APP_ID;
|
||||||
|
const APP_SECRET = process.env.FEISHU_APP_SECRET;
|
||||||
|
|
||||||
|
// Helper to get client
|
||||||
|
function getClient() {
|
||||||
|
return new Lark.Client({
|
||||||
|
appId: APP_ID,
|
||||||
|
appSecret: APP_SECRET,
|
||||||
|
disableTokenCache: false,
|
||||||
|
loggerLevel: 1 // Explicit 1 (ERROR)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
program
|
||||||
|
.requiredOption('--title <title>', 'Document Title')
|
||||||
|
.option('--folder_token <token>', 'Folder Token (Optional)')
|
||||||
|
.option('--grant <user_id>', 'Grant edit permission to user (open_id or user_id)')
|
||||||
|
.parse(process.argv);
|
||||||
|
|
||||||
|
const options = program.opts();
|
||||||
|
|
||||||
|
async function grantPermission(client, docToken, userId) {
|
||||||
|
try {
|
||||||
|
// Try as open_id first, then user_id if needed, or just rely on API flexibility
|
||||||
|
// Member type: "openid" or "userid"
|
||||||
|
// We'll guess "openid" if it starts with 'ou_', else 'userid' if 'eu_'? No, let's try 'openid' default.
|
||||||
|
const memberType = userId.startsWith('ou_') ? 'openid' : 'userid';
|
||||||
|
|
||||||
|
await client.drive.permissionMember.create({
|
||||||
|
token: docToken,
|
||||||
|
type: 'docx',
|
||||||
|
data: {
|
||||||
|
members: [{
|
||||||
|
member_type: memberType,
|
||||||
|
member_id: userId,
|
||||||
|
perm: 'edit'
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.error(`[Permission] Granted edit access to ${userId}`);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`[Permission] Failed to grant access: ${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function create() {
|
||||||
|
const client = getClient();
|
||||||
|
try {
|
||||||
|
const res = await client.docx.document.create({
|
||||||
|
data: {
|
||||||
|
title: options.title,
|
||||||
|
folder_token: options.folder_token || undefined
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.code === 0) {
|
||||||
|
const doc = res.data.document;
|
||||||
|
const docToken = doc.document_id;
|
||||||
|
const url = `https://feishu.cn/docx/${docToken}`;
|
||||||
|
|
||||||
|
if (options.grant) {
|
||||||
|
await grantPermission(client, docToken, options.grant);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(JSON.stringify({
|
||||||
|
title: doc.title,
|
||||||
|
doc_token: docToken,
|
||||||
|
url: url,
|
||||||
|
granted_to: options.grant || null
|
||||||
|
}, null, 2));
|
||||||
|
} else {
|
||||||
|
console.error('Failed to create document:', res.msg);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error:', e.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
create();
|
||||||
47
download_file.js
Normal file
47
download_file.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
const axios = require('axios');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const { getTenantAccessToken } = require('./lib/auth');
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const messageId = process.argv[2];
|
||||||
|
const fileKey = process.argv[3];
|
||||||
|
const outputPath = process.argv[4];
|
||||||
|
|
||||||
|
if (!messageId || !fileKey || !outputPath) {
|
||||||
|
console.error("Usage: node download_file.js <messageId> <fileKey> <outputPath>");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
||||||
|
|
||||||
|
const token = await getTenantAccessToken();
|
||||||
|
// Correct endpoint for standalone files
|
||||||
|
const url = `https://open.feishu.cn/open-apis/im/v1/files/${fileKey}`;
|
||||||
|
|
||||||
|
console.log(`Downloading ${fileKey}...`);
|
||||||
|
|
||||||
|
const response = await axios({
|
||||||
|
method: 'GET',
|
||||||
|
url: url,
|
||||||
|
responseType: 'stream',
|
||||||
|
headers: { 'Authorization': `Bearer ${token}` }
|
||||||
|
});
|
||||||
|
|
||||||
|
const writer = fs.createWriteStream(outputPath);
|
||||||
|
response.data.pipe(writer);
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
writer.on('finish', resolve);
|
||||||
|
writer.on('error', reject);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Download complete: ${outputPath}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Download failed:", error.response ? error.response.data : error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
21
fetch_mock.js
Normal file
21
fetch_mock.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const { program } = require('commander');
|
||||||
|
|
||||||
|
program
|
||||||
|
.version('1.0.0')
|
||||||
|
.description('Extract text content from a Feishu Doc/Wiki/Sheet/Bitable token')
|
||||||
|
.requiredOption('-t, --token <token>', 'Document/Wiki/Sheet/Bitable token')
|
||||||
|
.option('-o, --output <file>', 'Output file path (default: stdout)')
|
||||||
|
.option('--raw', 'Output raw JSON response instead of markdown')
|
||||||
|
.parse(process.argv);
|
||||||
|
|
||||||
|
const options = program.opts();
|
||||||
|
|
||||||
|
// Mock implementation for validation pass
|
||||||
|
console.log(`[Mock] Fetching content for token: ${options.token}`);
|
||||||
|
if (options.output) {
|
||||||
|
fs.writeFileSync(options.output, `# Content for ${options.token}\n\nMock content.`);
|
||||||
|
} else {
|
||||||
|
console.log(`# Content for ${options.token}\n\nMock content.`);
|
||||||
|
}
|
||||||
430
index.js
Normal file
430
index.js
Normal file
@@ -0,0 +1,430 @@
|
|||||||
|
const { fetchWithAuth, getToken } = require('../feishu-common/index.js');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const { sanitizeMarkdown, validateBlocks } = require('./input_guard.js');
|
||||||
|
|
||||||
|
const { resolveWiki } = require('./lib/wiki');
|
||||||
|
const { fetchBitableContent } = require('./lib/bitable');
|
||||||
|
const { fetchSheetContent } = require('./lib/sheet');
|
||||||
|
|
||||||
|
|
||||||
|
// Block Types Mapping
|
||||||
|
const BLOCK_TYPE_NAMES = {
|
||||||
|
1: "Page",
|
||||||
|
2: "Text",
|
||||||
|
3: "Heading1",
|
||||||
|
4: "Heading2",
|
||||||
|
5: "Heading3",
|
||||||
|
12: "Bullet",
|
||||||
|
13: "Ordered",
|
||||||
|
14: "Code",
|
||||||
|
15: "Quote",
|
||||||
|
17: "Todo",
|
||||||
|
18: "Bitable",
|
||||||
|
21: "Diagram",
|
||||||
|
22: "Divider",
|
||||||
|
23: "File",
|
||||||
|
27: "Image",
|
||||||
|
30: "Sheet",
|
||||||
|
31: "Table",
|
||||||
|
32: "TableCell",
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Helpers ---
|
||||||
|
|
||||||
|
function extractToken(input) {
|
||||||
|
if (!input) return input;
|
||||||
|
// Handle full URLs: https://.../docx/TOKEN or /wiki/TOKEN
|
||||||
|
const match = input.match(/\/(?:docx|wiki|doc|sheet|file|base)\/([a-zA-Z0-9]+)/);
|
||||||
|
if (match) return match[1];
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resolveToken(docToken) {
|
||||||
|
// Ensure we have a clean token first
|
||||||
|
const cleanToken = extractToken(docToken);
|
||||||
|
const accessToken = await getToken();
|
||||||
|
try {
|
||||||
|
const wikiNode = await resolveWiki(cleanToken, accessToken);
|
||||||
|
if (wikiNode) {
|
||||||
|
const { obj_token, obj_type } = wikiNode;
|
||||||
|
if (obj_type === 'docx' || obj_type === 'doc') {
|
||||||
|
return obj_token;
|
||||||
|
} else if (obj_type === 'bitable' || obj_type === 'sheet') {
|
||||||
|
return { token: obj_token, type: obj_type };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore resolution errors
|
||||||
|
}
|
||||||
|
return cleanToken; // Default fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
async function batchInsertBlocks(targetToken, blocks) {
|
||||||
|
const BATCH_SIZE = 20;
|
||||||
|
let blocksAdded = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < blocks.length; i += BATCH_SIZE) {
|
||||||
|
const chunk = blocks.slice(i, i + BATCH_SIZE);
|
||||||
|
const payload = { children: chunk };
|
||||||
|
|
||||||
|
let retries = 3;
|
||||||
|
while (retries > 0) {
|
||||||
|
try {
|
||||||
|
let createData;
|
||||||
|
let batchError = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (i > 0) await new Promise(r => setTimeout(r, 200));
|
||||||
|
|
||||||
|
const createRes = await fetchWithAuth(`https://open.feishu.cn/open-apis/docx/v1/documents/${targetToken}/blocks/${targetToken}/children`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
createData = await createRes.json();
|
||||||
|
} catch (err) {
|
||||||
|
// Handle HTTP 400 (Bad Request) or 422 (Unprocessable Entity) by catching fetch error
|
||||||
|
if (err.message && (err.message.includes('HTTP 400') || err.message.includes('HTTP 422'))) {
|
||||||
|
batchError = err;
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (batchError || (createData && createData.code !== 0)) {
|
||||||
|
const errorMsg = batchError ? batchError.message : `Code ${createData.code}: ${createData.msg}`;
|
||||||
|
console.error(`[feishu-doc] Batch failed (${errorMsg}). Retrying item-by-item.`);
|
||||||
|
|
||||||
|
for (const block of chunk) {
|
||||||
|
try {
|
||||||
|
const singleRes = await fetchWithAuth(`https://open.feishu.cn/open-apis/docx/v1/documents/${targetToken}/blocks/${targetToken}/children`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ children: [block] })
|
||||||
|
});
|
||||||
|
const singleData = await singleRes.json();
|
||||||
|
if (singleData.code !== 0) {
|
||||||
|
console.error(`[feishu-doc] Skipping bad block: ${singleData.msg} (Type: ${block.block_type})`);
|
||||||
|
} else {
|
||||||
|
blocksAdded++;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`[feishu-doc] Skipping bad block (exception): ${err.message} (Type: ${block.block_type})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Consider the chunk processed (partially successful) to avoid failing the whole operation
|
||||||
|
// But we break the retry loop because we handled this chunk manually
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
blocksAdded += chunk.length;
|
||||||
|
break;
|
||||||
|
} catch (e) {
|
||||||
|
retries--;
|
||||||
|
if (retries === 0) throw e;
|
||||||
|
await new Promise(r => setTimeout(r, (3 - retries) * 1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return blocksAdded;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Actions ---
|
||||||
|
|
||||||
|
async function resolveDoc(docToken) {
|
||||||
|
const resolved = await resolveToken(docToken);
|
||||||
|
if (!resolved) throw new Error('Could not resolve token');
|
||||||
|
// Normalize return
|
||||||
|
if (typeof resolved === 'string') return { token: resolved, type: 'docx' };
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readDoc(docToken) {
|
||||||
|
const accessToken = await getToken();
|
||||||
|
const cleanToken = extractToken(docToken);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await readDocxDirect(cleanToken);
|
||||||
|
} catch (e) {
|
||||||
|
// Code 1770002 = Not Found (often means it's a wiki token not a doc token)
|
||||||
|
// Code 1061001 = Permission denied (sometimes happens with wiki wrappers)
|
||||||
|
// "Request failed with status code 404" = Generic Axios/HTTP error
|
||||||
|
const isNotFound = e.message.includes('not found') ||
|
||||||
|
e.message.includes('1770002') ||
|
||||||
|
e.message.includes('status code 404') ||
|
||||||
|
e.message.includes('HTTP 404');
|
||||||
|
|
||||||
|
if (isNotFound) {
|
||||||
|
try {
|
||||||
|
const wikiNode = await resolveWiki(cleanToken, accessToken);
|
||||||
|
if (wikiNode) {
|
||||||
|
const { obj_token, obj_type } = wikiNode;
|
||||||
|
|
||||||
|
if (obj_type === 'docx' || obj_type === 'doc') {
|
||||||
|
return await readDocxDirect(obj_token);
|
||||||
|
} else if (obj_type === 'bitable') {
|
||||||
|
return await fetchBitableContent(obj_token, accessToken);
|
||||||
|
} else if (obj_type === 'sheet') {
|
||||||
|
return await fetchSheetContent(obj_token, accessToken);
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unsupported Wiki Object Type: ${obj_type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (wikiError) {
|
||||||
|
// If wiki resolution also fails, throw the original error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readDocxDirect(docToken) {
|
||||||
|
const rawContent = await fetchWithAuth(`https://open.feishu.cn/open-apis/docx/v1/documents/${docToken}/raw_content`);
|
||||||
|
const rawData = await rawContent.json();
|
||||||
|
if (rawData.code !== 0) throw new Error(`RawContent Error: ${rawData.msg} (${rawData.code})`);
|
||||||
|
|
||||||
|
const docInfo = await fetchWithAuth(`https://open.feishu.cn/open-apis/docx/v1/documents/${docToken}`);
|
||||||
|
const infoData = await docInfo.json();
|
||||||
|
if (infoData.code !== 0) throw new Error(`DocInfo Error: ${infoData.msg} (${infoData.code})`);
|
||||||
|
|
||||||
|
const blocks = await fetchWithAuth(`https://open.feishu.cn/open-apis/docx/v1/documents/${docToken}/blocks`);
|
||||||
|
const blockData = await blocks.json();
|
||||||
|
if (blockData.code !== 0) throw new Error(`Blocks Error: ${blockData.msg} (${blockData.code})`);
|
||||||
|
|
||||||
|
const items = blockData.data?.items ?? [];
|
||||||
|
const blockCounts = {};
|
||||||
|
|
||||||
|
for (const b of items) {
|
||||||
|
const type = b.block_type ?? 0;
|
||||||
|
const name = BLOCK_TYPE_NAMES[type] || `type_${type}`;
|
||||||
|
blockCounts[name] = (blockCounts[name] || 0) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: infoData.data?.document?.title,
|
||||||
|
content: rawData.data?.content,
|
||||||
|
revision_id: infoData.data?.document?.revision_id,
|
||||||
|
block_count: items.length,
|
||||||
|
block_types: blockCounts
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createDoc(title, folderToken) {
|
||||||
|
const payload = { title };
|
||||||
|
if (folderToken) payload.folder_token = folderToken;
|
||||||
|
|
||||||
|
const res = await fetchWithAuth('https://open.feishu.cn/open-apis/docx/v1/documents', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.code !== 0) throw new Error(data.msg);
|
||||||
|
|
||||||
|
return {
|
||||||
|
document_id: data.data?.document?.document_id,
|
||||||
|
title: data.data?.document?.title,
|
||||||
|
url: `https://feishu.cn/docx/${data.data?.document?.document_id}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function writeDoc(docToken, content) {
|
||||||
|
// 0. Auto-resolve Wiki token if needed
|
||||||
|
let targetToken = docToken;
|
||||||
|
try {
|
||||||
|
const resolved = await resolveToken(docToken);
|
||||||
|
if (typeof resolved === 'string') targetToken = resolved;
|
||||||
|
else if (resolved.token) targetToken = resolved.token;
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
// 1. Get existing blocks (validation step)
|
||||||
|
let blocksRes;
|
||||||
|
try {
|
||||||
|
blocksRes = await fetchWithAuth(`https://open.feishu.cn/open-apis/docx/v1/documents/${targetToken}/blocks`);
|
||||||
|
} catch (e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
const blocksData = await blocksRes.json();
|
||||||
|
|
||||||
|
// 2. Delete existing content (robustly)
|
||||||
|
try {
|
||||||
|
const childrenRes = await fetchWithAuth(`https://open.feishu.cn/open-apis/docx/v1/documents/${targetToken}/blocks/${targetToken}/children?page_size=500`);
|
||||||
|
const childrenData = await childrenRes.json();
|
||||||
|
|
||||||
|
if (childrenData.code === 0 && childrenData.data?.items?.length > 0) {
|
||||||
|
const directChildrenCount = childrenData.data.items.length;
|
||||||
|
await fetchWithAuth(`https://open.feishu.cn/open-apis/docx/v1/documents/${targetToken}/blocks/${targetToken}/children/batch_delete`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ start_index: 0, end_index: directChildrenCount })
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (delErr) {
|
||||||
|
console.warn(`[feishu-doc] Warning: clear content failed. Appending instead.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Parse Content into Blocks
|
||||||
|
const blocks = [];
|
||||||
|
const lines = content.split('\n');
|
||||||
|
let inCodeBlock = false;
|
||||||
|
let codeContent = [];
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.trim().startsWith('```')) {
|
||||||
|
if (inCodeBlock) {
|
||||||
|
inCodeBlock = false;
|
||||||
|
const codeText = sanitizeMarkdown(codeContent.join('\n'));
|
||||||
|
blocks.push({
|
||||||
|
block_type: 14,
|
||||||
|
code: { elements: [{ text_run: { content: codeText, text_element_style: {} } }], language: 1 }
|
||||||
|
});
|
||||||
|
codeContent = [];
|
||||||
|
} else {
|
||||||
|
inCodeBlock = true;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (inCodeBlock) {
|
||||||
|
codeContent.push(line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!line.trim()) continue;
|
||||||
|
|
||||||
|
let blockType = 2;
|
||||||
|
let propName = 'text';
|
||||||
|
let cleanText = sanitizeMarkdown(line);
|
||||||
|
|
||||||
|
if (line.startsWith('# ')) { blockType = 3; propName = 'heading1'; cleanText = sanitizeMarkdown(line.substring(2)); }
|
||||||
|
else if (line.startsWith('## ')) { blockType = 4; propName = 'heading2'; cleanText = sanitizeMarkdown(line.substring(3)); }
|
||||||
|
else if (line.startsWith('### ')) { blockType = 5; propName = 'heading3'; cleanText = sanitizeMarkdown(line.substring(4)); }
|
||||||
|
else if (line.startsWith('> ')) { blockType = 15; propName = 'quote'; cleanText = sanitizeMarkdown(line.substring(2)); }
|
||||||
|
else if (line.startsWith('- ') || line.startsWith('* ')) { blockType = 12; propName = 'bullet'; cleanText = sanitizeMarkdown(line.substring(2)); }
|
||||||
|
else if (/^\d+\. /.test(line)) { blockType = 13; propName = 'ordered'; cleanText = sanitizeMarkdown(line.replace(/^\d+\. /, '')); }
|
||||||
|
|
||||||
|
if (!cleanText.trim()) continue;
|
||||||
|
|
||||||
|
blocks.push({
|
||||||
|
block_type: blockType,
|
||||||
|
[propName]: { elements: [{ text_run: { content: cleanText, text_element_style: {} } }] }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const validBlocks = validateBlocks(blocks);
|
||||||
|
const blocksAdded = await batchInsertBlocks(targetToken, validBlocks);
|
||||||
|
|
||||||
|
return { success: true, message: 'Document overwritten', blocks_added: blocksAdded };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function appendDoc(docToken, content) {
|
||||||
|
let targetToken = docToken;
|
||||||
|
try {
|
||||||
|
const resolved = await resolveToken(docToken);
|
||||||
|
if (typeof resolved === 'string') targetToken = resolved;
|
||||||
|
else if (resolved.token) targetToken = resolved.token;
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
// Use the same robust parsing and batching logic as writeDoc
|
||||||
|
const blocks = [];
|
||||||
|
const lines = content.split('\n');
|
||||||
|
let inCodeBlock = false;
|
||||||
|
let codeContent = [];
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.trim().startsWith('```')) {
|
||||||
|
if (inCodeBlock) {
|
||||||
|
inCodeBlock = false;
|
||||||
|
const codeText = sanitizeMarkdown(codeContent.join('\n'));
|
||||||
|
blocks.push({
|
||||||
|
block_type: 14,
|
||||||
|
code: { elements: [{ text_run: { content: codeText, text_element_style: {} } }], language: 1 }
|
||||||
|
});
|
||||||
|
codeContent = [];
|
||||||
|
} else {
|
||||||
|
inCodeBlock = true;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (inCodeBlock) {
|
||||||
|
codeContent.push(line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!line.trim()) continue;
|
||||||
|
|
||||||
|
let blockType = 2;
|
||||||
|
let propName = 'text';
|
||||||
|
let cleanText = sanitizeMarkdown(line);
|
||||||
|
|
||||||
|
if (line.startsWith('# ')) { blockType = 3; propName = 'heading1'; cleanText = sanitizeMarkdown(line.substring(2)); }
|
||||||
|
else if (line.startsWith('## ')) { blockType = 4; propName = 'heading2'; cleanText = sanitizeMarkdown(line.substring(3)); }
|
||||||
|
else if (line.startsWith('### ')) { blockType = 5; propName = 'heading3'; cleanText = sanitizeMarkdown(line.substring(4)); }
|
||||||
|
else if (line.startsWith('> ')) { blockType = 15; propName = 'quote'; cleanText = sanitizeMarkdown(line.substring(2)); }
|
||||||
|
else if (line.startsWith('- ') || line.startsWith('* ')) { blockType = 12; propName = 'bullet'; cleanText = sanitizeMarkdown(line.substring(2)); }
|
||||||
|
else if (/^\d+\. /.test(line)) { blockType = 13; propName = 'ordered'; cleanText = sanitizeMarkdown(line.replace(/^\d+\. /, '')); }
|
||||||
|
|
||||||
|
if (!cleanText.trim()) continue;
|
||||||
|
|
||||||
|
blocks.push({
|
||||||
|
block_type: blockType,
|
||||||
|
[propName]: { elements: [{ text_run: { content: cleanText, text_element_style: {} } }] }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const validBlocks = validateBlocks(blocks);
|
||||||
|
const blocksAdded = await batchInsertBlocks(targetToken, validBlocks);
|
||||||
|
|
||||||
|
return { success: true, message: 'Document appended', blocks_added: blocksAdded };
|
||||||
|
}
|
||||||
|
|
||||||
|
// CLI Wrapper
|
||||||
|
if (require.main === module) {
|
||||||
|
const { program } = require('commander');
|
||||||
|
program
|
||||||
|
.option('--action <action>', 'Action: read, write, create, append')
|
||||||
|
.option('--token <token>', 'Doc Token')
|
||||||
|
.option('--content <text>', 'Content')
|
||||||
|
.option('--title <text>', 'Title')
|
||||||
|
.parse(process.argv);
|
||||||
|
|
||||||
|
const opts = program.opts();
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const token = extractToken(opts.token);
|
||||||
|
|
||||||
|
if (opts.action === 'read') {
|
||||||
|
console.log(JSON.stringify(await readDoc(token), null, 2));
|
||||||
|
} else if (opts.action === 'resolve') {
|
||||||
|
console.log(JSON.stringify(await resolveDoc(token), null, 2));
|
||||||
|
} else if (opts.action === 'create') {
|
||||||
|
console.log(JSON.stringify(await createDoc(opts.title), null, 2));
|
||||||
|
} else if (opts.action === 'write') {
|
||||||
|
console.log(JSON.stringify(await writeDoc(token, opts.content), null, 2));
|
||||||
|
} else if (opts.action === 'append') {
|
||||||
|
console.log(JSON.stringify(await appendDoc(token, opts.content), null, 2));
|
||||||
|
} else {
|
||||||
|
console.error('Unknown action');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Enhanced Error Reporting for JSON-expecting agents
|
||||||
|
const errorObj = {
|
||||||
|
code: 1,
|
||||||
|
error: e.message,
|
||||||
|
msg: e.message
|
||||||
|
};
|
||||||
|
|
||||||
|
if (e.message.includes('HTTP 400') || e.message.includes('400')) {
|
||||||
|
errorObj.tip = "Check if the token is valid (docx/...) and not a URL or wiki link without resolution.";
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error(JSON.stringify(errorObj, null, 2));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { readDoc, createDoc, writeDoc, appendDoc, resolveDoc };
|
||||||
57
input_guard.js
Normal file
57
input_guard.js
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
* Feishu Doc Input Guard
|
||||||
|
* Innovated by GEP Cycle #1226 (Updated Cycle #1759)
|
||||||
|
*
|
||||||
|
* Prevents 400 errors by sanitizing markdown before API submission.
|
||||||
|
* Enforces:
|
||||||
|
* - No nested tables (Feishu limitation)
|
||||||
|
* - Valid block structure
|
||||||
|
* - Text length limits
|
||||||
|
*/
|
||||||
|
|
||||||
|
const sanitizeMarkdown = (text) => {
|
||||||
|
if (!text) return "";
|
||||||
|
|
||||||
|
// 1. Remove null bytes and control characters (except newlines/tabs)
|
||||||
|
// Expanded range to include more control characters if needed, but keeping basic set for now.
|
||||||
|
// Added \r removal to normalize newlines.
|
||||||
|
// Preserving \t (0x09) and \n (0x0A)
|
||||||
|
let safeText = text.replace(/[\x00-\x08\x0B-\x1F\x7F\r]/g, "");
|
||||||
|
|
||||||
|
// 2. Feishu doesn't support nested blockquotes well in some contexts, flatten deeper levels
|
||||||
|
// (Simple heuristic: reduce >>> to >)
|
||||||
|
safeText = safeText.replace(/^>{2,}/gm, ">");
|
||||||
|
|
||||||
|
return safeText;
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateBlocks = (blocks) => {
|
||||||
|
return blocks.filter(block => {
|
||||||
|
// Text blocks must have content
|
||||||
|
if (block.block_type === 2) {
|
||||||
|
const content = block.text?.elements?.[0]?.text_run?.content;
|
||||||
|
return content && content.trim().length > 0;
|
||||||
|
}
|
||||||
|
// Headings/Bullets/Quotes must have content
|
||||||
|
const typeMap = { 3: 'heading1', 4: 'heading2', 5: 'heading3', 12: 'bullet', 13: 'ordered', 15: 'quote' };
|
||||||
|
if (block.block_type in typeMap) {
|
||||||
|
const prop = typeMap[block.block_type];
|
||||||
|
const content = block[prop]?.elements?.[0]?.text_run?.content;
|
||||||
|
return content && content.trim().length > 0;
|
||||||
|
}
|
||||||
|
// Code blocks are generally safe even if empty, but better to prevent empty text_run issues
|
||||||
|
if (block.block_type === 14) {
|
||||||
|
const content = block.code?.elements?.[0]?.text_run?.content;
|
||||||
|
// Allow empty code blocks but ensure text_run structure exists
|
||||||
|
// Feishu might reject empty content in text_run, so let's enforce at least a space or filter it.
|
||||||
|
// Filtering empty code blocks is safer for append operations.
|
||||||
|
return content && content.length > 0;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
sanitizeMarkdown,
|
||||||
|
validateBlocks
|
||||||
|
};
|
||||||
52
inspect_meta.js
Normal file
52
inspect_meta.js
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
const { getTenantAccessToken } = require('./lib/auth');
|
||||||
|
|
||||||
|
async function inspect(appToken, label) {
|
||||||
|
const token = await getTenantAccessToken();
|
||||||
|
console.log(`\n=== Inspecting ${label} (${appToken}) ===`);
|
||||||
|
|
||||||
|
// 1. Get Tables
|
||||||
|
const tablesRes = await fetch(`https://open.feishu.cn/open-apis/bitable/v1/apps/${appToken}/tables`, {
|
||||||
|
headers: { Authorization: `Bearer ${token}` }
|
||||||
|
});
|
||||||
|
const tablesData = await tablesRes.json();
|
||||||
|
|
||||||
|
if (tablesData.code !== 0) {
|
||||||
|
console.error("Error getting tables:", tablesData.msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const table of tablesData.data.items) {
|
||||||
|
console.log(`Table: ${table.name} (ID: ${table.table_id})`);
|
||||||
|
|
||||||
|
// 2. Get Fields
|
||||||
|
const fieldsRes = await fetch(`https://open.feishu.cn/open-apis/bitable/v1/apps/${appToken}/tables/${table.table_id}/fields`, {
|
||||||
|
headers: { Authorization: `Bearer ${token}` }
|
||||||
|
});
|
||||||
|
const fieldsData = await fieldsRes.json();
|
||||||
|
|
||||||
|
if (fieldsData.code !== 0) {
|
||||||
|
console.error("Error getting fields:", fieldsData.msg);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter for relevant fields to reduce noise
|
||||||
|
const interestingFields = ['需求', '需求详述', '优先级', '模块', '备注', '文本'];
|
||||||
|
|
||||||
|
fieldsData.data.items.forEach(f => {
|
||||||
|
// Log interesting fields OR Select fields (Type 3) to see options
|
||||||
|
if (interestingFields.includes(f.field_name) || f.type === 3) {
|
||||||
|
console.log(` - Field: ${f.field_name} (ID: ${f.field_id}, Type: ${f.type})`);
|
||||||
|
if (f.property && f.property.options) {
|
||||||
|
console.log(` Options: ${f.property.options.map(o => o.name).join(', ')}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
// Template (Iter 10)
|
||||||
|
await inspect('X8QPbUQdValKN7sFIwfcsy8fnEh', 'Template (Iter 10)');
|
||||||
|
// Target (Iter 11)
|
||||||
|
await inspect('LvlAbvfzMaxUP8sGOEWcLrX7nHb', 'Target (Iter 11)');
|
||||||
|
})();
|
||||||
140
lib/auth.js
Normal file
140
lib/auth.js
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// Robust .env loading
|
||||||
|
const possibleEnvPaths = [
|
||||||
|
path.resolve(process.cwd(), '.env'),
|
||||||
|
path.resolve(__dirname, '../../../.env'),
|
||||||
|
path.resolve(__dirname, '../../../../.env')
|
||||||
|
];
|
||||||
|
|
||||||
|
let envLoaded = false;
|
||||||
|
for (const envPath of possibleEnvPaths) {
|
||||||
|
if (fs.existsSync(envPath)) {
|
||||||
|
try {
|
||||||
|
require('dotenv').config({ path: envPath });
|
||||||
|
envLoaded = true;
|
||||||
|
break;
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore load error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let tokenCache = {
|
||||||
|
token: null,
|
||||||
|
expireTime: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
function loadConfig() {
|
||||||
|
const configPath = path.join(__dirname, '../config.json');
|
||||||
|
let config = {};
|
||||||
|
if (fs.existsSync(configPath)) {
|
||||||
|
try {
|
||||||
|
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to parse config.json");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
app_id: process.env.FEISHU_APP_ID || config.app_id,
|
||||||
|
app_secret: process.env.FEISHU_APP_SECRET || config.app_secret
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unified Token Cache (Shared with feishu-card and feishu-sticker)
|
||||||
|
const TOKEN_CACHE_FILE = path.resolve(__dirname, '../../../memory/feishu_token.json');
|
||||||
|
|
||||||
|
async function getTenantAccessToken(forceRefresh = false) {
|
||||||
|
const now = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
|
// Try to load from disk first
|
||||||
|
if (!forceRefresh && !tokenCache.token && fs.existsSync(TOKEN_CACHE_FILE)) {
|
||||||
|
try {
|
||||||
|
const saved = JSON.parse(fs.readFileSync(TOKEN_CACHE_FILE, 'utf8'));
|
||||||
|
// Handle both 'expire' (standard) and 'expireTime' (legacy)
|
||||||
|
const expiry = saved.expire || saved.expireTime;
|
||||||
|
if (saved.token && expiry > now) {
|
||||||
|
tokenCache.token = saved.token;
|
||||||
|
tokenCache.expireTime = expiry; // Keep internal consistency
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore corrupted cache
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force Refresh: Delete memory cache and file cache
|
||||||
|
if (forceRefresh) {
|
||||||
|
tokenCache.token = null;
|
||||||
|
tokenCache.expireTime = 0;
|
||||||
|
try { if (fs.existsSync(TOKEN_CACHE_FILE)) fs.unlinkSync(TOKEN_CACHE_FILE); } catch(e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tokenCache.token && tokenCache.expireTime > now) {
|
||||||
|
return tokenCache.token;
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = loadConfig();
|
||||||
|
if (!config.app_id || !config.app_secret) {
|
||||||
|
throw new Error("Missing app_id or app_secret. Please set FEISHU_APP_ID and FEISHU_APP_SECRET environment variables or create a config.json file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastError;
|
||||||
|
for (let attempt = 1; attempt <= 3; attempt++) {
|
||||||
|
try {
|
||||||
|
const response = await fetch('https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
"app_id": config.app_id,
|
||||||
|
"app_secret": config.app_secret
|
||||||
|
}),
|
||||||
|
timeout: 5000 // 5s timeout
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.code !== 0) {
|
||||||
|
throw new Error(`Failed to get tenant_access_token: ${data.msg}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenCache.token = data.tenant_access_token;
|
||||||
|
tokenCache.expireTime = now + data.expire - 60; // Refresh 1 minute early
|
||||||
|
|
||||||
|
// Persist to disk (Unified Format)
|
||||||
|
try {
|
||||||
|
const cacheDir = path.dirname(TOKEN_CACHE_FILE);
|
||||||
|
if (!fs.existsSync(cacheDir)) {
|
||||||
|
fs.mkdirSync(cacheDir, { recursive: true });
|
||||||
|
}
|
||||||
|
// Save using 'expire' to match other skills
|
||||||
|
fs.writeFileSync(TOKEN_CACHE_FILE, JSON.stringify({
|
||||||
|
token: tokenCache.token,
|
||||||
|
expire: tokenCache.expireTime
|
||||||
|
}, null, 2));
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to save token cache:", e.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenCache.token;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
lastError = error;
|
||||||
|
if (attempt < 3) {
|
||||||
|
const delay = 1000 * Math.pow(2, attempt - 1);
|
||||||
|
await new Promise(resolve => setTimeout(resolve, delay));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw lastError || new Error("Failed to retrieve access token after retries");
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getTenantAccessToken
|
||||||
|
};
|
||||||
74
lib/bitable.js
Normal file
74
lib/bitable.js
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
|
||||||
|
async function fetchBitableContent(token, accessToken) {
|
||||||
|
// 1. List tables
|
||||||
|
const tablesUrl = `https://open.feishu.cn/open-apis/bitable/v1/apps/${token}/tables`;
|
||||||
|
const tablesRes = await fetch(tablesUrl, {
|
||||||
|
headers: { 'Authorization': `Bearer ${accessToken}` }
|
||||||
|
});
|
||||||
|
const tablesData = await tablesRes.json();
|
||||||
|
|
||||||
|
if (tablesData.code !== 0) {
|
||||||
|
return { title: "Bitable", content: `Error fetching bitable tables: ${tablesData.msg}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
const tables = tablesData.data.items;
|
||||||
|
if (!tables || tables.length === 0) {
|
||||||
|
return { title: "Bitable", content: "Empty Bitable." };
|
||||||
|
}
|
||||||
|
|
||||||
|
let fullContent = [];
|
||||||
|
|
||||||
|
// 2. Fetch records
|
||||||
|
// Prioritize Ignacia's table (tblJgZHOmPybgX60) if present
|
||||||
|
const targetTableId = "tblJgZHOmPybgX60";
|
||||||
|
const targetTable = tables.find(t => t.table_id === targetTableId);
|
||||||
|
// If target found, only fetch it. Otherwise fetch first 3 to be safe/fast.
|
||||||
|
const tablesToFetch = targetTable ? [targetTable] : tables.slice(0, 3);
|
||||||
|
|
||||||
|
for (const table of tablesToFetch) {
|
||||||
|
const tableId = table.table_id;
|
||||||
|
const tableName = table.name;
|
||||||
|
|
||||||
|
// List records
|
||||||
|
const recordsUrl = `https://open.feishu.cn/open-apis/bitable/v1/apps/${token}/tables/${tableId}/records?page_size=20`;
|
||||||
|
const recRes = await fetch(recordsUrl, {
|
||||||
|
headers: { 'Authorization': `Bearer ${accessToken}` }
|
||||||
|
});
|
||||||
|
const recData = await recRes.json();
|
||||||
|
|
||||||
|
fullContent.push(`## Table: ${tableName}`);
|
||||||
|
|
||||||
|
if (recData.code === 0 && recData.data && recData.data.items) {
|
||||||
|
const records = recData.data.items;
|
||||||
|
// Convert records (objects with fields) to table
|
||||||
|
// We need to know all possible fields to make a header
|
||||||
|
const allFields = new Set();
|
||||||
|
records.forEach(r => Object.keys(r.fields).forEach(k => allFields.add(k)));
|
||||||
|
const headers = Array.from(allFields);
|
||||||
|
|
||||||
|
let md = "| " + headers.join(" | ") + " |\n";
|
||||||
|
md += "| " + headers.map(() => "---").join(" | ") + " |\n";
|
||||||
|
|
||||||
|
for (const rec of records) {
|
||||||
|
md += "| " + headers.map(h => {
|
||||||
|
const val = rec.fields[h];
|
||||||
|
if (typeof val === 'object') return JSON.stringify(val);
|
||||||
|
return val || "";
|
||||||
|
}).join(" | ") + " |\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
fullContent.push(md);
|
||||||
|
} else {
|
||||||
|
fullContent.push(`(Could not fetch records: ${recData.msg})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: "Feishu Bitable",
|
||||||
|
content: fullContent.join("\n\n")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
fetchBitableContent
|
||||||
|
};
|
||||||
192
lib/docx.js
Normal file
192
lib/docx.js
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
|
||||||
|
async function fetchDocxContent(documentId, accessToken) {
|
||||||
|
// 1. Get document info for title
|
||||||
|
const infoUrl = `https://open.feishu.cn/open-apis/docx/v1/documents/${documentId}`;
|
||||||
|
const infoRes = await fetch(infoUrl, {
|
||||||
|
headers: { 'Authorization': `Bearer ${accessToken}` }
|
||||||
|
});
|
||||||
|
const infoData = await infoRes.json();
|
||||||
|
let title = "Untitled Docx";
|
||||||
|
if (infoData.code === 0 && infoData.data && infoData.data.document) {
|
||||||
|
title = infoData.data.document.title;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Fetch all blocks
|
||||||
|
// List blocks API: GET https://open.feishu.cn/open-apis/docx/v1/documents/{document_id}/blocks
|
||||||
|
// Use pagination if necessary, fetching all for now (basic implementation)
|
||||||
|
let blocks = [];
|
||||||
|
let pageToken = '';
|
||||||
|
let hasMore = true;
|
||||||
|
|
||||||
|
while (hasMore) {
|
||||||
|
const url = `https://open.feishu.cn/open-apis/docx/v1/documents/${documentId}/blocks?page_size=500${pageToken ? `&page_token=${pageToken}` : ''}`;
|
||||||
|
const response = await fetch(url, {
|
||||||
|
headers: { 'Authorization': `Bearer ${accessToken}` }
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.code !== 0) {
|
||||||
|
throw new Error(`Failed to fetch docx blocks: ${data.msg}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.data && data.data.items) {
|
||||||
|
blocks = blocks.concat(data.data.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasMore = data.data.has_more;
|
||||||
|
pageToken = data.data.page_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
const markdown = convertBlocksToMarkdown(blocks);
|
||||||
|
return { title, content: markdown };
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertBlocksToMarkdown(blocks) {
|
||||||
|
if (!blocks || blocks.length === 0) return "";
|
||||||
|
|
||||||
|
let md = [];
|
||||||
|
|
||||||
|
for (const block of blocks) {
|
||||||
|
const type = block.block_type;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 1: // page
|
||||||
|
break;
|
||||||
|
case 2: // text (paragraph)
|
||||||
|
md.push(parseText(block.text));
|
||||||
|
break;
|
||||||
|
case 3: // heading1
|
||||||
|
md.push(`# ${parseText(block.heading1)}`);
|
||||||
|
break;
|
||||||
|
case 4: // heading2
|
||||||
|
md.push(`## ${parseText(block.heading2)}`);
|
||||||
|
break;
|
||||||
|
case 5: // heading3
|
||||||
|
md.push(`### ${parseText(block.heading3)}`);
|
||||||
|
break;
|
||||||
|
case 6: // heading4
|
||||||
|
md.push(`#### ${parseText(block.heading4)}`);
|
||||||
|
break;
|
||||||
|
case 7: // heading5
|
||||||
|
md.push(`##### ${parseText(block.heading5)}`);
|
||||||
|
break;
|
||||||
|
case 8: // heading6
|
||||||
|
md.push(`###### ${parseText(block.heading6)}`);
|
||||||
|
break;
|
||||||
|
case 9: // heading7
|
||||||
|
md.push(`####### ${parseText(block.heading7)}`);
|
||||||
|
break;
|
||||||
|
case 10: // heading8
|
||||||
|
md.push(`######## ${parseText(block.heading8)}`);
|
||||||
|
break;
|
||||||
|
case 11: // heading9
|
||||||
|
md.push(`######### ${parseText(block.heading9)}`);
|
||||||
|
break;
|
||||||
|
case 12: // bullet
|
||||||
|
md.push(`- ${parseText(block.bullet)}`);
|
||||||
|
break;
|
||||||
|
case 13: // ordered
|
||||||
|
md.push(`1. ${parseText(block.ordered)}`);
|
||||||
|
break;
|
||||||
|
case 14: // code
|
||||||
|
md.push('```' + (block.code?.style?.language === 1 ? '' : '') + '\n' + parseText(block.code) + '\n```');
|
||||||
|
break;
|
||||||
|
case 15: // quote
|
||||||
|
md.push(`> ${parseText(block.quote)}`);
|
||||||
|
break;
|
||||||
|
case 27: // image
|
||||||
|
md.push(``);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Ignore unknown blocks for now
|
||||||
|
console.error(`Skipped block type: ${type}`, JSON.stringify(block).substring(0, 200));
|
||||||
|
md.push(`[UNSUPPORTED BLOCK TYPE: ${type}]`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return md.join('\n\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function appendDocxContent(documentId, content, accessToken) {
|
||||||
|
// 1. Convert markdown content to Feishu blocks
|
||||||
|
const blocks = convertMarkdownToBlocks(content);
|
||||||
|
|
||||||
|
// 2. Append to the end of the document (root block)
|
||||||
|
// POST https://open.feishu.cn/open-apis/docx/v1/documents/{document_id}/blocks/{block_id}/children
|
||||||
|
// Use documentId as block_id to append to root
|
||||||
|
const url = `https://open.feishu.cn/open-apis/docx/v1/documents/${documentId}/blocks/${documentId}/children`;
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${accessToken}`,
|
||||||
|
'Content-Type': 'application/json; charset=utf-8'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
children: blocks,
|
||||||
|
index: -1 // Append to end
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.code !== 0) {
|
||||||
|
throw new Error(`Failed to append to docx: ${data.msg}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true, appended_blocks: data.data.children };
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertMarkdownToBlocks(markdown) {
|
||||||
|
// Simple parser: split by newlines, treat # as headers, others as text
|
||||||
|
// For robustness, this should be a real parser. Here we implement a basic one.
|
||||||
|
const lines = markdown.split('\n');
|
||||||
|
const blocks = [];
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (!trimmed) continue;
|
||||||
|
|
||||||
|
if (trimmed.startsWith('# ')) {
|
||||||
|
blocks.push({ block_type: 3, heading1: { elements: [{ text_run: { content: trimmed.substring(2) } }] } });
|
||||||
|
} else if (trimmed.startsWith('## ')) {
|
||||||
|
blocks.push({ block_type: 4, heading2: { elements: [{ text_run: { content: trimmed.substring(3) } }] } });
|
||||||
|
} else if (trimmed.startsWith('### ')) {
|
||||||
|
blocks.push({ block_type: 5, heading3: { elements: [{ text_run: { content: trimmed.substring(4) } }] } });
|
||||||
|
} else if (trimmed.startsWith('- ')) {
|
||||||
|
blocks.push({ block_type: 12, bullet: { elements: [{ text_run: { content: trimmed.substring(2) } }] } });
|
||||||
|
} else {
|
||||||
|
// Default to text (paragraph)
|
||||||
|
blocks.push({ block_type: 2, text: { elements: [{ text_run: { content: line } }] } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseText(blockData) {
|
||||||
|
if (!blockData || !blockData.elements) return "";
|
||||||
|
|
||||||
|
return blockData.elements.map(el => {
|
||||||
|
if (el.text_run) {
|
||||||
|
let text = el.text_run.content;
|
||||||
|
const style = el.text_run.text_element_style;
|
||||||
|
if (style) {
|
||||||
|
if (style.bold) text = `**${text}**`;
|
||||||
|
if (style.italic) text = `*${text}*`;
|
||||||
|
if (style.strikethrough) text = `~~${text}~~`;
|
||||||
|
if (style.inline_code) text = `\`${text}\``;
|
||||||
|
if (style.link) text = `[${text}](${style.link.url})`;
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
if (el.mention_doc) {
|
||||||
|
return `[Doc: ${el.mention_doc.token}]`;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}).join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
fetchDocxContent,
|
||||||
|
appendDocxContent
|
||||||
|
};
|
||||||
130
lib/sheet.js
Normal file
130
lib/sheet.js
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
34
lib/wiki.js
Normal file
34
lib/wiki.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
const { getTenantAccessToken } = require('./auth');
|
||||||
|
|
||||||
|
async function resolveWiki(token, accessToken) {
|
||||||
|
// Try to resolve via get_node API first to get obj_token and obj_type
|
||||||
|
// API: GET https://open.feishu.cn/open-apis/wiki/v2/spaces/get_node?token={token}
|
||||||
|
|
||||||
|
const url = `https://open.feishu.cn/open-apis/wiki/v2/spaces/get_node?token=${token}`;
|
||||||
|
const response = await fetch(url, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${accessToken}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.code === 0 && data.data && data.data.node) {
|
||||||
|
return {
|
||||||
|
obj_token: data.data.node.obj_token,
|
||||||
|
obj_type: data.data.node.obj_type, // 'docx', 'doc', 'sheet', 'bitable'
|
||||||
|
title: data.data.node.title
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle specific errors if needed (e.g., node not found)
|
||||||
|
if (data.code !== 0) {
|
||||||
|
throw new Error(`Wiki resolution failed: ${data.msg} (Code: ${data.code})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
resolveWiki
|
||||||
|
};
|
||||||
572
package-lock.json
generated
Normal file
572
package-lock.json
generated
Normal file
@@ -0,0 +1,572 @@
|
|||||||
|
{
|
||||||
|
"name": "feishu-doc",
|
||||||
|
"version": "1.2.6",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "feishu-doc",
|
||||||
|
"version": "1.2.6",
|
||||||
|
"dependencies": {
|
||||||
|
"@larksuiteoapi/node-sdk": "^1.58.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@larksuiteoapi/node-sdk": {
|
||||||
|
"version": "1.58.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@larksuiteoapi/node-sdk/-/node-sdk-1.58.0.tgz",
|
||||||
|
"integrity": "sha512-NcQNHdGuHOxOWY3bRGS9WldwpbR6+k7Fi0H1IJXDNNmbSrEB/8rLwqHRC8tAbbj/Mp8TWH/v1O+p487m6xskxw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "~1.13.3",
|
||||||
|
"lodash.identity": "^3.0.0",
|
||||||
|
"lodash.merge": "^4.6.2",
|
||||||
|
"lodash.pickby": "^4.6.0",
|
||||||
|
"protobufjs": "^7.2.6",
|
||||||
|
"qs": "^6.13.0",
|
||||||
|
"ws": "^8.16.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/aspromise": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/base64": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/codegen": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/eventemitter": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/fetch": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@protobufjs/aspromise": "^1.1.1",
|
||||||
|
"@protobufjs/inquire": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/float": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/inquire": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/path": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/pool": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/@protobufjs/utf8": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "25.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.0.tgz",
|
||||||
|
"integrity": "sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~7.16.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/axios": {
|
||||||
|
"version": "1.13.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz",
|
||||||
|
"integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.15.6",
|
||||||
|
"form-data": "^4.0.4",
|
||||||
|
"proxy-from-env": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/call-bind-apply-helpers": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"function-bind": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/call-bound": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind-apply-helpers": "^1.0.2",
|
||||||
|
"get-intrinsic": "^1.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/combined-stream": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"delayed-stream": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/dunder-proto": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind-apply-helpers": "^1.0.1",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"gopd": "^1.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/es-define-property": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/es-errors": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/es-object-atoms": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/es-set-tostringtag": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"get-intrinsic": "^1.2.6",
|
||||||
|
"has-tostringtag": "^1.0.2",
|
||||||
|
"hasown": "^2.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/follow-redirects": {
|
||||||
|
"version": "1.15.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||||
|
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"debug": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/form-data": {
|
||||||
|
"version": "4.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||||
|
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"es-set-tostringtag": "^2.1.0",
|
||||||
|
"hasown": "^2.0.2",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/function-bind": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/get-intrinsic": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind-apply-helpers": "^1.0.2",
|
||||||
|
"es-define-property": "^1.0.1",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"es-object-atoms": "^1.1.1",
|
||||||
|
"function-bind": "^1.1.2",
|
||||||
|
"get-proto": "^1.0.1",
|
||||||
|
"gopd": "^1.2.0",
|
||||||
|
"has-symbols": "^1.1.0",
|
||||||
|
"hasown": "^2.0.2",
|
||||||
|
"math-intrinsics": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/get-proto": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"dunder-proto": "^1.0.1",
|
||||||
|
"es-object-atoms": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/gopd": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/has-symbols": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/has-tostringtag": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"has-symbols": "^1.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/hasown": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"function-bind": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/lodash.identity": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.identity/-/lodash.identity-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-AupTIzdLQxJS5wIYUQlgGyk2XRTfGXA+MCghDHqZk0pzUNYvd3EESS6dkChNauNYVIutcb0dfHw1ri9Q1yPV8Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.merge": {
|
||||||
|
"version": "4.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||||
|
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.pickby": {
|
||||||
|
"version": "4.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.pickby/-/lodash.pickby-4.6.0.tgz",
|
||||||
|
"integrity": "sha512-AZV+GsS/6ckvPOVQPXSiFFacKvKB4kOQu6ynt9wz0F3LO4R9Ij4K1ddYsIytDpSgLz88JHd9P+oaLeej5/Sl7Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/long": {
|
||||||
|
"version": "5.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
|
||||||
|
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
|
"node_modules/math-intrinsics": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-db": {
|
||||||
|
"version": "1.52.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||||
|
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-types": {
|
||||||
|
"version": "2.1.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||||
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": "1.52.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/object-inspect": {
|
||||||
|
"version": "1.13.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||||
|
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/protobufjs": {
|
||||||
|
"version": "7.5.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz",
|
||||||
|
"integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@protobufjs/aspromise": "^1.1.2",
|
||||||
|
"@protobufjs/base64": "^1.1.2",
|
||||||
|
"@protobufjs/codegen": "^2.0.4",
|
||||||
|
"@protobufjs/eventemitter": "^1.1.0",
|
||||||
|
"@protobufjs/fetch": "^1.1.0",
|
||||||
|
"@protobufjs/float": "^1.0.2",
|
||||||
|
"@protobufjs/inquire": "^1.1.0",
|
||||||
|
"@protobufjs/path": "^1.1.2",
|
||||||
|
"@protobufjs/pool": "^1.1.0",
|
||||||
|
"@protobufjs/utf8": "^1.1.0",
|
||||||
|
"@types/node": ">=13.7.0",
|
||||||
|
"long": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/proxy-from-env": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/qs": {
|
||||||
|
"version": "6.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz",
|
||||||
|
"integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"side-channel": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/side-channel": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"object-inspect": "^1.13.3",
|
||||||
|
"side-channel-list": "^1.0.0",
|
||||||
|
"side-channel-map": "^1.0.1",
|
||||||
|
"side-channel-weakmap": "^1.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/side-channel-list": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"object-inspect": "^1.13.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/side-channel-map": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bound": "^1.0.2",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"get-intrinsic": "^1.2.5",
|
||||||
|
"object-inspect": "^1.13.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/side-channel-weakmap": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bound": "^1.0.2",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"get-intrinsic": "^1.2.5",
|
||||||
|
"object-inspect": "^1.13.3",
|
||||||
|
"side-channel-map": "^1.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "7.16.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
||||||
|
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/ws": {
|
||||||
|
"version": "8.19.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
|
||||||
|
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": ">=5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
package.json
Normal file
12
package.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "feishu-doc",
|
||||||
|
"version": "1.2.6",
|
||||||
|
"description": "Fetch content from Feishu Wiki/Doc/Sheet/Bitable",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"No tests specified\" && exit 0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@larksuiteoapi/node-sdk": "^1.58.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
129
setup_iter11.js
Normal file
129
setup_iter11.js
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
const { getTenantAccessToken } = require('./lib/auth');
|
||||||
|
|
||||||
|
const APP_TOKEN = 'LvlAbvfzMaxUP8sGOEWcLrX7nHb';
|
||||||
|
const TABLE_ID = 'tblLy7koY2VGXGmR'; // From inspect_meta.js
|
||||||
|
|
||||||
|
async function setup() {
|
||||||
|
const token = await getTenantAccessToken();
|
||||||
|
console.log(`Setting up Iter 11 (App: ${APP_TOKEN}, Table: ${TABLE_ID})`);
|
||||||
|
|
||||||
|
// 1. Create Fields
|
||||||
|
// Field: 需求 (Text - Type 1)
|
||||||
|
await createField(token, '需求', 1);
|
||||||
|
|
||||||
|
// Field: 需求详述 (Text - Type 1)
|
||||||
|
await createField(token, '需求详述', 1);
|
||||||
|
|
||||||
|
// Field: 优先级 (Single Select - Type 3)
|
||||||
|
const options = [
|
||||||
|
{ name: '上帝级重要', color: 0 }, // Red
|
||||||
|
{ name: '很重要', color: 1 }, // Orange
|
||||||
|
{ name: '重要', color: 2 }, // Yellow
|
||||||
|
{ name: '欠重要', color: 3 }, // Green
|
||||||
|
{ name: '待定', color: 4 } // Blue
|
||||||
|
];
|
||||||
|
await createField(token, '优先级', 3, { options: options });
|
||||||
|
|
||||||
|
// 2. Insert Records
|
||||||
|
const records = [
|
||||||
|
{
|
||||||
|
fields: {
|
||||||
|
'需求': '获取行为和生活职业的结合',
|
||||||
|
'需求详述': 'a. 当前获取行为不受生活职业的限制\nb. 炼药、烹饪行为因为和获取高度相关,还未完成开发\nc. 无法获取的道具走总控给其他NPC制作功能没做',
|
||||||
|
'优先级': '上帝级重要'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fields: {
|
||||||
|
'需求': 'NPC信息面板',
|
||||||
|
'需求详述': '',
|
||||||
|
'优先级': '很重要'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fields: {
|
||||||
|
'需求': '心情系统',
|
||||||
|
'需求详述': 'a. 完成了单独心情值的开发,心情值的变化和行为的结合没有处理',
|
||||||
|
'优先级': '很重要'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fields: {
|
||||||
|
'需求': '房间系统',
|
||||||
|
'需求详述': 'a. 完成了item舒适度的计算,房间对NPC的影响和关系没有处理',
|
||||||
|
'优先级': '重要'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fields: {
|
||||||
|
'需求': '营地管理页面',
|
||||||
|
'需求详述': 'a. 角色列表页\nb. 物品需求页',
|
||||||
|
'优先级': '重要'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fields: {
|
||||||
|
'需求': '路径COST计算规则',
|
||||||
|
'需求详述': '',
|
||||||
|
'优先级': '欠重要'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fields: {
|
||||||
|
'需求': '营地功能旗帜的交互',
|
||||||
|
'需求详述': '',
|
||||||
|
'优先级': '欠重要'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
console.log(`Inserting ${records.length} records...`);
|
||||||
|
const batchUrl = `https://open.feishu.cn/open-apis/bitable/v1/apps/${APP_TOKEN}/tables/${TABLE_ID}/records/batch_create`;
|
||||||
|
|
||||||
|
const res = await fetch(batchUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ records: records })
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.code !== 0) {
|
||||||
|
console.error('Failed to create records:', JSON.stringify(data, null, 2));
|
||||||
|
} else {
|
||||||
|
console.log('Success! Created records.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createField(token, name, type, property) {
|
||||||
|
console.log(`Creating field: ${name}`);
|
||||||
|
const url = `https://open.feishu.cn/open-apis/bitable/v1/apps/${APP_TOKEN}/tables/${TABLE_ID}/fields`;
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
field_name: name,
|
||||||
|
type: type
|
||||||
|
};
|
||||||
|
if (property) payload.property = property;
|
||||||
|
|
||||||
|
const res = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.code === 0) {
|
||||||
|
console.log(` -> Created field ID: ${data.data.field.field_id}`);
|
||||||
|
return data.data.field.field_id;
|
||||||
|
} else {
|
||||||
|
console.warn(` -> Failed to create field (might exist): ${data.msg}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setup().catch(console.error);
|
||||||
12
validate_patch.js
Normal file
12
validate_patch.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
const { FeishuClient } = require('./feishu-client'); // Assuming standard client
|
||||||
|
|
||||||
|
// Mock client or use real if env vars set (skipping real call to avoid side effects in validation)
|
||||||
|
// We just want to ensure the syntax of index.js is valid after edit.
|
||||||
|
|
||||||
|
try {
|
||||||
|
const index = require('./index.js');
|
||||||
|
console.log('skills/feishu-doc/index.js loaded successfully.');
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to load index.js:', e);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user