Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion shortcuts/drive/drive_import.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ var DriveImport = common.Shortcut{
ConditionalScopes: []string{"wiki:node:retrieve"},
AuthTypes: []string{"user", "bot"},
Flags: []common.Flag{
{Name: "file", Desc: "local file path (e.g. .docx, .xlsx, .md, .base, .pptx; large files auto use multipart upload; .base is capped at 20MB, .pptx at 500MB)", Required: true},
{Name: "file", Desc: "local file path (e.g. .docx, .xlsx, .md, .base, .pptx, .png; large files auto use multipart upload; .base is capped at 20MB, .pptx at 500MB)", Required: true},
{Name: "type", Desc: "target document type (docx, sheet, bitable, slides)", Required: true},
{Name: "folder-token", Desc: "target folder token (omit for root folder; API accepts empty mount_key as root)"},
{Name: "name", Desc: "imported file name (default: local file name without extension)"},
Expand Down
9 changes: 5 additions & 4 deletions shortcuts/drive/drive_import_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ var driveImportExtToDocTypes = map[string][]string{
"csv": {"sheet", "bitable"},
"base": {"bitable"},
"pptx": {"slides"},
"png": {"slides"},
}

var driveImportConcurrentOperationCodes = []int{232140101, 232140100, 233523001}
Expand Down Expand Up @@ -230,7 +231,7 @@ func validateDriveImportFileSize(ext, docType string, fileSize int64) error {
func validateDriveImportSpec(spec driveImportSpec) error {
ext := spec.FileExtension()
if ext == "" {
return errs.NewValidationError(errs.SubtypeInvalidArgument, "file must have an extension (e.g. .md, .docx, .xlsx, .pptx)").WithParam("--file")
return errs.NewValidationError(errs.SubtypeInvalidArgument, "file must have an extension (e.g. .md, .docx, .xlsx, .pptx, .png)").WithParam("--file")
}

switch spec.DocType {
Expand All @@ -241,7 +242,7 @@ func validateDriveImportSpec(spec driveImportSpec) error {

supportedTypes, ok := driveImportExtToDocTypes[ext]
if !ok {
return errs.NewValidationError(errs.SubtypeInvalidArgument, "unsupported file extension: %s. Supported extensions are: docx, doc, txt, md, mark, markdown, html, xlsx, xls, csv, base, pptx", ext).WithParam("--file")
return errs.NewValidationError(errs.SubtypeInvalidArgument, "unsupported file extension: %s. Supported extensions are: docx, doc, txt, md, mark, markdown, html, xlsx, xls, csv, base, pptx, png", ext).WithParam("--file")
}

typeAllowed := false
Expand All @@ -262,8 +263,8 @@ func validateDriveImportSpec(spec driveImportSpec) error {
hint = fmt.Sprintf(".xls files can only be imported as 'sheet', not '%s'", spec.DocType)
case "base":
hint = fmt.Sprintf(".base files can only be imported as 'bitable', not '%s'", spec.DocType)
case "pptx":
hint = fmt.Sprintf(".pptx files can only be imported as 'slides', not '%s'", spec.DocType)
case "pptx", "png":
hint = fmt.Sprintf(".%s files can only be imported as 'slides', not '%s'", ext, spec.DocType)
default:
hint = fmt.Sprintf(".%s files can only be imported as 'docx', not '%s'", ext, spec.DocType)
}
Expand Down
9 changes: 9 additions & 0 deletions shortcuts/drive/drive_import_common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ func TestValidateDriveImportSpec(t *testing.T) {
name: "pptx slides ok",
spec: driveImportSpec{FilePath: "./deck.pptx", DocType: "slides"},
},
{
name: "png slides ok",
spec: driveImportSpec{FilePath: "./screenshot.png", DocType: "slides"},
},
{
name: "base non bitable rejected",
spec: driveImportSpec{FilePath: "./snapshot.base", DocType: "sheet"},
Expand All @@ -52,6 +56,11 @@ func TestValidateDriveImportSpec(t *testing.T) {
spec: driveImportSpec{FilePath: "./deck.pptx", DocType: "docx"},
wantErr: ".pptx files can only be imported as 'slides'",
},
{
name: "png non slides rejected",
spec: driveImportSpec{FilePath: "./screenshot.png", DocType: "docx"},
wantErr: ".png files can only be imported as 'slides'",
},
{
name: "unknown extension rejected",
spec: driveImportSpec{FilePath: "./data.rtf", DocType: "docx"},
Expand Down
4 changes: 2 additions & 2 deletions skills/lark-drive/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: lark-drive
version: 1.0.0
description: "飞书云空间(云盘/云存储):管理 Drive 文件和文件夹,包含上传/下载、创建文件夹、复制/移动/删除、查看元数据、评论/权限/订阅、标题、版本和本地文件导入。用户需要整理云盘目录、处理云空间资源 URL/token、判断链接类型/真实 token/标题,或导入 Word/Markdown/Excel/CSV/PPTX/.base 为 docx/sheet/bitable/slides 时使用;doubao.com 云空间 URL/token 也按资源路径和 token 路由,不回退 WebFetch。不负责:文档内容编辑(走 lark-doc)、表格/Base 表内数据操作(走 lark-sheets/lark-base)、知识空间节点/成员管理(走 lark-wiki)、原生 Markdown 文件读写/patch/diff(走 lark-markdown)。"
description: "飞书云空间(云盘/云存储):管理 Drive 文件和文件夹,包含上传/下载、创建文件夹、复制/移动/删除、查看元数据、评论/权限/订阅、标题、版本和本地文件导入。用户需要整理云盘目录、处理云空间资源 URL/token、判断链接类型/真实 token/标题,或导入 Word/Markdown/Excel/CSV/PPTX/PNG/.base 为 docx/sheet/bitable/slides 时使用;doubao.com 云空间 URL/token 也按资源路径和 token 路由,不回退 WebFetch。不负责:文档内容编辑(走 lark-doc)、表格/Base 表内数据操作(走 lark-sheets/lark-base)、知识空间节点/成员管理(走 lark-wiki)、原生 Markdown 文件读写/patch/diff(走 lark-markdown)。"
metadata:
requires:
bins: ["lark-cli"]
Expand Down Expand Up @@ -31,7 +31,7 @@ metadata:
- 用户给出 doubao.com 的云空间资源 URL/token,或明确提到豆包里的 file/folder/docx/sheet/bitable/wiki 资源时,仍按资源类型、URL 路径和 token 路由到本 skill;不要因为域名不是飞书而回退到 WebFetch。
- 用户要把本地 `.xlsx` / `.csv` / `.base` 导入成 Base / 多维表格 / bitable,第一步必须使用 `lark-cli drive +import --type bitable`。
- 用户要把本地 `.md` / `.docx` / `.doc` / `.txt` / `.html` 导入成在线文档,使用 `lark-cli drive +import --type docx`。
- 用户要把本地 `.pptx` 导入成飞书幻灯片,使用 `lark-cli drive +import --type slides`;当前 PPTX 导入上限是 500MB。
- 用户要把本地 `.pptx` 或 `.png` 导入成飞书幻灯片,使用 `lark-cli drive +import --type slides`;当前 PPTX 导入上限是 500MB,PNG 由服务端校验大小限制
- 批量执行 `drive +import` 且目标是同一个位置(同一 `--folder-token`、默认根目录,或同一 `--target-token`)时,必须串行执行;不要并发导入到同一位置,服务端可能返回并发冲突错误。
- 用户要在 Drive 里上传、创建、读取、局部 patch 或覆盖更新**原生 `.md` 文件**(不是导入成 docx),切到 [`lark-markdown`](../lark-markdown/SKILL.md)。
- 用户要比较原生 `.md` 文件的**历史版本差异**,或比较远端 Markdown 与本地草稿,切到 [`lark-markdown`](../lark-markdown/SKILL.md) 的 `lark-cli markdown +diff`;需要版本号时先用 `drive +version-history`。
Expand Down
7 changes: 6 additions & 1 deletion skills/lark-drive/references/lark-drive-import.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

> **前置条件:** 先阅读 [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) 了解认证、全局参数和安全规则。

将本地文件(如 Word、TXT、Markdown、Excel、PPTX 等)导入并转换为飞书在线云文档(docx、sheet、bitable、slides)。底层统一通过 `POST /open-apis/drive/v1/import_tasks` 接口创建导入任务,并在 shortcut 内做有限次数轮询 `GET /open-apis/drive/v1/import_tasks/:ticket`。
将本地文件(如 Word、TXT、Markdown、Excel、PPTX、PNG 等)导入并转换为飞书在线云文档(docx、sheet、bitable、slides)。底层统一通过 `POST /open-apis/drive/v1/import_tasks` 接口创建导入任务,并在 shortcut 内做有限次数轮询 `GET /open-apis/drive/v1/import_tasks/:ticket`。

> [!IMPORTANT]
> 当用户说“把本地 Excel / CSV / `.base` 快照导入成 Base / 多维表格 / bitable 文档”时,第一步必须使用 `drive +import --type bitable`。
Expand Down Expand Up @@ -55,6 +55,9 @@ lark-cli drive +import --file ./snapshot.base --type bitable --name "快照还
# 导入 PPTX 为飞书幻灯片 (slides)(文件不能超过 500MB)
lark-cli drive +import --file ./deck.pptx --type slides --name "项目汇报"

# 导入 PNG 为飞书幻灯片 (slides)
lark-cli drive +import --file ./screenshot.png --type slides --name "图片转幻灯片"

# 导入到指定文件夹,并指定导入后的文件名
lark-cli drive +import --file ./data.csv --type bitable --folder-token <FOLDER_TOKEN> --name "导入数据表"

Expand Down Expand Up @@ -101,6 +104,7 @@ lark-cli drive +import --file ./README.md --type docx --dry-run
| `.csv` | `sheet`, `bitable` | CSV 数据文件 |
| `.base` | `bitable` | 多维表格快照文件 |
| `.pptx` | `slides` | Microsoft PowerPoint 演示文稿 |
| `.png` | `slides` | PNG 图片(Image to Slide) |

> [!IMPORTANT]
> 用户口头说的 “Base” / “多维表格” / “bitable”,在命令里统一对应 `--type bitable`。
Expand All @@ -111,6 +115,7 @@ lark-cli drive +import --file ./README.md --type docx --dry-run
> - `.xls` 文件**只能**导入为 `sheet`
> - `.base` 文件**只能**导入为 `bitable`
> - `.pptx` 文件**只能**导入为 `slides`
> - `.png` 文件**只能**导入为 `slides`
> - 例如:`.csv` 文件不能导入为 `docx`,`.md` 文件不能导入为 `sheet`

> [!IMPORTANT]
Expand Down
43 changes: 43 additions & 0 deletions tests/cli_e2e/drive/drive_import_dryrun_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,46 @@ func TestDriveImportDryRunFolderTokenWikiProbe(t *testing.T) {
t.Fatalf("data.api.2.body.point.mount_key = %q, want fldcnImportDryRunTarget\nstdout:\n%s", got, out)
}
}

func TestDriveImportPNGToSlidesDryRun(t *testing.T) {
setDriveDryRunConfigEnv(t)

workDir := t.TempDir()
if err := os.WriteFile(workDir+"/screenshot.png", []byte("fake png for dry run"), 0o644); err != nil {
t.Fatalf("WriteFile: %v", err)
}

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
t.Cleanup(cancel)

result, err := clie2e.RunCmd(ctx, clie2e.Request{
Args: []string{
"drive", "+import",
"--file", "screenshot.png",
"--type", "slides",
"--name", "image-to-slide",
"--dry-run",
},
WorkDir: workDir,
DefaultAs: "bot",
})
require.NoError(t, err)
result.AssertExitCode(t, 0)

out := result.Stdout
if got := clie2e.DryRunGet(out, "api.0.url").String(); got != "/open-apis/drive/v1/medias/upload_all" {
t.Fatalf("data.api.0.url = %q, want upload_all\nstdout:\n%s", got, out)
}
if got := clie2e.DryRunGet(out, "api.0.body.extra").String(); got != `{"file_extension":"png","obj_type":"slides"}` {
t.Fatalf("data.api.0.body.extra = %q, want PNG-to-slides metadata\nstdout:\n%s", got, out)
}
if got := clie2e.DryRunGet(out, "api.1.url").String(); got != "/open-apis/drive/v1/import_tasks" {
t.Fatalf("data.api.1.url = %q, want import_tasks\nstdout:\n%s", got, out)
}
if got := clie2e.DryRunGet(out, "api.1.body.file_extension").String(); got != "png" {
t.Fatalf("data.api.1.body.file_extension = %q, want png\nstdout:\n%s", got, out)
}
if got := clie2e.DryRunGet(out, "api.1.body.type").String(); got != "slides" {
t.Fatalf("data.api.1.body.type = %q, want slides\nstdout:\n%s", got, out)
}
}
Loading