Skip to content
Open
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
94 changes: 94 additions & 0 deletions shortcuts/mail/mail_signature_create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT

package mail

import (
"context"
"fmt"
"strings"

"github.com/larksuite/cli/shortcuts/common"
)

var MailSignatureCreate = common.Shortcut{
Service: "mail",
Command: "+signature-create",
Description: "Create a personal (USER) mail signature. Scans HTML <img src> local paths, uploads inline images to Drive, rewrites them to cid: references, and POSTs to settings/signatures.",
Risk: "write",
Scopes: []string{"mail:user_mailbox.message:modify", "mail:user_mailbox:readonly"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: []common.Flag{
{Name: "mailbox", Desc: "Mailbox email address that owns the signature (default: me)."},
{Name: "name", Desc: "Required. Signature name.", Required: true},
{Name: "content", Desc: "Signature body. Prefer HTML; local <img src> values are auto-uploaded and rewritten to cid: refs."},
{Name: "content-file", Desc: "Path to a file used as --content. Relative path only. Mutually exclusive with --content."},
{Name: "device", Desc: "PC (default) or MOBILE."},
},
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
mailboxID := resolveComposeMailboxID(runtime)
content, _, rcErr := resolveSignatureContent(runtime)
if rcErr != nil {
fmt.Fprintf(runtime.IO().ErrOut, "warning: dry-run could not load signature content: %v\n", rcErr)
}
api := common.NewDryRunAPI().
Desc("Create a personal mail signature. The command scans HTML for local <img src> references, uploads each inline image to Drive, rewrites <img src> values to cid: references, and POSTs a USER signature payload.")
for _, img := range parseLocalImgs(content) {
addTemplateUploadSteps(runtime, api, img.Path)
}
device, _ := signatureDeviceFromRuntime(runtime)
return api.POST(signatureMailboxPath(mailboxID)).
Body(map[string]interface{}{
"signature": map[string]interface{}{
"name": runtime.Str("name"),
"content": "<rewritten-HTML>",
"signature_type": "USER",
"signature_device": string(device),
"images": "<computed from uploads>",
},
})
},
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if err := validateBotMailboxNotMe(runtime); err != nil {
return err
}
if strings.TrimSpace(runtime.Str("name")) == "" {
return mailValidationParamError("--name", "--name is required")
}
if runtime.Str("content") != "" && runtime.Str("content-file") != "" {
return mailValidationError("--content and --content-file are mutually exclusive").
WithParams(
mailInvalidParam("--content", "mutually exclusive with --content-file"),
mailInvalidParam("--content-file", "mutually exclusive with --content"),
)
}
_, err := signatureDeviceFromRuntime(runtime)
return err
},
Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
mailboxID := resolveComposeMailboxID(runtime)
content, _, err := resolveSignatureContent(runtime)
if err != nil {
return err
}
device, err := signatureDeviceFromRuntime(runtime)
if err != nil {
return err
}
payload, err := buildSignaturePayloadFromFlags(ctx, runtime, runtime.Str("name"), content, device)
if err != nil {
return err
}
resp, err := createSignature(runtime, mailboxID, payload)
if err != nil {
return decorateSignatureWriteError(err, "create signature failed")
}
sig, err := extractSignaturePayload(resp)
if err != nil {
return err
}
outputSignatureResult(runtime, "Signature created.", sig)
return nil
},
}
58 changes: 58 additions & 0 deletions shortcuts/mail/mail_signature_delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT

package mail

import (
"context"
"io"
"strings"

"github.com/larksuite/cli/shortcuts/common"
)

var MailSignatureDelete = common.Shortcut{
Service: "mail",
Command: "+signature-delete",
Description: "Delete a personal (USER) mail signature by ID.",
Risk: "write",
Scopes: []string{"mail:user_mailbox.message:modify", "mail:user_mailbox:readonly"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: []common.Flag{
{Name: "mailbox", Desc: "Mailbox email address that owns the signature (default: me)."},
{Name: "signature-id", Desc: "Signature ID to delete.", Required: true},
},
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
mailboxID := resolveComposeMailboxID(runtime)
return common.NewDryRunAPI().
Desc("Delete a personal mail signature.").
DELETE(signatureMailboxPath(mailboxID, runtime.Str("signature-id")))
},
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if err := validateBotMailboxNotMe(runtime); err != nil {
return err
}
if strings.TrimSpace(runtime.Str("signature-id")) == "" {
return mailValidationParamError("--signature-id", "--signature-id is required")
}
return nil
},
Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
mailboxID := resolveComposeMailboxID(runtime)
signatureID := runtime.Str("signature-id")
if err := deleteSignature(runtime, mailboxID, signatureID); err != nil {
return decorateSignatureWriteError(err, "delete signature failed")
}
out := map[string]interface{}{
"deleted": true,
"mailbox_id": mailboxID,
"signature_id": signatureID,
}
runtime.OutFormat(out, nil, func(w io.Writer) {
w.Write([]byte("Signature deleted.\n"))
w.Write([]byte("signature_id: " + signatureID + "\n"))
})
return nil
},
}
103 changes: 103 additions & 0 deletions shortcuts/mail/mail_signature_update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT

