package usecase import ( "context" "fmt" "strings" 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) RunAPISmokeTest( ctx context.Context, tenantID, ownerUID, accountID string, ) ([]domusecase.APISmokeTestItem, 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 token 無效") } var out []domusecase.APISmokeTestItem add := func(scope, name, status, message string, count int) { out = append(out, domusecase.APISmokeTestItem{ Scope: scope, Name: name, Status: status, Message: message, Count: count, }) } run := func(scope, name string, fn func() (int, string, error)) { count, detail, err := fn() if err != nil { msg := err.Error() if libthreads.IsPermissionError(err) { msg += "(請確認 Meta App 已授權此 scope,且帳號在測試人員名單)" } add(scope, name, "error", msg, 0) return } if detail != "" { add(scope, name, "ok", detail, count) return } add(scope, name, "ok", fmt.Sprintf("成功(%d 筆)", count), count) } profile, profileErr := libthreads.GetMeProfile(ctx, token) userID := strings.TrimSpace(account.ThreadsUserID) username := strings.TrimSpace(account.Username) if profileErr == nil && profile != nil { if userID == "" { userID = profile.ID.String() } if username == "" { username = profile.Username } } run("threads_basic", "GET /me 個人資料", func() (int, string, error) { if profileErr != nil { return 0, "", profileErr } return 1, "user_id=" + profile.ID.String() + " @" + profile.Username, nil }) if userID == "" { add("threads_basic", "GET /{user-id}/threads 貼文列表", "error", "缺少 threads_user_id,請重新 OAuth", 0) } else { var sampleMediaID string run("threads_basic", "GET /{user-id}/threads 貼文列表", func() (int, string, error) { items, err := client.UserThreads(ctx, userID, 5) if err != nil { return 0, "", err } if len(items) > 0 { sampleMediaID = items[0].ID } return len(items), "", nil }) if sampleMediaID != "" { run("threads_manage_insights", "GET /{media-id}/insights 貼文互動", func() (int, string, error) { stats, err := client.GetMediaStats(ctx, sampleMediaID) if err != nil { return 0, "", err } return 1, fmt.Sprintf("likes=%d replies=%d views=%d", stats.LikeCount, stats.ReplyCount, stats.Views), nil }) run("threads_read_replies", "GET /{media-id}/replies 留言列表", func() (int, string, error) { items, err := client.MediaReplies(ctx, sampleMediaID, 5) if err != nil { return 0, "", err } return len(items), "", nil }) } else { add("threads_manage_insights", "GET /{media-id}/insights 貼文互動", "skip", "帳號尚無貼文,略過", 0) add("threads_read_replies", "GET /{media-id}/replies 留言列表", "skip", "帳號尚無貼文,略過", 0) } } run("threads_keyword_search", "GET /keyword_search 關鍵字搜尋", func() (int, string, error) { items, err := client.KeywordSearch(ctx, libthreads.KeywordSearchOptions{ Query: "threads", Limit: 5, SearchType: "TOP", }) if err != nil { return 0, "", err } return len(items), "", nil }) if username != "" { run("threads_profile_discovery", "GET /profile_lookup 帳號查詢", func() (int, string, error) { res, err := client.ProfileLookup(ctx, username) if err != nil { return 0, "", err } return 1, "@" + res.Username + " name=" + res.Name, nil }) } else { add("threads_profile_discovery", "GET /profile_lookup 帳號查詢", "skip", "缺少 username,略過", 0) } if userID != "" { run("threads_manage_mentions", "GET /{user-id}/mentions 提及", func() (int, string, error) { items, err := client.UserMentions(ctx, userID, 5) if err != nil { return 0, "", err } return len(items), "", nil }) run("threads_manage_insights", "GET /{user-id}/threads_insights 洞察", func() (int, string, error) { items, err := client.UserInsights(ctx, userID, nil) if err != nil { return 0, "", err } return len(items), fmt.Sprintf("%d 項指標", len(items)), nil }) } else { add("threads_manage_mentions", "GET /{user-id}/mentions 提及", "skip", "缺少 threads_user_id", 0) add("threads_manage_insights", "GET /{user-id}/threads_insights 洞察", "skip", "缺少 threads_user_id", 0) } run("threads_location_tagging", "GET /location_search 地點搜尋", func() (int, string, error) { items, err := client.LocationSearch(ctx, "Taipei", 5) if err != nil { return 0, "", err } return len(items), "", nil }) add("threads_content_publish", "POST 發文 / 回覆", "manual", "請用產品 API:POST /personas/:id/copy-drafts/:draftId/publish 或 outreach-drafts/publish", 0) add("threads_delete", "DELETE /{media-id} 刪文", "manual", "會真的刪除貼文,請在 Threads 後台或自行呼叫 DeleteMedia 測試", 0) add("threads_manage_replies", "POST 隱藏留言", "manual", "會改變留言狀態,請用 HideReply(reply_id) 手動測試", 0) add("threads_share_to_instagram", "POST 同步到 IG", "manual", "需已連結 IG 且會真的 crosspost,請用 ShareToInstagram(media_id) 手動測試", 0) return out, nil }