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
165 changes: 154 additions & 11 deletions shortcuts/drive/drive_add_comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@
var DriveAddComment = common.Shortcut{
Service: "drive",
Command: "+add-comment",
Description: "Add a comment to doc/docx/file/sheet/slides; file targets support selected extensions and full comments only",
Description: "Add a comment to doc/docx/file/sheet/slides/base(bitable); file targets support selected extensions and full comments only",
Risk: "write",
Scopes: []string{
"drive:drive.metadata:readonly",
Expand All @@ -131,12 +131,12 @@
},
AuthTypes: []string{"user", "bot"},
Flags: []common.Flag{
{Name: "doc", Desc: "document URL/token, file URL/token, sheet/slides URL, or wiki URL that resolves to doc/docx/file/sheet/slides", Required: true},
{Name: "type", Desc: "document type: doc, docx, file, sheet, slides (required when --doc is a bare token; auto-detected for URLs)", Enum: []string{"doc", "docx", "file", "sheet", "slides"}},
{Name: "doc", Desc: "document URL/token, file URL/token, sheet/slides/base/bitable URL, or wiki URL that resolves to doc/docx/file/sheet/slides/base(bitable)", Required: true},
{Name: "type", Desc: "document type: doc, docx, file, sheet, slides, bitable, base (required when --doc is a bare token; auto-detected for URLs; use bitable as the wire value, base is accepted as a compatibility alias)", Enum: []string{"doc", "docx", "file", "sheet", "slides", "bitable", "base"}},
{Name: "content", Desc: "reply_elements JSON string", Required: true},
{Name: "full-comment", Type: "bool", Desc: "create a full-document comment; also the default when no location is provided"},
{Name: "selection-with-ellipsis", Desc: "target content locator (plain text or 'start...end')"},
{Name: "block-id", Desc: "for docx: anchor block ID; for sheet: <sheetId>!<cell> (e.g. a281f9!D6); for slides: <slide-block-type>!<xml-id> (e.g. shape!bPq)"},
{Name: "block-id", Desc: "for docx: anchor block ID; for sheet: <sheetId>!<cell>; for slides: <slide-block-type>!<xml-id>; for base(bitable): <table-id>!<record-id>!<view-id>"},
},
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
docRef, err := parseCommentDocRef(runtime.Str("doc"), runtime.Str("type"))
Expand All @@ -148,6 +148,17 @@
return err
}

if docRef.Kind == "base" {
if runtime.Bool("full-comment") {
return output.ErrValidation("--full-comment is not applicable for base(bitable) comments; use --block-id <table-id>!<record-id>!<view-id>")
}
if strings.TrimSpace(runtime.Str("selection-with-ellipsis")) != "" {
return output.ErrValidation("--selection-with-ellipsis is not applicable for base(bitable) comments; use --block-id <table-id>!<record-id>!<view-id>")
}
_, err := parseBaseCommentAnchor(runtime)
return err
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Sheet comment validation.
if docRef.Kind == "sheet" {
blockID := strings.TrimSpace(runtime.Str("block-id"))
Expand Down Expand Up @@ -188,7 +199,7 @@
return validateFileCommentMode(mode, "")
}
if mode == commentModeLocal && docRef.Kind == "doc" {
return output.ErrValidation("local comments only support docx, sheet, and slides; old doc format only supports full comments")
return output.ErrValidation("local comments only support docx, sheet, slides, and base(bitable); old doc format only supports full comments")
}

return nil
Expand All @@ -215,6 +226,23 @@
resolvedToken = target.FileToken
}

if resolvedKind == "base" {
anchor, err := parseBaseCommentAnchor(runtime)
if err != nil {
return common.NewDryRunAPI().Set("error", err.Error())

Check warning on line 232 in shortcuts/drive/drive_add_comment.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/drive/drive_add_comment.go#L232

Added line #L232 was not covered by tests
}
commentBody := buildBaseCommentCreateV2Request(replyElements, anchor)
desc := "1-step request: create base(bitable) record-local comment"
if isWiki {
desc = "2-step orchestration: resolve wiki -> create base(bitable) record-local comment"

Check warning on line 237 in shortcuts/drive/drive_add_comment.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/drive/drive_add_comment.go#L237

Added line #L237 was not covered by tests
}
return common.NewDryRunAPI().
Desc(desc).
POST("/open-apis/drive/v1/files/:file_token/new_comments").
Body(commentBody).
Set("file_token", resolvedToken)
}

