bf9395e022
CI / license-header (push) Has been skipped
CI / e2e-dry-run (push) Has been skipped
CI / fast-gate (push) Failing after 0s
Test PR Label Logic / test-pr-labels (push) Failing after 1s
Skill Format Check / check-format (push) Failing after 2s
CI / security (push) Failing after 5s
CI / unit-test (push) Has been skipped
CI / lint (push) Has been skipped
CI / script-test (push) Has been skipped
CI / deterministic-gate (push) Has been skipped
CI / coverage (push) Has been skipped
CI / results (push) Has been cancelled
CI / deadcode (push) Has been cancelled
CI / e2e-live (push) Has been cancelled
98 lines
2.7 KiB
Go
98 lines
2.7 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
// Package appmeta exposes read-only views of a Feishu app's published version, subscribed event types, and scopes.
|
|
package appmeta
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/larksuite/cli/internal/event"
|
|
)
|
|
|
|
// APIClient aliases event.APIClient so one concrete adapter satisfies event, appmeta, and consume.
|
|
type APIClient = event.APIClient
|
|
|
|
// AppVersion is the projected subset of one /app_versions item preflight cares about.
|
|
type AppVersion struct {
|
|
VersionID string
|
|
Version string
|
|
EventTypes []string
|
|
TenantScopes []string
|
|
}
|
|
|
|
const appVersionStatusPublished = 1
|
|
|
|
// FetchCurrentPublished returns the most recently published version of appID, or (nil, nil) if never published.
|
|
// page_size=2 suffices: Feishu disallows a new version while an in-progress one exists, so the first status==1 item with publish_time is the live one.
|
|
func FetchCurrentPublished(ctx context.Context, client APIClient, appID string) (*AppVersion, error) {
|
|
path := fmt.Sprintf(
|
|
"/open-apis/application/v6/applications/%s/app_versions?lang=zh_cn&page_size=2",
|
|
appID,
|
|
)
|
|
raw, err := client.CallAPI(ctx, "GET", path, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var envelope struct {
|
|
Data struct {
|
|
Items []struct {
|
|
VersionID string `json:"version_id"`
|
|
Version string `json:"version"`
|
|
Status int `json:"status"`
|
|
PublishTime json.RawMessage `json:"publish_time"`
|
|
EventInfos []struct {
|
|
EventType string `json:"event_type"`
|
|
} `json:"event_infos"`
|
|
Scopes []struct {
|
|
Scope string `json:"scope"`
|
|
TokenTypes []string `json:"token_types"`
|
|
} `json:"scopes"`
|
|
} `json:"items"`
|
|
} `json:"data"`
|
|
}
|
|
if err := json.Unmarshal(raw, &envelope); err != nil {
|
|
return nil, fmt.Errorf("decode app_versions response: %w", err)
|
|
}
|
|
|
|
for _, it := range envelope.Data.Items {
|
|
if it.Status != appVersionStatusPublished || !publishTimeSet(it.PublishTime) {
|
|
continue
|
|
}
|
|
v := &AppVersion{
|
|
VersionID: it.VersionID,
|
|
Version: it.Version,
|
|
}
|
|
for _, e := range it.EventInfos {
|
|
if e.EventType != "" {
|
|
v.EventTypes = append(v.EventTypes, e.EventType)
|
|
}
|
|
}
|
|
for _, s := range it.Scopes {
|
|
if s.Scope != "" && containsString(s.TokenTypes, "tenant") {
|
|
v.TenantScopes = append(v.TenantScopes, s.Scope)
|
|
}
|
|
}
|
|
return v, nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// publishTimeSet rejects null and empty-string; any other value is a real publish_time.
|
|
func publishTimeSet(raw json.RawMessage) bool {
|
|
s := string(raw)
|
|
return s != "" && s != "null" && s != `""`
|
|
}
|
|
|
|
func containsString(haystack []string, needle string) bool {
|
|
for _, s := range haystack {
|
|
if s == needle {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|