diff --git a/shortcuts/drive/drive_import.go b/shortcuts/drive/drive_import.go index 330c119e..a9f6e32f 100644 --- a/shortcuts/drive/drive_import.go +++ b/shortcuts/drive/drive_import.go @@ -19,7 +19,7 @@ import ( var DriveImport = common.Shortcut{ Service: "drive", Command: "+import", - Description: "Import a local file to Drive as a cloud document (docx, sheet, bitable)", + Description: "Import a local file to Drive as a cloud document (docx, sheet, bitable, slides)", Risk: "write", Scopes: []string{ "docs:document.media:upload", @@ -27,8 +27,8 @@ var DriveImport = common.Shortcut{ }, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ - {Name: "file", Desc: "local file path (e.g. .docx, .xlsx, .md, .base; large files auto use multipart upload; .base is capped at 20MB)", Required: true}, - {Name: "type", Desc: "target document type (docx, sheet, bitable)", Required: true}, + {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: "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)"}, {Name: "target-token", Desc: "existing token to import data into (only for type=bitable); when set, data is mounted into this bitable instead of creating a new one"}, @@ -221,10 +221,10 @@ func appendDriveImportUploadDryRun(dry *common.DryRunAPI, spec driveImportSpec, // from the API and isn't normalized; if it ever returns aliases like "sheets" // or "sheet_v2" the URL construction would silently fall through. Fall back // to the user-supplied --type, which is already validated to docx/sheet/ -// bitable, so out.url stays populated whenever status.Token is set. +// bitable/slides, so out.url stays populated whenever status.Token is set. func normalizeDriveImportKindForURL(serverType, fallback string) string { switch strings.ToLower(strings.TrimSpace(serverType)) { - case "docx", "sheet", "bitable": + case "docx", "sheet", "bitable", "slides": return strings.ToLower(strings.TrimSpace(serverType)) } return fallback diff --git a/shortcuts/drive/drive_import_common.go b/shortcuts/drive/drive_import_common.go index ed5fa318..e4abe0b8 100644 --- a/shortcuts/drive/drive_import_common.go +++ b/shortcuts/drive/drive_import_common.go @@ -25,6 +25,7 @@ const ( // These limits follow the current product-side import constraints per format. driveImport20MBFileSizeLimit int64 = 20 * 1024 * 1024 driveImport100MBFileSizeLimit int64 = 100 * 1024 * 1024 + driveImport500MBFileSizeLimit int64 = 500 * 1024 * 1024 driveImport600MBFileSizeLimit int64 = 600 * 1024 * 1024 driveImport800MBFileSizeLimit int64 = 800 * 1024 * 1024 ) @@ -43,6 +44,7 @@ var driveImportExtToDocTypes = map[string][]string{ "xls": {"sheet"}, "csv": {"sheet", "bitable"}, "base": {"bitable"}, + "pptx": {"slides"}, } // driveImportSpec contains the user-facing import inputs after normalization. @@ -151,6 +153,8 @@ func driveImportFileSizeLimit(filePath, docType string) (int64, bool) { switch strings.TrimPrefix(strings.ToLower(filepath.Ext(filePath)), ".") { case "docx", "doc": return driveImport600MBFileSizeLimit, true + case "pptx": + return driveImport500MBFileSizeLimit, true case "txt", "md", "mark", "markdown", "html", "xls", "base": return driveImport20MBFileSizeLimit, true case "xlsx": @@ -195,18 +199,18 @@ func validateDriveImportFileSize(filePath, docType string, fileSize int64) error func validateDriveImportSpec(spec driveImportSpec) error { ext := spec.FileExtension() if ext == "" { - return output.ErrValidation("file must have an extension (e.g. .md, .docx, .xlsx)") + return output.ErrValidation("file must have an extension (e.g. .md, .docx, .xlsx, .pptx)") } switch spec.DocType { - case "docx", "sheet", "bitable": + case "docx", "sheet", "bitable", "slides": default: - return output.ErrValidation("unsupported target document type: %s. Supported types are: docx, sheet, bitable", spec.DocType) + return output.ErrValidation("unsupported target document type: %s. Supported types are: docx, sheet, bitable, slides", spec.DocType) } supportedTypes, ok := driveImportExtToDocTypes[ext] if !ok { - return output.ErrValidation("unsupported file extension: %s. Supported extensions are: docx, doc, txt, md, mark, markdown, html, xlsx, xls, csv, base", ext) + return output.ErrValidation("unsupported file extension: %s. Supported extensions are: docx, doc, txt, md, mark, markdown, html, xlsx, xls, csv, base, pptx", ext) } typeAllowed := false @@ -227,6 +231,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) default: hint = fmt.Sprintf(".%s files can only be imported as 'docx', not '%s'", ext, spec.DocType) } diff --git a/shortcuts/drive/drive_import_common_test.go b/shortcuts/drive/drive_import_common_test.go index 2c786eed..3f3093c1 100644 --- a/shortcuts/drive/drive_import_common_test.go +++ b/shortcuts/drive/drive_import_common_test.go @@ -35,11 +35,20 @@ func TestValidateDriveImportSpec(t *testing.T) { name: "base bitable ok", spec: driveImportSpec{FilePath: "./snapshot.base", DocType: "bitable"}, }, + { + name: "pptx slides ok", + spec: driveImportSpec{FilePath: "./deck.pptx", DocType: "slides"}, + }, { name: "base non bitable rejected", spec: driveImportSpec{FilePath: "./snapshot.base", DocType: "sheet"}, wantErr: ".base files can only be imported as 'bitable'", }, + { + name: "pptx non slides rejected", + spec: driveImportSpec{FilePath: "./deck.pptx", DocType: "docx"}, + wantErr: ".pptx files can only be imported as 'slides'", + }, { name: "unknown extension rejected", spec: driveImportSpec{FilePath: "./data.rtf", DocType: "docx"}, @@ -114,6 +123,19 @@ func TestValidateDriveImportFileSize(t *testing.T) { docType: "sheet", fileSize: driveImport800MBFileSizeLimit, }, + { + name: "pptx exceeds 500mb limit", + filePath: "./deck.pptx", + docType: "slides", + fileSize: driveImport500MBFileSizeLimit + 1, + wantText: "exceeds 500.0 MB import limit for .pptx", + }, + { + name: "pptx within 500mb limit", + filePath: "./deck.pptx", + docType: "slides", + fileSize: driveImport500MBFileSizeLimit, + }, { name: "base exceeds 20mb limit", filePath: "./snapshot.base", diff --git a/shortcuts/drive/drive_import_test.go b/shortcuts/drive/drive_import_test.go index c5b6aa10..ba540753 100644 --- a/shortcuts/drive/drive_import_test.go +++ b/shortcuts/drive/drive_import_test.go @@ -732,3 +732,33 @@ func TestDriveImportFallbackURLWhenServerTypeIsAlias(t *testing.T) { t.Fatalf("data.url = %#v, want %q (alias normalized via spec.DocType fallback)", got, want) } } + +func TestDriveImportFallbackURLForSlides(t *testing.T) { + f, stdout, _, reg := cmdutil.TestFactory(t, driveImportTestConfig("slides")) + driveImportMockEnv(t, reg, "ticket_slides", map[string]interface{}{ + "token": "sldcn_imported", + "type": "slides", + "job_status": float64(0), + }) + + tmpDir := t.TempDir() + origDir, _ := os.Getwd() + if err := os.Chdir(tmpDir); err != nil { + t.Fatalf("Chdir: %v", err) + } + defer os.Chdir(origDir) + if err := os.WriteFile("deck.pptx", []byte("fake-pptx"), 0o644); err != nil { + t.Fatalf("WriteFile: %v", err) + } + + if err := mountAndRunDrive(t, DriveImport, []string{ + "+import", "--file", "deck.pptx", "--type", "slides", "--as", "user", + }, f, stdout); err != nil { + t.Fatalf("import should succeed, got: %v", err) + } + + data := decodeDriveEnvelope(t, stdout) + if got, want := data["url"], "https://www.feishu.cn/slides/sldcn_imported"; got != want { + t.Fatalf("data.url = %#v, want %q (slides fallback)", got, want) + } +} diff --git a/skills/lark-drive/SKILL.md b/skills/lark-drive/SKILL.md index bad95340..0ab6a463 100644 --- a/skills/lark-drive/SKILL.md +++ b/skills/lark-drive/SKILL.md @@ -1,7 +1,7 @@ --- name: lark-drive version: 1.0.0 -description: "飞书云空间(云盘/云存储):管理云空间(云盘/云存储)中的文件和文件夹。上传和下载文件、创建文件夹、复制/移动/删除文件、查看文件元数据、管理文档评论、管理文档权限、订阅用户评论变更事件、修改文件标题(docx、sheet、bitable、file、folder、wiki);也负责把本地 Word/Markdown/Excel/CSV 以及 Base 快照(.base)导入为飞书在线云文档(docx、sheet、bitable)。当用户需要上传或下载文件、整理云空间(云盘/云存储)目录、查看文件详情、管理评论、管理文档权限、修改文件标题、订阅用户评论变更事件,或要把本地文件导入成新版文档、电子表格、多维表格/Base 时使用。\"云空间\"、\"云盘\"和\"云存储\"是同一概念,用户说\"云盘\"、\"云存储\"、\"网盘\"、\"我的空间\"时均路由到本 skill。" +description: "飞书云空间(云盘/云存储):管理云空间(云盘/云存储)中的文件和文件夹。上传和下载文件、创建文件夹、复制/移动/删除文件、查看文件元数据、管理文档评论、管理文档权限、订阅用户评论变更事件、修改文件标题(docx、sheet、bitable、file、folder、wiki);也负责把本地 Word/Markdown/Excel/CSV/PPTX 以及 Base 快照(.base)导入为飞书在线云文档(docx、sheet、bitable、slides)。当用户需要上传或下载文件、整理云空间(云盘/云存储)目录、查看文件详情、管理评论、管理文档权限、修改文件标题、订阅用户评论变更事件,或要把本地文件导入成新版文档、电子表格、多维表格/Base/幻灯片 时使用。\"云空间\"、\"云盘\"和\"云存储\"是同一概念,用户说\"云盘\"、\"云存储\"、\"网盘\"、\"我的空间\"时均路由到本 skill。" metadata: requires: bins: ["lark-cli"] @@ -21,6 +21,7 @@ metadata: - 用户要**搜文档 / Wiki / 电子表格 / 多维表格 / 云空间(云盘/云存储)对象**,优先使用 `lark-cli drive +search`。自然语言里"最近我编辑过的"、"我创建的"(→ `--mine`,实为 owner 语义)、"最近一周我打开过的 xxx"、"某人 owner 的 docx" 等直接映射到扁平 flag,避免手写嵌套 JSON。 - 用户要把本地 `.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。 - 用户要在 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`。 - 用户要查看、下载、回滚或删除文件的**历史版本**,使用 `drive +version-history`、`drive +version-get`、`drive +version-revert`、`drive +version-delete`;这组命令同时支持 `--as user` 和 `--as bot`,自动化场景优先 `--as bot`。 @@ -271,7 +272,7 @@ Shortcut 是对常用操作的高级封装(`lark-cli drive + [flags]`) | [`+add-comment`](references/lark-drive-add-comment.md) | Add a comment to doc/docx/file/sheet/slides, also supports wiki URL resolving to doc/docx/file/sheet/slides; file targets support selected extensions and full comments only | | [`+export`](references/lark-drive-export.md) | Export a doc/docx/sheet/bitable/slides to a local file with limited polling; supports `--file-name` for local naming | | [`+export-download`](references/lark-drive-export-download.md) | Download an exported file by file_token | -| [`+import`](references/lark-drive-import.md) | Import a local file to Drive as a cloud document (docx, sheet, bitable) | +| [`+import`](references/lark-drive-import.md) | Import a local file to Drive as a cloud document (docx, sheet, bitable, slides) | | [`+version-history`](references/lark-drive-version-history.md) | List historical versions of a file with only_tag=true and cursor-based pagination | | [`+version-get`](references/lark-drive-version-get.md) | Download a specific historical version of a file | | [`+version-revert`](references/lark-drive-version-revert.md) | Revert a file to a specific historical version | diff --git a/skills/lark-drive/references/lark-drive-import.md b/skills/lark-drive/references/lark-drive-import.md index 99573eb4..9b5d35aa 100644 --- a/skills/lark-drive/references/lark-drive-import.md +++ b/skills/lark-drive/references/lark-drive-import.md @@ -2,7 +2,7 @@ > **前置条件:** 先阅读 [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) 了解认证、全局参数和安全规则。 -将本地文件(如 Word、TXT、Markdown、Excel 等)导入并转换为飞书在线云文档(docx、sheet、bitable)。底层统一通过 `POST /open-apis/drive/v1/import_tasks` 接口创建导入任务,并在 shortcut 内做有限次数轮询 `GET /open-apis/drive/v1/import_tasks/:ticket`。 +将本地文件(如 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`。 > [!IMPORTANT] > 当用户说“把本地 Excel / CSV / `.base` 快照导入成 Base / 多维表格 / bitable 文档”时,第一步必须使用 `drive +import --type bitable`。 @@ -40,6 +40,9 @@ lark-cli drive +import --file ./crm.xlsx --type bitable --name "客户台账" # 导入 .base 快照为多维表格 / Base (bitable)(文件不能超过 20MB) lark-cli drive +import --file ./snapshot.base --type bitable --name "快照还原" +# 导入 PPTX 为飞书幻灯片 (slides)(文件不能超过 500MB) +lark-cli drive +import --file ./deck.pptx --type slides --name "项目汇报" + # 导入到指定文件夹,并指定导入后的文件名 lark-cli drive +import --file ./data.csv --type bitable --folder-token --name "导入数据表" @@ -55,7 +58,7 @@ lark-cli drive +import --file ./README.md --type docx --dry-run | 参数 | 必填 | 说明 | |------|------|------| | `--file` | 是 | 本地文件路径,根据文件后缀名自动推断 `file_extension`;文件需满足对应格式的导入大小限制,超过 20MB 且仍在允许范围内时会自动切换分片上传 | -| `--type` | 是 | 导入目标云文档格式。可选值:`docx` (新版文档)、`sheet` (电子表格)、`bitable` (多维表格) | +| `--type` | 是 | 导入目标云文档格式。可选值:`docx` (新版文档)、`sheet` (电子表格)、`bitable` (多维表格)、`slides` (飞书幻灯片) | | `--folder-token` | 否 | 目标文件夹 token,不传则请求中的 `point.mount_key` 为空字符串,Import API 会将其解释为导入到云空间(云盘/云存储)根目录 | | `--name` | 否 | 导入后的在线云文档名称,不传默认使用本地文件名去掉扩展名后的结果 | | `--target-token` | 否 | 已有的多维表格 token,将数据导入到该多维表格中(**仅支持 `--type bitable`**);传入后数据会挂载到目标多维表格而非新建一个 | @@ -85,6 +88,7 @@ lark-cli drive +import --file ./README.md --type docx --dry-run | `.xls` | `sheet` | Microsoft Excel 97-2003 表格 | | `.csv` | `sheet`, `bitable` | CSV 数据文件 | | `.base` | `bitable` | 多维表格快照文件 | +| `.pptx` | `slides` | Microsoft PowerPoint 演示文稿 | > [!IMPORTANT] > 用户口头说的 “Base” / “多维表格” / “bitable”,在命令里统一对应 `--type bitable`。 @@ -94,6 +98,7 @@ lark-cli drive +import --file ./README.md --type docx --dry-run > - `.xlsx` / `.csv` 文件**只能**导入为 `sheet` 或 `bitable` > - `.xls` 文件**只能**导入为 `sheet` > - `.base` 文件**只能**导入为 `bitable` +> - `.pptx` 文件**只能**导入为 `slides` > - 例如:`.csv` 文件不能导入为 `docx`,`.md` 文件不能导入为 `sheet` > [!IMPORTANT] @@ -127,6 +132,7 @@ lark-cli drive +import --file ./README.md --type docx --dry-run | `.csv` | `bitable` | 100MB | | `.xls` | `sheet` | 20MB | | `.base` | `bitable` | 20MB | +| `.pptx` | `slides` | 500MB | - 如果文件超出对应上限,shortcut 会在真正上传前直接返回验证错误。 - “超过 20MB 自动切换分片上传”只表示上传链路会切到 multipart,不代表所有格式都允许导入超过 20MB 的文件。