Documentation
¶
Index ¶
Constants ¶
View Source
const ( // ErrCodeTaskInvalidParams is returned when request parameters are invalid. ErrCodeTaskInvalidParams = 1470400 // ErrCodeTaskPermissionDenied is returned when the user has no permission. ErrCodeTaskPermissionDenied = 1470403 // ErrCodeTaskNotFound is returned when the resource is not found. ErrCodeTaskNotFound = 1470404 // ErrCodeTaskConflict is returned when concurrent call conflict. ErrCodeTaskConflict = 1470422 // ErrCodeTaskInternalError is returned when server error occurs. ErrCodeTaskInternalError = 1470500 // ErrCodeTaskAssigneeLimit is returned when assignee limit exceeded. ErrCodeTaskAssigneeLimit = 1470610 // ErrCodeTaskFollowerLimit is returned when follower limit exceeded. ErrCodeTaskFollowerLimit = 1470611 // ErrCodeTasklistMemberLimit is returned when tasklist member limit exceeded. ErrCodeTasklistMemberLimit = 1470612 // ErrCodeTaskReminderExists is returned when reminder already exists. ErrCodeTaskReminderExists = 1470613 )
Variables ¶
View Source
var AddTaskToTasklist = common.Shortcut{ Service: "task", Command: "+tasklist-task-add", Description: "add tasks to a tasklist", Risk: "write", Scopes: []string{"task:task:write"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: []common.Flag{ {Name: "tasklist-id", Desc: "tasklist id", Required: true}, {Name: "task-id", Desc: "task id (comma-separated for multiple)", Required: true}, }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { taskIds := strings.Split(runtime.Str("task-id"), ",") taskId := url.PathEscape(strings.TrimSpace(taskIds[0])) body := map[string]interface{}{ "tasklist_guid": extractTasklistGuid(runtime.Str("tasklist-id")), } return common.NewDryRunAPI(). POST("/open-apis/task/v2/tasks/" + taskId + "/add_tasklist"). Params(map[string]interface{}{"user_id_type": "open_id"}). Body(body) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { tasklistGuid := extractTasklistGuid(runtime.Str("tasklist-id")) taskIds := strings.Split(runtime.Str("task-id"), ",") queryParams := make(larkcore.QueryParams) queryParams.Set("user_id_type", "open_id") body := map[string]interface{}{ "tasklist_guid": tasklistGuid, } var successful []map[string]interface{} var failed []map[string]interface{} for _, taskId := range taskIds { taskId = strings.TrimSpace(taskId) if taskId == "" { continue } apiResp, err := runtime.DoAPI(&larkcore.ApiReq{ HttpMethod: http.MethodPost, ApiPath: "/open-apis/task/v2/tasks/" + url.PathEscape(taskId) + "/add_tasklist", QueryParams: queryParams, Body: body, }) var result map[string]interface{} if err == nil { if parseErr := json.Unmarshal(apiResp.RawBody, &result); parseErr != nil { err = WrapTaskError(ErrCodeTaskInternalError, fmt.Sprintf("failed to parse response: %v", parseErr), "parse add task response") } } data, err := HandleTaskApiResult(result, err, "add task to tasklist") if err != nil { failDetail := map[string]interface{}{ "guid": taskId, } if exitErr, ok := err.(*output.ExitError); ok && exitErr.Detail != nil { failDetail["type"] = exitErr.Detail.Type failDetail["code"] = exitErr.Detail.Code failDetail["message"] = exitErr.Detail.Message failDetail["hint"] = exitErr.Detail.Hint } else { failDetail["type"] = "api_error" failDetail["message"] = err.Error() } failed = append(failed, failDetail) } else { task, _ := data["task"].(map[string]interface{}) guid, _ := task["guid"].(string) taskUrl, _ := task["url"].(string) taskUrl = truncateTaskURL(taskUrl) successful = append(successful, map[string]interface{}{ "guid": guid, "url": taskUrl, }) } } resultData := map[string]interface{}{ "successful_tasks": successful, "failed_tasks": failed, "tasklist_guid": tasklistGuid, } runtime.OutFormat(resultData, nil, func(w io.Writer) { fmt.Fprintf(w, "✅ Tasks added to tasklist %s!\n", tasklistGuid) fmt.Fprintf(w, "Successful: %d, Failed: %d\n", len(successful), len(failed)) if len(successful) > 0 { fmt.Fprintln(w, "Successful Tasks:") for _, t := range successful { guid, _ := t["guid"].(string) taskUrl, _ := t["url"].(string) fmt.Fprintf(w, " - ID: %s", guid) if taskUrl != "" { fmt.Fprintf(w, ", URL: %s", taskUrl) } fmt.Fprintln(w) } } if len(failed) > 0 { fmt.Fprintln(w, "Failed Tasks:") for _, f := range failed { fmt.Fprintf(w, " - %s: %s\n", f["guid"], f["message"]) } } }) return nil }, }
View Source
var AssignTask = common.Shortcut{ Service: "task", Command: "+assign", Description: "assign or remove task members", Risk: "write", Scopes: []string{"task:task:write"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: []common.Flag{ {Name: "task-id", Desc: "task id", Required: true}, {Name: "add", Desc: "comma-separated open_ids to add as assignees"}, {Name: "remove", Desc: "comma-separated open_ids to remove from assignees"}, {Name: "idempotency-key", Desc: "client token for idempotency (used for add_members)"}, }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { if runtime.Str("add") == "" && runtime.Str("remove") == "" { return WrapTaskError(ErrCodeTaskInvalidParams, "must specify either --add or --remove", "validate assign") } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { d := common.NewDryRunAPI() taskId := url.PathEscape(runtime.Str("task-id")) if addStr := runtime.Str("add"); addStr != "" { body := buildMembersBody(addStr, runtime.Str("idempotency-key")) d.POST("/open-apis/task/v2/tasks/" + taskId + "/add_members"). Params(map[string]interface{}{"user_id_type": "open_id"}). Body(body) } if removeStr := runtime.Str("remove"); removeStr != "" { body := buildMembersBody(removeStr, "") d.POST("/open-apis/task/v2/tasks/" + taskId + "/remove_members"). Params(map[string]interface{}{"user_id_type": "open_id"}). Body(body) } return d }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { taskId := url.PathEscape(runtime.Str("task-id")) queryParams := make(larkcore.QueryParams) queryParams.Set("user_id_type", "open_id") var lastData map[string]interface{} if addStr := runtime.Str("add"); addStr != "" { body := buildMembersBody(addStr, runtime.Str("idempotency-key")) apiResp, err := runtime.DoAPI(&larkcore.ApiReq{ HttpMethod: http.MethodPost, ApiPath: "/open-apis/task/v2/tasks/" + taskId + "/add_members", QueryParams: queryParams, Body: body, }) var result map[string]interface{} if err == nil { if parseErr := json.Unmarshal(apiResp.RawBody, &result); parseErr != nil { return WrapTaskError(ErrCodeTaskInternalError, fmt.Sprintf("failed to parse response: %v", parseErr), "parse add members") } } data, err := HandleTaskApiResult(result, err, "add task members") if err != nil { return err } lastData = data } if removeStr := runtime.Str("remove"); removeStr != "" { body := buildMembersBody(removeStr, "") apiResp, err := runtime.DoAPI(&larkcore.ApiReq{ HttpMethod: http.MethodPost, ApiPath: "/open-apis/task/v2/tasks/" + taskId + "/remove_members", QueryParams: queryParams, Body: body, }) var result map[string]interface{} if err == nil { if parseErr := json.Unmarshal(apiResp.RawBody, &result); parseErr != nil { return WrapTaskError(ErrCodeTaskInternalError, fmt.Sprintf("failed to parse response: %v", parseErr), "parse remove members") } } data, err := HandleTaskApiResult(result, err, "remove task members") if err != nil { return err } lastData = data } task, _ := lastData["task"].(map[string]interface{}) urlVal, _ := task["url"].(string) urlVal = truncateTaskURL(urlVal) outData := map[string]interface{}{ "guid": taskId, "url": urlVal, } runtime.OutFormat(outData, nil, func(w io.Writer) { fmt.Fprintf(w, "✅ Task assignees updated successfully!\n") fmt.Fprintf(w, "Task ID: %s\n", taskId) if urlVal != "" { fmt.Fprintf(w, "Task URL: %s\n", urlVal) } if members, ok := task["members"].([]interface{}); ok { fmt.Fprintf(w, "Current Assignees: %d\n", len(members)) } }) return nil }, }
View Source
var CommentTask = common.Shortcut{ Service: "task", Command: "+comment", Description: "add a comment to a task", Risk: "write", Scopes: []string{"task:comment:write"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: []common.Flag{ {Name: "task-id", Desc: "task id", Required: true}, {Name: "content", Desc: "comment content", Required: true}, }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { body := map[string]interface{}{ "content": runtime.Str("content"), "resource_id": runtime.Str("task-id"), "resource_type": "task", } return common.NewDryRunAPI(). POST("/open-apis/task/v2/comments"). Params(map[string]interface{}{"user_id_type": "open_id"}). Body(body) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { body := map[string]interface{}{ "content": runtime.Str("content"), "resource_id": runtime.Str("task-id"), "resource_type": "task", } queryParams := make(larkcore.QueryParams) queryParams.Set("user_id_type", "open_id") apiResp, err := runtime.DoAPI(&larkcore.ApiReq{ HttpMethod: http.MethodPost, ApiPath: "/open-apis/task/v2/comments", QueryParams: queryParams, Body: body, }) var result map[string]interface{} if err == nil { if parseErr := json.Unmarshal(apiResp.RawBody, &result); parseErr != nil { return WrapTaskError(ErrCodeTaskInternalError, fmt.Sprintf("failed to parse response: %v", parseErr), "parse comment response") } } data, err := HandleTaskApiResult(result, err, "add task comment") if err != nil { return err } comment, _ := data["comment"].(map[string]interface{}) id, _ := comment["id"].(string) outData := map[string]interface{}{ "id": id, } runtime.OutFormat(outData, nil, func(w io.Writer) { fmt.Fprintf(w, "✅ Comment added successfully!\n") if id != "" { fmt.Fprintf(w, "Comment ID: %s\n", id) } }) return nil }, }
View Source
var CompleteTask = common.Shortcut{ Service: "task", Command: "+complete", Description: "mark a task as complete", Risk: "write", Scopes: []string{"task:task:write"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: []common.Flag{ {Name: "task-id", Desc: "task id", Required: true}, }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { body := buildCompleteBody() taskId := url.PathEscape(runtime.Str("task-id")) return common.NewDryRunAPI(). PATCH("/open-apis/task/v2/tasks/" + taskId). Params(map[string]interface{}{"user_id_type": "open_id"}). Body(body) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { taskId := url.PathEscape(runtime.Str("task-id")) body := buildCompleteBody() queryParams := make(larkcore.QueryParams) queryParams.Set("user_id_type", "open_id") apiResp, err := runtime.DoAPI(&larkcore.ApiReq{ HttpMethod: http.MethodPatch, ApiPath: "/open-apis/task/v2/tasks/" + taskId, QueryParams: queryParams, Body: body, }) var result map[string]interface{} if err == nil { if parseErr := json.Unmarshal(apiResp.RawBody, &result); parseErr != nil { return WrapTaskError(ErrCodeTaskInternalError, fmt.Sprintf("failed to parse response: %v", parseErr), "parse complete response") } } data, err := HandleTaskApiResult(result, err, "complete task") if err != nil { return err } task, _ := data["task"].(map[string]interface{}) guid, _ := task["guid"].(string) urlVal, _ := task["url"].(string) urlVal = truncateTaskURL(urlVal) outData := map[string]interface{}{ "guid": guid, "url": urlVal, } runtime.OutFormat(outData, nil, func(w io.Writer) { summary, _ := task["summary"].(string) fmt.Fprintf(w, "✅ Task completed successfully!\n") if guid != "" { fmt.Fprintf(w, "Task ID: %s\n", guid) } if summary != "" { fmt.Fprintf(w, "Summary: %s\n", summary) } if urlVal != "" { fmt.Fprintf(w, "Task URL: %s\n", urlVal) } }) return nil }, }
View Source
var CreateTask = common.Shortcut{ Service: "task", Command: "+create", Description: "create a task", Risk: "write", Scopes: []string{"task:task:write"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: []common.Flag{ {Name: "summary", Desc: "task title"}, {Name: "description", Desc: "task description"}, {Name: "assignee", Desc: "assignee open_id"}, {Name: "due", Desc: "due date (ISO 8601 / date:YYYY-MM-DD / relative:+2d / ms timestamp)"}, {Name: "tasklist-id", Desc: "tasklist id or applink URL"}, {Name: "idempotency-key", Desc: "client token for idempotency"}, {Name: "data", Desc: "JSON payload for creating task"}, }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { body, err := buildTaskCreateBody(runtime) if err != nil { return common.NewDryRunAPI().Set("error", err.Error()) } return common.NewDryRunAPI(). POST("/open-apis/task/v2/tasks"). Params(map[string]interface{}{"user_id_type": "open_id"}). Body(body) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { body, err := buildTaskCreateBody(runtime) if err != nil { return WrapTaskError(ErrCodeTaskInvalidParams, err.Error(), "create task") } queryParams := make(larkcore.QueryParams) queryParams.Set("user_id_type", "open_id") apiResp, err := runtime.DoAPI(&larkcore.ApiReq{ HttpMethod: http.MethodPost, ApiPath: "/open-apis/task/v2/tasks", QueryParams: queryParams, Body: body, }) var result map[string]interface{} if err == nil { if parseErr := json.Unmarshal(apiResp.RawBody, &result); parseErr != nil { return fmt.Errorf("failed to parse response: %v", parseErr) } } data, err := HandleTaskApiResult(result, err, "create task") if err != nil { return err } task, _ := data["task"].(map[string]interface{}) guid, _ := task["guid"].(string) urlVal, _ := task["url"].(string) urlVal = truncateTaskURL(urlVal) outData := map[string]interface{}{ "guid": guid, "url": urlVal, } runtime.OutFormat(outData, nil, func(w io.Writer) { fmt.Fprintf(w, "✅ Task created successfully!\n") fmt.Fprintf(w, "Summary: %s\n", body["summary"]) if guid != "" { fmt.Fprintf(w, "Task ID: %s\n", guid) } if urlVal != "" { fmt.Fprintf(w, "Task URL: %s\n", urlVal) } }) return nil }, }
View Source
var CreateTasklist = common.Shortcut{ Service: "task", Command: "+tasklist-create", Description: "create a tasklist and optionally add tasks", Risk: "write", Scopes: []string{"task:tasklist:write", "task:task:write"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: []common.Flag{ {Name: "name", Desc: "tasklist name", Required: true}, {Name: "member", Desc: "comma-separated open_ids to add as editors"}, {Name: "data", Desc: "JSON array of tasks to create within this tasklist"}, }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { body := buildTasklistCreateBody(runtime) d := common.NewDryRunAPI(). Desc("1. Create Tasklist"). POST("/open-apis/task/v2/tasklists"). Params(map[string]interface{}{"user_id_type": "open_id"}). Body(body) if dataStr := runtime.Str("data"); dataStr != "" { d.Desc("2. Create Tasks within the new tasklist (concurrently)") } return d }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { body := buildTasklistCreateBody(runtime) queryParams := make(larkcore.QueryParams) queryParams.Set("user_id_type", "open_id") apiResp, err := runtime.DoAPI(&larkcore.ApiReq{ HttpMethod: http.MethodPost, ApiPath: "/open-apis/task/v2/tasklists", QueryParams: queryParams, Body: body, }) var result map[string]interface{} if err == nil { if parseErr := json.Unmarshal(apiResp.RawBody, &result); parseErr != nil { return WrapTaskError(ErrCodeTaskInternalError, fmt.Sprintf("failed to parse response: %v", parseErr), "parse create tasklist") } } data, err := HandleTaskApiResult(result, err, "create tasklist") if err != nil { return err } tasklist, _ := data["tasklist"].(map[string]interface{}) tasklistGuid, _ := tasklist["guid"].(string) tasklistName, _ := tasklist["name"].(string) tasklistUrl, _ := tasklist["url"].(string) tasklistUrl = truncateTaskURL(tasklistUrl) // Create tasks if data is provided var tasks []map[string]interface{} var createdTasks []map[string]interface{} var failedTasks []string if dataStr := runtime.Str("data"); dataStr != "" { if err := json.Unmarshal([]byte(dataStr), &tasks); err != nil { return WrapTaskError(ErrCodeTaskInvalidParams, fmt.Sprintf("failed to parse --data as JSON array: %v", err), "parse data") } var wg sync.WaitGroup var mu sync.Mutex for i, taskDef := range tasks { wg.Add(1) go func(idx int, tDef map[string]interface{}) { defer func() { if r := recover(); r != nil { fmt.Fprintf(runtime.IO().ErrOut, "recovered in defer: %v\n", r) } wg.Done() }() tDef["tasklists"] = []map[string]interface{}{ { "tasklist_guid": tasklistGuid, }, } if assignee, ok := tDef["assignee"].(string); ok { tDef["members"] = []map[string]interface{}{ { "id": assignee, "role": "assignee", "type": "user", }, } delete(tDef, "assignee") } tResp, tErr := runtime.DoAPI(&larkcore.ApiReq{ HttpMethod: http.MethodPost, ApiPath: "/open-apis/task/v2/tasks", QueryParams: queryParams, Body: tDef, }) mu.Lock() defer mu.Unlock() var tResult map[string]interface{} if tErr == nil { if json.Unmarshal(tResp.RawBody, &tResult) != nil { tErr = WrapTaskError(ErrCodeTaskInternalError, "failed to parse task response", "parse task") } } tData, tErr := HandleTaskApiResult(tResult, tErr, "create task in tasklist") if tErr != nil { summary, _ := tDef["summary"].(string) failedTasks = append(failedTasks, fmt.Sprintf("Index %d (%s): %v", idx, summary, tErr)) return } if t, ok := tData["task"].(map[string]interface{}); ok { guid, _ := t["guid"].(string) urlVal, _ := t["url"].(string) urlVal = truncateTaskURL(urlVal) createdTasks = append(createdTasks, map[string]interface{}{ "guid": guid, "url": urlVal, }) } }(i, taskDef) } wg.Wait() } outData := map[string]interface{}{ "guid": tasklistGuid, "url": tasklistUrl, "created_tasks": createdTasks, } runtime.OutFormat(outData, nil, func(w io.Writer) { fmt.Fprintf(w, "✅ Tasklist created successfully!\n") fmt.Fprintf(w, "Tasklist Name: %s\n", tasklistName) fmt.Fprintf(w, "Tasklist ID: %s\n", tasklistGuid) if tasklistUrl != "" { fmt.Fprintf(w, "Tasklist URL: %s\n", tasklistUrl) } if len(tasks) > 0 { fmt.Fprintln(w, strings.Repeat("-", 20)) fmt.Fprintf(w, "Tasks created: %d/%d\n", len(createdTasks), len(tasks)) for _, t := range createdTasks { guid, _ := t["guid"].(string) urlVal, _ := t["url"].(string) fmt.Fprintf(w, " - ID: %s", guid) if urlVal != "" { fmt.Fprintf(w, ", URL: %s", urlVal) } fmt.Fprintln(w) } if len(failedTasks) > 0 { fmt.Fprintf(w, "\nFailed tasks:\n") for _, f := range failedTasks { fmt.Fprintf(w, " - %s\n", f) } } } }) return nil }, }
View Source
var FollowersTask = common.Shortcut{ Service: "task", Command: "+followers", Description: "manage task followers", Risk: "write", Scopes: []string{"task:task:write"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: []common.Flag{ {Name: "task-id", Desc: "task id", Required: true}, {Name: "add", Desc: "comma-separated open_ids to add as followers"}, {Name: "remove", Desc: "comma-separated open_ids to remove from followers"}, {Name: "idempotency-key", Desc: "client token for idempotency (used for add_members)"}, }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { if runtime.Str("add") == "" && runtime.Str("remove") == "" { return WrapTaskError(ErrCodeTaskInvalidParams, "must specify either --add or --remove", "validate followers") } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { d := common.NewDryRunAPI() taskId := url.PathEscape(runtime.Str("task-id")) if addStr := runtime.Str("add"); addStr != "" { body := buildFollowersBody(addStr, runtime.Str("idempotency-key")) d.POST("/open-apis/task/v2/tasks/" + taskId + "/add_members"). Params(map[string]interface{}{"user_id_type": "open_id"}). Body(body) } if removeStr := runtime.Str("remove"); removeStr != "" { body := buildFollowersBody(removeStr, "") d.POST("/open-apis/task/v2/tasks/" + taskId + "/remove_members"). Params(map[string]interface{}{"user_id_type": "open_id"}). Body(body) } return d }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { taskId := url.PathEscape(runtime.Str("task-id")) queryParams := make(larkcore.QueryParams) queryParams.Set("user_id_type", "open_id") var lastData map[string]interface{} if addStr := runtime.Str("add"); addStr != "" { body := buildFollowersBody(addStr, runtime.Str("idempotency-key")) apiResp, err := runtime.DoAPI(&larkcore.ApiReq{ HttpMethod: http.MethodPost, ApiPath: "/open-apis/task/v2/tasks/" + taskId + "/add_members", QueryParams: queryParams, Body: body, }) var result map[string]interface{} if err == nil { if parseErr := json.Unmarshal(apiResp.RawBody, &result); parseErr != nil { return WrapTaskError(ErrCodeTaskInternalError, fmt.Sprintf("failed to parse response: %v", parseErr), "parse add followers") } } data, err := HandleTaskApiResult(result, err, "add task followers") if err != nil { return err } lastData = data } if removeStr := runtime.Str("remove"); removeStr != "" { body := buildFollowersBody(removeStr, "") apiResp, err := runtime.DoAPI(&larkcore.ApiReq{ HttpMethod: http.MethodPost, ApiPath: "/open-apis/task/v2/tasks/" + taskId + "/remove_members", QueryParams: queryParams, Body: body, }) var result map[string]interface{} if err == nil { if parseErr := json.Unmarshal(apiResp.RawBody, &result); parseErr != nil { return WrapTaskError(ErrCodeTaskInternalError, fmt.Sprintf("failed to parse response: %v", parseErr), "parse remove followers") } } data, err := HandleTaskApiResult(result, err, "remove task followers") if err != nil { return err } lastData = data } task, _ := lastData["task"].(map[string]interface{}) urlVal, _ := task["url"].(string) urlVal = truncateTaskURL(urlVal) outData := map[string]interface{}{ "guid": taskId, "url": urlVal, } runtime.OutFormat(outData, nil, func(w io.Writer) { fmt.Fprintf(w, "✅ Task followers updated successfully!\n") fmt.Fprintf(w, "Task ID: %s\n", taskId) if urlVal != "" { fmt.Fprintf(w, "Task URL: %s\n", urlVal) } }) return nil }, }
View Source
var GetMyTasks = common.Shortcut{ Service: "task", Command: "+get-my-tasks", Description: "List tasks assigned to me", Risk: "read", Scopes: []string{"task:task:read"}, AuthTypes: []string{"user"}, HasFormat: true, Flags: []common.Flag{ {Name: "query", Desc: "search for tasks by summary (exact match first, then partial match)"}, {Name: "complete", Type: "bool", Desc: "if true, query completed tasks; default is false"}, {Name: "created_at", Desc: "query tasks created after this time (date/relative/ms)"}, {Name: "due-start", Desc: "query tasks with due date after this time (date/relative/ms)"}, {Name: "due-end", Desc: "query tasks with due date before this time (date/relative/ms)"}, {Name: "page-all", Type: "bool", Desc: "automatically paginate through all pages (max 40)"}, {Name: "page-limit", Type: "int", Default: "20", Desc: "max page limit (default 20, max 40 with --page-all)"}, }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { d := common.NewDryRunAPI() params := map[string]interface{}{ "type": "my_tasks", "user_id_type": "open_id", "page_size": 50, } if runtime.Cmd.Flags().Changed("complete") { params["completed"] = runtime.Bool("complete") } return d.GET("/open-apis/task/v2/tasks").Params(params) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { startTime := time.Now() queryParams := make(larkcore.QueryParams) queryParams.Set("type", "my_tasks") queryParams.Set("user_id_type", "open_id") queryParams.Set("page_size", "50") if runtime.Cmd.Flags().Changed("complete") { if runtime.Bool("complete") { queryParams.Set("completed", "true") } else { queryParams.Set("completed", "false") } } // parse time flags to ms timestamp if provided var createdAfterMs, dueStartMs, dueEndMs int64 if createdStr := runtime.Str("created_at"); createdStr != "" { tStr, err := parseTimeFlagSec(createdStr, "start") if err != nil { return WrapTaskError(ErrCodeTaskInvalidParams, fmt.Sprintf("invalid created_at: %v", err), "parse created_at") } createdAfterMs, _ = strconv.ParseInt(tStr, 10, 64) createdAfterMs *= 1000 } if dueStartStr := runtime.Str("due-start"); dueStartStr != "" { tStr, err := parseTimeFlagSec(dueStartStr, "start") if err != nil { return WrapTaskError(ErrCodeTaskInvalidParams, fmt.Sprintf("invalid due-start: %v", err), "parse due-start") } dueStartMs, _ = strconv.ParseInt(tStr, 10, 64) dueStartMs *= 1000 } if dueEndStr := runtime.Str("due-end"); dueEndStr != "" { tStr, err := parseTimeFlagSec(dueEndStr, "end") if err != nil { return WrapTaskError(ErrCodeTaskInvalidParams, fmt.Sprintf("invalid due-end: %v", err), "parse due-end") } dueEndMs, _ = strconv.ParseInt(tStr, 10, 64) dueEndMs *= 1000 } var allItems []interface{} var lastPageToken string var lastHasMore bool pageCount := 0 pageLimit := runtime.Int("page-limit") if runtime.Bool("page-all") { pageLimit = 40 } for { pageCount++ apiReq := &larkcore.ApiReq{ HttpMethod: "GET", ApiPath: "/open-apis/task/v2/tasks", QueryParams: queryParams, } apiResp, err := runtime.DoAPI(apiReq) var result map[string]interface{} if err == nil { if parseErr := json.Unmarshal(apiResp.RawBody, &result); parseErr != nil { return WrapTaskError(ErrCodeTaskInternalError, fmt.Sprintf("failed to parse response: %v", parseErr), "parse my tasks") } } data, err := HandleTaskApiResult(result, err, "list tasks") if err != nil { return err } itemsRaw, _ := data["items"].([]interface{}) allItems = append(allItems, itemsRaw...) hasMore, _ := data["has_more"].(bool) lastHasMore = hasMore lastPageToken, _ = data["page_token"].(string) if !hasMore || lastPageToken == "" { break } if pageCount >= pageLimit { break } queryParams.Set("page_token", lastPageToken) } var filteredItems []map[string]interface{} for _, itemRaw := range allItems { item, ok := itemRaw.(map[string]interface{}) if !ok { continue } if createdAfterMs > 0 { createdAtStr, _ := item["created_at"].(string) createdAtMs, _ := strconv.ParseInt(createdAtStr, 10, 64) if createdAtMs < createdAfterMs { continue } } if dueStartMs > 0 || dueEndMs > 0 { dueObj, _ := item["due"].(map[string]interface{}) if dueObj == nil { continue } dueTimeStr, _ := dueObj["timestamp"].(string) dueTimeMs, _ := strconv.ParseInt(dueTimeStr, 10, 64) if dueStartMs > 0 && dueTimeMs < dueStartMs { continue } if dueEndMs > 0 && dueTimeMs > dueEndMs { continue } } filteredItems = append(filteredItems, item) } if query := runtime.Str("query"); query != "" { var exactMatches []map[string]interface{} var partialMatches []map[string]interface{} for _, item := range filteredItems { summary, _ := item["summary"].(string) if summary == query { exactMatches = append(exactMatches, item) } else if strings.Contains(summary, query) { partialMatches = append(partialMatches, item) } } if len(exactMatches) > 0 { filteredItems = exactMatches } else { filteredItems = partialMatches } } var outputItems []interface{} for _, item := range filteredItems { urlVal, _ := item["url"].(string) urlVal = truncateTaskURL(urlVal) outputItem := map[string]interface{}{ "guid": item["guid"], "summary": item["summary"], "url": urlVal, } if createdAtStr, ok := item["created_at"].(string); ok { if ts, err := strconv.ParseInt(createdAtStr, 10, 64); err == nil { outputItem["created_at"] = time.UnixMilli(ts).UTC().Format(time.RFC3339) } } if dueObj, ok := item["due"].(map[string]interface{}); ok { if tsStr, ok := dueObj["timestamp"].(string); ok { if ts, err := strconv.ParseInt(tsStr, 10, 64); err == nil { outputItem["due_at"] = time.UnixMilli(ts).UTC().Format(time.RFC3339) } } } outputItems = append(outputItems, outputItem) } outData := map[string]interface{}{ "items": outputItems, "page_token": lastPageToken, "has_more": lastHasMore, } runtime.OutFormat(outData, nil, func(w io.Writer) { if len(filteredItems) == 0 { fmt.Fprintln(w, "No tasks found.") return } for i, item := range filteredItems { guid, _ := item["guid"].(string) summary, _ := item["summary"].(string) urlVal, _ := item["url"].(string) urlVal = truncateTaskURL(urlVal) var dueTimeStr string if dueObj, ok := item["due"].(map[string]interface{}); ok { if tsStr, ok := dueObj["timestamp"].(string); ok { if ts, err := strconv.ParseInt(tsStr, 10, 64); err == nil { dueTimeStr = time.UnixMilli(ts).Format("2006-01-02 15:04") } } } var createdDateStr string if createdStr, ok := item["created_at"].(string); ok { if ts, err := strconv.ParseInt(createdStr, 10, 64); err == nil { createdDateStr = time.UnixMilli(ts).Format("2006-01-02") } } fmt.Fprintf(w, "[%d] %s\n", i+1, summary) fmt.Fprintf(w, " ID: %s\n", guid) if urlVal != "" { fmt.Fprintf(w, " URL: %s\n", urlVal) } if dueTimeStr != "" { fmt.Fprintf(w, " Due: %s\n", dueTimeStr) } if createdDateStr != "" { fmt.Fprintf(w, " Created: %s\n", createdDateStr) } fmt.Fprintln(w) } if lastHasMore && lastPageToken != "" && !runtime.Cmd.Flags().Changed("page-limit") && !runtime.Cmd.Flags().Changed("page-all") { fmt.Fprintf(w, "\n[Warning] Too many tasks! Stopped after fetching %d pages.\n", pageLimit) } fmt.Fprintf(w, "\nTotal execution time: %v\n", time.Since(startTime)) }) return nil }, }
View Source
var MembersTasklist = common.Shortcut{ Service: "task", Command: "+tasklist-members", Description: "manage tasklist members", Risk: "write", Scopes: []string{"task:tasklist:write"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: []common.Flag{ {Name: "tasklist-id", Desc: "tasklist id", Required: true}, {Name: "set", Desc: "comma-separated open_ids to set as exact members (replaces existing)"}, {Name: "add", Desc: "comma-separated open_ids to add as members"}, {Name: "remove", Desc: "comma-separated open_ids to remove from members"}, }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { hasSet := runtime.Str("set") != "" hasAdd := runtime.Str("add") != "" hasRemove := runtime.Str("remove") != "" if hasSet && (hasAdd || hasRemove) { return WrapTaskError(ErrCodeTaskInvalidParams, "cannot combine --set with --add or --remove", "validate tasklist members") } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { d := common.NewDryRunAPI() tlId := url.PathEscape(extractTasklistGuid(runtime.Str("tasklist-id"))) if runtime.Str("set") != "" || (runtime.Str("add") == "" && runtime.Str("remove") == "") { d.Desc("GET tasklist details/members"). GET("/open-apis/task/v2/tasklists/" + tlId). Params(map[string]interface{}{"user_id_type": "open_id"}) } if runtime.Str("add") != "" { body := buildTlMembersBody(runtime.Str("add")) d.Desc("Add members"). POST("/open-apis/task/v2/tasklists/" + tlId + "/add_members"). Params(map[string]interface{}{"user_id_type": "open_id"}). Body(body) } if runtime.Str("remove") != "" { body := buildTlMembersBody(runtime.Str("remove")) d.Desc("Remove members"). POST("/open-apis/task/v2/tasklists/" + tlId + "/remove_members"). Params(map[string]interface{}{"user_id_type": "open_id"}). Body(body) } return d }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { tlId := url.PathEscape(extractTasklistGuid(runtime.Str("tasklist-id"))) queryParams := make(larkcore.QueryParams) queryParams.Set("user_id_type", "open_id") setStr := runtime.Str("set") addStr := runtime.Str("add") removeStr := runtime.Str("remove") if setStr == "" && addStr == "" && removeStr == "" { getResp, err := runtime.DoAPI(&larkcore.ApiReq{ HttpMethod: http.MethodGet, ApiPath: "/open-apis/task/v2/tasklists/" + tlId, QueryParams: queryParams, }) var getResult map[string]interface{} if err == nil { if parseErr := json.Unmarshal(getResp.RawBody, &getResult); parseErr != nil { return WrapTaskError(ErrCodeTaskInternalError, fmt.Sprintf("failed to parse response: %v", parseErr), "parse tasklist details") } } data, err := HandleTaskApiResult(getResult, err, "get tasklist members") if err != nil { return err } tl, _ := data["tasklist"].(map[string]interface{}) membersRaw, _ := tl["members"].([]interface{}) tlUrl, _ := tl["url"].(string) tlUrl = truncateTaskURL(tlUrl) var members []interface{} for _, m := range membersRaw { if mObj, ok := m.(map[string]interface{}); ok { members = append(members, map[string]interface{}{ "id": mObj["id"], "role": mObj["role"], "type": mObj["type"], }) } } outData := map[string]interface{}{ "guid": tlId, "url": tlUrl, "name": tl["name"], "members": members, } runtime.OutFormat(outData, nil, func(w io.Writer) { fmt.Fprintf(w, "Tasklist: %s (%s)\n", tl["name"], tlId) if tlUrl != "" { fmt.Fprintf(w, "Tasklist URL: %s\n", tlUrl) } fmt.Fprintf(w, "Members (%d):\n", len(members)) for _, m := range members { if mObj, ok := m.(map[string]interface{}); ok { fmt.Fprintf(w, " - %s (%s)\n", mObj["id"], mObj["role"]) } } }) return nil } var lastTasklist map[string]interface{} if setStr != "" { getResp, err := runtime.DoAPI(&larkcore.ApiReq{ HttpMethod: http.MethodGet, ApiPath: "/open-apis/task/v2/tasklists/" + tlId, QueryParams: queryParams, }) var getResult map[string]interface{} if err == nil { if parseErr := json.Unmarshal(getResp.RawBody, &getResult); parseErr != nil { return WrapTaskError(ErrCodeTaskInternalError, fmt.Sprintf("failed to parse response: %v", parseErr), "parse tasklist details") } } data, err := HandleTaskApiResult(getResult, err, "get tasklist details for set") if err != nil { return err } lastTasklist, _ = data["tasklist"].(map[string]interface{}) var existingIds []string if members, ok := lastTasklist["members"].([]interface{}); ok { for _, m := range members { if mObj, ok := m.(map[string]interface{}); ok { if id, ok := mObj["id"].(string); ok { existingIds = append(existingIds, id) } } } } targetIds := strings.Split(setStr, ",") var targetClean []string for _, t := range targetIds { t = strings.TrimSpace(t) if t != "" { targetClean = append(targetClean, t) } } // Diff var toAdd []string var toRemove []string for _, t := range targetClean { if !contains(existingIds, t) { toAdd = append(toAdd, t) } } for _, e := range existingIds { if !contains(targetClean, e) { toRemove = append(toRemove, e) } } if len(toAdd) > 0 { body := buildTlMembersBody(strings.Join(toAdd, ",")) apiResp, err := runtime.DoAPI(&larkcore.ApiReq{ HttpMethod: http.MethodPost, ApiPath: "/open-apis/task/v2/tasklists/" + tlId + "/add_members", QueryParams: queryParams, Body: body, }) var addResult map[string]interface{} if err == nil { if parseErr := json.Unmarshal(apiResp.RawBody, &addResult); parseErr != nil { return WrapTaskError(ErrCodeTaskInternalError, fmt.Sprintf("failed to parse response: %v", parseErr), "parse add members") } } data, err := HandleTaskApiResult(addResult, err, "add tasklist members") if err != nil { return err } lastTasklist, _ = data["tasklist"].(map[string]interface{}) } if len(toRemove) > 0 { body := buildTlMembersBody(strings.Join(toRemove, ",")) apiResp, err := runtime.DoAPI(&larkcore.ApiReq{ HttpMethod: http.MethodPost, ApiPath: "/open-apis/task/v2/tasklists/" + tlId + "/remove_members", QueryParams: queryParams, Body: body, }) var removeResult map[string]interface{} if err == nil { if parseErr := json.Unmarshal(apiResp.RawBody, &removeResult); parseErr != nil { return WrapTaskError(ErrCodeTaskInternalError, fmt.Sprintf("failed to parse response: %v", parseErr), "parse remove members") } } data, err := HandleTaskApiResult(removeResult, err, "remove tasklist members") if err != nil { return err } lastTasklist, _ = data["tasklist"].(map[string]interface{}) } } else { if addStr != "" { body := buildTlMembersBody(addStr) apiResp, err := runtime.DoAPI(&larkcore.ApiReq{ HttpMethod: http.MethodPost, ApiPath: "/open-apis/task/v2/tasklists/" + tlId + "/add_members", QueryParams: queryParams, Body: body, }) var addResult map[string]interface{} if err == nil { if parseErr := json.Unmarshal(apiResp.RawBody, &addResult); parseErr != nil { return WrapTaskError(ErrCodeTaskInternalError, fmt.Sprintf("failed to parse response: %v", parseErr), "parse add members") } } data, err := HandleTaskApiResult(addResult, err, "add tasklist members") if err != nil { return err } lastTasklist, _ = data["tasklist"].(map[string]interface{}) } if removeStr != "" { body := buildTlMembersBody(removeStr) apiResp, err := runtime.DoAPI(&larkcore.ApiReq{ HttpMethod: http.MethodPost, ApiPath: "/open-apis/task/v2/tasklists/" + tlId + "/remove_members", QueryParams: queryParams, Body: body, }) var removeResult map[string]interface{} if err == nil { if parseErr := json.Unmarshal(apiResp.RawBody, &removeResult); parseErr != nil { return WrapTaskError(ErrCodeTaskInternalError, fmt.Sprintf("failed to parse response: %v", parseErr), "parse remove members") } } data, err := HandleTaskApiResult(removeResult, err, "remove tasklist members") if err != nil { return err } lastTasklist, _ = data["tasklist"].(map[string]interface{}) } } tlUrl, _ := lastTasklist["url"].(string) tlUrl = truncateTaskURL(tlUrl) outData := map[string]interface{}{ "guid": tlId, "url": tlUrl, } runtime.OutFormat(outData, nil, func(w io.Writer) { fmt.Fprintf(w, "✅ Tasklist members updated successfully!\n") fmt.Fprintf(w, "Tasklist ID: %s\n", tlId) if tlUrl != "" { fmt.Fprintf(w, "Tasklist URL: %s\n", tlUrl) } }) return nil }, }
View Source
var ReminderTask = common.Shortcut{ Service: "task", Command: "+reminder", Description: "manage task reminders", Risk: "write", Scopes: []string{"task:task:write"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: []common.Flag{ {Name: "task-id", Desc: "task id", Required: true}, {Name: "set", Desc: "relative fire minutes to set (e.g. 15m, 1h, 1d)"}, {Name: "remove", Type: "bool", Desc: "removes all existing reminders"}, }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { if runtime.Str("set") == "" && !runtime.Bool("remove") { return WrapTaskError(ErrCodeTaskInvalidParams, "must specify either --set or --remove", "validate reminder") } if runtime.Str("set") != "" && runtime.Bool("remove") { return WrapTaskError(ErrCodeTaskInvalidParams, "cannot specify both --set and --remove", "validate reminder") } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { d := common.NewDryRunAPI() taskId := url.PathEscape(runtime.Str("task-id")) if runtime.Bool("remove") { d.Desc("1. GET task to find existing reminder IDs"). GET("/open-apis/task/v2/tasks/" + taskId). Params(map[string]interface{}{"user_id_type": "open_id"}). Desc("2. POST to remove_reminders with found IDs") } else if setStr := runtime.Str("set"); setStr != "" { d.Desc("1. GET task to check existing reminders"). GET("/open-apis/task/v2/tasks/" + taskId). Params(map[string]interface{}{"user_id_type": "open_id"}). Desc("2. POST to remove_reminders if any exist"). Desc("3. POST to add_reminders"). POST("/open-apis/task/v2/tasks/" + taskId + "/add_reminders") } return d }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { taskId := url.PathEscape(runtime.Str("task-id")) queryParams := make(larkcore.QueryParams) queryParams.Set("user_id_type", "open_id") getResp, err := runtime.DoAPI(&larkcore.ApiReq{ HttpMethod: http.MethodGet, ApiPath: "/open-apis/task/v2/tasks/" + taskId, QueryParams: queryParams, }) var getResult map[string]interface{} if err == nil { if parseErr := json.Unmarshal(getResp.RawBody, &getResult); parseErr != nil { return WrapTaskError(ErrCodeTaskInternalError, fmt.Sprintf("failed to parse task details: %v", parseErr), "parse task details") } } data, err := HandleTaskApiResult(getResult, err, "get task reminders") if err != nil { return err } taskObj, _ := data["task"].(map[string]interface{}) reminders, _ := taskObj["reminders"].([]interface{}) if runtime.Bool("remove") { if len(reminders) == 0 { runtime.OutFormat(map[string]interface{}{"guid": taskId}, nil, func(w io.Writer) { fmt.Fprintln(w, "No existing reminders to remove.") }) return nil } var reminderIds []string for _, r := range reminders { if rMap, ok := r.(map[string]interface{}); ok { if id, ok := rMap["id"].(string); ok { reminderIds = append(reminderIds, id) } } } if len(reminderIds) > 0 { body := map[string]interface{}{ "reminder_ids": reminderIds, } apiResp, err := runtime.DoAPI(&larkcore.ApiReq{ HttpMethod: http.MethodPost, ApiPath: "/open-apis/task/v2/tasks/" + taskId + "/remove_reminders", QueryParams: queryParams, Body: body, }) var removeResult map[string]interface{} if err == nil { if parseErr := json.Unmarshal(apiResp.RawBody, &removeResult); parseErr != nil { return WrapTaskError(ErrCodeTaskInternalError, fmt.Sprintf("failed to parse response: %v", parseErr), "parse remove response") } } if _, err := HandleTaskApiResult(removeResult, err, "remove task reminders"); err != nil { return err } } } else if setStr := runtime.Str("set"); setStr != "" { // Parse relative time string (e.g. 15m, 1h, 1d, or plain 30) var minutes int var parseErr error if strings.HasSuffix(setStr, "m") { minutes, parseErr = strconv.Atoi(strings.TrimSuffix(setStr, "m")) } else if strings.HasSuffix(setStr, "h") { h, e := strconv.Atoi(strings.TrimSuffix(setStr, "h")) if e == nil { minutes = h * 60 } parseErr = e } else if strings.HasSuffix(setStr, "d") { d, e := strconv.Atoi(strings.TrimSuffix(setStr, "d")) if e == nil { minutes = d * 24 * 60 } parseErr = e } else { minutes, parseErr = strconv.Atoi(setStr) } if parseErr != nil { return WrapTaskError(ErrCodeTaskInvalidParams, parseErr.Error(), "set reminder") } if len(reminders) > 0 { var reminderIds []string for _, r := range reminders { if rMap, ok := r.(map[string]interface{}); ok { if id, ok := rMap["id"].(string); ok { reminderIds = append(reminderIds, id) } } } if len(reminderIds) > 0 { body := map[string]interface{}{ "reminder_ids": reminderIds, } apiResp, err := runtime.DoAPI(&larkcore.ApiReq{ HttpMethod: http.MethodPost, ApiPath: "/open-apis/task/v2/tasks/" + taskId + "/remove_reminders", QueryParams: queryParams, Body: body, }) var removeResult map[string]interface{} if err == nil { if parseErr := json.Unmarshal(apiResp.RawBody, &removeResult); parseErr != nil { return WrapTaskError(ErrCodeTaskInternalError, fmt.Sprintf("failed to parse response: %v", parseErr), "parse remove response") } } if _, err := HandleTaskApiResult(removeResult, err, "remove existing task reminders before setting new one"); err != nil { return err } } } body := map[string]interface{}{ "reminders": []map[string]interface{}{ { "relative_fire_minute": minutes, }, }, } apiResp, err := runtime.DoAPI(&larkcore.ApiReq{ HttpMethod: http.MethodPost, ApiPath: "/open-apis/task/v2/tasks/" + taskId + "/add_reminders", QueryParams: queryParams, Body: body, }) var addResult map[string]interface{} if err == nil { if parseErr := json.Unmarshal(apiResp.RawBody, &addResult); parseErr != nil { return WrapTaskError(ErrCodeTaskInternalError, fmt.Sprintf("failed to parse response: %v", parseErr), "parse add response") } } if _, err := HandleTaskApiResult(addResult, err, "add task reminder"); err != nil { return err } } urlVal, _ := taskObj["url"].(string) urlVal = truncateTaskURL(urlVal) outData := map[string]interface{}{ "guid": taskId, "url": urlVal, } runtime.OutFormat(outData, nil, func(w io.Writer) { fmt.Fprintf(w, "✅ Task reminders updated successfully!\n") fmt.Fprintf(w, "Task ID: %s\n", taskId) if urlVal != "" { fmt.Fprintf(w, "Task URL: %s\n", urlVal) } }) return nil }, }
View Source
var ReopenTask = common.Shortcut{ Service: "task", Command: "+reopen", Description: "reopen a completed task", Risk: "write", Scopes: []string{"task:task:write"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: []common.Flag{ {Name: "task-id", Desc: "task id", Required: true}, }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { body := buildReopenBody() taskId := url.PathEscape(runtime.Str("task-id")) return common.NewDryRunAPI(). PATCH("/open-apis/task/v2/tasks/" + taskId). Params(map[string]interface{}{"user_id_type": "open_id"}). Body(body) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { taskId := url.PathEscape(runtime.Str("task-id")) body := buildReopenBody() queryParams := make(larkcore.QueryParams) queryParams.Set("user_id_type", "open_id") apiResp, err := runtime.DoAPI(&larkcore.ApiReq{ HttpMethod: http.MethodPatch, ApiPath: "/open-apis/task/v2/tasks/" + taskId, QueryParams: queryParams, Body: body, }) var result map[string]interface{} if err == nil { if parseErr := json.Unmarshal(apiResp.RawBody, &result); parseErr != nil { return WrapTaskError(ErrCodeTaskInternalError, fmt.Sprintf("failed to parse response: %v", parseErr), "parse reopen response") } } data, err := HandleTaskApiResult(result, err, "reopen task") if err != nil { return err } task, _ := data["task"].(map[string]interface{}) guid, _ := task["guid"].(string) urlVal, _ := task["url"].(string) urlVal = truncateTaskURL(urlVal) outData := map[string]interface{}{ "guid": guid, "url": urlVal, } runtime.OutFormat(outData, nil, func(w io.Writer) { summary, _ := task["summary"].(string) fmt.Fprintf(w, "✅ Task reopened successfully!\n") if guid != "" { fmt.Fprintf(w, "Task ID: %s\n", guid) } if summary != "" { fmt.Fprintf(w, "Summary: %s\n", summary) } if urlVal != "" { fmt.Fprintf(w, "Task URL: %s\n", urlVal) } }) return nil }, }
View Source
var UpdateTask = common.Shortcut{ Service: "task", Command: "+update", Description: "update task attributes", Risk: "write", Scopes: []string{"task:task:write"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: []common.Flag{ {Name: "task-id", Desc: "task id (comma-separated for multiple)", Required: true}, {Name: "summary", Desc: "task title"}, {Name: "description", Desc: "task description"}, {Name: "due", Desc: "due date (ISO 8601 / date:YYYY-MM-DD / relative:+2d / ms timestamp)"}, {Name: "data", Desc: "JSON payload for task object"}, }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { body, err := buildTaskUpdateBody(runtime) if err != nil { return common.NewDryRunAPI().Set("error", err.Error()) } taskIds := strings.Split(runtime.Str("task-id"), ",") taskId := url.PathEscape(strings.TrimSpace(taskIds[0])) return common.NewDryRunAPI(). PATCH("/open-apis/task/v2/tasks/" + taskId). Params(map[string]interface{}{"user_id_type": "open_id"}). Body(body) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { body, err := buildTaskUpdateBody(runtime) if err != nil { return WrapTaskError(ErrCodeTaskInvalidParams, err.Error(), "update task") } taskIds := strings.Split(runtime.Str("task-id"), ",") var updatedTasks []map[string]interface{} for _, taskId := range taskIds { taskId = strings.TrimSpace(taskId) if taskId == "" { continue } queryParams := make(larkcore.QueryParams) queryParams.Set("user_id_type", "open_id") apiResp, err := runtime.DoAPI(&larkcore.ApiReq{ HttpMethod: http.MethodPatch, ApiPath: "/open-apis/task/v2/tasks/" + url.PathEscape(taskId), QueryParams: queryParams, Body: body, }) var result map[string]interface{} if err == nil { if parseErr := json.Unmarshal(apiResp.RawBody, &result); parseErr != nil { return fmt.Errorf("failed to parse response for task %s: %v", taskId, parseErr) } } data, err := HandleTaskApiResult(result, err, "update task "+taskId) if err != nil { return err } taskObj, _ := data["task"].(map[string]interface{}) if taskObj != nil { updatedTasks = append(updatedTasks, taskObj) } } var tasks []map[string]interface{} for _, task := range updatedTasks { guid, _ := task["guid"].(string) urlVal, _ := task["url"].(string) urlVal = truncateTaskURL(urlVal) tasks = append(tasks, map[string]interface{}{ "guid": guid, "url": urlVal, }) } outData := map[string]interface{}{ "tasks": tasks, } runtime.OutFormat(outData, &output.Meta{Count: len(updatedTasks)}, func(w io.Writer) { for _, task := range updatedTasks { guid, _ := task["guid"].(string) summary, _ := task["summary"].(string) urlVal, _ := task["url"].(string) urlVal = truncateTaskURL(urlVal) fmt.Fprintf(w, "✅ Task updated successfully!\n") fmt.Fprintf(w, "Task ID: %s\n", guid) if summary != "" { fmt.Fprintf(w, "Summary: %s\n", summary) } if urlVal != "" { fmt.Fprintf(w, "Task URL: %s\n", urlVal) } fmt.Fprintln(w, strings.Repeat("-", 20)) } }) return nil }, }
Functions ¶
func HandleTaskApiResult ¶
func HandleTaskApiResult(result interface{}, err error, action string) (map[string]interface{}, error)
HandleTaskApiResult is a wrapper around common.HandleApiResult that applies task-specific error mapping.
Types ¶
Click to show internal directories.
Click to hide internal directories.