// Sheet comment dry-run.
if resolvedKind == "sheet" {
anchor, _ := parseSheetCellRef(blockID)
Expand Down Expand Up @@ -352,6 +380,14 @@
Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
// Sheet comment: direct URL or token fast path.
docRef, _ := parseCommentDocRef(runtime.Str("doc"), runtime.Str("type"))
if docRef.Kind == "base" {
return executeBaseComment(runtime, resolvedCommentTarget{
DocID: docRef.Token,
FileToken: docRef.Token,
FileType: "base",
ResolvedBy: "base",
})
}
if docRef.Kind == "sheet" {
return executeSheetComment(runtime, docRef)
}
Expand All @@ -375,6 +411,9 @@
if target.FileType == "slides" {
return executeSlidesComment(runtime, commentDocRef{Kind: "slides", Token: target.FileToken})
}
if target.FileType == "base" {
return executeBaseComment(runtime, target)
}
if target.FileType == "file" {
return executeFileComment(runtime, target)
}
Expand Down Expand Up @@ -482,6 +521,12 @@
if token, ok := extractURLToken(raw, "/sheets/"); ok {
return commentDocRef{Kind: "sheet", Token: token}, nil
}
if token, ok := extractURLToken(raw, "/base/"); ok {
return commentDocRef{Kind: "base", Token: token}, nil
}
if token, ok := extractURLToken(raw, "/bitable/"); ok {
return commentDocRef{Kind: "base", Token: token}, nil
}
if token, ok := extractURLToken(raw, "/file/"); ok {
return commentDocRef{Kind: "file", Token: token}, nil
}
Expand All @@ -495,7 +540,7 @@
return commentDocRef{Kind: "doc", Token: token}, nil
}
if strings.Contains(raw, "://") {
return commentDocRef{}, output.ErrValidation("unsupported --doc input %q: use a doc/docx/file/sheet/slides URL, a token with --type, or a wiki URL that resolves to doc/docx/file/sheet/slides", raw)
return commentDocRef{}, output.ErrValidation("unsupported --doc input %q: use a doc/docx/file/sheet/slides/base/bitable URL, a token with --type, or a wiki URL that resolves to doc/docx/file/sheet/slides/base(bitable)", raw)
}
if strings.ContainsAny(raw, "/?#") {
return commentDocRef{}, output.ErrValidation("unsupported --doc input %q: use a token with --type, or a wiki URL", raw)
Expand All @@ -504,7 +549,10 @@
// Bare token: --type is required.
docType = strings.TrimSpace(docType)
if docType == "" {
return commentDocRef{}, output.ErrValidation("--type is required when --doc is a bare token (allowed values: doc, docx, file, sheet, slides)")
return commentDocRef{}, output.ErrValidation("--type is required when --doc is a bare token (allowed values: doc, docx, file, sheet, slides, bitable, base; use bitable as the wire value, base is accepted as a compatibility alias)")
}
if docType == "bitable" || docType == "base" {
return commentDocRef{Kind: "base", Token: raw}, nil
}
return commentDocRef{Kind: docType, Token: raw}, nil
}
Expand All @@ -515,11 +563,11 @@
return resolvedCommentTarget{}, err
}

