haixunMaster/haixun-backend/internal/library/placement/discover.go

82 lines
2.2 KiB
Go
Raw Normal View History

2026-06-24 10:02:42 +00:00
package placement
import (
"context"
"fmt"
)
// DiscoverChannel identifies which backend fulfilled a placement discover query.
type DiscoverChannel string
const (
DiscoverThreadsAPI DiscoverChannel = "threads_api"
DiscoverBrave DiscoverChannel = "brave"
DiscoverCrawler DiscoverChannel = "crawler"
)
// DiscoverRequest is used by scan jobs; expand-graph only uses Brave knowledge_expand.
type DiscoverRequest struct {
Query string
Keyword string // plain tag for crawler; optional
Recency bool
Limit int
Member MemberContext
Crawler CrawlerSearchFn
}
type DiscoverPost struct {
Text string
Permalink string
ExternalID string
Author string
PostedAt string
LikeCount int
ReplyCount int
Source DiscoverChannel
}
// Discover runs keyword discovery respecting the member's connection prefs.
// Formal mode (dev_mode=false) never falls back to crawler.
func Discover(ctx context.Context, req DiscoverRequest) ([]DiscoverPost, DiscoverChannel, error) {
m := req.Member
if m.DevMode {
if !m.BrowserConnected {
return nil, "", fmt.Errorf("開發模式需先同步 Chrome Session")
}
if req.Crawler == nil {
return nil, DiscoverCrawler, fmt.Errorf("crawler search not configured")
}
keyword := CrawlerKeywordFromQuery(req.Query, req.Keyword)
if keyword == "" {
return nil, DiscoverCrawler, fmt.Errorf("crawler keyword is empty")
}
posts, err := req.Crawler(ctx, m, keyword, req.Limit)
if err != nil {
return nil, DiscoverCrawler, err
}
return posts, DiscoverCrawler, nil
}
if m.AllowsThreadsAPI {
if !m.ApiConnected {
return nil, "", fmt.Errorf("正式模式需先完成 Threads API 連線")
}
posts, err := keywordSearchViaThreadsAPI(ctx, req)
if err == nil && len(posts) > 0 {
return posts, DiscoverThreadsAPI, nil
}
if err != nil && !m.AllowsBrave {
return nil, "", err
}
}
if m.AllowsBrave {
if m.BraveAPIKey == "" {
return nil, "", fmt.Errorf("請在設定頁設定 Brave Search API key跟隨此登入帳號")
}
return nil, DiscoverBrave, fmt.Errorf("brave threads discover delegated to worker")
}
return nil, "", fmt.Errorf("目前搜尋來源模式無可用管道:%s", m.SearchSourceMode)
}