190 lines
5.6 KiB
Go
190 lines
5.6 KiB
Go
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,
|
||
MediaType: post.MediaType,
|
||
MediaURL: post.MediaURL,
|
||
ThumbnailURL: post.ThumbnailURL,
|
||
TopicTag: post.TopicTag,
|
||
Shortcode: post.Shortcode,
|
||
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,
|
||
}
|
||
}
|