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

109 lines
3.0 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 placement
import (
"context"
"fmt"
)
// DiscoverChannel identifies which backend fulfilled a placement discover query.
type DiscoverChannel string
const (
DiscoverThreadsAPI DiscoverChannel = "threads_api"
DiscoverBrave DiscoverChannel = "brave"
DiscoverExa DiscoverChannel = "exa"
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
AuthorVerified bool
FollowerCount int
LikeCount int
ReplyCount int
Source DiscoverChannel
}
// Discover runs keyword discovery respecting search_source_mode and available connections.
// Crawler-first modes skip Threads API when the browser session returns posts (saves API quota).
func Discover(ctx context.Context, req DiscoverRequest) ([]DiscoverPost, DiscoverChannel, error) {
m := req.Member
if !m.HasDiscoverPath() {
return nil, "", discoverMissingPathError(m)
}
if ShouldTryCrawlerFirst(m) {
posts, err := runCrawlerDiscover(ctx, req)
if err == nil && len(posts) > 0 {
return posts, DiscoverCrawler, nil
}
if m.SearchSourceMode == SearchSourceCrawler {
if err != nil {
return nil, DiscoverCrawler, err
}
return posts, DiscoverCrawler, nil
}
}
if m.AllowsThreadsAPI {
if !m.ApiConnected {
if !m.CrawlerFallbackAllowed() {
return nil, "", fmt.Errorf("正式模式需先完成 Threads API 連線")
}
} else {
posts, err := keywordSearchViaThreadsAPI(ctx, req)
if err == nil && len(posts) > 0 {
return posts, DiscoverThreadsAPI, nil
}
if err != nil {
if m.CrawlerFallbackAllowed() {
cPosts, cErr := runCrawlerDiscover(ctx, req)
if cErr == nil && len(cPosts) > 0 {
return cPosts, DiscoverCrawler, nil
}
}
if !m.AllowsBrave && !m.CrawlerFallbackAllowed() {
// Optional API field gaps must not fail the whole patrol; return empty for this keyword.
return []DiscoverPost{}, DiscoverThreadsAPI, nil
}
}
}
}
if m.AllowsBrave {
if m.WebSearchAPIKey() == "" {
if m.CrawlerFallbackAllowed() {
posts, err := runCrawlerDiscover(ctx, req)
if err == nil {
return posts, DiscoverCrawler, nil
}
}
return nil, "", fmt.Errorf("請在設定頁設定 %s Search API key跟隨此登入帳號", m.WebSearchProviderLabel())
}
return nil, m.WebSearchDiscoverChannel(), fmt.Errorf("web search threads discover delegated to worker")
}
if m.CrawlerFallbackAllowed() {
posts, err := runCrawlerDiscover(ctx, req)
if err != nil {
return nil, DiscoverCrawler, err
}
return posts, DiscoverCrawler, nil
}
return nil, "", fmt.Errorf("目前搜尋來源模式無可用管道:%s", m.SearchSourceMode)
}