mirror of
https://github.com/larksuite/cli.git
synced 2026-09-14 18:42:53 +08:00
501bf539af
Block 1 — field completion: audio renders <audio key="..." duration="Xs"/> (falls back to [Voice: Xs]/[Voice]); post renders emotion -> :emoji_type:, applies text.style (bold/italic/underline/lineThrough), passes through md; sticker unchanged. Block 2 — opt-in --download-resources (default off) on +chat-messages-list, +messages-mget, +threads-messages-list: extract downloadable resource refs during formatting (image/file/audio/video/media + post-embedded; sticker excluded; merge_forward sub-items carry the top-level container message_id, since the resources endpoint rejects sub-item ids with "234003 File not in msg" and can only fetch a forwarded resource through the container; thread replies get their own block), then download each distinct (message_id, file_key) once into ./lark-im-resources/ with bounded concurrency (3), filling back local_path/size_bytes; single-resource failures are isolated (error:true + stderr warning). Path safety reuses normalizeDownloadOutputPath + ResolveSavePath. Batch download keys each file on disk by its unique file_key basename and only appends an extension (from the Content-Disposition filename or MIME type) — it does NOT substitute the server's Content-Disposition filename. Otherwise two resources whose servers return the same filename (e.g. download.bin) would resolve to the same ./lark-im-resources/ path and clobber each other concurrently. The friendly "adopt the server filename" behavior is kept only for an explicit +messages-resources-download with no --output. Resource ref extraction guards against self-referential / cyclic merge_forward prefetch maps (a real API sub-item list can include the container's own id or a back-pointing merge_forward) via a visited set, so extraction terminates instead of overflowing the stack. The container message_id is threaded through nested merge_forwards as the download owner. Also: document the feature (including the im:message:readonly scope requirement) in skills/lark-im — SKILL.md is generated from skill-template/domains/im.md (edit the source), plus the hand-written message-enrichment + 3 command references. Change-Id: I3a71d7d1b193130f551aaa2ec180ac1500d59ac4 Meego: https://meego.larkoffice.com/5e96d7bff4e7c525510f9156/story/detail/7331555925
62 lines
3.0 KiB
Go
62 lines
3.0 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package im
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/larksuite/cli/shortcuts/common"
|
|
convertlib "github.com/larksuite/cli/shortcuts/im/convert_lib"
|
|
)
|
|
|
|
// imResourceDownloadDir is the fixed sub-directory under the current working
|
|
// directory where --download-resources writes fetched resources, so users can
|
|
// find and clean them up easily.
|
|
const imResourceDownloadDir = "lark-im-resources"
|
|
|
|
// downloadResourcesFlag opts into automatic resource download. It is shared by
|
|
// the three message-listing commands (+chat-messages-list, +messages-mget,
|
|
// +threads-messages-list); off by default so the default output contract and
|
|
// request count are unchanged.
|
|
var downloadResourcesFlag = common.Flag{
|
|
Name: "download-resources",
|
|
Type: "bool",
|
|
Desc: "download image/file/audio/video/media resources (and post-embedded, excluding stickers) into ./lark-im-resources/ (default: off; no extra requests when off)",
|
|
}
|
|
|
|
// downloadResourcesDryRunDesc is the dry-run declaration appended when
|
|
// --download-resources is set, mirroring how --no-reactions surfaces the
|
|
// reactions enrichment call.
|
|
const downloadResourcesDryRunDesc = "Resource download (--download-resources): each downloadable resource (image/file/audio/video/media + post-embedded, excluding stickers) is fetched via GET /open-apis/im/v1/messages/:message_id/resources/:file_key into ./lark-im-resources/ after formatting; deduped by (message_id, file_key) with bounded concurrency, and single-resource failures are isolated."
|
|
|
|
// resolveResourceDownloadPath builds the safe relative path under
|
|
// ./lark-im-resources/ for a resource file_key, reusing
|
|
// normalizeDownloadOutputPath so abnormal keys (path separators, traversal,
|
|
// absolute paths) are rejected (AC8).
|
|
func resolveResourceDownloadPath(fileKey string) (string, error) {
|
|
return normalizeDownloadOutputPath(fileKey, imResourceDownloadDir+"/"+fileKey)
|
|
}
|
|
|
|
// enrichMessageResourceDownloads downloads the resource refs extracted during
|
|
// formatting into ./lark-im-resources/, filling local_path/size_bytes back into
|
|
// each message's resources block. The download engine isolates single-resource
|
|
// failures (fail-silent + stderr warning), so the main message output is never
|
|
// blocked.
|
|
func enrichMessageResourceDownloads(runtime *common.RuntimeContext, messages []map[string]interface{}) {
|
|
convertlib.EnrichResourceDownloads(runtime, messages, func(dlCtx context.Context, messageID, key, fileType string) (string, int64, error) {
|
|
rel, err := resolveResourceDownloadPath(key)
|
|
if err != nil {
|
|
return "", 0, err
|
|
}
|
|
if _, err := runtime.ResolveSavePath(rel); err != nil {
|
|
return "", 0, err
|
|
}
|
|
// preserveBasename=true: each resource is keyed by its unique file_key,
|
|
// so keep that basename and only append an extension. Adopting the
|
|
// server's Content-Disposition filename here would let two resources
|
|
// that share a filename (e.g. download.bin) clobber each other.
|
|
return downloadIMResourceToPath(dlCtx, runtime, messageID, key, fileType, rel, true)
|
|
})
|
|
}
|