package domain import ( "errors" "time" ) var ( ErrNotFound = errors.New("studio not found") ErrForbidden = errors.New("studio forbidden") ErrValidation = errors.New("studio validation") ErrNoAccount = errors.New("no usable threads account") ErrIllegalStatus = errors.New("illegal outbox status") ErrNotDue = errors.New("step not due") ErrRootBlocked = errors.New("root not published; reply blocked") ErrBadURL = errors.New("invalid external url") ErrFormulaRemove = errors.New("generateFromFormula removed") ) const ( PersonaEmpty = "empty" PersonaAnalyzing = "analyzing" PersonaReady = "ready" PlayDraft = "draft" PlayReady = "ready" PlayScheduling = "scheduling" PlayActive = "active" PlayCompleted = "completed" PlayPartial = "partial_failed" PlayArchived = "archived" StepRoot = "root" StepReply = "reply" OBScheduling = "scheduling" OBActive = "active" OBCompleted = "completed" OBPartial = "partial_failed" OBCancelled = "cancelled" StepDraft = "draft" StepScheduled = "scheduled" StepPublishing = "publishing" StepPublished = "published" StepFailed = "failed" StepCancelled = "cancelled" StepBlocked = "blocked" MentionPending = "pending" MentionReplied = "replied" MentionSkipped = "skipped" ) func NowNano() int64 { return time.Now().UTC().UnixNano() } // Persona — PE domain type Persona struct { ID string `bson:"_id" json:"id"` OwnerUID int64 `bson:"owner_uid" json:"owner_uid"` Name string `bson:"name" json:"name"` Brief string `bson:"brief" json:"brief"` Status string `bson:"status" json:"status"` Style PersonaStyle `bson:"style" json:"style"` Guard PersonaGuard `bson:"guard" json:"guard"` Voice string `bson:"voice,omitempty" json:"voice,omitempty"` Notes string `bson:"notes,omitempty" json:"notes,omitempty"` CreatedAt int64 `bson:"created_at" json:"created_at"` UpdatedAt int64 `bson:"updated_at" json:"updated_at"` } type PersonaStyle struct { Draft PersonaDraft `bson:"draft" json:"draft"` DraftText string `bson:"draft_text" json:"draft_text"` Source string `bson:"source" json:"source"` // manual|benchmark|seed BenchmarkUsername string `bson:"benchmark_username,omitempty" json:"benchmark_username,omitempty"` SourceLabel string `bson:"source_label,omitempty" json:"source_label,omitempty"` SampleCount int `bson:"sample_count" json:"sample_count"` AnalyzedAt int64 `bson:"analyzed_at,omitempty" json:"analyzed_at,omitempty"` SamplePreviews []string `bson:"sample_previews,omitempty" json:"sample_previews,omitempty"` // Dimensions — 8D 風格摘要(分析後必填,前端「分析」頁卡片) Dimensions map[string]StyleDimension `bson:"dimensions,omitempty" json:"dimensions,omitempty"` } // StyleDimension — 單一 8D 維度 type StyleDimension struct { Summary string `bson:"summary" json:"summary"` Evidence []string `bson:"evidence,omitempty" json:"evidence,omitempty"` } type PersonaDraft struct { Identity string `bson:"identity" json:"identity"` Tone string `bson:"tone" json:"tone"` Audience string `bson:"audience" json:"audience"` Hooks string `bson:"hooks" json:"hooks"` LanguageFingerprint string `bson:"language_fingerprint" json:"language_fingerprint"` Rhythm string `bson:"rhythm" json:"rhythm"` Punctuation string `bson:"punctuation" json:"punctuation"` ContentPatterns string `bson:"content_patterns" json:"content_patterns"` KnowledgeTranslation string `bson:"knowledge_translation" json:"knowledge_translation"` CtaStyle string `bson:"cta_style" json:"cta_style"` Examples string `bson:"examples" json:"examples"` Avoid string `bson:"avoid" json:"avoid"` } type PersonaGuard struct { Avoid []string `bson:"avoid" json:"avoid"` MaxChars int `bson:"max_chars" json:"max_chars"` BanAiTone bool `bson:"ban_ai_tone" json:"ban_ai_tone"` } // ActivePersona stores per-owner active id type ActivePersona struct { OwnerUID int64 `bson:"_id" json:"owner_uid"` PersonaID string `bson:"persona_id" json:"persona_id"` UpdatedAt int64 `bson:"updated_at" json:"updated_at"` } // Play — PL domain type ExternalTarget struct { URL string `bson:"url" json:"url"` RawURL string `bson:"raw_url,omitempty" json:"raw_url,omitempty"` Shortcode string `bson:"shortcode,omitempty" json:"shortcode,omitempty"` AuthorUsername string `bson:"author_username,omitempty" json:"author_username,omitempty"` TextPreview string `bson:"text_preview,omitempty" json:"text_preview,omitempty"` MediaID string `bson:"media_id,omitempty" json:"media_id,omitempty"` ResolvedAt int64 `bson:"resolved_at,omitempty" json:"resolved_at,omitempty"` } type PlayStep struct { ID string `bson:"id" json:"id"` SortOrder int `bson:"sort_order" json:"sort_order"` Kind string `bson:"kind" json:"kind"` AccountID string `bson:"account_id" json:"account_id"` Text string `bson:"text" json:"text"` DelayFromPreviousSec int `bson:"delay_from_previous_sec" json:"delay_from_previous_sec"` PersonaID string `bson:"persona_id,omitempty" json:"persona_id,omitempty"` BrandID string `bson:"brand_id,omitempty" json:"brand_id,omitempty"` ImageURLs []string `bson:"image_urls,omitempty" json:"image_urls,omitempty"` } type Play struct { ID string `bson:"_id" json:"id"` OwnerUID int64 `bson:"owner_uid" json:"owner_uid"` Title string `bson:"title" json:"title"` Topic string `bson:"topic" json:"topic"` Status string `bson:"status" json:"status"` LeadAccountID string `bson:"lead_account_id" json:"lead_account_id"` CastAccountIDs []string `bson:"cast_account_ids" json:"cast_account_ids"` TargetOwnPostID string `bson:"target_own_post_id,omitempty" json:"target_own_post_id,omitempty"` TargetExternal *ExternalTarget `bson:"target_external,omitempty" json:"target_external,omitempty"` Steps []PlayStep `bson:"steps" json:"steps"` ScheduleStartAt int64 `bson:"schedule_start_at" json:"schedule_start_at"` IntervalMinutes int `bson:"interval_minutes,omitempty" json:"interval_minutes,omitempty"` CreatedAt int64 `bson:"created_at" json:"created_at"` UpdatedAt int64 `bson:"updated_at" json:"updated_at"` } // Outbox type OutboxStep struct { ID string `bson:"id" json:"id"` StepID string `bson:"step_id" json:"step_id"` SortOrder int `bson:"sort_order" json:"sort_order"` Kind string `bson:"kind" json:"kind"` AccountID string `bson:"account_id" json:"account_id"` Text string `bson:"text" json:"text"` // TopicTag — 主貼可用的 Threads 話題標籤 TopicTag string `bson:"topic_tag,omitempty" json:"topic_tag,omitempty"` // ImageURLs — 公開 https 圖網址(Meta image_url 可抓;勿存 data URL) ImageURLs []string `bson:"image_urls,omitempty" json:"image_urls,omitempty"` Status string `bson:"status" json:"status"` Error string `bson:"error,omitempty" json:"error,omitempty"` ScheduledAt int64 `bson:"scheduled_at,omitempty" json:"scheduled_at,omitempty"` PublishedAt int64 `bson:"published_at,omitempty" json:"published_at,omitempty"` // MediaID after successful publish (for reply chain) MediaID string `bson:"media_id,omitempty" json:"media_id,omitempty"` // ReplyTo is parent media id (external or previous root) ReplyTo string `bson:"reply_to,omitempty" json:"reply_to,omitempty"` } type OutboxBundle struct { ID string `bson:"_id" json:"id"` OwnerUID int64 `bson:"owner_uid" json:"owner_uid"` PlayID string `bson:"play_id" json:"play_id"` Title string `bson:"title" json:"title"` Status string `bson:"status" json:"status"` Steps []OutboxStep `bson:"steps" json:"steps"` // Root reply target for under-post schemes ReplyToMediaID string `bson:"reply_to_media_id,omitempty" json:"reply_to_media_id,omitempty"` CreatedAt int64 `bson:"created_at" json:"created_at"` UpdatedAt int64 `bson:"updated_at" json:"updated_at"` } // OwnPost / Mention type OwnPostReply struct { ID string `bson:"id" json:"id"` Username string `bson:"username" json:"username"` Text string `bson:"text" json:"text"` CreatedAt int64 `bson:"created_at" json:"created_at"` LikeCount int `bson:"like_count,omitempty" json:"like_count,omitempty"` ReplyStatus string `bson:"reply_status,omitempty" json:"reply_status,omitempty"` RepliedBy string `bson:"replied_by,omitempty" json:"replied_by,omitempty"` RepliedAt int64 `bson:"replied_at,omitempty" json:"replied_at,omitempty"` ParentReplyID string `bson:"parent_reply_id,omitempty" json:"parent_reply_id,omitempty"` IsMine bool `bson:"is_mine,omitempty" json:"is_mine,omitempty"` } type OwnPost struct { ID string `bson:"_id" json:"id"` OwnerUID int64 `bson:"owner_uid" json:"owner_uid"` AccountID string `bson:"account_id" json:"account_id"` MediaID string `bson:"media_id,omitempty" json:"media_id,omitempty"` Text string `bson:"text" json:"text"` MediaType string `bson:"media_type" json:"media_type"` MediaURL string `bson:"media_url,omitempty" json:"media_url,omitempty"` ThumbnailURL string `bson:"thumbnail_url,omitempty" json:"thumbnail_url,omitempty"` Permalink string `bson:"permalink,omitempty" json:"permalink,omitempty"` Shortcode string `bson:"shortcode,omitempty" json:"shortcode,omitempty"` TopicTag string `bson:"topic_tag,omitempty" json:"topic_tag,omitempty"` LikeCount int `bson:"like_count" json:"like_count"` ReplyCount int `bson:"reply_count" json:"reply_count"` RepostCount int `bson:"repost_count" json:"repost_count"` QuoteCount int `bson:"quote_count" json:"quote_count"` ViewCount int `bson:"view_count" json:"view_count"` ShareCount int `bson:"share_count" json:"share_count"` InsightsStatus string `bson:"insights_status,omitempty" json:"insights_status,omitempty"` FormulaSummary string `bson:"formula_summary,omitempty" json:"formula_summary,omitempty"` Insight string `bson:"insight,omitempty" json:"insight,omitempty"` FormulaDetail string `bson:"formula_detail,omitempty" json:"formula_detail,omitempty"` Replies []OwnPostReply `bson:"replies" json:"replies"` PublishedAt int64 `bson:"published_at" json:"published_at"` } type Mention struct { ID string `bson:"_id" json:"id"` OwnerUID int64 `bson:"owner_uid" json:"owner_uid"` AccountID string `bson:"account_id" json:"account_id"` MediaID string `bson:"media_id,omitempty" json:"media_id,omitempty"` // Threads media id(回覆 reply_to) FromUsername string `bson:"from_username" json:"from_username"` Text string `bson:"text" json:"text"` ContextSnippet string `bson:"context_snippet" json:"context_snippet"` Permalink string `bson:"permalink,omitempty" json:"permalink,omitempty"` RootPostID string `bson:"root_post_id,omitempty" json:"root_post_id,omitempty"` ParentID string `bson:"parent_id,omitempty" json:"parent_id,omitempty"` Status string `bson:"status" json:"status"` DraftText string `bson:"draft_text,omitempty" json:"draft_text,omitempty"` CreatedAt int64 `bson:"created_at" json:"created_at"` } type SyncMeta struct { OwnerUID int64 `bson:"_id" json:"owner_uid"` LastSyncedAt int64 `bson:"last_synced_at" json:"last_synced_at"` } type ViralAnalysis struct { Hooks string `json:"hooks"` Structure string `json:"structure"` Emotion string `json:"emotion"` Summary string `json:"summary"` CTA string `json:"cta,omitempty"` Copyable string `json:"copyable,omitempty"` // 可複製要點 Risks string `json:"risks,omitempty"` // 風險/禁忌 } // PersonaPreview — 試產主貼 + 回文(真 LLM + 可選新聞話題) type PersonaPreview struct { Topic string `json:"topic"` TopicSource string `json:"topic_source"` // news | manual | fallback PostText string `json:"post_text"` ReplyText string `json:"reply_text"` Notes string `json:"notes,omitempty"` } // PublishRequest for transport (must be called for real publish). type PublishRequest struct { AccessToken string AccountID string Text string ReplyTo string // empty = root post // TopicTag — Threads 話題標籤(API topic_tag;1~50 字,不可含 . &) TopicTag string // ImageURLs — 公開可抓的 http(s) 圖;空=純文字 TEXT ImageURLs []string } type PublishResult struct { MediaID string }