mirror of
https://github.com/larksuite/cli.git
synced 2026-07-03 22:24:31 +08:00
296 lines
8.7 KiB
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
|
|
},
|
|
}
|