# Feishu Doc

> 创建飞书文档（Feishu DocX）。当用户说"写个飞书文档"、"创建飞书文档"、"生成飞书doc"、"feishu doc"等关键词时触发。

- Skill: `yangwhale/feishu-doc` (Agent Skill)
- Install (CLI): `npx skillmds@latest add yangwhale/feishu-doc`
- Raw SKILL.md: https://api.skillmd.com/api/skills/yangwhale/feishu-doc/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Docs & Writing
- License: MIT
- Author: yangwhale (https://skillmd.com/u/yangwhale)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/yangwhale/feishu-doc

---


# 飞书文档创建（Feishu DocX API）

通过飞书 DocX API 创建富格式文档，支持文本格式、标题、列表、表格、代码块、Callout、公式等全部原生能力。

## 触发条件

- "写个飞书文档"、"创建飞书文档"
- "生成飞书doc"、"feishu doc"
- "写到飞书文档里"、"输出成飞书文档"

## 前置条件

- 飞书 App 需要 `docx:document` 和 `drive:drive` 权限
- 环境变量已配置：`FEISHU_APP_ID_JARVIS` / `FEISHU_APP_SECRET_JARVIS`（或对应 bot 的 env）

## API 概览

### 1. 创建文档

```python
import os, lark_oapi as lark
from lark_oapi.api.docx.v1 import *

client = lark.Client.builder() \
    .app_id(os.environ['FEISHU_APP_ID_JARVIS']) \
    .app_secret(os.environ['FEISHU_APP_SECRET_JARVIS']) \
    .domain(lark.FEISHU_DOMAIN).build()

req = CreateDocumentRequest.builder() \
    .request_body(CreateDocumentRequestBody.builder()
        .title("文档标题")
        .folder_token("")  # 空字符串 = 根目录
        .build()).build()
resp = client.docx.v1.document.create(req)
doc_id = resp.data.document.document_id
```

### 2. 添加内容块

```python
body = CreateDocumentBlockChildrenRequestBody.builder() \
    .children(blocks)  # List[Block]
    .index(-1)         # -1 = 追加到末尾
    .build()
req = CreateDocumentBlockChildrenRequest.builder() \
    .document_id(doc_id) \
    .block_id(parent_id)      # doc_id = 根级，或容器 block_id
    .document_revision_id(-1) # -1 = 最新版本
    .request_body(body).build()
resp = client.docx.v1.document_block_children.create(req)
```

### 3. 获取子块（表格填充用）

```python
req = GetDocumentBlockChildrenRequest.builder() \
    .document_id(doc_id) \
    .block_id(table_block_id) \
    .build()
resp = client.docx.v1.document_block_children.get(req)
cell_ids = [item.block_id for item in resp.data.items]
```

## Block Types 速查

| block_type | 类型 | Builder setter | 说明 |
|-----------|------|---------------|------|
| 2 | text | `.text()` | 普通文本段落 |
| 3-8 | heading1-6 | `.heading1()` ~ `.heading6()` | 标题 H1-H6 |
| 12 | bullet | `.bullet()` | 无序列表 |
| 13 | ordered | `.ordered()` | 有序列表 |
| 14 | code | `.code()` | 代码块 |
| 15 | quote | `.quote()` | 引用行 |
| 17 | todo | `.todo()` | 任务清单 |
| 19 | callout | `.callout()` | 高亮块（容器） |
| 22 | divider | `.divider({})` | 分割线 |
| 25 | grid | `.grid()` | 多列布局（API 不支持创建） |
| 27 | iframe | `.iframe()` | 内嵌网页 |
| 28 | image | `.image()` | 图片（需先上传获取 token） |
| 31 | table | `.table()` | 表格 |
| 34 | quote_container | `.quote_container({})` | 引用容器（可嵌套） |

## 构建 Block 的工具函数

以下函数可直接复制使用：

### make_text_element — 创建带格式的文本片段

```python
def make_text_element(content, bold=False, italic=False, underline=False,
                      strikethrough=False, inline_code=False,
                      text_color=None, bg_color=None, link=None):
    style_builder = TextElementStyleBuilder()
    if bold: style_builder.bold(True)
    if italic: style_builder.italic(True)
    if underline: style_builder.underline(True)
    if strikethrough: style_builder.strikethrough(True)
    if inline_code: style_builder.inline_code(True)
    if text_color: style_builder.text_color(text_color)     # 1=红 2=橙 3=黄 4=绿 5=蓝 6=紫
    if bg_color: style_builder.background_color(bg_color)   # 同上
    if link: style_builder.link(LinkBuilder().url(link).build())
    return TextElementBuilder() \
        .text_run(TextRunBuilder().content(content)
            .text_element_style(style_builder.build()).build()).build()
```

### make_text_block — 创建文本类 Block

```python
def make_text_block(elements, block_type=2, align=None, style_kwargs=None):
    style_builder = TextStyleBuilder()
    if align: style_builder.align(align)  # 1=左 2=中 3=右
    if style_kwargs:
        if 'language' in style_kwargs: style_builder.language(style_kwargs['language'])
        if 'done' in style_kwargs: style_builder.done(style_kwargs['done'])
    text = TextBuilder().elements(elements).style(style_builder.build()).build()
    bb = BlockBuilder().block_type(block_type)
    type_map = {
        2: 'text', 3: 'heading1', 4: 'heading2', 5: 'heading3',
        6: 'heading4', 7: 'heading5', 8: 'heading6',
        12: 'bullet', 13: 'ordered', 14: 'code', 15: 'quote', 17: 'todo',
    }
    getattr(bb, type_map.get(block_type, 'text'))(text)
    return bb.build()
```

### make_divider — 分割线

```python
def make_divider():
    return BlockBuilder().block_type(22).divider({}).build()
```

## 常用代码块语言 ID

| ID | 语言 | ID | 语言 |
|----|------|----|------|
| 1 | PlainText | 22 | JavaScript |
| 15 | Bash/Shell | 40 | TypeScript |
| 49 | Python | 18 | Java |
| 19 | JSON | 12 | Go |
| 7 | C++ | 56 | YAML |
| 29 | Markdown | 54 | SQL |
| 53 | Rust | 4 | C |

## Callout 高亮块

Callout 是容器块，创建后需要往内部添加子块：

```python
callout = BlockBuilder().block_type(19) \
    .callout(CalloutBuilder()
        .background_color(4)   # 1=红 2=橙 3=黄 4=绿 5=蓝 6=紫
        .border_color(4)
        .emoji_id("bulb")      # 英文 emoji 名称，非 unicode
        .build()).build()
ids = add_blocks([callout])
add_blocks([make_text_block(...)], parent_id=ids[0])  # 填充内容
```

常用 emoji_id: `bulb`, `check`, `warning`, `star`, `crystal_ball`, `rocket`, `fire`, `heart`, `memo`, `pushpin`

## 表格

表格创建后自动生成空 cell，需要查询 cell_ids 再逐个填充：

```python
table = BlockBuilder().block_type(31) \
    .table(TableBuilder()
        .property(TablePropertyBuilder().row_size(3).column_size(4).build())
        .cells([]).build()).build()
table_ids = add_blocks([table])

# 获取 cell block_ids
req = GetDocumentBlockChildrenRequest.builder() \
    .document_id(doc_id).block_id(table_ids[0]).build()
resp = client.docx.v1.document_block_children.get(req)
cell_ids = [item.block_id for item in resp.data.items]
# cells 按行优先排列: [row0col0, row0col1, ..., row1col0, ...]

for idx, cell_id in enumerate(cell_ids):
    row, col = idx // num_cols, idx % num_cols
    add_blocks([make_text_block([make_text_element(data[row][col])])], parent_id=cell_id)
```

## 行内公式（LaTeX）

```python
make_text_block([
    make_text_element("能量公式："),
    TextElementBuilder().equation(
        EquationBuilder().content("E = mc^2").build()
    ).build(),
])
```

## 引用容器

容器块，可嵌套文本、列表等：

```python
qc = BlockBuilder().block_type(34).quote_container({}).build()
qc_ids = add_blocks([qc])
add_blocks([make_text_block(...)], parent_id=qc_ids[0])
```

## 图片

需要先上传文件获取 image token，再创建 image block：

```python
# 1. 上传图片到飞书（使用 drive API 的 upload_media）
# 2. 创建 image block
image_block = BlockBuilder().block_type(28) \
    .image(ImageBuilder().token(image_token).width(800).height(400).build()).build()
```

## 注意事项

- **Rate limit**: 每秒约 5 次 API 调用，批量操作间加 `time.sleep(0.2)`
- **Grid 不支持 API 创建**: `block_type=25` 返回 `block not support to create`，只能在客户端手动添加
- **容器块填充**: Callout (19)、Quote Container (34)、Table Cell (32) 都是容器，创建后需要往 children 添加内容
- **document_revision_id=-1**: 总是使用最新版本，避免并发冲突
- **文档 URL**: `https://bytedance.feishu.cn/docx/{document_id}`

## 工作流程

1. **创建空文档** → 拿到 `document_id`
2. **规划内容结构** → 标题、段落、表格、Callout 等
3. **批量添加块** → 一次 API 调用可加多个同级 block
4. **填充容器** → 表格 cell、callout、quote_container 需要二次填充
5. **返回文档链接** → `https://bytedance.feishu.cn/docx/{document_id}`

