2026-06-26 08:37:04 +00:00
|
|
|
package threads_account
|
|
|
|
|
|
|
|
|
|
import (
|
2026-06-30 09:10:23 +00:00
|
|
|
guarddomain "haixun-backend/internal/model/publish_guard/domain/usecase"
|
|
|
|
|
inventorydomain "haixun-backend/internal/model/publish_inventory/domain/usecase"
|
2026-06-30 07:09:44 +00:00
|
|
|
pqdomain "haixun-backend/internal/model/publish_queue/domain/usecase"
|
2026-06-30 09:10:23 +00:00
|
|
|
eventdomain "haixun-backend/internal/model/publish_queue_event/domain/usecase"
|
2026-06-26 08:37:04 +00:00
|
|
|
domusecase "haixun-backend/internal/model/threads_account/domain/usecase"
|
|
|
|
|
"haixun-backend/internal/types"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func toThreadsAccountData(item domusecase.AccountSummary) types.ThreadsAccountData {
|
|
|
|
|
return types.ThreadsAccountData{
|
2026-06-30 07:09:44 +00:00
|
|
|
ID: item.ID,
|
|
|
|
|
DisplayName: item.DisplayName,
|
|
|
|
|
Username: item.Username,
|
|
|
|
|
ThreadsUserID: item.ThreadsUserID,
|
|
|
|
|
PersonaID: item.PersonaID,
|
|
|
|
|
BrowserConnected: item.BrowserConnected,
|
|
|
|
|
ApiConnected: item.ApiConnected,
|
|
|
|
|
APITokenExpiresAt: item.APITokenExpiresAt,
|
|
|
|
|
Status: item.Status,
|
|
|
|
|
CreateAt: item.CreateAt,
|
|
|
|
|
UpdateAt: item.UpdateAt,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func toPostPerformanceData(result *domusecase.PostPerformanceResult) *types.ThreadsPostPerformanceData {
|
|
|
|
|
if result == nil {
|
|
|
|
|
return &types.ThreadsPostPerformanceData{Posts: []types.ThreadsPostPerformanceItem{}}
|
|
|
|
|
}
|
|
|
|
|
caps := make([]types.ThreadsPostMetricCapability, 0, len(result.Capabilities))
|
|
|
|
|
for _, item := range result.Capabilities {
|
|
|
|
|
caps = append(caps, types.ThreadsPostMetricCapability{
|
|
|
|
|
Scope: item.Scope, Name: item.Name, Metrics: item.Metrics,
|
|
|
|
|
Trackable: item.Trackable, Note: item.Note,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
metrics := make([]types.ThreadsInsightMetricItem, 0, len(result.AccountInsights.Metrics))
|
|
|
|
|
for _, item := range result.AccountInsights.Metrics {
|
|
|
|
|
metrics = append(metrics, types.ThreadsInsightMetricItem{Name: item.Name, Value: item.Value})
|
|
|
|
|
}
|
|
|
|
|
posts := make([]types.ThreadsPostPerformanceItem, 0, len(result.Posts))
|
|
|
|
|
for _, item := range result.Posts {
|
|
|
|
|
posts = append(posts, types.ThreadsPostPerformanceItem{
|
|
|
|
|
MediaID: item.MediaID, Text: item.Text, Permalink: item.Permalink, Timestamp: item.Timestamp,
|
2026-07-01 08:42:51 +00:00
|
|
|
MediaType: item.MediaType, MediaURL: item.MediaURL, ThumbnailURL: item.ThumbnailURL,
|
|
|
|
|
TopicTag: item.TopicTag, Shortcode: item.Shortcode,
|
2026-06-30 07:09:44 +00:00
|
|
|
LikeCount: item.LikeCount, ReplyCount: item.ReplyCount, RepostCount: item.RepostCount, QuoteCount: item.QuoteCount,
|
|
|
|
|
Views: item.Views, Shares: item.Shares, InsightsStatus: item.InsightsStatus, InsightsMessage: item.InsightsMessage,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return &types.ThreadsPostPerformanceData{
|
|
|
|
|
AccountID: result.AccountID, Username: result.Username, ThreadsUserID: result.ThreadsUserID,
|
|
|
|
|
FetchedAt: result.FetchedAt, Capabilities: caps,
|
|
|
|
|
AccountInsights: types.ThreadsAccountInsightsBlock{
|
|
|
|
|
Status: result.AccountInsights.Status, Message: result.AccountInsights.Message, Metrics: metrics,
|
|
|
|
|
},
|
|
|
|
|
Posts: posts,
|
2026-06-26 08:37:04 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func toListData(result *domusecase.ListResult) *types.ListThreadsAccountsData {
|
|
|
|
|
if result == nil {
|
|
|
|
|
return &types.ListThreadsAccountsData{List: []types.ThreadsAccountData{}}
|
|
|
|
|
}
|
|
|
|
|
list := make([]types.ThreadsAccountData, 0, len(result.List))
|
|
|
|
|
for _, item := range result.List {
|
|
|
|
|
list = append(list, toThreadsAccountData(item))
|
|
|
|
|
}
|
|
|
|
|
return &types.ListThreadsAccountsData{
|
|
|
|
|
List: list,
|
|
|
|
|
ActiveAccountID: result.ActiveAccountID,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func toConnectionData(data *domusecase.ConnectionData) *types.ThreadsAccountConnectionData {
|
|
|
|
|
if data == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
return &types.ThreadsAccountConnectionData{
|
|
|
|
|
AccountID: data.AccountID,
|
|
|
|
|
AccountName: data.AccountName,
|
|
|
|
|
Username: data.Username,
|
|
|
|
|
BrowserConnected: data.BrowserConnected,
|
|
|
|
|
ApiConnected: data.ApiConnected,
|
|
|
|
|
Prefs: types.ThreadsAccountConnectionPrefs{
|
|
|
|
|
SearchViaApi: data.Prefs.SearchViaApi,
|
|
|
|
|
SearchSourceMode: data.Prefs.SearchSourceMode,
|
|
|
|
|
PublishViaApi: data.Prefs.PublishViaApi,
|
|
|
|
|
DevMode: data.Prefs.DevMode,
|
|
|
|
|
ScrapeReplies: data.Prefs.ScrapeReplies,
|
|
|
|
|
RepliesPerPost: data.Prefs.RepliesPerPost,
|
|
|
|
|
PublishHeaded: data.Prefs.PublishHeaded,
|
|
|
|
|
PlaywrightDebug: data.Prefs.PlaywrightDebug,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-30 07:09:44 +00:00
|
|
|
func toPublishQueueItemData(item *pqdomain.QueueItemSummary) *types.PublishQueueItemData {
|
|
|
|
|
if item == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
return &types.PublishQueueItemData{
|
2026-06-30 09:10:23 +00:00
|
|
|
ID: item.ID,
|
|
|
|
|
AccountID: item.AccountID,
|
|
|
|
|
PersonaID: item.PersonaID,
|
|
|
|
|
CopyMissionID: item.CopyMissionID,
|
|
|
|
|
CopyDraftID: item.CopyDraftID,
|
|
|
|
|
Text: item.Text,
|
|
|
|
|
ScheduledAt: item.ScheduledAt,
|
|
|
|
|
Status: item.Status,
|
|
|
|
|
MediaID: item.MediaID,
|
|
|
|
|
Permalink: item.Permalink,
|
|
|
|
|
PublishedAt: item.PublishedAt,
|
|
|
|
|
ErrorMessage: item.ErrorMessage,
|
|
|
|
|
RetryCount: item.RetryCount,
|
|
|
|
|
LastAttemptAt: item.LastAttemptAt,
|
|
|
|
|
NextAttemptAt: item.NextAttemptAt,
|
|
|
|
|
MissedAt: item.MissedAt,
|
|
|
|
|
PausedReason: item.PausedReason,
|
|
|
|
|
CreateAt: item.CreateAt,
|
|
|
|
|
UpdateAt: item.UpdateAt,
|
2026-06-30 07:09:44 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-30 09:10:23 +00:00
|
|
|
func toInventoryPolicyData(item *inventorydomain.PolicySummary) *types.PublishInventoryPolicyData {
|
|
|
|
|
if item == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
slots := make([]types.PublishSlotData, 0, len(item.Slots))
|
|
|
|
|
for _, slot := range item.Slots {
|
|
|
|
|
slots = append(slots, types.PublishSlotData{Weekday: slot.Weekday, Time: slot.Time})
|
|
|
|
|
}
|
|
|
|
|
refs := make([]types.PublishSourceRefData, 0, len(item.SourceRefs))
|
|
|
|
|
for _, ref := range item.SourceRefs {
|
|
|
|
|
refs = append(refs, types.PublishSourceRefData{
|
|
|
|
|
Type: ref.Type, PersonaID: ref.PersonaID, CopyMissionID: ref.CopyMissionID, ScanPostID: ref.ScanPostID, ManualSeed: ref.ManualSeed,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return &types.PublishInventoryPolicyData{
|
|
|
|
|
AccountID: item.AccountID, Enabled: item.Enabled, TargetDailyCount: item.TargetDailyCount,
|
|
|
|
|
LowStockThreshold: item.LowStockThreshold, LookaheadDays: item.LookaheadDays,
|
|
|
|
|
Timezone: item.Timezone, Slots: slots, SourceRefs: refs, UpdateAt: item.UpdateAt,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func toGuardPolicyData(item *guarddomain.PolicySummary) *types.PublishGuardPolicyData {
|
|
|
|
|
if item == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
return &types.PublishGuardPolicyData{
|
|
|
|
|
AccountID: item.AccountID, Enabled: item.Enabled, MaxDailyPosts: item.MaxDailyPosts,
|
|
|
|
|
MinIntervalMinutes: item.MinIntervalMinutes, ConsecutiveFailurePauseLimit: item.ConsecutiveFailurePauseLimit,
|
|
|
|
|
Paused: item.Paused, PausedReason: item.PausedReason, UpdateAt: item.UpdateAt,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func toQueueEventData(items []eventdomain.EventSummary) []types.PublishQueueEventData {
|
|
|
|
|
out := make([]types.PublishQueueEventData, 0, len(items))
|
|
|
|
|
for _, item := range items {
|
|
|
|
|
out = append(out, types.PublishQueueEventData{
|
|
|
|
|
ID: item.ID, QueueID: item.QueueID, EventType: item.EventType,
|
|
|
|
|
FromStatus: item.FromStatus, ToStatus: item.ToStatus, Message: item.Message, CreateAt: item.CreateAt,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func firstNonEmpty(values ...string) string {
|
|
|
|
|
for _, value := range values {
|
|
|
|
|
if value != "" {
|
|
|
|
|
return value
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-30 07:09:44 +00:00
|
|
|
func toPublishQueueListData(result *pqdomain.ListResult) *types.PublishQueueListData {
|
|
|
|
|
if result == nil {
|
|
|
|
|
return &types.PublishQueueListData{List: []types.PublishQueueItemData{}}
|
|
|
|
|
}
|
|
|
|
|
list := make([]types.PublishQueueItemData, 0, len(result.Items))
|
|
|
|
|
for _, item := range result.Items {
|
|
|
|
|
if data := toPublishQueueItemData(&item); data != nil {
|
|
|
|
|
list = append(list, *data)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return &types.PublishQueueListData{
|
|
|
|
|
Pagination: types.PaginationData{
|
|
|
|
|
Total: result.Total,
|
|
|
|
|
Page: int64(result.Page),
|
|
|
|
|
PageSize: int64(result.PageSize),
|
|
|
|
|
TotalPages: int64(result.TotalPages),
|
|
|
|
|
},
|
|
|
|
|
List: list,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func toPublishHealthData(result *pqdomain.PublishHealthResult) *types.ThreadsPublishHealthData {
|
|
|
|
|
if result == nil {
|
|
|
|
|
return &types.ThreadsPublishHealthData{List: []types.ThreadsPublishHealthItem{}}
|
|
|
|
|
}
|
|
|
|
|
list := make([]types.ThreadsPublishHealthItem, 0, len(result.Items))
|
|
|
|
|
for _, item := range result.Items {
|
|
|
|
|
cps := make([]types.ThreadsPublishHealthCheckpoint, 0, len(item.Checkpoints))
|
|
|
|
|
for _, cp := range item.Checkpoints {
|
|
|
|
|
cps = append(cps, types.ThreadsPublishHealthCheckpoint{
|
|
|
|
|
Checkpoint: cp.Checkpoint, LikeCount: cp.LikeCount, ReplyCount: cp.ReplyCount,
|
|
|
|
|
RepostCount: cp.RepostCount, QuoteCount: cp.QuoteCount, Views: cp.Views, CheckedAt: cp.CheckedAt,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
list = append(list, types.ThreadsPublishHealthItem{
|
|
|
|
|
MediaID: item.MediaID, Permalink: item.Permalink, Text: item.Text,
|
|
|
|
|
PublishedAt: item.PublishedAt, LikeCount: item.LikeCount, ReplyCount: item.ReplyCount,
|
|
|
|
|
RepostCount: item.RepostCount, QuoteCount: item.QuoteCount,
|
|
|
|
|
Views: item.Views, Shares: item.Shares, Checkpoints: cps,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return &types.ThreadsPublishHealthData{
|
|
|
|
|
Summary: types.ThreadsPublishHealthSummary{
|
|
|
|
|
PendingScheduled: result.Summary.PendingScheduled,
|
|
|
|
|
FailedCount: result.Summary.FailedCount,
|
|
|
|
|
Published7d: result.Summary.Published7d,
|
|
|
|
|
TotalLikes7d: result.Summary.TotalLikes7d,
|
|
|
|
|
TotalReplies7d: result.Summary.TotalReplies7d,
|
|
|
|
|
},
|
|
|
|
|
Pagination: types.PaginationData{
|
|
|
|
|
Total: result.Total, Page: int64(result.Page), PageSize: int64(result.PageSize), TotalPages: int64(result.TotalPages),
|
|
|
|
|
},
|
|
|
|
|
List: list,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-26 08:37:04 +00:00
|
|
|
func toConnectionPatch(req *types.UpdateThreadsAccountConnectionReq) domusecase.ConnectionPrefsPatch {
|
|
|
|
|
if req == nil {
|
|
|
|
|
return domusecase.ConnectionPrefsPatch{}
|
|
|
|
|
}
|
|
|
|
|
return domusecase.ConnectionPrefsPatch{
|
|
|
|
|
SearchViaApi: req.SearchViaApi,
|
|
|
|
|
SearchSourceMode: req.SearchSourceMode,
|
|
|
|
|
PublishViaApi: req.PublishViaApi,
|
|
|
|
|
DevMode: req.DevMode,
|
|
|
|
|
ScrapeReplies: req.ScrapeReplies,
|
|
|
|
|
RepliesPerPost: req.RepliesPerPost,
|
|
|
|
|
PublishHeaded: req.PublishHeaded,
|
|
|
|
|
PlaywrightDebug: req.PlaywrightDebug,
|
|
|
|
|
}
|
|
|
|
|
}
|