thread-master/apps/backend/internal/module/studio/domain/domain.go

289 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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_tag150 字,不可含 . &
TopicTag string
// ImageURLs — 公開可抓的 http(s) 圖;空=純文字 TEXT
ImageURLs []string
}
type PublishResult struct {
MediaID string
}