185 lines
5.4 KiB
Go
185 lines
5.4 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,
|
|||
|
|
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,
|
|||
|
|
}
|
|||
|
|
}
|