Files
larksuite-cli/shortcuts/doc/doc_media_preview.go
wittam-01 99b8aaa556 feat: improve doc media extension inference (#364)
Change-Id: Ifc7c0e7844908b88e2d527e0933d080b140a50eb
2026-04-09 21:11:47 +08:00

105 lines
3.4 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package doc
import (
"context"
"fmt"
"net/http"
larkcore "github.com/larksuite/oapi-sdk-go/v3/core"
"github.com/larksuite/cli/extension/fileio"
"github.com/larksuite/cli/internal/output"
"github.com/larksuite/cli/internal/validate"
"github.com/larksuite/cli/shortcuts/common"
)
const PreviewType_SOURCE_FILE = "16"
var DocMediaPreview = common.Shortcut{
Service: "docs",
Command: "+media-preview",
Description: "Preview document media file (auto-detects extension)",
Risk: "read",
Scopes: []string{"docs:document.media:download"},
AuthTypes: []string{"user", "bot"},
Flags: []common.Flag{
{Name: "token", Desc: "media file token", Required: true},
{Name: "output", Desc: "local save path", Required: true},
{Name: "overwrite", Type: "bool", Desc: "overwrite existing output file"},
},
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
token := runtime.Str("token")
outputPath := runtime.Str("output")
return common.NewDryRunAPI().
GET("/open-apis/drive/v1/medias/:token/preview_download").
Desc("Preview document media file").
Params(map[string]interface{}{"preview_type": PreviewType_SOURCE_FILE}).
Set("token", token).Set("output", outputPath)
},
Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
token := runtime.Str("token")
outputPath := runtime.Str("output")
overwrite := runtime.Bool("overwrite")
if err := validate.ResourceName(token, "--token"); err != nil {
return output.ErrValidation("%s", err)
}
// Early path validation before API call (final validation after auto-extension below)
if _, err := runtime.ResolveSavePath(outputPath); err != nil {
return output.ErrValidation("unsafe output path: %s", err)
}
fmt.Fprintf(runtime.IO().ErrOut, "Previewing: media %s\n", common.MaskToken(token))
encodedToken := validate.EncodePathSegment(token)
apiPath := fmt.Sprintf("/open-apis/drive/v1/medias/%s/preview_download", encodedToken)
resp, err := runtime.DoAPIStream(ctx, &larkcore.ApiReq{
HttpMethod: http.MethodGet,
ApiPath: apiPath,
QueryParams: larkcore.QueryParams{
"preview_type": []string{PreviewType_SOURCE_FILE},
},
})
if err != nil {
return output.ErrNetwork("preview failed: %v", err)
}
defer resp.Body.Close()
finalPath, _ := autoAppendDocMediaExtension(outputPath, resp.Header, "")
// Validate final path after extension append
if finalPath != outputPath {
if _, err := runtime.ResolveSavePath(finalPath); err != nil {
return output.ErrValidation("unsafe output path: %s", err)
}
}
// Overwrite check on final path (after extension detection)
if !overwrite {
if _, statErr := runtime.FileIO().Stat(finalPath); statErr == nil {
return output.ErrValidation("output file already exists: %s (use --overwrite to replace)", finalPath)
}
}
result, err := runtime.FileIO().Save(finalPath, fileio.SaveOptions{
ContentType: resp.Header.Get("Content-Type"),
ContentLength: resp.ContentLength,
}, resp.Body)
if err != nil {
return common.WrapSaveErrorByCategory(err, "io")
}
savedPath, _ := runtime.ResolveSavePath(finalPath)
runtime.Out(map[string]interface{}{
"saved_path": savedPath,
"size_bytes": result.Size(),
"content_type": resp.Header.Get("Content-Type"),
}, nil)
return nil
},
}