Files
larksuite-cli/shortcuts/task/task_get_my_tasks.go

296 lines
8.7 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package task
import (
"context"
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
"time"
larkcore "github.com/larksuite/oapi-sdk-go/v3/core"
"github.com/larksuite/cli/shortcuts/common"
)
// GetMyTasks lists tasks assigned to the current user.
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;if false, query incompleted tasks; if not provided, both completed and incompleted tasks are queried."},
{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)"},
{Name: "page-token", Desc: "start from the specified page token"},
},
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")
}
if pageToken := runtime.Str("page-token"); pageToken != "" {
params["page_token"] = pageToken
}
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")
}
}
if pageToken := runtime.Str("page-token"); pageToken != "" {
queryParams.Set("page_token", pageToken)
}
// 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 // Convert sec to ms
}
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
}
// Set page_token for next iteration
queryParams.Set("page_token", lastPageToken)
}
var filteredItems []map[string]interface{}
for _, itemRaw := range allItems {
item, ok := itemRaw.(map[string]interface{})
if !ok {
continue
}
// Apply client-side filtering for created_at, due-start, due-end
// because the API might not support these filters natively in GET /v2/tasks
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 {
// If due filtering is requested but task has no due date, filter it out
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)
}
// Apply query filtering if provided
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).Local().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).Local().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).Local().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).Local().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
},
}