thread-master/internal/library/placement/discover_route.go

114 lines
3.4 KiB
Go
Raw Normal View History

2026-06-26 08:37:04 +00:00
package placement
import (
"context"
"fmt"
)
// ShouldTryCrawlerFirst reports whether discover should attempt Playwright before Threads API
// to minimize official API calls when a browser session is available.
func ShouldTryCrawlerFirst(m MemberContext) bool {
if !m.AllowsCrawler || !m.BrowserConnected || m.CrawlerBlocked() {
return false
}
switch m.SearchSourceMode {
case SearchSourceCrawler, SearchSourceThreadsCrawler, SearchSourceBraveCrawler:
return true
case SearchSourceMixed, SearchSourceThreadsBrave:
return true
default:
return false
}
}
// CrawlerBlocked returns true when the mode is API-only or web-search-only.
func (m MemberContext) CrawlerBlocked() bool {
switch m.SearchSourceMode {
case SearchSourceThreads, SearchSourceBrave:
return true
default:
return false
}
}
// CrawlerFallbackAllowed returns true when crawler may be used after API/web search fails.
func (m MemberContext) CrawlerFallbackAllowed() bool {
if !m.AllowsCrawler || !m.BrowserConnected {
return false
}
switch m.SearchSourceMode {
case SearchSourceThreadsCrawler, SearchSourceBraveCrawler, SearchSourceMixed:
return true
default:
return false
}
}
// HasDiscoverPath reports whether at least one discover backend is configured and connected.
func (m MemberContext) HasDiscoverPath() bool {
if m.AllowsCrawler && m.BrowserConnected {
return true
}
if m.AllowsThreadsAPI && m.ApiConnected {
return true
}
if m.AllowsBrave && m.WebSearchAPIKey() != "" {
return true
}
return false
}
// DiscoverPathLabel summarizes the active routing for job progress UI.
func (m MemberContext) DiscoverPathLabel() string {
if ShouldTryCrawlerFirst(m) {
if m.AllowsThreadsAPI && m.ApiConnected {
return "爬蟲優先(不足再 API"
}
return "爬蟲"
}
if m.SearchSourceMode == SearchSourceCrawler {
return "爬蟲"
}
if m.AllowsThreadsAPI && m.ApiConnected {
return "Threads API"
}
if m.AllowsBrave {
return m.WebSearchProviderLabel()
}
return string(m.SearchSourceMode)
}
func discoverMissingPathError(m MemberContext) error {
switch m.SearchSourceMode {
case SearchSourceCrawler:
return fmt.Errorf("請先同步 Chrome Session 以使用爬蟲搜尋")
case SearchSourceThreadsCrawler, SearchSourceBraveCrawler:
if !m.BrowserConnected && !m.ApiConnected && m.WebSearchAPIKey() == "" {
return fmt.Errorf("請同步 Chrome Session 或完成 Threads API / Web Search 連線")
}
if !m.BrowserConnected {
return fmt.Errorf("爬蟲優先模式建議先同步 Chrome Session亦可改用僅 Threads API")
}
return fmt.Errorf("請完成 Threads API 或 Web Search 連線作為備援")
case SearchSourceThreads, SearchSourceThreadsBrave:
return fmt.Errorf("請先完成 Threads API 連線")
case SearchSourceBrave:
return fmt.Errorf("請在設定頁設定 %s Search API key", m.WebSearchProviderLabel())
case SearchSourceMixed:
return fmt.Errorf("請同步 Chrome Session、完成 Threads API 或設定 Web Search API key 至少一項")
default:
return fmt.Errorf("目前搜尋來源模式無可用管道:%s", m.SearchSourceMode)
}
}
func runCrawlerDiscover(ctx context.Context, req DiscoverRequest) ([]DiscoverPost, error) {
if req.Crawler == nil {
return nil, fmt.Errorf("crawler search not configured")
}
keyword := CrawlerKeywordFromQuery(req.Query, req.Keyword)
if keyword == "" {
return nil, fmt.Errorf("crawler keyword is empty")
}
return req.Crawler(ctx, req.Member, keyword, req.Limit)
}