Files
larksuite-cli/shortcuts/common/common.go
sang-neo03 9ba0d15161 Feat/risk tiering (#633)
* feat(risk): implement confirmation for high-risk write operations

* feat(risk): streamline confirmation for high-risk write operations

* feat(risk): document approval protocol for high-risk write operations

* feat(risk): refine confirmation protocol for high-risk write operations

* feat(risk): remove redundant variable declaration in risk test

* feat(risk): add 'Yes' flag to various test cases for confirmation
2026-04-28 18:15:56 +08:00

191 lines
4.9 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package common
import (
"fmt"
"io"
"strings"
"time"
"github.com/larksuite/cli/internal/output"
"github.com/larksuite/cli/internal/util"
)
func FormatSize(bytes int64) string {
if bytes < 1024 {
return fmt.Sprintf("%d B", bytes)
}
if bytes < 1024*1024 {
return fmt.Sprintf("%.1f KB", float64(bytes)/1024)
}
if bytes < 1024*1024*1024 {
return fmt.Sprintf("%.1f MB", float64(bytes)/1024/1024)
}
return fmt.Sprintf("%.1f GB", float64(bytes)/1024/1024/1024)
}
func MaskToken(token string) string {
if len(token) < 2 {
return "***"
}
if len(token) <= 8 {
return token[:2] + "***"
}
return token[:4] + "..." + token[len(token)-4:]
}
// ParseTime converts time expressions to Unix seconds string.
//
// Optional hint: "end" makes day-granularity inputs snap to 23:59:59 instead of 00:00:00.
//
// ParseTime("2026-01-01") → 2026-01-01 00:00:00
// ParseTime("2026-01-01", "end") → 2026-01-01 23:59:59
//
// Supported formats: ISO 8601 (with or without time/timezone), date-only, Unix timestamp.
func ParseTime(input string, hint ...string) (string, error) {
input = strings.TrimSpace(input)
isEnd := len(hint) > 0 && hint[0] == "end"
// snapDay aligns to start-of-day or end-of-day based on hint.
snapDay := func(t time.Time) time.Time {
if isEnd {
return time.Date(t.Year(), t.Month(), t.Day(), 23, 59, 59, 0, t.Location())
}
return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
}
// ISO 8601 with timezone (precise)
tzFormats := []string{
time.RFC3339,
"2006-01-02T15:04Z07:00",
"2006-01-02T15:04:05Z07:00",
}
for _, f := range tzFormats {
if t, err := time.Parse(f, input); err == nil {
return fmt.Sprintf("%d", t.Unix()), nil
}
}
// ISO 8601 without timezone — with time component (precise)
preciseFormats := []string{
"2006-01-02T15:04:05",
"2006-01-02 15:04:05",
"2006-01-02T15:04",
"2006-01-02 15:04",
}
for _, f := range preciseFormats {
if t, err := time.ParseInLocation(f, input, time.Local); err == nil {
return fmt.Sprintf("%d", t.Unix()), nil
}
}
// Date-only (day-granularity)
if t, err := time.ParseInLocation("2006-01-02", input, time.Local); err == nil {
return fmt.Sprintf("%d", snapDay(t).Unix()), nil
}
// Unix timestamp (precise, passed through as-is) — must be purely numeric
var ts int64
if n, err := fmt.Sscanf(input, "%d", &ts); err == nil && n == 1 && ts > 0 && fmt.Sprintf("%d", ts) == input {
return input, nil
}
return "", fmt.Errorf("cannot parse time %q (supported: ISO 8601 e.g. 2026-01-01 / 2026-01-01T15:04:05+08:00, Unix timestamp)", input)
}
// FormatTimeWithSeconds converts Unix seconds/ms string to local time string with seconds precision.
func FormatTimeWithSeconds(ts interface{}) string {
if ts == nil {
return ""
}
s := fmt.Sprintf("%v", ts)
if s == "" {
return ""
}
var n int64
fmt.Sscanf(s, "%d", &n)
if n == 0 {
return s
}
if n > 1e12 {
n = n / 1000
}
t := time.Unix(n, 0)
return t.Local().Format("2006-01-02 15:04:05")
}
// FormatTime converts Unix seconds/ms string to local time string.
func FormatTime(ts interface{}) string {
if ts == nil {
return ""
}
s := fmt.Sprintf("%v", ts)
if s == "" {
return ""
}
var n int64
fmt.Sscanf(s, "%d", &n)
if n == 0 {
return s
}
// Detect ms vs seconds
if n > 1e12 {
n = n / 1000
}
t := time.Unix(n, 0)
return t.Local().Format("2006-01-02 15:04")
}
// SplitCSV 解析逗号分隔的列表,忽略空项并去除空格
func SplitCSV(input string) []string {
if input == "" {
return nil
}
parts := strings.Split(input, ",")
var result []string
for _, p := range parts {
p = strings.TrimSpace(p)
if p != "" {
result = append(result, p)
}
}
return result
}
// CheckApiError checks if API result is an error and prints it to w.
func CheckApiError(w io.Writer, result interface{}, action string) bool {
if resultMap, ok := result.(map[string]interface{}); ok {
code, _ := util.ToFloat64(resultMap["code"])
if code != 0 {
msg, _ := resultMap["msg"].(string)
output.PrintError(w, fmt.Sprintf("%s: [%.0f] %s", action, code, msg))
return true
}
}
return false
}
// HandleApiResult checks for network/API errors and returns the "data" field.
func HandleApiResult(result interface{}, err error, action string) (map[string]interface{}, error) {
if err != nil {
return nil, output.Errorf(output.ExitAPI, "api_error", "%s: %s", action, err)
}
resultMap, _ := result.(map[string]interface{})
code, _ := util.ToFloat64(resultMap["code"])
if code != 0 {
msg, _ := resultMap["msg"].(string)
larkCode := int(code)
fullMsg := fmt.Sprintf("%s: [%d] %s", action, larkCode, msg)
return nil, output.ErrAPI(larkCode, fullMsg, resultMap["error"])
}
data, _ := resultMap["data"].(map[string]interface{})
return data, nil
}
// TruncateStr truncates s to at most n runes.
func TruncateStr(s string, n int) string {
r := []rune(s)
if len(r) <= n {
return s
}
return string(r[:n])
}