if docRef.Kind == "docx" || docRef.Kind == "doc" || docRef.Kind == "file" || docRef.Kind == "sheet" || docRef.Kind == "slides" {
if docRef.Kind == "docx" || docRef.Kind == "doc" || docRef.Kind == "file" || docRef.Kind == "sheet" || docRef.Kind == "slides" || docRef.Kind == "base" {
if mode == commentModeLocal {
switch docRef.Kind {
case "doc":
return resolvedCommentTarget{}, output.ErrValidation("local comments only support docx, sheet, and slides; old doc format only supports full comments")
return resolvedCommentTarget{}, output.ErrValidation("local comments only support docx, sheet, slides, and base(bitable); old doc format only supports full comments")

Check warning on line 570 in shortcuts/drive/drive_add_comment.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/drive/drive_add_comment.go#L570

Added line #L570 was not covered by tests
case "file":
if err := validateFileCommentMode(mode, ""); err != nil {
return resolvedCommentTarget{}, err
Expand Down Expand Up @@ -557,6 +605,16 @@
if objType == "slides" && strings.TrimSpace(runtime.Str("selection-with-ellipsis")) != "" {
return resolvedCommentTarget{}, output.ErrValidation("wiki resolved to %q, but --selection-with-ellipsis is not applicable for slide comments; use --block-id <slide-block-type>!<xml-id>", objType)
}
if objType == "bitable" || objType == "base" {
fmt.Fprintf(runtime.IO().ErrOut, "Resolved wiki to base: %s\n", common.MaskToken(objToken))
return resolvedCommentTarget{
DocID: objToken,
FileToken: objToken,
FileType: "base",
ResolvedBy: "wiki",
WikiToken: docRef.Token,
}, nil
}
if objType == "sheet" {
// Sheet comments are handled via the sheet fast path in Execute.
fmt.Fprintf(runtime.IO().ErrOut, "Resolved wiki to %s: %s\n", objType, common.MaskToken(objToken))
Expand Down Expand Up @@ -592,10 +650,10 @@
}, nil
}
if mode == commentModeLocal && objType != "docx" {
return resolvedCommentTarget{}, output.ErrValidation("wiki resolved to %q, but local comments only support docx, sheet, and slides; for sheet use --block-id <sheetId>!<cell>, for slides use --block-id <slide-block-type>!<xml-id>", objType)
return resolvedCommentTarget{}, output.ErrValidation("wiki resolved to %q, but local comments only support docx, sheet, slides, and base(bitable); for sheet use --block-id <sheetId>!<cell>, for slides use --block-id <slide-block-type>!<xml-id>, for base use --block-id <table-id>!<record-id>!<view-id>", objType)

Check warning on line 653 in shortcuts/drive/drive_add_comment.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/drive/drive_add_comment.go#L653

Added line #L653 was not covered by tests
}
if mode == commentModeFull && objType != "docx" && objType != "doc" {
return resolvedCommentTarget{}, output.ErrValidation("wiki resolved to %q, but comments only support doc/docx/file/sheet/slides", objType)
return resolvedCommentTarget{}, output.ErrValidation("wiki resolved to %q, but comments only support doc/docx/file/sheet/slides/base(bitable)", objType)

Check warning on line 656 in shortcuts/drive/drive_add_comment.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/drive/drive_add_comment.go#L656

Added line #L656 was not covered by tests
}

fmt.Fprintf(runtime.IO().ErrOut, "Resolved wiki to %s: %s\n", objType, common.MaskToken(objToken))
Expand Down Expand Up @@ -791,6 +849,12 @@
Row int
}

type baseAnchor struct {
BlockID string
BaseRecordID string
BaseViewID string
}

func buildCommentCreateV2Request(fileType, blockID, slideBlockType string, replyElements []map[string]interface{}, sheet *sheetAnchor) map[string]interface{} {
body := map[string]interface{}{
"file_type": fileType,
Expand All @@ -817,13 +881,45 @@
return body
}

func buildBaseCommentCreateV2Request(replyElements []map[string]interface{}, anchor baseAnchor) map[string]interface{} {
return map[string]interface{}{
"file_type": "bitable",
"reply_elements": replyElements,
"anchor": map[string]interface{}{
"block_id": anchor.BlockID,
"base_record_id": anchor.BaseRecordID,
"base_view_id": anchor.BaseViewID,
},
}
}

func anchorBlockIDForDryRun(blockID string) string {
if strings.TrimSpace(blockID) != "" {
return strings.TrimSpace(blockID)
}
return "<anchor_block_id>"
}

func parseBaseCommentAnchor(runtime *common.RuntimeContext) (baseAnchor, error) {
blockID := strings.TrimSpace(runtime.Str("block-id"))
if blockID == "" {
return baseAnchor{}, output.ErrValidation("--block-id is required for base(bitable) record-local comments (format: <table-id>!<record-id>!<view-id>, e.g. tbl9mp6fj9kDKHQV!recBIBgGmb!vewc46MG1R)")
}
return parseBaseBlockRef(blockID)
}

func parseBaseBlockRef(blockID string) (baseAnchor, error) {
parts := strings.Split(strings.TrimSpace(blockID), "!")
if len(parts) != 3 || strings.TrimSpace(parts[0]) == "" || strings.TrimSpace(parts[1]) == "" || strings.TrimSpace(parts[2]) == "" {
return baseAnchor{}, output.ErrValidation("base(bitable) record-local comments require --block-id in <table-id>!<record-id>!<view-id> format, e.g. tbl9mp6fj9kDKHQV!recBIBgGmb!vewc46MG1R")
}
return baseAnchor{
BlockID: strings.TrimSpace(parts[0]),
BaseRecordID: strings.TrimSpace(parts[1]),
BaseViewID: strings.TrimSpace(parts[2]),
}, nil
}

func parseSlidesBlockRef(blockID string) (string, string, error) {
blockID = strings.TrimSpace(blockID)
if blockID == "" {
Expand Down Expand Up @@ -1038,6 +1134,53 @@
return nil
}

func executeBaseComment(runtime *common.RuntimeContext, target resolvedCommentTarget) error {
replyElements, err := parseCommentReplyElements(runtime.Str("content"))
if err != nil {
return err

Check warning on line 1140 in shortcuts/drive/drive_add_comment.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/drive/drive_add_comment.go#L1140

Added line #L1140 was not covered by tests
}
anchor, err := parseBaseCommentAnchor(runtime)
if err != nil {
return err

Check warning on line 1144 in shortcuts/drive/drive_add_comment.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/drive/drive_add_comment.go#L1144

Added line #L1144 was not covered by tests
}

requestPath := fmt.Sprintf("/open-apis/drive/v1/files/%s/new_comments", validate.EncodePathSegment(target.FileToken))
requestBody := buildBaseCommentCreateV2Request(replyElements, anchor)

fmt.Fprintf(runtime.IO().ErrOut, "Creating base(bitable) record-local comment in %s (table=%s, record=%s, view=%s)\n",
common.MaskToken(target.FileToken), anchor.BlockID, anchor.BaseRecordID, anchor.BaseViewID)

data, err := runtime.CallAPI("POST", requestPath, nil, requestBody)
if err != nil {
return err

Check warning on line 1155 in shortcuts/drive/drive_add_comment.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/drive/drive_add_comment.go#L1155

Added line #L1155 was not covered by tests
}

out := map[string]interface{}{
"file_token": target.FileToken,
"file_type": "bitable",
"resolved_by": target.ResolvedBy,
"comment_mode": "base_record",
"base_block_id": anchor.BlockID,
"base_record_id": anchor.BaseRecordID,
"base_view_id": anchor.BaseViewID,
}
if commentID := data["comment_id"]; commentID != nil {
out["comment_id"] = commentID
}
if replyID := data["reply_id"]; replyID != nil {
out["reply_id"] = replyID
}
if createdAt := firstPresentValue(data, "created_at", "create_time"); createdAt != nil {
out["created_at"] = createdAt
}
if target.WikiToken != "" {
out["wiki_token"] = target.WikiToken
}

runtime.Out(out, nil)
return nil
}

func executeFileComment(runtime *common.RuntimeContext, target resolvedCommentTarget) error {
replyElements, err := parseCommentReplyElements(runtime.Str("content"))
if err != nil {
Expand Down
Loading
Loading