slides

package
v1.0.13 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 16, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var SlidesCreate = common.Shortcut{
	Service:     "slides",
	Command:     "+create",
	Description: "Create a Lark Slides presentation",
	Risk:        "write",
	AuthTypes:   []string{"user", "bot"},

	Scopes: []string{"slides:presentation:create", "slides:presentation:write_only", "docs:document.media:upload"},
	Flags: []common.Flag{
		{Name: "title", Desc: "presentation title"},
		{Name: "slides", Desc: "slide content JSON array (each element is a <slide> XML string, max 10; for more pages, create first then add via xml_presentation.slide.create). <img src=\"@./local.png\"> placeholders are auto-uploaded and replaced with file_token."},
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		if slidesStr := runtime.Str("slides"); slidesStr != "" {
			var slides []string
			if err := json.Unmarshal([]byte(slidesStr), &slides); err != nil {
				return common.FlagErrorf("--slides invalid JSON, must be an array of XML strings")
			}
			if len(slides) > maxSlidesPerCreate {
				return common.FlagErrorf("--slides array exceeds maximum of %d slides; create the presentation first, then add slides via xml_presentation.slide.create", maxSlidesPerCreate)
			}

			for _, path := range extractImagePlaceholderPaths(slides) {
				stat, err := runtime.FileIO().Stat(path)
				if err != nil {
					return common.WrapInputStatError(err, fmt.Sprintf("--slides @%s: file not found", path))
				}
				if !stat.Mode().IsRegular() {
					return common.FlagErrorf("--slides @%s: must be a regular file", path)
				}
				if stat.Size() > common.MaxDriveMediaUploadSinglePartSize {
					return common.FlagErrorf("--slides @%s: file size %s exceeds 20 MB limit for slides image upload",
						path, common.FormatSize(stat.Size()))
				}
			}
		}
		return nil
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		title := effectiveTitle(runtime.Str("title"))
		slidesStr := runtime.Str("slides")
		createBody := map[string]interface{}{
			"xml_presentation": map[string]interface{}{"content": buildPresentationXML(title)},
		}

		dry := common.NewDryRunAPI()

		if slidesStr == "" {
			dry.Desc("Create empty presentation").
				POST("/open-apis/slides_ai/v1/xml_presentations").
				Body(createBody)
		} else {
			var slides []string
			_ = json.Unmarshal([]byte(slidesStr), &slides)
			n := len(slides)
			placeholders := extractImagePlaceholderPaths(slides)
			total := n + 1 + len(placeholders)

			descSuffix := ""
			if len(placeholders) > 0 {
				descSuffix = fmt.Sprintf(" + upload %d image(s)", len(placeholders))
			}
			dry.Desc(fmt.Sprintf("Create presentation%s + add %d slide(s)", descSuffix, n)).
				POST("/open-apis/slides_ai/v1/xml_presentations").
				Desc(fmt.Sprintf("[1/%d] Create presentation", total)).
				Body(createBody)

			for i, path := range placeholders {
				appendSlidesUploadDryRun(dry, path, "<xml_presentation_id>", i+2)
			}

			slideStepStart := 2 + len(placeholders)
			slideDescSuffix := ""
			if len(placeholders) > 0 {
				slideDescSuffix = " (img placeholders auto-replaced)"
			}
			for i, slideXML := range slides {
				dry.POST("/open-apis/slides_ai/v1/xml_presentations/<xml_presentation_id>/slide").
					Desc(fmt.Sprintf("[%d/%d] Add slide %d%s", slideStepStart+i, total, i+1, slideDescSuffix)).
					Body(map[string]interface{}{
						"slide": map[string]interface{}{"content": slideXML},
					})
			}
		}

		if runtime.IsBot() {
			dry.Desc("After creation succeeds in bot mode, the CLI will also try to grant the current CLI user full_access (可管理权限) on the new presentation.")
		}
		return dry
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		title := effectiveTitle(runtime.Str("title"))
		content := buildPresentationXML(title)
		slidesStr := runtime.Str("slides")

		data, err := runtime.CallAPI(
			"POST",
			"/open-apis/slides_ai/v1/xml_presentations",
			nil,
			map[string]interface{}{
				"xml_presentation": map[string]interface{}{
					"content": content,
				},
			},
		)
		if err != nil {
			return err
		}

		presentationID := common.GetString(data, "xml_presentation_id")
		if presentationID == "" {
			return output.Errorf(output.ExitAPI, "api_error", "slides create returned no xml_presentation_id")
		}

		result := map[string]interface{}{
			"xml_presentation_id": presentationID,
			"title":               title,
		}
		if revisionID := common.GetFloat(data, "revision_id"); revisionID > 0 {
			result["revision_id"] = int(revisionID)
		}

		if slidesStr != "" {
			var slides []string
			_ = json.Unmarshal([]byte(slidesStr), &slides)

			if len(slides) > 0 {

				placeholders := extractImagePlaceholderPaths(slides)
				if len(placeholders) > 0 {
					tokens, uploaded, err := uploadSlidesPlaceholders(runtime, presentationID, placeholders)
					if err != nil {
						return output.Errorf(output.ExitAPI, "api_error",
							"image upload failed: %v (presentation %s was created; %d image(s) uploaded before failure)",
							err, presentationID, uploaded)
					}
					for i := range slides {
						slides[i] = replaceImagePlaceholders(slides[i], tokens)
					}
					result["images_uploaded"] = uploaded
				}

				slideURL := fmt.Sprintf(
					"/open-apis/slides_ai/v1/xml_presentations/%s/slide",
					validate.EncodePathSegment(presentationID),
				)

				var slideIDs []string
				for i, slideXML := range slides {
					slideData, err := runtime.CallAPI(
						"POST",
						slideURL,
						map[string]interface{}{"revision_id": -1},
						map[string]interface{}{
							"slide": map[string]interface{}{"content": slideXML},
						},
					)
					if err != nil {
						return output.Errorf(output.ExitAPI, "api_error",
							"slide %d/%d failed: %v (presentation %s was created; %d slide(s) added before failure)",
							i+1, len(slides), err, presentationID, i)
					}
					if sid := common.GetString(slideData, "slide_id"); sid != "" {
						slideIDs = append(slideIDs, sid)
					}
				}

				result["slide_ids"] = slideIDs
				result["slides_added"] = len(slideIDs)
			}
		}

		if metaData, err := runtime.CallAPI(
			"POST",
			"/open-apis/drive/v1/metas/batch_query",
			nil,
			map[string]interface{}{
				"request_docs": []map[string]interface{}{
					{
						"doc_token": presentationID,
						"doc_type":  "slides",
					},
				},
				"with_url": true,
			},
		); err == nil {
			metas := common.GetSlice(metaData, "metas")
			if len(metas) > 0 {
				if meta, ok := metas[0].(map[string]interface{}); ok {
					if url := common.GetString(meta, "url"); url != "" {
						result["url"] = url
					}
				}
			}
		}

		if grant := common.AutoGrantCurrentUserDrivePermission(runtime, presentationID, "slides"); grant != nil {
			result["permission_grant"] = grant
		}

		runtime.Out(result, nil)
		return nil
	},
}