package mail

import (
"context"
"fmt"
"strings"

"github.com/larksuite/cli/shortcuts/common"
)

var MailSignatureUpdate = common.Shortcut{
Service: "mail",
Command: "+signature-update",
Description: "Update an existing personal (USER) mail signature with full-replace semantics. Omitted fields are cleared; local HTML images are uploaded and rewritten to cid: references before PUT.",
Risk: "write",
Scopes: []string{"mail:user_mailbox.message:modify", "mail:user_mailbox:readonly"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: []common.Flag{
{Name: "mailbox", Desc: "Mailbox email address that owns the signature (default: me)."},
{Name: "signature-id", Desc: "Signature ID to update.", Required: true},
{Name: "name", Desc: "Required. Replacement signature name.", Required: true},
{Name: "content", Desc: "Replacement signature body. Prefer HTML; local <img src> values are auto-uploaded and rewritten to cid: refs."},
{Name: "content-file", Desc: "Path to a file used as --content. Relative path only. Mutually exclusive with --content."},
{Name: "device", Desc: "PC (default) or MOBILE."},
},
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
mailboxID := resolveComposeMailboxID(runtime)
signatureID := runtime.Str("signature-id")
content, _, rcErr := resolveSignatureContent(runtime)
if rcErr != nil {
fmt.Fprintf(runtime.IO().ErrOut, "warning: dry-run could not load signature content: %v\n", rcErr)
}
api := common.NewDryRunAPI().
Desc("Update a personal mail signature with full-replace semantics. Omitted fields are cleared. The command uploads local inline images to Drive, rewrites HTML to cid: references, and PUTs a USER signature payload.")
for _, img := range parseLocalImgs(content) {
addTemplateUploadSteps(runtime, api, img.Path)
}
device, _ := signatureDeviceFromRuntime(runtime)
return api.PUT(signatureMailboxPath(mailboxID, signatureID)).
Body(map[string]interface{}{
"signature": map[string]interface{}{
"id": signatureID,
"name": runtime.Str("name"),
"content": "<rewritten-HTML-or-empty>",
"signature_type": "USER",
"signature_device": string(device),
"images": "<computed from uploads>",
},
"_warning": "Full replace: omitted fields are cleared.",
})
},
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if err := validateBotMailboxNotMe(runtime); err != nil {
return err
}
if strings.TrimSpace(runtime.Str("signature-id")) == "" {
return mailValidationParamError("--signature-id", "--signature-id is required")
}
if strings.TrimSpace(runtime.Str("name")) == "" {
return mailValidationParamError("--name", "--name is required")
}
if runtime.Str("content") != "" && runtime.Str("content-file") != "" {
return mailValidationError("--content and --content-file are mutually exclusive").
WithParams(
mailInvalidParam("--content", "mutually exclusive with --content-file"),
mailInvalidParam("--content-file", "mutually exclusive with --content"),
)
}
_, err := signatureDeviceFromRuntime(runtime)
return err
},
Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
mailboxID := resolveComposeMailboxID(runtime)
signatureID := runtime.Str("signature-id")
content, _, err := resolveSignatureContent(runtime)
if err != nil {
return err
}
device, err := signatureDeviceFromRuntime(runtime)
if err != nil {
return err
}
payload, err := buildSignaturePayloadFromFlags(ctx, runtime, runtime.Str("name"), content, device)
if err != nil {
return err
}
fmt.Fprintln(runtime.IO().ErrOut, "warning: signature update is full-replace; omitted fields are cleared.")
resp, err := updateSignature(runtime, mailboxID, signatureID, payload)
if err != nil {
return decorateSignatureWriteError(err, "update signature failed")
}
sig, err := extractSignaturePayload(resp)
if err != nil {
return err
}
outputSignatureResult(runtime, "Signature updated.", sig)
return nil
},
}
Loading
Loading