thread-master/backend/internal/model/threads_account/usecase/post_performance.go

185 lines
5.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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}/insightslifetime",
},
{
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,
}
}