SlidesCreate creates a new Lark Slides presentation with bot auto-grant.

View Source
var SlidesMediaUpload = common.Shortcut{
	Service:     "slides",
	Command:     "+media-upload",
	Description: "Upload a local image to a slides presentation and return the file_token (use as <img src=...>)",
	Risk:        "write",

	Scopes:    []string{"docs:document.media:upload", "wiki:node:read"},
	AuthTypes: []string{"user", "bot"},
	Flags: []common.Flag{
		{Name: "file", Desc: "local image path (max 20 MB)", Required: true},
		{Name: "presentation", Desc: "xml_presentation_id, slides URL, or wiki URL that resolves to slides", Required: true},
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		if _, err := parsePresentationRef(runtime.Str("presentation")); err != nil {
			return err
		}
		return nil
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		filePath := runtime.Str("file")
		ref, err := parsePresentationRef(runtime.Str("presentation"))
		if err != nil {
			return common.NewDryRunAPI().Set("error", err.Error())
		}

		dry := common.NewDryRunAPI()
		parentNode := ref.Token
		stepBase := 1
		if ref.Kind == "wiki" {
			parentNode = "<resolved_slides_token>"
			stepBase = 2
			dry.Desc("2-step orchestration: resolve wiki → upload media").
				GET("/open-apis/wiki/v2/spaces/get_node").
				Desc("[1] Resolve wiki node to slides presentation").
				Params(map[string]interface{}{"token": ref.Token})
		} else {
			dry.Desc("Upload local file to slides presentation")
		}
		appendSlidesUploadDryRun(dry, filePath, parentNode, stepBase)
		return dry.Set("presentation_id", ref.Token)
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		filePath := runtime.Str("file")
		ref, err := parsePresentationRef(runtime.Str("presentation"))
		if err != nil {
			return err
		}
		presentationID, err := resolvePresentationID(runtime, ref)
		if err != nil {
			return err
		}

		stat, err := runtime.FileIO().Stat(filePath)
		if err != nil {
			return common.WrapInputStatError(err, "file not found")
		}
		if !stat.Mode().IsRegular() {
			return output.ErrValidation("file must be a regular file: %s", filePath)
		}

		if stat.Size() > common.MaxDriveMediaUploadSinglePartSize {
			return output.ErrValidation("file %s is %s, exceeds 20 MB limit for slides image upload",
				filepath.Base(filePath), common.FormatSize(stat.Size()))
		}

		fileName := filepath.Base(filePath)
		fmt.Fprintf(runtime.IO().ErrOut, "Uploading: %s (%s) -> presentation %s\n",
			fileName, common.FormatSize(stat.Size()), common.MaskToken(presentationID))

		fileToken, err := uploadSlidesMedia(runtime, filePath, fileName, stat.Size(), presentationID)
		if err != nil {
			return err
		}

		runtime.Out(map[string]interface{}{
			"file_token":      fileToken,
			"file_name":       fileName,
			"size":            stat.Size(),
			"presentation_id": presentationID,
		}, nil)
		return nil
	},
}

SlidesMediaUpload uploads a local image to drive media against a slides presentation and returns the file_token. The token can be used as the value of <img src="..."> in slide XML.

This is the atomic building block for getting a local image into a slides deck. Higher-level shortcuts (e.g. +create with @path placeholders) reuse the same upload helpers.

Functions

func Shortcuts

func Shortcuts() []common.Shortcut

Shortcuts returns all slides shortcuts.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL