package usecase import ( "context" "fmt" "strings" "time" app "haixun-backend/internal/library/errors" "haixun-backend/internal/library/errors/code" libthreads "haixun-backend/internal/library/threadsapi" domusecase "haixun-backend/internal/model/threads_account/domain/usecase" ) func (u *threadsAccountUseCase) ListPostPerformance( ctx context.Context, tenantID, ownerUID, accountID string, limit int, ) (*domusecase.PostPerformanceResult, error) { account, err := u.assertOwned(ctx, tenantID, ownerUID, accountID) if err != nil { return nil, err } token, err := u.ResolveAccountAPIAccessToken(ctx, tenantID, ownerUID, accountID) if err != nil { return nil, err } client := libthreads.NewClient(token) if !client.Enabled() { return nil, app.For(code.ThreadsAccount).InputMissingRequired("Threads API 未連線") } if limit <= 0 { limit = 15 } if limit > 30 { limit = 30 } userID := strings.TrimSpace(account.ThreadsUserID) username := strings.TrimSpace(account.Username) if profile, perr := libthreads.GetMeProfile(ctx, token); perr == nil && profile != nil { if userID == "" { userID = profile.ID.String() } if username == "" { username = profile.Username } } result := &domusecase.PostPerformanceResult{ AccountID: account.ID, Username: username, ThreadsUserID: userID, FetchedAt: time.Now().UnixNano(), Capabilities: postPerformanceCapabilities(), AccountInsights: domusecase.AccountInsightsBlock{ Status: "skip", Message: "缺少 threads_user_id", }, Posts: []domusecase.PostPerformanceItem{}, } if userID != "" { result.AccountInsights = fetchAccountInsights(ctx, client, userID) posts, err := client.UserThreads(ctx, userID, limit) if err != nil { return nil, err } for _, post := range posts { item := domusecase.PostPerformanceItem{ MediaID: post.ID, Text: post.Text, Permalink: post.Permalink, Timestamp: post.Timestamp, LikeCount: post.LikeCount, ReplyCount: post.ReplyCount, } if stats, serr := client.GetMediaStats(ctx, post.ID); serr == nil && stats != nil { item.LikeCount = stats.LikeCount item.ReplyCount = stats.ReplyCount item.RepostCount = stats.RepostCount item.QuoteCount = stats.QuoteCount item.Views = stats.Views item.Shares = stats.Shares item.InsightsStatus = "ok" } else { item.InsightsStatus = "error" if serr != nil { item.InsightsMessage = serr.Error() } else { item.InsightsMessage = "無法讀取貼文洞察" } } result.Posts = append(result.Posts, item) } } return result, nil } func postPerformanceCapabilities() []domusecase.PostMetricCapability { return []domusecase.PostMetricCapability{ { Scope: "threads_manage_insights", Name: "貼文互動數", Metrics: []string{"likes", "replies", "reposts", "quotes", "views", "shares"}, Trackable: true, Note: "GET /{media-id}/insights(非 media 物件欄位)", }, { Scope: "threads_manage_insights", Name: "貼文洞察", Metrics: []string{"views", "likes", "replies", "reposts", "quotes", "shares"}, Trackable: true, Note: "GET /{media-id}/insights(lifetime)", }, { Scope: "threads_manage_insights", Name: "帳號洞察", Metrics: []string{"views", "likes", "replies", "reposts", "quotes", "followers_count", "clicks"}, Trackable: true, Note: "GET /{user-id}/threads_insights", }, { Scope: "threads_read_replies", Name: "留言內容", Metrics: []string{"reply_list"}, Trackable: true, Note: "GET /{media-id}/replies(非數字統計)", }, { Scope: "threads_keyword_search", Name: "關鍵字搜尋", Trackable: false, Note: "搜尋他人貼文,非你的發文成效", }, { Scope: "threads_profile_discovery", Name: "帳號查詢", Trackable: false, Note: "查他人 profile,非成效統計", }, { Scope: "threads_manage_mentions", Name: "提及", Trackable: false, Note: "誰 @ 你,非單篇貼文成效", }, { Scope: "threads_location_tagging", Name: "地點", Trackable: false, Note: "地點標籤搜尋", }, { Scope: "threads_content_publish", Name: "發文", Trackable: false, Note: "寫入類;發文後用本頁追蹤成效", }, { Scope: "threads_delete", Name: "刪文", Trackable: false, Note: "寫入類", }, { Scope: "threads_manage_replies", Name: "留言管理", Trackable: false, Note: "隱藏留言,非統計", }, { Scope: "threads_share_to_instagram", Name: "同步 IG", Trackable: false, Note: "crosspost 動作", }, } } func fetchAccountInsights(ctx context.Context, client *libthreads.Client, userID string) domusecase.AccountInsightsBlock { items, err := client.UserInsights(ctx, userID, nil) if err != nil { msg := err.Error() if libthreads.IsPermissionError(err) { msg += "(需 threads_manage_insights)" } return domusecase.AccountInsightsBlock{Status: "error", Message: msg} } metrics := make([]domusecase.InsightMetricItem, 0, len(items)) for _, item := range items { name := strings.TrimSpace(item.Name) if name == "" || len(item.Values) == 0 { continue } metrics = append(metrics, domusecase.InsightMetricItem{Name: name, Value: item.Values[0].Value}) } if len(metrics) == 0 { return domusecase.AccountInsightsBlock{Status: "ok", Message: "尚無帳號洞察資料"} } return domusecase.AccountInsightsBlock{ Status: "ok", Message: fmt.Sprintf("%d 項指標", len(metrics)), Metrics: metrics, } }